Skip to content

feat(m5stack-tab5): optional audio playback / recording and DPI DMA2D - #878

Open
finger563 wants to merge 4 commits into
mainfrom
feat/tab5-reclaim-internal-ram
Open

finger563 wants to merge 4 commits into
mainfrom
feat/tab5-reclaim-internal-ram

Conversation

@finger563

@finger563 finger563 commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

Description

Lets applications skip parts of the m5stack-tab5 BSP they don't use, to save internal DMA-capable RAM. Both are runtime parameters that follow the existing DisplayConfig / initialize_display(config) pattern. The existing calls keep their behavior.

  • initialize_audio(const AudioConfig &) takes sample_rate, playback_enabled, recording_enabled and task_config. Each direction is set up only when it's enabled: its I2S channel and DMA ring, its codec (ES8388 / ES7210) and its task.
    • A single channel runs as a simplex master. Two channels are created together as before and run full duplex.
    • The RX ring can't be made smaller on its own: both channels come from one i2s_new_channel() call (needed for full duplex) and share its dma_desc_num / dma_frame_num.
    • Without playback, play_audio() queues nothing, and volume() / mute() just store their value.
    • Without recording, start_audio_recording() fails.
    • audio_sample_rate(rate) reconfigures whichever channel drives the clocks.
    • initialize_audio(sample_rate, task_config) keeps its signature and enables both directions.
  • initialize_lcd(const LcdConfig &) takes dma2d_enabled. When it's false, the panel keeps CPU draw_bitmap() copies instead of calling esp_lcd_dpi_panel_enable_dma2d() (or setting flags.use_dma2d before IDF 6.0).
    • Rotated LVGL flushes go through the PPA straight into the frame buffer, so an application that always rotates never uses DMA2D.
    • initialize_lcd() is unchanged.

The README has a new "Saving internal DMA-capable RAM" section.

Motivation and Context

In 1.4.0 the I2S DMA ring doubled to 12 x 240 frames, for RX as well as TX, and DMA2D is now enabled on the ST71xx panels. Together that cut free internal DMA-capable RAM roughly in half in tab5-emu. The ESP-IDF SD card driver needs some of that RAM as a bounce buffer for unaligned transfers. With the reduced headroom, tab5-emu's TPM save states failed with sdmmc_cmd: allocate_dma_buf: not enough mem. tab5-emu never records and always runs in landscape.

How has this been tested?

  • First commit (Kconfig version): on an ST71xx Tab5, tab5-emu ran a soak with recording and DMA2D off: 8 game sessions across six emulators, each playing 90 s and saving and loading a state through the menu. Results are below. There were no errors, resets or audio underruns, and free internal RAM stayed flat.
  • This rework: tab5-emu built against abe3da8 with initialize_lcd({.dma2d_enabled = false}) and initialize_audio({.sample_rate = 48000, .recording_enabled = false}) ran the same 8-session soak on the Tab5. It was clean, with the same memory as the first run (83.1 KB internal and 43.7 KB DMA-capable free after GUI init), stable across launches, and no audio drops. The m5stack-tab5 example builds against it with the defaults. 74df8e1 changes only two log lines.
  • Not tested on hardware: the recording-only path (simplex RX master).
tab5-emu, after GUI init 1.4.0 recording and DMA2D off
internal RAM free 56.9 KB 83.2 KB
internal DMA-capable RAM free 17.5 KB 43.8 KB
saved by initialize_audio() 23.3 KB (RX ring, microphone task stack, RX buffer)
saved by initialize_lcd() (DMA2D) ~3 KB

Types of changes

  • New feature (non-breaking change which adds functionality)
  • Documentation Update

Checklist:

  • My change requires a change to the documentation.
  • I have updated the documentation accordingly.

🤖 Generated with Claude Code

https://claude.ai/code/session_01SFCMzomCZ5wRHJN4sxwkvY

…MA2D

Two opt-outs for applications that are short of internal DMA-capable RAM
(the SD card driver needs some of it for unaligned transfers):

- CONFIG_M5STACK_TAB5_AUDIO_RECORDING (default y): when disabled,
  initialize_audio() creates the I2S TX channel alone, with no RX channel
  (whose DMA ring matches the 12 x 240-frame playback ring), no ES7210 setup
  and no microphone task (whose stack is in internal RAM).
  start_audio_recording() then fails.
- CONFIG_M5STACK_TAB5_DPI_DMA2D (default y): when disabled, the DPI panel
  keeps CPU draw_bitmap() copies instead of enabling DMA2D. Rotated LVGL
  flushes go through the PPA straight into the frame buffer, so an
  application that always rotates never uses it.

Both defaults keep the current behavior.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SFCMzomCZ5wRHJN4sxwkvY
Copilot AI balanced review requested due to automatic review settings November 3, 2026 18:19
@github-actions

github-actions Bot commented Nov 3, 2026

Copy link
Copy Markdown

✅Static analysis result - no issues found! ✅

@finger563 finger563 left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the diff against main (70d1474). Both opt-outs keep the default paths byte-for-byte, and every recording-dependent call is behind kAudioRecordingEnabled (I checked the whole of audio.cpp on the branch, not just the hunks: audio_sample_rate(uint32_t) only touches TX, stop_audio_recording() is harmless without a task, the microphone callback can only run from the task that is not created). Using constexpr bool + plain if rather than #if around the bodies keeps the disabled code compiling, which is the right call. Nothing blocking; small points:

  1. The DMA2D saving is quoted as two different numbers. The Kconfig help and the README say "about 5 KB"; the test table in the description says "~3 KB". Worth settling on the measured one in all three places.
  2. microphone_volume(float) doc. The description says it only stores the value when recording is off, but the header comment on microphone_volume() was not updated (only initialize_audio() and start_audio_recording() got a \note). One line there would complete the picture.
  3. Startup log with recording off still reads "Initializing dual audio system (ES8388 + ES7210)"; the later "Recording disabled" line corrects it, so purely cosmetic.
  4. Heads-up on an upcoming merge conflict, not on this PR: the new README section is inserted at the same spot (just before "## Example") as the section the tear-free-flush PR I am preparing adds. Whichever lands second needs a one-line conflict resolution.
  5. Aside, pre-existing and out of scope: audio_sample_rate(uint32_t) reconfigures only the TX clock while the RX channel (when enabled) shares BCLK/WS and stays enabled through the change. Not something this PR touches, just noting it while I was in there.

🤖 Generated with Claude Code

https://claude.ai/code/session_01DFjjnq3XCTRAXENSxsCxJU

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

Copilot review overview

4 open issues
What changed in this PR

Adds Kconfig switches to let Tab5 applications opt out of (1) audio recording setup and (2) DPI DMA2D, reducing internal DMA-capable RAM usage while keeping defaults unchanged.

Changes:

  • Add CONFIG_M5STACK_TAB5_AUDIO_RECORDING to optionally skip ES7210 + I2S RX + microphone task setup.
  • Add CONFIG_M5STACK_TAB5_DPI_DMA2D to optionally avoid DMA2D usage for DPI panel draw_bitmap() copies.
  • Document the RAM-saving options in the component README.
File Description
components/​m5stack-tab5/​src/​video.cpp Gate pre-IDF6 DPI use_dma2d config and log DMA2D-disabled behavior.
components/​m5stack-tab5/​src/​audio.cpp Add build-time opt-out for recording setup; guard RX channel, ES7210 init, mic task, and recording API.
components/​m5stack-tab5/​include/​m5stack-tab5.hpp Document behavior changes when audio recording is disabled.
components/​m5stack-tab5/​README.md Add “Saving internal DMA-capable RAM” section describing the new options.
components/​m5stack-tab5/​Kconfig Introduce the two new Kconfig options and their help text.

Review effort: Lite

Comment thread components/m5stack-tab5/Kconfig Resolved
Comment thread components/m5stack-tab5/README.md Resolved
Comment thread components/m5stack-tab5/src/audio.cpp Resolved
Comment thread components/m5stack-tab5/src/video.cpp Resolved
…tions

Replace the two Kconfig options with parameters, following the
DisplayConfig / initialize_display(config) pattern:

- initialize_audio(const AudioConfig &): sample_rate, playback_enabled,
  recording_enabled and task_config. Each direction is set up only when
  enabled (its I2S channel and DMA ring, its codec and its task); alone,
  either channel runs as a simplex master. initialize_audio(sample_rate,
  task_config) keeps its signature and enables both.
- initialize_lcd(const LcdConfig &): dma2d_enabled. initialize_lcd() keeps
  the current behavior.

play_audio(), volume() and mute() do nothing for the ES8388 without
playback, start_audio_recording() fails without recording, and the sample
rate setter reconfigures whichever channel drives the clocks.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SFCMzomCZ5wRHJN4sxwkvY
@finger563 finger563 changed the title feat(m5stack-tab5): Kconfig options to skip audio recording and DPI DMA2D feat(m5stack-tab5): optional audio playback / recording and DPI DMA2D Nov 3, 2026
@finger563
finger563 requested a balanced review from Copilot November 3, 2026 20:40
- audio_sample_rate(rate) also disables the RX channel while it
  reconfigures the shared clock in full duplex
- volume(), mute() and microphone_volume() note that they only store the
  value when their direction is not set up
- quote the measured DMA2D saving (about 3 KB) everywhere

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SFCMzomCZ5wRHJN4sxwkvY
@finger563

Copy link
Copy Markdown
Contributor Author

Thanks for the review. Going through the points against the current head (abe3da8):

  1. DMA2D figure: now about 3 KB (measured) everywhere. The Kconfig help is gone; the README and the LcdConfig::dma2d_enabled doc both say 3 KB.
  2. microphone_volume(float) doc: added a note that it only stores the value without recording. volume() and mute() got the same note for playback, which is now optional too.
  3. Startup log: it now names only what it sets up, e.g. Initializing audio system (ES8388 playback) at 48000 Hz.
  4. README conflict with the tear-free-flush PR: noted. Whichever lands second resolves it.
  5. audio_sample_rate(rate) with RX running: fixed here, since this PR rewrote that function anyway. It now also disables RX while it reconfigures the shared clock, and re-enables it afterwards. Without playback, it reconfigures the RX channel, which then drives the clocks itself.

Since your review, 1929957 replaced the Kconfig options with runtime parameters: initialize_audio(AudioConfig{.playback_enabled, .recording_enabled, ...}) and initialize_lcd(LcdConfig{.dma2d_enabled}). The existing calls keep their behavior.

🤖 Generated with Claude Code

https://claude.ai/code/session_01SFCMzomCZ5wRHJN4sxwkvY

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines +167 to +170
if (!es8388_i2c_device_) {
logger_.error("Could not initialize ES8388 I2C device: {}", ec.message());
return false;
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed it would be better, but this predates the PR. On main, initialize_audio() also returns false after creating and enabling both channels and the stream buffer, and its I2S calls are ESP_ERROR_CHECKs that abort rather than return. This PR keeps the same failure semantics and only makes each step conditional. A proper unwind (disable and delete the channels, delete the stream, stop the tasks, release the I2C devices, reset the codec state) touches every step, so it belongs in its own PR. Leaving this open as a follow-up.

Comment on lines +241 to 244
if (es7210_adc_init(&es7210_cfg) != ESP_OK) {
logger_.error("ES7210 init failed");
return false;
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as the other thread on this function: pre-existing failure semantics, left for a separate cleanup PR.

Comment thread components/m5stack-tab5/src/audio.cpp Resolved
Comment thread components/m5stack-tab5/src/video.cpp Resolved
Comment thread components/m5stack-tab5/src/video.cpp Resolved
Comment thread components/m5stack-tab5/src/video.cpp Resolved
Comment thread components/m5stack-tab5/src/video.cpp Resolved
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SFCMzomCZ5wRHJN4sxwkvY

This branch has not been deployed

No deployments
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