consortium-hal-stm32mp2 0.5.0

Consortium HAL for STM32MP2 family devices
Documentation

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 = consortium_hal_stm32mp2::init();

let i2c = consortium_hal_stm32mp2::i2c::I2c::new(p.I2C1, 64_000_000, 400_000);

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:

#[interrupt]
fn USART2() {
    consortium_hal_stm32mp2::uart::on_buffered_interrupt::<peripherals::USART2>();
}

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

cargo clippy -p consortium-hal-stm32mp2 --all-targets -- -D warnings
just check-ipc-m8    # thumbv8m.main-none-eabihf, the CM33 target

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 consortium_hal_stm32mp2::watchdog::{Config, Watchdog};

let p = consortium_hal_stm32mp2::init();
let mut watchdog = Watchdog::new(p.IWDG3);
watchdog.start(Config { reload: 4095, ..Config::default() }).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.