ADR-005: Embedded platform choices and C/C++ conventions
Status
Accepted
Context
The “from math to metal” pipeline requires embedded C/C++ implementations alongside the Python prototypes. Three topics already have embedded.qmd pages targeting STM32F4 and ESP32. We need to standardize on specific, purchasable hardware and establish conventions before scaling to more topics.
Key constraints: - The STM32F4-Discovery board (used in the original DSBC practicum source material) is discontinued. Students can no longer buy it new. - ADR-002 establishes Python as the primary language. C/C++ is a companion for embedded.qmd pages, not a replacement. - Target general-purpose MCUs, not dedicated DSP chips (per CLAUDE.md).
Decision
1. Two-platform strategy
| Platform | Board | MCU | Clock | FPU | DSP library |
|---|---|---|---|---|---|
| ESP32-S3 | ESP32-S3-DevKitC | Xtensa LX7 dual-core | 240 MHz | Single-precision | ESP-DSP |
| STM32F4 | NUCLEO-F446RE | Cortex-M4F | 180 MHz | Single-precision | CMSIS-DSP |
- ESP32-S3 for accessible prototyping: cheap (~EUR 8), built-in WiFi/BT and I2S, ESP-DSP library with optimized FIR/FFT.
- NUCLEO-F446RE for professional embedded DSP: CMSIS-DSP (the industry-standard ARM DSP library), SIMD for Q15/Q31 fixed-point, deterministic real-time behavior.
- RP2350 / Pico 2 (Cortex-M33, $5) is referenced in platform comparison tables as a budget alternative but does not get its own code sections.
- Teensy, dedicated DSP chips (TI C6x, SHARC) are excluded. FPGAs were excluded here originally but are admitted in a bounded way by amendment 1b below.
Audio I/O: pair either board with INMP441 I2S MEMS mic (~EUR 2) and PCM5102/MAX98357A I2S DAC (~EUR 3).
1a. Extended capability ladder (amended 2026-06-15)
The two-platform pair above remains the default for embedded pages. But where an algorithm’s portability and feasibility across capability tiers is itself the lesson, a page may use a wider capability ladder instead, drawn from the HAN Embedded Machine Learning (EML) course board set (Arends and Veen, gitlab.han.nl/aea/ese/eml). The ladder spans, from bottom to top:
| Tier | Board | Core | Clock | FPU | RAM |
|---|---|---|---|---|---|
| 8-bit, no FPU | ATmega328P Xplained Mini | AVR | 16 MHz | none | 2 KB |
| M0+, no FPU | FRDM-KL25Z | Cortex-M0+ | 48 MHz | none | 16 KB |
| M4F | NUCLEO-F411RE | Cortex-M4F | 100 MHz | single-prec | 128 KB |
| M33 + NPU | FRDM-MCXN947 | Cortex-M33 ×2 + eIQ Neutron NPU | 150 MHz | single-prec | 512 KB |
Rationale: the same portable C runs on every rung, but memory and arithmetic differ by more than an order of magnitude, so “same algorithm, different feasibility” becomes a teachable result rather than an aside. First applied in topics/gabor-filters/embedded.qmd (2-D convolution). The shared-lib/, per-target-glue, multi-toolchain (PlatformIO / Keil / MCUXpresso / Microchip Studio) structure is also borrowed from the EML course. Conventions 2-6 below (float-first, write-your-own-then-library, inline code, negated IIR coefficients) still apply on whichever boards a page uses.
1b. Gateware / HDL exception (amended 2026-07-10)
FPGAs were excluded above to keep the platform set small and buyable. That still holds as the default: embedded pages target MCUs. But a few DSP structures exist because of the gateware they map to, and for those an HDL companion is the honest “to metal” endpoint, not scope creep. Such a page may add a single VHDL block (the primary HDL taught here; Verilog optional) showing the structure as registers and adders.
Bounds, so this does not become a second platform track: - Only where the hardware mapping is the lesson. The CIC filter (basics/10-multirate/embedded.qmd) is the reference case: multiplier-free, register-growth/overflow behaviour that only makes sense in gateware. Do not add HDL to a page just because it could be synthesised. - Illustrative, not a maintained flow. The workshop has no HDL simulator in CI. HDL blocks are verified by inspection and by an equivalent, tested Python model (e.g. the bit-accurate overflow check in test_multirate.py), and are labelled as not-CI-simulated. No vendor project files, no board commitment. - Inline, self-contained, like the C blocks (convention 5), and cross-linked to the Python that validates them.
Conventions 2–6 (companion-not-replacement, write-your-own-first, inline code) apply to HDL as they do to C.
2. C/C++ is companion, not replacement
ADR-002 is not superseded. Python remains the primary language for theory, prototyping, and analysis. C/C++ code appears only in embedded.qmd pages within topic directories. Each embedded.qmd links back to its topic’s index.qmd for the mathematical derivation and Python implementation.
3. Float first, fixed-point as optimization
All implementations use float (single-precision) by default. Fixed-point (Q15/Q31) is shown as an optimization section within the same page, not as a separate implementation. Both target MCUs have hardware single-precision FPU; fixed-point is relevant for power-constrained or FPU-less targets.
4. Write your own first, then benchmark against library
Each embedded page shows a hand-written C implementation first (teaches the algorithm), then the CMSIS-DSP or ESP-DSP equivalent, followed by a performance comparison. This mirrors the existing pattern in the biquad, adaptive-filtering, and matched-filtering pages.
6. IIR coefficient convention
All IIR filter code uses negated denominator coefficients (CMSIS-DSP convention): w[0] = x + a1*w[1] where the stored a1 is \(-a_1\) from the transfer function. When porting from SciPy, negate a1 and a2. This matches the existing biquad embedded.qmd and avoids a subtraction in the inner loop.
Consequences
- Positive: Students can buy the recommended hardware today. Both boards are actively manufactured and widely available.
- Positive: Two platforms cover complementary use cases (IoT/prototyping vs. professional/industrial) without overwhelming contributors with three or more toolchains.
- Positive: Float-first approach means students write their first filter without learning Q-format notation.
- Negative: NUCLEO-F446RE lacks built-in audio hardware (unlike the old Discovery board’s CS43L22 codec). External I2S breakouts add ~EUR 5 to the BOM.
- Negative: Excluding RP2350 code sections means Pico 2 users must adapt STM32 CMSIS-DSP code themselves (both are ARM Cortex-M, so this is straightforward).