diff --git a/.vscode/settings.json b/.vscode/settings.json index dc200f79e..8a292c0be 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,8 +4,8 @@ "rust-analyzer.cargo.allFeatures": false, "rust-analyzer.checkOnSave.allFeatures": false, "rust-analyzer.checkOnSave.allTargets": false, - "rust-analyzer.cargo.target": "thumbv7em-none-eabihf", - "rust-analyzer.checkOnSave.target": "thumbv7em-none-eabihf", + "rust-analyzer.cargo.target": "thumbv7em-none-eabi", + "rust-analyzer.checkOnSave.target": "thumbv7em-none-eabi", "rust-analyzer.procMacro.enable": true, "rust-analyzer.cargo.loadOutDirsFromCheck": true, "files.watcherExclude": { diff --git a/embassy-nrf-examples/src/bin/uart_idle.rs b/embassy-nrf-examples/src/bin/uart_idle.rs new file mode 100644 index 000000000..54d524ae5 --- /dev/null +++ b/embassy-nrf-examples/src/bin/uart_idle.rs @@ -0,0 +1,49 @@ +#![no_std] +#![no_main] +#![feature(min_type_alias_impl_trait)] +#![feature(impl_trait_in_bindings)] +#![feature(type_alias_impl_trait)] +#![allow(incomplete_features)] + +#[path = "../example_common.rs"] +mod example_common; +use embassy_traits::uart::ReadUntilIdle; +use example_common::*; + +use defmt::panic; +use embassy::executor::Spawner; +use embassy::traits::uart::{Read, Write}; +use embassy::util::Steal; +use embassy_nrf::gpio::NoPin; +use embassy_nrf::{interrupt, uarte, Peripherals}; + +#[embassy::main] +async fn main(spawner: Spawner) { + let p = unsafe { Peripherals::steal() }; + + let mut config = uarte::Config::default(); + config.parity = uarte::Parity::EXCLUDED; + config.baudrate = uarte::Baudrate::BAUD115200; + + let irq = interrupt::take!(UARTE0_UART0); + let mut uart = unsafe { + uarte::UarteWithIdle::new( + p.UARTE0, p.TIMER0, p.PPI_CH0, p.PPI_CH1, irq, p.P0_08, p.P0_06, NoPin, NoPin, config, + ) + }; + + info!("uarte initialized!"); + + // Message must be in SRAM + let mut buf = [0; 8]; + buf.copy_from_slice(b"Hello!\r\n"); + + unwrap!(uart.write(&buf).await); + info!("wrote hello in uart!"); + + loop { + info!("reading..."); + let n = unwrap!(uart.read_until_idle(&mut buf).await); + info!("got {} bytes", n); + } +} diff --git a/embassy-nrf/src/uarte.rs b/embassy-nrf/src/uarte.rs index a02f7c347..04907fb56 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,168 @@ impl<'d, T: Instance> Write for Uarte<'d, T> { } } +/// Interface to an UARTE peripheral that uses an additional timer and two PPI channels, +/// allowing it to implement the ReadUntilIdle trait. +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_rxdrdy)); + 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) }); + + poll_fn(|cx| { + s.endrx_waker.register(cx.waker()); + if r.events_endrx.read().bits() != 0 { + return Poll::Ready(()); + } + Poll::Pending + }) + .await; + + compiler_fence(Ordering::SeqCst); + let n = r.rxd.amount.read().amount().bits() as usize; + + // Stop timer + rt.tasks_stop.write(|w| unsafe { w.bits(1) }); + r.events_rxstarted.reset(); + + 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> { + async move { + self.ppi_ch1.disable(); + let result = self.uarte.read(rx_buffer).await; + self.ppi_ch1.enable(); + result + } + } +} + +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::*; diff --git a/embassy-stm32-examples/.cargo/config.toml b/embassy-stm32-examples/.cargo/config.toml index 7c1d4dfb6..3ccca879d 100644 --- a/embassy-stm32-examples/.cargo/config.toml +++ b/embassy-stm32-examples/.cargo/config.toml @@ -25,4 +25,4 @@ rustflags = [ ] [build] -target = "thumbv7em-none-eabihf" +target = "thumbv7em-none-eabi"