platform: inhibit motor pins and remove output routes
This commit is contained in:
1 parent
5cb4b7df68
commit
b0d22b61d8
19 files changed
+426
-1126
No files matched your search
@@ -1,6 +1,6 @@
|
||||
//! Profile-independent STM32F722 flight state and ownership graph.
|
||||
//! Physical-sensor telemetry STM32F722 board-I/O state and ownership graph.
|
||||
|
||||
use core::sync::atomic::{AtomicBool, AtomicU8, Ordering};
|
||||
use core::sync::atomic::{AtomicU8, Ordering};
|
||||
|
||||
use super::peripherals::{
|
||||
DwtMicrosecondClock, I2c1, I2c1Config, ReceiveInterrupt, Tim2Periodic, Uart4,
|
||||
@@ -9,7 +9,6 @@ use super::peripherals::{
|
||||
configure_uart4_pins_pa0_pa1, configure_usart1_rx_pin_pa10, configure_usart1_tx_pin_pa9,
|
||||
configure_usart2_pins_pa2_pa3,
|
||||
};
|
||||
use kaptan_actuator::{ArmingDuration, MotorProtocol};
|
||||
use kaptan_board::{BoardConfig, VerticalSensingKind};
|
||||
use kaptan_data_channel::{
|
||||
DataChannel, DataSources, LatestImuMailbox, LatestOpticalFlowMailbox, LatestRangeMailbox,
|
||||
@@ -18,17 +17,13 @@ use kaptan_drivers::{
|
||||
lidar_lite_v3::{AcquisitionMode, LidarLiteV3, LidarLiteV3Service, LidarLiteV3ServiceEvent},
|
||||
mtf01::Mtf01Service,
|
||||
};
|
||||
use kaptan_mavlink::{
|
||||
commands::arm::{ArmRequest, decode_arm_request},
|
||||
receiver::MavlinkReceiver,
|
||||
};
|
||||
use kaptan_spi::SpiCompletion;
|
||||
use stm32f7::stm32f722;
|
||||
|
||||
use crate::{BoardError, F722BoardRoutes, F722Profile};
|
||||
|
||||
use super::{
|
||||
MotorRoute, VerticalRoute, clock,
|
||||
VerticalRoute, clock,
|
||||
data_channel::{
|
||||
F722MavlinkTransmitter, F722StatusLed, GeprcStatusLed, SERVICE_HZ, SERVICE_TIMER_CONFIG,
|
||||
vertical_data_config,
|
||||
@@ -42,24 +37,6 @@ const MTF01_BAUD: u32 = 115_200;
|
||||
const I2C1_CONFIG: I2c1Config = I2c1Config::from_timing_register(0x2040_4768);
|
||||
const LIDAR_MODE: AcquisitionMode = AcquisitionMode::WithReceiverBiasCorrection;
|
||||
const LIDAR_STARTUP_TICKS: u8 = 3;
|
||||
const MOTOR_PROTOCOL: MotorProtocol = MotorProtocol::Dshot(
|
||||
match kaptan_actuator::dshot::Config::new(
|
||||
kaptan_actuator::dshot::Rate::Dshot600,
|
||||
match kaptan_actuator::dshot::Throttle::new(300) {
|
||||
Some(throttle) => throttle,
|
||||
None => panic!("armed idle throttle must be valid"),
|
||||
},
|
||||
match ArmingDuration::from_millis(2_000) {
|
||||
Some(duration) => duration,
|
||||
None => panic!("arming duration must be valid"),
|
||||
},
|
||||
) {
|
||||
Some(config) => config,
|
||||
None => panic!("DShot motor protocol must be valid"),
|
||||
},
|
||||
);
|
||||
|
||||
static IMU_HEALTHY: AtomicBool = AtomicBool::new(false);
|
||||
static UART_COMPLETION: AtomicU8 = AtomicU8::new(0);
|
||||
static SAMPLES: LatestImuMailbox = LatestImuMailbox::new();
|
||||
static RANGES: LatestRangeMailbox = LatestRangeMailbox::new();
|
||||
@@ -75,13 +52,11 @@ pub type F722ServiceTimer = Tim2Periodic;
|
||||
|
||||
/// RTIC-shared MAVLink state.
|
||||
///
|
||||
/// Only USART receive and the MAVLink worker access this state, so its
|
||||
/// priority ceiling is 9 rather than the IMU or vertical-sensor IRQ ceiling.
|
||||
/// Only the MAVLink worker accesses this state, so its priority ceiling is 9
|
||||
/// rather than the IMU or vertical-sensor IRQ ceiling.
|
||||
pub struct F722MavlinkState {
|
||||
receiver: MavlinkReceiver<4>,
|
||||
data_channel: DataChannel<'static, F722MavlinkTransmitter, SERVICE_HZ>,
|
||||
led: F722StatusLed,
|
||||
config: BoardConfig,
|
||||
}
|
||||
|
||||
/// RTIC-shared LIDAR state.
|
||||
@@ -104,7 +79,6 @@ pub struct F722InitResources {
|
||||
pub(crate) timer: F722ServiceTimer,
|
||||
pub(crate) lidar: F722LidarState,
|
||||
pub(crate) mavlink: F722MavlinkState,
|
||||
pub(crate) motors: crate::actuator::F722MotorOutput,
|
||||
}
|
||||
|
||||
/// Validates the relationship between portable metadata and selected sensor
|
||||
@@ -113,7 +87,8 @@ pub(crate) fn validate_vertical_route(
|
||||
config: BoardConfig,
|
||||
routes: F722BoardRoutes,
|
||||
) -> Result<(), BoardError> {
|
||||
if config.data_channel.service_hz != SERVICE_HZ {
|
||||
let telemetry = config.telemetry().ok_or(BoardError::Initialization)?;
|
||||
if telemetry.data_channel.service_hz != SERVICE_HZ {
|
||||
return Err(BoardError::UnsupportedServiceFrequency);
|
||||
}
|
||||
let expected = match routes.vertical {
|
||||
@@ -121,7 +96,7 @@ pub(crate) fn validate_vertical_route(
|
||||
VerticalRoute::Mtf01 => VerticalSensingKind::RangeAndOpticalFlow,
|
||||
VerticalRoute::LidarLiteV3 => VerticalSensingKind::Range,
|
||||
};
|
||||
if config.vertical_sensing != expected {
|
||||
if telemetry.vertical_sensing != expected {
|
||||
return Err(BoardError::VerticalSensorCapabilityMismatch);
|
||||
}
|
||||
Ok(())
|
||||
@@ -136,16 +111,15 @@ pub(crate) fn initialize(
|
||||
let clock = clock::initialize(&mut core, &device).map_err(|_| BoardError::Initialization)?;
|
||||
|
||||
// The profile is written once before any interrupt is enabled. ISR helpers
|
||||
// read it with Acquire ordering and only access a route initialized by the
|
||||
// matching branch below.
|
||||
// read it with Acquire ordering and only access the initialized route.
|
||||
interrupts::activate_profile(routes.profile());
|
||||
|
||||
match routes.profile() {
|
||||
F722Profile::Mpu6000Spi1Usart1 => {
|
||||
initialize_mpu6000(device, clock, config, routes.motors, routes.vertical)
|
||||
initialize_mpu6000(device, clock, config, routes.vertical)
|
||||
}
|
||||
F722Profile::Icm42688Spi3Usart2 => {
|
||||
initialize_icm42688(device, clock, config, routes.motors, routes.vertical)
|
||||
initialize_icm42688(device, clock, config, routes.vertical)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -154,7 +128,6 @@ fn initialize_mpu6000(
|
||||
device: stm32f722::Peripherals,
|
||||
clock: DwtMicrosecondClock,
|
||||
config: BoardConfig,
|
||||
motors: MotorRoute,
|
||||
vertical: VerticalRoute,
|
||||
) -> Result<F722InitResources, BoardError> {
|
||||
configure_spi1_pins_pa5_pa6_pa7(&device.RCC, &device.GPIOA);
|
||||
@@ -172,7 +145,13 @@ fn initialize_mpu6000(
|
||||
clock.fork(),
|
||||
vertical,
|
||||
)?;
|
||||
let imu = F722Imu::mpu6000(&device.RCC, device.SPI1, device.DMA2, device.GPIOB, clock);
|
||||
let imu = F722Imu::mpu6000(
|
||||
&device.RCC,
|
||||
device.SPI1,
|
||||
device.DMA2,
|
||||
device.GPIOB,
|
||||
clock.fork(),
|
||||
);
|
||||
let transmitter = F722MavlinkTransmitter::usart1(&device.RCC, device.USART1)
|
||||
.map_err(|_| BoardError::Initialization)?;
|
||||
let led = F722StatusLed::Kakute(super::peripherals::ActiveLowLedPd2::new(
|
||||
@@ -182,10 +161,7 @@ fn initialize_mpu6000(
|
||||
finish(
|
||||
&device.RCC,
|
||||
device.TIM2,
|
||||
device.TIM3,
|
||||
device.TIM5,
|
||||
config,
|
||||
motors,
|
||||
vertical,
|
||||
imu,
|
||||
range,
|
||||
@@ -200,7 +176,6 @@ fn initialize_icm42688(
|
||||
device: stm32f722::Peripherals,
|
||||
clock: DwtMicrosecondClock,
|
||||
config: BoardConfig,
|
||||
motors: MotorRoute,
|
||||
vertical: VerticalRoute,
|
||||
) -> Result<F722InitResources, BoardError> {
|
||||
configure_spi3_pins_pc10_pc11_pc12(&device.RCC, &device.GPIOC);
|
||||
@@ -224,17 +199,20 @@ fn initialize_icm42688(
|
||||
clock.fork(),
|
||||
vertical,
|
||||
)?;
|
||||
let imu = F722Imu::icm42688(&device.RCC, device.SPI3, device.DMA1, &device.GPIOA, clock);
|
||||
let imu = F722Imu::icm42688(
|
||||
&device.RCC,
|
||||
device.SPI3,
|
||||
device.DMA1,
|
||||
&device.GPIOA,
|
||||
clock.fork(),
|
||||
);
|
||||
let transmitter = F722MavlinkTransmitter::usart2(&device.RCC, device.USART2)
|
||||
.map_err(|_| BoardError::Initialization)?;
|
||||
let led = F722StatusLed::Geprc(GeprcStatusLed::new(&device.RCC, device.GPIOA));
|
||||
finish(
|
||||
&device.RCC,
|
||||
device.TIM2,
|
||||
device.TIM3,
|
||||
device.TIM5,
|
||||
config,
|
||||
motors,
|
||||
vertical,
|
||||
imu,
|
||||
range,
|
||||
@@ -249,10 +227,7 @@ fn initialize_icm42688(
|
||||
fn finish(
|
||||
rcc: &stm32f722::RCC,
|
||||
tim2: stm32f722::TIM2,
|
||||
tim3: stm32f722::TIM3,
|
||||
tim5: stm32f722::TIM5,
|
||||
config: BoardConfig,
|
||||
motors: MotorRoute,
|
||||
vertical: VerticalRoute,
|
||||
imu: F722Imu,
|
||||
range: Option<F722Range>,
|
||||
@@ -281,13 +256,7 @@ fn finish(
|
||||
range,
|
||||
startup_ticks: 0,
|
||||
},
|
||||
mavlink: F722MavlinkState {
|
||||
receiver: MavlinkReceiver::new(),
|
||||
data_channel,
|
||||
led,
|
||||
config,
|
||||
},
|
||||
motors: crate::actuator::F722MotorOutput::new(rcc, tim3, tim5, MOTOR_PROTOCOL, motors),
|
||||
mavlink: F722MavlinkState { data_channel, led },
|
||||
})
|
||||
}
|
||||
|
||||
@@ -439,42 +408,21 @@ pub(crate) fn process_imu(imu: &mut F722Imu, events: u8) {
|
||||
fn accept_imu_event(event: ImuEvent) {
|
||||
match event {
|
||||
ImuEvent::Sample(sample) => {
|
||||
IMU_HEALTHY.store(true, Ordering::Release);
|
||||
SAMPLES.publish(sample);
|
||||
}
|
||||
ImuEvent::Fault => IMU_HEALTHY.store(false, Ordering::Release),
|
||||
ImuEvent::Fault => {}
|
||||
ImuEvent::Idle | ImuEvent::Initialized => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn imu_is_healthy() -> bool {
|
||||
IMU_HEALTHY.load(Ordering::Acquire)
|
||||
}
|
||||
|
||||
impl F722MavlinkState {
|
||||
pub(crate) fn on_mavlink_byte(&mut self, receive: ReceiveInterrupt) -> bool {
|
||||
match receive {
|
||||
ReceiveInterrupt::Byte(byte) => self.receiver.receive_byte(byte),
|
||||
ReceiveInterrupt::Fault => {
|
||||
self.receiver.reset();
|
||||
false
|
||||
}
|
||||
ReceiveInterrupt::NotPending => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Handles the bounded DMA and timer work that does not need the motor
|
||||
/// resource. `motors_armed` is sampled under its separate RTIC lock.
|
||||
pub(crate) fn process_worker_events(&mut self, events: u8, motors_armed: bool) {
|
||||
/// Handles bounded DMA and timer work for the telemetry image.
|
||||
pub(crate) fn process_worker_events(&mut self, events: u8) {
|
||||
const TIMER: u8 = 1 << 0;
|
||||
const DMA: u8 = 1 << 2;
|
||||
|
||||
if events & DMA != 0 {
|
||||
let completion = match UART_COMPLETION.swap(0, Ordering::AcqRel) {
|
||||
1 => kaptan_dma::TxCompletion::Complete,
|
||||
2 => kaptan_dma::TxCompletion::TransferFault,
|
||||
_ => kaptan_dma::TxCompletion::NotPending,
|
||||
};
|
||||
let completion = take_mavlink_completion();
|
||||
self.data_channel.on_dma_complete(completion);
|
||||
if completion == kaptan_dma::TxCompletion::Complete {
|
||||
self.led.toggle();
|
||||
@@ -482,31 +430,21 @@ impl F722MavlinkState {
|
||||
}
|
||||
|
||||
if events & TIMER != 0 {
|
||||
// The board is initialized and transmitting physical telemetry,
|
||||
// but has no flight runtime or output authority. `STANDBY` with
|
||||
// an unarmed base mode communicates that accurately; `UNINIT`
|
||||
// would incorrectly claim initialization never completed.
|
||||
self.data_channel
|
||||
.on_service_tick(IMU_HEALTHY.load(Ordering::Acquire), motors_armed);
|
||||
.on_service_tick(kaptan_mavlink::message::MavState::MAV_STATE_STANDBY, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes already-decoded non-arm frames and returns one arm command at a
|
||||
/// time. The worker releases this resource before taking the motor lock.
|
||||
pub(crate) fn next_arm_request(&mut self) -> Option<ArmRequest> {
|
||||
while let Some(frame) = self.receiver.take_received() {
|
||||
if let Some(request) = decode_arm_request(
|
||||
&frame,
|
||||
self.config.mavlink.system_id,
|
||||
self.config.mavlink.component_id,
|
||||
) {
|
||||
return Some(request);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub(crate) fn acknowledge_arm_request(&mut self, result: kaptan_mavlink::message::MavResult) {
|
||||
self.data_channel.acknowledge_command(
|
||||
kaptan_mavlink::message::MavCmd::MAV_CMD_COMPONENT_ARM_DISARM,
|
||||
result,
|
||||
);
|
||||
pub(crate) fn take_mavlink_completion() -> kaptan_dma::TxCompletion {
|
||||
match UART_COMPLETION.swap(0, Ordering::AcqRel) {
|
||||
1 => kaptan_dma::TxCompletion::Complete,
|
||||
2 => kaptan_dma::TxCompletion::TransferFault,
|
||||
_ => kaptan_dma::TxCompletion::NotPending,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -530,14 +468,16 @@ fn publish_lidar_event<E>(event: LidarLiteV3ServiceEvent<E>) {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::validate_vertical_route;
|
||||
use crate::{F7BoardBuilder, Mavlink, MotorOutputs, Mpu6000, Pa4, VerticalSensor};
|
||||
use kaptan_board::{BoardConfig, DataChannelRates, MavlinkEndpoint, VerticalSensingKind};
|
||||
use crate::{F7BoardBuilder, Mavlink, Mpu6000, Pa4, VerticalSensor};
|
||||
use kaptan_board::{
|
||||
BoardConfig, BoardProfile, DataChannelRates, MavlinkEndpoint, TelemetryProfile,
|
||||
VerticalSensingKind,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn rejects_metadata_that_claims_flow_for_a_lidar_route() {
|
||||
let routes = F7BoardBuilder::new()
|
||||
.imu(Mpu6000::on_spi1().with_drdy(Pa4))
|
||||
.motors(MotorOutputs::kakute_tim3_tim5().dshot600())
|
||||
.mavlink(Mavlink::on_usart1())
|
||||
.vertical_sensor(VerticalSensor::lidar_lite_v3_on_i2c1())
|
||||
.build()
|
||||
@@ -548,14 +488,16 @@ mod tests {
|
||||
system_id: 1,
|
||||
component_id: 1,
|
||||
},
|
||||
vertical_sensing: VerticalSensingKind::RangeAndOpticalFlow,
|
||||
data_channel: DataChannelRates {
|
||||
service_hz: 100,
|
||||
raw_imu_hz: 20,
|
||||
range_hz: Some(20),
|
||||
optical_flow_hz: Some(20),
|
||||
heartbeat_hz: 2,
|
||||
},
|
||||
profile: BoardProfile::TelemetryOnly(TelemetryProfile {
|
||||
vertical_sensing: VerticalSensingKind::RangeAndOpticalFlow,
|
||||
data_channel: DataChannelRates {
|
||||
service_hz: 100,
|
||||
raw_imu_hz: 20,
|
||||
range_hz: Some(20),
|
||||
optical_flow_hz: Some(20),
|
||||
heartbeat_hz: 2,
|
||||
},
|
||||
}),
|
||||
};
|
||||
assert_eq!(
|
||||
validate_vertical_route(config, routes),
|
||||
|
||||
@@ -9,6 +9,11 @@ use kaptan_mavlink::{
|
||||
transmitter::{FrameClass, MavlinkTransmitter},
|
||||
};
|
||||
|
||||
// TODO(configuration): transport baud rate, sensor orientation/range, and
|
||||
// freshness are currently development profile constants. A ground-station
|
||||
// configuration flow must stage them with sensor calibration and link
|
||||
// authority, apply them only with output inhibited, then reset estimator,
|
||||
// controller, allocator, and safety prearm state as one revision.
|
||||
pub(crate) const UART1_CLOCK_HZ: u32 = 108_000_000;
|
||||
pub(crate) const UART2_CLOCK_HZ: u32 = 54_000_000;
|
||||
pub(crate) const MAVLINK_BAUD: u32 = 115_200;
|
||||
@@ -21,7 +26,10 @@ pub(crate) const SERVICE_TIMER_CONFIG: peripherals::Tim2PeriodicConfig =
|
||||
auto_reload: 99,
|
||||
};
|
||||
|
||||
/// Maps a selected vertical electrical route to complete data-channel metadata.
|
||||
/// Maps a selected vertical electrical route to development data-channel metadata.
|
||||
///
|
||||
/// The values are telemetry descriptions only; they neither calibrate a flight
|
||||
/// estimator nor authorize an `ExternalControlOnly` profile.
|
||||
pub(crate) fn vertical_data_config(route: VerticalRoute) -> VerticalDataConfig {
|
||||
let distance =
|
||||
|minimum_distance_centimetres, maximum_distance_centimetres| DistanceSensorConfig {
|
||||
@@ -104,8 +112,12 @@ impl FrameSink for F722MavlinkTransmitter {
|
||||
|
||||
fn on_dma_complete(&mut self, completion: TxCompletion) {
|
||||
match self {
|
||||
Self::Usart1(transmitter) => transmitter.on_dma_complete(completion),
|
||||
Self::Usart2(transmitter) => transmitter.on_dma_complete(completion),
|
||||
Self::Usart1(transmitter) => {
|
||||
let _ = transmitter.on_dma_complete(completion);
|
||||
}
|
||||
Self::Usart2(transmitter) => {
|
||||
let _ = transmitter.on_dma_complete(completion);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,16 +24,15 @@ macro_rules! f722_flight_application {
|
||||
use core::sync::atomic::{AtomicBool, AtomicU8, Ordering};
|
||||
|
||||
use $crate::f722::{
|
||||
F722Imu, F722InitResources, F722LidarState, F722MavlinkState, F722Mtf01,
|
||||
F722Mtf01Uart, F722ServiceTimer, acknowledge_exti3, acknowledge_exti4,
|
||||
F722Imu, F722ImuTransferCompletion, F722InitResources, F722LidarState,
|
||||
F722MavlinkState, F722Mtf01, F722Mtf01Uart, F722ServiceTimer,
|
||||
acknowledge_exti3, acknowledge_exti4,
|
||||
acknowledge_imu_rx_dma1, acknowledge_imu_rx_dma2, acknowledge_imu_spi1_error,
|
||||
acknowledge_imu_spi3_error, acknowledge_imu_tx_dma1, acknowledge_imu_tx_dma2,
|
||||
acknowledge_mavlink_dma1, acknowledge_mavlink_dma2, acknowledge_mavlink_rx_usart1,
|
||||
acknowledge_mavlink_rx_usart2, imu_is_healthy, initialize, on_uart4_interrupt,
|
||||
process_imu, record_mavlink_completion,
|
||||
acknowledge_mavlink_rx_usart2, initialize, on_uart4_interrupt, process_imu,
|
||||
record_mavlink_completion,
|
||||
};
|
||||
use $crate::actuator::F722MotorOutput;
|
||||
use $crate::__platform_internal::spi::SpiCompletion;
|
||||
#[allow(unused_imports)]
|
||||
use $crate::__platform_internal::rtic_app::Mutex;
|
||||
|
||||
@@ -44,7 +43,6 @@ macro_rules! f722_flight_application {
|
||||
const IMU_EVENT_START: u8 = 1 << 4;
|
||||
const MAVLINK_EVENT_TIMER: u8 = 1 << 0;
|
||||
const MAVLINK_EVENT_DMA: u8 = 1 << 2;
|
||||
const MAVLINK_EVENT_RECEIVED: u8 = 1 << 3;
|
||||
|
||||
// Hardware IRQs and both software workers coalesce event bits.
|
||||
// Release/Acquire pairs make each terminal acknowledgement visible
|
||||
@@ -58,7 +56,6 @@ macro_rules! f722_flight_application {
|
||||
#[shared]
|
||||
struct Shared {
|
||||
mavlink: F722MavlinkState,
|
||||
motors: F722MotorOutput,
|
||||
lidar: F722LidarState,
|
||||
}
|
||||
|
||||
@@ -80,14 +77,12 @@ macro_rules! f722_flight_application {
|
||||
timer,
|
||||
lidar,
|
||||
mavlink,
|
||||
motors,
|
||||
} = initialize(context.core, context.device, $config(), routes)
|
||||
.unwrap_or_else(|_| safe_idle());
|
||||
schedule_imu(IMU_EVENT_START);
|
||||
(
|
||||
Shared {
|
||||
mavlink,
|
||||
motors,
|
||||
lidar,
|
||||
},
|
||||
Local {
|
||||
@@ -123,15 +118,15 @@ macro_rules! f722_flight_application {
|
||||
#[task(binds = DMA2_STREAM0, priority = 14)]
|
||||
fn imu_rx_dma2(_: imu_rx_dma2::Context) {
|
||||
match acknowledge_imu_rx_dma2() {
|
||||
SpiCompletion::Complete => schedule_imu(IMU_EVENT_TRANSFER_COMPLETE),
|
||||
SpiCompletion::TransferFault => schedule_imu(IMU_EVENT_TRANSFER_FAULT),
|
||||
SpiCompletion::NotPending => {}
|
||||
F722ImuTransferCompletion::Complete => schedule_imu(IMU_EVENT_TRANSFER_COMPLETE),
|
||||
F722ImuTransferCompletion::TransferFault => schedule_imu(IMU_EVENT_TRANSFER_FAULT),
|
||||
F722ImuTransferCompletion::NotPending => {}
|
||||
}
|
||||
}
|
||||
|
||||
#[task(binds = DMA2_STREAM3, priority = 14)]
|
||||
fn imu_tx_dma2(_: imu_tx_dma2::Context) {
|
||||
if acknowledge_imu_tx_dma2() == SpiCompletion::TransferFault {
|
||||
if acknowledge_imu_tx_dma2() == F722ImuTransferCompletion::TransferFault {
|
||||
schedule_imu(IMU_EVENT_TRANSFER_FAULT);
|
||||
}
|
||||
}
|
||||
@@ -146,15 +141,15 @@ macro_rules! f722_flight_application {
|
||||
#[task(binds = DMA1_STREAM0, priority = 14)]
|
||||
fn imu_rx_dma1(_: imu_rx_dma1::Context) {
|
||||
match acknowledge_imu_rx_dma1() {
|
||||
SpiCompletion::Complete => schedule_imu(IMU_EVENT_TRANSFER_COMPLETE),
|
||||
SpiCompletion::TransferFault => schedule_imu(IMU_EVENT_TRANSFER_FAULT),
|
||||
SpiCompletion::NotPending => {}
|
||||
F722ImuTransferCompletion::Complete => schedule_imu(IMU_EVENT_TRANSFER_COMPLETE),
|
||||
F722ImuTransferCompletion::TransferFault => schedule_imu(IMU_EVENT_TRANSFER_FAULT),
|
||||
F722ImuTransferCompletion::NotPending => {}
|
||||
}
|
||||
}
|
||||
|
||||
#[task(binds = DMA1_STREAM5, priority = 14)]
|
||||
fn imu_tx_dma1(_: imu_tx_dma1::Context) {
|
||||
if acknowledge_imu_tx_dma1() == SpiCompletion::TransferFault {
|
||||
if acknowledge_imu_tx_dma1() == F722ImuTransferCompletion::TransferFault {
|
||||
schedule_imu(IMU_EVENT_TRANSFER_FAULT);
|
||||
}
|
||||
}
|
||||
@@ -171,16 +166,6 @@ macro_rules! f722_flight_application {
|
||||
on_uart4_interrupt(context.local.mtf01);
|
||||
}
|
||||
|
||||
#[task(binds = TIM3, priority = 12, shared = [motors])]
|
||||
fn motor_update(mut context: motor_update::Context) {
|
||||
context.shared.motors.lock(F722MotorOutput::on_tim3_interrupt);
|
||||
}
|
||||
|
||||
#[task(binds = TIM5, priority = 12, shared = [motors])]
|
||||
fn motor_scheduler(mut context: motor_scheduler::Context) {
|
||||
context.shared.motors.lock(F722MotorOutput::on_tim5_interrupt);
|
||||
}
|
||||
|
||||
#[task(binds = I2C1_EV, priority = 11, shared = [lidar])]
|
||||
fn i2c1_event(mut context: i2c1_event::Context) {
|
||||
context
|
||||
@@ -215,26 +200,20 @@ macro_rules! f722_flight_application {
|
||||
}
|
||||
}
|
||||
|
||||
#[task(binds = USART1, priority = 8, shared = [mavlink])]
|
||||
fn mavlink_rx_usart1(mut context: mavlink_rx_usart1::Context) {
|
||||
let admitted = context
|
||||
.shared
|
||||
.mavlink
|
||||
.lock(|mavlink| mavlink.on_mavlink_byte(acknowledge_mavlink_rx_usart1()));
|
||||
if admitted {
|
||||
schedule_mavlink(MAVLINK_EVENT_RECEIVED);
|
||||
}
|
||||
#[task(binds = USART1, priority = 8)]
|
||||
fn mavlink_rx_usart1(_: mavlink_rx_usart1::Context) {
|
||||
// `ExternalControlOnly` is rejected and production signing is
|
||||
// not implemented, so this image has no inbound authority
|
||||
// path. Acknowledge every RX condition to keep the peripheral
|
||||
// quiescent without taking any RTIC-shared flight state.
|
||||
let _ = acknowledge_mavlink_rx_usart1();
|
||||
}
|
||||
|
||||
#[task(binds = USART2, priority = 8, shared = [mavlink])]
|
||||
fn mavlink_rx_usart2(mut context: mavlink_rx_usart2::Context) {
|
||||
let admitted = context
|
||||
.shared
|
||||
.mavlink
|
||||
.lock(|mavlink| mavlink.on_mavlink_byte(acknowledge_mavlink_rx_usart2()));
|
||||
if admitted {
|
||||
schedule_mavlink(MAVLINK_EVENT_RECEIVED);
|
||||
}
|
||||
#[task(binds = USART2, priority = 8)]
|
||||
fn mavlink_rx_usart2(_: mavlink_rx_usart2::Context) {
|
||||
// See USART1: inbound bytes are drained but never decoded or
|
||||
// dispatched by the telemetry-only image.
|
||||
let _ = acknowledge_mavlink_rx_usart2();
|
||||
}
|
||||
|
||||
#[task(binds = TIM2, priority = 4, local = [timer], shared = [lidar])]
|
||||
@@ -258,38 +237,13 @@ macro_rules! f722_flight_application {
|
||||
}
|
||||
}
|
||||
|
||||
#[task(priority = 9, shared = [mavlink, motors])]
|
||||
#[task(priority = 9, shared = [mavlink])]
|
||||
async fn mavlink_worker(mut context: mavlink_worker::Context) {
|
||||
let events = MAVLINK_EVENTS.swap(0, Ordering::AcqRel);
|
||||
let motors_armed = context.shared.motors.lock(|motors| motors.is_armed());
|
||||
context
|
||||
.shared
|
||||
.mavlink
|
||||
.lock(|mavlink| mavlink.process_worker_events(events, motors_armed));
|
||||
if events & MAVLINK_EVENT_RECEIVED != 0 {
|
||||
while let Some(request) =
|
||||
context.shared.mavlink.lock(F722MavlinkState::next_arm_request)
|
||||
{
|
||||
let result = match request {
|
||||
$crate::__platform_internal::mavlink::commands::arm::ArmRequest::Arm if !imu_is_healthy() =>
|
||||
{
|
||||
$crate::__platform_internal::mavlink::message::MavResult::MAV_RESULT_DENIED
|
||||
}
|
||||
$crate::__platform_internal::mavlink::commands::arm::ArmRequest::Arm => {
|
||||
context.shared.motors.lock(F722MotorOutput::arm);
|
||||
$crate::__platform_internal::mavlink::message::MavResult::MAV_RESULT_ACCEPTED
|
||||
}
|
||||
$crate::__platform_internal::mavlink::commands::arm::ArmRequest::Disarm => {
|
||||
context.shared.motors.lock(F722MotorOutput::disarm);
|
||||
$crate::__platform_internal::mavlink::message::MavResult::MAV_RESULT_ACCEPTED
|
||||
}
|
||||
};
|
||||
context
|
||||
.shared
|
||||
.mavlink
|
||||
.lock(|mavlink| mavlink.acknowledge_arm_request(result));
|
||||
}
|
||||
}
|
||||
.lock(|mavlink| mavlink.process_worker_events(events));
|
||||
MAVLINK_QUEUED.store(false, Ordering::Release);
|
||||
if MAVLINK_EVENTS.load(Ordering::Acquire) != 0
|
||||
&& !MAVLINK_QUEUED.swap(true, Ordering::AcqRel)
|
||||
|
||||
@@ -2,11 +2,12 @@
|
||||
|
||||
use core::sync::atomic::{AtomicU8, Ordering};
|
||||
|
||||
use super::peripherals::{
|
||||
ReceiveInterrupt, Spi1, Spi3, Usart1Tx, Usart1TxDma, Usart2Tx, Usart2TxDma,
|
||||
use super::peripherals::{Spi1, Spi3, Usart2Tx, Usart2TxDma};
|
||||
use super::{
|
||||
F722ImuTransferCompletion,
|
||||
peripherals::{ReceiveInterrupt, Usart1Tx, Usart1TxDma},
|
||||
};
|
||||
use kaptan_dma::TxCompletion;
|
||||
use kaptan_spi::SpiCompletion;
|
||||
use stm32f7::stm32f722;
|
||||
|
||||
use crate::F722Profile;
|
||||
@@ -59,40 +60,36 @@ pub fn acknowledge_exti3() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
pub fn acknowledge_imu_rx_dma2() -> SpiCompletion {
|
||||
pub fn acknowledge_imu_rx_dma2() -> F722ImuTransferCompletion {
|
||||
if active_profile() == PROFILE_MPU6000_SPI1_USART1 {
|
||||
Spi1::acknowledge_rx_interrupt()
|
||||
} else {
|
||||
SpiCompletion::NotPending
|
||||
return Spi1::acknowledge_rx_interrupt().into();
|
||||
}
|
||||
F722ImuTransferCompletion::NotPending
|
||||
}
|
||||
|
||||
pub fn acknowledge_imu_tx_dma2() -> SpiCompletion {
|
||||
pub fn acknowledge_imu_tx_dma2() -> F722ImuTransferCompletion {
|
||||
if active_profile() == PROFILE_MPU6000_SPI1_USART1 {
|
||||
Spi1::acknowledge_tx_interrupt()
|
||||
} else {
|
||||
SpiCompletion::NotPending
|
||||
return Spi1::acknowledge_tx_interrupt().into();
|
||||
}
|
||||
F722ImuTransferCompletion::NotPending
|
||||
}
|
||||
|
||||
pub fn acknowledge_imu_spi1_error() -> bool {
|
||||
active_profile() == PROFILE_MPU6000_SPI1_USART1 && Spi1::acknowledge_error_interrupt()
|
||||
}
|
||||
|
||||
pub fn acknowledge_imu_rx_dma1() -> SpiCompletion {
|
||||
pub fn acknowledge_imu_rx_dma1() -> F722ImuTransferCompletion {
|
||||
if active_profile() == PROFILE_ICM42688_SPI3_USART2 {
|
||||
Spi3::acknowledge_rx_interrupt()
|
||||
} else {
|
||||
SpiCompletion::NotPending
|
||||
return Spi3::acknowledge_rx_interrupt().into();
|
||||
}
|
||||
F722ImuTransferCompletion::NotPending
|
||||
}
|
||||
|
||||
pub fn acknowledge_imu_tx_dma1() -> SpiCompletion {
|
||||
pub fn acknowledge_imu_tx_dma1() -> F722ImuTransferCompletion {
|
||||
if active_profile() == PROFILE_ICM42688_SPI3_USART2 {
|
||||
Spi3::acknowledge_tx_interrupt()
|
||||
} else {
|
||||
SpiCompletion::NotPending
|
||||
return Spi3::acknowledge_tx_interrupt().into();
|
||||
}
|
||||
F722ImuTransferCompletion::NotPending
|
||||
}
|
||||
|
||||
pub fn acknowledge_imu_spi3_error() -> bool {
|
||||
@@ -109,10 +106,9 @@ pub fn acknowledge_mavlink_dma2() -> TxCompletion {
|
||||
|
||||
pub fn acknowledge_mavlink_dma1() -> TxCompletion {
|
||||
if active_profile() == PROFILE_ICM42688_SPI3_USART2 {
|
||||
Usart2TxDma::acknowledge_interrupt()
|
||||
} else {
|
||||
TxCompletion::NotPending
|
||||
return Usart2TxDma::acknowledge_interrupt();
|
||||
}
|
||||
TxCompletion::NotPending
|
||||
}
|
||||
|
||||
pub fn acknowledge_mavlink_rx_usart1() -> ReceiveInterrupt {
|
||||
@@ -125,8 +121,7 @@ pub fn acknowledge_mavlink_rx_usart1() -> ReceiveInterrupt {
|
||||
|
||||
pub fn acknowledge_mavlink_rx_usart2() -> ReceiveInterrupt {
|
||||
if active_profile() == PROFILE_ICM42688_SPI3_USART2 {
|
||||
Usart2Tx::acknowledge_receive_interrupt()
|
||||
} else {
|
||||
ReceiveInterrupt::NotPending
|
||||
return Usart2Tx::acknowledge_receive_interrupt();
|
||||
}
|
||||
ReceiveInterrupt::NotPending
|
||||
}
|
||||
@@ -10,6 +10,7 @@ mod clock;
|
||||
mod data_channel;
|
||||
mod firmware;
|
||||
mod interrupts;
|
||||
mod motor_inhibit;
|
||||
mod peripherals;
|
||||
mod routes;
|
||||
mod sensors;
|
||||
@@ -17,6 +18,24 @@ mod sensors;
|
||||
use crate::{BoardError, F722BoardRoutes};
|
||||
use kaptan_board::BoardConfig;
|
||||
|
||||
/// Completion classification crossing the RTIC interrupt template.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub(crate) enum F722ImuTransferCompletion {
|
||||
Complete,
|
||||
TransferFault,
|
||||
NotPending,
|
||||
}
|
||||
|
||||
impl From<kaptan_spi::SpiCompletion> for F722ImuTransferCompletion {
|
||||
fn from(value: kaptan_spi::SpiCompletion) -> Self {
|
||||
match value {
|
||||
kaptan_spi::SpiCompletion::Complete => Self::Complete,
|
||||
kaptan_spi::SpiCompletion::TransferFault => Self::TransferFault,
|
||||
kaptan_spi::SpiCompletion::NotPending => Self::NotPending,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub(crate) enum VerticalRoute {
|
||||
None,
|
||||
@@ -24,20 +43,9 @@ pub(crate) enum VerticalRoute {
|
||||
LidarLiteV3,
|
||||
}
|
||||
|
||||
/// Logical-M1–M4 ordering for the fixed TIM3 physical channels.
|
||||
///
|
||||
/// Both supported boards use PB4/PB5/PB0/PB1 electrically, but their ESC
|
||||
/// connectors assign those pins to logical motors differently. This remains a
|
||||
/// platform-private route choice rather than a board-side execution detail.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub(crate) enum MotorRoute {
|
||||
KakuteTim3Tim5,
|
||||
GeprcTim3Tim5,
|
||||
}
|
||||
|
||||
pub(crate) use board::{
|
||||
F722InitResources, F722LidarState, F722MavlinkState, F722Mtf01, F722Mtf01Uart,
|
||||
F722ServiceTimer, imu_is_healthy, on_uart4_interrupt, process_imu, record_mavlink_completion,
|
||||
F722ServiceTimer, on_uart4_interrupt, process_imu, record_mavlink_completion,
|
||||
};
|
||||
pub(crate) use interrupts::{
|
||||
acknowledge_exti3, acknowledge_exti4, acknowledge_imu_rx_dma1, acknowledge_imu_rx_dma2,
|
||||
@@ -56,6 +64,10 @@ pub fn initialize(
|
||||
config: BoardConfig,
|
||||
routes: F722BoardRoutes,
|
||||
) -> Result<F722InitResources, BoardError> {
|
||||
// This is deliberately the first hardware access, before configuration
|
||||
// validation, clocks, sensor routes, interrupts, or UART setup. It keeps
|
||||
// the former four motor pins low even for malformed configuration.
|
||||
motor_inhibit::inhibit_motor_pins(&device.RCC, &device.GPIOB);
|
||||
config.validate().map_err(BoardError::InvalidConfig)?;
|
||||
board::validate_vertical_route(config, routes)?;
|
||||
board::initialize(core, device, config, routes)
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
//! Common former-motor-pin interlock for every F722 image.
|
||||
//!
|
||||
//! This is intentionally independent of profile initialization and is called
|
||||
//! as the first PAC access. The GPIO-low interlock is a separate electrical
|
||||
//! boundary from all telemetry initialization.
|
||||
|
||||
use stm32f7::stm32f722;
|
||||
|
||||
/// Drives PB0/PB1/PB4/PB5 low before selecting push-pull GPIO output mode.
|
||||
pub(crate) fn inhibit_motor_pins(rcc: &stm32f722::RCC, port: &stm32f722::GPIOB) {
|
||||
rcc.ahb1enr().modify(|_, write| write.gpioben().set_bit());
|
||||
port.bsrr().write(|write| {
|
||||
write
|
||||
.br0()
|
||||
.set_bit()
|
||||
.br1()
|
||||
.set_bit()
|
||||
.br4()
|
||||
.set_bit()
|
||||
.br5()
|
||||
.set_bit()
|
||||
});
|
||||
port.otyper().modify(|_, write| {
|
||||
write
|
||||
.ot0()
|
||||
.push_pull()
|
||||
.ot1()
|
||||
.push_pull()
|
||||
.ot4()
|
||||
.push_pull()
|
||||
.ot5()
|
||||
.push_pull()
|
||||
});
|
||||
port.ospeedr().modify(|_, write| {
|
||||
write
|
||||
.ospeedr0()
|
||||
.low_speed()
|
||||
.ospeedr1()
|
||||
.low_speed()
|
||||
.ospeedr4()
|
||||
.low_speed()
|
||||
.ospeedr5()
|
||||
.low_speed()
|
||||
});
|
||||
port.pupdr().modify(|_, write| {
|
||||
write
|
||||
.pupdr0()
|
||||
.floating()
|
||||
.pupdr1()
|
||||
.floating()
|
||||
.pupdr4()
|
||||
.floating()
|
||||
.pupdr5()
|
||||
.floating()
|
||||
});
|
||||
port.moder().modify(|_, write| {
|
||||
write
|
||||
.moder0()
|
||||
.output()
|
||||
.moder1()
|
||||
.output()
|
||||
.moder4()
|
||||
.output()
|
||||
.moder5()
|
||||
.output()
|
||||
});
|
||||
}
|
||||
@@ -152,7 +152,11 @@ pub fn configure_usart1_rx_pin_pa10(port: &stm32f722::GPIOA) {
|
||||
// SAFETY: AF7 selects USART1 RX on PA10 on STM32F722.
|
||||
unsafe { write.afrh10().bits(0b0111) }
|
||||
});
|
||||
port.pupdr().modify(|_, write| write.pupdr10().floating());
|
||||
// USART is idle-high. Leaving PA10 floating before its peer drives the
|
||||
// line can manufacture framing and noise errors. The weak internal pull-up
|
||||
// supplies only the electrically correct idle level; it does not mask a
|
||||
// driven low, framing, noise, or overrun fault.
|
||||
port.pupdr().modify(|_, write| write.pupdr10().pull_up());
|
||||
}
|
||||
|
||||
/// Configures PA2/PA3 for USART2 transmit/receive alternate function 7.
|
||||
|
||||
@@ -114,14 +114,28 @@ impl Spi1 {
|
||||
}
|
||||
|
||||
/// Releases controller-side ownership after a DMA terminal event.
|
||||
///
|
||||
/// Both streams are explicitly disabled for every outcome, including an
|
||||
/// SPI peripheral fault. Disabling the SPI DMA requests first prevents a
|
||||
/// new request while the stream hardware drains its enable bit. This
|
||||
/// method deliberately does not wait for that bit: the next submission
|
||||
/// observes it and returns `Busy` until the F7 DMA controller completes
|
||||
/// the documented disable sequence.
|
||||
pub fn complete_transfer(&mut self, _: SpiCompletion) {
|
||||
// TODO: Abort both DMA streams on peripheral faults.
|
||||
self.peripheral
|
||||
.cr2()
|
||||
.modify(|_, write| write.rxdmaen().clear_bit().txdmaen().clear_bit());
|
||||
self.peripheral
|
||||
.cr1()
|
||||
.modify(|_, write| write.spe().clear_bit());
|
||||
self.rx_stream()
|
||||
.cr()
|
||||
.modify(|_, write| write.en().clear_bit());
|
||||
self.tx_stream()
|
||||
.cr()
|
||||
.modify(|_, write| write.en().clear_bit());
|
||||
self.clear_rx_flags();
|
||||
self.clear_tx_flags();
|
||||
self.active = false;
|
||||
}
|
||||
|
||||
|
||||
@@ -27,10 +27,10 @@ pub struct Spi3 {
|
||||
}
|
||||
|
||||
impl Spi3 {
|
||||
/// Configures SPI3 mode 0 master operation at APB2/128 and reserves DMA1.
|
||||
/// Configures SPI3 mode 0 master operation at APB1/128 and reserves DMA1.
|
||||
///
|
||||
/// This keeps the ICM-42688-P route below 1 MHz with the board's 108 MHz
|
||||
/// APB2 clock while retaining substantial margin for board-level wiring.
|
||||
/// This keeps the ICM-42688-P route below 1 MHz with the board's 54 MHz
|
||||
/// APB1 clock while retaining substantial margin for board-level wiring.
|
||||
pub fn new(
|
||||
rcc: &stm32f722::RCC,
|
||||
peripheral: stm32f722::SPI3,
|
||||
@@ -114,14 +114,28 @@ impl Spi3 {
|
||||
}
|
||||
|
||||
/// Releases controller-side ownership after a DMA terminal event.
|
||||
///
|
||||
/// Both streams are explicitly disabled for every outcome, including an
|
||||
/// SPI peripheral fault. Disabling the SPI DMA requests first prevents a
|
||||
/// new request while the stream hardware drains its enable bit. This
|
||||
/// method deliberately does not wait for that bit: the next submission
|
||||
/// observes it and returns `Busy` until the F7 DMA controller completes
|
||||
/// the documented disable sequence.
|
||||
pub fn complete_transfer(&mut self, _: SpiCompletion) {
|
||||
// TODO: Abort both DMA streams on peripheral faults.
|
||||
self.peripheral
|
||||
.cr2()
|
||||
.modify(|_, write| write.rxdmaen().clear_bit().txdmaen().clear_bit());
|
||||
self.peripheral
|
||||
.cr1()
|
||||
.modify(|_, write| write.spe().clear_bit());
|
||||
self.rx_stream()
|
||||
.cr()
|
||||
.modify(|_, write| write.en().clear_bit());
|
||||
self.tx_stream()
|
||||
.cr()
|
||||
.modify(|_, write| write.en().clear_bit());
|
||||
self.clear_rx_flags();
|
||||
self.clear_tx_flags();
|
||||
self.active = false;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ use kaptan_time::{MonotonicClock, Timestamp};
|
||||
/// A DWT cycle counter extended into monotonic microseconds.
|
||||
///
|
||||
/// `now` must be called at least once during every 32-bit cycle-counter wrap
|
||||
/// interval. At the Kakute reset clock this is approximately 268 seconds; the
|
||||
/// interval. At the Kakute reset clock this is approximately 20 seconds; the
|
||||
/// platform periodic service calls it much more frequently.
|
||||
pub struct DwtMicrosecondClock {
|
||||
cycles_per_microsecond: u32,
|
||||
|
||||
@@ -104,6 +104,10 @@ impl Usart1Tx {
|
||||
) -> Result<Self, BaudRateError> {
|
||||
let brr = calculate_brr(peripheral_clock_hz, baud)?;
|
||||
|
||||
// Do not inherit a bootloader or earlier image's USART1 kernel-clock
|
||||
// selection. The F722 platform passes PCLK2 when calculating BRR, so
|
||||
// select it explicitly before programming the divisor.
|
||||
rcc.dckcfgr2().modify(|_, write| write.usart1sel().apb2());
|
||||
rcc.apb2rstr()
|
||||
.modify(|_, write| write.usart1rst().set_bit());
|
||||
rcc.apb2rstr()
|
||||
|
||||
@@ -9,6 +9,11 @@ use stm32f7::stm32f722;
|
||||
|
||||
use super::Usart1Tx;
|
||||
|
||||
// The F7 DMA controller can expose TCIF a few bus cycles before CR.EN reads
|
||||
// low. This bounded IRQ-side settling observation never spins indefinitely;
|
||||
// a later service turn can retry a start if the stream is not yet available.
|
||||
const DISABLE_POLL_LIMIT: u8 = 32;
|
||||
|
||||
/// Owns the fixed STM32F722 USART1 TX DMA route.
|
||||
pub struct Usart1TxDma {
|
||||
usart: Usart1Tx,
|
||||
@@ -73,12 +78,19 @@ impl Usart1TxDma {
|
||||
}
|
||||
|
||||
// A normal-mode STM32 DMA stream clears EN at terminal completion.
|
||||
// Request disable without polling: a later start observes EN and
|
||||
// refuses ownership rather than blocking an interrupt handler.
|
||||
// Request disable and observe that transition for a small fixed bound;
|
||||
// no unbounded wait is permitted in an IRQ. If the bound expires before
|
||||
// EN becomes visible as low, a later service turn observes `Busy` and
|
||||
// retries without treating this documented hardware latency as a fault.
|
||||
controller
|
||||
.st(7)
|
||||
.cr()
|
||||
.modify(|_, write| write.en().disabled());
|
||||
for _ in 0..DISABLE_POLL_LIMIT {
|
||||
if !controller.st(7).cr().read().en().is_enabled() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
controller.hifcr().write(|write| {
|
||||
write
|
||||
.ctcif7()
|
||||
@@ -165,8 +177,8 @@ impl Usart1TxDma {
|
||||
}
|
||||
|
||||
fn controller(&self) -> &stm32f722::dma2::RegisterBlock {
|
||||
// SAFETY: SPI1 owns DMA2 initialization; USART1 TX is permanently
|
||||
// assigned to the disjoint Stream7 route and accesses no other stream.
|
||||
// SAFETY: USART1 TX permanently owns this disjoint Stream7 route and
|
||||
// accesses no other DMA2 stream.
|
||||
unsafe { &*stm32f722::DMA2::ptr() }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,12 @@ use stm32f7::stm32f722;
|
||||
|
||||
use super::routes::{Icm42688Spi3Route, Mpu6000Spi1Route};
|
||||
|
||||
// TODO(configuration): this is an immutable board-development value, not an
|
||||
// airframe calibration. A ground configuration flow must stage the complete
|
||||
// IMU profile (device settings, mounting transform, bias/scale calibration,
|
||||
// noise model, source/revision) while output is inhibited, then reset the
|
||||
// estimator/controller/allocation state before activation. Never apply one
|
||||
// register/calibration field to a live flight runtime.
|
||||
const MPU6000_CONFIG: Mpu6000Config = Mpu6000Config {
|
||||
accelerometer: AccelerometerRange::PlusMinus4G,
|
||||
gyroscope: GyroscopeRange::PlusMinus500DegreesPerSecond,
|
||||
@@ -131,8 +137,12 @@ fn icm_event<E>(event: Icm42688ServiceEvent<E>) -> ImuEvent {
|
||||
}
|
||||
}
|
||||
|
||||
/// Aligns the verified GEP-F722-HD V2 ICM-42688-P yaw-180° mounting to the
|
||||
/// Aligns the development GEP-F722-HD V2 ICM-42688-P yaw-180° mounting to the
|
||||
/// flight-controller body frame. The MPU6000 profile is already aligned.
|
||||
///
|
||||
/// TODO(configuration): replace this build-time transform only as part of the
|
||||
/// same atomic sensor-profile activation described at `MPU6000_CONFIG`; it is
|
||||
/// not a user-adjustable live setting.
|
||||
fn align_icm42688(mut sample: ImuSnapshot) -> ImuSnapshot {
|
||||
sample.acceleration_milligravity[0] = sample.acceleration_milligravity[0].saturating_neg();
|
||||
sample.acceleration_milligravity[1] = sample.acceleration_milligravity[1].saturating_neg();
|
||||
|
||||
Reference in new issue
Block a user