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
@@ -8,7 +8,7 @@
|
||||
/// A borrowed byte range prepared for a device-to-read DMA transfer.
|
||||
///
|
||||
/// This is a capability rather than a general byte-slice wrapper. It exists
|
||||
/// only after a board-selected policy proves DMA reachability and CPU-to-device
|
||||
/// only after a platform-selected policy proves DMA reachability and CPU-to-device
|
||||
/// visibility for the selected controller.
|
||||
pub struct DmaReadableBuffer<'a> {
|
||||
bytes: &'a [u8],
|
||||
|
||||
@@ -42,7 +42,7 @@ pub trait I2cBus {
|
||||
/// [`Self::start_read`] for the completed read transaction.
|
||||
fn complete_transaction(&mut self, completion: I2cCompletion, read: Option<&mut [u8]>);
|
||||
|
||||
/// Cancels an in-flight transaction after a board-selected timeout.
|
||||
/// Cancels an in-flight transaction after a platform-selected timeout.
|
||||
///
|
||||
/// Implementations must release controller-owned transaction state and
|
||||
/// arrange for any required STOP or peripheral recovery without waiting.
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
[package]
|
||||
name = "kaptan-stm32f7"
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
rust-version.workspace = true
|
||||
version.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[features]
|
||||
default = []
|
||||
stm32f722 = ["stm32f7/stm32f722"]
|
||||
|
||||
[dependencies]
|
||||
cortex-m = "0.7"
|
||||
kaptan-dma = { path = "../kaptan-dma" }
|
||||
kaptan-i2c = { path = "../kaptan-i2c" }
|
||||
kaptan-spi = { path = "../kaptan-spi" }
|
||||
kaptan-time = { path = "../kaptan-time" }
|
||||
stm32f7 = { version = "0.16", default-features = false }
|
||||
@@ -1,14 +0,0 @@
|
||||
//! STM32F7 peripheral drivers used by Kaptan boards.
|
||||
//!
|
||||
//! This crate is organized by MCU variant and peripheral route. A board chooses
|
||||
//! an MCU feature, owns its pin muxing and RTIC interrupt graph, then composes
|
||||
//! the typed peripheral drivers from the selected module. Generic telemetry
|
||||
//! policy and frame ownership deliberately do not belong here.
|
||||
|
||||
#![no_std]
|
||||
|
||||
#[cfg(not(feature = "stm32f722"))]
|
||||
compile_error!("select one supported STM32F7 variant feature, such as `stm32f722`");
|
||||
|
||||
#[cfg(feature = "stm32f722")]
|
||||
pub mod stm32f722;
|
||||
@@ -1,89 +0,0 @@
|
||||
//! Deterministic STM32F722 PLL clock-source selection.
|
||||
|
||||
use stm32f7::stm32f722;
|
||||
|
||||
const CLOCK_SWITCH_POLL_LIMIT: u16 = 1_024;
|
||||
|
||||
/// Failure to establish the 216 MHz PLL clock profile.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum ClockProfileError {
|
||||
HsiNotReady,
|
||||
SystemClockNotHsi,
|
||||
PllNotStopped,
|
||||
PllNotReady,
|
||||
OverDriveNotReady,
|
||||
OverDriveSwitchNotReady,
|
||||
SystemClockNotPll,
|
||||
}
|
||||
|
||||
/// Selects the board's HSI PLL profile.
|
||||
///
|
||||
/// SYSCLK/HCLK run at 216 MHz; APB1 is 54 MHz (timers run at 108 MHz), and
|
||||
/// APB2 is 108 MHz. HSI is the PLL source, so no external-crystal assumption
|
||||
/// is introduced.
|
||||
pub fn select_pll216_profile(
|
||||
rcc: &stm32f722::RCC,
|
||||
pwr: &stm32f722::PWR,
|
||||
flash: &stm32f722::FLASH,
|
||||
) -> Result<(), ClockProfileError> {
|
||||
rcc.cr().modify(|_, write| write.hsion().on());
|
||||
if !wait_until(|| rcc.cr().read().hsirdy().is_ready()) {
|
||||
return Err(ClockProfileError::HsiNotReady);
|
||||
}
|
||||
|
||||
// Return to HSI before changing a PLL left configured by an earlier boot stage.
|
||||
rcc.cfgr().modify(|_, write| write.sw().hsi());
|
||||
if !wait_until(|| rcc.cfgr().read().sws().is_hsi()) {
|
||||
return Err(ClockProfileError::SystemClockNotHsi);
|
||||
}
|
||||
rcc.cr().modify(|_, write| write.pllon().off());
|
||||
if !wait_until(|| !rcc.cr().read().pllrdy().is_ready()) {
|
||||
return Err(ClockProfileError::PllNotStopped);
|
||||
}
|
||||
|
||||
// Scale 1, over-drive and seven flash wait states are required at 216 MHz.
|
||||
rcc.apb1enr().modify(|_, write| write.pwren().set_bit());
|
||||
pwr.cr1().modify(|read, write| unsafe {
|
||||
write.bits((read.bits() & !(0b11 << 14)) | (0b11 << 14) | (1 << 16))
|
||||
});
|
||||
if !wait_until(|| pwr.csr1().read().odrdy().bit_is_set()) {
|
||||
return Err(ClockProfileError::OverDriveNotReady);
|
||||
}
|
||||
pwr.cr1().modify(|_, write| write.odswen().set_bit());
|
||||
if !wait_until(|| pwr.csr1().read().odswrdy().bit_is_set()) {
|
||||
return Err(ClockProfileError::OverDriveSwitchNotReady);
|
||||
}
|
||||
flash.acr().modify(|read, write| unsafe {
|
||||
write.bits((read.bits() & !0x0f) | 7 | (1 << 8) | (1 << 9))
|
||||
});
|
||||
|
||||
// HSI / 16 * 432 / 2 = 216 MHz; Q=9 supplies a 48 MHz domain.
|
||||
rcc.pllcfgr()
|
||||
.write(|write| unsafe { write.bits(16 | (432 << 6) | (9 << 24)) });
|
||||
rcc.cr().modify(|_, write| write.pllon().on());
|
||||
if !wait_until(|| rcc.cr().read().pllrdy().is_ready()) {
|
||||
return Err(ClockProfileError::PllNotReady);
|
||||
}
|
||||
rcc.cfgr().modify(|read, write| unsafe {
|
||||
// AHB /1, APB1 /4, APB2 /2, SYSCLK = PLL.
|
||||
write.bits(
|
||||
(read.bits() & !((0x0f << 4) | (0x07 << 10) | (0x07 << 13) | 0x03))
|
||||
| (0b101 << 10)
|
||||
| (0b100 << 13)
|
||||
| 0b10,
|
||||
)
|
||||
});
|
||||
if !wait_until(|| rcc.cfgr().read().sws().is_pll()) {
|
||||
return Err(ClockProfileError::SystemClockNotPll);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn wait_until(mut ready: impl FnMut() -> bool) -> bool {
|
||||
for _ in 0..CLOCK_SWITCH_POLL_LIMIT {
|
||||
if ready() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
//! D-cache-disabled DMA memory policy for STM32F722 boards.
|
||||
|
||||
use kaptan_dma::{DmaReadableBuffer, DmaTxBufferFactory};
|
||||
///
|
||||
/// DMA transmit memory policy for a board with D-cache disabled.
|
||||
///
|
||||
/// The board's linker script must also place transfer frames in SRAM reachable
|
||||
/// by the selected DMA controller. This policy does not support a cached board;
|
||||
/// such a board requires a distinct policy that performs cache maintenance.
|
||||
pub struct DcacheDisabledDmaMemory {
|
||||
_private: (),
|
||||
}
|
||||
|
||||
impl DcacheDisabledDmaMemory {
|
||||
/// Declares that the selected board disabled D-cache before DMA setup.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// - D-cache must remain disabled for every CPU access to DMA-owned frames.
|
||||
/// - The linker policy must locate those frames in DMA-visible SRAM.
|
||||
pub const unsafe fn new() -> Self {
|
||||
Self { _private: () }
|
||||
}
|
||||
}
|
||||
|
||||
impl DmaTxBufferFactory for DcacheDisabledDmaMemory {
|
||||
type Error = core::convert::Infallible;
|
||||
|
||||
fn prepare<'a>(&mut self, bytes: &'a [u8]) -> Result<DmaReadableBuffer<'a>, Self::Error> {
|
||||
// SAFETY: `Self` can exist only after the board proved its D-cache and
|
||||
// linker-memory policy. The runtime retains frame ownership until a
|
||||
// terminal DMA outcome or successful recovery.
|
||||
Ok(unsafe { DmaReadableBuffer::new_unchecked(bytes) })
|
||||
}
|
||||
}
|
||||
@@ -1,128 +0,0 @@
|
||||
//! Named STM32F722 GPIO routes used by supported board wiring.
|
||||
|
||||
use stm32f7::stm32f722;
|
||||
|
||||
/// Configures PA5, PA6, and PA7 for SPI1 alternate function 5.
|
||||
pub fn configure_spi1_pins_pa5_pa6_pa7(rcc: &stm32f722::RCC, port: &stm32f722::GPIOA) {
|
||||
rcc.ahb1enr().modify(|_, write| write.gpioaen().set_bit());
|
||||
port.moder().modify(|_, write| {
|
||||
write
|
||||
.moder5()
|
||||
.alternate()
|
||||
.moder6()
|
||||
.alternate()
|
||||
.moder7()
|
||||
.alternate()
|
||||
});
|
||||
port.afrl().modify(|_, write| {
|
||||
// SAFETY: AF5 selects SPI1 on PA5, PA6, and PA7 on STM32F722.
|
||||
unsafe {
|
||||
write
|
||||
.afrl5()
|
||||
.bits(0b0101)
|
||||
.afrl6()
|
||||
.bits(0b0101)
|
||||
.afrl7()
|
||||
.bits(0b0101)
|
||||
}
|
||||
});
|
||||
port.otyper()
|
||||
.modify(|_, write| write.ot5().push_pull().ot6().push_pull().ot7().push_pull());
|
||||
port.ospeedr().modify(|_, write| {
|
||||
write
|
||||
.ospeedr5()
|
||||
.high_speed()
|
||||
.ospeedr6()
|
||||
.high_speed()
|
||||
.ospeedr7()
|
||||
.high_speed()
|
||||
});
|
||||
port.pupdr().modify(|_, write| {
|
||||
write
|
||||
.pupdr5()
|
||||
.floating()
|
||||
.pupdr6()
|
||||
.floating()
|
||||
.pupdr7()
|
||||
.floating()
|
||||
});
|
||||
}
|
||||
|
||||
/// Configures PB8/PB9 for I²C1 alternate function 4.
|
||||
pub fn configure_i2c1_pins_pb8_pb9(rcc: &stm32f722::RCC, port: &stm32f722::GPIOB) {
|
||||
rcc.ahb1enr().modify(|_, w| w.gpioben().set_bit());
|
||||
port.moder()
|
||||
.modify(|_, w| w.moder8().alternate().moder9().alternate());
|
||||
// SAFETY: AF4 is the documented I2C1 alternate-function selector for PB8/PB9.
|
||||
port.afrh()
|
||||
.modify(|_, w| unsafe { w.afrh8().bits(4).afrh9().bits(4) });
|
||||
port.otyper()
|
||||
.modify(|_, w| w.ot8().open_drain().ot9().open_drain());
|
||||
port.ospeedr()
|
||||
.modify(|_, w| w.ospeedr8().high_speed().ospeedr9().high_speed());
|
||||
port.pupdr()
|
||||
.modify(|_, w| w.pupdr8().floating().pupdr9().floating());
|
||||
}
|
||||
|
||||
/// Configures PA4 as the input pin for an EXTI4 route.
|
||||
///
|
||||
/// A board must select this explicitly rather than inheriting the pin mode
|
||||
/// left by a bootloader or an earlier firmware image.
|
||||
pub fn configure_exti4_pin_pa4(port: &stm32f722::GPIOA) {
|
||||
port.moder().modify(|_, write| write.moder4().input());
|
||||
port.pupdr().modify(|_, write| write.pupdr4().floating());
|
||||
}
|
||||
|
||||
/// Configures PA9 for USART1 transmit alternate function 7.
|
||||
pub fn configure_usart1_tx_pin_pa9(port: &stm32f722::GPIOA) {
|
||||
port.moder().modify(|_, write| write.moder9().alternate());
|
||||
port.afrh().modify(|_, write| {
|
||||
// SAFETY: AF7 selects USART1 TX on PA9 on STM32F722.
|
||||
unsafe { write.afrh9().bits(0b0111) }
|
||||
});
|
||||
port.otyper().modify(|_, write| write.ot9().push_pull());
|
||||
port.ospeedr()
|
||||
.modify(|_, write| write.ospeedr9().high_speed());
|
||||
port.pupdr().modify(|_, write| write.pupdr9().floating());
|
||||
}
|
||||
|
||||
/// Configures PA10 for USART1 receive alternate function 7.
|
||||
pub fn configure_usart1_rx_pin_pa10(port: &stm32f722::GPIOA) {
|
||||
port.moder().modify(|_, write| write.moder10().alternate());
|
||||
port.afrh().modify(|_, write| {
|
||||
// SAFETY: AF7 selects USART1 RX on PA10 on STM32F722.
|
||||
unsafe { write.afrh10().bits(0b0111) }
|
||||
});
|
||||
port.pupdr().modify(|_, write| write.pupdr10().floating());
|
||||
}
|
||||
|
||||
/// Active-low status LED connected to PD2.
|
||||
pub struct ActiveLowLedPd2 {
|
||||
port: stm32f722::GPIOD,
|
||||
is_on: bool,
|
||||
}
|
||||
|
||||
impl ActiveLowLedPd2 {
|
||||
/// Configures PD2 as an initially-off active-low output.
|
||||
pub fn new(rcc: &stm32f722::RCC, port: stm32f722::GPIOD) -> Self {
|
||||
rcc.ahb1enr().modify(|_, write| write.gpioden().set_bit());
|
||||
port.moder().modify(|_, write| write.moder2().output());
|
||||
port.otyper().modify(|_, write| write.ot2().push_pull());
|
||||
port.ospeedr()
|
||||
.modify(|_, write| write.ospeedr2().low_speed());
|
||||
port.pupdr().modify(|_, write| write.pupdr2().floating());
|
||||
port.bsrr().write(|write| write.bs2().set_bit());
|
||||
|
||||
Self { port, is_on: false }
|
||||
}
|
||||
|
||||
/// Toggles the LED state.
|
||||
pub fn toggle(&mut self) {
|
||||
self.is_on = !self.is_on;
|
||||
if self.is_on {
|
||||
self.port.bsrr().write(|write| write.br2().set_bit());
|
||||
} else {
|
||||
self.port.bsrr().write(|write| write.bs2().set_bit());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,361 +0,0 @@
|
||||
//! STM32F722 I²C1 interrupt-driven controller.
|
||||
|
||||
use kaptan_i2c::{I2cBus, I2cCompletion};
|
||||
use stm32f7::stm32f722;
|
||||
|
||||
const MAX_BYTES: usize = 255;
|
||||
const CR1_INTERRUPTS: u32 = (1 << 0) | (1 << 1) | (1 << 2) | (1 << 4) | (1 << 5) | (1 << 7);
|
||||
const ISR_TXIS: u32 = 1 << 1;
|
||||
const ISR_RXNE: u32 = 1 << 2;
|
||||
const ISR_NACKF: u32 = 1 << 4;
|
||||
const ISR_STOPF: u32 = 1 << 5;
|
||||
const ISR_BERR: u32 = 1 << 8;
|
||||
const ISR_ARLO: u32 = 1 << 9;
|
||||
const ISR_OVR: u32 = 1 << 10;
|
||||
const ISR_ERRORS: u32 = ISR_BERR | ISR_ARLO | ISR_OVR;
|
||||
const ICR_NACKCF: u32 = 1 << 4;
|
||||
const ICR_STOPCF: u32 = 1 << 5;
|
||||
const ICR_ERRORS: u32 = (1 << 8) | (1 << 9) | (1 << 10);
|
||||
const ICR_TERMINAL: u32 = ICR_NACKCF | ICR_STOPCF | ICR_ERRORS;
|
||||
const ICR_FAULTS: u32 = ICR_NACKCF | ICR_ERRORS;
|
||||
const CR2_STOP: u32 = 1 << 14;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum I2c1Error {
|
||||
InvalidLength,
|
||||
InvalidAddress,
|
||||
Busy,
|
||||
}
|
||||
|
||||
/// Board-selected I²C1 electrical timing.
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct I2c1Config {
|
||||
timing: u32,
|
||||
}
|
||||
|
||||
impl I2c1Config {
|
||||
/// Creates a configuration from an STM32 `I2C_TIMINGR` register value.
|
||||
pub const fn from_timing_register(timing: u32) -> Self {
|
||||
Self { timing }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum Direction {
|
||||
Write,
|
||||
Read,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
enum TransferState {
|
||||
Idle,
|
||||
Active,
|
||||
Stopping,
|
||||
}
|
||||
|
||||
fn read_index_after_event(direction: Direction, flags: u32, index: usize, length: usize) -> usize {
|
||||
if matches!(direction, Direction::Read) && flags & ISR_RXNE != 0 && index < length {
|
||||
index + 1
|
||||
} else {
|
||||
index
|
||||
}
|
||||
}
|
||||
|
||||
fn completion_after_stop(index: usize, length: usize) -> I2cCompletion {
|
||||
if index == length {
|
||||
I2cCompletion::Complete
|
||||
} else {
|
||||
I2cCompletion::TransferFault
|
||||
}
|
||||
}
|
||||
|
||||
impl TransferState {
|
||||
fn accepts_start(self, bus_busy: bool) -> bool {
|
||||
self == Self::Idle && !bus_busy
|
||||
}
|
||||
|
||||
fn after_stop(self) -> Self {
|
||||
if self == Self::Stopping {
|
||||
Self::Idle
|
||||
} else {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
fn after_driver_completion(self) -> Self {
|
||||
if self == Self::Active {
|
||||
Self::Idle
|
||||
} else {
|
||||
self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Owns I²C1 while one interrupt-driven transaction is active.
|
||||
pub struct I2c1 {
|
||||
peripheral: stm32f722::I2C1,
|
||||
buffer: [u8; MAX_BYTES],
|
||||
length: usize,
|
||||
index: usize,
|
||||
direction: Direction,
|
||||
state: TransferState,
|
||||
}
|
||||
|
||||
impl I2c1 {
|
||||
/// Configures I²C1 using board-selected pin electrical timing.
|
||||
pub fn new(rcc: &stm32f722::RCC, peripheral: stm32f722::I2C1, config: I2c1Config) -> Self {
|
||||
rcc.apb1rstr().modify(|_, w| w.i2c1rst().set_bit());
|
||||
rcc.apb1rstr().modify(|_, w| w.i2c1rst().clear_bit());
|
||||
rcc.apb1enr().modify(|_, w| w.i2c1en().set_bit());
|
||||
// SAFETY: zero is the documented reset configuration for CR1.
|
||||
peripheral.cr1().write(|w| unsafe { w.bits(0) });
|
||||
// SAFETY: `timing` is a board-selected I2C_TIMINGR bit pattern.
|
||||
peripheral
|
||||
.timingr()
|
||||
.write(|w| unsafe { w.bits(config.timing) });
|
||||
// SAFETY: these are only documented CR1 interrupt-enable and PE bits.
|
||||
peripheral
|
||||
.cr1()
|
||||
.write(|w| unsafe { w.bits(CR1_INTERRUPTS) });
|
||||
Self {
|
||||
peripheral,
|
||||
buffer: [0; MAX_BYTES],
|
||||
length: 0,
|
||||
index: 0,
|
||||
direction: Direction::Write,
|
||||
state: TransferState::Idle,
|
||||
}
|
||||
}
|
||||
|
||||
/// Advances the event IRQ; returns a terminal result only after STOPF.
|
||||
pub fn acknowledge_event_interrupt(&mut self) -> I2cCompletion {
|
||||
let flags = self.peripheral.isr().read().bits();
|
||||
if self.state != TransferState::Active {
|
||||
self.clear_inactive_event_flags(flags);
|
||||
return I2cCompletion::NotPending;
|
||||
}
|
||||
if flags & ISR_ERRORS != 0 {
|
||||
self.abort();
|
||||
self.acknowledge_stop_if_present(flags);
|
||||
return I2cCompletion::TransferFault;
|
||||
}
|
||||
if flags & ISR_NACKF != 0 {
|
||||
self.abort();
|
||||
self.acknowledge_stop_if_present(flags);
|
||||
return I2cCompletion::TransferFault;
|
||||
}
|
||||
// With AUTOEND a final RXNE and STOPF may be visible in one ISR
|
||||
// entry. Drain RXDR before retiring the transfer: otherwise RXNE
|
||||
// remains asserted after software advances its state, retriggering
|
||||
// I2C1_EV indefinitely at priority 11.
|
||||
if matches!(self.direction, Direction::Write)
|
||||
&& flags & ISR_TXIS != 0
|
||||
&& self.index < self.length
|
||||
{
|
||||
// SAFETY: the value is an eight-bit TXDR payload.
|
||||
self.peripheral
|
||||
.txdr()
|
||||
.write(|w| unsafe { w.bits(u32::from(self.buffer[self.index])) });
|
||||
self.index += 1;
|
||||
}
|
||||
let next_read_index =
|
||||
read_index_after_event(self.direction, flags, self.index, self.length);
|
||||
if next_read_index != self.index {
|
||||
self.buffer[self.index] = self.peripheral.rxdr().read().bits() as u8;
|
||||
self.index = next_read_index;
|
||||
}
|
||||
if flags & ISR_STOPF != 0 {
|
||||
self.acknowledge_stop();
|
||||
return completion_after_stop(self.index, self.length);
|
||||
}
|
||||
I2cCompletion::NotPending
|
||||
}
|
||||
|
||||
pub fn acknowledge_error_interrupt(&mut self) -> I2cCompletion {
|
||||
let flags = self.peripheral.isr().read().bits();
|
||||
if flags & ISR_ERRORS == 0 {
|
||||
return I2cCompletion::NotPending;
|
||||
}
|
||||
if self.state == TransferState::Active {
|
||||
self.abort();
|
||||
self.acknowledge_stop_if_present(flags);
|
||||
I2cCompletion::TransferFault
|
||||
} else {
|
||||
// A STOP may have already retired the software transaction before
|
||||
// the error IRQ is serviced. Its flags still must be acknowledged
|
||||
// or this high-priority IRQ will continuously retrigger.
|
||||
self.clear_inactive_event_flags(flags);
|
||||
I2cCompletion::NotPending
|
||||
}
|
||||
}
|
||||
|
||||
fn start(&mut self, address: u8, length: usize, direction: Direction) -> Result<(), I2c1Error> {
|
||||
if length == 0 || length > MAX_BYTES {
|
||||
return Err(I2c1Error::InvalidLength);
|
||||
}
|
||||
if address > 0x7f {
|
||||
return Err(I2c1Error::InvalidAddress);
|
||||
}
|
||||
if !self.state.accepts_start(self.bus_is_busy()) {
|
||||
return Err(I2c1Error::Busy);
|
||||
}
|
||||
// SAFETY: ICR_TERMINAL contains only documented write-one-to-clear flags.
|
||||
self.peripheral
|
||||
.icr()
|
||||
.write(|w| unsafe { w.bits(ICR_TERMINAL) });
|
||||
self.length = length;
|
||||
self.index = 0;
|
||||
self.direction = direction;
|
||||
self.state = TransferState::Active;
|
||||
let direction_bit = if matches!(direction, Direction::Read) {
|
||||
1 << 10
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let cr2 = (u32::from(address) << 1)
|
||||
| direction_bit
|
||||
| ((length as u32) << 16)
|
||||
| (1 << 25)
|
||||
| (1 << 13);
|
||||
// SAFETY: `cr2` contains a validated 7-bit address and documented transfer fields.
|
||||
self.peripheral.cr2().write(|w| unsafe { w.bits(cr2) });
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn abort(&mut self) {
|
||||
// Keep software ownership closed until STOPF is observed. A requested
|
||||
// STOP may be delayed indefinitely by a held bus, so this state keeps
|
||||
// a new START from being issued before the terminal STOPF arrives.
|
||||
self.state = TransferState::Stopping;
|
||||
// SAFETY: this only requests STOP while preserving the current CR2 fields.
|
||||
self.peripheral
|
||||
.cr2()
|
||||
.modify(|read, write| unsafe { write.bits(read.bits() | CR2_STOP) });
|
||||
// SAFETY: ICR_FAULTS contains only documented fault write-one-to-clear flags.
|
||||
self.peripheral
|
||||
.icr()
|
||||
.write(|w| unsafe { w.bits(ICR_FAULTS) });
|
||||
}
|
||||
|
||||
fn bus_is_busy(&self) -> bool {
|
||||
self.peripheral.isr().read().bits() & (1 << 15) != 0
|
||||
}
|
||||
|
||||
fn acknowledge_stop(&mut self) {
|
||||
// SAFETY: ICR_STOPCF is the documented STOP write-one-to-clear bit.
|
||||
self.peripheral
|
||||
.icr()
|
||||
.write(|w| unsafe { w.bits(ICR_STOPCF) });
|
||||
self.state = self.state.after_stop();
|
||||
}
|
||||
|
||||
fn acknowledge_stop_if_present(&mut self, flags: u32) {
|
||||
if flags & ISR_STOPF != 0 {
|
||||
self.acknowledge_stop();
|
||||
}
|
||||
}
|
||||
|
||||
fn clear_inactive_event_flags(&mut self, flags: u32) {
|
||||
// An abort may request STOP after the driver has already released the
|
||||
// transaction. Drain that terminal IRQ instead of leaving I2C1_EV
|
||||
// pending forever. RXNE is normally impossible here, but draining it
|
||||
// keeps a malformed terminal transfer from becoming an IRQ storm.
|
||||
if flags & ISR_RXNE != 0 {
|
||||
let _ = self.peripheral.rxdr().read().bits();
|
||||
}
|
||||
if flags & (ISR_NACKF | ISR_ERRORS) != 0 {
|
||||
// SAFETY: ICR_FAULTS contains only documented fault write-one-to-clear flags.
|
||||
self.peripheral
|
||||
.icr()
|
||||
.write(|w| unsafe { w.bits(ICR_FAULTS) });
|
||||
}
|
||||
self.acknowledge_stop_if_present(flags);
|
||||
if flags & ISR_TXIS != 0 {
|
||||
self.abort();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl I2cBus for I2c1 {
|
||||
type StartError = I2c1Error;
|
||||
fn start_write(&mut self, address: u8, write: &[u8]) -> Result<(), Self::StartError> {
|
||||
if write.is_empty() || write.len() > MAX_BYTES {
|
||||
return Err(I2c1Error::InvalidLength);
|
||||
}
|
||||
if !self.state.accepts_start(self.bus_is_busy()) {
|
||||
return Err(I2c1Error::Busy);
|
||||
}
|
||||
// Populate controller-owned storage before `start` makes the
|
||||
// transaction visible to the higher-priority I²C event IRQ.
|
||||
self.buffer[..write.len()].copy_from_slice(write);
|
||||
self.start(address, write.len(), Direction::Write)?;
|
||||
Ok(())
|
||||
}
|
||||
fn start_read(&mut self, address: u8, length: usize) -> Result<(), Self::StartError> {
|
||||
self.start(address, length, Direction::Read)
|
||||
}
|
||||
fn complete_transaction(&mut self, completion: I2cCompletion, read: Option<&mut [u8]>) {
|
||||
if completion == I2cCompletion::Complete
|
||||
&& matches!(self.direction, Direction::Read)
|
||||
&& let Some(read) = read
|
||||
{
|
||||
debug_assert_eq!(read.len(), self.length);
|
||||
read.copy_from_slice(&self.buffer[..self.length]);
|
||||
}
|
||||
self.state = self.state.after_driver_completion();
|
||||
self.length = 0;
|
||||
self.index = 0;
|
||||
}
|
||||
|
||||
fn cancel_transaction(&mut self) {
|
||||
if self.state == TransferState::Active {
|
||||
self.abort();
|
||||
}
|
||||
self.length = 0;
|
||||
self.index = 0;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{
|
||||
Direction, I2cCompletion, ICR_ERRORS, ICR_FAULTS, ICR_NACKCF, ICR_STOPCF, ISR_RXNE,
|
||||
ISR_STOPF, TransferState, completion_after_stop, read_index_after_event,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn rejects_starts_while_a_transaction_or_recovery_is_pending() {
|
||||
assert!(TransferState::Idle.accepts_start(false));
|
||||
assert!(!TransferState::Idle.accepts_start(true));
|
||||
assert!(!TransferState::Active.accepts_start(false));
|
||||
assert!(!TransferState::Stopping.accepts_start(false));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn abort_stays_closed_until_stop_and_normal_completion_releases_ownership() {
|
||||
assert_eq!(TransferState::Stopping.after_stop(), TransferState::Idle);
|
||||
assert_eq!(TransferState::Active.after_stop(), TransferState::Active);
|
||||
assert_eq!(
|
||||
TransferState::Active.after_driver_completion(),
|
||||
TransferState::Idle
|
||||
);
|
||||
assert_eq!(
|
||||
TransferState::Stopping.after_driver_completion(),
|
||||
TransferState::Stopping
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fault_cleanup_does_not_acknowledge_stop_before_stop_is_observed() {
|
||||
assert_eq!(ICR_FAULTS, ICR_NACKCF | ICR_ERRORS);
|
||||
assert_eq!(ICR_FAULTS & ICR_STOPCF, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rxne_and_stopf_complete_a_final_read_in_one_event() {
|
||||
let index = read_index_after_event(Direction::Read, ISR_RXNE | ISR_STOPF, 1, 2);
|
||||
|
||||
assert_eq!(index, 2);
|
||||
assert_eq!(completion_after_stop(index, 2), I2cCompletion::Complete);
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
//! STM32F722 peripheral implementations selected by supported boards.
|
||||
//!
|
||||
//! This module exports only typed peripheral building blocks. A board selects
|
||||
//! pins, clocks, interrupt priorities, and static ownership.
|
||||
|
||||
mod clock;
|
||||
mod dma_buffer;
|
||||
mod gpio;
|
||||
mod i2c1;
|
||||
mod spi1;
|
||||
mod tim2;
|
||||
mod time;
|
||||
mod usart;
|
||||
mod usart1_tx_dma2_stream7;
|
||||
|
||||
pub use clock::{ClockProfileError, select_pll216_profile};
|
||||
pub use dma_buffer::DcacheDisabledDmaMemory;
|
||||
pub use gpio::{
|
||||
ActiveLowLedPd2, configure_exti4_pin_pa4, configure_i2c1_pins_pb8_pb9,
|
||||
configure_spi1_pins_pa5_pa6_pa7, configure_usart1_rx_pin_pa10, configure_usart1_tx_pin_pa9,
|
||||
};
|
||||
pub use i2c1::{I2c1, I2c1Config, I2c1Error};
|
||||
pub use spi1::{Spi1, Spi1Error};
|
||||
pub use tim2::{Tim2Periodic, Tim2PeriodicConfig};
|
||||
pub use time::{DwtClockStartError, DwtMicrosecondClock};
|
||||
pub use usart::{BaudRateError, ReceiveInterrupt, Usart1Tx};
|
||||
pub use usart1_tx_dma2_stream7::{StartError, Usart1TxDma};
|
||||
@@ -1,335 +0,0 @@
|
||||
//! STM32F722 SPI1 full-duplex DMA controller.
|
||||
//!
|
||||
//! SPI1 uses DMA2 Stream0/Channel3 for receive and DMA2 Stream3/Channel3 for
|
||||
//! transmit. The receive terminal event is the transaction boundary because it
|
||||
//! is generated after the last serial frame; transmit completion can occur
|
||||
//! while the final serial clock is still in progress.
|
||||
|
||||
use kaptan_spi::SpiCompletion;
|
||||
use stm32f7::stm32f722;
|
||||
|
||||
const MAX_TRANSFER_LENGTH: usize = u16::MAX as usize;
|
||||
|
||||
/// Failure while submitting one SPI1 DMA transaction.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum Spi1Error {
|
||||
/// The transaction is malformed or exceeds the DMA counter range.
|
||||
InvalidLength,
|
||||
/// A previous DMA transaction still owns SPI1.
|
||||
Busy,
|
||||
}
|
||||
|
||||
/// Owns SPI1 and its fixed full-duplex DMA2 request route.
|
||||
pub struct Spi1 {
|
||||
peripheral: stm32f722::SPI1,
|
||||
controller: stm32f722::DMA2,
|
||||
active: bool,
|
||||
}
|
||||
|
||||
impl Spi1 {
|
||||
/// Configures SPI1 mode 0 master operation at APB2/128 and reserves DMA2.
|
||||
///
|
||||
/// This keeps the MPU6000 route below 1 MHz with the board's 108 MHz
|
||||
/// APB2 clock while retaining substantial margin for board-level wiring.
|
||||
pub fn new(
|
||||
rcc: &stm32f722::RCC,
|
||||
peripheral: stm32f722::SPI1,
|
||||
controller: stm32f722::DMA2,
|
||||
) -> Self {
|
||||
rcc.apb2rstr().modify(|_, write| write.spi1rst().set_bit());
|
||||
rcc.apb2rstr()
|
||||
.modify(|_, write| write.spi1rst().clear_bit());
|
||||
rcc.apb2enr().modify(|_, write| write.spi1en().set_bit());
|
||||
rcc.ahb1enr().modify(|_, write| write.dma2en().set_bit());
|
||||
|
||||
peripheral.cr1().write(|write| {
|
||||
write
|
||||
.mstr()
|
||||
.master()
|
||||
.br()
|
||||
.div128()
|
||||
.ssm()
|
||||
.set_bit()
|
||||
.ssi()
|
||||
.set_bit()
|
||||
});
|
||||
peripheral
|
||||
.cr2()
|
||||
.write(|write| write.ds().eight_bit().frxth().set_bit().errie().set_bit());
|
||||
|
||||
Self {
|
||||
peripheral,
|
||||
controller,
|
||||
active: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Starts a full-duplex DMA transaction without polling a peripheral flag.
|
||||
pub fn start_transfer(&mut self, transmit: &[u8], receive: &mut [u8]) -> Result<(), Spi1Error> {
|
||||
if transmit.is_empty()
|
||||
|| transmit.len() != receive.len()
|
||||
|| transmit.len() > MAX_TRANSFER_LENGTH
|
||||
{
|
||||
return Err(Spi1Error::InvalidLength);
|
||||
}
|
||||
if self.active
|
||||
|| self.rx_stream().cr().read().en().is_enabled()
|
||||
|| self.tx_stream().cr().read().en().is_enabled()
|
||||
{
|
||||
return Err(Spi1Error::Busy);
|
||||
}
|
||||
|
||||
// Arm the complete DMA receive path before SPI starts generating
|
||||
// clocks. This avoids an RX FIFO overrun between transactions.
|
||||
self.peripheral
|
||||
.cr1()
|
||||
.modify(|_, write| write.spe().clear_bit());
|
||||
self.clear_spi_overrun();
|
||||
self.clear_rx_flags();
|
||||
self.clear_tx_flags();
|
||||
self.configure_rx(receive);
|
||||
self.configure_tx(transmit);
|
||||
self.peripheral.cr2().modify(|_, write| {
|
||||
let write = write.rxdmaen().set_bit().txdmaen().set_bit();
|
||||
if transmit.len() & 1 == 0 {
|
||||
write.ldma_rx().even().ldma_tx().even()
|
||||
} else {
|
||||
// With 8-bit frames, F7 SPI needs this for the final
|
||||
// byte of an odd-length DMA transaction. MPU samples are
|
||||
// one command byte plus fourteen data bytes.
|
||||
write.ldma_rx().odd().ldma_tx().odd()
|
||||
}
|
||||
});
|
||||
self.rx_stream()
|
||||
.cr()
|
||||
.modify(|_, write| write.en().enabled());
|
||||
self.tx_stream()
|
||||
.cr()
|
||||
.modify(|_, write| write.en().enabled());
|
||||
self.peripheral
|
||||
.cr1()
|
||||
.modify(|_, write| write.spe().set_bit());
|
||||
self.active = true;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Releases controller-side ownership after a DMA terminal event.
|
||||
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.active = false;
|
||||
}
|
||||
|
||||
/// Acknowledges an SPI peripheral overrun interrupt.
|
||||
///
|
||||
/// An overrun is not a DMA-stream error, so it must be observed through
|
||||
/// SPI1's own IRQ. The reference manual clears OVR by reading DR then SR.
|
||||
pub fn acknowledge_error_interrupt() -> bool {
|
||||
// SAFETY: SPI1 is initialized once by `Spi1::new`; this ISR only
|
||||
// clears its peripheral OVR condition.
|
||||
let peripheral = unsafe { &*stm32f722::SPI1::ptr() };
|
||||
if !peripheral.sr().read().ovr().is_overrun() {
|
||||
return false;
|
||||
}
|
||||
let _ = peripheral.dr().read().bits();
|
||||
let _ = peripheral.sr().read().bits();
|
||||
true
|
||||
}
|
||||
|
||||
/// Acknowledges the RX terminal IRQ. Invoke only from DMA2 Stream0.
|
||||
pub fn acknowledge_rx_interrupt() -> SpiCompletion {
|
||||
// SAFETY: DMA2 is initialized once by `Spi1::new`; this ISR touches
|
||||
// only Stream0's status and clear registers, never worker state.
|
||||
let controller = unsafe { &*stm32f722::DMA2::ptr() };
|
||||
let status = controller.lisr().read();
|
||||
let fault =
|
||||
status.teif0().is_error() || status.dmeif0().is_error() || status.feif0().is_error();
|
||||
let complete = status.tcif0().is_complete();
|
||||
controller.lifcr().write(|write| {
|
||||
write
|
||||
.cfeif0()
|
||||
.clear()
|
||||
.cdmeif0()
|
||||
.clear()
|
||||
.cteif0()
|
||||
.clear()
|
||||
.chtif0()
|
||||
.clear()
|
||||
.ctcif0()
|
||||
.clear()
|
||||
});
|
||||
if fault {
|
||||
SpiCompletion::TransferFault
|
||||
} else if complete {
|
||||
SpiCompletion::Complete
|
||||
} else {
|
||||
SpiCompletion::NotPending
|
||||
}
|
||||
}
|
||||
|
||||
/// Acknowledges TX errors; TX completion alone is not a transaction event.
|
||||
pub fn acknowledge_tx_interrupt() -> SpiCompletion {
|
||||
// SAFETY: DMA2 Stream3 is initialized once by `Spi1::new`; this ISR
|
||||
// touches only Stream3's status and clear registers.
|
||||
let controller = unsafe { &*stm32f722::DMA2::ptr() };
|
||||
let status = controller.lisr().read();
|
||||
let fault =
|
||||
status.teif3().is_error() || status.dmeif3().is_error() || status.feif3().is_error();
|
||||
controller.lifcr().write(|write| {
|
||||
write
|
||||
.cfeif3()
|
||||
.clear()
|
||||
.cdmeif3()
|
||||
.clear()
|
||||
.cteif3()
|
||||
.clear()
|
||||
.chtif3()
|
||||
.clear()
|
||||
.ctcif3()
|
||||
.clear()
|
||||
});
|
||||
if fault {
|
||||
SpiCompletion::TransferFault
|
||||
} else {
|
||||
SpiCompletion::NotPending
|
||||
}
|
||||
}
|
||||
|
||||
fn configure_rx(&self, receive: &mut [u8]) {
|
||||
self.rx_stream().cr().write(|write| {
|
||||
// SAFETY: Channel 3 is SPI1_RX on DMA2 Stream0; checked slice
|
||||
// length fits NDTR, and all values map directly to documented bits.
|
||||
unsafe {
|
||||
write
|
||||
.chsel()
|
||||
.bits(3)
|
||||
.dir()
|
||||
.peripheral_to_memory()
|
||||
.pl()
|
||||
.very_high()
|
||||
.minc()
|
||||
.incremented()
|
||||
.pinc()
|
||||
.fixed()
|
||||
.msize()
|
||||
.bits8()
|
||||
.psize()
|
||||
.bits8()
|
||||
.circ()
|
||||
.disabled()
|
||||
.tcie()
|
||||
.enabled()
|
||||
.teie()
|
||||
.enabled()
|
||||
.dmeie()
|
||||
.enabled()
|
||||
}
|
||||
});
|
||||
self.rx_stream()
|
||||
.ndtr()
|
||||
// SAFETY: `start_transfer` bounds the length to the 16-bit NDTR field.
|
||||
.write(|write| unsafe { write.bits(receive.len() as u32) });
|
||||
self.rx_stream()
|
||||
.par()
|
||||
// SAFETY: `DR` is the memory-mapped SPI1 DMA peripheral endpoint.
|
||||
.write(|write| unsafe { write.bits(self.peripheral.dr().as_ptr() as u32) });
|
||||
self.rx_stream()
|
||||
.m0ar()
|
||||
// SAFETY: the caller retains `receive` until terminal completion.
|
||||
.write(|write| unsafe { write.bits(receive.as_mut_ptr() as u32) });
|
||||
}
|
||||
|
||||
fn configure_tx(&self, transmit: &[u8]) {
|
||||
self.tx_stream().cr().write(|write| {
|
||||
// SAFETY: Channel 3 is SPI1_TX on DMA2 Stream3; checked slice
|
||||
// length fits NDTR, and all values map directly to documented bits.
|
||||
unsafe {
|
||||
write
|
||||
.chsel()
|
||||
.bits(3)
|
||||
.dir()
|
||||
.memory_to_peripheral()
|
||||
.pl()
|
||||
.very_high()
|
||||
.minc()
|
||||
.incremented()
|
||||
.pinc()
|
||||
.fixed()
|
||||
.msize()
|
||||
.bits8()
|
||||
.psize()
|
||||
.bits8()
|
||||
.circ()
|
||||
.disabled()
|
||||
.teie()
|
||||
.enabled()
|
||||
.dmeie()
|
||||
.enabled()
|
||||
}
|
||||
});
|
||||
self.tx_stream()
|
||||
.ndtr()
|
||||
// SAFETY: `start_transfer` bounds the length to the 16-bit NDTR field.
|
||||
.write(|write| unsafe { write.bits(transmit.len() as u32) });
|
||||
self.tx_stream()
|
||||
.par()
|
||||
// SAFETY: `DR` is the memory-mapped SPI1 DMA peripheral endpoint.
|
||||
.write(|write| unsafe { write.bits(self.peripheral.dr().as_ptr() as u32) });
|
||||
self.tx_stream()
|
||||
.m0ar()
|
||||
// SAFETY: the caller retains `transmit` until terminal completion.
|
||||
.write(|write| unsafe { write.bits(transmit.as_ptr() as u32) });
|
||||
}
|
||||
|
||||
fn rx_stream(&self) -> &stm32f722::dma2::ST {
|
||||
self.controller.st(0)
|
||||
}
|
||||
|
||||
fn tx_stream(&self) -> &stm32f722::dma2::ST {
|
||||
self.controller.st(3)
|
||||
}
|
||||
|
||||
fn clear_rx_flags(&self) {
|
||||
self.controller.lifcr().write(|write| {
|
||||
write
|
||||
.cfeif0()
|
||||
.clear()
|
||||
.cdmeif0()
|
||||
.clear()
|
||||
.cteif0()
|
||||
.clear()
|
||||
.chtif0()
|
||||
.clear()
|
||||
.ctcif0()
|
||||
.clear()
|
||||
});
|
||||
}
|
||||
|
||||
fn clear_tx_flags(&self) {
|
||||
self.controller.lifcr().write(|write| {
|
||||
write
|
||||
.cfeif3()
|
||||
.clear()
|
||||
.cdmeif3()
|
||||
.clear()
|
||||
.cteif3()
|
||||
.clear()
|
||||
.chtif3()
|
||||
.clear()
|
||||
.ctcif3()
|
||||
.clear()
|
||||
});
|
||||
}
|
||||
|
||||
fn clear_spi_overrun(&self) {
|
||||
if self.peripheral.sr().read().ovr().is_overrun() {
|
||||
let _ = self.peripheral.dr().read().bits();
|
||||
let _ = self.peripheral.sr().read().bits();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
//! STM32F722 TIM2 periodic interrupt configuration.
|
||||
|
||||
use stm32f7::stm32f722;
|
||||
|
||||
/// Precomputed TIM2 periodic-update configuration.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub struct Tim2PeriodicConfig {
|
||||
/// Timer prescaler register value.
|
||||
pub prescaler: u16,
|
||||
/// Timer auto-reload register value.
|
||||
pub auto_reload: u32,
|
||||
}
|
||||
|
||||
/// TIM2 configured to issue periodic update interrupts.
|
||||
pub struct Tim2Periodic {
|
||||
peripheral: stm32f722::TIM2,
|
||||
}
|
||||
|
||||
impl Tim2Periodic {
|
||||
/// Enables TIM2 with the supplied board-derived period configuration.
|
||||
pub fn new(
|
||||
rcc: &stm32f722::RCC,
|
||||
peripheral: stm32f722::TIM2,
|
||||
config: Tim2PeriodicConfig,
|
||||
) -> Self {
|
||||
rcc.apb1rstr().modify(|_, write| write.tim2rst().set_bit());
|
||||
rcc.apb1rstr()
|
||||
.modify(|_, write| write.tim2rst().clear_bit());
|
||||
rcc.apb1enr().modify(|_, write| write.tim2en().set_bit());
|
||||
peripheral.cr1().modify(|_, write| write.cen().clear_bit());
|
||||
peripheral.psc().write(|write| {
|
||||
// SAFETY: `prescaler` is exactly the 16-bit PSC register width.
|
||||
unsafe { write.psc().bits(config.prescaler) }
|
||||
});
|
||||
peripheral.arr().write(|write| {
|
||||
// SAFETY: TIM2 ARR is a 32-bit register and accepts `auto_reload`.
|
||||
unsafe { write.arr().bits(config.auto_reload) }
|
||||
});
|
||||
peripheral.egr().write(|write| write.ug().set_bit());
|
||||
peripheral.sr().modify(|_, write| write.uif().clear_bit());
|
||||
peripheral.dier().modify(|_, write| write.uie().set_bit());
|
||||
peripheral.cr1().modify(|_, write| write.cen().set_bit());
|
||||
|
||||
Self { peripheral }
|
||||
}
|
||||
|
||||
/// Acknowledges a TIM2 update interrupt.
|
||||
pub fn acknowledge_update(&mut self) {
|
||||
self.peripheral
|
||||
.sr()
|
||||
.modify(|_, write| write.uif().clear_bit());
|
||||
}
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
//! DWT-backed monotonic microsecond clock for STM32F722.
|
||||
|
||||
use cortex_m::peripheral::{DCB, DWT};
|
||||
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
|
||||
/// board periodic service calls it much more frequently.
|
||||
pub struct DwtMicrosecondClock {
|
||||
cycles_per_microsecond: u32,
|
||||
previous_cycles: u32,
|
||||
elapsed_cycles: u64,
|
||||
}
|
||||
|
||||
/// Invalid fixed clock configuration for the DWT timestamp source.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum DwtClockStartError {
|
||||
/// A zero-megahertz core clock cannot produce microsecond timestamps.
|
||||
ZeroFrequency,
|
||||
}
|
||||
|
||||
impl DwtMicrosecondClock {
|
||||
/// Enables the DWT cycle counter for a fixed integer-MHz core clock.
|
||||
///
|
||||
/// `MEGAHERTZ` must match the board's core clock. This function is called
|
||||
/// once during board initialization before interrupts are enabled.
|
||||
pub fn start<const MEGAHERTZ: u32>(
|
||||
dcb: &mut DCB,
|
||||
dwt: &mut DWT,
|
||||
) -> Result<Self, DwtClockStartError> {
|
||||
if MEGAHERTZ == 0 {
|
||||
return Err(DwtClockStartError::ZeroFrequency);
|
||||
}
|
||||
|
||||
dcb.enable_trace();
|
||||
DWT::unlock();
|
||||
dwt.set_cycle_count(0);
|
||||
dwt.enable_cycle_counter();
|
||||
|
||||
Ok(Self {
|
||||
cycles_per_microsecond: MEGAHERTZ,
|
||||
previous_cycles: 0,
|
||||
elapsed_cycles: 0,
|
||||
})
|
||||
}
|
||||
|
||||
/// Captures the current monotonic microsecond timestamp.
|
||||
pub fn now(&mut self) -> Timestamp {
|
||||
self.advance_to(DWT::cycle_count())
|
||||
}
|
||||
|
||||
/// Creates another independent reader for the same already-running DWT
|
||||
/// counter. Each reader must be serviced often enough to observe wraps.
|
||||
pub const fn fork(&self) -> Self {
|
||||
Self {
|
||||
cycles_per_microsecond: self.cycles_per_microsecond,
|
||||
previous_cycles: self.previous_cycles,
|
||||
elapsed_cycles: self.elapsed_cycles,
|
||||
}
|
||||
}
|
||||
|
||||
fn advance_to(&mut self, current_cycles: u32) -> Timestamp {
|
||||
let elapsed = current_cycles.wrapping_sub(self.previous_cycles);
|
||||
self.previous_cycles = current_cycles;
|
||||
self.elapsed_cycles = self.elapsed_cycles.saturating_add(u64::from(elapsed));
|
||||
|
||||
Timestamp::from_microseconds(self.elapsed_cycles / u64::from(self.cycles_per_microsecond))
|
||||
}
|
||||
}
|
||||
|
||||
impl MonotonicClock for DwtMicrosecondClock {
|
||||
fn now(&mut self) -> Timestamp {
|
||||
Self::now(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::DwtMicrosecondClock;
|
||||
|
||||
fn clock(previous_cycles: u32) -> DwtMicrosecondClock {
|
||||
DwtMicrosecondClock {
|
||||
cycles_per_microsecond: 16,
|
||||
previous_cycles,
|
||||
elapsed_cycles: 0,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn converts_cycles_to_microseconds() {
|
||||
let mut clock = clock(0);
|
||||
|
||||
assert_eq!(clock.advance_to(160).as_microseconds(), 10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extends_a_single_cycle_counter_wrap() {
|
||||
let mut clock = clock(u32::MAX - 15);
|
||||
|
||||
assert_eq!(clock.advance_to(16).as_microseconds(), 2);
|
||||
}
|
||||
}
|
||||
@@ -1,148 +0,0 @@
|
||||
//! USART1 transmit configuration for STM32F722.
|
||||
|
||||
use stm32f7::stm32f722;
|
||||
|
||||
/// UART baud-rate configuration error.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum BaudRateError {
|
||||
/// Zero is not a valid baud rate.
|
||||
Zero,
|
||||
/// The requested divisor cannot fit USART BRR.
|
||||
OutOfRange,
|
||||
}
|
||||
|
||||
/// Terminal result from acknowledging the USART1 receive interrupt.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum ReceiveInterrupt {
|
||||
/// One received byte was removed from the UART data register.
|
||||
Byte(u8),
|
||||
/// A serial transport fault invalidated the current receive stream.
|
||||
Fault,
|
||||
/// This USART1 interrupt did not have a receive event pending.
|
||||
NotPending,
|
||||
}
|
||||
|
||||
/// Owns configured full-duplex USART1 hardware.
|
||||
///
|
||||
/// Pin alternate-function setup remains board-owned because it depends on the
|
||||
/// schematic. Configure pins before constructing this type.
|
||||
pub struct Usart1Tx {
|
||||
peripheral: stm32f722::USART1,
|
||||
}
|
||||
|
||||
impl Usart1Tx {
|
||||
/// Enables USART1 and configures 8N1 transmit and receive operation.
|
||||
pub fn new(
|
||||
rcc: &stm32f722::RCC,
|
||||
peripheral: stm32f722::USART1,
|
||||
peripheral_clock_hz: u32,
|
||||
baud: u32,
|
||||
) -> Result<Self, BaudRateError> {
|
||||
let brr = calculate_brr(peripheral_clock_hz, baud)?;
|
||||
|
||||
rcc.apb2rstr()
|
||||
.modify(|_, write| write.usart1rst().set_bit());
|
||||
rcc.apb2rstr()
|
||||
.modify(|_, write| write.usart1rst().clear_bit());
|
||||
rcc.apb2enr().modify(|_, write| write.usart1en().set_bit());
|
||||
peripheral.brr().write(|write| {
|
||||
// SAFETY: `brr` is validated to fit the complete BRR register.
|
||||
unsafe { write.bits(u32::from(brr)) }
|
||||
});
|
||||
peripheral.cr1().write(|write| {
|
||||
write
|
||||
.te()
|
||||
.set_bit()
|
||||
.re()
|
||||
.set_bit()
|
||||
.rxneie()
|
||||
.set_bit()
|
||||
.ue()
|
||||
.set_bit()
|
||||
});
|
||||
peripheral.cr2().write(|write| write.stop().stop1());
|
||||
|
||||
Ok(Self { peripheral })
|
||||
}
|
||||
|
||||
pub(crate) fn enable_dma_transmit(&self) {
|
||||
self.peripheral.cr3().write(|write| write.dmat().set_bit());
|
||||
}
|
||||
|
||||
pub(crate) fn transmit_data_register_address(&self) -> u32 {
|
||||
self.peripheral.tdr().as_ptr() as u32
|
||||
}
|
||||
|
||||
/// Acknowledges one USART1 receive interrupt without accessing TX state.
|
||||
///
|
||||
/// The board invokes this only from the `USART1` interrupt. USART1 TX DMA
|
||||
/// owns its disjoint data register and DMA stream, so this static route may
|
||||
/// safely acknowledge RX while that transfer is active.
|
||||
pub fn acknowledge_receive_interrupt() -> ReceiveInterrupt {
|
||||
// SAFETY: the board binds USART1 exactly once and this function touches
|
||||
// only its receive/error registers, never the TX DMA configuration.
|
||||
let peripheral = unsafe { &*stm32f722::USART1::ptr() };
|
||||
let status = peripheral.isr().read();
|
||||
let fault = status.pe().is_error()
|
||||
|| status.fe().is_error()
|
||||
|| status.nf().is_noise()
|
||||
|| status.ore().is_overrun();
|
||||
|
||||
if status.rxne().is_data_ready() {
|
||||
let byte = peripheral.rdr().read().rdr().bits() as u8;
|
||||
if fault {
|
||||
clear_receive_errors(peripheral);
|
||||
ReceiveInterrupt::Fault
|
||||
} else {
|
||||
ReceiveInterrupt::Byte(byte)
|
||||
}
|
||||
} else if fault {
|
||||
clear_receive_errors(peripheral);
|
||||
ReceiveInterrupt::Fault
|
||||
} else {
|
||||
ReceiveInterrupt::NotPending
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn clear_receive_errors(peripheral: &stm32f722::usart1::RegisterBlock) {
|
||||
peripheral.icr().write(|write| {
|
||||
write
|
||||
.pecf()
|
||||
.clear()
|
||||
.fecf()
|
||||
.clear()
|
||||
.ncf()
|
||||
.clear()
|
||||
.orecf()
|
||||
.clear()
|
||||
});
|
||||
}
|
||||
|
||||
const fn calculate_brr(peripheral_clock_hz: u32, baud: u32) -> Result<u16, BaudRateError> {
|
||||
if baud == 0 {
|
||||
return Err(BaudRateError::Zero);
|
||||
}
|
||||
|
||||
let divisor = (peripheral_clock_hz + (baud / 2)) / baud;
|
||||
if divisor == 0 || divisor > u16::MAX as u32 {
|
||||
Err(BaudRateError::OutOfRange)
|
||||
} else {
|
||||
Ok(divisor as u16)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{BaudRateError, calculate_brr};
|
||||
|
||||
#[test]
|
||||
fn calculates_reset_clock_brr() {
|
||||
assert_eq!(calculate_brr(16_000_000, 115_200), Ok(139));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_zero_baud() {
|
||||
assert_eq!(calculate_brr(16_000_000, 0), Err(BaudRateError::Zero));
|
||||
}
|
||||
}
|
||||
@@ -1,218 +0,0 @@
|
||||
//! Fixed STM32F722 DMA route for USART1 transmit.
|
||||
//!
|
||||
//! USART1 TX uses DMA2 stream 7, channel 4 on STM32F722. The route is a type,
|
||||
//! rather than board-provided integer parameters, so invalid request mappings
|
||||
//! cannot be selected at construction.
|
||||
|
||||
use kaptan_dma::{DmaReadableBuffer, DmaTxEngine, TxCompletion};
|
||||
use stm32f7::stm32f722;
|
||||
|
||||
use super::Usart1Tx;
|
||||
|
||||
/// Owns the fixed STM32F722 USART1 TX DMA route.
|
||||
pub struct Usart1TxDma {
|
||||
usart: Usart1Tx,
|
||||
}
|
||||
|
||||
/// Failure to start one DMA frame transfer.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum StartError {
|
||||
/// DMA cannot transfer an empty buffer or one exceeding the NDTR limit.
|
||||
InvalidBufferLength,
|
||||
/// DMA2 stream 7 still owns an earlier transfer.
|
||||
Busy,
|
||||
}
|
||||
|
||||
impl Usart1TxDma {
|
||||
/// Enables DMA2 and connects USART1 TX to its fixed request route.
|
||||
pub fn new(rcc: &stm32f722::RCC, usart: Usart1Tx) -> Self {
|
||||
rcc.ahb1enr().modify(|_, write| write.dma2en().set_bit());
|
||||
usart.enable_dma_transmit();
|
||||
|
||||
Self { usart }
|
||||
}
|
||||
|
||||
/// Starts one memory-to-USART DMA transfer.
|
||||
///
|
||||
/// # Ownership
|
||||
///
|
||||
/// The caller must retain `buffer`'s backing storage until
|
||||
/// [`Self::acknowledge_interrupt`] returns a terminal result.
|
||||
/// - The board must bind `DMA2_STREAM7`, call `acknowledge_interrupt` from
|
||||
/// that interrupt, and defer lifecycle handling to its single owner.
|
||||
pub fn start(&mut self, buffer: DmaReadableBuffer<'_>) -> Result<(), StartError> {
|
||||
let bytes = buffer.bytes();
|
||||
if bytes.is_empty() || bytes.len() > u16::MAX as usize {
|
||||
return Err(StartError::InvalidBufferLength);
|
||||
}
|
||||
if self.is_active() {
|
||||
return Err(StartError::Busy);
|
||||
}
|
||||
|
||||
self.clear_interrupt_flags();
|
||||
self.configure_stream(bytes);
|
||||
self.stream().cr().modify(|_, write| write.en().enabled());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Acknowledges a DMA2 Stream7 terminal IRQ without accessing engine state.
|
||||
pub fn acknowledge_interrupt() -> TxCompletion {
|
||||
// SAFETY: this handler accesses only the fixed DMA2 Stream7 route.
|
||||
let controller = unsafe { &*stm32f722::DMA2::ptr() };
|
||||
let status = controller.hisr().read();
|
||||
let completion = DmaTerminalStatus {
|
||||
transfer_complete: status.tcif7().is_complete(),
|
||||
transfer_error: status.teif7().is_error(),
|
||||
direct_mode_error: status.dmeif7().is_error(),
|
||||
fifo_error: status.feif7().is_error(),
|
||||
}
|
||||
.classify();
|
||||
if completion == TxCompletion::NotPending {
|
||||
return completion;
|
||||
}
|
||||
|
||||
// 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.
|
||||
controller
|
||||
.st(7)
|
||||
.cr()
|
||||
.modify(|_, write| write.en().disabled());
|
||||
controller.hifcr().write(|write| {
|
||||
write
|
||||
.ctcif7()
|
||||
.clear()
|
||||
.chtif7()
|
||||
.clear()
|
||||
.cteif7()
|
||||
.clear()
|
||||
.cdmeif7()
|
||||
.clear()
|
||||
.cfeif7()
|
||||
.clear()
|
||||
});
|
||||
completion
|
||||
}
|
||||
|
||||
/// Returns whether DMA2 stream 7 owns a transfer.
|
||||
pub fn is_active(&self) -> bool {
|
||||
self.stream().cr().read().en().is_enabled()
|
||||
}
|
||||
|
||||
fn configure_stream(&mut self, buffer: &[u8]) {
|
||||
self.stream().cr().write(|write| {
|
||||
// SAFETY: Channel 4 and these transfer fields are the documented
|
||||
// USART1 TX mapping for DMA2 stream 7 on STM32F722.
|
||||
unsafe {
|
||||
write
|
||||
.chsel()
|
||||
.bits(4)
|
||||
.dir()
|
||||
.memory_to_peripheral()
|
||||
.pl()
|
||||
.very_high()
|
||||
.minc()
|
||||
.incremented()
|
||||
.pinc()
|
||||
.fixed()
|
||||
.msize()
|
||||
.bits8()
|
||||
.psize()
|
||||
.bits8()
|
||||
.circ()
|
||||
.disabled()
|
||||
.tcie()
|
||||
.enabled()
|
||||
.teie()
|
||||
.enabled()
|
||||
.dmeie()
|
||||
.enabled()
|
||||
}
|
||||
});
|
||||
self.stream().ndtr().write(|write| {
|
||||
// SAFETY: `start` checked the slice length against the NDTR limit.
|
||||
unsafe { write.bits(buffer.len() as u32) }
|
||||
});
|
||||
self.stream().par().write(|write| {
|
||||
// SAFETY: This is the memory-mapped USART1 transmit-data register.
|
||||
unsafe { write.bits(self.usart.transmit_data_register_address()) }
|
||||
});
|
||||
self.stream().m0ar().write(|write| {
|
||||
// SAFETY: The caller of `start` upholds the buffer lifetime contract.
|
||||
unsafe { write.bits(buffer.as_ptr() as u32) }
|
||||
});
|
||||
}
|
||||
|
||||
fn stream(&self) -> &stm32f722::dma2::ST {
|
||||
self.controller().st(7)
|
||||
}
|
||||
|
||||
fn clear_interrupt_flags(&self) {
|
||||
self.controller().hifcr().write(|write| {
|
||||
write
|
||||
.ctcif7()
|
||||
.clear()
|
||||
.chtif7()
|
||||
.clear()
|
||||
.cteif7()
|
||||
.clear()
|
||||
.cdmeif7()
|
||||
.clear()
|
||||
.cfeif7()
|
||||
.clear()
|
||||
});
|
||||
}
|
||||
|
||||
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.
|
||||
unsafe { &*stm32f722::DMA2::ptr() }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
struct DmaTerminalStatus {
|
||||
transfer_complete: bool,
|
||||
transfer_error: bool,
|
||||
direct_mode_error: bool,
|
||||
fifo_error: bool,
|
||||
}
|
||||
|
||||
impl DmaTerminalStatus {
|
||||
const fn classify(self) -> TxCompletion {
|
||||
if self.transfer_error || self.direct_mode_error || self.fifo_error {
|
||||
TxCompletion::TransferFault
|
||||
} else if self.transfer_complete {
|
||||
TxCompletion::Complete
|
||||
} else {
|
||||
TxCompletion::NotPending
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DmaTxEngine for Usart1TxDma {
|
||||
type StartError = StartError;
|
||||
|
||||
fn start(&mut self, buffer: DmaReadableBuffer<'_>) -> Result<(), Self::StartError> {
|
||||
Self::start(self, buffer)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::DmaTerminalStatus;
|
||||
use kaptan_dma::TxCompletion;
|
||||
|
||||
#[test]
|
||||
fn terminal_error_has_precedence_over_completion() {
|
||||
let status = DmaTerminalStatus {
|
||||
transfer_complete: true,
|
||||
transfer_error: false,
|
||||
direct_mode_error: false,
|
||||
fifo_error: true,
|
||||
};
|
||||
|
||||
assert_eq!(status.classify(), TxCompletion::TransferFault);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user