Flash, Reboot, Repeat: Assembling a Hardware-in-the-Loop Bench That Catches Bad Firmware Before It Leaves the Building
Photo: National Instrument, Apache License 2.0, via Wikimedia Commons
The Simulator Problem
Every embedded team eventually learns the same lesson: the simulator passed, the emulator passed, the unit tests passed — and three days after the rollout, support tickets started arriving from Phoenix, where a customer's thermostat died mid-update during a brownout. Simulation models your code. It does not model your flash controller's bad erase block, your brownout detector's sluggish response, or the race condition that only appears when the watchdog fires during an NVM write on a two-year-old board revision.
Over-the-air updates have a uniquely unforgiving property: there is no technician on site. When a web deployment fails, you roll back in ninety seconds. When an OTA deployment fails on a device bolted behind a customer's dryer, you have manufactured a paperweight. This asymmetry is the entire argument for hardware-in-the-loop (HIL) testing. If your release pipeline has never touched real silicon, it isn't a release pipeline — it's a hypothesis.
What a Minimum Viable HIL Rig Looks Like
You do not need a climate-controlled lab to start. A credible HIL bench for an embedded fleet has four components:
1. Real target hardware. Buy the boards you ship — including the oldest board revision still in the field. A drawer of discontinued SKUs is one of the most valuable test assets an embedded team can own. Field devices don't all run the same silicon stepping, and your bootloader may behave differently on an early revision.
2. A flashing and control host. A Raspberry Pi 4 or an inexpensive x86 mini-PC per bench works well. It runs the flashing tools (JTAG/SWD probes like the Segger J-Link, or USB DFU for USB-bootable parts), a lightweight agent that talks to your CI system, and logging daemons.
3. A power-cutting mechanism. This is the part teams skip and regret. Software resets are polite; field failures are not. A USB-controlled relay board (a $15 Deltario or Numato board, or a smart plug with a local API if your device is wall-powered) lets your test harness perform a genuine cold boot at arbitrary moments — including in the middle of an OTA write. If your bootloader can survive a power interruption at every offset of the firmware image, you have tested something no simulator can tell you.
4. Serial visibility. A USB hub of UART adapters, wired to each device's console, streaming into a central log store. Update failures announce themselves in boot logs long before a user notices a dead device. The rig's most important output is not pass/fail — it's the boot-time transcript from the moment things went wrong.
The Test Matrix That Matters
Resist the urge to test everything. A focused matrix catches the vast majority of OTA field failures:
- Interrupted update: Kill power at 10%, 25%, 50%, 75%, and 95% of the image write. Verify the device boots the previous image or recovers cleanly.
- Downgrade and rollback: Flash the new version, then force a rollback. Then power-cut during the rollback. Rollback paths are notoriously less-tested than forward paths.
- Battery-floor update: Use a programmable power supply to simulate a device attempting an update at 12% battery. Your update agent should refuse gracefully, not attempt it.
- Repeated updates: Ten consecutive OTA cycles on the same board. Flash wear, NVM fragmentation, and memory leaks in the update agent all surface here.
- Version-skew scenarios: Update from every firmware version still meaningfully present in your fleet — not just the last release. Migration bugs live two versions back.
Wiring It Into CI
The rig earns its keep only when it runs without a human. The pattern most teams converge on:
- CI builds the firmware artifact and posts it to a staging OTA channel — ideally the same server infrastructure that serves production, just with a different signing key.
- A job claims devices from the bench pool (a simple HTTP API over your Pi agent works; a queue in Redis works better).
- The agent subscribes the device to the staging channel, waits for the device's normal update cadence to pick it up, and monitors the serial console for the new version banner.
- The harness then runs the power-cut and rollback scenarios against the updated device.
- Results — including full boot logs — attach to the pull request or release candidate.
Two practical notes from teams that have done this at scale. First, keep the devices on the staging update cadence fast (check in every minute) but never change the cadence in the shipped binary; you want to test the polling behavior your users will actually get. Second, sign test builds with a dedicated key. The day someone accidentally ships a debug build through the production channel is the day you'll be glad the keys were separate.
Simulators Still Have a Job
None of this argues against simulation. Simulated runs are where you iterate on logic, run thousands of timing permutations, and fuzz the update protocol. The division of labor is straightforward: simulators find the bugs you can predict; hardware finds the ones you can't. A rig that runs the full matrix nightly — a few hours of wall-clock time for a dozen boards — gives you a defensible answer to the question every release review should ask: has this firmware actually booted on the hardware our customers own?
For a discipline where the cost of failure is measured in unreturnable devices and truck rolls, that question deserves better than a hypothesis. A shelf of real boards, a relay, and a cron job will get you most of the way there.