Photo by ubahnverleih on Unsplash

2 Tips for Improving Your Firmware

Josh is having a very bad day at work. He is a firmware engineer btw.

“Wtf? Boss wants 2 more features added to the firmware. Heck, I didn’t even fix my bug yesterday” He complained.

“Why? Aren’t those 2 features simple to implement?” I asked him.

“Easier said than done! I need to touch maybe 20 files to do that. I’m afraid the code will break. It’s stressing me out!” He freaked out.

Here we can see the problem, well 3 problems:

• Josh is struggling to add more features
• Josh needs to touch tens of files to make small modifications
• Josh is scared and not confident his work will be stable enough

Poor Josh. This thing shouldn’t be happening. Adding features shouldn’t be scary. In this article, I’m going to share 2 tips to kill Josh’s problems. But before we go through those tips, a few important notes:

  • Improving your firmware means you need to improve your thinking. Because writing firmware is basically thinking in code

  • If you have too much hassle developing firmware, it means you need to either automate something or try another approach. Adding features should be straightforward (although not always easy, mind you)

Code Bloat → Kill with Method Extraction

Now, when Josh needed to add/change something, he had to modify his code in many locations. The code is similar (or even exactly the same), and he needed to use Ctrl+F to find everything.

This issue is called code duplication or code bloats. The solution is by extracting that similar code and create a function with appropriate parameters. For example, Josh has 2 similar code everywhere:

// First code block
uint8_t payload[255] = ....;

uint8_t checksum = 0;
for (int i = 0; i < 255; i++) {
    // Long code here..
    checksum = ....;
}
// Second code block
uint8_t payload[255] = ....;

uint8_t checksum = 0;
for (int i = 0; i < 254; i++) {
    // Long code here..
    checksum = ....;
}

Did you notice the difference between 1st vs 2nd code block?

Second block has 254, first block instead has 255. When I asked Josh, he said that the second block for some reason needs to exclude the last byte to calculate the checksum (Wtf Josh, that is dangerous!)

Now, those two blocks can be extracted and written into a single function:

uint8_t calculateChecksum(uint8_t* payload, bool excludeLastByte) {
    
    uint8_t checksum = 0;
    uint8_t payloadSize = excludeLastByte ? 254 : 255;
    // Long code here...
}

From there, we can calculate checksum simply by calling calculateChecksum(payload, true).

Insecure of Code Stability → Kill with Unit Test

We have reduced code bloats into a single function. And this gives us one more benefit: we can test it individually. By using unit test, we can ensure that the code is consistent and outputs the correct value as we expect.

Unit testing is basically calling calculateChecksum() with known payload, we compare the actual checksum with the expected value. If they match, we’re fine.

Remember that unit testing doesn’t necessarily use any testing framework like Catch2, Google Test. As long as you can verify the output, you’re doing unit tests (However, manual testing is painful as the code grows, that’s why we use testing framework, to do automated unit testing)

Summary

Use those 2 tips, and you will be better at dealing with code redundancy and feel confident in your firmware. If you want to explore more about this concept, we have plenty: Test-Driven Development (TDD), Modular programming, Assertion, etc.

Those topics are actually interconnected, so take your time to learn them all.


Whenever you're ready, there are 2 ways I can help you:

1. Professional Firmware Development Guide. If you're looking to build professional-grade firmware, I share 6+ years of expertise developing firmware. This guide shows you the exact workflow I use to build high-quality firmware for my company and my freelance clients.

2. Join other 2400+ engineers by subscribing my newsletter.