219 lines
6.8 KiB
Rust
219 lines
6.8 KiB
Rust
//! 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);
|
|
}
|
|
}
|