The bug was never in the firmware
The hardest bugs in connected hardware live in the seams between disciplines — where the firmware engineer blames the board and the board engineer blames the code. Here's how I work the seam.
- Published
- Reading time
- 3 min read
- Topics
- Debugging · Hardware · Firmware
The most expensive bugs I've chased weren't really in any one layer. They lived in the gap between two of them — and the gap is exactly where a team organized by discipline stops looking.
You know the shape of it. The firmware engineer is certain the code is correct, because in isolation it is. The hardware engineer is certain the board is correct, because the schematic checks out and it powered up fine. Both are right about their own layer. The bug is in the handshake between them, and it's nobody's job to own the handshake.
A representative failure
Here's a pattern I've hit in more than one form. A device works perfectly on the bench and fails intermittently in the field. The firmware logs look clean. The hardware passes every standalone test. It only misbehaves under conditions you can't easily reproduce at your desk.
The reflex is to instrument the firmware harder. More logging, more asserts, a debugger hanging off the JTAG. And you find nothing, because the firmware is doing precisely what it was told — it's the world underneath it that changed.
The usual culprits live below the abstraction line that firmware pretends is solid:
- A supply rail sags under a load case you never exercised on the bench, and a sensor browns out just long enough to return garbage that's still inside its valid range.
- A signal integrity problem on a bus that's fine at room temperature and marginal at the temperature the device actually ships into.
- A ground bounce or a missing pull-up that turns a clean digital edge into something the peripheral occasionally misreads.
None of these show up as a crash. They show up as plausible wrong data, which the firmware faithfully acts on.
How I work the seam
The thing that actually finds these is refusing to respect the layer boundary while debugging. The firmware-only view and the hardware-only view each explain why their own layer is innocent. You have to hold both at once.
In practice that means:
- Put a scope on the assumption, not the symptom. If the firmware thinks a rail is 3.3V, measure it during the failing case — don't trust that it's 3.3V because the regulator is rated for it. Most seam bugs are an assumption that's true on the bench and false in the field.
- Reproduce the environment, not just the input. Temperature, supply load, RF noise, cable length. The bug is often a function of a physical variable your bench setup holds constant by accident.
- Make the firmware suspicious of its own inputs. A reading that's electrically valid can still be physically impossible. Range checks, rate-of-change limits, and sanity gates turn a silent wrong-data failure into a loud, localizable one.
That third point is the durable fix. You can't always make the hardware perfect, but you can make the firmware honest about what it doesn't know. A controller that refuses to act on an implausible measurement degrades gracefully instead of confidently doing the wrong thing.
Why this is a positioning, not just a technique
This is the whole reason I work across the disciplines instead of staying in one. The bug is rarely in just the firmware or just the mechanism. Solving it usually means seeing all the layers at once — and being willing to put a multimeter on a board to settle an argument the code can't.
If a problem has survived a few rounds of "it's not my layer," that's the strongest signal you have. It's almost certainly in the seam. Go look there first.