From f3d3b8899358ce8540bf5b2107a71b02ff941213 Mon Sep 17 00:00:00 2001 From: Roi Bachynskyi Date: Mon, 15 Sep 2025 00:38:23 +0300 Subject: [PATCH] lpc55: dma and async usart --- embassy-nxp/CHANGELOG.md | 1 + embassy-nxp/Cargo.toml | 1 + embassy-nxp/src/chips/lpc55.rs | 30 ++ embassy-nxp/src/dma.rs | 5 + embassy-nxp/src/dma/lpc55.rs | 377 +++++++++++++++++++++++ embassy-nxp/src/lib.rs | 72 +++++ embassy-nxp/src/usart/lpc55.rs | 310 +++++++++++++++++-- examples/lpc55s69/src/bin/usart_async.rs | 70 +++++ 8 files changed, 846 insertions(+), 20 deletions(-) create mode 100644 embassy-nxp/src/dma.rs create mode 100644 embassy-nxp/src/dma/lpc55.rs create mode 100644 examples/lpc55s69/src/bin/usart_async.rs diff --git a/embassy-nxp/CHANGELOG.md b/embassy-nxp/CHANGELOG.md index ab97c4185..0fb677cd8 100644 --- a/embassy-nxp/CHANGELOG.md +++ b/embassy-nxp/CHANGELOG.md @@ -7,5 +7,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased - ReleaseDate +- LPC55: DMA Controller and asynchronous version of USART - Moved NXP LPC55S69 from `lpc55-pac` to `nxp-pac` - First release with changelog. diff --git a/embassy-nxp/Cargo.toml b/embassy-nxp/Cargo.toml index 073fdabe4..f3c828313 100644 --- a/embassy-nxp/Cargo.toml +++ b/embassy-nxp/Cargo.toml @@ -29,6 +29,7 @@ cortex-m-rt = "0.7.0" critical-section = "1.1.2" embassy-hal-internal = { version = "0.3.0", path = "../embassy-hal-internal", features = ["cortex-m", "prio-bits-2"] } embassy-sync = { version = "0.7.2", path = "../embassy-sync" } +embassy-futures = { version = "0.1.2", path = "../embassy-futures"} defmt = { version = "1", optional = true } log = { version = "0.4.27", optional = true } embassy-time = { version = "0.5.0", path = "../embassy-time", optional = true } diff --git a/embassy-nxp/src/chips/lpc55.rs b/embassy-nxp/src/chips/lpc55.rs index 711bff3e7..9f4e7269f 100644 --- a/embassy-nxp/src/chips/lpc55.rs +++ b/embassy-nxp/src/chips/lpc55.rs @@ -1,5 +1,9 @@ pub use nxp_pac as pac; +embassy_hal_internal::interrupt_mod!( + FLEXCOMM0, FLEXCOMM1, FLEXCOMM2, FLEXCOMM3, FLEXCOMM4, FLEXCOMM5, FLEXCOMM6, FLEXCOMM7 +); + embassy_hal_internal::peripherals! { // External pins. These are not only GPIOs, they are multi-purpose pins and can be used by other // peripheral types (e.g. I2C). @@ -68,6 +72,32 @@ embassy_hal_internal::peripherals! { PIO1_30, PIO1_31, + // Direct Memory Access (DMA) channels. They are used for asynchronous modes of peripherals. + DMA_CH0, + DMA_CH1, + DMA_CH2, + DMA_CH3, + DMA_CH4, + DMA_CH5, + DMA_CH6, + DMA_CH7, + DMA_CH8, + DMA_CH9, + DMA_CH10, + DMA_CH11, + DMA_CH12, + DMA_CH13, + DMA_CH14, + DMA_CH15, + DMA_CH16, + DMA_CH17, + DMA_CH18, + DMA_CH19, + DMA_CH20, + DMA_CH21, + DMA_CH22, + + // Universal Synchronous/Asynchronous Receiver/Transmitter (USART) instances. USART0, USART1, USART2, diff --git a/embassy-nxp/src/dma.rs b/embassy-nxp/src/dma.rs new file mode 100644 index 000000000..e2df65fc9 --- /dev/null +++ b/embassy-nxp/src/dma.rs @@ -0,0 +1,5 @@ +//! Direct Memory Access (DMA) driver. + +#[cfg_attr(feature = "lpc55-core0", path = "./dma/lpc55.rs")] +mod inner; +pub use inner::*; diff --git a/embassy-nxp/src/dma/lpc55.rs b/embassy-nxp/src/dma/lpc55.rs new file mode 100644 index 000000000..578d1fd88 --- /dev/null +++ b/embassy-nxp/src/dma/lpc55.rs @@ -0,0 +1,377 @@ +use core::cell::RefCell; +use core::future::Future; +use core::pin::Pin; +use core::sync::atomic::{compiler_fence, Ordering}; +use core::task::{Context, Poll}; + +use critical_section::Mutex; +use embassy_hal_internal::interrupt::InterruptExt; +use embassy_hal_internal::{impl_peripheral, PeripheralType}; +use embassy_sync::waitqueue::AtomicWaker; + +use crate::pac::{DMA0, SYSCON, *}; +use crate::{peripherals, Peri}; + +#[interrupt] +fn DMA0() { + let inta = DMA0.inta0().read().ia(); + for channel in 0..CHANNEL_COUNT { + if (DMA0.errint0().read().err() & (1 << channel)) != 0 { + panic!("DMA: error on DMA_0 channel {}", channel); + } + + if (inta & (1 << channel)) != 0 { + CHANNEL_WAKERS[channel].wake(); + DMA0.inta0().modify(|w| w.set_ia(1 << channel)); + } + } +} + +pub(crate) fn init() { + assert_eq!(core::mem::size_of::(), 16, "Descriptor must be 16 bytes"); + assert_eq!( + core::mem::align_of::(), + 16, + "Descriptor must be 16-byte aligned" + ); + assert_eq!( + core::mem::align_of::(), + 512, + "Table must be 512-byte aligned" + ); + // Start clock for DMA + SYSCON.ahbclkctrl0().modify(|w| w.set_dma0(true)); + // Reset DMA + SYSCON + .presetctrl0() + .modify(|w| w.set_dma0_rst(syscon::vals::Dma0Rst::ASSERTED)); + SYSCON + .presetctrl0() + .modify(|w| w.set_dma0_rst(syscon::vals::Dma0Rst::RELEASED)); + + // Address bits 31:9 of the beginning of the DMA descriptor table + critical_section::with(|cs| { + DMA0.srambase() + .write(|w| w.set_offset((DMA_DESCRIPTORS.borrow(cs).as_ptr() as u32) >> 9)); + }); + // Enable DMA controller + DMA0.ctrl().modify(|w| w.set_enable(true)); + + unsafe { + crate::pac::interrupt::DMA0.enable(); + } + info!("DMA initialized"); +} + +/// DMA read. +/// +/// SAFETY: Slice must point to a valid location reachable by DMA. +pub unsafe fn read<'a, C: Channel, W: Word>(ch: Peri<'a, C>, from: *const W, to: *mut [W]) -> Transfer<'a, C> { + copy_inner( + ch, + from as *const u32, + to as *mut W as *mut u32, + to.len(), + W::size(), + false, + true, + ) +} + +/// DMA write. +/// +/// SAFETY: Slice must point to a valid location reachable by DMA. +pub unsafe fn write<'a, C: Channel, W: Word>(ch: Peri<'a, C>, from: *const [W], to: *mut W) -> Transfer<'a, C> { + copy_inner( + ch, + from as *const W as *const u32, + to as *mut u32, + from.len(), + W::size(), + true, + false, + ) +} + +/// DMA copy between slices. +/// +/// SAFETY: Slices must point to locations reachable by DMA. +pub unsafe fn copy<'a, C: Channel, W: Word>(ch: Peri<'a, C>, from: &[W], to: &mut [W]) -> Transfer<'a, C> { + let from_len = from.len(); + let to_len = to.len(); + assert_eq!(from_len, to_len); + copy_inner( + ch, + from.as_ptr() as *const u32, + to.as_mut_ptr() as *mut u32, + from_len, + W::size(), + true, + true, + ) +} + +fn copy_inner<'a, C: Channel>( + ch: Peri<'a, C>, + from: *const u32, + to: *mut u32, + len: usize, + data_size: crate::pac::dma::vals::Width, + incr_src: bool, + incr_dest: bool, +) -> Transfer<'a, C> { + let p = ch.regs(); + + // Buffer ending address = buffer starting address + (XFERCOUNT * the transfer increment) + // XREFCOUNT = the number of transfers performed - 1. + // The 1st transfer is included in the starting address. + let source_end_addr = if incr_src { + from as u32 + len as u32 - 1 + } else { + from as u32 + }; + let dest_end_addr = if incr_dest { + to as u32 + len as u32 - 1 + } else { + to as u32 + }; + + compiler_fence(Ordering::SeqCst); + + critical_section::with(|cs| { + DMA_DESCRIPTORS.borrow(cs).borrow_mut().descriptors[ch.number() as usize] = DmaDescriptor { + reserved: 0, + source_end_addr, + dest_end_addr, + next_desc: 0, // Since only single transfers are made, there is no need for reload descriptor address. + } + }); + + compiler_fence(Ordering::SeqCst); + + p.cfg().modify(|w| { + // Peripheral DMA requests are enabled. + // DMA requests that pace transfers can be interpreted then. + w.set_periphreqen(true); + // There is no need to have them on. + // No complex transfers are performed for now. + w.set_hwtrigen(false); + w.set_chpriority(0); + }); + + p.xfercfg().modify(|w| { + // This bit indicates whether the current channel descriptor is + // valid and can potentially be acted upon, + // if all other activation criteria are fulfilled. + w.set_cfgvalid(true); + // Indicates whether the channel’s control structure will be reloaded + // when the current descriptor is exhausted. + // Reloading allows ping-pong and linked transfers. + w.set_reload(false); + // There is no hardware distinction between interrupt A and B. + // They can be used by software to assist with more complex descriptor usage. + // By convention, interrupt A may be used when only one interrupt flag is needed. + w.set_setinta(true); + w.set_setintb(false); + w.set_width(data_size); + w.set_srcinc(if incr_src { + dma::vals::Srcinc::WIDTH_X_1 + } else { + dma::vals::Srcinc::NO_INCREMENT + }); + w.set_dstinc(if incr_dest { + dma::vals::Dstinc::WIDTH_X_1 + } else { + dma::vals::Dstinc::NO_INCREMENT + }); + // Total number of transfers to be performed, minus 1 encoded. + w.set_xfercount((len as u16) - 1); + // Before triggering the channel, it has to be enabled. + w.set_swtrig(false); + }); + + compiler_fence(Ordering::SeqCst); + DMA0.enableset0().write(|w| w.set_ena(1 << ch.number())); + DMA0.intenset0().write(|w| w.set_inten(1 << ch.number())); + + compiler_fence(Ordering::SeqCst); + // Start transfer. + DMA0.settrig0().write(|w| w.set_trig(1 << ch.number())); + compiler_fence(Ordering::SeqCst); + Transfer::new(ch) +} + +/// DMA transfer driver. +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct Transfer<'a, C: Channel> { + channel: Peri<'a, C>, +} + +impl<'a, C: Channel> Transfer<'a, C> { + pub(crate) fn new(channel: Peri<'a, C>) -> Self { + Self { channel } + } +} + +impl<'a, C: Channel> Drop for Transfer<'a, C> { + fn drop(&mut self) { + DMA0.enableclr0().write(|w| w.set_clr(1 << self.channel.number())); + while (DMA0.busy0().read().bsy() & (1 << self.channel.number())) != 0 {} + DMA0.abort0().write(|w| w.set_abortctrl(1 << self.channel.number())); + } +} + +impl<'a, C: Channel> Unpin for Transfer<'a, C> {} +impl<'a, C: Channel> Future for Transfer<'a, C> { + type Output = (); + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + // We need to register/re-register the waker for each poll because any + // calls to wake will deregister the waker. + CHANNEL_WAKERS[self.channel.number() as usize].register(cx.waker()); + // Check if it is busy or not. + if (DMA0.busy0().read().bsy() & (1 << self.channel.number())) != 0 { + Poll::Pending + } else { + Poll::Ready(()) + } + } +} + +// Total number of channles including both DMA0 and DMA1. +// In spite of using only DMA0 channels, the descriptor table +// should be of this size. +pub(crate) const CHANNEL_COUNT: usize = 32; + +static CHANNEL_WAKERS: [AtomicWaker; CHANNEL_COUNT] = [const { AtomicWaker::new() }; CHANNEL_COUNT]; + +// See section 22.5.2 (table 450) +// UM11126, Rev. 2.8 +// The size of a descriptor must be aligned to a multiple of 16 bytes. +#[repr(C, align(16))] +#[derive(Clone, Copy)] +struct DmaDescriptor { + /// 0x0 Reserved. + reserved: u32, + /// 0x4 Source data end address. + source_end_addr: u32, + /// 0x8 Destination end address. + dest_end_addr: u32, + /// 0xC Link to next descriptor. + next_desc: u32, +} + +// See section 22.6.3 +// UM11126, Rev. 2.8 +// The table must begin on a 512 byte boundary. +#[repr(C, align(512))] +struct DmaDescriptorTable { + descriptors: [DmaDescriptor; CHANNEL_COUNT], +} + +// DMA descriptors are stored in on-chip SRAM. +static DMA_DESCRIPTORS: Mutex> = Mutex::new(RefCell::new(DmaDescriptorTable { + descriptors: [DmaDescriptor { + reserved: 0, + source_end_addr: 0, + dest_end_addr: 0, + next_desc: 0, + }; CHANNEL_COUNT], +})); + +trait SealedChannel {} +trait SealedWord {} + +/// DMA channel interface. +#[allow(private_bounds)] +pub trait Channel: PeripheralType + SealedChannel + Into + Sized + 'static { + /// Channel number. + fn number(&self) -> u8; + + /// Channel registry block. + fn regs(&self) -> crate::pac::dma::Channel { + crate::pac::DMA0.channel(self.number() as _) + } +} + +/// DMA word. +#[allow(private_bounds)] +pub trait Word: SealedWord { + /// Word size. + fn size() -> crate::pac::dma::vals::Width; +} + +impl SealedWord for u8 {} +impl Word for u8 { + fn size() -> crate::pac::dma::vals::Width { + crate::pac::dma::vals::Width::BIT_8 + } +} + +impl SealedWord for u16 {} +impl Word for u16 { + fn size() -> crate::pac::dma::vals::Width { + crate::pac::dma::vals::Width::BIT_16 + } +} + +impl SealedWord for u32 {} +impl Word for u32 { + fn size() -> crate::pac::dma::vals::Width { + crate::pac::dma::vals::Width::BIT_32 + } +} + +/// Type erased DMA channel. +pub struct AnyChannel { + number: u8, +} + +impl_peripheral!(AnyChannel); + +impl SealedChannel for AnyChannel {} +impl Channel for AnyChannel { + fn number(&self) -> u8 { + self.number + } +} + +macro_rules! channel { + ($name:ident, $num:expr) => { + impl SealedChannel for peripherals::$name {} + impl Channel for peripherals::$name { + fn number(&self) -> u8 { + $num + } + } + + impl From for crate::dma::AnyChannel { + fn from(val: peripherals::$name) -> Self { + Self { number: val.number() } + } + } + }; +} + +channel!(DMA_CH0, 0); +channel!(DMA_CH1, 1); +channel!(DMA_CH2, 2); +channel!(DMA_CH3, 3); +channel!(DMA_CH4, 4); +channel!(DMA_CH5, 5); +channel!(DMA_CH6, 6); +channel!(DMA_CH7, 7); +channel!(DMA_CH8, 8); +channel!(DMA_CH9, 9); +channel!(DMA_CH10, 10); +channel!(DMA_CH11, 11); +channel!(DMA_CH12, 12); +channel!(DMA_CH13, 13); +channel!(DMA_CH14, 14); +channel!(DMA_CH15, 15); +channel!(DMA_CH16, 16); +channel!(DMA_CH17, 17); +channel!(DMA_CH18, 18); +channel!(DMA_CH19, 19); +channel!(DMA_CH20, 20); +channel!(DMA_CH21, 21); +channel!(DMA_CH22, 22); diff --git a/embassy-nxp/src/lib.rs b/embassy-nxp/src/lib.rs index 74142a10b..f0f0afb6c 100644 --- a/embassy-nxp/src/lib.rs +++ b/embassy-nxp/src/lib.rs @@ -3,6 +3,8 @@ // This mod MUST go first, so that the others see its macros. pub(crate) mod fmt; +#[cfg(feature = "lpc55-core0")] +pub mod dma; pub mod gpio; #[cfg(feature = "lpc55-core0")] pub mod pint; @@ -20,6 +22,9 @@ mod time_driver; #[cfg_attr(feature = "mimxrt1062", path = "chips/mimxrt1062.rs")] mod chip; +// TODO: Remove when this module is implemented for other chips +#[cfg(feature = "lpc55-core0")] +pub use chip::interrupt; #[cfg(feature = "unstable-pac")] pub use chip::pac; #[cfg(not(feature = "unstable-pac"))] @@ -27,6 +32,67 @@ pub(crate) use chip::pac; pub use chip::{peripherals, Peripherals}; pub use embassy_hal_internal::{Peri, PeripheralType}; +/// Macro to bind interrupts to handlers. +/// (Copied from `embassy-rp`) +/// This defines the right interrupt handlers, and creates a unit struct (like `struct Irqs;`) +/// and implements the right [`Binding`]s for it. You can pass this struct to drivers to +/// prove at compile-time that the right interrupts have been bound. +/// +/// Example of how to bind one interrupt: +/// +/// ```rust,ignore +/// use embassy_nxp::{bind_interrupts, usart, peripherals}; +/// +/// bind_interrupts!( +/// /// Binds the USART Interrupts. +/// struct Irqs { +/// FLEXCOMM0 => usart::InterruptHandler; +/// } +/// ); +/// ``` +#[macro_export] +macro_rules! bind_interrupts { + ($(#[$attr:meta])* $vis:vis struct $name:ident { + $( + $(#[cfg($cond_irq:meta)])? + $irq:ident => $( + $(#[cfg($cond_handler:meta)])? + $handler:ty + ),*; + )* + }) => { + #[derive(Copy, Clone)] + $(#[$attr])* + $vis struct $name; + + $( + #[allow(non_snake_case)] + #[no_mangle] + $(#[cfg($cond_irq)])? + unsafe extern "C" fn $irq() { + unsafe { + $( + $(#[cfg($cond_handler)])? + <$handler as $crate::interrupt::typelevel::Handler<$crate::interrupt::typelevel::$irq>>::on_interrupt(); + + )* + } + } + + $(#[cfg($cond_irq)])? + $crate::bind_interrupts!(@inner + $( + $(#[cfg($cond_handler)])? + unsafe impl $crate::interrupt::typelevel::Binding<$crate::interrupt::typelevel::$irq, $handler> for $name {} + )* + ); + )* + }; + (@inner $($t:tt)*) => { + $($t)* + } +} + /// Initialize the `embassy-nxp` HAL with the provided configuration. /// /// This returns the peripheral singletons that can be used for creating drivers. @@ -92,6 +158,9 @@ pub fn init(_config: config::Config) -> Peripherals { #[cfg(feature = "_time_driver")] time_driver::init(); + #[cfg(feature = "lpc55-core0")] + dma::init(); + peripherals } @@ -133,5 +202,8 @@ macro_rules! impl_mode { /// Blocking mode. pub struct Blocking; +/// Asynchronous mode. +pub struct Async; impl_mode!(Blocking); +impl_mode!(Async); diff --git a/embassy-nxp/src/usart/lpc55.rs b/embassy-nxp/src/usart/lpc55.rs index 428b80c4b..9034ed429 100644 --- a/embassy-nxp/src/usart/lpc55.rs +++ b/embassy-nxp/src/usart/lpc55.rs @@ -1,14 +1,24 @@ +use core::fmt::Debug; +use core::future::poll_fn; use core::marker::PhantomData; +use core::sync::atomic::{AtomicBool, Ordering}; +use core::task::Poll; +use embassy_futures::select::{select, Either}; +use embassy_hal_internal::interrupt::InterruptExt; use embassy_hal_internal::{Peri, PeripheralType}; +use embassy_sync::waitqueue::AtomicWaker; use embedded_io::{self, ErrorKind}; +use crate::dma::{AnyChannel, Channel}; use crate::gpio::{match_iocon, AnyPin, Bank, SealedPin}; +use crate::interrupt::typelevel::{Binding, Interrupt as _}; +use crate::interrupt::Interrupt; use crate::pac::flexcomm::Flexcomm as FlexcommReg; use crate::pac::iocon::vals::PioFunc; use crate::pac::usart::Usart as UsartReg; use crate::pac::*; -use crate::{Blocking, Mode}; +use crate::{Async, Blocking, Mode}; /// Serial error #[derive(Debug, Eq, PartialEq, Copy, Clone)] @@ -101,6 +111,12 @@ impl Default for Config { } } +/// Internal DMA state of UART RX. +pub struct DmaState { + rx_err_waker: AtomicWaker, + rx_err: AtomicBool, +} + /// # Type parameters /// 'd: the lifetime marker ensuring correct borrow checking for peripherals used at compile time /// T: the peripheral instance type allowing usage of peripheral specific registers @@ -112,24 +128,33 @@ pub struct Usart<'d, M: Mode> { pub struct UsartTx<'d, M: Mode> { info: &'static Info, - phantom: PhantomData<(&'d (), M)>, + tx_dma: Option>, + phantom: PhantomData, } pub struct UsartRx<'d, M: Mode> { info: &'static Info, - phantom: PhantomData<(&'d (), M)>, + dma_state: &'static DmaState, + rx_dma: Option>, + phantom: PhantomData, } impl<'d, M: Mode> UsartTx<'d, M> { - pub fn new(_usart: Peri<'d, T>, tx: Peri<'d, impl TxPin>, config: Config) -> Self { + pub fn new( + _usart: Peri<'d, T>, + tx: Peri<'d, impl TxPin>, + tx_dma: Peri<'d, impl Channel>, + config: Config, + ) -> Self { Usart::::init::(Some(tx.into()), None, config); - Self::new_inner(T::info()) + Self::new_inner(T::info(), Some(tx_dma.into())) } #[inline] - fn new_inner(info: &'static Info) -> Self { + fn new_inner(info: &'static Info, tx_dma: Option>) -> Self { Self { info, + tx_dma, phantom: PhantomData, } } @@ -155,20 +180,65 @@ impl<'d, M: Mode> UsartTx<'d, M> { impl<'d> UsartTx<'d, Blocking> { pub fn new_blocking(_usart: Peri<'d, T>, tx: Peri<'d, impl TxPin>, config: Config) -> Self { Usart::::init::(Some(tx.into()), None, config); - Self::new_inner(T::info()) + Self::new_inner(T::info(), None) } } +impl<'d> UsartTx<'d, Async> { + /// Write to UART TX from the provided buffer using DMA. + pub async fn write(&mut self, buffer: &[u8]) -> Result<(), Error> { + // Unwrap() can be used because UsartTx::new() in Async mode always sets it to Some + let ch = self.tx_dma.as_mut().unwrap().reborrow(); + let transfer = unsafe { + // Enable to pace DMA transfers. + self.info.usart_reg.fifocfg().modify(|w| w.set_dmatx(true)); + // If future is not assigned to a variable, the data register pointer + // is held across an await and makes the future non-Send. + crate::dma::write(ch, buffer, self.info.usart_reg.fifowr().as_ptr() as *mut _) + }; + transfer.await; + Ok(()) + } +} impl<'d, M: Mode> UsartRx<'d, M> { - pub fn new(_usart: Peri<'d, T>, rx: Peri<'d, impl RxPin>, config: Config) -> Self { + pub fn new( + _usart: Peri<'d, T>, + rx: Peri<'d, impl RxPin>, + has_irq: bool, + rx_dma: Peri<'d, impl Channel>, + config: Config, + ) -> Self { Usart::::init::(None, Some(rx.into()), config); - Self::new_inner(T::info()) + Self::new_inner(T::info(), T::dma_state(), has_irq, Some(rx_dma.into())) } - #[inline] - fn new_inner(info: &'static Info) -> Self { + fn new_inner( + info: &'static Info, + dma_state: &'static DmaState, + has_irq: bool, + rx_dma: Option>, + ) -> Self { + core::debug_assert_eq!(has_irq, rx_dma.is_some()); + if has_irq { + // Disable all the related interrupts for now. + info.usart_reg.intenclr().write(|w| { + w.set_framerrclr(true); + w.set_parityerrclr(true); + w.set_rxnoiseclr(true); + }); + info.usart_reg.fifointenclr().modify(|w| { + w.set_rxlvl(true); + w.set_rxerr(true); + }); + info.interrupt.unpend(); + unsafe { + info.interrupt.enable(); + } + } Self { info, + dma_state, + rx_dma, phantom: PhantomData, } } @@ -211,7 +281,120 @@ impl<'d, M: Mode> UsartRx<'d, M> { impl<'d> UsartRx<'d, Blocking> { pub fn new_blocking(_usart: Peri<'d, T>, rx: Peri<'d, impl RxPin>, config: Config) -> Self { Usart::::init::(None, Some(rx.into()), config); - Self::new_inner(T::info()) + Self::new_inner(T::info(), T::dma_state(), false, None) + } +} + +/// Interrupt handler. +pub struct InterruptHandler { + _uart: PhantomData, +} + +impl crate::interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + let regs = T::info().usart_reg; + if !regs.fifocfg().read().dmarx() { + return; + } + let state = T::dma_state(); + state.rx_err.store(true, Ordering::Relaxed); + state.rx_err_waker.wake(); + // Disable the error interrupts instead of clearing the flags. Clearing the + // flags would allow the DMA transfer to continue, potentially signaling + // completion before we can check for errors that happened *during* the transfer. + regs.intenclr().write(|w| { + w.set_framerrclr(true); + w.set_rxnoiseclr(true); + w.set_parityerrclr(true); + }); + regs.fifointenclr().write(|w| w.set_rxerr(true)); + } +} + +impl<'d> UsartRx<'d, Async> { + /// Read from USART RX into the provided buffer. + pub async fn read(&mut self, buffer: &mut [u8]) -> Result<(), Error> { + // Clear error flags before the FIFO is drained. Errors that have accumulated + // in the flags will also be present in the FIFO. + self.dma_state.rx_err.store(false, Ordering::Relaxed); + self.info.usart_reg.intenclr().write(|w| { + w.set_framerrclr(true); + w.set_parityerrclr(true); + w.set_rxnoiseclr(true); + }); + self.info.usart_reg.fifointenclr().modify(|w| w.set_rxerr(true)); + // Then drain the fifo. It is necessary to read at most 16 bytes (the size of FIFO). + // Errors that apply to FIFO bytes will be reported directly. + let buffer = match { + let limit = buffer.len().min(16); + self.drain_fifo(&mut buffer[0..limit]) + } { + Ok(len) if len < buffer.len() => &mut buffer[len..], + Ok(_) => return Ok(()), + Err((_i, e)) => return Err(e), + }; + + // Start a DMA transfer. If errors have happened in the interim some error + // interrupt flags will have been raised, and those will be picked up immediately + // by the interrupt handler. + // Unwrap() can be used because UsartRx::new() in Async mode always sets it to Some + let ch = self.rx_dma.as_mut().unwrap().reborrow(); + + self.info.usart_reg.intenset().write(|w| { + w.set_framerren(true); + w.set_parityerren(true); + w.set_rxnoiseen(true); + }); + self.info.usart_reg.fifointenset().modify(|w| w.set_rxerr(true)); + self.info.usart_reg.fifocfg().modify(|w| w.set_dmarx(true)); + let transfer = unsafe { + // If we don't assign future to a variable, the data register pointer + // is held across an await and makes the future non-Send. + crate::dma::read(ch, self.info.usart_reg.fiford().as_ptr() as *const _, buffer) + }; + + // wait for either the transfer to complete or an error to happen. + let transfer_result = select( + transfer, + poll_fn(|cx| { + self.dma_state.rx_err_waker.register(cx.waker()); + match self.dma_state.rx_err.swap(false, Ordering::Relaxed) { + false => Poll::Pending, + e => Poll::Ready(e), + } + }), + ) + .await; + + let errors = match transfer_result { + Either::First(()) => { + // The DMA controller finished, BUT if an error occurred on the LAST + // byte, then we may still need to grab the error state! + self.dma_state.rx_err.swap(false, Ordering::Relaxed) + } + Either::Second(e) => { + // There is an error, which means this is the error that + // was problematic. + e + } + }; + + // If we got no error, just return at this point + if !errors { + return Ok(()); + } + + // If we DID get an error, we need to figure out which one it was. + if self.info.usart_reg.intstat().read().framerrint() { + return Err(Error::Framing); + } else if self.info.usart_reg.intstat().read().parityerrint() { + return Err(Error::Parity); + } else if self.info.usart_reg.intstat().read().rxnoiseint() { + return Err(Error::Noise); + } else if self.info.usart_reg.fifointstat().read().rxerr() { + return Err(Error::Overrun); + } + unreachable!("unrecognized rx error"); } } @@ -222,7 +405,29 @@ impl<'d> Usart<'d, Blocking> { rx: Peri<'d, impl RxPin>, config: Config, ) -> Self { - Self::new_inner(usart, tx.into(), rx.into(), config) + Self::new_inner(usart, tx.into(), rx.into(), false, None, None, config) + } +} + +impl<'d> Usart<'d, Async> { + pub fn new( + uart: Peri<'d, T>, + tx: Peri<'d, impl TxPin>, + rx: Peri<'d, impl RxPin>, + _irq: impl Binding>, + tx_dma: Peri<'d, impl TxChannel>, + rx_dma: Peri<'d, impl RxChannel>, + config: Config, + ) -> Self { + Self::new_inner( + uart, + tx.into(), + rx.into(), + true, + Some(tx_dma.into()), + Some(rx_dma.into()), + config, + ) } } @@ -231,12 +436,15 @@ impl<'d, M: Mode> Usart<'d, M> { _usart: Peri<'d, T>, mut tx: Peri<'d, AnyPin>, mut rx: Peri<'d, AnyPin>, + has_irq: bool, + tx_dma: Option>, + rx_dma: Option>, config: Config, ) -> Self { Self::init::(Some(tx.reborrow()), Some(rx.reborrow()), config); Self { - tx: UsartTx::new_inner(T::info()), - rx: UsartRx::new_inner(T::info()), + tx: UsartTx::new_inner(T::info(), tx_dma), + rx: UsartRx::new_inner(T::info(), T::dma_state(), has_irq, rx_dma), } } @@ -390,9 +598,11 @@ impl<'d, M: Mode> Usart<'d, M> { SYSCON .presetctrl1() .modify(|w| w.set_fc_rst(instance_number, syscon::vals::FcRst::RELEASED)); - flexcomm_register - .pselid() - .modify(|w| w.set_persel(flexcomm::vals::Persel::USART)); + flexcomm_register.pselid().modify(|w| { + w.set_persel(flexcomm::vals::Persel::USART); + // This will lock the peripheral PERSEL and will not allow any changes until the board is reset. + w.set_lock(true); + }); } fn configure_usart(info: &'static Info, config: &Config) { @@ -471,6 +681,8 @@ impl<'d, M: Mode> Usart<'d, M> { }); registers.cfg().modify(|w| w.set_enable(true)); + registers.fifointenset().modify(|w| w.set_rxerr(true)); + // Drain RX FIFO in case it still has some unrelevant data while registers.fifostat().read().rxnotempty() { let _ = registers.fiford().read().0; @@ -513,6 +725,17 @@ impl<'d, M: Mode> Usart<'d, M> { } } +impl<'d> Usart<'d, Async> { + /// Write to UART TX from the provided buffer. + pub async fn write(&mut self, buffer: &[u8]) -> Result<(), Error> { + self.tx.write(buffer).await + } + + /// Read from UART RX into the provided buffer. + pub async fn read(&mut self, buffer: &mut [u8]) -> Result<(), Error> { + self.rx.read(buffer).await + } +} impl<'d, M: Mode> embedded_hal_02::blocking::serial::Write for UsartTx<'d, M> { type Error = Error; @@ -584,10 +807,12 @@ impl<'d> embedded_io::Read for Usart<'d, Blocking> { struct Info { usart_reg: UsartReg, fc_reg: FlexcommReg, + interrupt: Interrupt, } trait SealedInstance { fn info() -> &'static Info; + fn dma_state() -> &'static DmaState; fn instance_number() -> usize; fn tx_pin_func() -> PioFunc; fn rx_pin_func() -> PioFunc; @@ -595,7 +820,10 @@ trait SealedInstance { /// UART instance. #[allow(private_bounds)] -pub trait Instance: SealedInstance + PeripheralType {} +pub trait Instance: SealedInstance + PeripheralType { + /// Interrupt for this instance. + type Interrupt: crate::interrupt::typelevel::Interrupt; +} macro_rules! impl_instance { ($inst:ident, $fc:ident, $tx_pin:ident, $rx_pin:ident, $fc_num:expr) => { @@ -604,9 +832,18 @@ macro_rules! impl_instance { static INFO: Info = Info { usart_reg: crate::pac::$inst, fc_reg: crate::pac::$fc, + interrupt: crate::interrupt::typelevel::$fc::IRQ, }; &INFO } + + fn dma_state() -> &'static DmaState { + static STATE: DmaState = DmaState { + rx_err_waker: AtomicWaker::new(), + rx_err: AtomicBool::new(false), + }; + &STATE + } #[inline] fn instance_number() -> usize { $fc_num @@ -620,7 +857,9 @@ macro_rules! impl_instance { PioFunc::$rx_pin } } - impl $crate::usart::Instance for $crate::peripherals::$inst {} + impl $crate::usart::Instance for $crate::peripherals::$inst { + type Interrupt = crate::interrupt::typelevel::$fc; + } }; } @@ -663,3 +902,34 @@ impl_pin!(PIO1_16, USART6, Tx); impl_pin!(PIO1_13, USART6, Rx); impl_pin!(PIO0_19, USART7, Tx); impl_pin!(PIO0_20, USART7, Rx); + +/// Trait for TX DMA channels. +pub trait TxChannel: crate::dma::Channel {} +/// Trait for RX DMA channels. +pub trait RxChannel: crate::dma::Channel {} + +macro_rules! impl_channel { + ($dma:ident, $instance:ident, Tx) => { + impl TxChannel for crate::peripherals::$dma {} + }; + ($dma:ident, $instance:ident, Rx) => { + impl RxChannel for crate::peripherals::$dma {} + }; +} + +impl_channel!(DMA_CH4, USART0, Rx); +impl_channel!(DMA_CH5, USART0, Tx); +impl_channel!(DMA_CH6, USART1, Rx); +impl_channel!(DMA_CH7, USART1, Tx); +impl_channel!(DMA_CH10, USART2, Rx); +impl_channel!(DMA_CH11, USART2, Tx); +impl_channel!(DMA_CH8, USART3, Rx); +impl_channel!(DMA_CH9, USART3, Tx); +impl_channel!(DMA_CH12, USART4, Rx); +impl_channel!(DMA_CH13, USART4, Tx); +impl_channel!(DMA_CH14, USART5, Rx); +impl_channel!(DMA_CH15, USART5, Tx); +impl_channel!(DMA_CH16, USART6, Rx); +impl_channel!(DMA_CH17, USART6, Tx); +impl_channel!(DMA_CH18, USART7, Rx); +impl_channel!(DMA_CH19, USART7, Tx); diff --git a/examples/lpc55s69/src/bin/usart_async.rs b/examples/lpc55s69/src/bin/usart_async.rs new file mode 100644 index 000000000..b06abd477 --- /dev/null +++ b/examples/lpc55s69/src/bin/usart_async.rs @@ -0,0 +1,70 @@ +#![no_std] +#![no_main] + +use core::str::from_utf8_mut; + +use defmt::*; +use embassy_executor::Spawner; +use embassy_nxp::bind_interrupts; +use embassy_nxp::gpio::{Level, Output}; +use embassy_nxp::peripherals::USART2; +use embassy_nxp::usart::{Config, InterruptHandler, Usart}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_halt as _}; + +bind_interrupts!(struct Irqs { + FLEXCOMM2 => InterruptHandler; + } +); + +#[embassy_executor::task] +async fn blinky_task(mut led: Output<'static>) { + loop { + info!("[TASK] led off!"); + led.set_high(); + Timer::after_millis(500).await; + + info!("[TASK] led on!"); + led.set_low(); + Timer::after_millis(500).await; + } +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_nxp::init(Default::default()); + let mut usart = Usart::new( + p.USART2, + p.PIO0_27, + p.PIO1_24, + Irqs, + p.DMA_CH11, + p.DMA_CH10, + Config::default(), + ); + let led = Output::new(p.PIO1_6, Level::Low); + spawner.spawn(blinky_task(led).unwrap()); + info!("[MAIN] Entering main loop"); + loop { + let tx_buf = b"Hello, Ferris!"; + let mut rx_buf = [0u8; 14]; + info!("[MAIN] Write a message"); + usart.write(tx_buf).await.unwrap(); + Timer::after_millis(500).await; + + info!("[MAIN] Read a message"); + match usart.read(&mut rx_buf).await { + Ok(_) => match from_utf8_mut(&mut rx_buf) { + Ok(str) => { + info!("[MAIN] The message is: {}", str); + } + Err(_) => { + error!("[MAIN] Error in converting to UTF8"); + } + }, + Err(e) => warn!("[MAIN] Error: {}", e), + } + + Timer::after_millis(500).await; + } +}