Skip to main content
The EVCore firmware coding standard applies to firmware/, simulation/, and tests/. It borrows direction from MISRA C:2012 and the SEI CERT C coding standard. It is not a claim of MISRA compliance. Formal compliance would require a qualified checker, a guideline enforcement plan, and approved deviation records, which becomes worthwhile once the STM32 project and production toolchain exist.

Automatically enforced gates

Warning set

The strict warning set is kept identical in CMakeLists.txt and scripts/run-safety.ps1:
With Clang/Zig, these are also enabled:
The sanitizer runs only in host builds. The UB it catches is still UB on the MCU, which is why host tests matter. The ARM build will use its own flag set.

Types and arithmetic

  • Use fixed-width types (uint32_t, int16_t) for anything with a hardware or protocol width. Use bool for truth values.
  • Bitmasks are unsigned: #define EV_OUT_5V ((uint32_t)1u << 0). Do not use enumerators for masks: enumeration constants are int, so ~mask would be a signed operand.
  • No implicit narrowing, sign changes, or float-to-double promotion. Write the cast where a conversion is intended.
  • Never compare floating-point values with ==/!= in firmware. Use range or tolerance checks, for example fabsf(gain) < FLT_MIN to reject a zero gain.
  • Check every float input with isfinite before using it.
  • Time uses uint32_t milliseconds. Compare ages as (uint32_t)(now - then) so the check still works when the counter rolls over.

Control flow

  • A switch on an enum lists every enumerator. A default is still required and must fail safe (OFF, UNKNOWN, or a fault), because stored or received values can be out of range.
  • No recursion, no goto, no variable-length arrays, no setjmp/longjmp.
  • Loops have a fixed upper bound tied to an array size or a configured limit.

Memory and interfaces

  • No dynamic allocation (malloc/free) in firmware. State lives in caller-owned structs, and queues are fixed-size ring buffers.
  • Public functions reject null pointers and out-of-range arguments and report failure through their return value. They must not assume a valid caller.
  • Internal functions and data are static. Every external function has a prototype in the module header.
  • Buffer writes check the remaining capacity before writing. Formatted output goes through a function carrying a printf format attribute so every call is type-checked.
  • Hardware access goes only through board callbacks. Core logic never names a pin, peripheral, or register.

Safety behavior

  • The default and error state is outputs OFF. Any unexpected state, invalid measurement, or missing configuration de-energizes and reports why.
  • Safety-relevant decisions are testable on the host, and tests check that the OFF command was actually issued, not just what state was reported.

Naming and layout

  • Public identifiers use the EV_ prefix with module-based CamelCase (EV_SafetyArm). Macros and enumerators are upper case (EV_FAULT_STOP).
  • Units go in names or comments: _ms, _mv, _ma, _ohms, _hz.
  • Files are UTF-8 with LF line endings (enforced by .gitattributes). C sources use 4-space indentation (.editorconfig).

Recorded deviations

Add a row to the deviation table, with its rationale, before introducing any new exception.