diff --git a/esp-hal/CHANGELOG.md b/esp-hal/CHANGELOG.md index 45c7048ffb..8b2263c48d 100644 --- a/esp-hal/CHANGELOG.md +++ b/esp-hal/CHANGELOG.md @@ -8,22 +8,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added + - RMT: All public types now derive `Debug` and `defmt::Format`. (#4302) - RMT: `Channel::apply_config` has been added. (#4302) - - Added blocking `send_break`, `wait_for_break` and `wait_for_break_with_timeout` for sending and detecting software breaks with the UART driver (#4284) - Added support for `RxBreakDetected` interrupt and `wait_for_break_async` for detecting software breaks asynchronously to the UART driver (#4284) - Unsafely expose GPIO pins that are only available on certain chip/module variants (#4520) - ESP32-H2: light sleep and deep sleep support with timer wakeup source (#4587) +- RMT: Added the `Encoder` trait and `CopyEncoder`, `IterEncoder` and `BytesEncoder` implementations. (#4604) ### Changed + - RMT: `SingleShotTxTransaction` has been renamed to `TxTransaction`. (#4302) - RMT: `ChannelCreator::configure_tx` and `ChannelCreator::configure_rx` now take the configuration by reference. (#4302) - RMT: `ChannelCreator::configure_tx` and `ChannelCreator::configure_rx` don't take a pin anymore, instead `Channel::with_pin` has been added. (#4302) - RMT: Configuration errors have been split out of `rmt::Error` into the new `rmt::ConfigError` enum. (#4494) - RMT: `Rmt::new()` now returns `Error::UnreachableTargetFrequency` instead of panicking when requesting 0 Hz. (#4509) - - Internal clock configuration rework (#4501) +- RMT: Support for `Into` and `From` has been removed from Tx and Rx methods, respectively. Instead, Tx methods now take data as `impl Encoder`. (#4604) ### Fixed diff --git a/esp-hal/MIGRATING-1.0.0.md b/esp-hal/MIGRATING-1.0.0.md index cc6d0fe35d..59604166a9 100644 --- a/esp-hal/MIGRATING-1.0.0.md +++ b/esp-hal/MIGRATING-1.0.0.md @@ -69,3 +69,30 @@ Configuration methods now return `ConfigError` instead of `Error`. Corresponding enum variants have been removed from `Error`, and some variants that are now part of `ConfigError` have been renamed. + +### RMT data type changes + +Support for `Into` and `From` has been removed from Tx and Rx methods, respectively. + +For Rx methods, you need to provide a `PulseCode` buffer now: + +```diff +-let mut data: [u32; 8] = [0u32; 8]; ++let mut data: [PulseCode; 8] = [PulseCode::default(); 8]; + rx_channel.receive(&mut data); +``` + +For Tx methods, data types now need to implement the `Encoder` trait. +Transmitting slices of `PulseCode` is possible via the provided `CopyEncoder` +type, but more sophisticated implementations are possible: + +```diff + let data: [PulseCode; 2] = [ + PulseCode::new(Level::High, 42, Level::Low, 24), + PulseCode::end_marker(), + ]; +-tx_channel.transmit(&data)?; ++let mut encoder = CopyEncoder::new(&data); ++tx_channel.transmit(&mut encoder)?; +``` + diff --git a/esp-hal/src/rmt.rs b/esp-hal/src/rmt.rs index 2f253856f1..17b1b79bf7 100644 --- a/esp-hal/src/rmt.rs +++ b/esp-hal/src/rmt.rs @@ -49,6 +49,13 @@ //! channels are indicated by n which is used as a placeholder for the channel //! number, and by m for RX channels. //! +//! ## Encoders +//! +//! Conversion of the data to be sent to [`PulseCode`]s is handled by the [`Encoder`] trait. +//! Conceptually, this trait is similar to `Iterator`, but the specific details +//! of its methods enable generation of more performant code. +//! `esp-hal` provides [`CopyEncoder`], [`IterEncoder`] and [`BytesEncoder`]. +//! //! ## Examples //! //! ### Initialization @@ -83,7 +90,7 @@ //! # {before_snippet} //! # use esp_hal::delay::Delay; //! # use esp_hal::gpio::Level; -//! # use esp_hal::rmt::{PulseCode, Rmt, TxChannelConfig, TxChannelCreator}; +//! # use esp_hal::rmt::{CopyEncoder, PulseCode, Rmt, TxChannelConfig, TxChannelCreator}; //! //! // Configure frequency based on chip type //! # {freq} @@ -103,7 +110,8 @@ //! data[data.len() - 1] = PulseCode::end_marker(); //! //! loop { -//! let transaction = channel.transmit(&data)?; +//! let mut encoder = CopyEncoder::new(&data); +//! let transaction = channel.transmit(&mut encoder)?; //! channel = transaction.wait()?; //! delay.delay_millis(500); //! } @@ -239,7 +247,18 @@ use crate::{ mod reader; use reader::{ReaderState, RmtReader}; mod writer; -use writer::{RmtWriter, WriterState}; +pub use writer::{ + BitOrder, + BytesEncoder, + CopyEncoder, + Encoder, + IterEncoder, + LsbFirst, + MsbFirst, + RmtSlot, + RmtWriter, +}; +use writer::{EncoderRef, WriterContext, WriterState}; /// A configuration error #[derive(Debug, Clone, Copy, PartialEq)] @@ -1429,28 +1448,31 @@ impl Drop for RxGuard { /// `.wait()` needs to be called before the entire buffer has been sent to avoid /// underruns. #[must_use = "transactions need to be `poll()`ed / `wait()`ed for to ensure progress"] -#[derive(Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct TxTransaction<'ch, 'data, T> -where - T: Into + Copy, -{ +pub struct TxTransaction<'ch, 'data> { // This must go first such that it is dropped before the channel (which might disable the // peripheral on drop)! _guard: TxGuard, channel: Channel<'ch, Blocking, Tx>, - writer: RmtWriter, + writer: WriterContext, // Remaining data that has not yet been written to channel RAM. May be empty. - remaining_data: &'data [T], + data: EncoderRef<'data>, } -impl<'ch, T> TxTransaction<'ch, '_, T> -where - T: Into + Copy, -{ +impl core::fmt::Debug for TxTransaction<'_, '_> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("TxTransaction") + .field("channel", &self.channel) + .field("writer", &self.writer) + // Skip self.data since we don't want to require it to impl Debug + .finish_non_exhaustive() + } +} + +impl<'ch> TxTransaction<'ch, '_> { #[cfg_attr(place_rmt_driver_in_ram, ram)] fn poll_internal(&mut self) -> Option { let raw = self.channel.raw; @@ -1459,9 +1481,9 @@ where if status == Some(Event::Threshold) { raw.clear_tx_interrupts(Event::Threshold); - // `RmtWriter::write()` is safe to call even if `poll_internal` is called repeatedly + // `WriterContext::write()` is safe to call even if `poll_internal` is called repeatedly // after the data is exhausted since it returns immediately if already done. - self.writer.write(&mut self.remaining_data, raw, false); + self.data.write(&mut self.writer, raw, false); } status @@ -1487,15 +1509,7 @@ where let result = loop { match self.poll_internal() { Some(Event::Error) => break Err(Error::TransmissionError), - Some(Event::End) => { - if !self.remaining_data.is_empty() { - // Unexpectedly done, even though we have data left: For example, this could - // happen if there is a stop code inside the data and not just at the end. - break Err(Error::TransmissionError); - } else { - break Ok(()); - } - } + Some(Event::End) => break self.writer.state().to_result(), _ => continue, } }; @@ -1708,25 +1722,29 @@ impl<'ch> Channel<'ch, Blocking, Tx> { /// This returns a [`TxTransaction`] which can be used to wait for /// the transaction to complete and get back the channel for further /// use. + #[inline] + pub fn transmit<'data>( + self, + data: &'data mut impl Encoder, + ) -> Result, (Error, Self)> { + let data = EncoderRef::new(data); + self.transmit_impl(data) + } + #[cfg_attr(place_rmt_driver_in_ram, ram)] - pub fn transmit<'data, T>( + fn transmit_impl<'data>( self, - mut data: &'data [T], - ) -> Result, (Error, Self)> - where - T: Into + Copy, - { + mut data: EncoderRef<'data>, + ) -> Result, (Error, Self)> { let raw = self.raw; let memsize = raw.memsize(); - match data.last() { - None => return Err((Error::InvalidArgument, self)), - Some(&code) if code.into().is_end_marker() => (), - Some(_) => return Err((Error::EndMarkerMissing, self)), - } + let mut writer = WriterContext::new(); + data.write(&mut writer, raw, true); - let mut writer = RmtWriter::new(); - writer.write(&mut data, raw, true); + if let WriterState::Error(e) = writer.state() { + return Err((e, self)); + } raw.clear_tx_interrupts(EnumSet::all()); raw.start_send(None, memsize); @@ -1734,7 +1752,7 @@ impl<'ch> Channel<'ch, Blocking, Tx> { Ok(TxTransaction { channel: self, writer, - remaining_data: data, + data, _guard: TxGuard::new(raw), }) } @@ -1751,15 +1769,22 @@ impl<'ch> Channel<'ch, Blocking, Tx> { doc = "When using a loop `mode` other than [`LoopMode::Infinite`], [`ContinuousTxTransaction::is_loopcount_interrupt_set`] can be used to check if the loop count is reached." )] /// The length of `data` cannot exceed the size of the allocated RMT RAM. + #[inline] + pub fn transmit_continuously( + self, + data: &mut impl Encoder, + mode: LoopMode, + ) -> Result, (Error, Self)> { + let data = EncoderRef::new(data); + self.transmit_continuously_impl(data, mode) + } + #[cfg_attr(place_rmt_driver_in_ram, ram)] - pub fn transmit_continuously( + fn transmit_continuously_impl( self, - mut data: &[T], + mut data: EncoderRef<'_>, mode: LoopMode, - ) -> Result, (Error, Self)> - where - T: Into + Copy, - { + ) -> Result, (Error, Self)> { let raw = self.raw; let memsize = raw.memsize(); @@ -1768,10 +1793,13 @@ impl<'ch> Channel<'ch, Blocking, Tx> { return Err((Error::InvalidArgument, self)); } - if data.is_empty() { - return Err((Error::InvalidArgument, self)); - } else if data.len() > memsize.codes() { - return Err((Error::Overflow, self)); + let mut writer = WriterContext::new(); + data.write(&mut writer, raw, true); + + match writer.state() { + WriterState::Error(e) => return Err((e, self)), + WriterState::Active => return Err((Error::Overflow, self)), + WriterState::Done => (), } let mut _guard = TxGuard::new(raw); @@ -1781,9 +1809,6 @@ impl<'ch> Channel<'ch, Blocking, Tx> { } if _guard.is_active() { - let mut writer = RmtWriter::new(); - writer.write(&mut data, raw, true); - raw.clear_tx_interrupts(EnumSet::all()); raw.start_send(Some(mode), memsize); } @@ -1799,10 +1824,7 @@ impl<'ch> Channel<'ch, Blocking, Tx> { #[must_use = "transactions need to be `poll()`ed / `wait()`ed for to ensure progress"] #[derive(Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct RxTransaction<'ch, 'data, T> -where - T: From, -{ +pub struct RxTransaction<'ch, 'data> { // This must go first such that it is dropped before the channel (which might disable the // peripheral on drop)! _guard: RxGuard, @@ -1811,13 +1833,10 @@ where reader: RmtReader, - data: &'data mut [T], + data: &'data mut [PulseCode], } -impl<'ch, T> RxTransaction<'ch, '_, T> -where - T: From, -{ +impl<'ch> RxTransaction<'ch, '_> { #[cfg_attr(place_rmt_driver_in_ram, ram)] fn poll_internal(&mut self) -> Option { let raw = self.channel.raw; @@ -1893,13 +1912,10 @@ impl<'ch> Channel<'ch, Blocking, Rx> { /// /// # {rx_size_limit} #[cfg_attr(place_rmt_driver_in_ram, ram)] - pub fn receive<'data, T>( + pub fn receive<'data>( self, - data: &'data mut [T], - ) -> Result, (Error, Self)> - where - T: From, - { + data: &'data mut [PulseCode], + ) -> Result, (Error, Self)> { let raw = self.raw; let memsize = raw.memsize(); @@ -1927,24 +1943,18 @@ static WAKER: [AtomicWaker; NUM_CHANNELS] = [const { AtomicWaker::new() }; NUM_C static RMT_LOCK: RawMutex = RawMutex::new(); #[must_use = "futures do nothing unless you `.await` or poll them"] -struct TxFuture<'a, T> -where - T: Into + Copy, -{ +struct TxFuture<'a> { raw: DynChannelAccess, _phantom: PhantomData>, - writer: RmtWriter, + writer: WriterContext, // Remaining data that has not yet been written to channel RAM. May be empty. - data: &'a [T], + data: EncoderRef<'a>, _guard: TxGuard, } -impl core::future::Future for TxFuture<'_, T> -where - T: Into + Copy, -{ +impl core::future::Future for TxFuture<'_> { type Output = Result<(), Error>; #[cfg_attr(place_rmt_driver_in_ram, ram)] @@ -1952,7 +1962,7 @@ where let this = self.get_mut(); let raw = this.raw; - if let WriterState::Error(err) = this.writer.state { + if let WriterState::Error(err) = this.writer.state() { return Poll::Ready(Err(err)); } @@ -1960,20 +1970,13 @@ where let result = match raw.get_tx_status() { Some(Event::Error) => Err(Error::TransmissionError), - Some(Event::End) => { - if this.writer.state == WriterState::Active { - // Unexpectedly done, even though we have data left. - Err(Error::TransmissionError) - } else { - Ok(()) - } - } + Some(Event::End) => this.writer.state().to_result(), Some(Event::Threshold) => { raw.clear_tx_interrupts(Event::Threshold); - this.writer.write(&mut this.data, raw, false); + this.data.write(&mut this.writer, raw, false); - if this.writer.state == WriterState::Active { + if this.writer.state() == WriterState::Active { raw.listen_tx_interrupt(Event::Threshold); } @@ -1991,30 +1994,25 @@ where /// TX channel in async mode impl Channel<'_, Async, Tx> { /// Start transmitting the given pulse code sequence. + #[inline] + pub fn transmit(&mut self, data: &mut impl Encoder) -> impl Future> { + let data = EncoderRef::new(data); + self.transmit_impl(data) + } + #[cfg_attr(place_rmt_driver_in_ram, ram)] - pub fn transmit(&mut self, mut data: &[T]) -> impl Future> - where - T: Into + Copy, - { + fn transmit_impl( + &mut self, + mut data: EncoderRef<'_>, + ) -> impl Future> { let raw = self.raw; let memsize = raw.memsize(); - let mut writer = RmtWriter::new(); - - match data.last() { - None => { - writer.state = WriterState::Error(Error::InvalidArgument); - } - Some(&code) if code.into().is_end_marker() => (), - Some(_) => { - writer.state = WriterState::Error(Error::EndMarkerMissing); - } - } - - let _guard = if !matches!(writer.state, WriterState::Error(_)) { - writer.write(&mut data, raw, true); + let mut writer = WriterContext::new(); + data.write(&mut writer, raw, true); - let wrap = match writer.state { + let _guard = if writer.state().is_ok() { + let wrap = match writer.state() { WriterState::Error(_) => false, WriterState::Active => true, WriterState::Done => false, @@ -2044,21 +2042,15 @@ impl Channel<'_, Async, Tx> { } #[must_use = "futures do nothing unless you `.await` or poll them"] -struct RxFuture<'a, T> -where - T: From + Unpin, -{ +struct RxFuture<'a> { raw: DynChannelAccess, _phantom: PhantomData>, reader: RmtReader, - data: &'a mut [T], + data: &'a mut [PulseCode], _guard: RxGuard, } -impl core::future::Future for RxFuture<'_, T> -where - T: From + Unpin, -{ +impl core::future::Future for RxFuture<'_> { type Output = Result; #[cfg_attr(place_rmt_driver_in_ram, ram)] @@ -2114,10 +2106,10 @@ impl Channel<'_, Async, Rx> { /// /// # {rx_size_limit} #[cfg_attr(place_rmt_driver_in_ram, ram)] - pub fn receive(&mut self, data: &mut [T]) -> impl Future> - where - T: From + Unpin, - { + pub fn receive( + &mut self, + data: &mut [PulseCode], + ) -> impl Future> { let raw = self.raw; let memsize = raw.memsize(); diff --git a/esp-hal/src/rmt/reader.rs b/esp-hal/src/rmt/reader.rs index 9682231ab2..674be47cdc 100644 --- a/esp-hal/src/rmt/reader.rs +++ b/esp-hal/src/rmt/reader.rs @@ -40,10 +40,12 @@ impl RmtReader { // If `final_` is set, read a full buffer length, potentially wrapping around. Otherwise, fetch // half the buffer's length. #[cfg_attr(place_rmt_driver_in_ram, ram)] - pub(super) fn read(&mut self, data: &mut &mut [T], raw: DynChannelAccess, final_: bool) - where - T: From, - { + pub(super) fn read( + &mut self, + data: &mut &mut [PulseCode], + raw: DynChannelAccess, + final_: bool, + ) { if self.state != ReaderState::Active { return; } @@ -97,7 +99,7 @@ impl RmtReader { // `data.len()` such that incrementing both pointers cannot advance them beyond // their allocation's end. unsafe { - data_ptr.write(ram_ptr.read_volatile().into()); + data_ptr.write(ram_ptr.read_volatile()); ram_ptr = ram_ptr.add(1); data_ptr = data_ptr.add(1); } diff --git a/esp-hal/src/rmt/writer.rs b/esp-hal/src/rmt/writer.rs index 3b50558bca..b995f9d072 100644 --- a/esp-hal/src/rmt/writer.rs +++ b/esp-hal/src/rmt/writer.rs @@ -1,91 +1,620 @@ +use core::{marker::PhantomData, ops::ControlFlow, ptr::NonNull}; + #[cfg(place_rmt_driver_in_ram)] use procmacros::ram; use super::{DynChannelAccess, Error, PulseCode, Tx}; -#[derive(Debug, PartialEq)] +/// Access to a free slot in an RMT channel hardware buffer. +/// +/// An `RmtSlot` indicates a guarantee that there's a free slot in memory +/// that can immediately be written to. If this slot isn't used, the +/// next call to `RmtWriter.next` will use the same slot again (i.e. +/// there will be no garbage data due to skipping unused slots). +/// +/// See [`Encoder::encode`] and [`RmtWriter::next`] for more details. +#[derive(Debug)] +pub struct RmtSlot<'a> { + writer: &'a mut RmtWriter, +} + +impl<'a> RmtSlot<'a> { + /// Append a `PulseCode` to the RMT hardware buffer. + #[inline(always)] + pub fn write(self, code: PulseCode) { + let writer = self.writer; + + debug_assert!(writer.ptr < writer.end); + + unsafe { writer.ptr.write_volatile(code) } + + writer.ptr = unsafe { writer.ptr.add(1) }; + writer.last_code = code; + } +} + +/// Provides methods to fill (part of) the RMT channel hardware buffer. +/// +/// An `RmtWriter` is provided to [`Encoder::encode`], see there for more details. +pub struct RmtWriter { + // Most recently written PulseCode + last_code: PulseCode, + // Current write pointer + ptr: *mut PulseCode, + // Start of the slice to be written + start: *mut PulseCode, + // End of the slice to be written + end: *mut PulseCode, +} + +// Given a pointer to RMT RAM, return the channel it belongs to +fn get_ch(ptr: *mut PulseCode) -> i32 { + let diff = unsafe { ptr.offset_from(property!("rmt.ram_start") as *mut PulseCode) }; + (diff / property!("rmt.channel_ram_size")) as i32 +} + +// Given a pointer to RMT RAM, return the offset relative to start of the channel's RAM +fn get_offset(ptr: *mut PulseCode) -> i32 { + let diff = unsafe { ptr.offset_from(property!("rmt.ram_start") as *mut PulseCode) }; + (diff % property!("rmt.channel_ram_size")) as i32 +} + +impl core::fmt::Debug for RmtWriter { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!( + f, + "RmtWriter {{ last_code: {:?}, ptr: CH{} + {:#x}, start: CH{} + {:#x}, end: CH{} + {:#x} }}", + self.last_code, + get_ch(self.ptr), + get_offset(self.ptr), + get_ch(self.start), + get_offset(self.start), + get_ch(self.end), + get_offset(self.end), + ) + } +} + +#[cfg(feature = "defmt")] +impl defmt::Format for RmtWriter { + fn format(&self, fmt: defmt::Formatter<'_>) { + defmt::write!( + fmt, + "RmtWriter {{ last_code: {:?}, ptr: CH{} + {:#x}, start: CH{} + {:#x}, end: CH{} + {:#x} }}", + self.last_code, + get_ch(self.ptr), + get_offset(self.ptr), + get_ch(self.start), + get_offset(self.start), + get_ch(self.end), + get_offset(self.end), + ) + } +} + +impl RmtWriter { + /// Try to obtain a slot in the channel's hardware buffer. + /// + /// Returns `None` if the buffer is full; in this case [`Encoder::encode`] must return. + #[inline(always)] + pub fn next<'s>(&'s mut self) -> Option> { + if self.ptr >= self.end { + return None; + } + + Some(RmtSlot { writer: self }) + } + + /// Write several pulse codes to the channel's hardware buffer. + /// + /// `f` will be called up to `count` times to obtain pulse codes. Returns the actual number of + /// codes written. If less than `count`, the buffer is full and [`Encoder::encode`] must + /// return. + /// + /// This explicitly supports `count = 0`, thus callers can avoid to check for that condition. + /// + /// This can lead to more efficient code compared to repeated use of [`RmtWriter::next`] when + /// the caller can provide an infallible function that yields `count` pulse codes. + #[inline(always)] + pub fn write_many(&mut self, count: usize, mut f: F) -> usize + where + F: FnMut() -> PulseCode, + { + let count = count.min(unsafe { self.end.offset_from(self.ptr) } as usize); + + let mut last_code = self.last_code; + let mut ptr = self.ptr; + let end = unsafe { self.ptr.add(count) }; + + while ptr < end { + last_code = f(); + unsafe { ptr.write_volatile(last_code) }; + ptr = unsafe { ptr.add(1) }; + } + + debug_assert!(self.ptr <= self.end); + + self.ptr = ptr; + self.last_code = last_code; + + count + } +} + +/// A trait to convert arbitrary data to RMT [`PulseCode`]s. +/// +/// Implementations need to provide the single `encode` method, which transmit methods will +/// (potentially repeatedly) call to obtain pulse codes. +pub trait Encoder { + /// (Re)Fill an RMT channel hardware buffer. + /// + /// Implementations can provide arbitrary conversions of data types, which will be performed + /// just before writing to the hardware, without intermediate buffering of `PulseCode`s. + /// This should mainly be used for low-cost operations (e.g. simple state machines and shift + /// out bits), since it needs to be fast enough to refill hardware buffers before transmission + /// exhausts them. If this cannot be guaranteed reliably, consider performing conversion to + /// `PulseCode`s upfront, before calling the channel's transmit methods. + /// + /// This function must write pulse codes via the `writer` until the latter indicates that the + /// buffer is full. Otherwise, the `writer` will consider the `Encoder` to be exhausted and not + /// call it again to check for more data. Upon exhaustion of the `Encoder` the last pulse code + /// written should contain an end marker. If the `Encoder` fails to provide one, the driver + /// will still ensure that transmission is never started or eventually stops, and return + /// `Error::EndMarkerMissing` from the transmit method. However, there are no further + /// guarantees: In particular, the current driver code might lead to truncated transmission in + /// this error case. + /// + /// In addition to writing less codes than the `writer` would allow for, implementations can + /// indicate that they have completed by returning `ControlFlow::Break`. This leads to a slight + /// optimization in case that the encoding ended just at the end of the hardware buffer, and + /// would otherwise only be detected when the subsequent call to `encode` would write zero + /// symbols. + /// + /// While this will presently not be called again after being exhausted, implementations must + /// not rely on this for safety or correctness. Rather, they should return without writing any + /// pulse codes in that case. + /// + /// For best performance, this should be inlined. It is likely that the compiler does so + /// automatically (since the driver has only a single, non-generic function that calls this + /// method). To be extra sure, consider marking your implementation of this method as + /// `#[inline(always)]`. This also applies when you're using the `PLACE_RMT_DRIVER_IN_RAM` + /// configuration: `encode` will be inlined in a driver-internal function, which in turn will + /// be placed in RAM. Annotating this method with `#[ram]` would prevent inlining and likely + /// impair performance. + fn encode(&mut self, writer: &mut RmtWriter) -> ControlFlow<()>; +} + +#[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub(super) enum WriterState { + // There's still data left to write Active, + // An error occured, and either + // - no data was written (and transmission was never started), or + // - some data was written and the last code written is an end marker, such that transmission + // will eventually stop Error(Error), + // Completed without errors Done, } +impl WriterState { + pub(super) fn is_ok(self) -> bool { + !matches!(self, Self::Error(_)) + } + + pub(super) fn to_result(self) -> Result<(), Error> { + match self { + // tx ended but writer wasn't done yet + Self::Active => Err(Error::TransmissionError), + Self::Error(e) => Err(e), + Self::Done => Ok(()), + } + } +} + #[derive(Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub(super) struct RmtWriter { +pub(super) struct WriterContext { // The position in channel RAM to continue writing at; must be either // 0 or half the available RAM size if there's further data. // The position may be invalid if there's no data left. offset: u16, - pub state: WriterState, + written: usize, + + last_code: PulseCode, + + state: WriterState, } -impl RmtWriter { +impl WriterContext { + #[inline] pub(super) fn new() -> Self { Self { offset: 0, + written: 0, + last_code: PulseCode::default(), state: WriterState::Active, } } - // Copy from `data` to the hardware buffer, advancing the `data` slice accordingly. - // - // If `initial` is set, fill the entire buffer. Otherwise, append half the buffer's length from - // `data`. - #[cfg_attr(place_rmt_driver_in_ram, ram)] - pub(super) fn write(&mut self, data: &mut &[T], raw: DynChannelAccess, initial: bool) - where - T: Into + Copy, - { - if self.state != WriterState::Active { - return; + #[inline] + pub(super) fn state(&self) -> WriterState { + self.state + } + + #[allow(unused)] + #[inline] + pub(super) fn written(&self) -> usize { + self.written + } +} + +// This is a bespoke `&'a dyn mut EncoderExt`, which optimizes for the case of not owning the +// `Encoder` and using only a single method, where +// +// pub(super) trait EncoderExt { +// fn write(&mut self, writer: &mut WriterContext, raw: DynChannelAccess, initial: bool) { +// // ... +// } +// } +// +// impl EncoderExt for E {} +// +// Thus, we only need the data pointer and a single function pointer, rather than the indirection +// via a vtable (which will likely be in flash). +pub(super) struct EncoderRef<'a> { + // Pointer to the Encoder instance, and a PhantomData to hold onto its lifetime. + data: NonNull<()>, + _phantom: PhantomData<&'a mut ()>, + + // Function pointer to encoder_write specialized for the type stored in data. + func: unsafe fn(NonNull<()>, &mut WriterContext, DynChannelAccess, bool), +} + +impl<'a> EncoderRef<'a> { + #[inline] + pub(super) fn new(enc: &'a mut E) -> Self { + Self { + data: NonNull::from(enc).cast(), + _phantom: PhantomData, + func: encoder_write::, } + } + + // Safety: E must be the same this was built with + #[allow(unused)] + #[inline] + pub(super) unsafe fn reconstruct(self) -> &'a mut E { + unsafe { self.data.cast().as_mut() } + } + + // Safety: The only way to construct this is via Self::new, which ensures that self.data and + // self.func correspond + #[inline] + pub(super) fn write( + &mut self, + writer: &mut WriterContext, + raw: DynChannelAccess, + initial: bool, + ) { + // SAFETY: The caller holds a mutable reference of self, which in turn keeps the borrow + // taken in `new()` alive. Thus, we have exclusive access to the encoder. + unsafe { (self.func)(self.data, writer, raw, initial) } + } +} + +// Copy from `data` to the hardware buffer, advancing the `data` slice accordingly. +// +// If `initial` is set, fill the entire buffer. Otherwise, append half the buffer's length from +// `data`. +#[cfg_attr(place_rmt_driver_in_ram, ram)] +#[cfg_attr(not(place_rmt_driver_in_ram), inline(never))] +unsafe fn encoder_write<'a, E: Encoder + 'a>( + encoder: NonNull<()>, + context: &mut WriterContext, + raw: DynChannelAccess, + initial: bool, +) { + let encoder: &'a mut E = unsafe { encoder.cast().as_mut() }; + + if context.state != WriterState::Active { + return; + } - debug_assert!(!initial || self.offset == 0); + debug_assert!(!initial || context.offset == 0); - let ram_start = raw.channel_ram_start(); - let memsize = raw.memsize().codes(); + let ram_start = raw.channel_ram_start(); + let memsize = raw.memsize().codes(); - let max_count = if initial { memsize } else { memsize / 2 }; - let count = data.len().min(max_count); + let max_count = if initial { memsize } else { memsize / 2 }; - let mut ram_ptr = unsafe { ram_start.add(self.offset as usize) }; + let start = unsafe { ram_start.add(context.offset as usize) }; + let mut writer = RmtWriter { + last_code: context.last_code, + ptr: start, + start, + end: unsafe { start.add(max_count) }, + }; - let mut data_ptr = data.as_ptr(); - let data_end = unsafe { data_ptr.add(count) }; + let done = encoder.encode(&mut writer); + context.last_code = writer.last_code; + context.written += unsafe { writer.ptr.offset_from(writer.start) } as usize; - while data_ptr < data_end { - // SAFETY: The iteration `count` is smaller than both `max_count` and `data.len()` such - // that incrementing both pointers cannot advance them beyond their allocation's end. - unsafe { - ram_ptr.write_volatile(data_ptr.read().into()); - ram_ptr = ram_ptr.add(1); - data_ptr = data_ptr.add(1); + debug_assert!(writer.ptr.addr() >= writer.start.addr()); + debug_assert!(writer.ptr.addr() <= writer.end.addr()); + + // If the input data was not exhausted, update offset as + // + // | initial | offset | max_count | new offset | + // | ------- + ----------- + ----------- + ----------- | + // | true | 0 | memsize | 0 | + // | false | 0 | memsize / 2 | memsize / 2 | + // | false | memsize / 2 | memsize / 2 | 0 | + // + // Otherwise, the new position is invalid but the new slice is empty and we won't use the + // offset again. In either case, the unsigned subtraction will not underflow. + context.offset = memsize as u16 - max_count as u16 - context.offset; + + if writer.ptr < writer.end || done.is_break() { + context.state = if context.written == 0 { + // The encoder was empty + WriterState::Error(Error::InvalidArgument) + // Do not check for end markers in the inner loop in Encoder::encode() since this would + // substantially increase the instruction count there. Instead, only check the last code to + // report on error. + } else if writer.last_code.is_end_marker() { + WriterState::Done + } else { + // Write an extra end marker to prevent looping forever with wrapping tx. + if writer.ptr < writer.end { + unsafe { writer.ptr.write_volatile(PulseCode::end_marker()) }; + } else { + // The buffer is full, and we would need to wait for the next Event::Threshold + // before writing an end marker. However, the data was invalid by missing an end + // marker, and the only guarantee that we provide is that transmission will + // eventually stop and return an error. Thus, to avoid the logic to implement this, + // simply modify the last code that was written to contain an end marker. + unsafe { + writer + .ptr + .sub(1) + .write_volatile(writer.last_code.with_length2(0).unwrap()) + }; } + WriterState::Error(Error::EndMarkerMissing) + }; + } + + debug_assert!(context.offset == 0 || context.offset as usize == memsize / 2); +} + +/// An [`Encoder`] that simply copies a slice of `PulseCode`s to the hardware. +#[derive(Clone, Debug)] +pub struct CopyEncoder<'a> { + data: &'a [PulseCode], +} + +impl<'a> CopyEncoder<'a> { + /// Create a new instance that transmits the provided `data`. + pub fn new(data: &'a [PulseCode]) -> Self { + Self { data } + } +} + +impl<'a> Encoder for CopyEncoder<'a> { + #[inline(always)] + fn encode(&mut self, writer: &mut RmtWriter) -> ControlFlow<()> { + let len = self.data.len(); + let mut ptr = self.data.as_ptr(); + let end = unsafe { ptr.add(len) }; + + let written = writer.write_many(len, || { + // SAFETY: write_many() guarantees that this will be called at most + // `len` times, such the pointer will remain in-bounds. + // FIXME: Check whether we can use safe indexing here and the compiler is smart enough + // to optimize the bounds check. + let code = unsafe { ptr.read() }; + ptr = unsafe { ptr.add(1) }; + code + }); + + debug_assert!(ptr <= end); + + self.data.split_off(..written).unwrap(); + + if self.data.is_empty() { + ControlFlow::Break(()) + } else { + ControlFlow::Continue(()) } + } +} + +/// An [`Encoder`] that writes `PulseCode`s from an iterator to the hardware. +#[derive(Clone, Debug)] +pub struct IterEncoder +where + D: Iterator, +{ + data: D, +} - // If the input data was not exhausted, update offset as - // - // | initial | offset | max_count | new offset | - // | ------- + ----------- + ----------- + ----------- | - // | true | 0 | memsize | 0 | - // | false | 0 | memsize / 2 | memsize / 2 | - // | false | memsize / 2 | memsize / 2 | 0 | - // - // Otherwise, the new position is invalid but the new slice is empty and we won't use the - // offset again. In either case, the unsigned subtraction will not underflow. - self.offset = memsize as u16 - max_count as u16 - self.offset; - - // The panic can never trigger since count <= data.len()! - data.split_off(..count).unwrap(); - if data.is_empty() { - self.state = WriterState::Done; +impl IterEncoder +where + D: Iterator, +{ + /// Create a new instance that transmits the provided `data`. + pub fn new(data: impl IntoIterator) -> Self { + Self { + data: data.into_iter(), + } + } +} + +impl Encoder for IterEncoder +where + D: Iterator, +{ + #[inline(always)] + fn encode(&mut self, writer: &mut RmtWriter) -> ControlFlow<()> { + while let Some(slot) = writer.next() { + if let Some(code) = self.data.next() { + slot.write(code); + } else { + return ControlFlow::Break(()); + } } - debug_assert!(self.offset == 0 || self.offset as usize == memsize / 2); + ControlFlow::Continue(()) + } +} + +/// A trait implemented by bit orders [`MsbFirst`] and [`LsbFirst`]. +// Use marker types that implement this trait rather than a BitOrder enum such that +// BytesEncoder::encode can be specialized depending on the bit order. +pub trait BitOrder: crate::private::Sealed { + #[doc(hidden)] + fn shift(value: u8, shift: usize) -> u8; + + #[doc(hidden)] + fn get_bit(value: u8, bit: usize) -> bool; +} + +/// Marker for MSB-first transmission +pub struct MsbFirst; + +/// Marker for LSB-first transmission +pub struct LsbFirst; + +impl crate::private::Sealed for MsbFirst {} + +impl crate::private::Sealed for LsbFirst {} + +impl BitOrder for MsbFirst { + #[inline(always)] + fn shift(value: u8, shift: usize) -> u8 { + value << shift + } + + #[inline(always)] + fn get_bit(value: u8, bit: usize) -> bool { + debug_assert!((bit as u32) < u8::BITS); + (value >> (7 - bit)) & 1 != 0 + } +} + +impl BitOrder for LsbFirst { + #[inline(always)] + fn shift(value: u8, shift: usize) -> u8 { + value >> shift + } + + #[inline(always)] + fn get_bit(value: u8, bit: usize) -> bool { + debug_assert!((bit as u32) < u8::BITS); + (value >> bit) & 1 != 0 + } +} + +/// An [`Encoder`] that writes `PulseCode`s for each bit of the data to the hardware. +#[derive(Clone, Copy, Debug)] +pub struct BytesEncoder<'a, B> +where + B: BitOrder, +{ + // remaining data + data: &'a [u8], + + // number of bits left in the first item in the data + bits: u8, + + // bit ordering when transmitting data + _bit_order: PhantomData, + + // the PulseCode for 0 bits + pulse_zero: PulseCode, + + // the PulseCode for 1 bits + pulse_one: PulseCode, + + // the final PulseCode + pulse_end: PulseCode, +} + +impl<'a, B> BytesEncoder<'a, B> +where + B: BitOrder, +{ + /// Create a new instance that transmits the provided `data`. + /// + /// For each 0 and 1 bit in `data`, `pulse_zero` and `pulse_one` will be sent, respectively. + /// After the data is exhausted, one copy of `pulse_end` will be sent. This should usually be + /// an end marker. + pub fn new( + data: &'a [u8], + bit_order: B, + pulse_one: PulseCode, + pulse_zero: PulseCode, + pulse_end: PulseCode, + ) -> Self { + let _ = bit_order; + Self { + data, + _bit_order: PhantomData, + bits: 8, + pulse_zero, + pulse_one, + pulse_end, + } + } +} + +impl Encoder for BytesEncoder<'_, B> +where + B: BitOrder, +{ + #[inline(always)] + fn encode(&mut self, writer: &mut RmtWriter) -> ControlFlow<()> { + let mut data = self.data; + let mut bits = self.bits as usize; + + let result = 'outer: { + while let Some(&byte) = data.first() { + let mut byte = B::shift(byte, u8::BITS as usize - bits); + + let written = writer.write_many(bits, || { + // MSB first for now + let bit = B::get_bit(byte, 0); + byte = B::shift(byte, 1); + core::hint::select_unpredictable(bit, self.pulse_one, self.pulse_zero) + }); + + bits -= written; + + if bits > 0 { + // buffer is full + break 'outer ControlFlow::Continue(()); + } + + let _ = data.split_off_first().unwrap(); + bits = 8; + } + + if let Some(slot) = writer.next() { + slot.write(self.pulse_end); + } else { + break 'outer ControlFlow::Continue(()); + } + + ControlFlow::Break(()) + }; + + self.data = data; + self.bits = bits as u8; + result } } diff --git a/examples/async/embassy_rmt_tx/src/main.rs b/examples/async/embassy_rmt_tx/src/main.rs index b1c2909709..61155884b9 100644 --- a/examples/async/embassy_rmt_tx/src/main.rs +++ b/examples/async/embassy_rmt_tx/src/main.rs @@ -14,7 +14,7 @@ use esp_backtrace as _; use esp_hal::{ gpio::Level, interrupt::software::SoftwareInterruptControl, - rmt::{PulseCode, Rmt, TxChannelConfig, TxChannelCreator}, + rmt::{CopyEncoder, PulseCode, Rmt, TxChannelConfig, TxChannelCreator}, time::Rate, timer::timg::TimerGroup, }; @@ -56,7 +56,8 @@ async fn main(_spawner: Spawner) { loop { println!("transmit"); - channel.transmit(&data).await.unwrap(); + let mut encoder = CopyEncoder::new(&data); + channel.transmit(&mut encoder).await.unwrap(); println!("transmitted\n"); Timer::after(Duration::from_millis(500)).await; } diff --git a/hil-test/src/bin/rmt.rs b/hil-test/src/bin/rmt.rs index 6ffd3fb612..732a0d6d4e 100644 --- a/hil-test/src/bin/rmt.rs +++ b/hil-test/src/bin/rmt.rs @@ -13,7 +13,12 @@ extern crate alloc; +use core::task::Poll; + use allocator_api2::{vec, vec::Vec}; +use embassy_futures::poll_once; +#[cfg(not(esp32))] +use esp_hal::rmt::LoopMode; use esp_hal::{ Async, Blocking, @@ -33,11 +38,17 @@ use esp_hal::{ peripherals::RMT, ram, rmt::{ + BitOrder, + BytesEncoder, CHANNEL_RAM_SIZE, Channel, ConfigError, + CopyEncoder, Error, HAS_RX_WRAP, + IterEncoder, + LsbFirst, + MsbFirst, PulseCode, Rmt, Rx, @@ -330,10 +341,11 @@ fn do_rmt_loopback_inner( rx_channel: Channel, ) { let tx_data = generate_tx_data(conf); - let mut rcv_data = vec![PulseCode::default(); conf.tx_len]; + let mut rx_data = vec![PulseCode::default(); conf.tx_len]; - let rx_transaction = rx_channel.receive(&mut rcv_data); - let mut tx_transaction = tx_channel.transmit(&tx_data).unwrap(); + let mut tx_enc = CopyEncoder::new(&tx_data); + let rx_transaction = rx_channel.receive(&mut rx_data); + let mut tx_transaction = tx_channel.transmit(&mut tx_enc).unwrap(); if conf.abort { // Start the transactions... @@ -375,7 +387,7 @@ fn do_rmt_loopback_inner( }; let rx_res = run(); - check_data_eq(conf, &tx_data, &rcv_data, rx_res); + check_data_eq(conf, &tx_data, &rx_data, rx_res); } } @@ -399,16 +411,17 @@ async fn do_rmt_loopback_async_inner( let mut rcv_data = vec![PulseCode::default(); conf.tx_len]; // Start the transactions... + let mut tx_enc = CopyEncoder::new(&tx_data); let rx_fut = rx_channel.receive(&mut rcv_data); - let tx_fut = tx_channel.transmit(&tx_data); + let tx_fut = tx_channel.transmit(&mut tx_enc); if conf.abort { Delay.delay_ms(2).await; // ...poll them, but then drop them before completion... // (`poll_once` takes the future by value and drops it before returning) - let rx_poll = embassy_futures::poll_once(rx_fut); - let tx_poll = embassy_futures::poll_once(tx_fut); + let rx_poll = poll_once(rx_fut); + let tx_poll = poll_once(tx_fut); // The test should fail here when the the delay above is increased, e.g. to 100ms. assert!(rx_poll.is_pending()); @@ -609,21 +622,146 @@ mod tests { do_rmt_loopback_async(&mut ctx, &conf).await; } + // Test that errors on invalid data are returned as expected (in particular, checks that the + // methods don't hang executing a never-ending/never-started tranmission). #[test] - fn rmt_fails_without_end_marker(mut ctx: Context) { + fn rmt_tx_errors_blocking(mut ctx: Context) { let conf = LoopbackConfig { end_marker: EndMarkerConfig::None, ..Default::default() }; - let (tx_channel, _) = ctx.setup_loopback(&conf); + let (mut tx_channel, _) = ctx.setup_loopback(&conf); + + let tx_data = generate_tx_data(&conf); + + let mut tx_enc = CopyEncoder::new(&tx_data); + + assert!( + matches!( + tx_channel.reborrow().transmit(&mut tx_enc), + Err((Error::EndMarkerMissing, _)) + ), + "Expected transmit to return an error without end marker" + ); + + let mut tx_enc = CopyEncoder::new(&tx_data[..0]); + + assert!( + matches!( + tx_channel.reborrow().transmit(&mut tx_enc), + Err((Error::InvalidArgument, _)) + ), + "Expected transmit to return an error on empty data" + ); + + let mut tx_enc = CopyEncoder::new(&tx_data); + + assert!( + matches!( + tx_channel + .reborrow() + .transmit_continuously(&mut tx_enc, LoopMode::Infinite), + Err((Error::EndMarkerMissing, _)) + ), + "Expected transmit_continuously to return an error without end marker" + ); + + let mut tx_enc = CopyEncoder::new(&tx_data[..0]); + assert!( + matches!( + tx_channel + .reborrow() + .transmit_continuously(&mut tx_enc, LoopMode::Infinite), + Err((Error::InvalidArgument, _)) + ), + "Expected transmit_continuously to return an error on empty data" + ); + + // Configuration that requires wrapping tx and is missing an end marker + let conf = LoopbackConfig { + tx_len: CHANNEL_RAM_SIZE * 3 / 2, + end_marker: EndMarkerConfig::None, + ..Default::default() + }; let tx_data = generate_tx_data(&conf); - assert!(matches!( - tx_channel.transmit(&tx_data), - Err((Error::EndMarkerMissing, _)) - )); + let mut tx_enc = CopyEncoder::new(&tx_data); + + // Most importantly, this should not hang indefinitely due to the missing end marker and + // re-transmitting the channel RAM content over and over again. + assert!( + matches!( + tx_channel.reborrow().transmit(&mut tx_enc).unwrap().wait(), + Err((Error::EndMarkerMissing, _)) + ), + "Expected transmit to return an error without end marker when wrapping" + ); + + let mut tx_enc = CopyEncoder::new(&tx_data); + + assert!( + matches!( + tx_channel + .reborrow() + .transmit_continuously(&mut tx_enc, LoopMode::Infinite), + Err((Error::Overflow, _)) + ), + "Expected transmit_continuously to return an error on overflow" + ); + } + + #[test] + async fn rmt_tx_errors_async(mut ctx: Context) { + let conf = LoopbackConfig { + end_marker: EndMarkerConfig::None, + ..Default::default() + }; + + let (mut tx_channel, _) = ctx.setup_loopback_async(&conf); + + let tx_data = generate_tx_data(&conf); + + let mut tx_enc = CopyEncoder::new(&tx_data); + + assert!( + matches!( + poll_once(tx_channel.transmit(&mut tx_enc)), + Poll::Ready(Err(Error::EndMarkerMissing)) + ), + "Expected transmit to return an error without end marker" + ); + + let mut tx_enc = CopyEncoder::new(&tx_data[..0]); + + assert!( + matches!( + poll_once(tx_channel.transmit(&mut tx_enc)), + Poll::Ready(Err(Error::InvalidArgument)) + ), + "Expected transmit to return an error on empty data" + ); + + // Configuration that requires wrapping tx and is missing an end marker + let conf = LoopbackConfig { + tx_len: CHANNEL_RAM_SIZE * 3 / 2, + end_marker: EndMarkerConfig::None, + ..Default::default() + }; + let tx_data = generate_tx_data(&conf); + + let mut tx_enc = CopyEncoder::new(&tx_data); + + // Most importantly, this should not hang indefinitely due to the missing end marker and + // re-transmitting the channel RAM content over and over again. + assert!( + matches!( + tx_channel.transmit(&mut tx_enc).await, + Err(Error::EndMarkerMissing) + ), + "Expected transmit to return an error without end marker when wrapping" + ); } #[test] @@ -821,8 +959,9 @@ mod tests { .unwrap(); let tx_data = generate_tx_data(&conf); + let mut tx_enc = CopyEncoder::new(&tx_data); - ch0.transmit(&tx_data).await.unwrap(); + ch0.transmit(&mut tx_enc).await.unwrap(); } } @@ -952,8 +1091,6 @@ mod tests { #[cfg(not(esp32))] fn rmt_loopback_continuous_tx_impl(mut ctx: Context, use_autostop: bool) { - use esp_hal::rmt::LoopMode; - const TX_COUNT: usize = 10; const MAX_LOOP_COUNT: usize = 3; const MAX_RX_LEN: usize = (MAX_LOOP_COUNT + 1) * TX_COUNT; @@ -979,11 +1116,13 @@ mod tests { if use_autostop { loopmode = LoopMode::Finite(loopcount as u16); }; + + let mut tx_enc = CopyEncoder::new(&tx_data); rx_data.fill(PulseCode::default()); let rx_transaction = rx_channel.reborrow().receive(&mut rx_data).unwrap(); let tx_transaction = tx_channel .reborrow() - .transmit_continuously(&tx_data, loopmode) + .transmit_continuously(&mut tx_enc, loopmode) .unwrap(); // All data is small enough to fit a single hardware buffer, so we don't need to poll @@ -1046,7 +1185,7 @@ mod tests { #[cfg(any(esp32c6, esp32h2, esp32s3))] #[test] fn rmt_continuous_tx_zero_loopcount(mut ctx: Context) { - use esp_hal::{rmt::LoopMode, time::Instant}; + use esp_hal::time::Instant; let conf = LoopbackConfig { idle_output: true, @@ -1063,12 +1202,13 @@ mod tests { PulseCode::new(Level::High, 10_000, Level::Low, 10_000), PulseCode::end_marker(), ]; + let mut tx_enc = CopyEncoder::new(&tx_data); let start = Instant::now(); let tx_transaction = tx_channel .reborrow() - .transmit_continuously(&tx_data, LoopMode::Finite(0)) + .transmit_continuously(&mut tx_enc, LoopMode::Finite(0)) .unwrap(); while !tx_transaction.is_loopcount_interrupt_set() {} @@ -1082,4 +1222,115 @@ mod tests { "tx with loopcount 0 did not complete immediately" ); } + + #[test] + fn rmt_iter_encoder(mut ctx: Context) { + // Ensure tx wraps, such that there are several calls to the encoder + let conf = LoopbackConfig { + tx_len: CHANNEL_RAM_SIZE * 3 / 2, + tx_memsize: 1, + rx_memsize: 2, + ..Default::default() + }; + + let (tx_channel, rx_channel) = ctx.setup_loopback(&conf); + + let tx_data = generate_tx_data(&conf); + let mut rx_data = vec![PulseCode::default(); conf.tx_len]; + + let mut tx_enc = IterEncoder::new(tx_data.iter().copied()); + let rx_transaction = rx_channel.receive(&mut rx_data); + let mut tx_transaction = tx_channel.transmit(&mut tx_enc).unwrap(); + + let rx_res = match rx_transaction { + Ok(mut rx_transaction) => { + // ... poll them until completion. + loop { + let tx_done = tx_transaction.poll(); + let rx_done = rx_transaction.poll(); + if tx_done && rx_done { + break; + } + } + + tx_transaction.wait().unwrap(); + match rx_transaction.wait() { + Ok((rx_count, _channel)) => Ok(rx_count), + Err((err, _channel)) => Err(err), + } + } + Err((e, _)) => Err(e), + }; + + check_data_eq(&conf, &tx_data, &rx_data, rx_res); + } + + fn rmt_bytes_encoder_impl(ctx: &mut Context, bit_order: B) { + const LEN: usize = 4; + const PULSE_ONE: PulseCode = PulseCode::new(Level::High, 150, Level::Low, 50); + const PULSE_ZERO: PulseCode = PulseCode::new(Level::High, 300, Level::Low, 50); + + // Ensure tx wraps, such that there are several calls to the encoder + let conf = LoopbackConfig { + tx_len: CHANNEL_RAM_SIZE * 3 / 2, + tx_memsize: 1, + rx_memsize: 2, + ..Default::default() + }; + + // Pulse that exceeds idle threshold + end marker in one PulseCode + let pulse_end = PulseCode::new(Level::High, 2 * conf.idle_threshold, Level::Low, 0); + + let (tx_channel, rx_channel) = ctx.setup_loopback(&conf); + + let tx_data: [u8; LEN] = [0xAA, 0x55, 0xC3, 0x3C]; + let mut tx_enc = BytesEncoder::new(&tx_data, bit_order, PULSE_ONE, PULSE_ZERO, pulse_end); + + let mut expected_rx_data: Vec<_> = tx_data + .iter() + .flat_map(|&byte| { + (0..8).map(move |i| { + if B::get_bit(byte, i) { + PULSE_ONE + } else { + PULSE_ZERO + } + }) + }) + .collect(); + expected_rx_data.push(pulse_end); + + let mut rx_data = [PulseCode::default(); const { LEN * 8 + 1 }]; + + let rx_transaction = rx_channel.receive(&mut rx_data); + let mut tx_transaction = tx_channel.transmit(&mut tx_enc).unwrap(); + + let rx_res = match rx_transaction { + Ok(mut rx_transaction) => { + // ... poll them until completion. + loop { + let tx_done = tx_transaction.poll(); + let rx_done = rx_transaction.poll(); + if tx_done && rx_done { + break; + } + } + + tx_transaction.wait().unwrap(); + match rx_transaction.wait() { + Ok((rx_count, _channel)) => Ok(rx_count), + Err((err, _channel)) => Err(err), + } + } + Err((e, _)) => Err(e), + }; + + check_data_eq(&conf, &expected_rx_data, &rx_data, rx_res); + } + + #[test] + fn rmt_bytes_encoder(mut ctx: Context) { + rmt_bytes_encoder_impl(&mut ctx, MsbFirst); + rmt_bytes_encoder_impl(&mut ctx, LsbFirst); + } }