1What makes embedded software different
Embedded software lives inside one device and exists only for that device. Compared with PC software, the assumptions change:
| PC software | Embedded software | |
|---|---|---|
| Hardware | Unknown PCs | One specific board |
| Time | Late but correct is fine | Must respond within a deadline (real-time) |
| Resources | Plenty of memory | Often tens to hundreds of KB of RAM |
| Lifetime | Reinstalled every few years | Often in service for 10+ years |
| When it fails | The user restarts it | There may be nobody to restart it |
Real-time doesn't mean fast. If a motor stops one second after the stop signal, the result is correct but the behavior is wrong. In embedded systems, timing is part of correctness.
In practice
Design as if nobody will ever be there to power-cycle the device. That one assumption puts a watchdog (a circuit that restarts a stalled program) and power-failure recovery into the design from day one.
2Why an OS?
Small devices often run without an OS, using a super loop: an endless loop in main checks each job in turn, and urgent events are handled by interrupts. It is simple, small and predictable.
It breaks down as jobs multiply. One long job delays all the others — the button feels sluggish while data is being processed. Splitting long jobs into slices adds state variables and scatters timing logic across the code.
An RTOS (real-time operating system) solves this structurally:
- Separation — each function becomes its own task.
- Priority — a high-priority task preempts a lower one that is running.
- Timing — periodic execution, timed waits, and safe data exchange between tasks (queues, semaphores).
The cost: stack memory for every task, careful handling of shared resources, and new failure modes such as priority inversion, where a low-priority task holding a resource blocks a high-priority one.
In practice
With two or three simple periodic jobs, or a battery device that sleeps most of the time, stay without an OS. If the answer to "must other functions keep their response time while communication is being processed?" is yes, use an RTOS.
3Free operating systems
| OS | Character | Choose it when |
|---|---|---|
| FreeRTOS | Small, simple kernel; supported on more MCUs than almost any other | Small MCUs, or adding tasks to existing code |
| Zephyr | Integrated RTOS with kernel, drivers, networking and wireless stacks, and its own build system | Network-heavy products supporting several boards from one codebase; steeper learning curve |
| RT-Thread | Kernel plus selectable components such as file systems, networking and USB | Mid-sized devices that need file systems or USB |
| NuttX | RTOS following POSIX interfaces | You want Linux-style programming on an MCU |
| Eclipse ThreadX | A real-time operating system for small MCUs, now available as open source | Your chip vendor's development tools already include it |
| Embedded Linux | General-purpose OS needing an MMU and tens of MB of memory or more | Displays, complex networking, heavy file handling — at a cost in boot time, real-time behavior and power |
The five RTOSes use business-friendly licenses (MIT or Apache 2.0). The Linux kernel is GPL, which carries source-disclosure obligations when you ship.
In practice
Before comparing features, check whether the chip vendor officially supports the OS and whether drivers for your peripherals already exist. Writing drivers usually costs far more time than any feature gap.
4Verification after development
Working features are not the end of verification. Field failures are usually "occasional", "after long use" or "only under certain conditions".
- Unit tests — modules tested in isolation; with hardware access separated (section 7), many run on a PC.
- Hardware integration tests — peripherals, communication and power on the real board.
- Long-run tests — days to weeks of continuous operation expose memory leaks, counter overflows and rare timing collisions. A 32-bit millisecond counter wraps after about 49.7 days; code that mishandles it never fails in a short test.
- Boundary and fault tests — power dropouts, unplugged cables, malformed data, temperature extremes. The goal is not "never fails" but "fails safely and recovers on its own".
- Static analysis — tools find uninitialized variables, out-of-bounds access and risky casts without running the code.
In practice
The latest-found and most expensive bugs come from long-run and fault tests. The tighter the schedule, the earlier these two go into the plan.
5Designing hardware for production
A few prototypes can be built by hand. Thousands of identical units need production built into the design — and most of those decisions are made at the schematic and PCB stage.
- Programming pads placed where fixture pins can reach them, so many boards are programmed at once without connectors.
- Test points for supply voltages and key signals, so failures can be traced.
- Unique values — serial numbers, addresses, calibration data: when, where and how they are written and logged.
- Supply and obsolescence — long-life parts and parts with second sources. End-of-life notices arrive mid-production.
- Test time and unit cost — saving one minute per unit saves about 167 hours over 10,000 units. Test commands built into the firmware let the fixture check functions quickly.
In practice
Adding test pads when moving from prototype to production board means another board revision. Having built production test equipment ourselves, we put them in the very first prototype — it is the cheapest point.
6Bootloaders and field updates
To fix and extend a product after shipping, memory is split into a bootloader (a small program that runs first, checks the application and starts it) and the application.
- Update path — serial, USB, network or wireless, as the product requires.
- Survive interruption — power can fail mid-update. Receive the new image into a separate area and verify before switching, or fall back to the previous image.
- Integrity — check the image with a CRC and, where needed, a digital signature.
- Version tracking — know which version each unit runs.
In practice
Assume the bootloader can never be changed after shipping. Keep it minimal, and test it the most.
7Layers against component obsolescence
Over a long product life, the MCU or a peripheral chip will be discontinued. To avoid rewriting everything, separate hardware-dependent code from application logic from the start.
- Hardware abstraction (drivers) — registers, pins, clocks. Only this layer changes with the chip.
- Services — protocol handling, storage, updates.
- Application — what the product does, calling only the layers below.
With function interfaces between layers, a chip change or a move from super loop to RTOS touches little code — and PC-based unit testing becomes possible.
In practice
Register names appearing in application code are an early sign that porting costs are rising. Across many MCU generations, a well-layered first product lets us build the next one in about half the time.
8Debugging tools
| Tool | What it does | Use it for |
|---|---|---|
| JTAG/SWD debugger | Halts execution to inspect variables, memory and registers; SWD needs only two pins | Logic errors, locating exceptions |
| Logging | Records flow and values with timestamps over serial or similar | Behavior that must not be halted, field reproduction |
| Logic analyzer | Shows many digital signals on one timeline and decodes protocols | Communication timing and ordering |
| Oscilloscope | Shows voltage waveforms | Supply ripple, noise, signal quality |
Halting in a debugger changes timing and can make a bug disappear. For communication or motor control, observe with logs and measurements instead — and remember that heavy logging changes timing too.
In practice
The rarer the symptom, the more it pays to leave logging and a logic analyzer attached and wait for it to recur. One record of the moment it happened beats any amount of guessing.