Firmware is the part of a hardware product where mistakes are cheapest to make and most expensive to fix. A bad plastic part costs you a mold revision. A bad architectural decision in firmware costs nothing the day you make it, then quietly taxes every week afterward — and if the wrong version ships, it can cost you the ability to fix anything at all.

The mistakes below show up on first firmware projects with striking consistency, whether the code came from a talented generalist, a contractor, or a self-taught founder. They are worth knowing even if you never write a line, because most are decisions you can ask about at a design review.

1. Shipping without a bootloader

The most expensive mistake on the list, and almost always a schedule decision rather than a technical one. The team is behind, the update mechanism is not done, and the reasoning is "we will add it in the next revision."

There is no next revision for units already in the field. If a device cannot receive new firmware, every shipped bug is permanent, every expired certificate is a dead device, and every protocol change orphans the installed base. Recovery means physical access to every unit.

Getting it right up front costs a bootloader, flash for a second image, and signature verification. That flash decision must happen before the PCB is finalized, making it a hardware conversation as much as a firmware one. See bootloaders in embedded products for how the update path is structured.

2. No version reporting and no build traceability

The support call goes: a customer's device is misbehaving. What firmware is it running? Nobody knows. Was it built from the tag or from someone's working tree? Nobody knows.

The fix belongs in the first week's work: embed a version string and commit hash in the image, report it on every boot and cloud connection, and build release images only from a clean tagged checkout in CI. Keep every released binary somewhere you can retrieve it in five years. The habits are in firmware version management in production and the field.

3. Timing built from delay calls

Early firmware is full of blocking delays because they work. Wait 100 ms for the sensor. Wait 2 seconds for the modem. Wait 50 ms after setting the pin.

Each is a hidden assumption that nothing else needs to happen in that window. They accumulate until the main loop takes an unpredictable amount of time and every timing-sensitive behavior becomes flaky in ways that are miserable to debug. On a battery product they are also directly expensive, since the core runs at full current throughout.

The alternative is a non-blocking architecture: a scheduler, an RTOS, or a state machine that advances on timer events. Decide early — it is very hard to retrofit. Tradeoffs in RTOS versus bare metal.

4. Doing real work inside interrupts

An interrupt handler that parses a packet, writes to flash, or calls a logging function will eventually cause a bug that looks like hardware failure: dropped bytes, corrupted variables, lockups that only happen under load and never on the bench.

The rule is simple and constantly violated. A handler should do the minimum — grab the byte, set a flag, push to a queue — and return. Any variable shared with the main loop needs the right qualifier and, if multi-byte, protection against being read halfway through an update. A review that specifically hunts for long handlers and unprotected shared state is one of the highest-yield hours on a first firmware project.

5. Product logic as a pile of flags

Firmware that grows organically ends up with a dozen boolean flags — is_connected, is_charging, error_pending — and conditionals testing four at a time. It works until it does not, and then nobody can tell which combination is legal. States grow as two to the power of the flag count: with ten flags you have over a thousand combinations and have tested maybe fifteen. Devices found in impossible states in the field are almost always this bug.

An explicit state machine — enumerated states, defined transitions, one place where transitions happen — makes illegal combinations unrepresentable rather than merely unlikely. See state machines in firmware.

6. Code that cannot be tested without hardware

When business logic, protocol parsing, and register writes are interleaved in the same functions, nothing can be tested except by flashing a board. Test cycles go from seconds to minutes, edge cases go untested, and regressions arrive silently.

The fix is a hardware abstraction layer thin enough to stub: keep decision-making code free of direct register access so it compiles and runs on a PC. Then a unit test can feed it a malformed packet, an out-of-range sensor value, or a clock that jumps backward — all tedious to arrange on a bench. More achievable than people expect; see unit testing embedded code.

7. Treating the prototype platform as the product platform

The proof of concept runs on a hobbyist board with a friendly library ecosystem. Six months later that board is still the plan, and now it has to pass FCC testing, survive a temperature range, and be procurable in volume. The libraries that made prototyping fast are written for convenience, not determinism or low power, and migrating late means rewriting the layer everything else was built on at the worst possible moment. What has to change is spelled out in going from Arduino prototype to production product.

8. No plan for what happens when things break

First firmware assumes the happy path. Sensors respond, buses behave, power is stable. Then the product goes to a customer site and none of that holds. A checklist worth running at design review:

  • Every bus transaction has a timeout and a defined failure behavior. An I2C bus can hang with a slave holding the line low; without a recovery routine the device is dead until power-cycled.
  • A watchdog is enabled in production builds and kicked from a place that proves the system is healthy, not a timer that runs while a task is stuck.
  • The reset reason is recorded and reported, so you learn whether field units reboot and why.
  • Power loss mid-flash-write does not corrupt configuration — use a validity marker and a fallback copy.
  • Out-of-range sensor readings are rejected, not propagated into control logic.

9. Debug hooks left in — or left out

Both directions are mistakes. A debug UART or readout-unprotected flash left enabled in production hands an attacker your firmware; removing every hook means a field failure cannot be diagnosed. Keep diagnostics, gate them behind authentication, disable the programming interface in production builds, and verify readout protection at end-of-line test. And make sure the tools exist on the bench — a project without a debug probe and a logic analyzer burns weeks on problems those tools resolve in an hour, as described in embedded debugging tools.

10. Underestimating the schedule by a factor of three

The last one is commercial rather than technical. Firmware estimates are made against the functional feature list — read the sensor, drive the motor, talk to the app — which is often 30–40% of the actual work.

The rest is infrastructure nobody demos: bootloader, provisioning, power management, error handling, production test support, calibration, logging, certification-driven changes, and the long tail of integration bugs that appear only when firmware meets real hardware. A useful sanity check on any firmware quote is to ask which of those are in scope. If the answer is fewer than half, the number is an opening bid, not a plan. Realistic ranges are in firmware development cost for IoT and embedded devices.

What to ask at your next review

  1. Can we update a unit in the field today? Show me.
  2. What version is on the bench unit, and which commit built it?
  3. What happens if the sensor never answers?
  4. What can we test without a board attached?

None of these require you to read code, and the answers tell you most of what you need to know about the state of the firmware.

Projects House develops embedded firmware for products that must survive years in the field, treating the bootloader, diagnostics, and production test path as part of the first release rather than a later phase. To talk through a firmware plan, reach us through the contact form.