//! STM32F722 USART routes used by supported boards. use kaptan_log::KaptanError; use stm32f7::stm32f722; /// UART baud-rate configuration error. #[derive(Clone, Copy, Debug, Eq, PartialEq, KaptanError)] pub enum BaudRateError { /// Zero is not a valid baud rate. Zero, /// The requested divisor cannot fit USART BRR. OutOfRange, } /// Terminal result from acknowledging a USART 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 USART interrupt did not have a receive event pending. NotPending, } /// Owns configured full-duplex USART1 hardware. /// /// The family platform owns schematic-selected pin setup. Configure pins /// before constructing this type. pub struct Usart1Tx { peripheral: stm32f722::USART1, } /// Owns configured full-duplex USART2 hardware. /// /// The family platform owns schematic-selected pin setup. Configure pins /// before constructing this type. pub struct Usart2Tx { peripheral: stm32f722::USART2, } impl Usart2Tx { /// Enables USART2 and configures 8N1 transmit and receive operation. pub fn new( rcc: &stm32f722::RCC, peripheral: stm32f722::USART2, peripheral_clock_hz: u32, baud: u32, ) -> Result { let brr = calculate_brr(peripheral_clock_hz, baud)?; // Do not inherit a bootloader's USART2 kernel-clock selection. The // F722 flight clock profile supplies `peripheral_clock_hz` from PCLK1, // so select PCLK1 before programming BRR. rcc.dckcfgr2().modify(|_, write| write.usart2sel().apb1()); rcc.apb1rstr() .modify(|_, write| write.usart2rst().set_bit()); rcc.apb1rstr() .modify(|_, write| write.usart2rst().clear_bit()); rcc.apb1enr().modify(|_, write| write.usart2en().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 USART2 receive interrupt without accessing TX state. pub fn acknowledge_receive_interrupt() -> ReceiveInterrupt { // SAFETY: the platform binds USART2 exactly once and this function touches // only its receive/error registers, never the TX DMA configuration. let peripheral = unsafe { &*stm32f722::USART2::ptr() }; acknowledge_receive_interrupt(peripheral) } } 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 { 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() .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 platform 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 platform binds USART1 exactly once and this function touches // only its receive/error registers, never the TX DMA configuration. let peripheral = unsafe { &*stm32f722::USART1::ptr() }; acknowledge_receive_interrupt(peripheral) } } /// Owns configured UART4 hardware. /// /// This route is intended for peripherals that continuously stream data, such /// as the MTF-01. It does not enable DMA state. pub struct Uart4 { _peripheral: stm32f722::UART4, } impl Uart4 { /// Enables UART4 and configures 8N1 transmit and receive operation. pub fn new( rcc: &stm32f722::RCC, peripheral: stm32f722::UART4, peripheral_clock_hz: u32, baud: u32, ) -> Result { let brr = calculate_brr(peripheral_clock_hz, baud)?; rcc.apb1rstr().modify(|_, write| write.uart4rst().set_bit()); rcc.apb1rstr() .modify(|_, write| write.uart4rst().clear_bit()); rcc.apb1enr().modify(|_, write| write.uart4en().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: peripheral, }) } /// Acknowledges one UART4 receive interrupt. pub fn acknowledge_receive_interrupt() -> ReceiveInterrupt { // SAFETY: the platform binds UART4 exactly once and this function // accesses only its receive and error registers. let peripheral = unsafe { &*stm32f722::UART4::ptr() }; acknowledge_receive_interrupt(peripheral) } } fn acknowledge_receive_interrupt( peripheral: &stm32f722::usart1::RegisterBlock, ) -> ReceiveInterrupt { 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 { 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)); } }