For the few years I have been writing embedded firmware, there is also something else I have been doing, just like every writer does. Reading. I have been reading lots and lots of code, from firmware to platform level code, to automation scripts, back to firmware. One thing I have noticed, most developers, especially in hardware, are hell-bent on writing code for the specific machine they wanna hack such that they forget the other most important element in software, the human element. It's not wrong to write code for machines, I mean, that's the whole point of your job anyway, to make embedded controllers work, otherwise you lose trust from customers if you are not able to deliver as desired, or even lose your job if its that bad.
This post will not be about which documentation framework to use for whichever language ( though I might mention it), but a reminder to you as developer to write software with two goals in mind, To be able to run on the intended machine, and secondly to be understood by humans.
Execution intelligence
Let me digress a little and share something I have acquired over the years, and still not mastered enough. When you have a project to deliver, you have scratchpad design files, software flowcharts, expected result etc, and a timeline, keeping all other factors constant, the only thing that will come between you and delivering the project is something I call execution intelligence. How fast are you able to convert plans, ideas, strategies, goals into actual tangible and visible results?
This marries to embedded development in the following way. Most times when writing code, you always never know everything, and you have to rely on code someone else has written, maybe a library or a snippet from Stack Overflow (Excuse me vibe coders…). From my experience, the faster you can understand that piece of code, the faster you are able to solve your specific problem, the faster you are able to piece the whole system together, and its a Domino effect from there.
I like to think of it as an interweb of brains interlinked through code, such that the quality of your public code will affect someone else's execution speed and vice versa.
What is the human element
But what exactly is this human element? Simple. Your future you and any other developer for that matter. If you can write good code in a matter of minutes and it actually works without much hustle, great. I mean, I have seen guys implement algorithms from scratch in one sitting, with little to no outside help. Respect.
However, if your code is cryptic and someone else has to sit for hours to “decode” it, just to understand the code, then something significant is missing. In case of corporate or engineering firm codebases that have thousands of lines of code or files, and you have to port some function to a new product, trust me if the code is not documented, you will suffer, especially if you are not the one who wrote it.
Now, there is an argument that goes about defining what a good developer is, one who writes good, clean, simple and working code, or one who writes cryptic but working code? That’s a topic for another day. I have heard it being defined that there is no such thing as clean code, only working code. Yes and yes. I agree, but for the human element, I’d like to add this: good, clean, simple, working and documented code.
Why?
Earlier today I came across this code snippet: source(https://www.snad.cz/en/2018/12/21/uptime-and-esp8266/)
uint64_t millis64() {
static uint32_t low32, high32;
uint32_t new_low32 = millis();
if (new_low32 < low32) high32++;
low32 = new_low32;
return (uint64_t) high32 << 32 | low32;
}
Now this is a super simple piece of function. Basically its use is to allow 32bit MCUs not to overflow when measuring time in millis e.g ESP32, and extend them to more than 47 days when they overflow. You can see bit-packing applied here as the function returns a 64 bit unsigned integer. (I’ll leave you to do the math).
My point of sharing this code has nothing to do with the writer, but at first glance, how fast did it take you to understand this function?
Now, if I am to use this piece of code, I would not change a line, but I would have a whole paragraph at the beginning of the function explaining how the function does this timing overflow extension, because I am certain 4 months from this point, if I don't document this function, I will waste valuable minutes trying to decode it again. And I don't want that.
My past code
I have to admit at this point that I never used to document my code when I started. I never even knew that documentation is a thing. And I do not regret it, simply because I set myself up for future suffering, and now I can painfully advise someone to document their code. On the flipside however, when starting I was not writing very cryptic code that couldn't be understood, but as the system's complexities increased, I slowly found myself needing to just explain to whomever will be reading the code, how it works, and why that decision was made.
Explain the why, not the how
Take the following code line from one of my recent projects for example:
/**
* @brief This task reads the packet received from ESP32 and extracts the WIFI state
*
*/
void xTaskReadESPWIFI(void const *pvParameters) {
uint8_t uart1_rx_buffer[5];
for(;;) {
/* receive state from ESP32 */
HAL_StatusTypeDef status = HAL_UART_Receive(&huart1, uart1_rx_buffer, sizeof(uart1_rx_buffer), 200);
if(status == HAL_OK) {
/* todo: nul terminate */
/* extract the WIFI state */
int wifi_state = atoi((char*) uart1_rx_buffer);
HAL_UART_Transmit(&huart6, (uint8_t*)wifi_state, sizeof(wifi_state), 100);
} else {
HAL_UART_Transmit(&huart6, (uint8_t*)"Timeout\r\n", sizeof("Timeout\r\n"), 100);
}
vTaskDelay(pdMS_TO_TICKS(5)); /* prevent task starvation */
}
}
Looking at the vTaskDelay() line, the comment says prevent task starvation. This is the why. Why that delay was used, how it prevents task starvation is another story.
By doing this, you leave it to the reader to go research on their own about RTOS tasks and what task starvation is, but you have answered someone elses question, “why did he use a delay here? And why 5 milliseconds?”
Easter eggs
To conclude, I believe developing embedded systems is an art, and I therefore treat it as such. Be it a PCB, embeddded firmware etc. Personally, sometimes I write so much code that I fall in love with it.The process as well as the end result. I treat it as it should be treated, with care. Because I know if it failes, losses are going to be incurred and I’ll be in a bad situation.
Most times the code is proprietary such that the only people that are going to read/review it are the internal team members, which is fine.Other times the code might be open source. So I leave some easter eggs in the code either for myself so that years later I will kind of relieve that moment, or for the future team members to decode. I find it fun. That’s all I think life should be, fun, in the midst of all these troubles.
Below is a snippet of a simple easter egg from past firmware, flight software, I know someone has found it, but probably they have never bothered to decrypt:
/**
* @file main.cpp
* @author Edwin Mwiti
* @version N4
* @date July 15 2024
*
* @brief This file contains the main driver code for the flight computer
*
* 0x5765206D6179206D616B65206F757220706C616E73202C
* 0x62757420476F642068617320746865206C61737420776F7264
*
*/
#include <Arduino.h>
#include <Wire.h>
#include <WiFi.h>
///.....
For software that needed to work the right way at the right time, and failing was not an option, the team sure needed this encouragement. That’s a hint.
That’s it. Write for humans too, not just machines.
Cover image credits: https://commons.wikimedia.org/wiki/File:Voyager_Golden_Record_fx.png

