//! 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 { _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 F7BoardBuilder { /// 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(self, _: Selected) -> F7BoardBuilder { F7BoardBuilder { _state: PhantomData, } } /// Selects a validated MAVLink route. Only profile-compatible route types /// have a final [`F7BoardBuilder::build`] implementation. pub fn mavlink(self, _: Selected) -> F7BoardBuilder { 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(self, _: Selected) -> F7BoardBuilder { 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 { /// Finishes a complete supported MPU6000 F722 declaration. pub const fn build(self) -> Result { 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 { /// Finishes a complete supported ICM-42688-P F722 declaration. pub const fn build(self) -> Result { 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; } /// 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); } }