Skip to content

esp: don't busy-spin the MIDI task when the FreeRTOS tick is 100 Hz#960

Open
bwhitman wants to merge 1 commit into
mainfrom
fix-midi-poll-timeout
Open

esp: don't busy-spin the MIDI task when the FreeRTOS tick is 100 Hz#960
bwhitman wants to merge 1 commit into
mainfrom
fix-midi-poll-timeout

Conversation

@bwhitman

Copy link
Copy Markdown
Collaborator

The bug

esp_poll_midi() passes 1/portTICK_PERIOD_MS as the uart_read_bytes timeout, intending "block ~1 ms". At CONFIG_FREERTOS_HZ=100 — the default for both ESP-IDF and MicroPython — portTICK_PERIOD_MS is 10, so the expression is integer-zero: a non-blocking read. Since run_midi_task() is while(1){ esp_poll_midi(); }, the MIDI task busy-spins its pinned core at 100%, at priority ESP_TASK_PRIO_MAX-2 — which also starves anything below it on that core (esp_timer runs at 22).

AMYboard/tulipcc never see this because they ship CONFIG_FREERTOS_HZ=1000, where the same expression happens to equal 1 tick. Anyone embedding amy in a stock IDF or MicroPython project gets the spin.

Found on

Tulip 5 (ESP32-P4 running MicroPython, tick 100 Hz): per-core CPU read 100% / 14% with AMY sitting idle. With this fix: 1.9% / 4.7%.

The fix

pdMS_TO_TICKS(1) floored to one tick, so the task always sleeps:

  • at 1 kHz tick: unchanged behavior (1 ms block)
  • at 100 Hz tick: blocks 10 ms per poll — fine for 31250-baud MIDI (≤ ~31 bytes per poll, buffered by the uart driver), and infinitely better than a pegged core

🤖 Generated with Claude Code

esp_poll_midi() passed 1/portTICK_PERIOD_MS as the uart_read_bytes
timeout, meaning "block ~1 ms". But portTICK_PERIOD_MS is 10 at the
default CONFIG_FREERTOS_HZ=100 (ESP-IDF's and MicroPython's default),
so the expression is integer-zero: a non-blocking read. run_midi_task()
is while(1){esp_poll_midi();}, so on those builds the MIDI task
busy-spins its pinned core at 100% from priority ESP_TASK_PRIO_MAX-2,
starving anything below it on that core (esp_timer runs at 22).

Found on Tulip 5 (ESP32-P4, MicroPython, tick 100 Hz) as "cpu reads
100%/14% with AMY idle"; dropped to 1.9%/4.7% with the timeout fixed.
Never visible on AMYboard/tulipcc, which set CONFIG_FREERTOS_HZ=1000.

Use pdMS_TO_TICKS(1) floored to one tick, so the task always sleeps: at
1 kHz tick behavior is unchanged (1 ms), at 100 Hz it blocks 10 ms per
poll — fine for 31250-baud MIDI (~31 bytes buffered by the uart driver)
and infinitely better than a pegged core.
@github-actions

Copy link
Copy Markdown
Contributor

🎛️ AMY HW CI (AMYboard bench)

Flashed this PR's AMY (LoadTestChord: 6-voice Juno patch=1, one held note every 2 s) onto the physical AMYboard and measured the smoothed render load as the chord grows — back-to-back with the same sketch built at the PR's merge base, so Δ is this PR's own cost.

PASS — the bench ran the test to completion.

notes held main @ 3555365 this PR Δ
1 1378 1379 +1
2 1547 1546 -1
3 2134 2133 -1
4 2323 2324 +1
5 2962 2959 -3
6 3161 3159 -2

Full chord settled render μs: 3157 (was 3159, Δ -0.1%) (peak 3164, 39 samples)

⬇️ Artifacts: serial log · load trace · report

Self-hosted bench (amyboardci). FAIL means only that the test could not run — the load values are informational, with no threshold and no audio compare. See tools/arduino_loadsweep/.

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.

1 participant