lpc55: dma and async usart

This commit is contained in:
Roi Bachynskyi 2025-09-15 00:38:23 +03:00
parent 56019ba197
commit f3d3b88993
8 changed files with 846 additions and 20 deletions

View File

@ -7,5 +7,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
<!-- next-header -->
## Unreleased - ReleaseDate
- LPC55: DMA Controller and asynchronous version of USART
- Moved NXP LPC55S69 from `lpc55-pac` to `nxp-pac`
- First release with changelog.

View File

@ -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 }

View File

@ -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,

5
embassy-nxp/src/dma.rs Normal file
View File

@ -0,0 +1,5 @@
//! Direct Memory Access (DMA) driver.
#[cfg_attr(feature = "lpc55-core0", path = "./dma/lpc55.rs")]
mod inner;
pub use inner::*;

View File

@ -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::<DmaDescriptor>(), 16, "Descriptor must be 16 bytes");
assert_eq!(
core::mem::align_of::<DmaDescriptor>(),
16,
"Descriptor must be 16-byte aligned"
);
assert_eq!(
core::mem::align_of::<DmaDescriptorTable>(),
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 channels 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<Self::Output> {
// 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<RefCell<DmaDescriptorTable>> = 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<AnyChannel> + 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<peripherals::$name> 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);

View File

@ -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<peripherals::USART0>;
/// }
/// );
/// ```
#[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);

View File

@ -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<Peri<'d, AnyChannel>>,
phantom: PhantomData<M>,
}
pub struct UsartRx<'d, M: Mode> {
info: &'static Info,
phantom: PhantomData<(&'d (), M)>,
dma_state: &'static DmaState,
rx_dma: Option<Peri<'d, AnyChannel>>,
phantom: PhantomData<M>,
}
impl<'d, M: Mode> UsartTx<'d, M> {
pub fn new<T: Instance>(_usart: Peri<'d, T>, tx: Peri<'d, impl TxPin<T>>, config: Config) -> Self {
pub fn new<T: Instance>(
_usart: Peri<'d, T>,
tx: Peri<'d, impl TxPin<T>>,
tx_dma: Peri<'d, impl Channel>,
config: Config,
) -> Self {
Usart::<M>::init::<T>(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<Peri<'d, AnyChannel>>) -> 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<T: Instance>(_usart: Peri<'d, T>, tx: Peri<'d, impl TxPin<T>>, config: Config) -> Self {
Usart::<Blocking>::init::<T>(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<T: Instance>(_usart: Peri<'d, T>, rx: Peri<'d, impl RxPin<T>>, config: Config) -> Self {
pub fn new<T: Instance>(
_usart: Peri<'d, T>,
rx: Peri<'d, impl RxPin<T>>,
has_irq: bool,
rx_dma: Peri<'d, impl Channel>,
config: Config,
) -> Self {
Usart::<M>::init::<T>(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<Peri<'d, AnyChannel>>,
) -> 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<T: Instance>(_usart: Peri<'d, T>, rx: Peri<'d, impl RxPin<T>>, config: Config) -> Self {
Usart::<Blocking>::init::<T>(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<T: Instance> {
_uart: PhantomData<T>,
}
impl<T: Instance> crate::interrupt::typelevel::Handler<T::Interrupt> for InterruptHandler<T> {
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<T>>,
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<T: Instance>(
uart: Peri<'d, T>,
tx: Peri<'d, impl TxPin<T>>,
rx: Peri<'d, impl RxPin<T>>,
_irq: impl Binding<T::Interrupt, InterruptHandler<T>>,
tx_dma: Peri<'d, impl TxChannel<T>>,
rx_dma: Peri<'d, impl RxChannel<T>>,
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<Peri<'d, AnyChannel>>,
rx_dma: Option<Peri<'d, AnyChannel>>,
config: Config,
) -> Self {
Self::init::<T>(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<u8> 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<T: Instance>: crate::dma::Channel {}
/// Trait for RX DMA channels.
pub trait RxChannel<T: Instance>: crate::dma::Channel {}
macro_rules! impl_channel {
($dma:ident, $instance:ident, Tx) => {
impl TxChannel<crate::peripherals::$instance> for crate::peripherals::$dma {}
};
($dma:ident, $instance:ident, Rx) => {
impl RxChannel<crate::peripherals::$instance> 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);

View File

@ -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<USART2>;
}
);
#[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;
}
}