Skip to content

Speaker: keep the DMA fed at the hold level while waiting for new data - #391

Merged
lovyan03 merged 1 commit into
m5stack:developfrom
ainyan03:spk_dac_tail
Oct 7, 2026
Merged

lovyan03 merged 1 commit into
m5stack:developfrom
ainyan03:spk_dac_tail

Conversation

@ainyan03

@ainyan03 ainyan03 commented Oct 7, 2026

Copy link
Copy Markdown
Contributor

Fixes #371.

Problem

On the ESP32 internal DAC (e.g. M5Stack Basic), every tone ends with a short drop of the output to the DAC zero level followed by a jump back to the bias level, before the fade-out. It is heard as crackling at the end of each tone. Observed with Arduino core 3.x (ESP-IDF 5 I2S driver).

Cause

After the last samples of a sound, spk_task slept dma_buf_count times, for a time estimated from dma_buf_len, before writing the fade-out ramp. The DMA reached the auto-cleared part of the ring before the ramp was queued, so about half a DMA buffer of zeros was output, then the ramp started from the bias level.

Change

While waiting for new data, write one buffer of the hold level per step instead of sleeping. The blocking write paces the loop by the DMA itself, so the hold data and the ramp follow the last samples without a gap. The hold level is the current DAC bias for use_dac and 0 otherwise. The buzzer output keeps the timed wait (its behavior is unchanged).

Side effect: a new sound requested during this hold starts after the queued hold data (about one DMA ring later) instead of being packed right after the previous sound.

Test

  • M5Stack Basic, Arduino core 3.3.x, GPIO25 recorded with a logic analyzer, 12 tones of 500 ms with 50 ms gaps at volume 255: drops at the tone end 12 -> 0. Also checked by ear.
  • M5Stack Basic with Arduino core 2.x: no regression (no drop before and after).
  • M5Stack Core2 (I2S amplifier, mono): checked by ear.
  • Buzzer output (Basic, temporarily configured as buzzer on GPIO26): no truncation at the tone end, unchanged.
  • end() called right after a tone (during the hold writes), 42 times on Basic and Core2: all returned (max 3 ms / 10 ms) and begin() succeeded again.
  • Build: ESP32 / S3 / C3 / C5 / C6 / H2 / P4 with Arduino core 2.x / 3.x and ESP-IDF 5.1 to 6.1.

After the last samples of a sound, spk_task slept dma_buf_count times, for
a time estimated from dma_buf_len, before writing the fade-out ramp. With
the ESP-IDF 5 I2S driver (Arduino core 3.x) the ramp was observed to
arrive after the DMA had reached the auto-cleared region: the DAC output
dropped to the zero level for about half a DMA buffer and then jumped
back to the bias level at the end of every tone, heard as noise on the
ESP32 internal DAC (e.g. M5Stack Basic, m5stack#371). The legacy driver has the
same buffer layout and auto-clear, so the timed wait was fragile there as
well, although the drop was not observed with Arduino core 2.x.

Write one buffer of the hold level per step instead of sleeping. The
blocking write paces the loop by the DMA itself, so the hold data and the
ramp follow the last samples without a gap, and no timing estimate is
needed. The hold level is the current DAC bias for use_dac and 0
otherwise. The buzzer output keeps the timed wait, to leave its behavior
unchanged in this fix.

A new sound requested during this hold now starts after the queued hold
data, about one DMA ring later (dma_buf_count * dma_buf_len samples),
instead of being packed right after the previous sound.

Measured on M5Stack Basic (Arduino core 3.3.x, sample_rate 96 kHz,
dma_buf_len 256, dma_buf_count 8, GPIO25 with a logic analyzer, 12 tones
of 500 ms with 50 ms gaps): drops at the tone end 12 -> 0. The interval
from completion of the final audio write to submission of the fade-out
ramp is about 21 ms (previously about 15 ms).
@lovyan03
lovyan03 merged commit 6de06bf into m5stack:develop Oct 7, 2026
30 checks passed
@ainyan03
ainyan03 deleted the spk_dac_tail branch October 7, 2026 00:56
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.

2 participants