C++ Embedded Coding Guidelines¶
Applies on top of the general C++ coding guidelines.
General Embedded¶
- No exceptions
- No RTTI
- Prefer static allocation
- Avoid dynamic memory allocation; prefer fixed-size buffers to prevent heap fragmentation
- No busy waiting, prefer interrupts and state machines
- Always fully qualify
std::— never ausing std::X;declaration, even function-scoped (stricter than the general guideline's allowance): keeps every standard-library use visibly distinguishable from project code, matching MISRA C++/AUTOSAR C++14 convention for safety-relevant firmware
Arduino (8 bit AVR)¶
- Dialect: C++17, enforced via
platform.local.txt:
compiler.cpp.flags=-c -g -Os -w -std=gnu++17 -ffunction-sections -fdata-sections -fno-exceptions -ffreestanding -fno-threadsafe-statics
<project>.inoas entry point; all logic in.cpp/.hppfiles.- No heavy STL (
std::vector,std::string); use C-strings, fixed arrays, or ArduinoString. F()macro for string literals inSerial.print()to keep them in flash.- No
delay(); usemillis()-based state machines. constexprfor pin definitions (not#define).- Hardware objects declared global;
.begin()called insetup(). - Check all
.begin()/.connect()return values — hardware may not be ready.
STM32 (Cortex-M via CubeMX)¶
- Dialect: C++23,
-fno-exceptions -fno-rtti -fno-threadsafe-statics(matches the firmware toolchain file and the host build's compile options — code behaves identically in both). - Everything CubeMX generates lives under
vendor/cubemx/and is never hand-edited — see that directory'sREADME.md. - Hardware access goes exclusively through the
Halinterface (src/infra/hal.hpp);Appitself stays free of any STM32 HAL header so it compiles for the host (tests) and the target (firmware) unchanged. - The
App's main-loop entry point is called forever from the target's main loop and a fixed number of times from host tests — no shutdown/exit path. Embedded firmware never returns frommain(); if there is nothing to do, enter a low-power mode (e.g.__WFI()) there instead of trying to leave the loop. - Calling hand-written C++ from CubeMX's generated
main.cgoes exclusively through theextern "C"bridge insrc/app_c_api.h, wired into CubeMX'sUSER CODE BEGIN/ENDmarkers — never editmain.coutside those markers, or the next CubeMX regeneration silently discards it. - HAL callbacks (
HAL_*Callback) live insrc/app_c_api.cppwithextern "C"linkage, never inmain.c;main.conly includesapp_c_api.hand callsapp_init()andapp_poll(). - Catch2 (not Unity) for tests, host-side only; on-target tests are deferred until the hardware interaction is worth automating.
- No Conan for the firmware build: the vendor toolchain and HAL come from CubeMX, not from a package manager.
Acceptance Tests¶
Bare-metal firmware has no operating system and no network-reachable running instance, so
pytest-bdd cannot drive it like a service. A thin subprocess-drivable harness in
test/bdd/harness/ wires App to the same mocked HAL the Catch2 tests use, not to the
app_c_api.h/Target_Hal bridge, which grows real hardware calls and stops compiling on the host;
the ARM cross-build exercises that bridge. This is a host-side acceptance tier only and does not
replace on-target or hardware-in-the-loop tests.
Directives¶
- No exceptions, no RTTI, no dynamic allocation on the target; prefer static allocation and fixed-size buffers
- No busy waiting; use interrupts and state machines instead of
delay()-style loops - Always fully qualify
std::; never ausing std::X;declaration, even scoped — stricter than the general guideline - Arduino (AVR): C++17 via
platform.local.txt;.inois the entry point only, all logic lives in.cpp/.hpp - Arduino: no heavy STL (
std::vector,std::string) — use C-strings, fixed arrays, or ArduinoString; wrap string literals inF()to keep them in flash - Arduino: no
delay()— usemillis()-based state machines;constexprfor pin definitions, not#define - Arduino: hardware objects declared global,
.begin()called insetup(); always check.begin()/.connect()return values - CubeMX-generated code lives only under
vendor/cubemx/; never hand-edit it - Hardware access only through the
Halinterface; keepAppfree of any vendor HAL header - No shutdown/exit path; a target with nothing to do enters a low-power mode inside its main-loop entry point
- Call hand-written C++ from
main.conly through theextern "C"bridge, wired into CubeMX'sUSER CODE BEGIN/ENDmarkers — never editmain.coutside those markers - HAL callbacks live in
src/app_c_api.cppwithextern "C"linkage, never inmain.c - Catch2 for host-side tests; no on-target test framework until a concrete need arises
- Firmware BDD drives a host harness through a subprocess, wired to the same mocked HAL as the Catch2 tests, never to the target-only bridge