Why structure matters
Embedded firmware usually starts as a quick proof of concept. The trouble begins when that proof of concept becomes the product. A maintainable STM32 project needs clear boundaries between hardware access, business logic, communication, and diagnostics.
The goal is not to overbuild the first revision. The goal is to make every future bug easier to isolate.
Suggested layers
Use a small set of explicit layers:
bsp: board support, pin maps, clocks, DMA, and peripheral handles.drivers: sensor, actuator, storage, and interface drivers.services: domain logic such as sampling, safety checks, state machines, and protocol routing.app: initialization, scheduling, and high-level orchestration.diagnostics: logs, counters, health state, and debug commands.
typedef enum {
DEVICE_OK,
DEVICE_WARN,
DEVICE_FAULT
} device_health_t;
typedef struct {
device_health_t health;
uint32_t rx_errors;
uint32_t sensor_timeouts;
} diagnostics_snapshot_t;
Communication as a service
Communication code is easiest to test when it is treated as a service rather than scattered through callbacks. Keep interrupt handlers short, push data into buffers, and let protocol code run in a controlled execution context.
Production checklist
- Record boot reason, firmware version, and configuration checksum.
- Track sensor timeouts and protocol error counters.
- Keep hardware-specific logic out of application state machines.
- Add a small command surface for field diagnostics.
- Test failure paths, not only the happy path.
Good firmware is not just code that runs. It is code that can be understood when the device is sealed inside a cabinet, deployed on a site, and sending one strange packet every ten minutes.