Skip to content

9. Firmware architecture and testing

Time: 3 to 4 weeks · Board: STM32 Nucleo (and your laptop) · Prerequisites: Module 8

Why this matters

Everything so far was a few hundred lines that you held in your head. Products are tens of thousands of lines written by several people over years, and the difference between a codebase that survives that and one that collapses is structure: layers with clear boundaries, state machines instead of flag soup, buffers that do not lose data, tests that run on a laptop in seconds, and a coding standard so the code looks like one person wrote it. This is the module hiring managers care about most.

You will be able to

  • Split firmware into hardware, HAL, driver, and application layers and explain what each may and may not know about.
  • Write a driver from a datasheet with a clean interface that can be tested without the hardware.
  • Implement a state machine and a ring buffer correctly, and know when to use event-driven design.
  • Unit test firmware on your laptop with test doubles for the hardware, and run those tests in CI.
  • Follow a coding standard and explain why MISRA C exists.
  • Describe how a bootloader and a firmware update work.

Learn

Architecture and patterns

Standards and quality

Testing

  • Article Embedded C/C++ Unit Testing Basics by Memfault Interrupt ~1 h
    Building firmware modules on the host with CppUTest, and faking the hardware. Follow along.
  • Tool Unity and Ceedling by ThrowTheSwitch Setup ~1 h
    The most common C unit test framework in firmware, with a mock generator (CMock). Alternative: CppUTest.
  • Video Embedded Software Testing by Phil Koopman, CMU ~1.5 h
    Test plans, coverage, and why "it worked on my desk" is not a test strategy, from a safety expert.
  • Book Paid Test-Driven Development for Embedded C by James Grenning
    The book that convinced firmware engineers TDD applies to them. Read chapters 1 to 5 if you can get it.

Shipping

Do

Build these as one growing project on the Nucleo. It becomes the base for Module 10 and your capstone.

  • Checkpoint 9.1: Layer it. Restructure your Module 8 code into folders: hw/ (register access, vendor HAL), drivers/ (UART, I2C, your sensor), app/ (the logic), and bsp/ (which pin is which on this board). The rule: app/ includes nothing from hw/. Draw the dependency diagram in your README.
  • Checkpoint 9.2: Ring buffer and interrupt-driven UART. Write a ring buffer module with a unit test that runs on your laptop and covers empty, full, wrap-around, and overflow. Then use two of them for interrupt-driven UART TX and RX on the Nucleo. Blast data at it faster than the app consumes and prove nothing is silently lost (or that overflow is detected).
  • Checkpoint 9.3: State machine. Implement a small product behavior as an explicit state machine: for example, a device that is IDLE, then SAMPLING when a button is pressed, ALERTING when the sensor crosses a threshold, and back to IDLE after a timeout. Events come from a queue; no blocking anywhere. Draw the state diagram first. Unit test the state machine on the host with fake events.
  • Checkpoint 9.4: Test the driver without the hardware. Give your sensor driver a small interface for "read register" and "write register." In the unit test, replace it with a fake that returns canned bytes, and test that the driver decodes temperature correctly, handles a NACK, and rejects a wrong chip ID. Run the tests with one command.
  • Checkpoint 9.5: CI and standards. Put the host tests in GitHub Actions so every push runs them and cross-compiles the firmware. Add a formatter configuration and apply BARR-C rules you can automate. Add a CLI with version, sensor read, and reboot commands. Tag a release with a semantic version that the firmware reports over the CLI.

Check yourself

  • Why should the application layer not include the vendor's HAL header? What does it cost you when it does?
  • Explain how a single-producer, single-consumer ring buffer can be safe between an ISR and main() without disabling interrupts. What assumption makes it work?
  • What is wrong with a switch on a state variable where each case has nested ifs on flags? What does a table-driven or hierarchical state machine buy you?
  • How do you unit test a function that writes to a hardware register?
  • A firmware update loses power halfway. Describe what a well-designed bootloader does on next boot.

Go deeper

Optional extras