//! 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); } }