platforms/stm32f7: add reusable F722 firmware
This commit is contained in:
1 parent
2ae66c49a8
commit
ac54c8308d
32 files changed
+4006
-248
No files matched your search
@@ -0,0 +1,484 @@
|
||||
//! 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 actuator;
|
||||
mod f722;
|
||||
|
||||
/// Dependencies used only while the platform expands its private RTIC macro.
|
||||
/// They stay crate-private: board binaries never name them.
|
||||
#[cfg(target_arch = "arm")]
|
||||
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;
|
||||
pub(crate) use kaptan_mavlink as mavlink;
|
||||
pub(crate) use kaptan_spi as spi;
|
||||
pub(crate) use panic_halt as panic;
|
||||
pub(crate) use rtic as rtic_app;
|
||||
pub(crate) use stm32f7 as pac;
|
||||
}
|
||||
|
||||
/// The isolated diagnostic macro expands an entry attribute in a board binary.
|
||||
/// Rust macro visibility requires this one dependency to be reachable; it is
|
||||
/// not a platform configuration or execution API.
|
||||
#[cfg(target_arch = "arm")]
|
||||
#[doc(hidden)]
|
||||
pub mod __macro_support {
|
||||
pub use cortex_m_rt;
|
||||
}
|
||||
|
||||
/// 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;
|
||||
|
||||
/// Motor-output component selector.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub struct MotorOutputs;
|
||||
/// Kakute TIM3/TIM5 motor route before selecting a waveform protocol.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub struct KakuteTim3Tim5Motors;
|
||||
/// GEPRC TIM3/TIM5 motor route before selecting a waveform protocol.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub struct GeprcTim3Tim5Motors;
|
||||
/// Validated Kakute TIM3/TIM5 DShot600 motor route.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub struct KakuteTim3Tim5Dshot600;
|
||||
/// Validated GEPRC TIM3/TIM5 DShot600 motor route.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub struct GeprcTim3Tim5Dshot600;
|
||||
|
||||
impl MotorOutputs {
|
||||
/// Selects Kakute's M1–M4 mapping: PB0, PB1, PB4, PB5.
|
||||
pub const fn kakute_tim3_tim5() -> KakuteTim3Tim5Motors {
|
||||
KakuteTim3Tim5Motors
|
||||
}
|
||||
|
||||
/// Selects GEPRC's M1–M4 mapping: PB4, PB5, PB0, PB1.
|
||||
pub const fn geprc_tim3_tim5() -> GeprcTim3Tim5Motors {
|
||||
GeprcTim3Tim5Motors
|
||||
}
|
||||
}
|
||||
|
||||
impl KakuteTim3Tim5Motors {
|
||||
/// Selects the platform-validated DShot600 waveform configuration.
|
||||
pub const fn dshot600(self) -> KakuteTim3Tim5Dshot600 {
|
||||
KakuteTim3Tim5Dshot600
|
||||
}
|
||||
}
|
||||
|
||||
impl GeprcTim3Tim5Motors {
|
||||
/// Selects the platform-validated DShot600 waveform configuration.
|
||||
pub const fn dshot600(self) -> GeprcTim3Tim5Dshot600 {
|
||||
GeprcTim3Tim5Dshot600
|
||||
}
|
||||
}
|
||||
|
||||
/// 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,
|
||||
Motors = Missing,
|
||||
MavlinkRoute = Missing,
|
||||
Vertical = Missing,
|
||||
> {
|
||||
_state: PhantomData<(Imu, Motors, 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, M, T, V> F7BoardBuilder<I, M, 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, M, T, V> {
|
||||
F7BoardBuilder {
|
||||
_state: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Selects the TIM3/TIM5 DShot600 output route.
|
||||
pub fn motors<Selected>(self, _: Selected) -> F7BoardBuilder<I, 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, M, 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, M, 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,
|
||||
motors: f722::MotorRoute,
|
||||
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, KakuteTim3Tim5Dshot600, MavlinkUsart1, $vertical> {
|
||||
/// Finishes a complete supported MPU6000 F722 declaration.
|
||||
pub const fn build(self) -> Result<F722BoardRoutes, BoardError> {
|
||||
Ok(F722BoardRoutes {
|
||||
profile: F722Profile::Mpu6000Spi1Usart1,
|
||||
motors: f722::MotorRoute::KakuteTim3Tim5,
|
||||
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, GeprcTim3Tim5Dshot600, MavlinkUsart2, $vertical> {
|
||||
/// Finishes a complete supported ICM-42688-P F722 declaration.
|
||||
pub const fn build(self) -> Result<F722BoardRoutes, BoardError> {
|
||||
Ok(F722BoardRoutes {
|
||||
profile: F722Profile::Icm42688Spi3Usart2,
|
||||
motors: f722::MotorRoute::GeprcTim3Tim5,
|
||||
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 firmware is
|
||||
/// linked, because no matching `build` implementation exists.
|
||||
///
|
||||
/// ```compile_fail
|
||||
/// use platform_stm32f7::{
|
||||
/// F7BoardBuilder, Mavlink, MotorOutputs, Mpu6000, Pa4,
|
||||
/// };
|
||||
///
|
||||
/// let _ = F7BoardBuilder::new()
|
||||
/// .imu(Mpu6000::on_spi1().with_drdy(Pa4))
|
||||
/// .motors(MotorOutputs::kakute_tim3_tim5().dshot600())
|
||||
/// .mavlink(Mavlink::on_usart1())
|
||||
/// .build(); // vertical route is missing
|
||||
/// ```
|
||||
///
|
||||
/// ```compile_fail
|
||||
/// use platform_stm32f7::{
|
||||
/// F7BoardBuilder, Icm42688, Mavlink, MotorOutputs, Pc3,
|
||||
/// VerticalSensor,
|
||||
/// };
|
||||
///
|
||||
/// let _ = F7BoardBuilder::new()
|
||||
/// .imu(Icm42688::on_spi3().with_drdy(Pc3))
|
||||
/// .motors(MotorOutputs::geprc_tim3_tim5().dshot600())
|
||||
/// .mavlink(Mavlink::on_usart1()) // ICM route requires USART2
|
||||
/// .vertical_sensor(VerticalSensor::none())
|
||||
/// .build();
|
||||
/// ```
|
||||
///
|
||||
/// ```compile_fail
|
||||
/// use platform_stm32f7::{
|
||||
/// F7BoardBuilder, Icm42688, Mavlink, MotorOutputs, Pc3, VerticalSensor,
|
||||
/// };
|
||||
///
|
||||
/// let _ = F7BoardBuilder::new()
|
||||
/// .imu(Icm42688::on_spi3().with_drdy(Pc3))
|
||||
/// .motors(MotorOutputs::kakute_tim3_tim5().dshot600())
|
||||
/// .mavlink(Mavlink::on_usart2())
|
||||
/// .vertical_sensor(VerticalSensor::none())
|
||||
/// .build(); // ICM/USART2 uses the GEPRC logical motor order.
|
||||
/// ```
|
||||
/// 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 platform-owned RTIC flight firmware for a declarative board.
|
||||
///
|
||||
/// 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_FIRMWARE: fn() = $crate::firmware_anchor;
|
||||
};
|
||||
}
|
||||
|
||||
/// Installs the isolated GEPRC UART2 physical-route diagnostic image.
|
||||
///
|
||||
/// This diagnostic never installs the flight application. It remains a
|
||||
/// platform-owned image and is intentionally separate from board declarations.
|
||||
#[macro_export]
|
||||
macro_rules! geprc_uart2_wire_test {
|
||||
() => {
|
||||
#[$crate::__macro_support::cortex_m_rt::entry]
|
||||
fn main() -> ! {
|
||||
$crate::__private::geprc_uart2_wire_test()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Implementation support consumed only by the exported firmware macros.
|
||||
#[doc(hidden)]
|
||||
pub mod __private {
|
||||
pub use crate::f722::geprc_uart2_wire_test;
|
||||
}
|
||||
|
||||
/// Link anchor for the platform-owned F722 RTIC 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))
|
||||
.motors(MotorOutputs::kakute_tim3_tim5().dshot600())
|
||||
.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))
|
||||
.motors(MotorOutputs::geprc_tim3_tim5().dshot600())
|
||||
.mavlink(Mavlink::on_usart2())
|
||||
.vertical_sensor(VerticalSensor::none())
|
||||
.build()
|
||||
.unwrap();
|
||||
assert_eq!(routes.profile(), F722Profile::Icm42688Spi3Usart2);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user