Files
kaptan/platforms/stm32f7/src/lib.rs
T

396 lines
13 KiB
Rust

//! STM32F7 execution platform for declarative Kaptan boards.
//!
//! A board crate selects the safe route descriptors below and invokes
//! [`flight_firmware!`]. The macro emits only a declarative link-time board
//! registration; the one RTIC application for every supported STM32F722
//! profile is defined and compiled in this crate. Consequently RTIC, PAC
//! ownership, interrupt acknowledgement, DMA and atomics remain platform
//! implementation details.
#![no_std]
use core::marker::PhantomData;
pub use kaptan_board::BoardConfig;
use kaptan_board::BoardConfigError;
mod f722;
/// Dependencies used only while the platform expands its private RTIC macro.
/// They stay crate-private: board binaries never name them.
pub(crate) mod __platform_internal {
pub(crate) use cortex_m as cm;
pub(crate) use cortex_m_rt as cmrt;
pub(crate) use kaptan_dma as dma;
#[cfg(not(test))]
pub(crate) use panic_halt as panic;
pub(crate) use rtic as rtic_app;
pub(crate) use stm32f7 as pac;
#[cfg(test)]
pub(crate) mod panic {}
}
/// Construction or validation failure for a declarative F722 board.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum BoardError {
/// Portable board metadata is self-inconsistent.
InvalidConfig(BoardConfigError),
/// Metadata and the selected vertical-sensor route disagree.
VerticalSensorCapabilityMismatch,
/// The declaration requests a service-timer frequency not implemented by
/// the selected F722 platform profile.
UnsupportedServiceFrequency,
/// The platform could not establish a clock, peripheral, or DMA owner.
Initialization,
}
/// MPU6000 component selector.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Mpu6000;
/// The supported MPU6000 SPI1 electrical route before selecting DRDY.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Mpu6000Spi1;
/// The complete MPU6000 SPI1 / PA4 data-ready route.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Mpu6000Spi1Exti4;
impl Mpu6000 {
/// Selects the supported SPI1 MPU6000 connection.
pub const fn on_spi1() -> Mpu6000Spi1 {
Mpu6000Spi1
}
}
impl Mpu6000Spi1 {
/// Completes the supported PA4 data-ready connection.
pub const fn with_drdy(self, _: Pa4) -> Mpu6000Spi1Exti4 {
Mpu6000Spi1Exti4
}
}
/// ICM-42688-P component selector.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Icm42688;
/// The supported ICM-42688-P SPI3 electrical route before selecting DRDY.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Icm42688Spi3;
/// The complete ICM-42688-P SPI3 / PC3 data-ready route.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Icm42688Spi3Exti3;
impl Icm42688 {
/// Selects the supported SPI3 ICM-42688-P connection.
pub const fn on_spi3() -> Icm42688Spi3 {
Icm42688Spi3
}
}
impl Icm42688Spi3 {
/// Completes the supported PC3 data-ready connection.
pub const fn with_drdy(self, _: Pc3) -> Icm42688Spi3Exti3 {
Icm42688Spi3Exti3
}
}
/// MPU6000 data-ready pin selector.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Pa4;
/// ICM-42688-P data-ready pin selector.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Pc3;
/// MAVLink component selector.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Mavlink;
/// Validated MAVLink route using USART1.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct MavlinkUsart1;
/// Validated MAVLink route using USART2.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct MavlinkUsart2;
impl Mavlink {
/// Selects the supported USART1 MAVLink route.
pub const fn on_usart1() -> MavlinkUsart1 {
MavlinkUsart1
}
/// Selects the supported USART2 MAVLink route.
pub const fn on_usart2() -> MavlinkUsart2 {
MavlinkUsart2
}
}
/// Vertical-sensor component selector.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct VerticalSensor;
/// No vertical-sensor connection is selected.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct NoVerticalSensor;
/// Validated MTF-01 Microlink route.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Mtf01Uart4;
/// Validated LIDAR-Lite v3 route.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct LidarLiteV3I2c1;
impl VerticalSensor {
/// Declares no vertical-sensor peripheral.
pub const fn none() -> NoVerticalSensor {
NoVerticalSensor
}
/// Selects the supported MTF-01 Microlink connection.
pub const fn mtf01_on_uart4() -> Mtf01Uart4 {
Mtf01Uart4
}
/// Selects the supported LIDAR-Lite v3 connection.
pub const fn lidar_lite_v3_on_i2c1() -> LidarLiteV3I2c1 {
LidarLiteV3I2c1
}
}
/// Unselected typestate used only while a board declaration is incomplete.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Missing;
/// Safe typestate builder for a declarative F722 board.
///
/// This type has no peripherals, PAC types, interrupt names, DMA identifiers,
/// atomics, or unsafe operations. The F722 platform has one fixed validated
/// clock tree, so only board-selectable electrical routes participate in
/// typestate. A final `build()` exists only for complete supported route
/// combinations, so omitted or cross-wired essential routes fail to compile at
/// the board declaration.
pub struct F7BoardBuilder<Imu = Missing, MavlinkRoute = Missing, Vertical = Missing> {
_state: PhantomData<(Imu, MavlinkRoute, Vertical)>,
}
impl F7BoardBuilder {
/// Starts an empty board declaration.
pub const fn new() -> Self {
Self {
_state: PhantomData,
}
}
}
impl Default for F7BoardBuilder {
fn default() -> Self {
Self::new()
}
}
impl<I, T, V> F7BoardBuilder<I, T, V> {
/// Selects an IMU route descriptor.
///
/// Only a complete, profile-compatible descriptor has a final
/// [`F7BoardBuilder::build`] implementation. Keeping this one selection
/// operation uniform avoids teaching a board author platform profile
/// mechanics merely because the selected IMU model differs.
pub fn imu<Selected>(self, _: Selected) -> F7BoardBuilder<Selected, T, V> {
F7BoardBuilder {
_state: PhantomData,
}
}
/// Selects a validated MAVLink route. Only profile-compatible route types
/// have a final [`F7BoardBuilder::build`] implementation.
pub fn mavlink<Selected>(self, _: Selected) -> F7BoardBuilder<I, Selected, V> {
F7BoardBuilder {
_state: PhantomData,
}
}
/// Selects a validated vertical-sensor route. Its capability is checked
/// against [`F7Board::CONFIG`] before any peripheral is initialized.
pub fn vertical_sensor<Selected>(self, _: Selected) -> F7BoardBuilder<I, T, Selected> {
F7BoardBuilder {
_state: PhantomData,
}
}
}
/// Finite STM32F722 execution profile selected from safe route descriptors.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum F722Profile {
Mpu6000Spi1Usart1,
Icm42688Spi3Usart2,
}
/// Fully validated, non-executing F722 board declaration.
///
/// It is deliberately only a compact route description. Peripheral tokens
/// are taken later by the platform's RTIC initializer and never reach a board
/// crate.
#[doc(hidden)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct F722BoardRoutes {
profile: F722Profile,
vertical: f722::VerticalRoute,
}
impl F722BoardRoutes {
pub(crate) const fn profile(self) -> F722Profile {
self.profile
}
}
macro_rules! impl_mpu6000_routes {
($vertical:ty, $route:expr) => {
impl F7BoardBuilder<Mpu6000Spi1Exti4, MavlinkUsart1, $vertical> {
/// Finishes a complete supported MPU6000 F722 declaration.
pub const fn build(self) -> Result<F722BoardRoutes, BoardError> {
Ok(F722BoardRoutes {
profile: F722Profile::Mpu6000Spi1Usart1,
vertical: $route,
})
}
}
};
}
impl_mpu6000_routes!(Mtf01Uart4, f722::VerticalRoute::Mtf01);
impl_mpu6000_routes!(LidarLiteV3I2c1, f722::VerticalRoute::LidarLiteV3);
impl_mpu6000_routes!(NoVerticalSensor, f722::VerticalRoute::None);
macro_rules! impl_icm42688_routes {
($vertical:ty, $route:expr) => {
impl F7BoardBuilder<Icm42688Spi3Exti3, MavlinkUsart2, $vertical> {
/// Finishes a complete supported ICM-42688-P F722 declaration.
pub const fn build(self) -> Result<F722BoardRoutes, BoardError> {
Ok(F722BoardRoutes {
profile: F722Profile::Icm42688Spi3Usart2,
vertical: $route,
})
}
}
};
}
impl_icm42688_routes!(NoVerticalSensor, f722::VerticalRoute::None);
impl_icm42688_routes!(Mtf01Uart4, f722::VerticalRoute::Mtf01);
impl_icm42688_routes!(LidarLiteV3I2c1, f722::VerticalRoute::LidarLiteV3);
/// Missing and cross-profile route selections are rejected before board I/O is
/// linked, because no matching `build` implementation exists.
///
/// ```compile_fail
/// use platform_stm32f7::{
/// F7BoardBuilder, Mavlink, Mpu6000, Pa4,
/// };
///
/// let _ = F7BoardBuilder::new()
/// .imu(Mpu6000::on_spi1().with_drdy(Pa4))
/// .mavlink(Mavlink::on_usart1())
/// .build(); // vertical route is missing
/// ```
///
/// ```compile_fail
/// use platform_stm32f7::{
/// F7BoardBuilder, Icm42688, Mavlink, Pc3,
/// VerticalSensor,
/// };
///
/// let _ = F7BoardBuilder::new()
/// .imu(Icm42688::on_spi3().with_drdy(Pc3))
/// .mavlink(Mavlink::on_usart1()) // ICM route requires USART2
/// .vertical_sensor(VerticalSensor::none())
/// .build();
/// ```
///
/// ```compile_fail
/// use platform_stm32f7::{
/// F7BoardBuilder, Icm42688, Mavlink, Pc3, VerticalSensor,
/// };
///
/// let _ = F7BoardBuilder::new()
/// .imu(Icm42688::on_spi3().with_drdy(Pc3))
/// .mavlink(Mavlink::on_usart2())
/// .vertical_sensor(VerticalSensor::none())
/// .build(); // MPU6000/USART1 is the only other supported profile.
/// ```
/// Contract implemented by a declarative STM32F722 board crate.
pub trait F7Board {
/// Portable identity, capabilities and outbound data-channel policy.
const CONFIG: BoardConfig;
/// Selects one complete supported F722 route graph.
fn build(platform: F7BoardBuilder) -> Result<F722BoardRoutes, BoardError>;
}
/// Checks portable metadata at compile time.
#[macro_export]
macro_rules! enforce_f7_board_contract {
($board:ty) => {
const _: () = match <$board as $crate::F7Board>::CONFIG.validate() {
Ok(()) => (),
Err(_) => panic!("invalid STM32F7 board configuration"),
};
};
}
/// Installs the one platform-owned RTIC application for a declarative board.
///
/// Board crates can only select one of the reviewed physical-sensor telemetry
/// route descriptors; RTIC ownership stays platform-private.
///
/// The application binds the finite superset of interrupt roles needed by the
/// currently supported F722 profiles. The route declaration selects which
/// roles are initialized; inactive bindings acknowledge nothing. Adding an
/// interrupt role is therefore a platform-only change, not a board task graph.
#[macro_export]
macro_rules! flight_firmware {
($board:ty) => {
$crate::enforce_f7_board_contract!($board);
// The platform RTIC application calls these symbols during `init`.
// They carry only the declarative board contract; RTIC, PAC, DMA,
// atomics, and unsafe operations remain inside this platform crate.
#[unsafe(no_mangle)]
pub static KAPTAN_F722_BOARD_CONFIG: $crate::BoardConfig =
<$board as $crate::F7Board>::CONFIG;
#[unsafe(no_mangle)]
pub extern "Rust" fn kaptan_f722_board_routes()
-> Result<$crate::F722BoardRoutes, $crate::BoardError> {
<$board as $crate::F7Board>::build($crate::F7BoardBuilder::new())
}
#[used]
static KAPTAN_F722_PLATFORM_FLIGHT: fn() = $crate::firmware_anchor;
};
}
/// Link anchor for the platform-owned F722 board-I/O application.
#[doc(hidden)]
pub fn firmware_anchor() {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn route_descriptors_select_the_mpu6000_profile() {
let routes = F7BoardBuilder::new()
.imu(Mpu6000::on_spi1().with_drdy(Pa4))
.mavlink(Mavlink::on_usart1())
.vertical_sensor(VerticalSensor::mtf01_on_uart4())
.build()
.unwrap();
assert_eq!(routes.profile(), F722Profile::Mpu6000Spi1Usart1);
}
#[test]
fn route_descriptors_select_the_icm42688_profile() {
let routes = F7BoardBuilder::new()
.imu(Icm42688::on_spi3().with_drdy(Pc3))
.mavlink(Mavlink::on_usart2())
.vertical_sensor(VerticalSensor::none())
.build()
.unwrap();
assert_eq!(routes.profile(), F722Profile::Icm42688Spi3Usart2);
}
}