Skip to content

Fix I2S DMA edge cases#5413

Open
main-- wants to merge 2 commits intoesp-rs:mainfrom
main--:main
Open

Fix I2S DMA edge cases#5413
main-- wants to merge 2 commits intoesp-rs:mainfrom
main--:main

Conversation

@main--
Copy link
Copy Markdown

@main-- main-- commented Apr 22, 2026

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 📝

  • I have updated existing examples or added new ones (if applicable).
  • I have used cargo xtask fmt-packages command to ensure that all changed code is formatted correctly.
  • My changes were added to the CHANGELOG.md in the proper section.
  • I have added necessary changes to user code to the latest Migration Guide.
  • My changes are in accordance to the esp-rs developer guidelines

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.

main-- added 2 commits April 22, 2026 15:29
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.
@bugadani
Copy link
Copy Markdown
Contributor

/hil full --test i2s

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 22, 2026

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).

@main--
Copy link
Copy Markdown
Author

main-- commented Apr 22, 2026

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.

@main--
Copy link
Copy Markdown
Author

main-- commented Apr 22, 2026

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?

@Dominaezzz
Copy link
Copy Markdown
Collaborator

and a chunk size of 800

How are you setting the chunk size?

@bjoernQ
Copy link
Copy Markdown
Contributor

bjoernQ commented Apr 22, 2026

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

@main--
Copy link
Copy Markdown
Author

main-- commented Apr 22, 2026

and a chunk size of 800

How are you setting the chunk size?

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

@Dominaezzz
Copy link
Copy Markdown
Collaborator

The real solution here is #5395 which would let the user configure the rx_eof_num register.
I'm not sure if it's worth debugging the current interface as is.

@main--
Copy link
Copy Markdown
Author

main-- commented Apr 23, 2026

I'm not sure if it's worth debugging the current interface as is.

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 len - 1 should be a quick and easy bugfix, but if you have concerns regarding the other chip variants then I'm afraid I can't help with testing it on those.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants