Loading header...

Testing, Debugging, and CI

Firmware quality improves when tests run before hardware is available, diagnostics explain failures on the bench, and continuous integration keeps the build reproducible. The goal is not to test everything on a PC; it is to place each check at the cheapest useful level.

Learning Objectives

By the end of this lesson, you should be able to separate unit, integration, hardware-in-loop, and production tests; design useful logs and assertions; use debuggers safely; and define a CI pipeline for embedded firmware.

Test Pyramid for Firmware

flowchart TB HIL[Hardware-in-loop tests] INT[Target integration tests] UNIT[Host unit tests] STATIC[Static analysis and formatting] STATIC --> UNIT --> INT --> HIL

Fast checks should run often. Slow hardware tests should protect the most important real-device behavior.

Unit Tests

Host unit tests are valuable for pure logic: parsers, filters, protocol framing, state machines, calibration math, ring buffers, and fault policy decisions.

#include <assert.h>
#include <stdint.h>

uint16_t adc_to_mv(uint16_t code, uint16_t vref_mv) {
    return (uint32_t)code * vref_mv / 4095u;
}

int main(void) {
    assert(adc_to_mv(0, 3300) == 0);
    assert(adc_to_mv(4095, 3300) == 3300);
    assert(adc_to_mv(2048, 3300) >= 1649);
    return 0;
}

Keep hardware access behind interfaces so logic can be tested without registers.

Integration and Hardware Tests

Target tests verify startup, clocks, interrupts, drivers, DMA, storage, communication, sleep states, and fault recovery. Hardware-in-loop tests can press relays, loop UARTs, inject sensor values, power-cycle the board, and check current draw.

A useful hardware test states setup, fixture wiring, firmware image, stimulus, expected observation, timeout, and cleanup.

Debugging Tools

Use the cheapest tool that answers the question:

  • logs for state transitions and rare field faults;
  • SWD or JTAG for halt, watchpoints, registers, and memory;
  • logic analyzer for digital timing and protocol decoding;
  • oscilloscope for edges, analog levels, reset, and power droop;
  • current probe or power analyzer for sleep and radio peaks.

Do not leave blocking debug prints in timing-critical code.

Assertions and Fault Capture

Assertions catch impossible states during development. In production, convert critical assertions into fault records that include build ID, reset reason, file or code, line or module, state, and uptime.

#define FW_ASSERT(expr, code) do { \
    if (!(expr)) fault_record_and_reset((code), __LINE__); \
} while (0)

CI Pipeline

A practical CI pipeline runs:

  1. formatting or style check;
  2. static analysis warnings as errors for selected rules;
  3. host unit tests;
  4. cross-compile for all board variants;
  5. artifact packaging with version metadata;
  6. optional emulator, simulator, or hardware rack tests;
  7. release signing only from protected branches.

Worked Example: UART Driver Regression

A previous bug dropped bytes when the RX ring buffer wrapped. Add host tests for wrap-around, overflow policy, and empty reads. Add a target test that loops TX to RX at the required baud rate. Add a CI artifact that stores the map file so RAM changes are visible in reviews.

Common Mistakes

  • Testing only with a debugger attached.
  • Mocking so much that tests no longer match real behavior.
  • No reproducible compiler version.
  • Logs without timestamps or state context.
  • CI builds one board while releases support several variants.

Practical Checks

Break the build intentionally to confirm CI fails. Run tests from a clean checkout. Verify artifacts include commit ID. Confirm hardware tests reset the board between cases. Review compiler warnings and memory map changes before release.

Summary

Good firmware testing combines fast host checks, meaningful target tests, useful diagnostics, and repeatable CI. Debugging becomes faster when the firmware records context before the failure disappears.

Further Reading

  • Ceedling, Unity, CMock, and CppUTest documentation.
  • Memfault firmware testing and debugging guides.
  • Vendor debug probe documentation for SWD, JTAG, trace, and semihosting.

Mind Map

mindmap root((Firmware testing)) Core concept Test at right level Reproduce builds Capture failure context Applications Drivers Protocol stacks State machines Release builds Metrics Warning count Test pass rate Coverage for pure logic RAM and flash size Design rules Isolate hardware access Automate clean builds Keep logs structured Store artifacts Practical checks Clean checkout CI Loopback tests Fault injection Map file review Common mistakes Debugger-only testing Weak mocks Missing version ID Ignored warnings