diff --git a/esp-hal/CHANGELOG.md b/esp-hal/CHANGELOG.md index 11668bf5c40..fac9bee32ee 100644 --- a/esp-hal/CHANGELOG.md +++ b/esp-hal/CHANGELOG.md @@ -138,6 +138,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `AnyPin::downcast`/`AnyPeripheral::downcast` to allow retrieving the original GPIO/peripheral type (#3783, #3784) - Add `ESP_HAL_CONFIG_PLACE_RMT_DRIVER_IN_RAM` configuration option to pin the RMT driver in RAM (#3778) - The `rt` feature (#3706) +- Added `DmaTxStreamBuf` (#3639) ### Changed @@ -157,6 +158,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Adjusted ESP32-S2 deep-sleep to hibernate for the Ext1WakeupSource (#3785) - Libraries depending on esp-hal should now disable default features, so that only the final binary crate enables the `rt` feature (#3706) - Changed `interrupt::RESERVED_INTERRUPTS` from `&[usize]` to `&[u32]` (#3798) +- Migrate the I2S driver to a move based API (#3639) ### Fixed diff --git a/esp-hal/src/dma/buffers.rs b/esp-hal/src/dma/buffers.rs index edfbfaca3b6..87fde57f242 100644 --- a/esp-hal/src/dma/buffers.rs +++ b/esp-hal/src/dma/buffers.rs @@ -1429,6 +1429,231 @@ impl DmaRxStreamBufView { } } +/// DMA Streaming Transmit Buffer. +/// +/// This is symmetric implementation to [DmaRxStreamBuf], used for continuously +/// streaming data to a peripheral's FIFO. +/// +/// The list starts out like so `A(full) -> B(full) -> C(full) -> D(full) -> NULL`. +/// +/// As the DMA writes to FIFO, the list progresses like so: +/// - `A(full) -> B(full) -> C(full) -> D(full) -> NULL` +/// - `A(empty) -> B(full) -> C(full) -> D(full) -> NULL` +/// - `A(empty) -> B(empty) -> C(full) -> D(full) -> NULL` +/// - `A(empty) -> B(empty) -> C(empty) -> D(full) -> NULL` +/// +/// As you call [DmaTxStreamBufView::push] the list (approximately) progresses like so: +/// - `A(empty) -> B(empty) -> C(empty) -> D(full) -> NULL` +/// - `B(empty) -> C(empty) -> D(full) -> A(full) -> NULL` +/// - `C(empty) -> D(full) -> A(full) -> B(full) -> NULL` +/// - `D(full) -> A(full) -> B(full) -> C(full) -> NULL` +/// +/// If all the descriptors run out, the [DmaTxInterrupt::TotalEof] interrupt will fire and DMA +/// will stop writing, at which point it is up to you to resume/restart the transfer. +pub struct DmaTxStreamBuf { + descriptors: &'static mut [DmaDescriptor], + buffer: &'static mut [u8], + burst: BurstConfig, + pre_filled: Option, +} + +impl DmaTxStreamBuf { + /// Creates a new [DmaTxStreamBuf] evenly distributing the buffer between + /// the provided descriptors. + pub fn new( + descriptors: &'static mut [DmaDescriptor], + buffer: &'static mut [u8], + ) -> Result { + match () { + _ if !is_slice_in_dram(descriptors) => Err(DmaBufError::UnsupportedMemoryRegion)?, + _ if !is_slice_in_dram(buffer) => Err(DmaBufError::UnsupportedMemoryRegion)?, + _ if descriptors.is_empty() => Err(DmaBufError::InsufficientDescriptors)?, + _ => (), + }; + + // Evenly distribute the buffer between the descriptors. + let chunk_size = Some(buffer.len() / descriptors.len()) + .filter(|x| *x <= 4095) + .ok_or(DmaBufError::InsufficientDescriptors)?; + + let mut chunks = buffer.chunks_exact_mut(chunk_size); + for (desc, chunk) in descriptors.iter_mut().zip(chunks.by_ref()) { + desc.buffer = chunk.as_mut_ptr(); + desc.set_size(chunk.len()); + } + let remainder = chunks.into_remainder(); + + if !remainder.is_empty() { + // Append any excess to the last descriptor. + let last_descriptor = descriptors.last_mut().unwrap(); + let size = last_descriptor.size() + remainder.len(); + if size > 4095 { + Err(DmaBufError::InsufficientDescriptors)?; + } + last_descriptor.set_size(size); + } + + Ok(Self { + descriptors, + buffer, + burst: Default::default(), + pre_filled: None, + }) + } + + /// Consume the buf, returning the descriptors and buffer. + pub fn split(self) -> (&'static mut [DmaDescriptor], &'static mut [u8]) { + (self.descriptors, self.buffer) + } + + /// Push the buffer with the given data before DMA transfer starts. + /// + /// Otherwise the streaming buffer will transfer garbage data to + /// DMA so that CPU has enough time to fill the buffer after transfer starts. + pub fn push(&mut self, data: &[u8]) -> usize { + self.push_with(|buf| { + let len = buf.len().min(data.len()); + buf[..len].copy_from_slice(&data[..len]); + len + }) + } + + /// Push the buffer with the given data before DMA transfer starts. + /// + /// Returns the number of bytes filled. + pub fn push_with(&mut self, f: impl FnOnce(&mut [u8]) -> usize) -> usize { + let start = self.pre_filled.unwrap_or(0); + let bytes_pushed = f(&mut self.buffer[start..]); + self.pre_filled = Some(start + bytes_pushed); + bytes_pushed + } +} + +unsafe impl DmaTxBuffer for DmaTxStreamBuf { + type View = DmaTxStreamBufView; + type Final = Self; + + fn prepare(&mut self) -> Preparation { + // Link up all the descriptors (but not in a circle). + let mut next = null_mut(); + for desc in self.descriptors.iter_mut().rev() { + desc.next = next; + next = desc; + + desc.set_suc_eof(false); + } + Preparation { + start: self.descriptors.as_mut_ptr(), + direction: TransferDirection::Out, + #[cfg(psram_dma)] + accesses_psram: false, + burst_transfer: self.burst, + + // Whilst we give ownership of the descriptors the DMA, the correctness of this buffer + // implementation doesn't rely on the DMA checking for descriptor ownership. + // No descriptor is added back to the end of the stream before it's ready for the DMA + // to consume it. + check_owner: None, + auto_write_back: true, + } + } + + fn into_view(self) -> Self::View { + // If the buffer is not pre-filled, treat it as fully filled + let pre_filled = self.pre_filled.unwrap_or(self.buffer.len()); + let mut view = DmaTxStreamBufView { + buf: self, + descriptor_idx: 0, + descriptor_offset: 0, + }; + view.advance(pre_filled); + view + } + + fn from_view(view: Self::View) -> Self { + view.buf + } +} + +/// A view into a [DmaRxStreamBuf] +pub struct DmaTxStreamBufView { + buf: DmaTxStreamBuf, + descriptor_idx: usize, + descriptor_offset: usize, +} + +impl DmaTxStreamBufView { + /// Returns the number of bytes available for writing. + pub fn available_bytes(&self) -> usize { + let (tail, head) = self.buf.descriptors.split_at(self.descriptor_idx); + head.iter() + .chain(tail) + .take_while(|d| d.owner() == Owner::Cpu) + .map(|d| d.size()) + .sum::() + - self.descriptor_offset + } + + /// Pushes a buffer into the stream buffer. + /// Returns the number of bytes pushed. + pub fn push_with(&mut self, f: impl FnOnce(&mut [u8]) -> usize) -> usize { + let chunk_size = self.buf.descriptors[0].size(); + let dma_start = self.descriptor_idx * chunk_size + self.descriptor_offset; + let dma_end = (dma_start + self.available_bytes()).min(self.buf.buffer.len()); + let bytes_pushed = f(&mut self.buf.buffer[dma_start..dma_end]); + + self.advance(bytes_pushed); + bytes_pushed + } + + /// Advances the first `n` bytes from the available data + pub fn advance(&mut self, bytes_pushed: usize) { + let mut bytes_filled = 0; + for d in (self.descriptor_idx..self.buf.descriptors.len()).chain(core::iter::once(0)) { + let desc = &mut self.buf.descriptors[d]; + let bytes_in_d = desc.size() - self.descriptor_offset; + // There is at least one byte left in `desc`. + if bytes_in_d + bytes_filled > bytes_pushed { + self.descriptor_idx = d; + self.descriptor_offset = self.descriptor_offset + bytes_pushed - bytes_filled; + break; + } + bytes_filled += bytes_in_d; + self.descriptor_offset = 0; + + // Put the current descriptor at the end of the list + desc.set_owner(Owner::Dma); + desc.set_length(desc.size()); + desc.set_suc_eof(true); + let p = d.checked_sub(1).unwrap_or(self.buf.descriptors.len() - 1); + if p != d { + let [prev, desc] = self.buf.descriptors.get_disjoint_mut([p, d]).unwrap(); + desc.next = null_mut(); + prev.next = desc; + prev.set_suc_eof(false); + } + } + } + + /// Pushes a buffer into the stream buffer. + /// Returns the number of bytes pushed. + pub fn push(&mut self, data: &[u8]) -> usize { + let total_len = data.len(); + let mut remaining = data; + + while self.available_bytes() >= remaining.len() && !remaining.is_empty() { + let written = self.push_with(|buffer| { + let len = usize::min(buffer.len(), remaining.len()); + buffer[..len].copy_from_slice(&remaining[..len]); + len + }); + remaining = &remaining[written..]; + } + + total_len - remaining.len() + } +} + static mut EMPTY: [DmaDescriptor; 1] = [DmaDescriptor::EMPTY]; /// An empty buffer that can be used when you don't need to transfer any data. diff --git a/esp-hal/src/dma/mod.rs b/esp-hal/src/dma/mod.rs index 40637a6adf2..b90327d1c0a 100644 --- a/esp-hal/src/dma/mod.rs +++ b/esp-hal/src/dma/mod.rs @@ -77,147 +77,6 @@ use crate::{ system::Cpu, }; -trait Word: crate::private::Sealed {} - -macro_rules! impl_word { - ($w:ty) => { - impl $crate::private::Sealed for $w {} - impl Word for $w {} - }; -} - -impl_word!(u8); -impl_word!(u16); -impl_word!(u32); -impl_word!(i8); -impl_word!(i16); -impl_word!(i32); - -impl crate::private::Sealed for [W; S] where W: Word {} - -impl crate::private::Sealed for &[W; S] where W: Word {} - -impl crate::private::Sealed for &[W] where W: Word {} - -impl crate::private::Sealed for &mut [W] where W: Word {} - -/// Trait for buffers that can be given to DMA for reading. -/// -/// # Safety -/// -/// Once the `read_buffer` method has been called, it is unsafe to call any -/// `&mut self` methods on this object as long as the returned value is in use -/// (by DMA). -pub unsafe trait ReadBuffer { - /// Provide a buffer usable for DMA reads. - /// - /// The return value is: - /// - /// - pointer to the start of the buffer - /// - buffer size in bytes - /// - /// # Safety - /// - /// Once this method has been called, it is unsafe to call any `&mut self` - /// methods on this object as long as the returned value is in use (by DMA). - unsafe fn read_buffer(&self) -> (*const u8, usize); -} - -unsafe impl ReadBuffer for [W; S] -where - W: Word, -{ - unsafe fn read_buffer(&self) -> (*const u8, usize) { - (self.as_ptr() as *const u8, core::mem::size_of_val(self)) - } -} - -unsafe impl ReadBuffer for &[W; S] -where - W: Word, -{ - unsafe fn read_buffer(&self) -> (*const u8, usize) { - (self.as_ptr() as *const u8, core::mem::size_of_val(*self)) - } -} - -unsafe impl ReadBuffer for &mut [W; S] -where - W: Word, -{ - unsafe fn read_buffer(&self) -> (*const u8, usize) { - (self.as_ptr() as *const u8, core::mem::size_of_val(*self)) - } -} - -unsafe impl ReadBuffer for &[W] -where - W: Word, -{ - unsafe fn read_buffer(&self) -> (*const u8, usize) { - (self.as_ptr() as *const u8, core::mem::size_of_val(*self)) - } -} - -unsafe impl ReadBuffer for &mut [W] -where - W: Word, -{ - unsafe fn read_buffer(&self) -> (*const u8, usize) { - (self.as_ptr() as *const u8, core::mem::size_of_val(*self)) - } -} - -/// Trait for buffers that can be given to DMA for writing. -/// -/// # Safety -/// -/// Once the `write_buffer` method has been called, it is unsafe to call any -/// `&mut self` methods, except for `write_buffer`, on this object as long as -/// the returned value is in use (by DMA). -pub unsafe trait WriteBuffer { - /// Provide a buffer usable for DMA writes. - /// - /// The return value is: - /// - /// - pointer to the start of the buffer - /// - buffer size in bytes - /// - /// # Safety - /// - /// Once this method has been called, it is unsafe to call any `&mut self` - /// methods, except for `write_buffer`, on this object as long as the - /// returned value is in use (by DMA). - unsafe fn write_buffer(&mut self) -> (*mut u8, usize); -} - -unsafe impl WriteBuffer for [W; S] -where - W: Word, -{ - unsafe fn write_buffer(&mut self) -> (*mut u8, usize) { - (self.as_mut_ptr() as *mut u8, core::mem::size_of_val(self)) - } -} - -unsafe impl WriteBuffer for &mut [W; S] -where - W: Word, -{ - unsafe fn write_buffer(&mut self) -> (*mut u8, usize) { - (self.as_mut_ptr() as *mut u8, core::mem::size_of_val(*self)) - } -} - -unsafe impl WriteBuffer for &mut [W] -where - W: Word, -{ - unsafe fn write_buffer(&mut self) -> (*mut u8, usize) { - (self.as_mut_ptr() as *mut u8, core::mem::size_of_val(*self)) - } -} - bitfield::bitfield! { /// DMA descriptor flags. #[derive(Clone, Copy, PartialEq, Eq)] @@ -775,6 +634,36 @@ macro_rules! dma_rx_stream_buffer { }}; } +#[procmacros::doc_replace] +/// Convenience macro to create a [DmaTxStreamBuf] from buffer size and +/// optional chunk size (uses max if unspecified). +/// The buffer and descriptors are statically allocated and +/// used to create the [DmaTxStreamBuf]. +/// +/// Smaller chunk sizes are recommended for lower latency. +/// +/// ## Usage +/// ```rust,no_run +/// # {before_snippet} +/// use esp_hal::dma_tx_stream_buffer; +/// +/// let buf = dma_tx_stream_buffer!(32000); +/// let buf = dma_tx_stream_buffer!(32000, 1000); +/// # {after_snippet} +/// ``` +#[macro_export] +macro_rules! dma_tx_stream_buffer { + ($tx_size:expr) => { + $crate::dma_tx_stream_buffer!($tx_size, 4095) + }; + ($tx_size:expr, $chunk_size:expr) => {{ + let (buffer, descriptors) = + $crate::dma_buffers_impl!($tx_size, $chunk_size, is_circular = false); + + $crate::dma::DmaTxStreamBuf::new(descriptors, buffer).unwrap() + }}; +} + #[procmacros::doc_replace] /// Convenience macro to create a [DmaLoopBuf] from a buffer size. /// @@ -1290,274 +1179,6 @@ impl From for DmaExtMemBKSize { } } -pub(crate) struct TxCircularState { - write_offset: usize, - write_descr_ptr: *mut DmaDescriptor, - pub(crate) available: usize, - last_seen_handled_descriptor_ptr: *mut DmaDescriptor, - buffer_start: *const u8, - buffer_len: usize, - - first_desc_ptr: *mut DmaDescriptor, -} - -impl TxCircularState { - pub(crate) fn new(chain: &mut DescriptorChain) -> Self { - Self { - write_offset: 0, - write_descr_ptr: chain.first_mut(), - available: 0, - last_seen_handled_descriptor_ptr: chain.first_mut(), - buffer_start: chain.descriptors[0].buffer as _, - buffer_len: chain.descriptors.iter().map(|d| d.len()).sum(), - - first_desc_ptr: chain.first_mut(), - } - } - - pub(crate) fn update(&mut self, channel: &ChannelTx) -> Result<(), DmaError> - where - Dm: DriverMode, - CH: DmaTxChannel, - { - if channel - .pending_out_interrupts() - .contains(DmaTxInterrupt::Eof) - { - channel.clear_out(DmaTxInterrupt::Eof); - - // check if all descriptors are owned by CPU - this indicates we failed to push - // data fast enough in future we can enable `check_owner` and check - // the interrupt instead - let mut current = self.last_seen_handled_descriptor_ptr; - loop { - let descr = unsafe { current.read_volatile() }; - if descr.owner() == Owner::Cpu { - current = descr.next; - } else { - break; - } - - if core::ptr::eq(current, self.last_seen_handled_descriptor_ptr) { - return Err(DmaError::Late); - } - } - - let descr_address = channel.last_out_dscr_address() as *mut DmaDescriptor; - - let mut ptr = self.last_seen_handled_descriptor_ptr; - if descr_address >= self.last_seen_handled_descriptor_ptr { - unsafe { - while ptr < descr_address { - let dw0 = ptr.read_volatile(); - self.available += dw0.len(); - ptr = ptr.offset(1); - } - } - } else { - unsafe { - while !((*ptr).next.is_null() - || core::ptr::eq((*ptr).next, self.first_desc_ptr)) - { - let dw0 = ptr.read_volatile(); - self.available += dw0.len(); - ptr = ptr.offset(1); - } - - // add bytes pointed to by the last descriptor - let dw0 = ptr.read_volatile(); - self.available += dw0.len(); - - // in circular mode we need to honor the now available bytes at start - if core::ptr::eq((*ptr).next, self.first_desc_ptr) { - ptr = self.first_desc_ptr; - while ptr < descr_address { - let dw0 = ptr.read_volatile(); - self.available += dw0.len(); - ptr = ptr.offset(1); - } - } - } - } - - if self.available >= self.buffer_len { - unsafe { - let dw0 = self.write_descr_ptr.read_volatile(); - let segment_len = dw0.len(); - let next_descriptor = dw0.next; - self.available -= segment_len; - self.write_offset = (self.write_offset + segment_len) % self.buffer_len; - - self.write_descr_ptr = if next_descriptor.is_null() { - self.first_desc_ptr - } else { - next_descriptor - } - } - } - - self.last_seen_handled_descriptor_ptr = descr_address; - } - - Ok(()) - } - - pub(crate) fn push(&mut self, data: &[u8]) -> Result { - let avail = self.available; - - if avail < data.len() { - return Err(DmaError::Overflow); - } - - let mut remaining = data.len(); - let mut offset = 0; - while self.available >= remaining && remaining > 0 { - let written = self.push_with(|buffer| { - let len = usize::min(buffer.len(), data.len() - offset); - buffer[..len].copy_from_slice(&data[offset..][..len]); - len - })?; - offset += written; - remaining -= written; - } - - Ok(data.len()) - } - - pub(crate) fn push_with( - &mut self, - f: impl FnOnce(&mut [u8]) -> usize, - ) -> Result { - // this might write less than available in case of a wrap around - // caller needs to check and write the remaining part - let written = unsafe { - let dst = self.buffer_start.add(self.write_offset).cast_mut(); - let block_size = usize::min(self.available, self.buffer_len - self.write_offset); - let buffer = core::slice::from_raw_parts_mut(dst, block_size); - f(buffer) - }; - - let mut forward = written; - loop { - unsafe { - let mut descr = self.write_descr_ptr.read_volatile(); - descr.set_owner(Owner::Dma); - self.write_descr_ptr.write_volatile(descr); - - let segment_len = descr.len(); - self.write_descr_ptr = if descr.next.is_null() { - self.first_desc_ptr - } else { - descr.next - }; - - if forward <= segment_len { - break; - } - - forward -= segment_len; - } - } - - self.write_offset = (self.write_offset + written) % self.buffer_len; - self.available -= written; - - Ok(written) - } -} - -pub(crate) struct RxCircularState { - read_descr_ptr: *mut DmaDescriptor, - pub(crate) available: usize, - last_seen_handled_descriptor_ptr: *mut DmaDescriptor, - last_descr_ptr: *mut DmaDescriptor, -} - -impl RxCircularState { - pub(crate) fn new(chain: &mut DescriptorChain) -> Self { - Self { - read_descr_ptr: chain.first_mut(), - available: 0, - last_seen_handled_descriptor_ptr: core::ptr::null_mut(), - last_descr_ptr: chain.last_mut(), - } - } - - pub(crate) fn update(&mut self) -> Result<(), DmaError> { - if self.last_seen_handled_descriptor_ptr.is_null() { - // initially start at last descriptor (so that next will be the first - // descriptor) - self.last_seen_handled_descriptor_ptr = self.last_descr_ptr; - } - - let mut current_in_descr_ptr = - unsafe { self.last_seen_handled_descriptor_ptr.read_volatile() }.next; - let mut current_in_descr = unsafe { current_in_descr_ptr.read_volatile() }; - - let last_seen_ptr = self.last_seen_handled_descriptor_ptr; - while current_in_descr.owner() == Owner::Cpu { - self.available += current_in_descr.len(); - self.last_seen_handled_descriptor_ptr = current_in_descr_ptr; - - current_in_descr_ptr = - unsafe { self.last_seen_handled_descriptor_ptr.read_volatile() }.next; - current_in_descr = unsafe { current_in_descr_ptr.read_volatile() }; - - if core::ptr::eq(current_in_descr_ptr, last_seen_ptr) { - return Err(DmaError::Late); - } - } - - Ok(()) - } - - pub(crate) fn pop(&mut self, data: &mut [u8]) -> Result { - let len = data.len(); - let mut avail = self.available; - - if avail > len { - return Err(DmaError::BufferTooSmall); - } - - let mut remaining_buffer = data; - let mut descr_ptr = self.read_descr_ptr; - - if descr_ptr.is_null() { - return Ok(0); - } - - let mut descr = unsafe { descr_ptr.read_volatile() }; - - while avail > 0 && !remaining_buffer.is_empty() && remaining_buffer.len() >= descr.len() { - unsafe { - let dst = remaining_buffer.as_mut_ptr(); - let src = descr.buffer; - let count = descr.len(); - core::ptr::copy_nonoverlapping(src, dst, count); - - descr.set_owner(Owner::Dma); - descr.set_suc_eof(false); - descr.set_length(0); - descr_ptr.write_volatile(descr); - - remaining_buffer = &mut remaining_buffer[count..]; - avail -= count; - descr_ptr = descr.next; - } - - if descr_ptr.is_null() { - break; - } - - descr = unsafe { descr_ptr.read_volatile() }; - } - - self.read_descr_ptr = descr_ptr; - self.available = avail; - Ok(len - remaining_buffer.len()) - } -} - #[doc(hidden)] macro_rules! impl_dma_eligible { ([$dma_ch:ident] $name:ident => $dma:ident) => { @@ -2486,345 +2107,6 @@ impl From> for Channel { } } -pub(crate) mod dma_private { - use super::*; - - pub trait DmaSupport { - type DriverMode: DriverMode; - - /// Wait until the transfer is done. - /// - /// Depending on the peripheral this might include checking the DMA - /// channel and/or the peripheral. - /// - /// After this all data should be processed by the peripheral - i.e. the - /// peripheral should have processed it's FIFO(s) - /// - /// Please note: This is called in the transfer's `wait` function _and_ - /// by it's [Drop] implementation. - fn peripheral_wait_dma(&mut self, is_rx: bool, is_tx: bool); - - /// Only used by circular DMA transfers in both, the `stop` function - /// _and_ it's [Drop] implementation - fn peripheral_dma_stop(&mut self); - } - - #[instability::unstable] - pub trait DmaSupportTx: DmaSupport { - type Channel: DmaTxChannel; - - fn tx(&mut self) -> &mut ChannelTx; - - fn chain(&mut self) -> &mut DescriptorChain; - } - - #[instability::unstable] - pub trait DmaSupportRx: DmaSupport { - type Channel: DmaRxChannel; - - fn rx(&mut self) -> &mut ChannelRx; - - fn chain(&mut self) -> &mut DescriptorChain; - } -} - -/// DMA transaction for TX only transfers -/// -/// # Safety -/// -/// Never use [core::mem::forget] on an in-progress transfer -#[non_exhaustive] -#[must_use] -pub struct DmaTransferTx<'a, I> -where - I: dma_private::DmaSupportTx, -{ - instance: &'a mut I, -} - -impl<'a, I> DmaTransferTx<'a, I> -where - I: dma_private::DmaSupportTx, -{ - #[cfg_attr(esp32c2, allow(dead_code))] - pub(crate) fn new(instance: &'a mut I) -> Self { - Self { instance } - } - - /// Wait for the transfer to finish. - pub fn wait(self) -> Result<(), DmaError> { - self.instance.peripheral_wait_dma(false, true); - - if self - .instance - .tx() - .pending_out_interrupts() - .contains(DmaTxInterrupt::DescriptorError) - { - Err(DmaError::DescriptorError) - } else { - Ok(()) - } - } - - /// Check if the transfer is finished. - pub fn is_done(&mut self) -> bool { - self.instance.tx().is_done() - } -} - -impl Drop for DmaTransferTx<'_, I> -where - I: dma_private::DmaSupportTx, -{ - fn drop(&mut self) { - self.instance.peripheral_wait_dma(true, false); - } -} - -/// DMA transaction for RX only transfers -/// -/// # Safety -/// -/// Never use [core::mem::forget] on an in-progress transfer -#[non_exhaustive] -#[must_use] -pub struct DmaTransferRx<'a, I> -where - I: dma_private::DmaSupportRx, -{ - instance: &'a mut I, -} - -impl<'a, I> DmaTransferRx<'a, I> -where - I: dma_private::DmaSupportRx, -{ - #[cfg_attr(esp32c2, allow(dead_code))] - pub(crate) fn new(instance: &'a mut I) -> Self { - Self { instance } - } - - /// Wait for the transfer to finish. - pub fn wait(self) -> Result<(), DmaError> { - self.instance.peripheral_wait_dma(true, false); - - if self - .instance - .rx() - .pending_in_interrupts() - .contains(DmaRxInterrupt::DescriptorError) - { - Err(DmaError::DescriptorError) - } else { - Ok(()) - } - } - - /// Check if the transfer is finished. - pub fn is_done(&mut self) -> bool { - self.instance.rx().is_done() - } -} - -impl Drop for DmaTransferRx<'_, I> -where - I: dma_private::DmaSupportRx, -{ - fn drop(&mut self) { - self.instance.peripheral_wait_dma(true, false); - } -} - -/// DMA transaction for TX+RX transfers -/// -/// # Safety -/// -/// Never use [core::mem::forget] on an in-progress transfer -#[non_exhaustive] -#[must_use] -pub struct DmaTransferRxTx<'a, I> -where - I: dma_private::DmaSupportTx + dma_private::DmaSupportRx, -{ - instance: &'a mut I, -} - -impl<'a, I> DmaTransferRxTx<'a, I> -where - I: dma_private::DmaSupportTx + dma_private::DmaSupportRx, -{ - #[allow(dead_code)] - pub(crate) fn new(instance: &'a mut I) -> Self { - Self { instance } - } - - /// Wait for the transfer to finish. - pub fn wait(self) -> Result<(), DmaError> { - self.instance.peripheral_wait_dma(true, true); - - if self - .instance - .tx() - .pending_out_interrupts() - .contains(DmaTxInterrupt::DescriptorError) - || self - .instance - .rx() - .pending_in_interrupts() - .contains(DmaRxInterrupt::DescriptorError) - { - Err(DmaError::DescriptorError) - } else { - Ok(()) - } - } - - /// Check if the transfer is finished. - pub fn is_done(&mut self) -> bool { - self.instance.tx().is_done() && self.instance.rx().is_done() - } -} - -impl Drop for DmaTransferRxTx<'_, I> -where - I: dma_private::DmaSupportTx + dma_private::DmaSupportRx, -{ - fn drop(&mut self) { - self.instance.peripheral_wait_dma(true, true); - } -} - -/// DMA transaction for TX only circular transfers -/// -/// # Safety -/// -/// Never use [core::mem::forget] on an in-progress transfer -#[non_exhaustive] -#[must_use] -pub struct DmaTransferTxCircular<'a, I> -where - I: dma_private::DmaSupportTx, -{ - instance: &'a mut I, - state: TxCircularState, -} - -impl<'a, I> DmaTransferTxCircular<'a, I> -where - I: dma_private::DmaSupportTx, -{ - #[allow(unused)] // currently used by peripherals not available on all chips - pub(crate) fn new(instance: &'a mut I) -> Self { - let state = TxCircularState::new(instance.chain()); - Self { instance, state } - } - - /// Amount of bytes which can be pushed. - pub fn available(&mut self) -> Result { - self.state.update(self.instance.tx())?; - Ok(self.state.available) - } - - /// Push bytes into the DMA buffer. - pub fn push(&mut self, data: &[u8]) -> Result { - self.state.update(self.instance.tx())?; - self.state.push(data) - } - - /// Push bytes into the DMA buffer via the given closure. - /// The closure *must* return the actual number of bytes written. - /// The closure *might* get called with a slice which is smaller than the - /// total available buffer. - pub fn push_with(&mut self, f: impl FnOnce(&mut [u8]) -> usize) -> Result { - self.state.update(self.instance.tx())?; - self.state.push_with(f) - } - - /// Stop the DMA transfer - #[allow(clippy::type_complexity)] - pub fn stop(self) -> Result<(), DmaError> { - self.instance.peripheral_dma_stop(); - - if self - .instance - .tx() - .pending_out_interrupts() - .contains(DmaTxInterrupt::DescriptorError) - { - Err(DmaError::DescriptorError) - } else { - Ok(()) - } - } -} - -impl Drop for DmaTransferTxCircular<'_, I> -where - I: dma_private::DmaSupportTx, -{ - fn drop(&mut self) { - self.instance.peripheral_dma_stop(); - } -} - -/// DMA transaction for RX only circular transfers -/// -/// # Safety -/// -/// Never use [core::mem::forget] on an in-progress transfer -#[non_exhaustive] -#[must_use] -pub struct DmaTransferRxCircular<'a, I> -where - I: dma_private::DmaSupportRx, -{ - instance: &'a mut I, - state: RxCircularState, -} - -impl<'a, I> DmaTransferRxCircular<'a, I> -where - I: dma_private::DmaSupportRx, -{ - #[allow(unused)] // currently used by peripherals not available on all chips - pub(crate) fn new(instance: &'a mut I) -> Self { - let state = RxCircularState::new(instance.chain()); - Self { instance, state } - } - - /// Amount of bytes which can be popped. - /// - /// It's expected to call this before trying to [DmaTransferRxCircular::pop] - /// data. - pub fn available(&mut self) -> Result { - self.state.update()?; - Ok(self.state.available) - } - - /// Get available data. - /// - /// It's expected that the amount of available data is checked before by - /// calling [DmaTransferRxCircular::available] and that the buffer can hold - /// all available data. - /// - /// Fails with [DmaError::BufferTooSmall] if the given buffer is too small - /// to hold all available data - pub fn pop(&mut self, data: &mut [u8]) -> Result { - self.state.update()?; - self.state.pop(data) - } -} - -impl Drop for DmaTransferRxCircular<'_, I> -where - I: dma_private::DmaSupportRx, -{ - fn drop(&mut self) { - self.instance.peripheral_dma_stop(); - } -} - pub(crate) mod asynch { use core::task::Poll; @@ -2842,7 +2124,7 @@ pub(crate) mod asynch { where CH: DmaTxChannel, { - #[cfg_attr(esp32c2, allow(dead_code))] + #[cfg_attr(not(esp32), allow(dead_code))] pub fn new(tx: &'a mut ChannelTx) -> Self { Self { tx } } @@ -2987,6 +2269,13 @@ pub(crate) mod asynch { { self.tx.clear_out(DmaTxInterrupt::Done); Poll::Ready(Ok(())) + } else if self + .tx + .pending_out_interrupts() + .contains(DmaTxInterrupt::TotalEof) + { + self.tx.clear_interrupts(); + return Poll::Ready(Err(DmaError::Late)); } else if self .tx .pending_out_interrupts() @@ -2996,8 +2285,11 @@ pub(crate) mod asynch { Poll::Ready(Err(DmaError::DescriptorError)) } else { self.tx.waker().register(cx.waker()); - self.tx - .listen_out(DmaTxInterrupt::Done | DmaTxInterrupt::DescriptorError); + self.tx.listen_out( + DmaTxInterrupt::Done + | DmaTxInterrupt::DescriptorError + | DmaTxInterrupt::TotalEof, + ); Poll::Pending } } diff --git a/esp-hal/src/i2s/master.rs b/esp-hal/src/i2s/master.rs index a4d2b3d2954..3531522db2e 100644 --- a/esp-hal/src/i2s/master.rs +++ b/esp-hal/src/i2s/master.rs @@ -76,9 +76,9 @@ //! ```rust, no_run //! # {before_snippet} //! # use esp_hal::i2s::master::{I2s, Channels, DataFormat, Config}; -//! # use esp_hal::dma_buffers; +//! # use esp_hal::dma_rx_stream_buffer; //! # {dma_channel} -//! let (mut rx_buffer, rx_descriptors, _, _) = dma_buffers!(4 * 4092, 0); +//! let rx_buffer = dma_rx_stream_buffer!(4 * 4092); //! //! let i2s = I2s::new( //! peripherals.I2S0, @@ -94,16 +94,16 @@ //! .with_bclk(peripherals.GPIO1) //! .with_ws(peripherals.GPIO2) //! .with_din(peripherals.GPIO5) -//! .build(rx_descriptors); +//! .build(); //! -//! let mut transfer = i2s_rx.read_dma_circular(&mut rx_buffer)?; +//! let mut transfer = i2s_rx.read(rx_buffer, 4092)?; //! //! loop { -//! let avail = transfer.available()?; +//! let avail = transfer.available_bytes(); //! //! if avail > 0 { //! let mut rcv = [0u8; 5000]; -//! transfer.pop(&mut rcv[..avail])?; +//! transfer.pop(&mut rcv[..avail]); //! } //! } //! # } @@ -113,6 +113,11 @@ //! //! - Only TDM mode is supported. +use core::{ + mem::ManuallyDrop, + ops::{Deref, DerefMut}, +}; + use enumset::{EnumSet, EnumSetType}; use private::*; @@ -124,19 +129,13 @@ use crate::{ Channel, ChannelRx, ChannelTx, - DescriptorChain, DmaChannelFor, DmaEligible, DmaError, - DmaTransferRx, - DmaTransferRxCircular, - DmaTransferTx, - DmaTransferTxCircular, + DmaRxBuffer, + DmaTxBuffer, PeripheralRxChannel, PeripheralTxChannel, - ReadBuffer, - WriteBuffer, - dma_private::{DmaSupport, DmaSupportRx, DmaSupportTx}, }, gpio::{OutputConfig, interconnect::PeripheralOutput}, i2s::AnyI2s, @@ -169,15 +168,6 @@ pub(crate) const I2S_LL_MCLK_DIVIDER_BIT_WIDTH: usize = 9; pub(crate) const I2S_LL_MCLK_DIVIDER_MAX: usize = (1 << I2S_LL_MCLK_DIVIDER_BIT_WIDTH) - 1; -/// Data types that the I2S peripheral can work with. -pub trait AcceptedWord: crate::private::Sealed {} -impl AcceptedWord for u8 {} -impl AcceptedWord for u16 {} -impl AcceptedWord for u32 {} -impl AcceptedWord for i8 {} -impl AcceptedWord for i16 {} -impl AcceptedWord for i32 {} - /// I2S Error #[derive(Debug, Clone, Copy, PartialEq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] @@ -981,7 +971,6 @@ where { i2s: AnyI2s<'d>, tx_channel: ChannelTx>>, - tx_chain: DescriptorChain, _guard: PeripheralGuard, #[cfg(any(esp32, esp32s2))] data_format: DataFormat, @@ -996,60 +985,15 @@ where } } -impl DmaSupport for I2sTx<'_, Dm> +impl<'d, Dm> I2sTx<'d, Dm> where Dm: DriverMode, { - type DriverMode = Dm; - - fn peripheral_wait_dma(&mut self, _is_rx: bool, _is_tx: bool) { - self.i2s.wait_for_tx_done(); - } - - fn peripheral_dma_stop(&mut self) { - self.i2s.tx_stop(); - } -} - -impl<'d, Dm> DmaSupportTx for I2sTx<'d, Dm> -where - Dm: DriverMode, -{ - type Channel = PeripheralTxChannel>; - - fn tx(&mut self) -> &mut ChannelTx>> { - &mut self.tx_channel - } - - fn chain(&mut self) -> &mut DescriptorChain { - &mut self.tx_chain - } -} - -impl I2sTx<'_, Dm> -where - Dm: DriverMode, -{ - fn write(&mut self, data: &[u8]) -> Result<(), Error> { - self.start_tx_transfer(&data, false)?; - - // wait until I2S_TX_IDLE is 1 - self.i2s.wait_for_tx_done(); - - Ok(()) - } - - fn start_tx_transfer<'t, TXBUF>( - &'t mut self, - words: &'t TXBUF, - circular: bool, - ) -> Result<(), Error> - where - TXBUF: ReadBuffer, - Dm: DriverMode, - { - let (ptr, len) = unsafe { words.read_buffer() }; - + /// Starts a DMA transfer to write data to the I2S transmitter. + pub fn write( + mut self, + mut buf: TXBUF, + ) -> Result, Error> { // Reset TX unit and TX FIFO self.i2s.reset_tx(); @@ -1057,18 +1001,21 @@ where // configure DMA outlink unsafe { - self.tx_chain.fill_for_tx(circular, ptr, len)?; self.tx_channel - .prepare_transfer_without_start(self.i2s.dma_peripheral(), &self.tx_chain) - .and_then(|_| self.tx_channel.start_transfer())?; + .prepare_transfer(self.i2s.dma_peripheral(), &mut buf)?; } + let view = buf.into_view(); + self.tx_channel.start_transfer()?; // set I2S_TX_STOP_EN if needed // start: set I2S_TX_START self.i2s.tx_start(); - Ok(()) + Ok(I2sTxDmaTransfer { + i2s_tx: ManuallyDrop::new(self), + buffer_view: ManuallyDrop::new(view), + }) } /// Change the I2S Tx unit configuration. @@ -1081,40 +1028,6 @@ where } } } - - /// Writes a slice of data to the I2S peripheral. - pub fn write_words(&mut self, words: &[impl AcceptedWord]) -> Result<(), Error> { - self.write(unsafe { - core::slice::from_raw_parts(words.as_ptr().cast::(), core::mem::size_of_val(words)) - }) - } - - /// Write I2S. - /// Returns [DmaTransferTx] which represents the in-progress DMA - /// transfer - pub fn write_dma<'t>( - &'t mut self, - words: &'t impl ReadBuffer, - ) -> Result, Error> - where - Self: DmaSupportTx, - { - self.start_tx_transfer(words, false)?; - Ok(DmaTransferTx::new(self)) - } - - /// Continuously write to I2S. Returns [DmaTransferTxCircular] which - /// represents the in-progress DMA transfer - pub fn write_dma_circular<'t>( - &'t mut self, - words: &'t impl ReadBuffer, - ) -> Result, Error> - where - Self: DmaSupportTx, - { - self.start_tx_transfer(words, true)?; - Ok(DmaTransferTxCircular::new(self)) - } } /// I2S RX channel @@ -1124,7 +1037,6 @@ where { i2s: AnyI2s<'d>, rx_channel: ChannelRx>>, - rx_chain: DescriptorChain, _guard: PeripheralGuard, #[cfg(any(esp32, esp32s2))] data_format: DataFormat, @@ -1139,134 +1051,205 @@ where } } -impl DmaSupport for I2sRx<'_, Dm> +impl<'d, Dm> I2sRx<'d, Dm> where Dm: DriverMode, { - type DriverMode = Dm; + /// Start reading from the I2S RX unit. Notice because of the DMA transfer, + /// the buffer must be aligned to 32-bit. + pub fn read( + mut self, + mut buf: RXBUF, + chunk_size: usize, + ) -> Result, Error> { + // Reset RX unit and RX FIFO + self.i2s.reset_rx(); + + // Enable corresponding interrupts if needed + + // configure DMA inlink + unsafe { + // self.rx_chain.fill_for_rx(circular, ptr, len)?; + self.rx_channel + .prepare_transfer(self.i2s.dma_peripheral(), &mut buf)?; + } + self.rx_channel.start_transfer()?; - fn peripheral_wait_dma(&mut self, _is_rx: bool, _is_tx: bool) { - self.i2s.wait_for_rx_done(); + self.i2s.rx_start(chunk_size); + Ok(I2sRxDmaTransfer { + i2s_rx: ManuallyDrop::new(self), + buffer_view: ManuallyDrop::new(buf.into_view()), + chunk_size, + }) } - fn peripheral_dma_stop(&mut self) { - self.i2s.reset_rx(); + /// Change the I2S Rx unit configuration. + pub fn apply_config(&mut self, rx_config: &UnitConfig) -> Result<(), ConfigError> { + cfg_if::cfg_if! { + if #[cfg(any(esp32, esp32s2))] { + self.i2s.configure_rx(rx_config, self.data_format) + } else { + self.i2s.configure_rx(rx_config) + } + } } } -#[instability::unstable] -impl<'d, Dm> DmaSupportRx for I2sRx<'d, Dm> -where - Dm: DriverMode, -{ - type Channel = PeripheralRxChannel>; +/// An in-progress DMA write transfer. +pub struct I2sTxDmaTransfer<'d, Dm: DriverMode, BUFFER: DmaTxBuffer> { + i2s_tx: ManuallyDrop>, + buffer_view: ManuallyDrop, +} + +impl<'d, Dm: DriverMode, BUFFER: DmaTxBuffer> Deref for I2sTxDmaTransfer<'d, Dm, BUFFER> { + type Target = BUFFER::View; - fn rx(&mut self) -> &mut ChannelRx>> { - &mut self.rx_channel + fn deref(&self) -> &Self::Target { + &self.buffer_view } +} - fn chain(&mut self) -> &mut DescriptorChain { - &mut self.rx_chain +impl<'d, Dm: DriverMode, BUFFER: DmaTxBuffer> DerefMut for I2sTxDmaTransfer<'d, Dm, BUFFER> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.buffer_view } } -impl I2sRx<'_, Dm> -where - Dm: DriverMode, -{ - fn read(&mut self, mut data: &mut [u8]) -> Result<(), Error> { - self.start_rx_transfer(&mut data, false)?; +impl<'d, Dm: DriverMode, BUFFER: DmaTxBuffer> Drop for I2sTxDmaTransfer<'d, Dm, BUFFER> { + fn drop(&mut self) { + self.stop_peripheral(); + // SAFETY: This is Drop, we know that self.i2s_tx and self.buffer_view + // won't be touched again. + unsafe { + ManuallyDrop::drop(&mut self.i2s_tx); + ManuallyDrop::drop(&mut self.buffer_view); + } + } +} - // wait until I2S_RX_IDLE is 1 - self.i2s.wait_for_rx_done(); +impl<'d, Dm: DriverMode, BUFFER: DmaTxBuffer> I2sTxDmaTransfer<'d, Dm, BUFFER> { + /// Stops the DMA transfer and returns the I2S transmitter and buffer. + pub fn stop(mut self) -> (I2sTx<'d, Dm>, BUFFER::Final) { + self.stop_peripheral(); + let (i2s_tx, view) = self.release(); + (i2s_tx, BUFFER::from_view(view)) + } - Ok(()) + /// Checks if the DMA transfer is done. + pub fn is_done(&self) -> bool { + self.i2s_tx.i2s.tx_done() } - fn start_rx_transfer<'t, RXBUF>( - &'t mut self, - words: &'t mut RXBUF, - circular: bool, - ) -> Result<(), Error> + /// Stops and restarts the DMA transfer. + pub fn restart(self) -> Result where - RXBUF: WriteBuffer, + BUFFER: DmaTxBuffer, { - let (ptr, len) = unsafe { words.write_buffer() }; + let (i2s, buf) = self.stop(); + i2s.write(buf) + } - if !len.is_multiple_of(4) { - return Err(Error::IllegalArgument); - } + /// Checks if the transfer has an error. + pub fn has_error(&self) -> bool { + self.i2s_tx.tx_channel.has_error() + } - // Reset RX unit and RX FIFO - self.i2s.reset_rx(); + fn release(mut self) -> (I2sTx<'d, Dm>, BUFFER::View) { + // SAFETY: Since forget is called on self, we know that self.i2s_tx and + // self.buffer_view won't be touched again. + let result = unsafe { + ( + ManuallyDrop::take(&mut self.i2s_tx), + ManuallyDrop::take(&mut self.buffer_view), + ) + }; + core::mem::forget(self); + result + } - // Enable corresponding interrupts if needed + fn stop_peripheral(&mut self) { + self.i2s_tx.i2s.tx_stop(); + self.i2s_tx.tx_channel.stop_transfer(); + } +} - // configure DMA inlink - unsafe { - self.rx_chain.fill_for_rx(circular, ptr, len)?; - self.rx_channel - .prepare_transfer_without_start(self.i2s.dma_peripheral(), &self.rx_chain) - .and_then(|_| self.rx_channel.start_transfer())?; - } +/// An in-progress async circular DMA read transfer. +pub struct I2sRxDmaTransfer<'d, Dm: DriverMode, BUFFER: DmaRxBuffer> { + i2s_rx: ManuallyDrop>, + chunk_size: usize, + buffer_view: ManuallyDrop, +} - // start: set I2S_RX_START - self.i2s.rx_start(len); - Ok(()) +impl<'d, Dm: DriverMode, BUFFER: DmaRxBuffer> Deref for I2sRxDmaTransfer<'d, Dm, BUFFER> { + type Target = BUFFER::View; + + fn deref(&self) -> &Self::Target { + &self.buffer_view } +} - /// Change the I2S Rx unit configuration. - pub fn apply_config(&mut self, rx_config: &UnitConfig) -> Result<(), ConfigError> { - cfg_if::cfg_if! { - if #[cfg(any(esp32, esp32s2))] { - self.i2s.configure_rx(rx_config, self.data_format) - } else { - self.i2s.configure_rx(rx_config) - } - } +impl<'d, Dm: DriverMode, BUFFER: DmaRxBuffer> DerefMut for I2sRxDmaTransfer<'d, Dm, BUFFER> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.buffer_view } +} - /// Reads a slice of data from the I2S peripheral and stores it in the - /// provided buffer. - pub fn read_words(&mut self, words: &mut [impl AcceptedWord]) -> Result<(), Error> { - if core::mem::size_of_val(words) > 4096 || words.is_empty() { - return Err(Error::IllegalArgument); +impl<'d, Dm: DriverMode, BUFFER: DmaRxBuffer> Drop for I2sRxDmaTransfer<'d, Dm, BUFFER> { + fn drop(&mut self) { + self.stop_peripheral(); + // SAFETY: This is Drop, we know that self.i2s_rx and self.buffer_view + // won't be touched again. + unsafe { + ManuallyDrop::drop(&mut self.i2s_rx); + ManuallyDrop::drop(&mut self.buffer_view); } + } +} - self.read(unsafe { - core::slice::from_raw_parts_mut( - words.as_mut_ptr().cast::(), - core::mem::size_of_val(words), - ) - }) +impl<'d, Dm: DriverMode, BUFFER: DmaRxBuffer> I2sRxDmaTransfer<'d, Dm, BUFFER> { + /// Stops the transfer and returns the I2S receiver and the buffer. + pub fn stop(mut self) -> (I2sRx<'d, Dm>, BUFFER::Final) { + self.stop_peripheral(); + let (i2s_rx, view) = self.release(); + (i2s_rx, BUFFER::from_view(view)) } - /// Read I2S. - /// Returns [DmaTransferRx] which represents the in-progress DMA - /// transfer - pub fn read_dma<'t>( - &'t mut self, - words: &'t mut impl WriteBuffer, - ) -> Result, Error> - where - Self: DmaSupportRx, - { - self.start_rx_transfer(words, false)?; - Ok(DmaTransferRx::new(self)) + /// Returns true if the transfer is done. + pub fn is_done(&self) -> bool { + self.i2s_rx.i2s.rx_done() } - /// Continuously read from I2S. - /// Returns [DmaTransferRxCircular] which represents the in-progress DMA - /// transfer - pub fn read_dma_circular<'t>( - &'t mut self, - words: &'t mut impl WriteBuffer, - ) -> Result, Error> + /// Stops and restarts the DMA transfer. + pub fn restart(self) -> Result where - Self: DmaSupportRx, + BUFFER: DmaRxBuffer, { - self.start_rx_transfer(words, true)?; - Ok(DmaTransferRxCircular::new(self)) + let chunk_size = self.chunk_size; + let (i2s, buf) = self.stop(); + i2s.read(buf, chunk_size) + } + + /// Checks if the transfer has an error. + pub fn has_error(&self) -> bool { + self.i2s_rx.rx_channel.has_error() + } + + fn release(mut self) -> (I2sRx<'d, Dm>, BUFFER::View) { + // SAFETY: Since forget is called on self, we know that self.i2s_tx and + // self.buffer_view won't be touched again. + let result = unsafe { + ( + ManuallyDrop::take(&mut self.i2s_rx), + ManuallyDrop::take(&mut self.buffer_view), + ) + }; + core::mem::forget(self); + result + } + + fn stop_peripheral(&mut self) { + self.i2s_rx.i2s.rx_stop(); + self.i2s_rx.rx_channel.stop_transfer(); } } @@ -1286,7 +1269,7 @@ mod private { use crate::pac::i2s0::RegisterBlock; use crate::{ DriverMode, - dma::{ChannelRx, ChannelTx, DescriptorChain, DmaDescriptor, DmaEligible}, + dma::{ChannelRx, ChannelTx, DmaEligible}, gpio::{ InputConfig, InputSignal, @@ -1318,12 +1301,11 @@ mod private { where Dm: DriverMode, { - pub fn build(self, descriptors: &'static mut [DmaDescriptor]) -> I2sTx<'d, Dm> { + pub fn build(self) -> I2sTx<'d, Dm> { let peripheral = self.i2s.peripheral(); I2sTx { i2s: self.i2s, tx_channel: self.tx_channel, - tx_chain: DescriptorChain::new(descriptors), _guard: PeripheralGuard::new(peripheral), #[cfg(any(esp32, esp32s2))] data_format: self.data_format, @@ -1379,12 +1361,11 @@ mod private { where Dm: DriverMode, { - pub fn build(self, descriptors: &'static mut [DmaDescriptor]) -> I2sRx<'d, Dm> { + pub fn build(self) -> I2sRx<'d, Dm> { let peripheral = self.i2s.peripheral(); I2sRx { i2s: self.i2s, rx_channel: self.rx_channel, - rx_chain: DescriptorChain::new(descriptors), _guard: PeripheralGuard::new(peripheral), #[cfg(any(esp32, esp32s2))] data_format: self.data_format, @@ -1704,6 +1685,14 @@ mod private { self.regs().conf().modify(|_, w| w.tx_start().clear_bit()); } + fn rx_stop(&self) { + self.regs().conf().modify(|_, w| w.rx_start().clear_bit()); + } + + fn tx_done(&self) -> bool { + self.regs().state().read().tx_idle().bit_is_set() + } + fn wait_for_tx_done(&self) { while self.regs().state().read().tx_idle().bit_is_clear() { // wait @@ -1752,6 +1741,10 @@ mod private { self.regs().conf().modify(|_, w| w.rx_start().set_bit()); } + fn rx_done(&self) -> bool { + self.regs().int_raw().read().in_dscr_empty().bit_is_set() + } + fn wait_for_rx_done(&self) { while self.regs().int_raw().read().in_suc_eof().bit_is_clear() { // wait @@ -2114,6 +2107,10 @@ mod private { }); } + fn tx_done(&self) -> bool { + self.regs().int_raw().read().tx_done().bit_is_set() + } + fn tx_start(&self) { self.regs().tx_conf().modify(|_, w| w.tx_start().set_bit()); } @@ -2156,13 +2153,22 @@ mod private { fn rx_start(&self, len: usize) { let len = len - 1; - self.regs() .rxeof_num() .write(|w| unsafe { w.rx_eof_num().bits(len as u16) }); self.regs().rx_conf().modify(|_, w| w.rx_start().set_bit()); } + fn rx_stop(&self) { + self.regs() + .rx_conf() + .modify(|_, w| w.rx_start().clear_bit()); + } + + fn rx_done(&self) -> bool { + self.regs().int_raw().read().rx_done().bit_is_set() + } + fn wait_for_rx_done(&self) { while self.regs().int_raw().read().rx_done().bit_is_clear() { // wait @@ -2507,219 +2513,49 @@ mod private { /// Async functionality pub mod asynch { - use super::{Error, I2sRx, I2sTx, RegisterAccessPrivate}; + use super::{Error, I2sTxDmaTransfer}; use crate::{ Async, dma::{ - DmaEligible, - ReadBuffer, - RxCircularState, - TxCircularState, - WriteBuffer, - asynch::{DmaRxDoneChFuture, DmaRxFuture, DmaTxDoneChFuture, DmaTxFuture}, + DmaRxBuffer, + DmaTxBuffer, + asynch::{DmaRxDoneChFuture, DmaTxDoneChFuture}, }, + i2s::master::I2sRxDmaTransfer, }; - impl<'d> I2sTx<'d, Async> { - /// One-shot write I2S. - pub async fn write_dma_async(&mut self, words: &mut [u8]) -> Result<(), Error> { - let (ptr, len) = (words.as_ptr(), words.len()); - - self.i2s.reset_tx(); - - let future = DmaTxFuture::new(&mut self.tx_channel); - - unsafe { - self.tx_chain.fill_for_tx(false, ptr, len)?; - future - .tx - .prepare_transfer_without_start(self.i2s.dma_peripheral(), &self.tx_chain) - .and_then(|_| future.tx.start_transfer())?; + impl I2sTxDmaTransfer<'_, Async, BUFFER> { + /// Waits for DMA process to be made and additional room to be + /// available. + /// + /// In particular, it waits for [crate::dma::DmaTxInterrupt::Done] + /// which is trigger every time a descriptor in the list has been filled. + /// In addition, if [crate::dma::DmaTxInterrupt::TotalEof] has been triggered, + /// it means that the descriptor list is most likely used up and user needs to + /// manually reset the I2S and DMA channel. + /// + /// Returns [crate::dma::DmaError::Late] if DMA descriptors are used up. + pub async fn wait_for_available(&mut self) -> Result<(), Error> { + if self.is_done() { + return Err(Error::DmaError(crate::dma::DmaError::Late)); } - - self.i2s.tx_start(); - future.await?; - + DmaTxDoneChFuture::new(&mut self.i2s_tx.tx_channel).await?; Ok(()) } - - /// Continuously write to I2S. Returns [I2sWriteDmaTransferAsync] - pub fn write_dma_circular_async( - mut self, - words: TXBUF, - ) -> Result, Error> { - let (ptr, len) = unsafe { words.read_buffer() }; - - // Reset TX unit and TX FIFO - self.i2s.reset_tx(); - - // Enable corresponding interrupts if needed - - // configure DMA outlink - unsafe { - self.tx_chain.fill_for_tx(true, ptr, len)?; - self.tx_channel - .prepare_transfer_without_start(self.i2s.dma_peripheral(), &self.tx_chain) - .and_then(|_| self.tx_channel.start_transfer())?; - } - - // set I2S_TX_STOP_EN if needed - - // start: set I2S_TX_START - self.i2s.tx_start(); - - let state = TxCircularState::new(&mut self.tx_chain); - Ok(I2sWriteDmaTransferAsync { - i2s_tx: self, - state, - _buffer: words, - }) - } - } - - /// An in-progress async circular DMA write transfer. - pub struct I2sWriteDmaTransferAsync<'d, BUFFER> { - i2s_tx: I2sTx<'d, Async>, - state: TxCircularState, - _buffer: BUFFER, } - impl I2sWriteDmaTransferAsync<'_, BUFFER> { - /// How many bytes can be pushed into the DMA transaction. - /// Will wait for more than 0 bytes available. - pub async fn available(&mut self) -> Result { - loop { - self.state.update(&self.i2s_tx.tx_channel)?; - let res = self.state.available; - - if res != 0 { - break Ok(res); - } - - DmaTxDoneChFuture::new(&mut self.i2s_tx.tx_channel).await? + impl I2sRxDmaTransfer<'_, Async, BUFFER> { + /// Waits for DMA process to be made and additional room to be + /// available. + /// + /// In particular, it waits for [crate::dma::DmaRxInterrupt::Done] + /// which is trigger every time a descriptor in the list has been filled. + pub async fn wait_for_available(&mut self) -> Result<(), Error> { + if self.is_done() { + return Err(Error::DmaError(crate::dma::DmaError::Late)); } - } - - /// Push bytes into the DMA transaction. - pub async fn push(&mut self, data: &[u8]) -> Result { - let avail = self.available().await?; - let to_send = usize::min(avail, data.len()); - Ok(self.state.push(&data[..to_send])?) - } - - /// Push bytes into the DMA buffer via the given closure. - /// The closure *must* return the actual number of bytes written. - /// The closure *might* get called with a slice which is smaller than - /// the total available buffer. Only useful for circular DMA - /// transfers - pub async fn push_with( - &mut self, - f: impl FnOnce(&mut [u8]) -> usize, - ) -> Result { - let _avail = self.available().await; - Ok(self.state.push_with(f)?) - } - } - - impl<'d> I2sRx<'d, Async> { - /// One-shot read I2S. - pub async fn read_dma_async(&mut self, words: &mut [u8]) -> Result<(), Error> { - let (ptr, len) = (words.as_mut_ptr(), words.len()); - - if !len.is_multiple_of(4) { - return Err(Error::IllegalArgument); - } - - // Reset RX unit and RX FIFO - self.i2s.reset_rx(); - - let future = DmaRxFuture::new(&mut self.rx_channel); - - // configure DMA inlink - unsafe { - self.rx_chain.fill_for_rx(false, ptr, len)?; - future - .rx - .prepare_transfer_without_start(self.i2s.dma_peripheral(), &self.rx_chain) - .and_then(|_| future.rx.start_transfer())?; - } - - // start: set I2S_RX_START - self.i2s.rx_start(len); - - future.await?; - + DmaRxDoneChFuture::new(&mut self.i2s_rx.rx_channel).await?; Ok(()) } - - /// Continuously read from I2S. Returns [I2sReadDmaTransferAsync] - pub fn read_dma_circular_async( - mut self, - mut words: RXBUF, - ) -> Result, Error> - where - RXBUF: WriteBuffer, - { - let (ptr, len) = unsafe { words.write_buffer() }; - - if !len.is_multiple_of(4) { - return Err(Error::IllegalArgument); - } - - // Reset RX unit and RX FIFO - self.i2s.reset_rx(); - - // Enable corresponding interrupts if needed - - // configure DMA inlink - unsafe { - self.rx_chain.fill_for_rx(true, ptr, len)?; - self.rx_channel - .prepare_transfer_without_start(self.i2s.dma_peripheral(), &self.rx_chain) - .and_then(|_| self.rx_channel.start_transfer())?; - } - - // start: set I2S_RX_START - self.i2s.rx_start(len); - - let state = RxCircularState::new(&mut self.rx_chain); - Ok(I2sReadDmaTransferAsync { - i2s_rx: self, - state, - _buffer: words, - }) - } - } - - /// An in-progress async circular DMA read transfer. - pub struct I2sReadDmaTransferAsync<'d, BUFFER> { - i2s_rx: I2sRx<'d, Async>, - state: RxCircularState, - _buffer: BUFFER, - } - - impl I2sReadDmaTransferAsync<'_, BUFFER> { - /// How many bytes can be popped from the DMA transaction. - /// Will wait for more than 0 bytes available. - pub async fn available(&mut self) -> Result { - loop { - self.state.update()?; - - let res = self.state.available; - - if res != 0 { - break Ok(res); - } - - DmaRxDoneChFuture::new(&mut self.i2s_rx.rx_channel).await?; - } - } - - /// Pop bytes from the DMA transaction. - pub async fn pop(&mut self, data: &mut [u8]) -> Result { - let avail = self.available().await?; - let to_rcv = usize::min(avail, data.len()); - Ok(self.state.pop(&mut data[..to_rcv])?) - } } } diff --git a/hil-test/src/bin/embassy_interrupt_spi_dma.rs b/hil-test/src/bin/embassy_interrupt_spi_dma.rs index 8a201eb6d03..bc57d81050b 100644 --- a/hil-test/src/bin/embassy_interrupt_spi_dma.rs +++ b/hil-test/src/bin/embassy_interrupt_spi_dma.rs @@ -53,15 +53,20 @@ async fn interrupt_driven_task(spi: esp_hal::spi::master::SpiDma<'static, Blocki #[cfg(not(any(esp32, esp32s2, esp32s3)))] #[embassy_executor::task] async fn interrupt_driven_task(i2s_tx: esp_hal::i2s::master::I2s<'static, Blocking>) { - let (_, _, _, tx_descriptors) = dma_buffers!(128); - - let mut i2s_tx = i2s_tx.into_async().i2s_tx.build(tx_descriptors); + use esp_hal::dma_tx_stream_buffer; + let mut buf = dma_tx_stream_buffer!(128, 128 / 3); + let mut i2s_tx = i2s_tx.into_async().i2s_tx.build(); loop { let mut buffer: [u8; 8] = [0; 8]; INTERRUPT_TASK_WORKING.store(true, portable_atomic::Ordering::Relaxed); - i2s_tx.write_dma_async(&mut buffer).await.unwrap(); + let mut transfer = i2s_tx.write(buf).unwrap(); + while transfer.available_bytes() == 0 { + transfer.wait_for_available().await.unwrap(); + } + transfer.push(&mut buffer); + (i2s_tx, buf) = transfer.stop(); INTERRUPT_TASK_WORKING.store(false, portable_atomic::Ordering::Relaxed); if STOP_INTERRUPT_TASK.load(portable_atomic::Ordering::Relaxed) { diff --git a/hil-test/src/bin/i2s.rs b/hil-test/src/bin/i2s.rs index e276cb899d8..5c2519537ec 100644 --- a/hil-test/src/bin/i2s.rs +++ b/hil-test/src/bin/i2s.rs @@ -13,7 +13,7 @@ use esp_hal::{ Async, delay::Delay, - dma_buffers, + dma::DmaTxStreamBuf, gpio::{AnyPin, NoPin, Pin}, i2s::master::{Channels, Config, DataFormat, I2s, I2sTx}, peripherals::I2S0, @@ -57,24 +57,27 @@ impl Iterator for SampleSource { } #[embassy_executor::task] -async fn writer(tx_buffer: &'static mut [u8], i2s_tx: I2sTx<'static, Async>) { +async fn writer(mut tx_buffer: DmaTxStreamBuf, i2s_tx: I2sTx<'static, Async>) { let mut samples = SampleSource::new(); - for b in tx_buffer.iter_mut() { - *b = samples.next().unwrap(); - } + tx_buffer.push_with(|buf| { + for b in buf.iter_mut() { + *b = samples.next().unwrap(); + } + buf.len() + }); - let mut tx_transfer = i2s_tx.write_dma_circular_async(tx_buffer).unwrap(); + let mut tx_transfer = i2s_tx.write(tx_buffer).unwrap(); loop { - tx_transfer - .push_with(|buffer| { - for b in buffer.iter_mut() { - *b = samples.next().unwrap(); - } - buffer.len() - }) - .await - .unwrap(); + while tx_transfer.available_bytes() == 0 { + tx_transfer.wait_for_available().await.unwrap(); + } + tx_transfer.push_with(|buffer| { + for b in buffer.iter_mut() { + *b = samples.next().unwrap(); + } + buffer.len() + }); } } @@ -96,6 +99,8 @@ fn enable_loopback() { #[embedded_test::tests(default_timeout = 3, executor = hil_test::Executor::new())] mod tests { + use esp_hal::{dma_rx_stream_buffer, dma_tx_stream_buffer}; + use super::*; struct Context { @@ -131,8 +136,9 @@ mod tests { async fn test_i2s_loopback_async(ctx: Context) { let spawner = unsafe { embassy_executor::Spawner::for_current_executor().await }; - let (rx_buffer, rx_descriptors, tx_buffer, tx_descriptors) = - esp_hal::dma_circular_buffers!(BUFFER_SIZE, BUFFER_SIZE); + // We need more than 3 descriptors for continuous transfer to work + let rx_buffer = dma_rx_stream_buffer!(BUFFER_SIZE, BUFFER_SIZE / 3); + let tx_buffer = dma_tx_stream_buffer!(BUFFER_SIZE, BUFFER_SIZE / 3); let i2s = I2s::new( ctx.i2s, @@ -152,26 +158,29 @@ mod tests { .with_bclk(NoPin) .with_ws(NoPin) .with_dout(dout) - .build(tx_descriptors); + .build(); let i2s_rx = i2s .i2s_rx .with_bclk(NoPin) .with_ws(NoPin) .with_din(din) - .build(rx_descriptors); + .build(); enable_loopback(); - let mut rx_transfer = i2s_rx.read_dma_circular_async(rx_buffer).unwrap(); + let mut rx_transfer = i2s_rx.read(rx_buffer, BUFFER_SIZE).unwrap(); spawner.must_spawn(writer(tx_buffer, i2s_tx)); - let mut rcv = [0u8; BUFFER_SIZE]; let mut sample_idx = 0; let mut samples = SampleSource::new(); for _ in 0..30 { - let len = rx_transfer.pop(&mut rcv).await.unwrap(); - for &b in &rcv[..len] { + while rx_transfer.available_bytes() == 0 { + rx_transfer.wait_for_available().await.unwrap(); + } + let data = rx_transfer.peek(); + let len = data.len(); + for &b in data { let expected = samples.next().unwrap(); assert_eq!( b, expected, @@ -180,12 +189,17 @@ mod tests { ); sample_idx += 1; } + rx_transfer.consume(len); } } #[test] fn test_i2s_loopback(ctx: Context) { - let (rx_buffer, rx_descriptors, tx_buffer, tx_descriptors) = dma_buffers!(16000, 16000); + // NOTE: 32000 bits of buffer maybe too large, but for some reason it fails with buffer + // size 16000 as it seems DMA can be quick enough to run out of descriptors in that + // case. + let rx_buffer = dma_rx_stream_buffer!(32000, 4000); + let mut tx_buffer = dma_tx_stream_buffer!(32000, 4000); let i2s = I2s::new( ctx.i2s, @@ -199,75 +213,73 @@ mod tests { let (din, dout) = unsafe { ctx.dout.split() }; - let mut i2s_tx = i2s + let i2s_tx = i2s .i2s_tx .with_bclk(NoPin) .with_ws(NoPin) .with_dout(dout) - .build(tx_descriptors); + .build(); - let mut i2s_rx = i2s + let i2s_rx = i2s .i2s_rx .with_bclk(NoPin) .with_ws(NoPin) .with_din(din) - .build(rx_descriptors); + .build(); enable_loopback(); let mut samples = SampleSource::new(); - for b in tx_buffer.iter_mut() { - *b = samples.next().unwrap(); - } + tx_buffer.push_with(|buf| { + for b in buf.iter_mut() { + *b = samples.next().unwrap(); + } + buf.len() + }); let mut rcv = [0u8; 11000]; let mut filler = [0x1u8; 12000]; - let mut rx_transfer = i2s_rx.read_dma_circular(rx_buffer).unwrap(); - // trying to pop data before calling `available` should just do nothing - assert_eq!(0, rx_transfer.pop(&mut rcv[..100]).unwrap()); + let mut rx_transfer = i2s_rx.read(rx_buffer, 4000).unwrap(); + // trying to peek data before calling `available` should just do nothing + assert_eq!(0, rx_transfer.peek().len()); // no data available yet - assert_eq!(0, rx_transfer.available().unwrap()); + assert_eq!(0, rx_transfer.available_bytes()); - let mut tx_transfer = i2s_tx.write_dma_circular(tx_buffer).unwrap(); + let mut tx_transfer = i2s_tx.write(tx_buffer).unwrap(); let mut iteration = 0; let mut sample_idx = 0; let mut check_samples = SampleSource::new(); loop { - let tx_avail = tx_transfer.available().unwrap(); + let tx_avail = tx_transfer.available_bytes(); // make sure there are more than one descriptor buffers ready to push if tx_avail > 5000 { for b in &mut filler[0..tx_avail].iter_mut() { *b = samples.next().unwrap(); } - tx_transfer.push(&filler[0..tx_avail]).unwrap(); + tx_transfer.push(&filler[0..tx_avail]); } // test calling available multiple times doesn't break anything - rx_transfer.available().unwrap(); - rx_transfer.available().unwrap(); - rx_transfer.available().unwrap(); - rx_transfer.available().unwrap(); - rx_transfer.available().unwrap(); - rx_transfer.available().unwrap(); - let rx_avail = rx_transfer.available().unwrap(); + rx_transfer.available_bytes(); + rx_transfer.available_bytes(); + rx_transfer.available_bytes(); + rx_transfer.available_bytes(); + rx_transfer.available_bytes(); + rx_transfer.available_bytes(); + let rx_avail = rx_transfer.available_bytes(); // make sure there are more than one descriptor buffers ready to pop if rx_avail > 0 { - // trying to pop less data than available is an error - assert_eq!( - Err(esp_hal::dma::DmaError::BufferTooSmall), - rx_transfer.pop(&mut rcv[..rx_avail / 2]) - ); - rcv.fill(0xff); - let len = rx_transfer.pop(&mut rcv).unwrap(); + let data = rx_transfer.peek(); + let len = data.len(); assert!(len > 0); - for &b in &rcv[..len] { + for &b in data { let expected = check_samples.next().unwrap(); assert_eq!( b, expected, @@ -277,6 +289,8 @@ mod tests { sample_idx += 1; } + rx_transfer.consume(len); + iteration += 1; if iteration == 1 { @@ -292,66 +306,6 @@ mod tests { } } - #[test] - fn test_i2s_push_too_late(ctx: Context) { - let (_, _, tx_buffer, tx_descriptors) = dma_buffers!(0, 16000); - - let i2s = I2s::new( - ctx.i2s, - ctx.dma_channel, - Config::new_tdm_philips() - .with_sample_rate(Rate::from_hz(16000)) - .with_data_format(DataFormat::Data16Channel16) - .with_channels(Channels::STEREO), - ) - .unwrap(); - - let mut i2s_tx = i2s - .i2s_tx - .with_bclk(NoPin) - .with_ws(NoPin) - .with_dout(ctx.dout) - .build(tx_descriptors); - - let mut tx_transfer = i2s_tx.write_dma_circular(tx_buffer).unwrap(); - - let delay = esp_hal::delay::Delay::new(); - delay.delay_millis(300); - - assert!(matches!(tx_transfer.push(&[0; 128]), Err(_))); - } - - #[test] - #[timeout(1)] - fn test_i2s_read_too_late(ctx: Context) { - let (rx_buffer, rx_descriptors, _, _) = dma_buffers!(16000, 0); - - let i2s = I2s::new( - ctx.i2s, - ctx.dma_channel, - Config::new_tdm_philips() - .with_sample_rate(Rate::from_hz(16000)) - .with_data_format(DataFormat::Data16Channel16) - .with_channels(Channels::STEREO), - ) - .unwrap(); - - let mut i2s_rx = i2s - .i2s_rx - .with_bclk(NoPin) - .with_ws(NoPin) - .with_din(ctx.dout) // not a typo - .build(rx_descriptors); - - let mut buffer = [0u8; 1024]; - let mut rx_transfer = i2s_rx.read_dma_circular(rx_buffer).unwrap(); - - let delay = esp_hal::delay::Delay::new(); - delay.delay_millis(300); - - assert!(matches!(rx_transfer.pop(&mut buffer), Err(_))); - } - #[test] #[cfg(not(esp32s2))] fn test_i2s_rx_half_sample_bits_regression(ctx: Context) { diff --git a/qa-test/src/bin/embassy_i2s_read.rs b/qa-test/src/bin/embassy_i2s_read.rs index dfb5f527492..39a6ea601fe 100644 --- a/qa-test/src/bin/embassy_i2s_read.rs +++ b/qa-test/src/bin/embassy_i2s_read.rs @@ -21,7 +21,7 @@ use esp_backtrace as _; #[cfg(target_arch = "riscv32")] use esp_hal::interrupt::software::SoftwareInterruptControl; use esp_hal::{ - dma_buffers, + dma_rx_stream_buffer, i2s::master::{Channels, Config, DataFormat, I2s}, time::Rate, timer::timg::TimerGroup, @@ -51,8 +51,6 @@ async fn main(_spawner: Spawner) { } } - let (rx_buffer, rx_descriptors, _, _) = dma_buffers!(4092 * 4, 0); - let i2s = I2s::new( peripherals.I2S0, dma_channel, @@ -70,18 +68,20 @@ async fn main(_spawner: Spawner) { .with_bclk(peripherals.GPIO2) .with_ws(peripherals.GPIO4) .with_din(peripherals.GPIO5) - .build(rx_descriptors); + .build(); - let buffer = rx_buffer; println!("Start"); let mut data = [0u8; 5000]; - let mut transaction = i2s_rx.read_dma_circular_async(buffer).unwrap(); + let mut transaction = i2s_rx + .read(dma_rx_stream_buffer!(4092 * 4, 4092), 4092) + .unwrap(); loop { - let avail = transaction.available().await.unwrap(); + transaction.wait_for_available().await.unwrap(); + let avail = transaction.available_bytes(); println!("available {}", avail); - let count = transaction.pop(&mut data).await.unwrap(); + let count = transaction.pop(&mut data); #[cfg(not(feature = "esp32s2"))] println!( diff --git a/qa-test/src/bin/embassy_i2s_sound.rs b/qa-test/src/bin/embassy_i2s_sound.rs index b92dc6f8eab..d5eb00a7265 100644 --- a/qa-test/src/bin/embassy_i2s_sound.rs +++ b/qa-test/src/bin/embassy_i2s_sound.rs @@ -35,7 +35,7 @@ use esp_backtrace as _; #[cfg(target_arch = "riscv32")] use esp_hal::interrupt::software::SoftwareInterruptControl; use esp_hal::{ - dma_buffers, + dma_loop_buffer, i2s::master::{Channels, Config, DataFormat, I2s}, time::Rate, timer::timg::TimerGroup, @@ -73,8 +73,6 @@ async fn main(_spawner: Spawner) { } } - let (_, _, tx_buffer, tx_descriptors) = dma_buffers!(0, 32000); - let i2s = I2s::new( peripherals.I2S0, dma_channel, @@ -91,36 +89,17 @@ async fn main(_spawner: Spawner) { .with_bclk(peripherals.GPIO2) .with_ws(peripherals.GPIO4) .with_dout(peripherals.GPIO5) - .build(tx_descriptors); + .build(); let data = unsafe { core::slice::from_raw_parts(&SINE as *const _ as *const u8, SINE.len() * 2) }; - let buffer = tx_buffer; - let mut idx = 0; - for i in 0..usize::min(data.len(), buffer.len()) { - buffer[i] = data[idx]; - - idx += 1; - - if idx >= data.len() { - idx = 0; - } - } - - let mut filler = [0u8; 10000]; - let mut idx = 32000 % data.len(); + let mut buffer = dma_loop_buffer!(128); + buffer.copy_from_slice(data); println!("Start"); - let mut transaction = i2s_tx.write_dma_circular_async(buffer).unwrap(); + let mut transaction = i2s_tx.write(buffer).unwrap(); loop { - for i in 0..filler.len() { - filler[i] = data[(idx + i) % data.len()]; - } - println!("Next"); - - let written = transaction.push(&filler).await.unwrap(); - idx = (idx + written) % data.len(); - println!("written {}", written); + transaction.wait_for_available().await.unwrap(); } } diff --git a/qa-test/src/bin/embassy_wifi_i2s.rs b/qa-test/src/bin/embassy_wifi_i2s.rs index eb367522ca8..3f8d432f08b 100644 --- a/qa-test/src/bin/embassy_wifi_i2s.rs +++ b/qa-test/src/bin/embassy_wifi_i2s.rs @@ -15,7 +15,8 @@ use esp_backtrace as _; #[cfg(target_arch = "riscv32")] use esp_hal::interrupt::software::SoftwareInterruptControl; use esp_hal::{ - dma_buffers, + dma::DmaRxStreamBuf, + dma_rx_stream_buffer, i2s::master::{Channels, Config as I2sConfig, DataFormat, I2s}, ram, rng::Rng, @@ -44,6 +45,7 @@ const PASSWORD: &str = env!("PASSWORD"); const SAMPLE_RATE: u32 = 16_000; const I2S_BUFFER_SIZE: usize = 4092 * 8; // DMA ring size +const I2S_CHUNK_SIZE: usize = 4092; macro_rules! mk_static { ($t:ty,$val:expr) => {{ @@ -127,19 +129,15 @@ async fn connection_manager( #[embassy_executor::task] async fn i2s_dma_drain( i2s_rx: esp_hal::i2s::master::I2sRx<'static, esp_hal::Async>, - buffer: &'static mut [u8], + buffer: DmaRxStreamBuf, connected_signal: &'static Signal, ) { - // Temporary buffer for DMA pops - static I2S_DATA_BUFFER: StaticCell<[u8; 8192]> = StaticCell::new(); - let i2s_data = I2S_DATA_BUFFER.init([0u8; 8192]); - - // Create circular DMA transaction + // Create streaming DMA transaction println!( - "๐ŸŽ›๏ธ Creating I2S DMA circular transaction with ring size: {} bytes", - buffer.len() + "๐ŸŽ›๏ธ Creating I2S DMA transaction with streaming buffer of size: {} bytes", + I2S_BUFFER_SIZE ); - let mut transaction = match i2s_rx.read_dma_circular_async(buffer) { + let mut transaction = match i2s_rx.read(buffer, I2S_CHUNK_SIZE) { Ok(t) => t, Err(e) => { println!("โŒ Failed to start I2S DMA: {:?}", e); @@ -154,10 +152,19 @@ async fn i2s_dma_drain( // Start continuous draining to prevent DmaError(Late) println!("๐Ÿงน Starting continuous buffer drain to keep DMA synchronized..."); while !connected_signal.signaled() { + let available_bytes = match transaction.available_bytes() { + 0 => transaction + .wait_for_available() + .await + .map(|_| transaction.available_bytes()), + b => Ok(b), + }; + // Check for available data and drain it - match transaction.pop(i2s_data).await { + match available_bytes { Ok(bytes) => { total_drained += bytes; + transaction.consume(bytes); } Err(e) => { if matches!( @@ -188,9 +195,17 @@ async fn i2s_dma_drain( late_errors, start.elapsed().as_millis() ); - match transaction.pop(i2s_data).await { + let available_bytes = match transaction.available_bytes() { + 0 => transaction + .wait_for_available() + .await + .map(|_| transaction.available_bytes()), + b => Ok(b), + }; + match available_bytes { Ok(bytes) => { println!("๐Ÿงน Final drained {} bytes total", bytes); + transaction.consume(bytes); } Err(e) => { if matches!( @@ -232,7 +247,7 @@ async fn main(spawner: Spawner) { let dma_channel = peripherals.DMA_I2S0; #[cfg(not(any(feature = "esp32", feature = "esp32s2")))] let dma_channel = peripherals.DMA_CH0; - let (rx_buffer, rx_descriptors, _, _) = dma_buffers!(I2S_BUFFER_SIZE, 0); + let rx_buffer = dma_rx_stream_buffer!(I2S_BUFFER_SIZE, I2S_CHUNK_SIZE); let i2s_cfg = I2sConfig::new_tdm_philips() .with_sample_rate(Rate::from_hz(SAMPLE_RATE)) @@ -248,7 +263,7 @@ async fn main(spawner: Spawner) { .with_bclk(peripherals.GPIO2) // SCK .with_ws(peripherals.GPIO4) // WS .with_din(peripherals.GPIO5) // SD - .build(rx_descriptors); + .build(); // WiFi + network stack let esp_radio_ctrl = &*mk_static!(Controller<'static>, esp_radio::init().unwrap());