refactor: move firmware workspace under firmware

This commit is contained in:
Onur Özkan committed 2026-08-10 16:58:55 +03:00
1 parent 153b0bf262
commit f03fc119a4
222 files changed
+56 -13

No files matched your search

@@ -0,0 +1,257 @@
//! F722 transport and vertical-route adapters for the portable data channel.
use super::{VerticalRoute, peripherals};
use kaptan_data_channel::{DmaCompletion, FrameAdmission, FrameSink, TxHealth, VerticalDataConfig};
use kaptan_dma::TxCompletion;
use kaptan_mavlink::{
MavlinkFrame,
encoder::DistanceSensorConfig,
transmitter::{FrameClass, MavlinkTransmitter, TransmitFaultKind},
};
// Board endpoint, baud, sensor topology, and mounting remain immutable
// build-time policy. They are not ground-station-editable in this telemetry
// image: the F722 has no authenticated config ingress or activation runtime.
pub(crate) const UART1_CLOCK_HZ: u32 = 108_000_000;
pub(crate) const UART2_CLOCK_HZ: u32 = 54_000_000;
pub(crate) const SERVICE_HZ: usize = 100;
/// Fixed service-timer configuration for the validated Pll216 profile.
pub(crate) const SERVICE_TIMER_CONFIG: peripherals::Tim2PeriodicConfig =
peripherals::Tim2PeriodicConfig {
prescaler: 10_799,
auto_reload: 99,
};
/// 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 {
sensor_id: 0,
orientation:
kaptan_mavlink::message::MavSensorOrientation::MAV_SENSOR_ROTATION_PITCH_270,
minimum_distance_centimetres,
maximum_distance_centimetres,
};
match route {
VerticalRoute::None => VerticalDataConfig {
distance: None,
optical_flow_sensor_id: None,
maximum_age_ticks: 10,
},
VerticalRoute::Mtf01 => VerticalDataConfig {
distance: Some(distance(2, 800)),
optical_flow_sensor_id: Some(0),
maximum_age_ticks: 10,
},
VerticalRoute::LidarLiteV3 => VerticalDataConfig {
distance: Some(distance(20, 4_000)),
optical_flow_sensor_id: None,
maximum_age_ticks: 10,
},
}
}
/// The two F722 DMA engines behind the portable data-channel transport.
enum F722MavlinkTransport {
Usart1(MavlinkTransmitter<peripherals::DcacheDisabledDmaMemory, peripherals::Usart1TxDma, 4>),
Usart2(MavlinkTransmitter<peripherals::DcacheDisabledDmaMemory, peripherals::Usart2TxDma, 4>),
}
/// Sole F722 MAVLink DMA owner plus unconfigurable transport-loss accounting.
pub(crate) struct F722MavlinkTransmitter {
inner: F722MavlinkTransport,
health: TxHealth,
pending_faults: u8,
}
impl F722MavlinkTransmitter {
pub(crate) fn usart1(
rcc: &stm32f7::stm32f722::RCC,
uart: stm32f7::stm32f722::USART1,
baud: u32,
) -> Result<Self, ()> {
let uart = peripherals::Usart1Tx::new(rcc, uart, UART1_CLOCK_HZ, baud).map_err(|_| ())?;
// SAFETY: platform initialization disables D-cache before this object
// is created, and the active MAVLink frame remains queue-owned until
// this platform receives a terminal DMA completion notification.
let memory = unsafe { peripherals::DcacheDisabledDmaMemory::new() };
Ok(Self {
inner: F722MavlinkTransport::Usart1(MavlinkTransmitter::new(
memory,
peripherals::Usart1TxDma::new(rcc, uart),
)),
health: TxHealth::default(),
pending_faults: 0,
})
}
pub(crate) fn usart2(
rcc: &stm32f7::stm32f722::RCC,
uart: stm32f7::stm32f722::USART2,
baud: u32,
) -> Result<Self, ()> {
let uart = peripherals::Usart2Tx::new(rcc, uart, UART2_CLOCK_HZ, baud).map_err(|_| ())?;
// SAFETY: identical DMA-memory invariant to the USART1 route above;
// only the fixed platform DMA endpoint differs.
let memory = unsafe { peripherals::DcacheDisabledDmaMemory::new() };
Ok(Self {
inner: F722MavlinkTransport::Usart2(MavlinkTransmitter::new(
memory,
peripherals::Usart2TxDma::new(rcc, uart),
)),
health: TxHealth::default(),
pending_faults: 0,
})
}
}
impl FrameSink for F722MavlinkTransmitter {
fn send(&mut self, class: FrameClass, frame: MavlinkFrame) -> FrameAdmission {
let (admission, start_fault) = match &mut self.inner {
F722MavlinkTransport::Usart1(transmitter) => {
let outcome = transmitter.admit_and_start(class, frame);
(
outcome.admission,
outcome.start.err().map(|fault| fault.kind()),
)
}
F722MavlinkTransport::Usart2(transmitter) => {
let outcome = transmitter.admit_and_start(class, frame);
(
outcome.admission,
outcome.start.err().map(|fault| fault.kind()),
)
}
};
if !matches!(
admission,
kaptan_mavlink::transmitter::EnqueueResult::Enqueued
) {
self.health.admission_loss_count = self.health.admission_loss_count.saturating_add(1);
}
if let Some(fault) = start_fault {
self.record_fault(fault);
}
FrameAdmission::Retained(admission)
}
fn on_dma_complete(&mut self, completion: TxCompletion) -> DmaCompletion {
let start_fault = match &mut self.inner {
F722MavlinkTransport::Usart1(transmitter) => transmitter
.on_dma_complete(completion)
.err()
.map(|error| error.kind()),
F722MavlinkTransport::Usart2(transmitter) => transmitter
.on_dma_complete(completion)
.err()
.map(|error| error.kind()),
};
let terminal = matches!(
completion,
TxCompletion::TransferFault | TxCompletion::Halted
);
if let Some(fault) = start_fault {
self.record_fault(fault);
}
if terminal {
self.record_fault(TransmitFaultKind::Terminal);
}
if start_fault.is_some() || terminal {
DmaCompletion::Fault
} else {
DmaCompletion::Complete
}
}
fn health(&self) -> TxHealth {
self.health
}
fn take_transport_fault(&mut self) -> Option<TransmitFaultKind> {
let (bit, fault) = if self.pending_faults & 1 != 0 {
(1, TransmitFaultKind::Memory)
} else if self.pending_faults & 2 != 0 {
(2, TransmitFaultKind::Engine)
} else if self.pending_faults & 4 != 0 {
(4, TransmitFaultKind::Terminal)
} else {
return None;
};
self.pending_faults &= !bit;
Some(fault)
}
}
impl F722MavlinkTransmitter {
fn record_fault(&mut self, fault: TransmitFaultKind) {
self.health.dma_fault_count = self.health.dma_fault_count.saturating_add(1);
self.pending_faults |= match fault {
TransmitFaultKind::Memory => {
self.health.memory_fault_count = self.health.memory_fault_count.saturating_add(1);
1
}
TransmitFaultKind::Engine => {
self.health.engine_fault_count = self.health.engine_fault_count.saturating_add(1);
2
}
TransmitFaultKind::Terminal => {
self.health.terminal_fault_count =
self.health.terminal_fault_count.saturating_add(1);
4
}
};
}
}
/// Board-route status light; it is not part of a board declaration API.
pub(crate) enum F722StatusLed {
Kakute(peripherals::ActiveLowLedPd2),
Geprc(GeprcStatusLed),
}
impl F722StatusLed {
pub(crate) fn toggle(&mut self) {
match self {
Self::Kakute(led) => led.toggle(),
Self::Geprc(led) => led.toggle(),
}
}
}
/// GEPRC GEP-F722-HD V2 blue status LED on PA13.
pub(crate) struct GeprcStatusLed {
port: stm32f7::stm32f722::GPIOA,
is_high: bool,
}
impl GeprcStatusLed {
pub(crate) fn new(rcc: &stm32f7::stm32f722::RCC, port: stm32f7::stm32f722::GPIOA) -> Self {
rcc.ahb1enr().modify(|_, write| write.gpioaen().set_bit());
port.moder().modify(|_, write| write.moder13().output());
port.otyper().modify(|_, write| write.ot13().push_pull());
port.ospeedr()
.modify(|_, write| write.ospeedr13().low_speed());
port.pupdr().modify(|_, write| write.pupdr13().floating());
port.bsrr().write(|write| write.br13().set_bit());
Self {
port,
is_high: false,
}
}
fn toggle(&mut self) {
self.is_high = !self.is_high;
self.port.bsrr().write(|write| {
if self.is_high {
write.bs13().set_bit()
} else {
write.br13().set_bit()
}
});
}
}