This repository contains the STM32H7 hardware, OSAL, and driver support used by the bare-metal F´ reference deployment. It targets the STM32H753XI-EVAL2 evaluation board and is intended to be reusable as a Git submodule.
The implementation is designed for a bare-metal cyclic executive:
- No FreeRTOS or other operating system is required.
Os::Taskis cooperative and creates no thread.Os::Mutexprovides bounded critical sections by preserving and restoring Cortex-M7PRIMASK.Os::Queueis a fixed-depth, interrupt-safe FIFO allocated during startup.Os::RawTimeprovides a race-safe microsecond clock from a free-running TIM2 counter and overflow interrupt.- DMA buffers are placed in DMA-accessible AXI SRAM rather than DTCM.
The current configuration is for the STM32H753XIH6 on the STM32H753XI-EVAL2:
- Cortex-M7, 2 MiB Flash, 1 MiB RAM
- 25 MHz HSE input and a 480 MHz PLL1 system clock
- USART1 through the embedded ST-LINK-V3E VCP
- USART1 TX on PB14 and RX on PB15
- Direct user LEDs on PF10 (LED1, green) and PA4 (LED3, red)
- TIM2 as the 1 MHz, 32-bit free-running time source
LED2 and LED4 are routed through the external MFX I2C expander and are not used by the direct bare-metal GPIO examples.
Os/Stm32H7: STM32H7 bare-metal delegates for Task, Mutex, Queue, and RawTimeDrv/STM32GpioDriver: passive GPIO input/output driverDrv/STM32Timer: channel-2 output-compare tick driver, instance-selectable (Stm32::TimerInstance::Tim1/Tim2/Tim3/Tim4/...) viaopen()Drv/STM32UartDriver: USART1 DMA-backed byte-stream driverDrv/STM32I2cDriver: blocking/polled I2C master driver (Drv.I2c)Drv/STM32SpiDriver: blocking/polled SPI master driver (Drv.Spi), no DMA/interrupts, multi-instance-safe (different bus + chip-select per instance)Drv/config: driver-tuning headers (e.g.UartDriverConfig.hpp) andStm32Config.hpp, the per-peripheral-instance enable/disable switchboard used by every driver below (see "Enabling and selecting peripheral instances")Allocator: fixed-pool bootstrap allocator + newlib--wraptraps enforcing the no-heap-after-bootstrap rule -- fully hardware-agnostic, so every project gets it without hand-rolling oneCore/CortexM7: ARM-core-level helpers (D-cache maintenance and the DMA-safe-buffer assertion), grouped by core rather than by ST family since they only depend on which Cortex-M core is in use. The next family added (e.g. STM32F4, Cortex-M4) needs its ownCore/CortexM4with the same function names and no-op bodies -- see "Adding a new chip family" below.
This library is hardware-agnostic: it contains no CubeMX-generated code and
no board-specific source. It only expects a CMake target named FprimeStm32
to already exist -- built and exposed by the consuming project, along with
its public include paths for main.h, stm32h7xx_hal_conf.h, and the rest
of the CubeMX/HAL headers. In this repository that target is defined in
FprimeStm32BaremetalReference/Hardware/CMakeLists.txt, which builds the actual
CubeMX-generated project (regenerable in place from its own .ioc file) plus
a handful of hand-written, project-specific clock/tick-source glue (which
timer backs Os::RawTime, the exact PLL/oscillator sequence -- these are
peripheral-role choices baked into one board's .ioc, not family-wide
constants, so they stay project-owned even though they rarely change). This
split means retargeting this library to different hardware never requires
forking it -- only editing the consuming project's own Hardware/ directory.
The real interrupt implementation (stm32h7xx_it.c) must be linked directly
into each deployment executable, rather than only through the FprimeStm32
static library, so its strong handlers override the startup file's weak
Default_Handler aliases -- see the NOTE in
FprimeStm32BaremetalReference/Hardware/CMakeLists.txt.
Stm32UartDriver, Stm32I2cDriver, and STM32Timer all resolve their
peripheral instance (USART1_UART_INSTANCE, I2C1_INSTANCE, TIM2_INSTANCE,
...) through #defines in Drv/config/Stm32Config.hpp, each defaulting to
false except the instances this reference board already uses. A driver's
toHalHandle(instance) returns nullptr for a false instance, and the
calling code immediately FW_ASSERTs on that nullptr — selecting a
disabled instance in an open() call is a build-time configuration mistake,
not a runtime condition to gracefully handle.
The library's copy of Stm32Config.hpp is the default; a consuming project
overrides it by placing its own copy at the same relative path under its
settings.ini-configured config_directory (this project's override lives
at FprimeStm32BaremetalReference/config/fprime-stm32/Stm32Config.hpp). When
retargeting this library to a new board, edit only the override copy — enable
the instances your .ioc actually configured, and pass the matching enum
value (Stm32::I2cInstance::I2c1, Stm32::TimerInstance::Tim2, ...) to the
driver's open() call from the topology's configureTopology(). Each
sensor's own docs/sdd.md under lib/fprime-sensors documents this
enable-then-select pairing for its specific port.
Enabling more than one instance of the same macro (e.g. both
USART1_UART_INSTANCE and USART2_UART_INSTANCE) and adding a second
Stm32UartDriver/Stm32I2cDriver/Stm32SpiDriver/STM32Timer instance to
the topology is fully supported: every driver in this library resolves its
HAL pointer(s) from HAL-free per-instance state on every call rather than
caching them in file-static state shared across instances, so two
simultaneously-open instances of the same driver type (different bus,
different chip-select, different physical peripheral) never interfere with
each other — this is what lets a CubeSat team add, say, a second UART for
an Iridium modem without touching the existing ground-link UART, or a
second I2C bus for a second temperature sensor.
Os/Stm32H7 and the Drv/STM32* drivers only reach HAL functionality
through CubeMX's own per-peripheral headers (main.h, gpio.h, usart.h,
i2c.h, tim.h, dma.h) -- CubeMX generates these under the same names
for every STM32 family, unlike the family-named umbrella headers
(stm32h7xx_hal.h, stm32f4xx_hal.h, ...). Never include a family-named HAL
header directly from library code; include the matching CubeMX per-peripheral
header instead (it chains to the right family headers with the right
pre-defines already set up). This is what keeps Drv/STM32* family-portable
without any per-family duplication.
Os/Stm32H7 itself is family-specific (its name says so) because OSAL
backends for different families are mutually exclusive per build. To add a
new family: create Os/Stm32<Family>/ alongside it with the same four
register_os_implementation calls (SUFFIX = the new family name), and add
an elseif (FPRIME_PLATFORM STREQUAL "stm32<family>") branch in
Os/CMakeLists.txt. The Drv/STM32*/CMakeLists.txt real/stub selection
already matches any stm32* platform, so no change is needed there.
STM32F4 is the next planned family. It's a useful worst-case example because
it differs from STM32H7 in exactly the place this library's DMA support
leans on hardest: Cortex-M4 has no data cache at all (not just "cache
disabled" -- SCB_CleanDCache_by_Addr/SCB_InvalidateDCache_by_Addr don't
exist in the M4 CMSIS core header, so calling them is a compile error, not a
logical no-op), and F4's memory map has no DTCM/AXI_SRAM split -- just
plain SRAM uniformly reachable by DMA. Every item below either doesn't apply
to F4 for that reason, or exists specifically to stay correct across a family
that has no cache and no DMA-excluded RAM region.
1. Toolchain & CMake platform
- Add
cmake/toolchain/stm32f4.cmakewith the family's own compiler flags (-mcpu=cortex-m4 -mfpu=fpv4-sp-d16 -mfloat-abi=hard, vs. H7's-mcpu=cortex-m7 -mfpu=fpv5-d16) and chip define (e.g.-DSTM32F407xxin place of-DSTM32H753xx). - Add
cmake/platform/stm32f4.cmakemirroringstm32h7.cmake's module wiring. - Add an
elseif (FPRIME_PLATFORM STREQUAL "stm32f4")branch inOs/CMakeLists.txt(see above) -- theDrv/STM32*/CMakeLists.txtFPRIME_PLATFORM MATCHES "^stm32"conditionals already matchstm32f4with no changes needed.
2. Vendor HAL / CMSIS / linker (project-owned, like today's
Hardware/stm32h753_hal/, but required for the library to compile against)
- Vendor
STM32F4xx_HAL_Driver+ CMSISDevice/ST/STM32F4xx, generated from a CubeMX.iocfor the chosen F4 part, under the consuming project'sHardware/tree. - Verify the vendored HAL version actually exposes
HAL_UARTEx_ReceiveToIdle_DMA/HAL_UARTEx_RxEventCallbackif DMA-mode UART is wanted on F4 -- this API landed in the HAL at a specific version per family; don't assume parity with the H7 package. - Write a new linker script for the F4 part's actual flash/RAM layout.
There is no
AXI_SRAM/DTCM_RAMsplit on F4 (justSRAM1/SRAM2/...), so this README's "Memory and placement" table needs an F4-specific variant, not a reused one. - Give
PlatformMemory.hpp'sATTR_DTCM_BSSmacro an F4 definition that compiles to nothing (plain.bssplacement) -- F4 has no DTCM bus for it to point at.
3. Core-level abstraction (cache + DMA-safety) -- the part this family actually changes
- Create
Core/CortexM4/CacheMaintenance.hppalongsideCore/CortexM7/CacheMaintenance.hpp, exporting the same three function names (CleanDCacheForDma,InvalidateDCacheForDma,AssertDmaSafe) in the sameStm32namespace, so no driver.cppever needs an#ifdef-- only the CMake include path changes:CleanDCacheForDma/InvalidateDCacheForDmabecome empty inline no-ops. Do not#includeor otherwise reuse the M7 version -- theSCB_*DCache_by_Addrcalls it wraps don't exist on M4.AssertDmaSafebecomes a no-op that always passes -- there's no DTCM-vs-SRAM split on F4, so there's no "unsafe for DMA" address range to assert against.
- Add a sibling
FprimeStm32CoreCortexM4interface CMake target (mirroringFprimeStm32CoreCortexM7's singletarget_include_directoriesline) pointing atCore/CortexM4. - In every
Drv/STM32*Driver/CMakeLists.txt's real-targetDEPENDS, select the core target by family, not by the generic^stm32match used for everything else (e.g.stm32h7depends onFprimeStm32CoreCortexM7,stm32f4onFprimeStm32CoreCortexM4).
4. OS abstraction layer
- Create
Os/Stm32F4/per the four-call pattern above. - Check whether
Os::RawTime/Os::Mutex's actual implementations are Cortex-M-generic (DWT cycle counter,PRIMASK) rather than H7-specific before copying them -- if they're already core-generic, factor the shared logic into anOs/CortexM/implementation bothStm32H7andStm32F4depend on instead of duplicating it.
5. Driver instance wiring (toHalHandle()/callInstanceInit()/hw*()
in each driver's real .cpp)
- Confirm the HAL struct/function names (
SPI_HandleTypeDef,HAL_SPI_TransmitReceive,HAL_SPI_TransmitReceive_DMA, ...) are identical between the F4 and H7 HAL packages -- they are, by ST's design, so these methods need no changes once the matching CubeMX-generatedspi.c/i2c.c/usart.cexterns exist for the F4 project. - Check each driver's instance-enable macros in
Stm32Config.hppagainst the chosen F4 part's actual peripheral count (fewer SPI/I2C/USART instances than H753 is common) -- leave the ones that don't existfalseforever; the enum itself doesn't need to shrink. - Verify the F4 HAL's
__HAL_LINKDMA()convention populateshdmatx/hdmarxthe same way H7's does -- if so, thehdmatx != nullptr/hdmarx != nullptrsafety assert inStm32SpiDriver/Stm32I2cDriver::open()works unmodified.
6. Documentation
- Revisit each driver's
docs/sdd.mdfor claims that were implicitly H7-specific (e.g. "D-cache maintenance," AXI SRAM wording) now that a second family's DMA path exists. - Add an F4 entry to this README's "Supported hardware" section once a concrete evaluation board is targeted.
7. Verification
-
fprime-util build stm32f4from a project with the F4Hardware/tree wired -- confirms the cross-compile, the newCore/CortexM4headers, and the new OS backend all link. - Host-native
fprime-util checkneeds no changes for a new family:<Driver>Stub.cpphas zero HAL/CMSIS dependency already, so the UT suite is family-agnostic by construction. - Smoke-test at least one DMA-mode and one POLLED-mode instance on real
F4 hardware before trusting the no-op cache functions silently -- on a
cache-less core, a successful build already proves more than it would
on H7 (there's no
SCB_*DCache_by_Addrcall left to have gotten wrong), but the DMA data path itself still needs a real hardware pass.
The parent F´ project selects this platform through its stm32h7 CMake
configuration. From the parent project:
source fprime-venv/bin/activate
fprime-util generate -f
fprime-util build -j"$(nproc)"To build the reference deployment directly after generation:
ninja -C build-fprime-stm32h7 ReferenceDeploymentThe deployment must call the hardware initialization routines in this order:
HAL_Init()FprimeStm32_ClockInit()SCB_EnableICache()andSCB_EnableDCache()Stm32_Tim2ClockInit()- topology setup and the cyclic executive
The clock initialization is required before configuring TIM2. The cache initialization is required before using the DMA cache-maintenance helpers; Cortex-M7 cache tags and data are undefined at reset.
Drv::Stm32UartDriver provides a non-blocking USART1 DMA transport over PB14/PB15
through the ST-LINK-V3E VCP. It uses fixed-size TX and RX rings, aligned DMA
staging buffers, idle-line detection for RX, and a polled DMA state machine so
the cyclic executive is never blocked waiting for serial I/O.
The driver was validated on the physical STM32H753XI-EVAL2 with:
- 115200 baud, 8N1 operation
- DMA on both transmit and receive
- 183 KiB transmitted in 180 seconds with zero TX/RX error counts
- 6,144 bytes captured from the VCP in 6.0 seconds, matching the driver's internal byte counter
- 96 injected uplink bytes received and drained without buffer leaks
- CCSDS Space Packet framing through the
ComCcsdssubtopology - A continuous 21+ minute
fprime-gdssession with command uplink, telemetry downlink, and event downlink
The GDS validation decoded more than 12,000 telemetry samples at approximately
10 samples per second. The command round trip included CMD_NO_OP, string
commands, and an oversized string that correctly returned FORMAT_ERROR.
The ground-link tests above were performed before the September 3 clock-tree fix, while the board was still running from the approximately 64 MHz HSI. The USART baud rate self-adjusted from the live peripheral clock query, so the link remained valid, but an extended ground-link soak at the corrected PLL clock is still required.
Stm32::Stm32I2cDriver implements the framework's Drv.I2c interface
(guarded, synchronous write/read/writeRead ports, each returning
Drv::I2cStatus directly) against I2C1 on PB6/PB7 (SCL/SDA). Unlike
Stm32UartDriver, it is deliberately blocking/polled, not interrupt-driven:
HAL_I2C_MspInit() only enables the peripheral clock/GPIO, no NVIC
event/error interrupt is armed, and every HAL_I2C_Master_Transmit/_Receive
call is bounded by a fixed 10 ms watchdog passed as the HAL's own Timeout
parameter. This is a deliberate choice, not a shortcut: Drv.I2c is a
synchronous contract (the same one Drv::LinuxI2cDriver implements by
blocking on ioctl), the cyclic executive has no thread to free up by not
blocking, and a real I2C transaction at 400 kHz is sub-millisecond. Interrupts
would only earn their complexity back for writeRead's one real limitation:
it is two back-to-back blocking calls (STOP then START), not a single
electrically-held repeated START, which is safe on this single-master bus but
would need the sequential IT/DMA API (with I2C1's NVIC interrupt enabled) for
a sensor that strictly requires the bus held across the register-address
write.
open(instance, busSpeed) selects both the peripheral (I2cInstance::I2c1
today; I2c2/I2c3/I2c4 are declared for other boards but not yet
CubeMX-configured) and a bus speed preset (I2cBusSpeed::Standard/Fast/
FastPlus — CubeMX-computed Timing register values for this project's
actual D2PCLK1 clock, since the H7 I2C peripheral has no runtime baud-rate
formula the way UART does). It follows the same Common/Real/Stub HAL
boundary convention described below, with one variant: open() itself is
implemented directly in Stm32I2cDriver.cpp/Stm32I2cDriverStub.cpp rather
than delegating to a private hwOpen(), since (unlike UART's DMA/ISR setup)
there's no separate hardware-independent work for a shared Common.cpp to do
around it.
Validated live against a real MPU-6050 IMU (wake-up register write, then repeated 14-byte accel/gyro/temp reads) — see "Adding a sensor" below for how that's wired without any application code touching the I2C bus directly.
Connect a sensor's ports directly to Stm32I2cDriver's write/read/
writeRead — if the sensor component already imports the framework's
Drv.I2c/Drv.I2cWriteRead port types (check its .fpp), no adapter
component is needed. This is exactly how the MPU-6050 was wired:
fprime-sensors' MpuImu.ImuManager (a queued component with its own
internal reset/enable/configure/read state machine) declares busWrite: Drv.I2c / busWriteRead: Drv.I2cWriteRead output ports, connected straight
to i2cDriver.write/i2cDriver.writeRead in Top/topology.fpp. Its standard
command/event/telemetry/param/time ports wire themselves via the topology's
existing pattern-graph specifiers (command connections instance CdhCore.cmdDisp, etc.) — the only manual connections needed were the two I2C
ports and a rate-group tick into its run port.
Don't use the sensor library's own bundled example Subtopology
(MpuImuSubtopologyConfig.fpp etc.), and don't add the whole library via
settings.ini's library_locations. Those bundled Subtopologies hardcode a
Drv.Linux*Driver instance type (e.g. Drv.LinuxI2cDriver for MpuImu,
Drv.LinuxSpiDriver for Bmp280). Since Linux-only driver modules are
skipped outright on stm32h7 — not just their C++ target, the type doesn't
exist in the fpp model at all — fpp-to-cpp fails with "symbol Drv is not defined" the instant any bundled Subtopology config gets registered, whether
or not the deployment references it. Instead, register only the specific
modules actually needed (for MpuImu: Helpers, MpuImu/Types,
MpuImu/Ports, MpuImu/Components) directly via add_fprime_subdirectory in
the project's top-level CMakeLists.txt, skipping each family's own
top-level CMakeLists.txt (that's what pulls in Subtopology). Because the
library's own sources #include "fprime-sensors/..." (paths relative to the
library root), also add lib/fprime-sensors as a plain
include_directories() root — both the source tree and
${CMAKE_CURRENT_BINARY_DIR}/lib/fprime-sensors, where fpp generates the
matching *Ac.hpp headers when the library isn't registered through
library_locations.
Stm32::Stm32SpiDriver implements the framework's Drv.Spi interface
(guarded SpiWriteRead, returning Drv::SpiStatus, plus the deprecated
void-returning SpiReadWrite) against SPI5 on PF6/PF7/PF8/PF9
(NSS/SCK/MISO/MOSI). Like Stm32I2cDriver, it is deliberately blocking/
polled with no DMA and no interrupts: sensor payloads on this bus (6-8 bytes
for the BMP280) are small enough that a single blocking
HAL_SPI_TransmitReceive() call is simpler and cheaper than setting up
DMA-safe AXI SRAM buffers and Cortex-M7 D-cache maintenance for a transfer
that's over before either would matter.
SPI5 is configured NSS_SOFT in this project's CubeMX setup — the HAL never
drives a chip-select pin on its own — so this driver owns the CS GPIO
directly rather than treating it as the peripheral's problem: open(instance, csPort, csPin, timeoutMs) configures the pin as a push-pull output (idle
high) alongside MX_SPIn_Init(), and every SpiWriteRead/SpiReadWrite call
drives it low for the exact duration of the HAL_SPI_TransmitReceive() call.
Stm32SpiDriver stores only HAL-free state on each component instance (the
SpiInstance enum, the chip-select Stm32::GpioPort+pin, the timeout) and
re-resolves the real HAL pointers from that state on every call, matching
the same per-instance approach every STM32 driver in this library now uses
(see "Enabling and selecting peripheral instances" above). This is
deliberate, not incidental: a BMP280 and any future SPI sensor on a
different bus or chip-select need two simultaneously-open Stm32SpiDriver
instances that never share mutable state.
open(instance, csPort, csPin, timeoutMs) selects the peripheral
(SpiInstance::Spi5 today; Spi1-Spi4/Spi6 are declared for other
boards but not yet CubeMX-configured), the chip-select pin using the same
Stm32::GpioPort vocabulary Stm32GpioDriver::open() uses, and a
per-transaction watchdog (default 10 ms, matching Stm32I2cDriver's
convention). It follows the same Common/Real/Stub HAL boundary
convention as every other driver here — see
Stm32SpiDriver's own docs/sdd.md for
the full design.
Not yet wired to a sensor component or validated on real hardware — the
BMP280 wiring follows the exact "Adding a sensor" pattern above (BmpManager
already declares an output port spiReadWrite: Drv.SpiReadWrite, matching
this driver's port type directly with no adapter needed) but is a separate,
not-yet-done checklist item.
The complete reference topology has been run on the physical board with the non-blocking cyclic executive and cooperative dispatch enabled. Validation included:
- Topology setup and all active-component queues created successfully
- No hits on assertion, abort, exit, HardFault, BusFault, or fatal-handler breakpoints during the recorded endurance runs
- PF10 LED activity, driven exclusively through
Drv::Stm32GpioDriver(wired intoinstances.fpp/topology.fppand opened fromconfigureTopology()), confirmed via a GDS-based integration test (led_integration_tests.py) rather than manualGPIOF_ODRregister polling - TIM2 measured at approximately 997.9 kHz over an undisturbed 30-second interval after PLL clock initialization
- The 100 Hz timer tick and rate-group tick counters remained synchronized
- USART1 DMA continued transmitting correctly after the corrected clock was enabled
The clock-tree bug was fixed by calling FprimeStm32_ClockInit() after
HAL_Init(). Before that change, TIM2 advanced at approximately 269.5 kHz,
which matched the unconfigured 64 MHz HSI divided by the intended TIM2
prescaler. The corrected implementation selects PLL1 and restores the intended
480 MHz system clock.
The STM32 linker configuration defines these regions:
| Region | Capacity |
|---|---|
FLASH |
2 MiB |
AXI_SRAM |
512 KiB |
DTCM_RAM |
128 KiB |
CPU-only framework state can be placed in DTCM, while DMA-visible buffers and
the bootstrap allocation pool remain in AXI SRAM. The latest recorded
baremetal-size stm32h7 result for the complete reference deployment was:
| Region | Used | Remaining |
|---|---|---|
Flash (.text+.data) |
645,708 bytes | 69.2% |
AXI SRAM .bss |
261,308 bytes | 50.1% |
DTCM .dtcm_bss |
8,584 bytes | 93.5% |
| Bootstrap pool | 112,656 of 131,072 bytes | 14.1% |
Flash and AXI SRAM .bss grew modestly from the Week 8 led/gpioDriver
topology wiring and the Week 9 Common/Real/Stub driver split (a few new
members per driver); DTCM .dtcm_bss is unchanged byte-for-byte. The
bootstrap-pool row is a runtime allocation count rather than a static ELF
section, so it's carried over from the last hardware run and still needs
live re-verification.
The reference deployment locks the bootstrap allocator after topology setup and wraps the C heap symbols so post-initialization allocations assert instead of silently using an unbounded heap. New components should be evaluated against both the AXI SRAM margin and the remaining bootstrap-pool capacity.
Every driver under Drv/ splits into three files sharing one HAL-free
header, so fprime-util check can compile and run its GTest unit test on
the host (x86_64 Linux) without any ARM/CMSIS toolchain:
<Driver>Common.cpp— hardware-independent logic (validation, ring buffers, state machines, event/telemetry emission). Always built, on every platform. This is where unit tests get real coverage.<Driver>.cpp— the real implementation, built only for thestm32h7target. Every HAL/CMSIS touch (register access,HAL_*calls, ISR callbacks) lives here behind a small set of private boundary methods (namedhw*) declared in the header. This is the only file allowed to#includea vendor CMSIS/HAL header.<Driver>Stub.cpp— built only for host unit tests. Implements the samehw*boundary methods with fixed, no-HAL-dependency behavior (e.g. "always succeeds," a settable fake counter). Never included in a flight build.
Each driver's CMakeLists.txt always registers the production module
(register_fprime_module/register_fprime_library) — only the choice of
<Driver>.cpp vs <Driver>Stub.cpp (and the matching DEPENDS) is
platform-conditional — and always registers register_fprime_ut (never
gated by restrict_platforms, which would make the UT target itself
unreachable and fprime-util check fail with NoTargetFoundException).
FprimeStm32 (the real vendor HAL static library) and its Os/
subdirectory are gated to the stm32h7 target in this directory's own
CMakeLists.txt.
Adding a new driver: don't add #ifdef BUILD_UT/#ifndef to
production code. If the driver only needs HAL calls that map cleanly onto
a boundary method, follow the Common/Real/Stub split above (copy an
existing driver's CMakeLists.txt), storing only a HAL-free instance enum
as a component member and re-resolving the real HAL pointer via
toHalHandle() on every call — never cache it in file-static state,
which would make two simultaneously-open instances of the driver clobber
each other (see "Enabling and selecting peripheral instances" above). If a
routine is genuinely hard to fake (e.g. an ISR callback with no
user-context pointer, like HAL_UART_TxCpltCallback), do what
Stm32UartDriver/STM32Timer do: route it through a public
signalX()/hwArmY() method on the component so a unit test can call it
directly to simulate the hardware event, and — since the callback receives
only a raw HAL handle, not an instance — keep a small fixed-size registry
table ({handle, component} pairs, sized to the instance enum's own
cardinality, indexed by instance) in the real .cpp only, with the
callback doing a short linear scan to resolve which live component
instance owns the handle it was given.
Verification commands:
fprime-util generate --ut -f # regenerate the host/native UT build cache
fprime-util check # from a driver's directory: build + run its UT
fprime-util check --coverage # same, plus a line/function/branch coverage report- Repeat the extended USART1 DMA and GDS soak at the corrected 480 MHz clock.
- Validate TIM2 rollover, interrupt masking, and long-duration stability.
- Continue hardware-in-the-loop automation for the STM32 target.
- Add real persistent file support for the MicroFs-backed services; the current
conservative configuration recognizes only
/bin<N>/file<M>paths. - Re-verify the bootstrap-pool usage figure in "Memory and placement" live on
hardware; it's a runtime allocation count, not a static ELF section, so it
couldn't be refreshed by the host-only
baremetal-sizere-measurement. Stm32I2cDriverhas notest/ut/files yet, despite already following theCommon/Real/Stubsplit (register_fprime_utis scaffolded but commented out in itsCMakeLists.txt) — write them.- Run the full I2C exit-criterion soak on real hardware: 1,000 iterations at 400 kHz with explicit event evidence for every injected error path (NACK, timeout, bus error), not just confirmed-working normal operation.
- If a sensor needs a true repeated START (loses its register pointer across
a STOP), upgrade
writeReadto the sequential IT/DMA API with I2C1's NVIC interrupt enabled — the current STOP-then-START is safe on this single-master bus but isn't electrically a repeated START.