From 1c7fccc4a083cf5c63940177fc25ba3f560da16f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Onur=20=C3=96zkan?= Date: Sun, 19 Jul 2026 22:40:04 +0300 Subject: [PATCH] libs/kaptan-stm32f7: add clock GPIO and timer drivers --- libs/kaptan-stm32f7/src/stm32f722/clock.rs | 54 +++++++++++ libs/kaptan-stm32f7/src/stm32f722/gpio.rs | 102 +++++++++++++++++++++ libs/kaptan-stm32f7/src/stm32f722/tim2.rs | 53 +++++++++++ 3 files changed, 209 insertions(+) create mode 100644 libs/kaptan-stm32f7/src/stm32f722/clock.rs create mode 100644 libs/kaptan-stm32f7/src/stm32f722/gpio.rs create mode 100644 libs/kaptan-stm32f7/src/stm32f722/tim2.rs diff --git a/libs/kaptan-stm32f7/src/stm32f722/clock.rs b/libs/kaptan-stm32f7/src/stm32f722/clock.rs new file mode 100644 index 0000000..7a3918d --- /dev/null +++ b/libs/kaptan-stm32f7/src/stm32f722/clock.rs @@ -0,0 +1,54 @@ +//! Deterministic STM32F722 clock-source selection. + +use stm32f7::stm32f722; + +const CLOCK_SWITCH_POLL_LIMIT: u16 = 1_024; + +/// Failure to establish the board's HSI16 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, +} + +/// Selects 16 MHz HSI as SYSCLK with undivided AHB, APB1, and APB2 clocks. +/// +/// 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> { + rcc.cr().modify(|_, write| write.hsion().on()); + if !wait_for_hsi(rcc) { + 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 Err(ClockProfileError::SystemClockNotHsi); + } + + Ok(()) +} + +fn wait_for_hsi(rcc: &stm32f722::RCC) -> bool { + for _ in 0..CLOCK_SWITCH_POLL_LIMIT { + if rcc.cr().read().hsirdy().is_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 +} diff --git a/libs/kaptan-stm32f7/src/stm32f722/gpio.rs b/libs/kaptan-stm32f7/src/stm32f722/gpio.rs new file mode 100644 index 0000000..2dad6c3 --- /dev/null +++ b/libs/kaptan-stm32f7/src/stm32f722/gpio.rs @@ -0,0 +1,102 @@ +//! 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 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()); +} + +/// 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()); + } + } +} diff --git a/libs/kaptan-stm32f7/src/stm32f722/tim2.rs b/libs/kaptan-stm32f7/src/stm32f722/tim2.rs new file mode 100644 index 0000000..300c641 --- /dev/null +++ b/libs/kaptan-stm32f7/src/stm32f722/tim2.rs @@ -0,0 +1,53 @@ +//! 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()); + } +}