Fix I2S DMA edge cases#5413
Conversation
While the TRM states that an interrupt is triggered once I2S_RX_EOF_NUM + 1 samples have been received, in reality my ESP32-C6 behaves very differently: 1. The documented multiplication by I2S_RX_BITS_MOD+1 does not happen, instead the value of I2S_RX_EOF_NUM is rounded up to the next multiple of I2S_RX_BITS_MOD+1 2. The addition of 1 to the value of I2S_RX_EOF_NUM does not happen either. The transfer finishes once I2S_RX_EOF_NUM bytes (rounded up) have been read. The subtraction here was probably supposed to counteract the +1, but in my testing turns out to be wrong, because the addition does not happen. However, this problem only happens in 8-bit mode, because otherwise the observed rounding simply reverts the subtraction.
The value written by rx_start to the underlying register is truncated to 12 bits, so for buffers larger than 4095 bytes we are effectively writing a random number here. For example, allocating 13kB currently creates three 4092 byte buffers and one 724 byte buffer. However 13000 & 0xFFF = 712, so after every 712 bytes the DMA transfer would switch over to the next buffer, effectively wasting 78% of the allocated space. Empirical testing on ESP32-C6 shows that passing the maximum value (0xFFF) resolves the issue and causes full buffers to be returned regardless of the buffer size. Unfortunately this does not seem to be not documented anywhere.
|
/hil full --test i2s |
|
Triggered full HIL run for #5413. Run: https://github.com/esp-rs/esp-hal/actions/runs/24785964346 Status update: ❌ HIL (full) run failed (conclusion: failure). |
|
Hm, I just did a few more tests and it seems that the second commit is not working as expected. This is all very strange. With a 2400 bytes buffer and a chunk size of 800 (which is clearly not a multiple of 4095) it's working as it should (i.e. all buffers are full). But dividing the same 2400 bytes buffer into 160 byte chunks suddenly falls back to the pattern where after every 4095 bytes one incomplete buffer is returned. |
|
In that case I should probably just change it to use the size of one chunk (like ESP-IDF) and throw away the last chunk in case it's shorter. What do you think? |
How are you setting the chunk size? |
|
I remember that S2 and even more the original ESP32 showed surprising behavior when implementing I2S - just saying we need to carefully test with these especially |
Any buffer smaller than 4092 gets cut into three chunks, and 2400 / 3 = 800 For the 2400/160 test I added a new API to customize chunk size though |
|
The real solution here is #5395 which would let the user configure the rx_eof_num register. |
Fair enough. Though I have to work with what exists right now, and I wanted to at least raise awareness of these findings. I'm fairly confident that the first commit regarding |
Thank you for your contribution!
We appreciate the time and effort you've put into this pull request.
To help us review it efficiently, please ensure you've gone through the following checklist:
Submission Checklist 📝
cargo xtask fmt-packagescommand to ensure that all changed code is formatted correctly.CHANGELOG.mdin the proper section.Extra:
Pull Request Details 📖
Description
While working with I2S on the ESP32-C6 I found two small problems concerning edge cases in less common I2S configurations (8-bit mode, weird DMA buffer sizes). Both problems are related to the I2S_RX_EOF_NUM register, which esp-hal is currently setting to the total size of all DMA buffers allocated for the transfer minus one, which appears to be just wrong according to my observations.
In ESP-IDF the value of this register is always set to the size of one DMA buffer. However they also ensure that each buffer has the same size, unlike in esp-hal where the last buffer may be shorter than the others.
I'm not sure how to deal with this or if you're willing to accept it at all, considering that the observed behavior here directly contradicts the TRM as I understand it, so I did not bother writing a changelog just yet. I'm not too familiar with these low-level details, so it's also entirely possible that I am just totally mistaken here.
Testing
Tested on ESP32-C6 with NAU88C22 in 16-bit 48kHz mode and in 8-bit A-Law 8kHz mode.