diff --git a/embassy-nrf/src/uarte.rs b/embassy-nrf/src/uarte.rs index a02f7c34..0f574ba3 100644 --- a/embassy-nrf/src/uarte.rs +++ b/embassy-nrf/src/uarte.rs @@ -5,7 +5,7 @@ use core::marker::PhantomData; use core::sync::atomic::{compiler_fence, Ordering}; use core::task::Poll; use embassy::interrupt::InterruptExt; -use embassy::traits::uart::{Error, Read, Write}; +use embassy::traits::uart::{Error, Read, ReadUntilIdle, Write}; use embassy::util::{AtomicWaker, OnDrop, Unborrow}; use embassy_extras::unborrow; use futures::future::poll_fn; @@ -17,7 +17,9 @@ use crate::interrupt; use crate::interrupt::Interrupt; use crate::pac; use crate::peripherals; +use crate::ppi::{AnyConfigurableChannel, ConfigurableChannel, Event, Ppi, Task}; use crate::target_constants::EASY_DMA_SIZE; +use crate::timer::Instance as TimerInstance; // 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}; @@ -281,6 +283,161 @@ impl<'d, T: Instance> Write for Uarte<'d, T> { } } +/// Interface to an UARTE peripheral that uses timers and PPI to emulate +/// ReadUntilIdle. +pub struct UarteWithIdle<'d, U: Instance, T: TimerInstance> { + uarte: Uarte<'d, U>, + timer: T, + _ppi_ch1: Ppi<'d, AnyConfigurableChannel>, + _ppi_ch2: Ppi<'d, AnyConfigurableChannel>, +} + +impl<'d, U: Instance, T: TimerInstance> UarteWithIdle<'d, U, T> { + /// Creates the interface to a UARTE instance. + /// Sets the baud rate, parity and assigns the pins to the UARTE peripheral. + /// + /// # Safety + /// + /// The returned API is safe unless you use `mem::forget` (or similar safe mechanisms) + /// on stack allocated buffers which which have been passed to [`send()`](Uarte::send) + /// or [`receive`](Uarte::receive). + #[allow(unused_unsafe)] + pub unsafe fn new( + uarte: impl Unborrow + 'd, + timer: impl Unborrow + 'd, + ppi_ch1: impl Unborrow + 'd, + ppi_ch2: impl Unborrow + 'd, + irq: impl Unborrow + 'd, + rxd: impl Unborrow + 'd, + txd: impl Unborrow + 'd, + cts: impl Unborrow + 'd, + rts: impl Unborrow + 'd, + config: Config, + ) -> Self { + let baudrate = config.baudrate; + let uarte = Uarte::new(uarte, irq, rxd, txd, cts, rts, config); + + unborrow!(timer, ppi_ch1, ppi_ch2); + + let r = U::regs(); + let rt = timer.regs(); + + // 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 / (baudrate as u32 / 40); + + rt.tasks_stop.write(|w| unsafe { w.bits(1) }); + rt.bitmode.write(|w| w.bitmode()._32bit()); + rt.prescaler.write(|w| unsafe { w.prescaler().bits(0) }); + rt.cc[0].write(|w| unsafe { w.bits(timeout) }); + rt.mode.write(|w| w.mode().timer()); + rt.shorts.write(|w| { + w.compare0_clear().set_bit(); + w.compare0_stop().set_bit(); + w + }); + + let mut ppi_ch1 = Ppi::new(ppi_ch1.degrade_configurable()); + ppi_ch1.set_event(Event::from_reg(&r.events_rxstarted)); + ppi_ch1.set_task(Task::from_reg(&rt.tasks_clear)); + ppi_ch1.set_fork_task(Task::from_reg(&rt.tasks_start)); + ppi_ch1.enable(); + + let mut ppi_ch2 = Ppi::new(ppi_ch2.degrade_configurable()); + ppi_ch2.set_event(Event::from_reg(&rt.events_compare[0])); + ppi_ch2.set_task(Task::from_reg(&r.tasks_stoprx)); + ppi_ch2.enable(); + + Self { + uarte, + timer, + _ppi_ch1: ppi_ch1, + _ppi_ch2: ppi_ch2, + } + } +} + +impl<'d, U: Instance, T: TimerInstance> ReadUntilIdle for UarteWithIdle<'d, U, T> { + #[rustfmt::skip] + type ReadUntilIdleFuture<'a> where Self: 'a = impl Future> + 'a; + fn read_until_idle<'a>(&'a mut self, rx_buffer: &'a mut [u8]) -> Self::ReadUntilIdleFuture<'a> { + async move { + let ptr = rx_buffer.as_ptr(); + let len = rx_buffer.len(); + assert!(len <= EASY_DMA_SIZE); + + let r = U::regs(); + let s = U::state(); + + let rt = self.timer.regs(); + + let drop = OnDrop::new(move || { + info!("read drop: stopping"); + + rt.tasks_stop.write(|w| unsafe { w.bits(1) }); + + r.intenclr.write(|w| w.endrx().clear()); + r.events_rxto.reset(); + r.tasks_stoprx.write(|w| unsafe { w.bits(1) }); + + while r.events_endrx.read().bits() == 0 {} + + info!("read drop: stopped"); + }); + + r.rxd.ptr.write(|w| unsafe { w.ptr().bits(ptr as u32) }); + r.rxd.maxcnt.write(|w| unsafe { w.maxcnt().bits(len as _) }); + + r.events_endrx.reset(); + r.intenset.write(|w| w.endrx().set()); + + compiler_fence(Ordering::SeqCst); + + trace!("startrx"); + r.tasks_startrx.write(|w| unsafe { w.bits(1) }); + + let n: usize = poll_fn(|cx| { + s.endrx_waker.register(cx.waker()); + if r.events_endrx.read().bits() != 0 { + let n: usize = r.rxd.amount.read().amount().bits() as usize; + return Poll::Ready(n); + } + Poll::Pending + }) + .await; + + compiler_fence(Ordering::SeqCst); + r.events_rxstarted.reset(); + // Stop timer + rt.tasks_stop.write(|w| unsafe { w.bits(1) }); + drop.defuse(); + + Ok(n) + } + } +} + +impl<'d, U: Instance, T: TimerInstance> Read for UarteWithIdle<'d, U, T> { + #[rustfmt::skip] + type ReadFuture<'a> where Self: 'a = impl Future> + 'a; + fn read<'a>(&'a mut self, rx_buffer: &'a mut [u8]) -> Self::ReadFuture<'a> { + self.uarte.read(rx_buffer) + } +} + +impl<'d, U: Instance, T: TimerInstance> Write for UarteWithIdle<'d, U, T> { + #[rustfmt::skip] + type WriteFuture<'a> where Self: 'a = impl Future> + 'a; + + fn write<'a>(&'a mut self, tx_buffer: &'a [u8]) -> Self::WriteFuture<'a> { + self.uarte.write(tx_buffer) + } +} + mod sealed { use super::*;