From 4314b823aaadc54387fbc1379f89300c70007d83 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Sat, 4 Mar 2023 05:24:38 +0100 Subject: [PATCH 1/5] nrf: add PPI channel group driver. --- embassy-nrf/src/ppi/dppi.rs | 2 +- embassy-nrf/src/ppi/mod.rs | 104 +++++++++++++++++++++++++++++++++--- embassy-nrf/src/ppi/ppi.rs | 2 +- 3 files changed, 98 insertions(+), 10 deletions(-) diff --git a/embassy-nrf/src/ppi/dppi.rs b/embassy-nrf/src/ppi/dppi.rs index 0908cd7b..3a1e7f17 100644 --- a/embassy-nrf/src/ppi/dppi.rs +++ b/embassy-nrf/src/ppi/dppi.rs @@ -6,7 +6,7 @@ use crate::{pac, Peripheral}; const DPPI_ENABLE_BIT: u32 = 0x8000_0000; const DPPI_CHANNEL_MASK: u32 = 0x0000_00FF; -fn regs() -> &'static pac::dppic::RegisterBlock { +pub(crate) fn regs() -> &'static pac::dppic::RegisterBlock { unsafe { &*pac::DPPIC::ptr() } } diff --git a/embassy-nrf/src/ppi/mod.rs b/embassy-nrf/src/ppi/mod.rs index b76eccf0..7c18da6e 100644 --- a/embassy-nrf/src/ppi/mod.rs +++ b/embassy-nrf/src/ppi/mod.rs @@ -17,16 +17,16 @@ use core::ptr::NonNull; -use embassy_hal_common::{impl_peripheral, PeripheralRef}; +use embassy_hal_common::{impl_peripheral, into_ref, PeripheralRef}; use crate::{peripherals, Peripheral}; -#[cfg(feature = "_dppi")] -mod dppi; -#[cfg(feature = "_ppi")] -mod ppi; +#[cfg_attr(feature = "_dppi", path = "dppi.rs")] +#[cfg_attr(feature = "_ppi", path = "ppi.rs")] +mod _version; +pub(crate) use _version::*; -/// An instance of the Programmable peripheral interconnect on nRF devices. +/// PPI channel driver. pub struct Ppi<'d, C: Channel, const EVENT_COUNT: usize, const TASK_COUNT: usize> { ch: PeripheralRef<'d, C>, #[cfg(feature = "_dppi")] @@ -35,6 +35,88 @@ pub struct Ppi<'d, C: Channel, const EVENT_COUNT: usize, const TASK_COUNT: usize tasks: [Task; TASK_COUNT], } +/// PPI channel group driver. +pub struct PpiGroup<'d, G: Group> { + g: PeripheralRef<'d, G>, +} + +impl<'d, G: Group> PpiGroup<'d, G> { + /// Create a new PPI group driver. + /// + /// The group is initialized as containing no channels. + pub fn new(g: impl Peripheral

+ 'd) -> Self { + into_ref!(g); + + let r = regs(); + let n = g.number(); + r.chg[n].write(|w| unsafe { w.bits(0) }); + + Self { g } + } + + /// Add a PPI channel to this group. + /// + /// If the channel is already in the group, this is a no-op. + pub fn add_channel( + &mut self, + ch: &Ppi<'_, C, EVENT_COUNT, TASK_COUNT>, + ) { + let r = regs(); + let ng = self.g.number(); + let nc = ch.ch.number(); + r.chg[ng].modify(|r, w| unsafe { w.bits(r.bits() | 1 << nc) }); + } + + /// Remove a PPI channel from this group. + /// + /// If the channel is already not in the group, this is a no-op. + pub fn remove_channel( + &mut self, + ch: &Ppi<'_, C, EVENT_COUNT, TASK_COUNT>, + ) { + let r = regs(); + let ng = self.g.number(); + let nc = ch.ch.number(); + r.chg[ng].modify(|r, w| unsafe { w.bits(r.bits() & !(1 << nc)) }); + } + + /// Enable all the channels in this group. + pub fn enable_all(&mut self) { + let n = self.g.number(); + regs().tasks_chg[n].en.write(|w| unsafe { w.bits(1) }); + } + + /// Disable all the channels in this group. + pub fn disable_all(&mut self) { + let n = self.g.number(); + regs().tasks_chg[n].dis.write(|w| unsafe { w.bits(1) }); + } + + /// Get a reference to the "enable all" task. + /// + /// When triggered, it will enable all the channels in this group. + pub fn task_enable_all(&self) -> Task { + let n = self.g.number(); + Task::from_reg(®s().tasks_chg[n].en) + } + + /// Get a reference to the "disable all" task. + /// + /// When triggered, it will disable all the channels in this group. + pub fn task_disable_all(&self) -> Task { + let n = self.g.number(); + Task::from_reg(®s().tasks_chg[n].dis) + } +} + +impl<'d, G: Group> Drop for PpiGroup<'d, G> { + fn drop(&mut self) { + let r = regs(); + let n = self.g.number(); + r.chg[n].write(|w| unsafe { w.bits(0) }); + } +} + #[cfg(feature = "_dppi")] const REGISTER_DPPI_CONFIG_OFFSET: usize = 0x80 / core::mem::size_of::(); @@ -112,7 +194,7 @@ pub(crate) mod sealed { } /// Interface for PPI channels. -pub trait Channel: sealed::Channel + Peripheral

+ Sized { +pub trait Channel: sealed::Channel + Peripheral

+ Sized + 'static { /// Returns the number of the channel fn number(&self) -> usize; } @@ -130,7 +212,7 @@ pub trait StaticChannel: Channel + Into { } /// Interface for a group of PPI channels. -pub trait Group: sealed::Group + Sized { +pub trait Group: sealed::Group + Peripheral

+ Into + Sized + 'static { /// Returns the number of the group. fn number(&self) -> usize; /// Convert into a type erased group. @@ -248,6 +330,12 @@ macro_rules! impl_group { $number } } + + impl From for crate::ppi::AnyGroup { + fn from(val: peripherals::$type) -> Self { + crate::ppi::Group::degrade(val) + } + } }; } diff --git a/embassy-nrf/src/ppi/ppi.rs b/embassy-nrf/src/ppi/ppi.rs index a96ab50b..f1eeaee1 100644 --- a/embassy-nrf/src/ppi/ppi.rs +++ b/embassy-nrf/src/ppi/ppi.rs @@ -14,7 +14,7 @@ impl Event { } } -fn regs() -> &'static pac::ppi::RegisterBlock { +pub(crate) fn regs() -> &'static pac::ppi::RegisterBlock { unsafe { &*pac::PPI::ptr() } } From 51478caad843e4b0be1d1baf83d1f0e8c0d96a30 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Sat, 4 Mar 2023 05:25:57 +0100 Subject: [PATCH 2/5] nrf/timer: add support for counter mode. --- embassy-nrf/src/timer.rs | 43 +++++++++++++++++++++++++++++++++++----- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/embassy-nrf/src/timer.rs b/embassy-nrf/src/timer.rs index d1ae5723..3b0d2f1c 100644 --- a/embassy-nrf/src/timer.rs +++ b/embassy-nrf/src/timer.rs @@ -132,7 +132,21 @@ impl<'d, T: Instance> Timer<'d, T, Awaitable> { irq.unpend(); irq.enable(); - Self::new_inner(timer) + Self::new_inner(timer, false) + } + + /// Create a new async-capable timer driver in counter mode. + pub fn new_awaitable_counter( + timer: impl Peripheral

+ 'd, + irq: impl Peripheral

+ 'd, + ) -> Self { + into_ref!(irq); + + irq.set_handler(Self::on_interrupt); + irq.unpend(); + irq.enable(); + + Self::new_inner(timer, true) } } @@ -142,7 +156,15 @@ impl<'d, T: Instance> Timer<'d, T, NotAwaitable> { /// This can be useful for triggering tasks via PPI /// `Uarte` uses this internally. pub fn new(timer: impl Peripheral

+ 'd) -> Self { - Self::new_inner(timer) + Self::new_inner(timer, false) + } + + /// Create a `Timer` driver in counter mode without an interrupt, meaning `Cc::wait` won't work. + /// + /// This can be useful for triggering tasks via PPI + /// `Uarte` uses this internally. + pub fn new_counter(timer: impl Peripheral

+ 'd) -> Self { + Self::new_inner(timer, true) } } @@ -150,7 +172,7 @@ impl<'d, T: Instance, I: TimerType> Timer<'d, T, I> { /// Create a `Timer` without an interrupt, meaning `Cc::wait` won't work. /// /// This is used by the public constructors. - fn new_inner(timer: impl Peripheral

+ 'd) -> Self { + fn new_inner(timer: impl Peripheral

+ 'd, is_counter: bool) -> Self { into_ref!(timer); let regs = T::regs(); @@ -164,8 +186,11 @@ impl<'d, T: Instance, I: TimerType> Timer<'d, T, I> { // since changing BITMODE while running can cause 'unpredictable behaviour' according to the specification. this.stop(); - // Set the instance to timer mode. - regs.mode.write(|w| w.mode().timer()); + if is_counter { + regs.mode.write(|w| w.mode().counter()); + } else { + regs.mode.write(|w| w.mode().timer()); + } // Make the counter's max value as high as possible. // TODO: is there a reason someone would want to set this lower? @@ -225,6 +250,14 @@ impl<'d, T: Instance, I: TimerType> Timer<'d, T, I> { Task::from_reg(&T::regs().tasks_clear) } + /// Returns the COUNT task, for use with PPI. + /// + /// When triggered, this task increments the timer's counter by 1. + /// Only works in counter mode. + pub fn task_count(&self) -> Task { + Task::from_reg(&T::regs().tasks_count) + } + /// Change the timer's frequency. /// /// This will stop the timer if it isn't already stopped, From ccc224c81ff1a56296576f4a249fe91a37c03fd8 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Sat, 4 Mar 2023 05:27:29 +0100 Subject: [PATCH 3/5] nrf/buffered_uarte: remove PeripheralMutex, make it work without rts/cts. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit > dirbaio: so I was checking how zephyr does UARTE RX on nRF > dirbaio: because currently we have the ugly "restart DMA on line idle to flush it" hack > dirbaio: because according to the docs "For each byte received over the RXD line, an RXDRDY event will be generated. This event is likely to occur before the corresponding data has been transferred to Data RAM." > dirbaio: so as I understood it, the only way to guarantee the data is actually transferred to RAM is to stop+restart DMA > dirbaio: well, guess what? > dirbaio: they just count RXDRDY's, and process that amount of data without restarting DMA > dirbaio: with a timer configured as counter https://github.com/zephyrproject-rtos/zephyr/blob/main/drivers/serial/uart_nrfx_uarte.c#L650-L692 > dirbaio: 🤔🤷⁉️ > dirbaio: someone saying you can do the "hook up rxdrdy to a counter" trick, someone else saying it's wrong 🤪 https://devzone.nordicsemi.com/f/nordic-q-a/28420/uarte-in-circular-mode So we're going to do just that! - BufferedUarte is lock-free now. No PeripheralMutex. - The "restart DMA on line idle to flush it" hack is GONE. This means - It'll work correctly without RTS/CTS now. - It'll have better throughput when using RTS/CTS. --- embassy-nrf/src/buffered_uarte.rs | 668 ++++++++++++--------- embassy-nrf/src/uarte.rs | 5 + examples/nrf52840/src/bin/buffered_uart.rs | 12 +- 3 files changed, 379 insertions(+), 306 deletions(-) diff --git a/embassy-nrf/src/buffered_uarte.rs b/embassy-nrf/src/buffered_uarte.rs index 112f084c..79f9a1f7 100644 --- a/embassy-nrf/src/buffered_uarte.rs +++ b/embassy-nrf/src/buffered_uarte.rs @@ -1,10 +1,5 @@ //! Async buffered UART driver. //! -//! WARNING!!! The functionality provided here is intended to be used only -//! in situations where hardware flow control are available i.e. CTS and RTS. -//! This is a problem that should be addressed at a later stage and can be -//! fully explained at . -//! //! Note that discarding a future from a read or write operation may lead to losing //! data. For example, when using `futures_util::future::select` and completion occurs //! on the "other" future, you should capture the incomplete future and continue to use @@ -13,82 +8,120 @@ //! //! Please also see [crate::uarte] to understand when [BufferedUarte] should be used. -use core::cell::RefCell; use core::cmp::min; use core::future::poll_fn; -use core::sync::atomic::{compiler_fence, Ordering}; +use core::slice; +use core::sync::atomic::{compiler_fence, AtomicU8, AtomicUsize, Ordering}; use core::task::Poll; -use embassy_cortex_m::peripheral::{PeripheralMutex, PeripheralState, StateStorage}; -use embassy_hal_common::ring_buffer::RingBuffer; +use embassy_cortex_m::interrupt::Interrupt; +use embassy_hal_common::atomic_ring_buffer::RingBuffer; use embassy_hal_common::{into_ref, PeripheralRef}; -use embassy_sync::waitqueue::WakerRegistration; +use embassy_sync::waitqueue::AtomicWaker; // Re-export SVD variants to allow user to directly set values pub use pac::uarte0::{baudrate::BAUDRATE_A as Baudrate, config::PARITY_A as Parity}; -use crate::gpio::{self, Pin as GpioPin}; +use crate::gpio::sealed::Pin; +use crate::gpio::{self, AnyPin, Pin as GpioPin, PselBits}; use crate::interrupt::InterruptExt; -use crate::ppi::{AnyConfigurableChannel, ConfigurableChannel, Event, Ppi, Task}; -use crate::timer::{Frequency, Instance as TimerInstance, Timer}; +use crate::ppi::{ + self, AnyConfigurableChannel, AnyGroup, Channel, ConfigurableChannel, Event, Group, Ppi, PpiGroup, Task, +}; +use crate::timer::{Instance as TimerInstance, Timer}; use crate::uarte::{apply_workaround_for_enable_anomaly, Config, Instance as UarteInstance}; use crate::{pac, Peripheral}; -#[derive(Copy, Clone, Debug, PartialEq)] -enum RxState { - Idle, - Receiving, -} +mod sealed { + use super::*; -#[derive(Copy, Clone, Debug, PartialEq)] -enum TxState { - Idle, - Transmitting(usize), -} + pub struct State { + pub tx_waker: AtomicWaker, + pub tx_buf: RingBuffer, + pub tx_count: AtomicUsize, -/// A type for storing the state of the UARTE peripheral that can be stored in a static. -pub struct State<'d, U: UarteInstance, T: TimerInstance>(StateStorage>); -impl<'d, U: UarteInstance, T: TimerInstance> State<'d, U, T> { - /// Create an instance for storing UARTE peripheral state. - pub fn new() -> Self { - Self(StateStorage::new()) + pub rx_waker: AtomicWaker, + pub rx_buf: RingBuffer, + pub rx_bufs: AtomicU8, + pub rx_ppi_ch: AtomicU8, } } -struct StateInner<'d, U: UarteInstance, T: TimerInstance> { - _peri: PeripheralRef<'d, U>, - timer: Timer<'d, T>, - _ppi_ch1: Ppi<'d, AnyConfigurableChannel, 1, 2>, - _ppi_ch2: Ppi<'d, AnyConfigurableChannel, 1, 1>, +pub(crate) use sealed::State; - rx: RingBuffer<'d>, - rx_state: RxState, - rx_waker: WakerRegistration, +impl State { + pub(crate) const fn new() -> Self { + Self { + tx_waker: AtomicWaker::new(), + tx_buf: RingBuffer::new(), + tx_count: AtomicUsize::new(0), - tx: RingBuffer<'d>, - tx_state: TxState, - tx_waker: WakerRegistration, + rx_waker: AtomicWaker::new(), + rx_buf: RingBuffer::new(), + rx_bufs: AtomicU8::new(0), + rx_ppi_ch: AtomicU8::new(0), + } + } } /// Buffered UARTE driver. pub struct BufferedUarte<'d, U: UarteInstance, T: TimerInstance> { - inner: RefCell>>, + _peri: PeripheralRef<'d, U>, + timer: Timer<'d, T>, + _ppi_ch1: Ppi<'d, AnyConfigurableChannel, 1, 1>, + _ppi_ch2: Ppi<'d, AnyConfigurableChannel, 1, 2>, + _ppi_group: PpiGroup<'d, AnyGroup>, } impl<'d, U: UarteInstance, T: TimerInstance> Unpin for BufferedUarte<'d, U, T> {} impl<'d, U: UarteInstance, T: TimerInstance> BufferedUarte<'d, U, T> { - /// Create a new instance of a BufferedUarte. + /// Create a new BufferedUarte without hardware flow control. /// - /// See the [module documentation](crate::buffered_uarte) for more details about the intended use. + /// # Panics /// - /// The BufferedUarte uses the provided state to store the buffers and peripheral state. The timer and ppi channels are used to 'emulate' idle line detection so that read operations - /// can return early if there is no data to receive. + /// Panics if `rx_buffer.len()` is odd. pub fn new( - state: &'d mut State<'d, U, T>, - peri: impl Peripheral

+ 'd, + uarte: impl Peripheral

+ 'd, timer: impl Peripheral

+ 'd, - ppi_ch1: impl Peripheral

+ 'd, - ppi_ch2: impl Peripheral

+ 'd, + ppi_ch1: impl Peripheral

+ 'd, + ppi_ch2: impl Peripheral

+ 'd, + ppi_group: impl Peripheral

+ 'd, + irq: impl Peripheral

+ 'd, + rxd: impl Peripheral

+ 'd, + txd: impl Peripheral

+ 'd, + config: Config, + rx_buffer: &'d mut [u8], + tx_buffer: &'d mut [u8], + ) -> Self { + into_ref!(rxd, txd, ppi_ch1, ppi_ch2, ppi_group); + Self::new_inner( + uarte, + timer, + ppi_ch1.map_into(), + ppi_ch2.map_into(), + ppi_group.map_into(), + irq, + rxd.map_into(), + txd.map_into(), + None, + None, + config, + rx_buffer, + tx_buffer, + ) + } + + /// Create a new BufferedUarte with hardware flow control (RTS/CTS) + /// + /// # Panics + /// + /// Panics if `rx_buffer.len()` is odd. + pub fn new_with_rtscts( + uarte: impl Peripheral

+ 'd, + timer: impl Peripheral

+ 'd, + ppi_ch1: impl Peripheral

+ 'd, + ppi_ch2: impl Peripheral

+ 'd, + ppi_group: impl Peripheral

+ 'd, irq: impl Peripheral

+ 'd, rxd: impl Peripheral

+ 'd, txd: impl Peripheral

+ 'd, @@ -98,12 +131,45 @@ impl<'d, U: UarteInstance, T: TimerInstance> BufferedUarte<'d, U, T> { rx_buffer: &'d mut [u8], tx_buffer: &'d mut [u8], ) -> Self { - into_ref!(peri, ppi_ch1, ppi_ch2, irq, rxd, txd, cts, rts); + into_ref!(rxd, txd, cts, rts, ppi_ch1, ppi_ch2, ppi_group); + Self::new_inner( + uarte, + timer, + ppi_ch1.map_into(), + ppi_ch2.map_into(), + ppi_group.map_into(), + irq, + rxd.map_into(), + txd.map_into(), + Some(cts.map_into()), + Some(rts.map_into()), + config, + rx_buffer, + tx_buffer, + ) + } + + fn new_inner( + peri: impl Peripheral

+ 'd, + timer: impl Peripheral

+ 'd, + ppi_ch1: PeripheralRef<'d, AnyConfigurableChannel>, + ppi_ch2: PeripheralRef<'d, AnyConfigurableChannel>, + ppi_group: PeripheralRef<'d, AnyGroup>, + irq: impl Peripheral

+ 'd, + rxd: PeripheralRef<'d, AnyPin>, + txd: PeripheralRef<'d, AnyPin>, + cts: Option>, + rts: Option>, + config: Config, + rx_buffer: &'d mut [u8], + tx_buffer: &'d mut [u8], + ) -> Self { + into_ref!(peri, timer, irq); + + assert!(rx_buffer.len() % 2 == 0); let r = U::regs(); - let mut timer = Timer::new(timer); - rxd.conf().write(|w| w.input().connect().drive().h0h1()); r.psel.rxd.write(|w| unsafe { w.bits(rxd.psel_bits()) }); @@ -111,92 +177,200 @@ impl<'d, U: UarteInstance, T: TimerInstance> BufferedUarte<'d, U, T> { txd.conf().write(|w| w.dir().output().drive().h0h1()); r.psel.txd.write(|w| unsafe { w.bits(txd.psel_bits()) }); - cts.conf().write(|w| w.input().connect().drive().h0h1()); + if let Some(pin) = &cts { + pin.conf().write(|w| w.input().connect().drive().h0h1()); + } r.psel.cts.write(|w| unsafe { w.bits(cts.psel_bits()) }); - rts.set_high(); - rts.conf().write(|w| w.dir().output().drive().h0h1()); + if let Some(pin) = &rts { + pin.set_high(); + pin.conf().write(|w| w.dir().output().drive().h0h1()); + } r.psel.rts.write(|w| unsafe { w.bits(rts.psel_bits()) }); - r.baudrate.write(|w| w.baudrate().variant(config.baudrate)); - r.config.write(|w| w.parity().variant(config.parity)); + // Initialize state + let s = U::buffered_state(); + s.tx_count.store(0, Ordering::Relaxed); + s.rx_bufs.store(0, Ordering::Relaxed); + let len = tx_buffer.len(); + unsafe { s.tx_buf.init(tx_buffer.as_mut_ptr(), len) }; + let len = rx_buffer.len(); + unsafe { s.rx_buf.init(rx_buffer.as_mut_ptr(), len) }; // Configure r.config.write(|w| { - w.hwfc().bit(true); + w.hwfc().bit(false); w.parity().variant(config.parity); w }); r.baudrate.write(|w| w.baudrate().variant(config.baudrate)); - // Enable interrupts - r.intenset.write(|w| w.endrx().set().endtx().set()); + // clear errors + let errors = r.errorsrc.read().bits(); + r.errorsrc.write(|w| unsafe { w.bits(errors) }); - // Disable the irq, let the Registration enable it when everything is set up. - irq.disable(); - irq.pend(); + r.events_rxstarted.reset(); + r.events_txstarted.reset(); + r.events_error.reset(); + r.events_endrx.reset(); + r.events_endtx.reset(); + + // Enable interrupts + r.intenclr.write(|w| unsafe { w.bits(!0) }); + r.intenset.write(|w| { + w.endtx().set(); + w.rxstarted().set(); + w.error().set(); + w + }); // Enable UARTE instance apply_workaround_for_enable_anomaly(&r); r.enable.write(|w| w.enable().enabled()); - // BAUDRATE register values are `baudrate * 2^32 / 16000000` - // source: https://devzone.nordicsemi.com/f/nordic-q-a/391/uart-baudrate-register-values - // - // We want to stop RX if line is idle for 2 bytes worth of time - // That is 20 bits (each byte is 1 start bit + 8 data bits + 1 stop bit) - // This gives us the amount of 16M ticks for 20 bits. - let timeout = 0x8000_0000 / (config.baudrate as u32 / 40); + // Configure byte counter. + let mut timer = Timer::new_counter(timer); + timer.cc(1).write(rx_buffer.len() as u32 * 2); + timer.cc(1).short_compare_clear(); + timer.clear(); + timer.start(); - timer.set_frequency(Frequency::F16MHz); - timer.cc(0).write(timeout); - timer.cc(0).short_compare_clear(); - timer.cc(0).short_compare_stop(); - - let mut ppi_ch1 = Ppi::new_one_to_two( - ppi_ch1.map_into(), - Event::from_reg(&r.events_rxdrdy), - timer.task_clear(), - timer.task_start(), - ); + let mut ppi_ch1 = Ppi::new_one_to_one(ppi_ch1, Event::from_reg(&r.events_rxdrdy), timer.task_count()); ppi_ch1.enable(); - let mut ppi_ch2 = Ppi::new_one_to_one( - ppi_ch2.map_into(), - timer.cc(0).event_compare(), - Task::from_reg(&r.tasks_stoprx), + s.rx_ppi_ch.store(ppi_ch2.number() as u8, Ordering::Relaxed); + let mut ppi_group = PpiGroup::new(ppi_group); + let mut ppi_ch2 = Ppi::new_one_to_two( + ppi_ch2, + Event::from_reg(&r.events_endrx), + Task::from_reg(&r.tasks_startrx), + ppi_group.task_disable_all(), ); - ppi_ch2.enable(); + ppi_ch2.disable(); + ppi_group.add_channel(&ppi_ch2); + + irq.disable(); + irq.set_handler(Self::on_interrupt); + irq.pend(); + irq.enable(); Self { - inner: RefCell::new(PeripheralMutex::new(irq, &mut state.0, move || StateInner { - _peri: peri, - timer, - _ppi_ch1: ppi_ch1, - _ppi_ch2: ppi_ch2, - - rx: RingBuffer::new(rx_buffer), - rx_state: RxState::Idle, - rx_waker: WakerRegistration::new(), - - tx: RingBuffer::new(tx_buffer), - tx_state: TxState::Idle, - tx_waker: WakerRegistration::new(), - })), + _peri: peri, + timer, + _ppi_ch1: ppi_ch1, + _ppi_ch2: ppi_ch2, + _ppi_group: ppi_group, } } + fn pend_irq() { + unsafe { ::steal() }.pend() + } + + fn on_interrupt(_: *mut ()) { + //trace!("irq: start"); + let r = U::regs(); + let s = U::buffered_state(); + + let buf_len = s.rx_buf.len(); + let half_len = buf_len / 2; + let mut tx = unsafe { s.tx_buf.reader() }; + let mut rx = unsafe { s.rx_buf.writer() }; + + if r.events_error.read().bits() != 0 { + r.events_error.reset(); + let errs = r.errorsrc.read(); + r.errorsrc.write(|w| unsafe { w.bits(errs.bits()) }); + + if errs.overrun().bit() { + panic!("BufferedUarte overrun"); + } + } + + // Received some bytes, wake task. + if r.inten.read().rxdrdy().bit_is_set() && r.events_rxdrdy.read().events_rxdrdy().bit_is_set() { + r.intenclr.write(|w| w.rxdrdy().clear()); + r.events_rxdrdy.reset(); + s.rx_waker.wake(); + } + + // If not RXing, start. + if s.rx_bufs.load(Ordering::Relaxed) == 0 { + let (ptr, len) = rx.push_buf(); + if len >= half_len { + //trace!(" irq_rx: starting {:?}", half_len); + s.rx_bufs.store(1, Ordering::Relaxed); + + // Set up the DMA read + r.rxd.ptr.write(|w| unsafe { w.ptr().bits(ptr as u32) }); + r.rxd.maxcnt.write(|w| unsafe { w.maxcnt().bits(half_len as _) }); + + // Start UARTE Receive transaction + r.tasks_startrx.write(|w| unsafe { w.bits(1) }); + rx.push_done(half_len); + r.intenset.write(|w| w.rxstarted().set()); + } + } + + if r.events_rxstarted.read().bits() != 0 { + //trace!(" irq_rx: rxstarted"); + let (ptr, len) = rx.push_buf(); + if len >= half_len { + //trace!(" irq_rx: starting second {:?}", half_len); + + // Set up the DMA read + r.rxd.ptr.write(|w| unsafe { w.ptr().bits(ptr as u32) }); + r.rxd.maxcnt.write(|w| unsafe { w.maxcnt().bits(half_len as _) }); + + let chn = s.rx_ppi_ch.load(Ordering::Relaxed); + + ppi::regs().chenset.write(|w| unsafe { w.bits(1 << chn) }); + + rx.push_done(half_len); + + r.events_rxstarted.reset(); + } else { + //trace!(" irq_rx: rxstarted no buf"); + r.intenclr.write(|w| w.rxstarted().clear()); + } + } + + // ============================= + + // TX end + if r.events_endtx.read().bits() != 0 { + r.events_endtx.reset(); + + let n = s.tx_count.load(Ordering::Relaxed); + //trace!(" irq_tx: endtx {:?}", n); + tx.pop_done(n); + s.tx_waker.wake(); + s.tx_count.store(0, Ordering::Relaxed); + } + + // If not TXing, start. + if s.tx_count.load(Ordering::Relaxed) == 0 { + let (ptr, len) = tx.pop_buf(); + if len != 0 { + //trace!(" irq_tx: starting {:?}", len); + s.tx_count.store(len, Ordering::Relaxed); + + // Set up the DMA write + r.txd.ptr.write(|w| unsafe { w.ptr().bits(ptr as u32) }); + r.txd.maxcnt.write(|w| unsafe { w.maxcnt().bits(len as _) }); + + // Start UARTE Transmit transaction + r.tasks_starttx.write(|w| unsafe { w.bits(1) }); + } + } + + //trace!("irq: end"); + } + /// Adjust the baud rate to the provided value. pub fn set_baudrate(&mut self, baudrate: Baudrate) { - self.inner.borrow_mut().with(|state| { - let r = U::regs(); - - let timeout = 0x8000_0000 / (baudrate as u32 / 40); - state.timer.cc(0).write(timeout); - state.timer.clear(); - - r.baudrate.write(|w| w.baudrate().variant(baudrate)); - }); + let r = U::regs(); + r.baudrate.write(|w| w.baudrate().variant(baudrate)); } /// Split the UART in reader and writer parts. @@ -206,120 +380,117 @@ impl<'d, U: UarteInstance, T: TimerInstance> BufferedUarte<'d, U, T> { (BufferedUarteRx { inner: self }, BufferedUarteTx { inner: self }) } - async fn inner_read<'a>(&'a self, buf: &'a mut [u8]) -> Result { - poll_fn(move |cx| { - let mut do_pend = false; - let mut inner = self.inner.borrow_mut(); - let res = inner.with(|state| { - compiler_fence(Ordering::SeqCst); - trace!("poll_read"); - - // We have data ready in buffer? Return it. - let data = state.rx.pop_buf(); - if !data.is_empty() { - trace!(" got {:?} {:?}", data.as_ptr() as u32, data.len()); - let len = data.len().min(buf.len()); - buf[..len].copy_from_slice(&data[..len]); - state.rx.pop(len); - do_pend = true; - return Poll::Ready(Ok(len)); - } - - trace!(" empty"); - state.rx_waker.register(cx.waker()); - Poll::Pending - }); - if do_pend { - inner.pend(); - } - - res - }) - .await + async fn inner_read(&self, buf: &mut [u8]) -> Result { + let data = self.inner_fill_buf().await?; + let n = data.len().min(buf.len()); + buf[..n].copy_from_slice(&data[..n]); + self.inner_consume(n); + Ok(n) } async fn inner_write<'a>(&'a self, buf: &'a [u8]) -> Result { poll_fn(move |cx| { - let mut inner = self.inner.borrow_mut(); - let res = inner.with(|state| { - trace!("poll_write: {:?}", buf.len()); + //trace!("poll_write: {:?}", buf.len()); + let s = U::buffered_state(); + let mut tx = unsafe { s.tx_buf.writer() }; - let tx_buf = state.tx.push_buf(); - if tx_buf.is_empty() { - trace!("poll_write: pending"); - state.tx_waker.register(cx.waker()); - return Poll::Pending; - } + let tx_buf = tx.push_slice(); + if tx_buf.is_empty() { + //trace!("poll_write: pending"); + s.tx_waker.register(cx.waker()); + return Poll::Pending; + } - let n = min(tx_buf.len(), buf.len()); - tx_buf[..n].copy_from_slice(&buf[..n]); - state.tx.push(n); + let n = min(tx_buf.len(), buf.len()); + tx_buf[..n].copy_from_slice(&buf[..n]); + tx.push_done(n); - trace!("poll_write: queued {:?}", n); + //trace!("poll_write: queued {:?}", n); - compiler_fence(Ordering::SeqCst); + compiler_fence(Ordering::SeqCst); + Self::pend_irq(); - Poll::Ready(Ok(n)) - }); - - inner.pend(); - - res + Poll::Ready(Ok(n)) }) .await } async fn inner_flush<'a>(&'a self) -> Result<(), core::convert::Infallible> { poll_fn(move |cx| { - self.inner.borrow_mut().with(|state| { - trace!("poll_flush"); + //trace!("poll_flush"); + let s = U::buffered_state(); + if !s.tx_buf.is_empty() { + //trace!("poll_flush: pending"); + s.tx_waker.register(cx.waker()); + return Poll::Pending; + } - if !state.tx.is_empty() { - trace!("poll_flush: pending"); - state.tx_waker.register(cx.waker()); - return Poll::Pending; - } - - Poll::Ready(Ok(())) - }) + Poll::Ready(Ok(())) }) .await } async fn inner_fill_buf<'a>(&'a self) -> Result<&'a [u8], core::convert::Infallible> { poll_fn(move |cx| { - self.inner.borrow_mut().with(|state| { - compiler_fence(Ordering::SeqCst); - trace!("fill_buf"); + compiler_fence(Ordering::SeqCst); + //trace!("poll_read"); - // We have data ready in buffer? Return it. - let buf = state.rx.pop_buf(); - if !buf.is_empty() { - trace!(" got {:?} {:?}", buf.as_ptr() as u32, buf.len()); - let buf: &[u8] = buf; - // Safety: buffer lives as long as uart - let buf: &[u8] = unsafe { core::mem::transmute(buf) }; - return Poll::Ready(Ok(buf)); - } + let r = U::regs(); + let s = U::buffered_state(); - trace!(" empty"); - state.rx_waker.register(cx.waker()); - Poll::>::Pending - }) + // Read the RXDRDY counter. + T::regs().tasks_capture[0].write(|w| unsafe { w.bits(1) }); + let mut end = T::regs().cc[0].read().bits() as usize; + //trace!(" rxdrdy count = {:?}", end); + + // We've set a compare channel that resets the counter to 0 when it reaches `len*2`. + // However, it's unclear if that's instant, or there's a small window where you can + // still read `len()*2`. + // This could happen if in one clock cycle the counter is updated, and in the next the + // clear takes effect. The docs are very sparse, they just say "Task delays: After TIMER + // is started, the CLEAR, COUNT, and STOP tasks are guaranteed to take effect within one + // clock cycle of the PCLK16M." :shrug: + // So, we wrap the counter ourselves, just in case. + if end > s.rx_buf.len() * 2 { + end = 0 + } + + // This logic mirrors `atomic_ring_buffer::Reader::pop_buf()` + let mut start = s.rx_buf.start.load(Ordering::Relaxed); + let len = s.rx_buf.len(); + if start == end { + //trace!(" empty"); + s.rx_waker.register(cx.waker()); + r.intenset.write(|w| w.rxdrdy().set_bit()); + return Poll::Pending; + } + + if start >= len { + start -= len + } + if end >= len { + end -= len + } + + let n = if end > start { end - start } else { len - start }; + assert!(n != 0); + //trace!(" uarte ringbuf: pop_buf {:?}..{:?}", start, start + n); + + let buf = s.rx_buf.buf.load(Ordering::Relaxed); + Poll::Ready(Ok(unsafe { slice::from_raw_parts(buf.add(start), n) })) }) .await } fn inner_consume(&self, amt: usize) { - let mut inner = self.inner.borrow_mut(); - let signal = inner.with(|state| { - let full = state.rx.is_full(); - state.rx.pop(amt); - full - }); - if signal { - inner.pend(); + if amt == 0 { + return; } + + let s = U::buffered_state(); + let mut rx = unsafe { s.rx_buf.reader() }; + rx.pop_done(amt); + U::regs().intenset.write(|w| w.rxstarted().set()); } } @@ -397,7 +568,7 @@ impl<'u, 'd: 'u, U: UarteInstance, T: TimerInstance> embedded_io::asynch::Write } } -impl<'a, U: UarteInstance, T: TimerInstance> Drop for StateInner<'a, U, T> { +impl<'a, U: UarteInstance, T: TimerInstance> Drop for BufferedUarte<'a, U, T> { fn drop(&mut self) { let r = U::regs(); @@ -418,108 +589,11 @@ impl<'a, U: UarteInstance, T: TimerInstance> Drop for StateInner<'a, U, T> { gpio::deconfigure_pin(r.psel.txd.read().bits()); gpio::deconfigure_pin(r.psel.rts.read().bits()); gpio::deconfigure_pin(r.psel.cts.read().bits()); - } -} - -impl<'a, U: UarteInstance, T: TimerInstance> PeripheralState for StateInner<'a, U, T> { - type Interrupt = U::Interrupt; - fn on_interrupt(&mut self) { - trace!("irq: start"); - let r = U::regs(); - - loop { - match self.rx_state { - RxState::Idle => { - trace!(" irq_rx: in state idle"); - - let buf = self.rx.push_buf(); - if !buf.is_empty() { - trace!(" irq_rx: starting {:?}", buf.len()); - self.rx_state = RxState::Receiving; - - // Set up the DMA read - r.rxd.ptr.write(|w| - // The PTR field is a full 32 bits wide and accepts the full range - // of values. - unsafe { w.ptr().bits(buf.as_ptr() as u32) }); - r.rxd.maxcnt.write(|w| - // We're giving it the length of the buffer, so no danger of - // accessing invalid memory. We have verified that the length of the - // buffer fits in an `u8`, so the cast to `u8` is also fine. - // - // The MAXCNT field is at least 8 bits wide and accepts the full - // range of values. - unsafe { w.maxcnt().bits(buf.len() as _) }); - trace!(" irq_rx: buf {:?} {:?}", buf.as_ptr() as u32, buf.len()); - - // Start UARTE Receive transaction - r.tasks_startrx.write(|w| unsafe { w.bits(1) }); - } - break; - } - RxState::Receiving => { - trace!(" irq_rx: in state receiving"); - if r.events_endrx.read().bits() != 0 { - self.timer.stop(); - - let n: usize = r.rxd.amount.read().amount().bits() as usize; - trace!(" irq_rx: endrx {:?}", n); - self.rx.push(n); - - r.events_endrx.reset(); - - self.rx_waker.wake(); - self.rx_state = RxState::Idle; - } else { - break; - } - } - } - } - - loop { - match self.tx_state { - TxState::Idle => { - trace!(" irq_tx: in state Idle"); - let buf = self.tx.pop_buf(); - if !buf.is_empty() { - trace!(" irq_tx: starting {:?}", buf.len()); - self.tx_state = TxState::Transmitting(buf.len()); - - // Set up the DMA write - r.txd.ptr.write(|w| - // The PTR field is a full 32 bits wide and accepts the full range - // of values. - unsafe { w.ptr().bits(buf.as_ptr() as u32) }); - r.txd.maxcnt.write(|w| - // We're giving it the length of the buffer, so no danger of - // accessing invalid memory. We have verified that the length of the - // buffer fits in an `u8`, so the cast to `u8` is also fine. - // - // The MAXCNT field is 8 bits wide and accepts the full range of - // values. - unsafe { w.maxcnt().bits(buf.len() as _) }); - - // Start UARTE Transmit transaction - r.tasks_starttx.write(|w| unsafe { w.bits(1) }); - } - break; - } - TxState::Transmitting(n) => { - trace!(" irq_tx: in state Transmitting"); - if r.events_endtx.read().bits() != 0 { - r.events_endtx.reset(); - - trace!(" irq_tx: endtx {:?}", n); - self.tx.pop(n); - self.tx_waker.wake(); - self.tx_state = TxState::Idle; - } else { - break; - } - } - } - } - trace!("irq: end"); + + let s = U::buffered_state(); + unsafe { + s.rx_buf.deinit(); + s.tx_buf.deinit(); + } } } diff --git a/embassy-nrf/src/uarte.rs b/embassy-nrf/src/uarte.rs index 48457744..00afbd05 100644 --- a/embassy-nrf/src/uarte.rs +++ b/embassy-nrf/src/uarte.rs @@ -883,6 +883,7 @@ pub(crate) mod sealed { pub trait Instance { fn regs() -> &'static pac::uarte0::RegisterBlock; fn state() -> &'static State; + fn buffered_state() -> &'static crate::buffered_uarte::State; } } @@ -902,6 +903,10 @@ macro_rules! impl_uarte { static STATE: crate::uarte::sealed::State = crate::uarte::sealed::State::new(); &STATE } + fn buffered_state() -> &'static crate::buffered_uarte::State { + static STATE: crate::buffered_uarte::State = crate::buffered_uarte::State::new(); + &STATE + } } impl crate::uarte::Instance for peripherals::$type { type Interrupt = crate::interrupt::$irq; diff --git a/examples/nrf52840/src/bin/buffered_uart.rs b/examples/nrf52840/src/bin/buffered_uart.rs index ea566f4b..584e6b2b 100644 --- a/examples/nrf52840/src/bin/buffered_uart.rs +++ b/examples/nrf52840/src/bin/buffered_uart.rs @@ -4,10 +4,9 @@ use defmt::*; use embassy_executor::Spawner; -use embassy_nrf::buffered_uarte::{BufferedUarte, State}; +use embassy_nrf::buffered_uarte::BufferedUarte; use embassy_nrf::{interrupt, uarte}; use embedded_io::asynch::{BufRead, Write}; -use futures::pin_mut; use {defmt_rtt as _, panic_probe as _}; #[embassy_executor::main] @@ -21,24 +20,19 @@ async fn main(_spawner: Spawner) { let mut rx_buffer = [0u8; 4096]; let irq = interrupt::take!(UARTE0_UART0); - let mut state = State::new(); - // Please note - important to have hardware flow control (https://github.com/embassy-rs/embassy/issues/536) - let u = BufferedUarte::new( - &mut state, + let mut u = BufferedUarte::new( p.UARTE0, p.TIMER0, p.PPI_CH0, p.PPI_CH1, + p.PPI_GROUP0, irq, p.P0_08, p.P0_06, - p.P0_07, - p.P0_05, config, &mut rx_buffer, &mut tx_buffer, ); - pin_mut!(u); info!("uarte initialized!"); From 916f94b36663cbb638654b34acd53e30beb5c7b6 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Sat, 4 Mar 2023 05:59:16 +0100 Subject: [PATCH 4/5] nrf/buffered_uarte: make available on stable. --- embassy-nrf/src/buffered_uarte.rs | 175 +++++++++++++++------ embassy-nrf/src/lib.rs | 1 - examples/nrf52840/src/bin/buffered_uart.rs | 2 +- 3 files changed, 125 insertions(+), 53 deletions(-) diff --git a/embassy-nrf/src/buffered_uarte.rs b/embassy-nrf/src/buffered_uarte.rs index 79f9a1f7..07f292c4 100644 --- a/embassy-nrf/src/buffered_uarte.rs +++ b/embassy-nrf/src/buffered_uarte.rs @@ -46,6 +46,14 @@ mod sealed { } } +/// UART error. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum Error { + // No errors for now +} + pub(crate) use sealed::State; impl State { @@ -380,7 +388,7 @@ impl<'d, U: UarteInstance, T: TimerInstance> BufferedUarte<'d, U, T> { (BufferedUarteRx { inner: self }, BufferedUarteTx { inner: self }) } - async fn inner_read(&self, buf: &mut [u8]) -> Result { + async fn inner_read(&self, buf: &mut [u8]) -> Result { let data = self.inner_fill_buf().await?; let n = data.len().min(buf.len()); buf[..n].copy_from_slice(&data[..n]); @@ -388,7 +396,7 @@ impl<'d, U: UarteInstance, T: TimerInstance> BufferedUarte<'d, U, T> { Ok(n) } - async fn inner_write<'a>(&'a self, buf: &'a [u8]) -> Result { + async fn inner_write<'a>(&'a self, buf: &'a [u8]) -> Result { poll_fn(move |cx| { //trace!("poll_write: {:?}", buf.len()); let s = U::buffered_state(); @@ -415,7 +423,7 @@ impl<'d, U: UarteInstance, T: TimerInstance> BufferedUarte<'d, U, T> { .await } - async fn inner_flush<'a>(&'a self) -> Result<(), core::convert::Infallible> { + async fn inner_flush<'a>(&'a self) -> Result<(), Error> { poll_fn(move |cx| { //trace!("poll_flush"); let s = U::buffered_state(); @@ -430,7 +438,7 @@ impl<'d, U: UarteInstance, T: TimerInstance> BufferedUarte<'d, U, T> { .await } - async fn inner_fill_buf<'a>(&'a self) -> Result<&'a [u8], core::convert::Infallible> { + async fn inner_fill_buf<'a>(&'a self) -> Result<&'a [u8], Error> { poll_fn(move |cx| { compiler_fence(Ordering::SeqCst); //trace!("poll_read"); @@ -492,6 +500,31 @@ impl<'d, U: UarteInstance, T: TimerInstance> BufferedUarte<'d, U, T> { rx.pop_done(amt); U::regs().intenset.write(|w| w.rxstarted().set()); } + + /// Pull some bytes from this source into the specified buffer, returning how many bytes were read. + pub async fn read(&mut self, buf: &mut [u8]) -> Result { + self.inner_read(buf).await + } + + /// Return the contents of the internal buffer, filling it with more data from the inner reader if it is empty. + pub async fn fill_buf(&mut self) -> Result<&[u8], Error> { + self.inner_fill_buf().await + } + + /// Tell this buffer that `amt` bytes have been consumed from the buffer, so they should no longer be returned in calls to `fill_buf`. + pub fn consume(&mut self, amt: usize) { + self.inner_consume(amt) + } + + /// Write a buffer into this writer, returning how many bytes were written. + pub async fn write(&mut self, buf: &[u8]) -> Result { + self.inner_write(buf).await + } + + /// Flush this output stream, ensuring that all intermediately buffered contents reach their destination. + pub async fn flush(&mut self) -> Result<(), Error> { + self.inner_flush().await + } } /// Reader part of the buffered UARTE driver. @@ -499,72 +532,112 @@ pub struct BufferedUarteTx<'u, 'd, U: UarteInstance, T: TimerInstance> { inner: &'u BufferedUarte<'d, U, T>, } +impl<'u, 'd, U: UarteInstance, T: TimerInstance> BufferedUarteTx<'u, 'd, U, T> { + /// Write a buffer into this writer, returning how many bytes were written. + pub async fn write(&mut self, buf: &[u8]) -> Result { + self.inner.inner_write(buf).await + } + + /// Flush this output stream, ensuring that all intermediately buffered contents reach their destination. + pub async fn flush(&mut self) -> Result<(), Error> { + self.inner.inner_flush().await + } +} + /// Writer part of the buffered UARTE driver. pub struct BufferedUarteRx<'u, 'd, U: UarteInstance, T: TimerInstance> { inner: &'u BufferedUarte<'d, U, T>, } -impl<'d, U: UarteInstance, T: TimerInstance> embedded_io::Io for BufferedUarte<'d, U, T> { - type Error = core::convert::Infallible; -} - -impl<'u, 'd, U: UarteInstance, T: TimerInstance> embedded_io::Io for BufferedUarteRx<'u, 'd, U, T> { - type Error = core::convert::Infallible; -} - -impl<'u, 'd, U: UarteInstance, T: TimerInstance> embedded_io::Io for BufferedUarteTx<'u, 'd, U, T> { - type Error = core::convert::Infallible; -} - -impl<'d, U: UarteInstance, T: TimerInstance> embedded_io::asynch::Read for BufferedUarte<'d, U, T> { - async fn read(&mut self, buf: &mut [u8]) -> Result { - self.inner_read(buf).await - } -} - -impl<'u, 'd: 'u, U: UarteInstance, T: TimerInstance> embedded_io::asynch::Read for BufferedUarteRx<'u, 'd, U, T> { - async fn read(&mut self, buf: &mut [u8]) -> Result { +impl<'u, 'd, U: UarteInstance, T: TimerInstance> BufferedUarteRx<'u, 'd, U, T> { + /// Pull some bytes from this source into the specified buffer, returning how many bytes were read. + pub async fn read(&mut self, buf: &mut [u8]) -> Result { self.inner.inner_read(buf).await } -} -impl<'d, U: UarteInstance, T: TimerInstance> embedded_io::asynch::BufRead for BufferedUarte<'d, U, T> { - async fn fill_buf(&mut self) -> Result<&[u8], Self::Error> { - self.inner_fill_buf().await - } - - fn consume(&mut self, amt: usize) { - self.inner_consume(amt) - } -} - -impl<'u, 'd: 'u, U: UarteInstance, T: TimerInstance> embedded_io::asynch::BufRead for BufferedUarteRx<'u, 'd, U, T> { - async fn fill_buf(&mut self) -> Result<&[u8], Self::Error> { + /// Return the contents of the internal buffer, filling it with more data from the inner reader if it is empty. + pub async fn fill_buf(&mut self) -> Result<&[u8], Error> { self.inner.inner_fill_buf().await } - fn consume(&mut self, amt: usize) { + /// Tell this buffer that `amt` bytes have been consumed from the buffer, so they should no longer be returned in calls to `fill_buf`. + pub fn consume(&mut self, amt: usize) { self.inner.inner_consume(amt) } } -impl<'d, U: UarteInstance, T: TimerInstance> embedded_io::asynch::Write for BufferedUarte<'d, U, T> { - async fn write(&mut self, buf: &[u8]) -> Result { - self.inner_write(buf).await +#[cfg(feature = "nightly")] +mod _embedded_io { + use super::*; + + impl embedded_io::Error for Error { + fn kind(&self) -> embedded_io::ErrorKind { + match *self {} + } } - async fn flush(&mut self) -> Result<(), Self::Error> { - self.inner_flush().await - } -} - -impl<'u, 'd: 'u, U: UarteInstance, T: TimerInstance> embedded_io::asynch::Write for BufferedUarteTx<'u, 'd, U, T> { - async fn write(&mut self, buf: &[u8]) -> Result { - self.inner.inner_write(buf).await + impl<'d, U: UarteInstance, T: TimerInstance> embedded_io::Io for BufferedUarte<'d, U, T> { + type Error = Error; } - async fn flush(&mut self) -> Result<(), Self::Error> { - self.inner.inner_flush().await + impl<'u, 'd, U: UarteInstance, T: TimerInstance> embedded_io::Io for BufferedUarteRx<'u, 'd, U, T> { + type Error = Error; + } + + impl<'u, 'd, U: UarteInstance, T: TimerInstance> embedded_io::Io for BufferedUarteTx<'u, 'd, U, T> { + type Error = Error; + } + + impl<'d, U: UarteInstance, T: TimerInstance> embedded_io::asynch::Read for BufferedUarte<'d, U, T> { + async fn read(&mut self, buf: &mut [u8]) -> Result { + self.inner_read(buf).await + } + } + + impl<'u, 'd: 'u, U: UarteInstance, T: TimerInstance> embedded_io::asynch::Read for BufferedUarteRx<'u, 'd, U, T> { + async fn read(&mut self, buf: &mut [u8]) -> Result { + self.inner.inner_read(buf).await + } + } + + impl<'d, U: UarteInstance, T: TimerInstance> embedded_io::asynch::BufRead for BufferedUarte<'d, U, T> { + async fn fill_buf(&mut self) -> Result<&[u8], Self::Error> { + self.inner_fill_buf().await + } + + fn consume(&mut self, amt: usize) { + self.inner_consume(amt) + } + } + + impl<'u, 'd: 'u, U: UarteInstance, T: TimerInstance> embedded_io::asynch::BufRead for BufferedUarteRx<'u, 'd, U, T> { + async fn fill_buf(&mut self) -> Result<&[u8], Self::Error> { + self.inner.inner_fill_buf().await + } + + fn consume(&mut self, amt: usize) { + self.inner.inner_consume(amt) + } + } + + impl<'d, U: UarteInstance, T: TimerInstance> embedded_io::asynch::Write for BufferedUarte<'d, U, T> { + async fn write(&mut self, buf: &[u8]) -> Result { + self.inner_write(buf).await + } + + async fn flush(&mut self) -> Result<(), Self::Error> { + self.inner_flush().await + } + } + + impl<'u, 'd: 'u, U: UarteInstance, T: TimerInstance> embedded_io::asynch::Write for BufferedUarteTx<'u, 'd, U, T> { + async fn write(&mut self, buf: &[u8]) -> Result { + self.inner.inner_write(buf).await + } + + async fn flush(&mut self) -> Result<(), Self::Error> { + self.inner.inner_flush().await + } } } diff --git a/embassy-nrf/src/lib.rs b/embassy-nrf/src/lib.rs index a9683df4..6b7dc779 100644 --- a/embassy-nrf/src/lib.rs +++ b/embassy-nrf/src/lib.rs @@ -37,7 +37,6 @@ pub(crate) mod util; #[cfg(feature = "_time-driver")] mod time_driver; -#[cfg(feature = "nightly")] pub mod buffered_uarte; pub mod gpio; #[cfg(feature = "gpiote")] diff --git a/examples/nrf52840/src/bin/buffered_uart.rs b/examples/nrf52840/src/bin/buffered_uart.rs index 584e6b2b..5b934b7d 100644 --- a/examples/nrf52840/src/bin/buffered_uart.rs +++ b/examples/nrf52840/src/bin/buffered_uart.rs @@ -6,7 +6,7 @@ use defmt::*; use embassy_executor::Spawner; use embassy_nrf::buffered_uarte::BufferedUarte; use embassy_nrf::{interrupt, uarte}; -use embedded_io::asynch::{BufRead, Write}; +use embedded_io::asynch::Write; use {defmt_rtt as _, panic_probe as _}; #[embassy_executor::main] From 7650fea5f2bed1c39a0ff6c5934709d316547a23 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Sat, 4 Mar 2023 05:37:13 +0100 Subject: [PATCH 5/5] nrf/buffered_uarte: add HIL tests. --- ci.sh | 1 + embassy-nrf/src/buffered_uarte.rs | 2 +- tests/nrf/.cargo/config.toml | 9 + tests/nrf/Cargo.toml | 20 ++ tests/nrf/build.rs | 16 ++ tests/nrf/link_ram.x | 254 ++++++++++++++++++++++++ tests/nrf/memory.x | 5 + tests/nrf/src/bin/buffered_uart.rs | 74 +++++++ tests/nrf/src/bin/buffered_uart_spam.rs | 86 ++++++++ 9 files changed, 466 insertions(+), 1 deletion(-) create mode 100644 tests/nrf/.cargo/config.toml create mode 100644 tests/nrf/Cargo.toml create mode 100644 tests/nrf/build.rs create mode 100644 tests/nrf/link_ram.x create mode 100644 tests/nrf/memory.x create mode 100644 tests/nrf/src/bin/buffered_uart.rs create mode 100644 tests/nrf/src/bin/buffered_uart_spam.rs diff --git a/ci.sh b/ci.sh index 417937d0..bbcb26bd 100755 --- a/ci.sh +++ b/ci.sh @@ -133,6 +133,7 @@ cargo batch \ --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32wb55rg --out-dir out/tests/nucleo-stm32wb55rg \ --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32u585ai --out-dir out/tests/iot-stm32u585ai \ --- build --release --manifest-path tests/rp/Cargo.toml --target thumbv6m-none-eabi --out-dir out/tests/rpi-pico \ + --- build --release --manifest-path tests/nrf/Cargo.toml --target thumbv7em-none-eabi --out-dir out/tests/nrf52840-dk \ $BUILD_EXTRA diff --git a/embassy-nrf/src/buffered_uarte.rs b/embassy-nrf/src/buffered_uarte.rs index 07f292c4..ab639aee 100644 --- a/embassy-nrf/src/buffered_uarte.rs +++ b/embassy-nrf/src/buffered_uarte.rs @@ -296,7 +296,7 @@ impl<'d, U: UarteInstance, T: TimerInstance> BufferedUarte<'d, U, T> { } // Received some bytes, wake task. - if r.inten.read().rxdrdy().bit_is_set() && r.events_rxdrdy.read().events_rxdrdy().bit_is_set() { + if r.inten.read().rxdrdy().bit_is_set() && r.events_rxdrdy.read().bits() != 0 { r.intenclr.write(|w| w.rxdrdy().clear()); r.events_rxdrdy.reset(); s.rx_waker.wake(); diff --git a/tests/nrf/.cargo/config.toml b/tests/nrf/.cargo/config.toml new file mode 100644 index 00000000..4eec189d --- /dev/null +++ b/tests/nrf/.cargo/config.toml @@ -0,0 +1,9 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +#runner = "teleprobe local run --chip nRF52840_xxAA --elf" +runner = "teleprobe client run --target nrf52840-dk --elf" + +[build] +target = "thumbv7em-none-eabi" + +[env] +DEFMT_LOG = "trace" diff --git a/tests/nrf/Cargo.toml b/tests/nrf/Cargo.toml new file mode 100644 index 00000000..2a4e8cf4 --- /dev/null +++ b/tests/nrf/Cargo.toml @@ -0,0 +1,20 @@ +[package] +edition = "2021" +name = "embassy-nrf-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } +embassy-sync = { version = "0.1.0", path = "../../embassy-sync", features = ["defmt", "nightly"] } +embassy-executor = { version = "0.1.0", path = "../../embassy-executor", features = ["defmt", "nightly", "integrated-timers"] } +embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "nightly", "defmt-timestamp-uptime"] } +embassy-nrf = { version = "0.1.0", path = "../../embassy-nrf", features = ["defmt", "nightly", "unstable-traits", "nrf52840", "time-driver-rtc1", "gpiote", "unstable-pac"] } +embedded-io = { version = "0.4.0", features = ["async"] } + +defmt = "0.3" +defmt-rtt = "0.4" + +cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } +cortex-m-rt = "0.7.0" +panic-probe = { version = "0.3", features = ["print-defmt"] } \ No newline at end of file diff --git a/tests/nrf/build.rs b/tests/nrf/build.rs new file mode 100644 index 00000000..6f487224 --- /dev/null +++ b/tests/nrf/build.rs @@ -0,0 +1,16 @@ +use std::error::Error; +use std::path::PathBuf; +use std::{env, fs}; + +fn main() -> Result<(), Box> { + let out = PathBuf::from(env::var("OUT_DIR").unwrap()); + fs::write(out.join("link_ram.x"), include_bytes!("link_ram.x")).unwrap(); + println!("cargo:rustc-link-search={}", out.display()); + println!("cargo:rerun-if-changed=link_ram.x"); + + println!("cargo:rustc-link-arg-bins=--nmagic"); + println!("cargo:rustc-link-arg-bins=-Tlink_ram.x"); + println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); + + Ok(()) +} diff --git a/tests/nrf/link_ram.x b/tests/nrf/link_ram.x new file mode 100644 index 00000000..26da86ba --- /dev/null +++ b/tests/nrf/link_ram.x @@ -0,0 +1,254 @@ +/* ##### EMBASSY NOTE + Originally from https://github.com/rust-embedded/cortex-m-rt/blob/master/link.x.in + Adjusted to put everything in RAM +*/ + +/* # Developer notes + +- Symbols that start with a double underscore (__) are considered "private" + +- Symbols that start with a single underscore (_) are considered "semi-public"; they can be + overridden in a user linker script, but should not be referred from user code (e.g. `extern "C" { + static mut __sbss }`). + +- `EXTERN` forces the linker to keep a symbol in the final binary. We use this to make sure a + symbol if not dropped if it appears in or near the front of the linker arguments and "it's not + needed" by any of the preceding objects (linker arguments) + +- `PROVIDE` is used to provide default values that can be overridden by a user linker script + +- On alignment: it's important for correctness that the VMA boundaries of both .bss and .data *and* + the LMA of .data are all 4-byte aligned. These alignments are assumed by the RAM initialization + routine. There's also a second benefit: 4-byte aligned boundaries means that you won't see + "Address (..) is out of bounds" in the disassembly produced by `objdump`. +*/ + +/* Provides information about the memory layout of the device */ +/* This will be provided by the user (see `memory.x`) or by a Board Support Crate */ +INCLUDE memory.x + +/* # Entry point = reset vector */ +EXTERN(__RESET_VECTOR); +EXTERN(Reset); +ENTRY(Reset); + +/* # Exception vectors */ +/* This is effectively weak aliasing at the linker level */ +/* The user can override any of these aliases by defining the corresponding symbol themselves (cf. + the `exception!` macro) */ +EXTERN(__EXCEPTIONS); /* depends on all the these PROVIDED symbols */ + +EXTERN(DefaultHandler); + +PROVIDE(NonMaskableInt = DefaultHandler); +EXTERN(HardFaultTrampoline); +PROVIDE(MemoryManagement = DefaultHandler); +PROVIDE(BusFault = DefaultHandler); +PROVIDE(UsageFault = DefaultHandler); +PROVIDE(SecureFault = DefaultHandler); +PROVIDE(SVCall = DefaultHandler); +PROVIDE(DebugMonitor = DefaultHandler); +PROVIDE(PendSV = DefaultHandler); +PROVIDE(SysTick = DefaultHandler); + +PROVIDE(DefaultHandler = DefaultHandler_); +PROVIDE(HardFault = HardFault_); + +/* # Interrupt vectors */ +EXTERN(__INTERRUPTS); /* `static` variable similar to `__EXCEPTIONS` */ + +/* # Pre-initialization function */ +/* If the user overrides this using the `pre_init!` macro or by creating a `__pre_init` function, + then the function this points to will be called before the RAM is initialized. */ +PROVIDE(__pre_init = DefaultPreInit); + +/* # Sections */ +SECTIONS +{ + PROVIDE(_stack_start = ORIGIN(RAM) + LENGTH(RAM)); + + /* ## Sections in RAM */ + /* ### Vector table */ + .vector_table ORIGIN(RAM) : + { + /* Initial Stack Pointer (SP) value */ + LONG(_stack_start); + + /* Reset vector */ + KEEP(*(.vector_table.reset_vector)); /* this is the `__RESET_VECTOR` symbol */ + __reset_vector = .; + + /* Exceptions */ + KEEP(*(.vector_table.exceptions)); /* this is the `__EXCEPTIONS` symbol */ + __eexceptions = .; + + /* Device specific interrupts */ + KEEP(*(.vector_table.interrupts)); /* this is the `__INTERRUPTS` symbol */ + } > RAM + + PROVIDE(_stext = ADDR(.vector_table) + SIZEOF(.vector_table)); + + /* ### .text */ + .text _stext : + { + __stext = .; + *(.Reset); + + *(.text .text.*); + + /* The HardFaultTrampoline uses the `b` instruction to enter `HardFault`, + so must be placed close to it. */ + *(.HardFaultTrampoline); + *(.HardFault.*); + + . = ALIGN(4); /* Pad .text to the alignment to workaround overlapping load section bug in old lld */ + __etext = .; + } > RAM + + /* ### .rodata */ + .rodata : ALIGN(4) + { + . = ALIGN(4); + __srodata = .; + *(.rodata .rodata.*); + + /* 4-byte align the end (VMA) of this section. + This is required by LLD to ensure the LMA of the following .data + section will have the correct alignment. */ + . = ALIGN(4); + __erodata = .; + } > RAM + + /* ## Sections in RAM */ + /* ### .data */ + .data : ALIGN(4) + { + . = ALIGN(4); + __sdata = .; + __edata = .; + *(.data .data.*); + . = ALIGN(4); /* 4-byte align the end (VMA) of this section */ + } > RAM + /* Allow sections from user `memory.x` injected using `INSERT AFTER .data` to + * use the .data loading mechanism by pushing __edata. Note: do not change + * output region or load region in those user sections! */ + . = ALIGN(4); + + /* LMA of .data */ + __sidata = LOADADDR(.data); + + /* ### .gnu.sgstubs + This section contains the TrustZone-M veneers put there by the Arm GNU linker. */ + /* Security Attribution Unit blocks must be 32 bytes aligned. */ + /* Note that this pads the RAM usage to 32 byte alignment. */ + .gnu.sgstubs : ALIGN(32) + { + . = ALIGN(32); + __veneer_base = .; + *(.gnu.sgstubs*) + . = ALIGN(32); + __veneer_limit = .; + } > RAM + + /* ### .bss */ + .bss (NOLOAD) : ALIGN(4) + { + . = ALIGN(4); + __sbss = .; + *(.bss .bss.*); + *(COMMON); /* Uninitialized C statics */ + . = ALIGN(4); /* 4-byte align the end (VMA) of this section */ + } > RAM + /* Allow sections from user `memory.x` injected using `INSERT AFTER .bss` to + * use the .bss zeroing mechanism by pushing __ebss. Note: do not change + * output region or load region in those user sections! */ + . = ALIGN(4); + __ebss = .; + + /* ### .uninit */ + .uninit (NOLOAD) : ALIGN(4) + { + . = ALIGN(4); + __suninit = .; + *(.uninit .uninit.*); + . = ALIGN(4); + __euninit = .; + } > RAM + + /* Place the heap right after `.uninit` in RAM */ + PROVIDE(__sheap = __euninit); + + /* ## .got */ + /* Dynamic relocations are unsupported. This section is only used to detect relocatable code in + the input files and raise an error if relocatable code is found */ + .got (NOLOAD) : + { + KEEP(*(.got .got.*)); + } + + /* ## Discarded sections */ + /DISCARD/ : + { + /* Unused exception related info that only wastes space */ + *(.ARM.exidx); + *(.ARM.exidx.*); + *(.ARM.extab.*); + } +} + +/* Do not exceed this mark in the error messages below | */ +/* # Alignment checks */ +ASSERT(ORIGIN(RAM) % 4 == 0, " +ERROR(cortex-m-rt): the start of the RAM region must be 4-byte aligned"); + +ASSERT(__sdata % 4 == 0 && __edata % 4 == 0, " +BUG(cortex-m-rt): .data is not 4-byte aligned"); + +ASSERT(__sidata % 4 == 0, " +BUG(cortex-m-rt): the LMA of .data is not 4-byte aligned"); + +ASSERT(__sbss % 4 == 0 && __ebss % 4 == 0, " +BUG(cortex-m-rt): .bss is not 4-byte aligned"); + +ASSERT(__sheap % 4 == 0, " +BUG(cortex-m-rt): start of .heap is not 4-byte aligned"); + +/* # Position checks */ + +/* ## .vector_table */ +ASSERT(__reset_vector == ADDR(.vector_table) + 0x8, " +BUG(cortex-m-rt): the reset vector is missing"); + +ASSERT(__eexceptions == ADDR(.vector_table) + 0x40, " +BUG(cortex-m-rt): the exception vectors are missing"); + +ASSERT(SIZEOF(.vector_table) > 0x40, " +ERROR(cortex-m-rt): The interrupt vectors are missing. +Possible solutions, from most likely to less likely: +- Link to a svd2rust generated device crate +- Check that you actually use the device/hal/bsp crate in your code +- Disable the 'device' feature of cortex-m-rt to build a generic application (a dependency +may be enabling it) +- Supply the interrupt handlers yourself. Check the documentation for details."); + +/* ## .text */ +ASSERT(ADDR(.vector_table) + SIZEOF(.vector_table) <= _stext, " +ERROR(cortex-m-rt): The .text section can't be placed inside the .vector_table section +Set _stext to an address greater than the end of .vector_table (See output of `nm`)"); + +ASSERT(_stext + SIZEOF(.text) < ORIGIN(RAM) + LENGTH(RAM), " +ERROR(cortex-m-rt): The .text section must be placed inside the RAM memory. +Set _stext to an address smaller than 'ORIGIN(RAM) + LENGTH(RAM)'"); + +/* # Other checks */ +ASSERT(SIZEOF(.got) == 0, " +ERROR(cortex-m-rt): .got section detected in the input object files +Dynamic relocations are not supported. If you are linking to C code compiled using +the 'cc' crate then modify your build script to compile the C code _without_ +the -fPIC flag. See the documentation of the `cc::Build.pic` method for details."); +/* Do not exceed this mark in the error messages above | */ + + +/* Provides weak aliases (cf. PROVIDED) for device specific interrupt handlers */ +/* This will usually be provided by a device crate generated using svd2rust (see `device.x`) */ +INCLUDE device.x \ No newline at end of file diff --git a/tests/nrf/memory.x b/tests/nrf/memory.x new file mode 100644 index 00000000..58900a7b --- /dev/null +++ b/tests/nrf/memory.x @@ -0,0 +1,5 @@ +MEMORY +{ + FLASH : ORIGIN = 0x00000000, LENGTH = 1024K + RAM : ORIGIN = 0x20000000, LENGTH = 256K +} diff --git a/tests/nrf/src/bin/buffered_uart.rs b/tests/nrf/src/bin/buffered_uart.rs new file mode 100644 index 00000000..0550b0bb --- /dev/null +++ b/tests/nrf/src/bin/buffered_uart.rs @@ -0,0 +1,74 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::{assert_eq, *}; +use embassy_executor::Spawner; +use embassy_futures::join::join; +use embassy_nrf::buffered_uarte::BufferedUarte; +use embassy_nrf::{interrupt, uarte}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_nrf::init(Default::default()); + let mut config = uarte::Config::default(); + config.parity = uarte::Parity::EXCLUDED; + config.baudrate = uarte::Baudrate::BAUD1M; + + let mut tx_buffer = [0u8; 1024]; + let mut rx_buffer = [0u8; 1024]; + + let mut u = BufferedUarte::new( + p.UARTE0, + p.TIMER0, + p.PPI_CH0, + p.PPI_CH1, + p.PPI_GROUP0, + interrupt::take!(UARTE0_UART0), + p.P1_03, + p.P1_02, + config.clone(), + &mut rx_buffer, + &mut tx_buffer, + ); + + info!("uarte initialized!"); + + let (mut rx, mut tx) = u.split(); + + const COUNT: usize = 40_000; + + let tx_fut = async { + let mut tx_buf = [0; 215]; + let mut i = 0; + while i < COUNT { + let n = tx_buf.len().min(COUNT - i); + let tx_buf = &mut tx_buf[..n]; + for (j, b) in tx_buf.iter_mut().enumerate() { + *b = (i + j) as u8; + } + let n = unwrap!(tx.write(tx_buf).await); + i += n; + } + }; + let rx_fut = async { + let mut i = 0; + while i < COUNT { + let buf = unwrap!(rx.fill_buf().await); + + for &b in buf { + assert_eq!(b, i as u8); + i = i + 1; + } + + let n = buf.len(); + rx.consume(n); + } + }; + + join(rx_fut, tx_fut).await; + + info!("Test OK"); + cortex_m::asm::bkpt(); +} diff --git a/tests/nrf/src/bin/buffered_uart_spam.rs b/tests/nrf/src/bin/buffered_uart_spam.rs new file mode 100644 index 00000000..57aaeca4 --- /dev/null +++ b/tests/nrf/src/bin/buffered_uart_spam.rs @@ -0,0 +1,86 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use core::mem; +use core::ptr::NonNull; + +use defmt::{assert_eq, *}; +use embassy_executor::Spawner; +use embassy_nrf::buffered_uarte::BufferedUarte; +use embassy_nrf::gpio::{Level, Output, OutputDrive}; +use embassy_nrf::ppi::{Event, Ppi, Task}; +use embassy_nrf::uarte::Uarte; +use embassy_nrf::{interrupt, pac, uarte}; +use embassy_time::{Duration, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut p = embassy_nrf::init(Default::default()); + let mut config = uarte::Config::default(); + config.parity = uarte::Parity::EXCLUDED; + config.baudrate = uarte::Baudrate::BAUD1M; + + let mut tx_buffer = [0u8; 1024]; + let mut rx_buffer = [0u8; 1024]; + + mem::forget(Output::new(&mut p.P1_02, Level::High, OutputDrive::Standard)); + + let mut u = BufferedUarte::new( + p.UARTE0, + p.TIMER0, + p.PPI_CH0, + p.PPI_CH1, + p.PPI_GROUP0, + interrupt::take!(UARTE0_UART0), + p.P1_03, + p.P1_04, + config.clone(), + &mut rx_buffer, + &mut tx_buffer, + ); + + info!("uarte initialized!"); + + // uarte needs some quiet time to start rxing properly. + Timer::after(Duration::from_millis(10)).await; + + // Tx spam in a loop. + const NSPAM: usize = 17; + static mut TX_BUF: [u8; NSPAM] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]; + let _spam = Uarte::new(p.UARTE1, interrupt::take!(UARTE1), p.P1_01, p.P1_02, config.clone()); + let spam_peri: pac::UARTE1 = unsafe { mem::transmute(()) }; + let event = unsafe { Event::new_unchecked(NonNull::new_unchecked(&spam_peri.events_endtx as *const _ as _)) }; + let task = unsafe { Task::new_unchecked(NonNull::new_unchecked(&spam_peri.tasks_starttx as *const _ as _)) }; + let mut spam_ppi = Ppi::new_one_to_one(p.PPI_CH2, event, task); + spam_ppi.enable(); + let p = unsafe { TX_BUF.as_mut_ptr() }; + spam_peri.txd.ptr.write(|w| unsafe { w.ptr().bits(p as u32) }); + spam_peri.txd.maxcnt.write(|w| unsafe { w.maxcnt().bits(NSPAM as _) }); + spam_peri.tasks_starttx.write(|w| unsafe { w.bits(1) }); + + let mut i = 0; + let mut total = 0; + while total < 256 * 1024 { + let buf = unwrap!(u.fill_buf().await); + //info!("rx {}", buf); + + for &b in buf { + assert_eq!(b, unsafe { TX_BUF[i] }); + + i = i + 1; + if i == NSPAM { + i = 0; + } + } + + // Read bytes have to be explicitly consumed, otherwise fill_buf() will return them again + let n = buf.len(); + u.consume(n); + total += n; + } + + info!("Test OK"); + cortex_m::asm::bkpt(); +}