Skip to content

audioif: the GIGA renders its audio from an interrupt - #392

Merged
bdbarnett merged 3 commits into
mainfrom
giga-irq-render
Nov 10, 2026
Merged

bdbarnett merged 3 commits into
mainfrom
giga-irq-render

Conversation

@bdbarnett

@bdbarnett bdbarnett commented Nov 10, 2026 •

Copy link
Copy Markdown
Contributor

On the Arduino GIGA R1, the audio pump has rendered on the interpreter, so anything that held the interpreter for longer than the DAC's 93 ms ring played silence: a garbage collection, a write to /flash, a burst of BLE callbacks. This moves the render to an interrupt of its own (SWPMI1, NVIC priority 7, on a 12 KB stack of its own), raised by the DAC's DMA and the 4 ms soft timer. This is the driver half of #290 and #291; the other half is audiodsp's interrupt hooks.

The interrupt sits below the flash code's mask (priority 6), which masks one block operation at a time, so the render catches up between blocks. _audioif.render_mode() switches between "irq" and "interpreter" at run time, and _audioif.render_stats() reports renders, the longest render, stack used and the priority. Built against an audiodsp without AUDIODSP_PORT_IRQ_HOOKS (today's pin), the driver stays on the interpreter as before and render_mode("irq") raises. The ESP32, unix and Windows drivers don't change.

What I ran, on a GIGA R1 with the Display Shield, playing Synth Station's electric piano with a held chord, gc.collect() every 3 s and a 32 KB write to /flash every 30 s, for ten minutes:

Firmware Starved frames Of played Longest gc Longest write
Interpreter render (before) 1,000,908 28,808,278 (3.5%) 111 ms 856 ms
Interrupt render 0 28,659,242 148 ms 1,172 ms
Interrupt build, render_mode("interpreter"), 2 min 194,806 5,768,392 (3.4%) 107 ms 860 ms

The longest render was 24.9 ms and the deepest stack 4,396 of 12,288 bytes. #291's case, 60 s of a PC central writing about 50 times a second while the board notifies every 10 ms, starved 1,721 frames before and 0 after. A full-screen LVGL refresh took a median 97 ms on the interpreter render and 99 ms on the interrupt render, with nothing starved in either. The board booted to its Home app and joined Wi-Fi on three resets.

On unix, a render through audiodsp's interrupt-hooks branch is byte-identical to one through its main (same SHA-256), and the audioif probes pass.

Not verified: the measurements above used an audiodsp with the interrupt hooks, which isn't merged yet; until it is and the pin moves, CI builds this in interpreter mode. One earlier interrupt build hung once and didn't reproduce in 300 play/stop cycles afterwards; the interpreter-side races found while chasing it are fixed on the audiodsp side.

The DAC's pump ran on the interpreter, so a garbage collection, a write to
/flash or a long Python callback emptied the 93 ms ring and the jack played
silence. It now runs from an interrupt of the driver's own (SWPMI1's vector,
priority 7, below the flash code's mask) on a 12 KB stack of its own, with the
heap locked. The interpreter's side of the pump lock holds the interrupt off,
and _audioif.render_mode() switches back to the interpreter.

The interrupt needs an audiodsp with render_kick (AUDIODSP_PORT_IRQ_HOOKS);
built against an older one the pump stays on the interpreter, as before.
Moves the audiodsp pin from 6d08b07 to 46e9224: the render_kick hook the
GIGA driver here uses to render from its DAC interrupt (audiodsp#403), the
20 ms walk of a Mix or level move in the nodes (audiodsp#400), and type
stubs for thirteen modules (audiodsp#401).
@bdbarnett
bdbarnett merged commit e71028a into main Nov 10, 2026
32 checks passed
@bdbarnett
bdbarnett deleted the giga-irq-render branch November 10, 2026 15:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant