consortium-hal-stm32mp2
Consortium HAL for the ST STM32MP2 family: peripheral drivers over
consortium-pac-stm32mp2, with owned peripheral
singletons.
Layering
consortium-hal Peri, PeripheralType, peripherals!, narrow traits
└── consortium-hal-stm32mp2 (this crate) drivers + generated singletons
└── consortium-pac-stm32mp2 register blocks, Interrupt, vector table
Both neighbours are re-exported as hal and pac, so a firmware crate needs one
dependency. interrupts, Interrupt, and — behind rt — the #[interrupt] attribute
macro come straight from the PAC.
Peripherals are owned, not addressed
init() hands out the chip's singletons once, and each driver constructor consumes the
one it drives:
let p = init;
let i2c = new;
Because the base address and NVIC line travel with the singleton, no driver takes an
MMIO base and no constructor is unsafe. init() panics if called twice — handing
the singletons out again would let two drivers claim the same registers.
peripherals::<NAME>::steal() is the escape hatch for bring-up outside init(); it
carries the exclusivity obligation the safe path discharges.
Drivers
Each behind a feature, all on by default:
| Feature | Module | Covers |
|---|---|---|
adc |
adc |
ADC |
can |
can |
FDCAN |
dma |
dma |
DMA (channels come from the chip dump) |
gpio |
gpio |
GPIO / pin handles |
i2c |
i2c |
I2C |
i3c |
i3c |
I3C |
spi |
spi |
SPI |
timer, lptim |
timer, lptim |
General timers, LPTIM (also pwm alias) |
time-driver-gp-tim |
timer_driver |
Embassy time driver over TIM2 |
uart |
uart |
USART, including IRQ-buffered BufferedUart |
watchdog |
watchdog |
IWDG1–4 (MP21), IWDG1–5 (MP25) |
timer_driver is what consortium-cfg brings up in the generated init() unless
[profile] time_driver = false. uart's BufferedUart is what
consortium-ipc-transport-uart-embedded binds to.
Chip selection
stm32mp21x, stm32mp23x, or stm32mp25x — exactly one.
| Feature | Singleton map | PAC chip | Notes |
|---|---|---|---|
stm32mp21x |
STM32MP21 | stm32mp21 |
IPCC1 only; no HSEM |
stm32mp23x |
STM32MP25 | stm32mp257 |
Shares MP25's layout |
stm32mp25x |
STM32MP25 | stm32mp257 |
Default |
MP23 and MP25 share one interrupt and peripheral layout, which is why both map to the same generated file and the same PAC chip. Enabling several is a duplicate-definition error.
A dependent selecting a non-default chip must set default-features = false —
otherwise stm32mp25x comes along too and the PAC's Interrupt enum becomes
ambiguous. Every doorbell and transport crate in this workspace that depends on a chip
HAL does this.
Interrupts are the application's
This crate installs no #[interrupt] handlers. The application owns the vector table
and forwards each vector into the driver's entry point, parameterized with the same
singleton the driver consumed — so there is no global static to name:
Enable the rt feature so the #[interrupt] macro and the PAC vector table are
available to that code. The IPCC and HSEM doorbells
(consortium-ipc-transport-memory::bell) follow the same convention — the non-secure
HSEM and IPCC1_RX/IPCC2_RX vectors go to bell::service_interrupt::<H>() — but
those vectors, like the TIM2
time-driver vector, are generated by consortium-cfg into consortium.gen.rs; only
peripherals the application drives itself need a hand-written forwarder.
Generated code
src/peripherals/<chip>.rs is generated by scripts/pac/gen_peripherals.py from the
same consortium-data chip dumps that feed gen_interrupts.py, and mounted by
lib.rs as the private chip module.
It declares the singletons (consortium_hal::peripherals!) and invokes each driver's
impl_<driver>! macro with that instance's base and NVIC line. The generated file is
data only — the driver module owns the macro and therefore the logic. Re-run the
generator after changing a chip dump; do not hand-edit.
Secure aliases
The singletons carry only the non-secure alias of each peripheral window. That is
why consortium-cfg rejects [peripheral.*] entries with secure = true: there is no
singleton to hand out for the secure view.
The doorbell crates make the same point differently — HsemDoorbell::new(p.HSEM)
consumes the singleton for ownership, but still resolves the window address from the
doorbell crate's own base table, so the security alias it has always used does not
change underfoot.
Testing
Keep this crate firmware-friendly: no_std, narrow unsafe blocks each explaining the
hardware or aliasing invariant, and chip-specific detail here rather than pushed up into
consortium-hal's shared traits.
License
Apache-2.0. See LICENSE.
Watchdog
The watchdog feature exposes an owned Watchdog with new, start, feed,
and is_running. Construction does not modify hardware. Start explicitly after
setting up the peripheral's access permissions, clocks and reset routing:
use ;
let p = init;
let mut watchdog = new;
watchdog.start.unwrap;
// Feed periodically after checking that the application is healthy.
watchdog.feed;
Timeout values are clock ticks, not milliseconds; select them using the actual watchdog clock. Window mode and early-warning interrupts are disabled. Dropping an active watchdog does not stop it. A synchronization error leaves the handle available, since hardware may already be running and still require feeding. Synchronization waits use a bounded status-read budget, not an Embassy timer.
IWDG cannot be disabled after starting until reset. Supported instances are
IWDG1–4 on MP21 and IWDG1–5 on MP25 (also the MP23 map). The separate WWDG
peripheral is not covered. The register API is regenerated with the rest of the PAC
by just pac-regen.
Register sequencing follows STM32CubeMP2's IWDG driver.