Merge pull request #1208 from embassy-rs/nrf-uarte-lockfree

nrf/buffered_uarte: make it work without rts/cts, and lock-free.
This commit is contained in:
Dario Nieuwenhuis 2023-03-05 02:56:15 +01:00 committed by GitHub
commit d91efe3e62
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 1107 additions and 376 deletions

1
ci.sh
View file

@ -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 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/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/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 $BUILD_EXTRA

View file

@ -1,10 +1,5 @@
//! Async buffered UART driver. //! 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 <https://github.com/embassy-rs/embassy/issues/536>.
//!
//! Note that discarding a future from a read or write operation may lead to losing //! 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 //! 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 //! on the "other" future, you should capture the incomplete future and continue to use
@ -13,82 +8,128 @@
//! //!
//! Please also see [crate::uarte] to understand when [BufferedUarte] should be used. //! Please also see [crate::uarte] to understand when [BufferedUarte] should be used.
use core::cell::RefCell;
use core::cmp::min; use core::cmp::min;
use core::future::poll_fn; 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 core::task::Poll;
use embassy_cortex_m::peripheral::{PeripheralMutex, PeripheralState, StateStorage}; use embassy_cortex_m::interrupt::Interrupt;
use embassy_hal_common::ring_buffer::RingBuffer; use embassy_hal_common::atomic_ring_buffer::RingBuffer;
use embassy_hal_common::{into_ref, PeripheralRef}; 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 // 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}; 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::interrupt::InterruptExt;
use crate::ppi::{AnyConfigurableChannel, ConfigurableChannel, Event, Ppi, Task}; use crate::ppi::{
use crate::timer::{Frequency, Instance as TimerInstance, Timer}; 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::uarte::{apply_workaround_for_enable_anomaly, Config, Instance as UarteInstance};
use crate::{pac, Peripheral}; use crate::{pac, Peripheral};
#[derive(Copy, Clone, Debug, PartialEq)] mod sealed {
enum RxState { use super::*;
Idle,
Receiving,
}
#[derive(Copy, Clone, Debug, PartialEq)] pub struct State {
enum TxState { pub tx_waker: AtomicWaker,
Idle, pub tx_buf: RingBuffer,
Transmitting(usize), pub tx_count: AtomicUsize,
}
/// A type for storing the state of the UARTE peripheral that can be stored in a static. pub rx_waker: AtomicWaker,
pub struct State<'d, U: UarteInstance, T: TimerInstance>(StateStorage<StateInner<'d, U, T>>); pub rx_buf: RingBuffer,
impl<'d, U: UarteInstance, T: TimerInstance> State<'d, U, T> { pub rx_bufs: AtomicU8,
/// Create an instance for storing UARTE peripheral state. pub rx_ppi_ch: AtomicU8,
pub fn new() -> Self {
Self(StateStorage::new())
} }
} }
struct StateInner<'d, U: UarteInstance, T: TimerInstance> { /// UART error.
_peri: PeripheralRef<'d, U>, #[derive(Debug, Clone, Copy, PartialEq, Eq)]
timer: Timer<'d, T>, #[cfg_attr(feature = "defmt", derive(defmt::Format))]
_ppi_ch1: Ppi<'d, AnyConfigurableChannel, 1, 2>, #[non_exhaustive]
_ppi_ch2: Ppi<'d, AnyConfigurableChannel, 1, 1>, pub enum Error {
// No errors for now
}
rx: RingBuffer<'d>, pub(crate) use sealed::State;
rx_state: RxState,
rx_waker: WakerRegistration,
tx: RingBuffer<'d>, impl State {
tx_state: TxState, pub(crate) const fn new() -> Self {
tx_waker: WakerRegistration, Self {
tx_waker: AtomicWaker::new(),
tx_buf: RingBuffer::new(),
tx_count: AtomicUsize::new(0),
rx_waker: AtomicWaker::new(),
rx_buf: RingBuffer::new(),
rx_bufs: AtomicU8::new(0),
rx_ppi_ch: AtomicU8::new(0),
}
}
} }
/// Buffered UARTE driver. /// Buffered UARTE driver.
pub struct BufferedUarte<'d, U: UarteInstance, T: TimerInstance> { pub struct BufferedUarte<'d, U: UarteInstance, T: TimerInstance> {
inner: RefCell<PeripheralMutex<'d, StateInner<'d, U, T>>>, _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> Unpin for BufferedUarte<'d, U, T> {}
impl<'d, U: UarteInstance, T: TimerInstance> 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 /// Panics if `rx_buffer.len()` is odd.
/// can return early if there is no data to receive.
pub fn new( pub fn new(
state: &'d mut State<'d, U, T>, uarte: impl Peripheral<P = U> + 'd,
peri: impl Peripheral<P = U> + 'd,
timer: impl Peripheral<P = T> + 'd, timer: impl Peripheral<P = T> + 'd,
ppi_ch1: impl Peripheral<P = impl ConfigurableChannel + 'd> + 'd, ppi_ch1: impl Peripheral<P = impl ConfigurableChannel> + 'd,
ppi_ch2: impl Peripheral<P = impl ConfigurableChannel + 'd> + 'd, ppi_ch2: impl Peripheral<P = impl ConfigurableChannel> + 'd,
ppi_group: impl Peripheral<P = impl Group> + 'd,
irq: impl Peripheral<P = U::Interrupt> + 'd,
rxd: impl Peripheral<P = impl GpioPin> + 'd,
txd: impl Peripheral<P = impl GpioPin> + '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<P = U> + 'd,
timer: impl Peripheral<P = T> + 'd,
ppi_ch1: impl Peripheral<P = impl ConfigurableChannel> + 'd,
ppi_ch2: impl Peripheral<P = impl ConfigurableChannel> + 'd,
ppi_group: impl Peripheral<P = impl Group> + 'd,
irq: impl Peripheral<P = U::Interrupt> + 'd, irq: impl Peripheral<P = U::Interrupt> + 'd,
rxd: impl Peripheral<P = impl GpioPin> + 'd, rxd: impl Peripheral<P = impl GpioPin> + 'd,
txd: impl Peripheral<P = impl GpioPin> + 'd, txd: impl Peripheral<P = impl GpioPin> + 'd,
@ -98,12 +139,45 @@ impl<'d, U: UarteInstance, T: TimerInstance> BufferedUarte<'d, U, T> {
rx_buffer: &'d mut [u8], rx_buffer: &'d mut [u8],
tx_buffer: &'d mut [u8], tx_buffer: &'d mut [u8],
) -> Self { ) -> 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<P = U> + 'd,
timer: impl Peripheral<P = T> + 'd,
ppi_ch1: PeripheralRef<'d, AnyConfigurableChannel>,
ppi_ch2: PeripheralRef<'d, AnyConfigurableChannel>,
ppi_group: PeripheralRef<'d, AnyGroup>,
irq: impl Peripheral<P = U::Interrupt> + 'd,
rxd: PeripheralRef<'d, AnyPin>,
txd: PeripheralRef<'d, AnyPin>,
cts: Option<PeripheralRef<'d, AnyPin>>,
rts: Option<PeripheralRef<'d, AnyPin>>,
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 r = U::regs();
let mut timer = Timer::new(timer);
rxd.conf().write(|w| w.input().connect().drive().h0h1()); rxd.conf().write(|w| w.input().connect().drive().h0h1());
r.psel.rxd.write(|w| unsafe { w.bits(rxd.psel_bits()) }); r.psel.rxd.write(|w| unsafe { w.bits(rxd.psel_bits()) });
@ -111,92 +185,200 @@ impl<'d, U: UarteInstance, T: TimerInstance> BufferedUarte<'d, U, T> {
txd.conf().write(|w| w.dir().output().drive().h0h1()); txd.conf().write(|w| w.dir().output().drive().h0h1());
r.psel.txd.write(|w| unsafe { w.bits(txd.psel_bits()) }); 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()) }); r.psel.cts.write(|w| unsafe { w.bits(cts.psel_bits()) });
rts.set_high(); if let Some(pin) = &rts {
rts.conf().write(|w| w.dir().output().drive().h0h1()); pin.set_high();
pin.conf().write(|w| w.dir().output().drive().h0h1());
}
r.psel.rts.write(|w| unsafe { w.bits(rts.psel_bits()) }); r.psel.rts.write(|w| unsafe { w.bits(rts.psel_bits()) });
r.baudrate.write(|w| w.baudrate().variant(config.baudrate)); // Initialize state
r.config.write(|w| w.parity().variant(config.parity)); 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 // Configure
r.config.write(|w| { r.config.write(|w| {
w.hwfc().bit(true); w.hwfc().bit(false);
w.parity().variant(config.parity); w.parity().variant(config.parity);
w w
}); });
r.baudrate.write(|w| w.baudrate().variant(config.baudrate)); r.baudrate.write(|w| w.baudrate().variant(config.baudrate));
// Enable interrupts // clear errors
r.intenset.write(|w| w.endrx().set().endtx().set()); 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. r.events_rxstarted.reset();
irq.disable(); r.events_txstarted.reset();
irq.pend(); 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 // Enable UARTE instance
apply_workaround_for_enable_anomaly(&r); apply_workaround_for_enable_anomaly(&r);
r.enable.write(|w| w.enable().enabled()); r.enable.write(|w| w.enable().enabled());
// BAUDRATE register values are `baudrate * 2^32 / 16000000` // Configure byte counter.
// source: https://devzone.nordicsemi.com/f/nordic-q-a/391/uart-baudrate-register-values let mut timer = Timer::new_counter(timer);
// timer.cc(1).write(rx_buffer.len() as u32 * 2);
// We want to stop RX if line is idle for 2 bytes worth of time timer.cc(1).short_compare_clear();
// That is 20 bits (each byte is 1 start bit + 8 data bits + 1 stop bit) timer.clear();
// This gives us the amount of 16M ticks for 20 bits. timer.start();
let timeout = 0x8000_0000 / (config.baudrate as u32 / 40);
timer.set_frequency(Frequency::F16MHz); let mut ppi_ch1 = Ppi::new_one_to_one(ppi_ch1, Event::from_reg(&r.events_rxdrdy), timer.task_count());
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(),
);
ppi_ch1.enable(); ppi_ch1.enable();
let mut ppi_ch2 = Ppi::new_one_to_one( s.rx_ppi_ch.store(ppi_ch2.number() as u8, Ordering::Relaxed);
ppi_ch2.map_into(), let mut ppi_group = PpiGroup::new(ppi_group);
timer.cc(0).event_compare(), let mut ppi_ch2 = Ppi::new_one_to_two(
Task::from_reg(&r.tasks_stoprx), 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 { Self {
inner: RefCell::new(PeripheralMutex::new(irq, &mut state.0, move || StateInner {
_peri: peri, _peri: peri,
timer, timer,
_ppi_ch1: ppi_ch1, _ppi_ch1: ppi_ch1,
_ppi_ch2: ppi_ch2, _ppi_ch2: ppi_ch2,
_ppi_group: ppi_group,
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(),
})),
} }
} }
fn pend_irq() {
unsafe { <U::Interrupt as Interrupt>::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().bits() != 0 {
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. /// Adjust the baud rate to the provided value.
pub fn set_baudrate(&mut self, baudrate: Baudrate) { pub fn set_baudrate(&mut self, baudrate: Baudrate) {
self.inner.borrow_mut().with(|state| {
let r = U::regs(); 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)); r.baudrate.write(|w| w.baudrate().variant(baudrate));
});
} }
/// Split the UART in reader and writer parts. /// Split the UART in reader and writer parts.
@ -206,120 +388,142 @@ impl<'d, U: UarteInstance, T: TimerInstance> BufferedUarte<'d, U, T> {
(BufferedUarteRx { inner: self }, BufferedUarteTx { inner: self }) (BufferedUarteRx { inner: self }, BufferedUarteTx { inner: self })
} }
async fn inner_read<'a>(&'a self, buf: &'a mut [u8]) -> Result<usize, core::convert::Infallible> { async fn inner_read(&self, buf: &mut [u8]) -> Result<usize, Error> {
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<usize, Error> {
poll_fn(move |cx| { poll_fn(move |cx| {
let mut do_pend = false; //trace!("poll_write: {:?}", buf.len());
let mut inner = self.inner.borrow_mut(); let s = U::buffered_state();
let res = inner.with(|state| { let mut tx = unsafe { s.tx_buf.writer() };
compiler_fence(Ordering::SeqCst);
trace!("poll_read");
// We have data ready in buffer? Return it. let tx_buf = tx.push_slice();
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_write<'a>(&'a self, buf: &'a [u8]) -> Result<usize, core::convert::Infallible> {
poll_fn(move |cx| {
let mut inner = self.inner.borrow_mut();
let res = inner.with(|state| {
trace!("poll_write: {:?}", buf.len());
let tx_buf = state.tx.push_buf();
if tx_buf.is_empty() { if tx_buf.is_empty() {
trace!("poll_write: pending"); //trace!("poll_write: pending");
state.tx_waker.register(cx.waker()); s.tx_waker.register(cx.waker());
return Poll::Pending; return Poll::Pending;
} }
let n = min(tx_buf.len(), buf.len()); let n = min(tx_buf.len(), buf.len());
tx_buf[..n].copy_from_slice(&buf[..n]); tx_buf[..n].copy_from_slice(&buf[..n]);
state.tx.push(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)) Poll::Ready(Ok(n))
});
inner.pend();
res
}) })
.await .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| { 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() {
if !state.tx.is_empty() { //trace!("poll_flush: pending");
trace!("poll_flush: pending"); s.tx_waker.register(cx.waker());
state.tx_waker.register(cx.waker());
return Poll::Pending; return Poll::Pending;
} }
Poll::Ready(Ok(())) Poll::Ready(Ok(()))
}) })
})
.await .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| { poll_fn(move |cx| {
self.inner.borrow_mut().with(|state| {
compiler_fence(Ordering::SeqCst); compiler_fence(Ordering::SeqCst);
trace!("fill_buf"); //trace!("poll_read");
// We have data ready in buffer? Return it. let r = U::regs();
let buf = state.rx.pop_buf(); let s = U::buffered_state();
if !buf.is_empty() {
trace!(" got {:?} {:?}", buf.as_ptr() as u32, buf.len()); // Read the RXDRDY counter.
let buf: &[u8] = buf; T::regs().tasks_capture[0].write(|w| unsafe { w.bits(1) });
// Safety: buffer lives as long as uart let mut end = T::regs().cc[0].read().bits() as usize;
let buf: &[u8] = unsafe { core::mem::transmute(buf) }; //trace!(" rxdrdy count = {:?}", end);
return Poll::Ready(Ok(buf));
// 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
} }
trace!(" empty"); // This logic mirrors `atomic_ring_buffer::Reader::pop_buf()`
state.rx_waker.register(cx.waker()); let mut start = s.rx_buf.start.load(Ordering::Relaxed);
Poll::<Result<&[u8], core::convert::Infallible>>::Pending 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 .await
} }
fn inner_consume(&self, amt: usize) { fn inner_consume(&self, amt: usize) {
let mut inner = self.inner.borrow_mut(); if amt == 0 {
let signal = inner.with(|state| { return;
let full = state.rx.is_full();
state.rx.pop(amt);
full
});
if signal {
inner.pend();
} }
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());
}
/// 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<usize, Error> {
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<usize, Error> {
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
} }
} }
@ -328,21 +532,60 @@ pub struct BufferedUarteTx<'u, 'd, U: UarteInstance, T: TimerInstance> {
inner: &'u BufferedUarte<'d, U, T>, 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<usize, Error> {
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. /// Writer part of the buffered UARTE driver.
pub struct BufferedUarteRx<'u, 'd, U: UarteInstance, T: TimerInstance> { pub struct BufferedUarteRx<'u, 'd, U: UarteInstance, T: TimerInstance> {
inner: &'u BufferedUarte<'d, U, T>, inner: &'u BufferedUarte<'d, U, T>,
} }
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<usize, Error> {
self.inner.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.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.inner_consume(amt)
}
}
#[cfg(feature = "nightly")]
mod _embedded_io {
use super::*;
impl embedded_io::Error for Error {
fn kind(&self) -> embedded_io::ErrorKind {
match *self {}
}
}
impl<'d, U: UarteInstance, T: TimerInstance> embedded_io::Io for BufferedUarte<'d, U, T> { impl<'d, U: UarteInstance, T: TimerInstance> embedded_io::Io for BufferedUarte<'d, U, T> {
type Error = core::convert::Infallible; type Error = Error;
} }
impl<'u, 'd, U: UarteInstance, T: TimerInstance> embedded_io::Io for BufferedUarteRx<'u, 'd, U, T> { impl<'u, 'd, U: UarteInstance, T: TimerInstance> embedded_io::Io for BufferedUarteRx<'u, 'd, U, T> {
type Error = core::convert::Infallible; type Error = Error;
} }
impl<'u, 'd, U: UarteInstance, T: TimerInstance> embedded_io::Io for BufferedUarteTx<'u, 'd, U, T> { impl<'u, 'd, U: UarteInstance, T: TimerInstance> embedded_io::Io for BufferedUarteTx<'u, 'd, U, T> {
type Error = core::convert::Infallible; type Error = Error;
} }
impl<'d, U: UarteInstance, T: TimerInstance> embedded_io::asynch::Read for BufferedUarte<'d, U, T> { impl<'d, U: UarteInstance, T: TimerInstance> embedded_io::asynch::Read for BufferedUarte<'d, U, T> {
@ -396,8 +639,9 @@ impl<'u, 'd: 'u, U: UarteInstance, T: TimerInstance> embedded_io::asynch::Write
self.inner.inner_flush().await self.inner.inner_flush().await
} }
} }
}
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) { fn drop(&mut self) {
let r = U::regs(); let r = U::regs();
@ -418,108 +662,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.txd.read().bits());
gpio::deconfigure_pin(r.psel.rts.read().bits()); gpio::deconfigure_pin(r.psel.rts.read().bits());
gpio::deconfigure_pin(r.psel.cts.read().bits()); gpio::deconfigure_pin(r.psel.cts.read().bits());
}
}
impl<'a, U: UarteInstance, T: TimerInstance> PeripheralState for StateInner<'a, U, T> { let s = U::buffered_state();
type Interrupt = U::Interrupt; unsafe {
fn on_interrupt(&mut self) { s.rx_buf.deinit();
trace!("irq: start"); s.tx_buf.deinit();
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");
}
}

View file

@ -37,7 +37,6 @@ pub(crate) mod util;
#[cfg(feature = "_time-driver")] #[cfg(feature = "_time-driver")]
mod time_driver; mod time_driver;
#[cfg(feature = "nightly")]
pub mod buffered_uarte; pub mod buffered_uarte;
pub mod gpio; pub mod gpio;
#[cfg(feature = "gpiote")] #[cfg(feature = "gpiote")]

View file

@ -6,7 +6,7 @@ use crate::{pac, Peripheral};
const DPPI_ENABLE_BIT: u32 = 0x8000_0000; const DPPI_ENABLE_BIT: u32 = 0x8000_0000;
const DPPI_CHANNEL_MASK: u32 = 0x0000_00FF; 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() } unsafe { &*pac::DPPIC::ptr() }
} }

View file

@ -17,16 +17,16 @@
use core::ptr::NonNull; use core::ptr::NonNull;
use embassy_hal_common::{impl_peripheral, PeripheralRef}; use embassy_hal_common::{impl_peripheral, into_ref, PeripheralRef};
use crate::{peripherals, Peripheral}; use crate::{peripherals, Peripheral};
#[cfg(feature = "_dppi")] #[cfg_attr(feature = "_dppi", path = "dppi.rs")]
mod dppi; #[cfg_attr(feature = "_ppi", path = "ppi.rs")]
#[cfg(feature = "_ppi")] mod _version;
mod ppi; 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> { pub struct Ppi<'d, C: Channel, const EVENT_COUNT: usize, const TASK_COUNT: usize> {
ch: PeripheralRef<'d, C>, ch: PeripheralRef<'d, C>,
#[cfg(feature = "_dppi")] #[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], 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<P = G> + '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<C: Channel, const EVENT_COUNT: usize, const TASK_COUNT: usize>(
&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<C: Channel, const EVENT_COUNT: usize, const TASK_COUNT: usize>(
&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(&regs().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(&regs().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")] #[cfg(feature = "_dppi")]
const REGISTER_DPPI_CONFIG_OFFSET: usize = 0x80 / core::mem::size_of::<u32>(); const REGISTER_DPPI_CONFIG_OFFSET: usize = 0x80 / core::mem::size_of::<u32>();
@ -112,7 +194,7 @@ pub(crate) mod sealed {
} }
/// Interface for PPI channels. /// Interface for PPI channels.
pub trait Channel: sealed::Channel + Peripheral<P = Self> + Sized { pub trait Channel: sealed::Channel + Peripheral<P = Self> + Sized + 'static {
/// Returns the number of the channel /// Returns the number of the channel
fn number(&self) -> usize; fn number(&self) -> usize;
} }
@ -130,7 +212,7 @@ pub trait StaticChannel: Channel + Into<AnyStaticChannel> {
} }
/// Interface for a group of PPI channels. /// Interface for a group of PPI channels.
pub trait Group: sealed::Group + Sized { pub trait Group: sealed::Group + Peripheral<P = Self> + Into<AnyGroup> + Sized + 'static {
/// Returns the number of the group. /// Returns the number of the group.
fn number(&self) -> usize; fn number(&self) -> usize;
/// Convert into a type erased group. /// Convert into a type erased group.
@ -248,6 +330,12 @@ macro_rules! impl_group {
$number $number
} }
} }
impl From<peripherals::$type> for crate::ppi::AnyGroup {
fn from(val: peripherals::$type) -> Self {
crate::ppi::Group::degrade(val)
}
}
}; };
} }

View file

@ -14,7 +14,7 @@ impl Event {
} }
} }
fn regs() -> &'static pac::ppi::RegisterBlock { pub(crate) fn regs() -> &'static pac::ppi::RegisterBlock {
unsafe { &*pac::PPI::ptr() } unsafe { &*pac::PPI::ptr() }
} }

View file

@ -132,7 +132,21 @@ impl<'d, T: Instance> Timer<'d, T, Awaitable> {
irq.unpend(); irq.unpend();
irq.enable(); 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<P = T> + 'd,
irq: impl Peripheral<P = T::Interrupt> + '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 /// This can be useful for triggering tasks via PPI
/// `Uarte` uses this internally. /// `Uarte` uses this internally.
pub fn new(timer: impl Peripheral<P = T> + 'd) -> Self { pub fn new(timer: impl Peripheral<P = T> + '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<P = T> + '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. /// Create a `Timer` without an interrupt, meaning `Cc::wait` won't work.
/// ///
/// This is used by the public constructors. /// This is used by the public constructors.
fn new_inner(timer: impl Peripheral<P = T> + 'd) -> Self { fn new_inner(timer: impl Peripheral<P = T> + 'd, is_counter: bool) -> Self {
into_ref!(timer); into_ref!(timer);
let regs = T::regs(); 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. // since changing BITMODE while running can cause 'unpredictable behaviour' according to the specification.
this.stop(); this.stop();
// Set the instance to timer mode. if is_counter {
regs.mode.write(|w| w.mode().counter());
} else {
regs.mode.write(|w| w.mode().timer()); regs.mode.write(|w| w.mode().timer());
}
// Make the counter's max value as high as possible. // Make the counter's max value as high as possible.
// TODO: is there a reason someone would want to set this lower? // 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) 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. /// Change the timer's frequency.
/// ///
/// This will stop the timer if it isn't already stopped, /// This will stop the timer if it isn't already stopped,

View file

@ -883,6 +883,7 @@ pub(crate) mod sealed {
pub trait Instance { pub trait Instance {
fn regs() -> &'static pac::uarte0::RegisterBlock; fn regs() -> &'static pac::uarte0::RegisterBlock;
fn state() -> &'static State; 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(); static STATE: crate::uarte::sealed::State = crate::uarte::sealed::State::new();
&STATE &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 { impl crate::uarte::Instance for peripherals::$type {
type Interrupt = crate::interrupt::$irq; type Interrupt = crate::interrupt::$irq;

View file

@ -4,10 +4,9 @@
use defmt::*; use defmt::*;
use embassy_executor::Spawner; use embassy_executor::Spawner;
use embassy_nrf::buffered_uarte::{BufferedUarte, State}; use embassy_nrf::buffered_uarte::BufferedUarte;
use embassy_nrf::{interrupt, uarte}; use embassy_nrf::{interrupt, uarte};
use embedded_io::asynch::{BufRead, Write}; use embedded_io::asynch::Write;
use futures::pin_mut;
use {defmt_rtt as _, panic_probe as _}; use {defmt_rtt as _, panic_probe as _};
#[embassy_executor::main] #[embassy_executor::main]
@ -21,24 +20,19 @@ async fn main(_spawner: Spawner) {
let mut rx_buffer = [0u8; 4096]; let mut rx_buffer = [0u8; 4096];
let irq = interrupt::take!(UARTE0_UART0); let irq = interrupt::take!(UARTE0_UART0);
let mut state = State::new(); let mut u = BufferedUarte::new(
// Please note - important to have hardware flow control (https://github.com/embassy-rs/embassy/issues/536)
let u = BufferedUarte::new(
&mut state,
p.UARTE0, p.UARTE0,
p.TIMER0, p.TIMER0,
p.PPI_CH0, p.PPI_CH0,
p.PPI_CH1, p.PPI_CH1,
p.PPI_GROUP0,
irq, irq,
p.P0_08, p.P0_08,
p.P0_06, p.P0_06,
p.P0_07,
p.P0_05,
config, config,
&mut rx_buffer, &mut rx_buffer,
&mut tx_buffer, &mut tx_buffer,
); );
pin_mut!(u);
info!("uarte initialized!"); info!("uarte initialized!");

View file

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

20
tests/nrf/Cargo.toml Normal file
View file

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

16
tests/nrf/build.rs Normal file
View file

@ -0,0 +1,16 @@
use std::error::Error;
use std::path::PathBuf;
use std::{env, fs};
fn main() -> Result<(), Box<dyn Error>> {
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(())
}

254
tests/nrf/link_ram.x Normal file
View file

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

5
tests/nrf/memory.x Normal file
View file

@ -0,0 +1,5 @@
MEMORY
{
FLASH : ORIGIN = 0x00000000, LENGTH = 1024K
RAM : ORIGIN = 0x20000000, LENGTH = 256K
}

View file

@ -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();
}

View file

@ -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();
}