libs/kaptan-stm32f7: prepare high-rate board runtime
Signed-off-by: Onur Özkan <work@onurozkan.dev>
This commit is contained in:
1 parent
57ff85fdbe
commit
4a5d0313ca
4 files changed
+126
-34
No files matched your search
@@ -46,6 +46,13 @@ where
|
||||
|
||||
/// Advances the reset-settling state on a board periodic event.
|
||||
pub fn on_periodic_tick(&mut self) -> ImuEvent<Bus::StartError> {
|
||||
if self.imu.is_ready() {
|
||||
return match self.imu.request_sample(self.clock.now()) {
|
||||
Ok(()) | Err(Mpu6000Error::NotReady) => ImuEvent::Idle,
|
||||
Err(error) => ImuEvent::Fault(error),
|
||||
};
|
||||
}
|
||||
|
||||
let _ = self.clock.now();
|
||||
match self.imu.advance_initialization() {
|
||||
Ok(()) => ImuEvent::Idle,
|
||||
@@ -132,6 +139,10 @@ mod tests {
|
||||
service.on_transfer_complete(SpiCompletion::Complete),
|
||||
ImuEvent::Initialized
|
||||
));
|
||||
assert!(matches!(service.on_data_ready(), ImuEvent::Idle));
|
||||
assert!(matches!(service.on_periodic_tick(), ImuEvent::Idle));
|
||||
assert!(matches!(
|
||||
service.on_transfer_complete(SpiCompletion::Complete),
|
||||
ImuEvent::Sample(_)
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -1,54 +1,89 @@
|
||||
//! Deterministic STM32F722 clock-source selection.
|
||||
//! Deterministic STM32F722 PLL clock-source selection.
|
||||
|
||||
use stm32f7::stm32f722;
|
||||
|
||||
const CLOCK_SWITCH_POLL_LIMIT: u16 = 1_024;
|
||||
|
||||
/// Failure to establish the board's HSI16 clock profile.
|
||||
/// Failure to establish the 216 MHz PLL clock profile.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum ClockProfileError {
|
||||
/// HSI did not become ready within the bounded polling budget.
|
||||
HsiNotReady,
|
||||
/// The system clock did not switch to HSI within the bounded polling budget.
|
||||
SystemClockNotHsi,
|
||||
PllNotStopped,
|
||||
PllNotReady,
|
||||
OverDriveNotReady,
|
||||
OverDriveSwitchNotReady,
|
||||
SystemClockNotPll,
|
||||
}
|
||||
|
||||
/// Selects 16 MHz HSI as SYSCLK with undivided AHB, APB1, and APB2 clocks.
|
||||
/// Selects the board's HSI PLL profile.
|
||||
///
|
||||
/// This profile is deliberately selected even when an earlier boot stage left
|
||||
/// the device running from a PLL. It establishes the clock assumptions used by
|
||||
/// DWT timestamps, USART1 baud calculation, and TIM2 heartbeat scheduling.
|
||||
pub fn select_hsi16_profile(rcc: &stm32f722::RCC) -> Result<(), ClockProfileError> {
|
||||
/// 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_for_hsi(rcc) {
|
||||
if !wait_until(|| rcc.cr().read().hsirdy().is_ready()) {
|
||||
return Err(ClockProfileError::HsiNotReady);
|
||||
}
|
||||
|
||||
rcc.cfgr()
|
||||
.modify(|_, write| write.sw().hsi().hpre().div1().ppre1().div1().ppre2().div1());
|
||||
if !wait_for_system_clock_hsi(rcc) {
|
||||
// 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_for_hsi(rcc: &stm32f722::RCC) -> bool {
|
||||
fn wait_until(mut ready: impl FnMut() -> bool) -> bool {
|
||||
for _ in 0..CLOCK_SWITCH_POLL_LIMIT {
|
||||
if rcc.cr().read().hsirdy().is_ready() {
|
||||
if ready() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
fn wait_for_system_clock_hsi(rcc: &stm32f722::RCC) -> bool {
|
||||
for _ in 0..CLOCK_SWITCH_POLL_LIMIT {
|
||||
if rcc.cfgr().read().sws().is_hsi() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
@@ -12,7 +12,7 @@ mod time;
|
||||
mod usart;
|
||||
mod usart1_tx_dma2_stream7;
|
||||
|
||||
pub use clock::{ClockProfileError, select_hsi16_profile};
|
||||
pub use clock::{ClockProfileError, select_pll216_profile};
|
||||
pub use dma_buffer::DcacheDisabledDmaMemory;
|
||||
pub use gpio::{
|
||||
ActiveLowLedPd2, configure_exti4_pin_pa4, configure_spi1_pins_pa5_pa6_pa7,
|
||||
|
||||
@@ -27,7 +27,10 @@ pub struct Spi1 {
|
||||
}
|
||||
|
||||
impl Spi1 {
|
||||
/// Configures SPI1 mode 0 master operation at APB2/64 and reserves DMA2.
|
||||
/// 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,
|
||||
@@ -44,7 +47,7 @@ impl Spi1 {
|
||||
.mstr()
|
||||
.master()
|
||||
.br()
|
||||
.div64()
|
||||
.div128()
|
||||
.ssm()
|
||||
.set_bit()
|
||||
.ssi()
|
||||
@@ -52,8 +55,7 @@ impl Spi1 {
|
||||
});
|
||||
peripheral
|
||||
.cr2()
|
||||
.write(|write| write.ds().eight_bit().frxth().set_bit());
|
||||
peripheral.cr1().modify(|_, write| write.spe().set_bit());
|
||||
.write(|write| write.ds().eight_bit().frxth().set_bit().errie().set_bit());
|
||||
|
||||
Self {
|
||||
peripheral,
|
||||
@@ -77,10 +79,27 @@ impl Spi1 {
|
||||
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());
|
||||
@@ -88,20 +107,40 @@ impl Spi1 {
|
||||
.cr()
|
||||
.modify(|_, write| write.en().enabled());
|
||||
self.peripheral
|
||||
.cr2()
|
||||
.modify(|_, write| write.rxdmaen().set_bit().txdmaen().set_bit());
|
||||
.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
|
||||
@@ -286,4 +325,11 @@ impl Spi1 {
|
||||
.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();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user