Fix circular DMA (again) and small I2S improvements (#1189)
* Fix calculation of available buffer * Lift requirement of circular DMA min buffer * Have a separate set of convenience macros for circular DMA buffers * Prefer `addr_of_mut!` * Add `push_with` for I2S TX * CHANGELOG.md
This commit is contained in:
parent
c18b5b4e78
commit
bc2f1a02cc
@ -23,6 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- Adding clock support for `ESP32-P4` (#1145)
|
- Adding clock support for `ESP32-P4` (#1145)
|
||||||
- Implementation OutputPin and InputPin for AnyPin (#1067)
|
- Implementation OutputPin and InputPin for AnyPin (#1067)
|
||||||
- Implement `estimate_xtal_frequency` for ESP32-C6 / ESP32-H2 (#1174)
|
- Implement `estimate_xtal_frequency` for ESP32-C6 / ESP32-H2 (#1174)
|
||||||
|
- A way to push into I2S DMA buffer via a closure (#1189)
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
@ -35,6 +36,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- Fix timer `now` for esp32c3 and esp32c6 (#1178)
|
- Fix timer `now` for esp32c3 and esp32c6 (#1178)
|
||||||
- Wait for registers to get synced before reading the timer count for all chips (#1183)
|
- Wait for registers to get synced before reading the timer count for all chips (#1183)
|
||||||
- Fix I2C error handling (#1184)
|
- Fix I2C error handling (#1184)
|
||||||
|
- Fix circular DMA (#1189)
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
@ -45,6 +47,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- Refactor the `Trace` driver to be generic around its peripheral (#1140)
|
- Refactor the `Trace` driver to be generic around its peripheral (#1140)
|
||||||
- Auto detect crystal frequency based on `RtcClock::estimate_xtal_frequency()` (#1165)
|
- Auto detect crystal frequency based on `RtcClock::estimate_xtal_frequency()` (#1165)
|
||||||
- ESP32-S3: Configure 32k ICACHE (#1169)
|
- ESP32-S3: Configure 32k ICACHE (#1169)
|
||||||
|
- Lift the minimal buffer size requirement for I2S (#1189)
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
- Remove `xtal-26mhz` and `xtal-40mhz` features (#1165)
|
- Remove `xtal-26mhz` and `xtal-40mhz` features (#1165)
|
||||||
|
|||||||
@ -183,6 +183,67 @@ macro_rules! dma_buffers {
|
|||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Convenience macro to create circular DMA buffers and descriptors
|
||||||
|
///
|
||||||
|
/// ## Usage
|
||||||
|
/// ```rust,no_run
|
||||||
|
/// // TX and RX buffers are 32000 bytes - passing only one parameter makes TX and RX the same size
|
||||||
|
/// let (tx_buffer, mut tx_descriptors, rx_buffer, mut rx_descriptors) =
|
||||||
|
/// dma_circular_buffers!(32000, 32000);
|
||||||
|
/// ```
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! dma_circular_buffers {
|
||||||
|
($tx_size:expr, $rx_size:expr) => {{
|
||||||
|
static mut TX_BUFFER: [u8; $tx_size] = [0u8; $tx_size];
|
||||||
|
static mut RX_BUFFER: [u8; $rx_size] = [0u8; $rx_size];
|
||||||
|
|
||||||
|
const tx_descriptor_len: usize = if $tx_size > 4092 * 2 {
|
||||||
|
($tx_size + 4091) / 4092
|
||||||
|
} else {
|
||||||
|
3
|
||||||
|
};
|
||||||
|
|
||||||
|
const rx_descriptor_len: usize = if $rx_size > 4092 * 2 {
|
||||||
|
($rx_size + 4091) / 4092
|
||||||
|
} else {
|
||||||
|
3
|
||||||
|
};
|
||||||
|
|
||||||
|
let tx_descriptors = [$crate::dma::DmaDescriptor::EMPTY; tx_descriptor_len];
|
||||||
|
let rx_descriptors = [$crate::dma::DmaDescriptor::EMPTY; rx_descriptor_len];
|
||||||
|
unsafe {
|
||||||
|
(
|
||||||
|
&mut TX_BUFFER,
|
||||||
|
tx_descriptors,
|
||||||
|
&mut RX_BUFFER,
|
||||||
|
rx_descriptors,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
|
||||||
|
($size:expr) => {{
|
||||||
|
static mut TX_BUFFER: [u8; $size] = [0u8; $size];
|
||||||
|
static mut RX_BUFFER: [u8; $size] = [0u8; $size];
|
||||||
|
|
||||||
|
const descriptor_len: usize = if $size > 4092 * 2 {
|
||||||
|
($size + 4091) / 4092
|
||||||
|
} else {
|
||||||
|
3
|
||||||
|
};
|
||||||
|
|
||||||
|
let tx_descriptors = [$crate::dma::DmaDescriptor::EMPTY; descriptor_len];
|
||||||
|
let rx_descriptors = [$crate::dma::DmaDescriptor::EMPTY; descriptor_len];
|
||||||
|
unsafe {
|
||||||
|
(
|
||||||
|
&mut TX_BUFFER,
|
||||||
|
tx_descriptors,
|
||||||
|
&mut RX_BUFFER,
|
||||||
|
rx_descriptors,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
/// Convenience macro to create DMA descriptors
|
/// Convenience macro to create DMA descriptors
|
||||||
///
|
///
|
||||||
/// ## Usage
|
/// ## Usage
|
||||||
@ -205,6 +266,46 @@ macro_rules! dma_descriptors {
|
|||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Convenience macro to create circular DMA descriptors
|
||||||
|
///
|
||||||
|
/// ## Usage
|
||||||
|
/// ```rust,no_run
|
||||||
|
/// // Create TX and RX descriptors for transactions up to 32000 bytes - passing only one parameter assumes TX and RX are the same size
|
||||||
|
/// let (mut tx_descriptors, mut rx_descriptors) = dma_circular_descriptors!(32000, 32000);
|
||||||
|
/// ```
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! dma_circular_descriptors {
|
||||||
|
($tx_size:expr, $rx_size:expr) => {{
|
||||||
|
const tx_descriptor_len: usize = if $tx_size > 4092 * 2 {
|
||||||
|
($tx_size + 4091) / 4092
|
||||||
|
} else {
|
||||||
|
3
|
||||||
|
};
|
||||||
|
|
||||||
|
const rx_descriptor_len: usize = if $rx_size > 4092 * 2 {
|
||||||
|
($rx_size + 4091) / 4092
|
||||||
|
} else {
|
||||||
|
3
|
||||||
|
};
|
||||||
|
|
||||||
|
let tx_descriptors = [$crate::dma::DmaDescriptor::EMPTY; tx_descriptor_len];
|
||||||
|
let rx_descriptors = [$crate::dma::DmaDescriptor::EMPTY; rx_descriptor_len];
|
||||||
|
(tx_descriptors, rx_descriptors)
|
||||||
|
}};
|
||||||
|
|
||||||
|
($size:expr) => {{
|
||||||
|
const descriptor_len: usize = if $size > 4092 * 2 {
|
||||||
|
($size + 4091) / 4092
|
||||||
|
} else {
|
||||||
|
3
|
||||||
|
};
|
||||||
|
|
||||||
|
let tx_descriptors = [$crate::dma::DmaDescriptor::EMPTY; descriptor_len];
|
||||||
|
let rx_descriptors = [$crate::dma::DmaDescriptor::EMPTY; descriptor_len];
|
||||||
|
(tx_descriptors, rx_descriptors)
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
/// DMA Errors
|
/// DMA Errors
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
@ -404,10 +505,16 @@ where
|
|||||||
|
|
||||||
compiler_fence(core::sync::atomic::Ordering::SeqCst);
|
compiler_fence(core::sync::atomic::Ordering::SeqCst);
|
||||||
|
|
||||||
|
let max_chunk_size = if !circular || len > CHUNK_SIZE * 2 {
|
||||||
|
CHUNK_SIZE
|
||||||
|
} else {
|
||||||
|
len / 3 + len % 3
|
||||||
|
};
|
||||||
|
|
||||||
let mut processed = 0;
|
let mut processed = 0;
|
||||||
let mut descr = 0;
|
let mut descr = 0;
|
||||||
loop {
|
loop {
|
||||||
let chunk_size = usize::min(CHUNK_SIZE, len - processed);
|
let chunk_size = usize::min(max_chunk_size, len - processed);
|
||||||
let last = processed + chunk_size >= len;
|
let last = processed + chunk_size >= len;
|
||||||
|
|
||||||
let next = if last {
|
let next = if last {
|
||||||
@ -538,7 +645,7 @@ where
|
|||||||
return Err(DmaError::InvalidAlignment);
|
return Err(DmaError::InvalidAlignment);
|
||||||
}
|
}
|
||||||
|
|
||||||
if circular && len < CHUNK_SIZE * 2 {
|
if circular && len <= 3 {
|
||||||
return Err(DmaError::BufferTooSmall);
|
return Err(DmaError::BufferTooSmall);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -651,7 +758,7 @@ where
|
|||||||
let buffer_ptr = self.descriptors[idx].buffer;
|
let buffer_ptr = self.descriptors[idx].buffer;
|
||||||
let next_dscr = self.descriptors[idx].next;
|
let next_dscr = self.descriptors[idx].next;
|
||||||
|
|
||||||
// Copy data to desination
|
// Copy data to destination
|
||||||
let (dst_chunk, dst_remaining) = dst.split_at_mut(chunk_len);
|
let (dst_chunk, dst_remaining) = dst.split_at_mut(chunk_len);
|
||||||
dst = dst_remaining;
|
dst = dst_remaining;
|
||||||
|
|
||||||
@ -740,6 +847,8 @@ pub trait TxPrivate {
|
|||||||
|
|
||||||
fn push(&mut self, data: &[u8]) -> Result<usize, DmaError>;
|
fn push(&mut self, data: &[u8]) -> Result<usize, DmaError>;
|
||||||
|
|
||||||
|
fn push_with(&mut self, f: impl FnOnce(&mut [u8]) -> usize) -> Result<usize, DmaError>;
|
||||||
|
|
||||||
#[cfg(feature = "async")]
|
#[cfg(feature = "async")]
|
||||||
fn waker() -> &'static embassy_sync::waitqueue::AtomicWaker;
|
fn waker() -> &'static embassy_sync::waitqueue::AtomicWaker;
|
||||||
}
|
}
|
||||||
@ -765,10 +874,16 @@ where
|
|||||||
|
|
||||||
compiler_fence(core::sync::atomic::Ordering::SeqCst);
|
compiler_fence(core::sync::atomic::Ordering::SeqCst);
|
||||||
|
|
||||||
|
let max_chunk_size = if !circular || len > CHUNK_SIZE * 2 {
|
||||||
|
CHUNK_SIZE
|
||||||
|
} else {
|
||||||
|
len / 3 + len % 3
|
||||||
|
};
|
||||||
|
|
||||||
let mut processed = 0;
|
let mut processed = 0;
|
||||||
let mut descr = 0;
|
let mut descr = 0;
|
||||||
loop {
|
loop {
|
||||||
let chunk_size = usize::min(CHUNK_SIZE, len - processed);
|
let chunk_size = usize::min(max_chunk_size, len - processed);
|
||||||
let last = processed + chunk_size >= len;
|
let last = processed + chunk_size >= len;
|
||||||
|
|
||||||
let next = if last {
|
let next = if last {
|
||||||
@ -936,7 +1051,7 @@ where
|
|||||||
return Err(DmaError::OutOfDescriptors);
|
return Err(DmaError::OutOfDescriptors);
|
||||||
}
|
}
|
||||||
|
|
||||||
if circular && len < CHUNK_SIZE * 2 {
|
if circular && len <= 3 {
|
||||||
return Err(DmaError::BufferTooSmall);
|
return Err(DmaError::BufferTooSmall);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -996,12 +1111,26 @@ where
|
|||||||
} else {
|
} else {
|
||||||
unsafe {
|
unsafe {
|
||||||
while !((*ptr).next.is_null()
|
while !((*ptr).next.is_null()
|
||||||
|| (*ptr).next == self.descriptors as *mut _ as *mut DmaDescriptor)
|
|| (*ptr).next == addr_of_mut!(self.descriptors[0]))
|
||||||
{
|
{
|
||||||
let dw0 = ptr.read_volatile();
|
let dw0 = ptr.read_volatile();
|
||||||
self.available += dw0.len();
|
self.available += dw0.len();
|
||||||
ptr = ptr.offset(1);
|
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 (*ptr).next == addr_of_mut!(self.descriptors[0]) {
|
||||||
|
ptr = addr_of_mut!(self.descriptors[0]);
|
||||||
|
while ptr < descr_address {
|
||||||
|
let dw0 = ptr.read_volatile();
|
||||||
|
self.available += dw0.len();
|
||||||
|
ptr = ptr.offset(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1034,23 +1163,30 @@ where
|
|||||||
return Err(DmaError::Overflow);
|
return Err(DmaError::Overflow);
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe {
|
let mut remaining = data.len();
|
||||||
let src = data.as_ptr();
|
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())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn push_with(&mut self, f: impl FnOnce(&mut [u8]) -> usize) -> Result<usize, DmaError> {
|
||||||
|
let written = unsafe {
|
||||||
let dst = self.buffer_start.add(self.write_offset).cast_mut();
|
let dst = self.buffer_start.add(self.write_offset).cast_mut();
|
||||||
let count = usize::min(data.len(), self.buffer_len - self.write_offset);
|
let block_size = usize::min(self.available(), self.buffer_len - self.write_offset);
|
||||||
core::ptr::copy_nonoverlapping(src, dst, count);
|
let buffer = core::slice::from_raw_parts_mut(dst, block_size);
|
||||||
}
|
f(buffer)
|
||||||
|
};
|
||||||
|
|
||||||
if self.write_offset + data.len() >= self.buffer_len {
|
let mut forward = written;
|
||||||
let remainder = (self.write_offset + data.len()) % self.buffer_len;
|
|
||||||
let dst = self.buffer_start.cast_mut();
|
|
||||||
unsafe {
|
|
||||||
let src = data.as_ptr().add(data.len() - remainder);
|
|
||||||
core::ptr::copy_nonoverlapping(src, dst, remainder);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut forward = data.len();
|
|
||||||
loop {
|
loop {
|
||||||
unsafe {
|
unsafe {
|
||||||
let dw0 = self.write_descr_ptr.read_volatile();
|
let dw0 = self.write_descr_ptr.read_volatile();
|
||||||
@ -1069,10 +1205,10 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.write_offset = (self.write_offset + data.len()) % self.buffer_len;
|
self.write_offset = (self.write_offset + written) % self.buffer_len;
|
||||||
self.available -= data.len();
|
self.available -= written;
|
||||||
|
|
||||||
Ok(data.len())
|
Ok(written)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_listening_eof(&self) -> bool {
|
fn is_listening_eof(&self) -> bool {
|
||||||
|
|||||||
@ -224,6 +224,14 @@ where
|
|||||||
Ok(self.i2s_tx.tx_channel.push(data)?)
|
Ok(self.i2s_tx.tx_channel.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. Only useful for circular DMA transfers
|
||||||
|
pub fn push_with(&mut self, f: impl FnOnce(&mut [u8]) -> usize) -> Result<usize, Error> {
|
||||||
|
Ok(self.i2s_tx.tx_channel.push_with(f)?)
|
||||||
|
}
|
||||||
|
|
||||||
/// Stop for the DMA transfer and return the buffer and the
|
/// Stop for the DMA transfer and return the buffer and the
|
||||||
/// I2sTx instance.
|
/// I2sTx instance.
|
||||||
#[allow(clippy::type_complexity)]
|
#[allow(clippy::type_complexity)]
|
||||||
@ -2112,6 +2120,19 @@ pub mod asynch {
|
|||||||
let to_send = usize::min(avail, data.len());
|
let to_send = usize::min(avail, data.len());
|
||||||
Ok(self.i2s_tx.tx_channel.push(&data[..to_send])?)
|
Ok(self.i2s_tx.tx_channel.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<usize, Error> {
|
||||||
|
let avail = self.available().await;
|
||||||
|
Ok(self.i2s_tx.tx_channel.push_with(f)?)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Initiate an async DMA rx transfer
|
/// Initiate an async DMA rx transfer
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user