Embedded Best Practices
Rules for deterministic, safe, low-footprint embedded Rust firmware.
How to Use This List
- Review before hardware bring-up milestones
- Treat ISR design and memory layout as architecture decisions
- Measure stack, flash, and RAM on every release candidate
A - Memory and Safety
- Prefer
heaplessover heap. Bounded buffers with explicit overflow handling. - Document static buffer owners. DMA and ISR lifetimes must be
'static. - No
unwrapin production paths. Usedefmt::unwrap!only in development. - Stack high-water checks. Fill pattern or probe-rs stack analysis before ship.
B - Interrupts and Timing
- Minimal ISRs. Set flags; defer work to tasks or main loop.
- No blocking in ISR. No mutex sleep, no I2C transactions.
- Document interrupt priority map. NVIC conflicts cause latent bugs.
- Watchdog enabled. Pet in known-good code path only.
C - Hardware Integration
- Verify pin mux spreadsheet. One pin, one function.
- External pull-ups on I2C. Do not assume internal pulls suffice.
- Level shifting on mixed voltages. Protect 3.3V MCU inputs.
- SPI/I2C modes from datasheet. Wrong mode looks like dead chip.
D - Tooling and Debug
- defmt or RTT logging. Avoid UART-only when SWD available.
- Pinned probe-rs chip ID. CI and dev machines flash consistently.
- cargo size in CI. Fail on unexpected flash/RAM growth.
- Reproducible
Cargo.lock. Firmware builds are time-sensitive artifacts.
E - Architecture
- HAL for application, PAC sparingly. Register twiddling only when required.
- Portable drivers on embedded-hal. Board crate wires peripherals.
- Feature per board. One firmware crate, multiple
featuresfor PCBs. - OTA/update plan documented. Even if "USB drag-and-drop" only.
FAQs
Heap ever acceptable?
Rarely, with pool allocator and soak tests measuring fragmentation over weeks.
RTIC or Embassy?
RTIC for strict preemptive priorities; Embassy for async networking-heavy firmware.
How much flash headroom?
Reserve 20% for bugfix updates and optional features.
Unit tests on host?
Test algorithms on std target. Hardware tests for timing and drivers.
MISRA-style rules?
Rust ownership helps; still enforce no unsafe without review and documented invariants.
Power budget?
Measure current in sleep modes. Disable unused peripherals after init.
Concurrency without RTOS?
RTIC, Embassy, or lock-free atomics. Avoid ad-hoc static mut without sync.
Field returns?
Log firmware version and git hash via defmt on boot for support calls.
Schema for config?
Store board config in flash with CRC. Migrate versions explicitly.
Rust vs C drivers?
Prefer Rust drivers on embedded-hal. FFI to C only for certified or vendor blobs.
Related
- Embedded Basics - toolchain
- no_std Programming - memory model
- Memory in Embedded - buffers and heaps
- Debugging Firmware - observability
Stack versions: This page was written for Rust 1.97.0 (edition 2024), Tokio 1.x, Axum 0.8, serde 1.0, sqlx 0.8, clap 4, and Polars 0.46+.