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:
commit
d91efe3e62
1
ci.sh
1
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
|
||||
|
||||
|
||||
|
@ -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 <https://github.com/embassy-rs/embassy/issues/536>.
|
||||
//!
|
||||
//! 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,128 @@
|
||||
//!
|
||||
//! 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<StateInner<'d, U, T>>);
|
||||
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>,
|
||||
/// UART error.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[non_exhaustive]
|
||||
pub enum Error {
|
||||
// No errors for now
|
||||
}
|
||||
|
||||
rx: RingBuffer<'d>,
|
||||
rx_state: RxState,
|
||||
rx_waker: WakerRegistration,
|
||||
pub(crate) use sealed::State;
|
||||
|
||||
tx: RingBuffer<'d>,
|
||||
tx_state: TxState,
|
||||
tx_waker: WakerRegistration,
|
||||
impl State {
|
||||
pub(crate) const fn new() -> Self {
|
||||
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.
|
||||
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> 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<P = U> + 'd,
|
||||
uarte: impl Peripheral<P = U> + 'd,
|
||||
timer: impl Peripheral<P = T> + 'd,
|
||||
ppi_ch1: impl Peripheral<P = impl ConfigurableChannel + 'd> + 'd,
|
||||
ppi_ch2: impl Peripheral<P = impl ConfigurableChannel + 'd> + '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,
|
||||
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,
|
||||
rxd: 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],
|
||||
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<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 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 +185,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(),
|
||||
})),
|
||||
_ppi_group: ppi_group,
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
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));
|
||||
});
|
||||
}
|
||||
|
||||
/// 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 })
|
||||
}
|
||||
|
||||
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| {
|
||||
let mut do_pend = false;
|
||||
let mut inner = self.inner.borrow_mut();
|
||||
let res = inner.with(|state| {
|
||||
compiler_fence(Ordering::SeqCst);
|
||||
trace!("poll_read");
|
||||
//trace!("poll_write: {:?}", buf.len());
|
||||
let s = U::buffered_state();
|
||||
let mut tx = unsafe { s.tx_buf.writer() };
|
||||
|
||||
// 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_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();
|
||||
let tx_buf = tx.push_slice();
|
||||
if tx_buf.is_empty() {
|
||||
trace!("poll_write: pending");
|
||||
state.tx_waker.register(cx.waker());
|
||||
//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);
|
||||
tx.push_done(n);
|
||||
|
||||
trace!("poll_write: queued {:?}", n);
|
||||
//trace!("poll_write: queued {:?}", n);
|
||||
|
||||
compiler_fence(Ordering::SeqCst);
|
||||
Self::pend_irq();
|
||||
|
||||
Poll::Ready(Ok(n))
|
||||
});
|
||||
|
||||
inner.pend();
|
||||
|
||||
res
|
||||
})
|
||||
.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| {
|
||||
self.inner.borrow_mut().with(|state| {
|
||||
trace!("poll_flush");
|
||||
|
||||
if !state.tx.is_empty() {
|
||||
trace!("poll_flush: pending");
|
||||
state.tx_waker.register(cx.waker());
|
||||
//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;
|
||||
}
|
||||
|
||||
Poll::Ready(Ok(()))
|
||||
})
|
||||
})
|
||||
.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| {
|
||||
self.inner.borrow_mut().with(|state| {
|
||||
compiler_fence(Ordering::SeqCst);
|
||||
trace!("fill_buf");
|
||||
//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();
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
trace!(" empty");
|
||||
state.rx_waker.register(cx.waker());
|
||||
Poll::<Result<&[u8], core::convert::Infallible>>::Pending
|
||||
})
|
||||
// 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());
|
||||
}
|
||||
|
||||
/// 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>,
|
||||
}
|
||||
|
||||
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.
|
||||
pub struct BufferedUarteRx<'u, 'd, U: UarteInstance, T: TimerInstance> {
|
||||
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> {
|
||||
type Error = core::convert::Infallible;
|
||||
type Error = Error;
|
||||
}
|
||||
|
||||
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> {
|
||||
type Error = core::convert::Infallible;
|
||||
type Error = Error;
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 +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.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;
|
||||
let s = U::buffered_state();
|
||||
unsafe {
|
||||
s.rx_buf.deinit();
|
||||
s.tx_buf.deinit();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
@ -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")]
|
||||
|
@ -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() }
|
||||
}
|
||||
|
||||
|
@ -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<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(®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::<u32>();
|
||||
|
||||
@ -112,7 +194,7 @@ pub(crate) mod sealed {
|
||||
}
|
||||
|
||||
/// 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
|
||||
fn number(&self) -> usize;
|
||||
}
|
||||
@ -130,7 +212,7 @@ pub trait StaticChannel: Channel + Into<AnyStaticChannel> {
|
||||
}
|
||||
|
||||
/// 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.
|
||||
fn number(&self) -> usize;
|
||||
/// Convert into a type erased group.
|
||||
@ -248,6 +330,12 @@ macro_rules! impl_group {
|
||||
$number
|
||||
}
|
||||
}
|
||||
|
||||
impl From<peripherals::$type> for crate::ppi::AnyGroup {
|
||||
fn from(val: peripherals::$type) -> Self {
|
||||
crate::ppi::Group::degrade(val)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -14,7 +14,7 @@ impl Event {
|
||||
}
|
||||
}
|
||||
|
||||
fn regs() -> &'static pac::ppi::RegisterBlock {
|
||||
pub(crate) fn regs() -> &'static pac::ppi::RegisterBlock {
|
||||
unsafe { &*pac::PPI::ptr() }
|
||||
}
|
||||
|
||||
|
@ -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<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
|
||||
/// `Uarte` uses this internally.
|
||||
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.
|
||||
///
|
||||
/// 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);
|
||||
|
||||
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.
|
||||
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,
|
||||
|
@ -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;
|
||||
|
@ -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 embedded_io::asynch::Write;
|
||||
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!");
|
||||
|
||||
|
9
tests/nrf/.cargo/config.toml
Normal file
9
tests/nrf/.cargo/config.toml
Normal 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
20
tests/nrf/Cargo.toml
Normal 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
16
tests/nrf/build.rs
Normal 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
254
tests/nrf/link_ram.x
Normal 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
5
tests/nrf/memory.x
Normal file
@ -0,0 +1,5 @@
|
||||
MEMORY
|
||||
{
|
||||
FLASH : ORIGIN = 0x00000000, LENGTH = 1024K
|
||||
RAM : ORIGIN = 0x20000000, LENGTH = 256K
|
||||
}
|
74
tests/nrf/src/bin/buffered_uart.rs
Normal file
74
tests/nrf/src/bin/buffered_uart.rs
Normal 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();
|
||||
}
|
86
tests/nrf/src/bin/buffered_uart_spam.rs
Normal file
86
tests/nrf/src/bin/buffered_uart_spam.rs
Normal 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();
|
||||
}
|
Loading…
Reference in New Issue
Block a user