Skip to content

M5Stack Core S3 Audio Display#364

Open
troyhacks wants to merge 10 commits into
MoonModules:mdevfrom
troyhacks:M5Stack_Core_S3_Display
Open

M5Stack Core S3 Audio Display#364
troyhacks wants to merge 10 commits into
MoonModules:mdevfrom
troyhacks:M5Stack_Core_S3_Display

Conversation

@troyhacks

@troyhacks troyhacks commented May 20, 2026

Copy link
Copy Markdown
Collaborator

The goal was to add a visualizer to the display on an M5Stack Core S3 board with their ModuleAudio add-on device so you can see it's working. It's intended to be used as a standalone line-in device for AudioReactive network sync.

This was an on-site hack as a gift to HexSauna camp at SideBurn 2026 to give their lighting lead something that didn't exist in WLED or WLED-MM... and looked fancy AF.

chrome_9RdK6uHAcj

Because of the WLED-MM FPS limiter being able to be set to "unlimited", the code keeps a minimum of 5 FPS on the display but otherwise stays out of the way as best it can. It also pushes a maximum of 100 FPS to the display match AudioReactive. The minimum was established because if you're using this display usermod, you likely want to see it do something.

Drawing the display uses a differential system so only changes are painted. This decreases the display overhead by 80% or more on average.

This PR also fixes a bug in xml.cpp:

if (psramFound()) oappend(SET_F(",33,34,35,36,37"));

This was unguarded on the S3 with PSRAM detected, so any S3 board blocked off these pins even if it's QSPI, as the Core S3 board uses. This is set now only when octal modes are used. The Core S3 has 8MB of PSRAM but it's QSPI, likely a design tradeoff to keep more pins usable for their addon boards but still ha e lots of PSRAM.

Intention was to combine the M5Stack Ethernet module with their Audio module, but they are incompatible due to overlapping pins.

This uses LovyanGFX as that sidesteps a TON of issues with TFT_eSPI in "keeping it simple".

@softhack007 This is absolutely "vibe coded" but with sane ideas and multiple rounds of code audits. My personal LTO script for the S3 is in here - it's not required, but could be added to the codebase.

Summary by CodeRabbit

  • New Features

    • Added M5Stack Core S3 Display usermod: header with SSID/IP, 16‑band audio‑reactive bouncing bars, simulated motion fallback, per‑bar colors and FPS control.
  • Documentation

    • Added Core S3 Display setup guide: pin mappings, build config and display/LovyanGFX notes.
  • Chores

    • Added automatic build helper to enable LTO for ESP32‑S3 builds.
    • Updated VS Code recommendation and .gitignore.
  • Bug Fixes

    • ESP32‑S3 GPIO reservation now respects flash/PSRAM mode settings.

@coderabbitai

coderabbitai Bot commented May 20, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

Adds a new M5Stack Core S3 display usermod (ILI9342 over SPI3) with docs, a PlatformIO helper script to enable LTO for ESP32‑S3 toolchains, and gates octal-PSRAM GPIO reservations on octal-mode build flags.

Changes

M5Stack Core S3 Display Usermod

Layer / File(s) Summary
Usermod ID and registration
wled00/const.h, wled00/usermods_list.cpp
Defines USERMOD_ID_M5STACK_CORE_S3_DISPLAY (95) and conditionally includes/registers the usermod via compile-time flag.
LGFX device, constants, and setup()
usermods/M5Stack_CoreS3_Display/usermod_m5stack_s3_display.h
Adds LovyanGFX ILI9342 wrapper, global tft, hardware constants, and implements setup() to init I2C/AXP2101, reset via AW9523B, lock SPI pins, and draw a loading splash.
Rendering loop with audio integration
usermods/M5Stack_CoreS3_Display/usermod_m5stack_s3_display.h
Throttles updates, redraws header when needed, reads GEQ bands from AudioReactive (or simulates), and renders 16 differential bouncing bars.
WLED JSON, config, and getId()
usermods/M5Stack_CoreS3_Display/usermod_m5stack_s3_display.h
Adds JSON info/state for enabled status, persists/restores enabled in config, appends config UI data, and returns the stable usermod ID. Note: readFromConfig() uses `top["enabled"]
Documentation
usermods/M5Stack_CoreS3_Display/readme.md
Documents pinout, PlatformIO override build flags, lib deps, feature list, display notes, and recommended AudioReactive settings.

LTO Build Optimization for ESP32-S3

Layer / File(s) Summary
enable_lto_s3.py script
pio-scripts/enable_lto_s3.py
Adds _find_tool() and enable_lto(env) to append LTO compiler/linker flags when missing, resolve env['CC'] to locate toolchain binaries, and prefer gcc-ar/gcc-ranlib wrappers; script invokes enable_lto(env) at import time.

ESP32-S3 PSRAM GPIO Reservation

Layer / File(s) Summary
Octal PSRAM GPIO gating
wled00/xml.cpp
Only appends GPIOs 33–37 to reserved list when octal PSRAM/Flash mode config (CONFIG_SPIRAM_MODE_OCT or CONFIG_ESPTOOLPY_FLASHMODE_OPI) is enabled.

Editor & gitignore tweaks

Layer / File(s) Summary
Gitignore and VS Code recommendation
.gitignore, .vscode/extensions.json
Adds .claude to .gitignore and replaces the VS Code PlatformIO extension recommendation with pioarduino.pioarduino-ide.

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • softhack007
  • netmindz
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 69.23% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and concisely summarizes the main change: adding an M5Stack Core S3 audio display visualization usermod.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 Infer (1.2.0)
wled00/usermods_list.cpp

In file included from wled00/usermods_list.cpp:1:
wled00/wled.h:99:10: fatal error: 'Arduino.h' file not found
99 | #include <Arduino.h>
| ^~~~~~~~~~~
1 error generated.
Error: the following clang command did not run successfully:
/opt/infer-linux-x86_64-v1.2.0/lib/infer/facebook-clang-plugins/clang/install/bin/clang-18
@/tmp/coderabbit-infer/e7b2db497b6adfa7a92ea2b56151b98435a0e2e6-109addb7cdea30fd/tmp/clang_command_.tmp.531e3b.txt
++Contents of '/tmp/coderabbit-infer/e7b2db497b6adfa7a92ea2b56151b98435a0e2e6-109addb7cdea30fd/tmp/clang_command_.tmp.531e3b.txt':
"-cc1" "-load"
"/opt/infer-linux-x86_64-v1.2.0/lib/infer/infer/bin/../../facebook-clang-plugins/libtooling/build/FacebookClangPlugin.dylib"
"-add-plugin" "BiniouASTExporter" "-plugin-arg-BiniouASTExporter" "-"
"-plugin-arg-BiniouASTExporter" "PREPEND_CURRENT_DIR=1"
"-plugin-arg-BiniouASTExporter" "MAX_STRING_SIZE=65535" "-cc1" "-triple"
"x86_64-unknown-linux-gnu" "-emit-obj" "-

... [truncated 1094 characters] ...

ng/install/lib/clang/18/include"
"-internal-isystem" "/usr/local/include" "-internal-isystem"
"/usr/lib/gcc/x86_64-linux-gnu/12/../../../../x86_64-linux-gnu/include"
"-internal-externc-isystem" "/usr/include/x86_64-linux-gnu"
"-internal-externc-isystem" "/include" "-internal-externc-isystem"
"/usr/include" "-Wno-ignored-optimization-argument" "-Wno-everything"
"-fdeprecated-macro" "-ferror-limit" "19" "-fgnuc-version=4.2.1"
"-fskip-odr-check-in-gmf" "-fcxx-exceptions" "-fexceptions"
"-D__GCC_HAVE_DWARF2_CFI_ASM=1" "-o"
"/tmp/coderabbit-infer/109addb7cdea30fd/file.o" "-x" "c++"
"wled00/usermods_list.cpp" "-O0" "-fno-builtin" "-include"
"/opt/infer-linux-x86_64-v1.2.0/lib/infer/infer/bin/../lib/clang_wrappers/global_defines.h"
"-Wno-everything"

wled00/xml.cpp

In file included from wled00/xml.cpp:1:
wled00/wled.h:99:10: fatal error: 'Arduino.h' file not found
99 | #include <Arduino.h>
| ^~~~~~~~~~~
1 error generated.
Error: the following clang command did not run successfully:
/opt/infer-linux-x86_64-v1.2.0/lib/infer/facebook-clang-plugins/clang/install/bin/clang-18
@/tmp/coderabbit-infer/e7b2db497b6adfa7a92ea2b56151b98435a0e2e6-ea99264d317ef01a/tmp/clang_command_.tmp.9d866f.txt
++Contents of '/tmp/coderabbit-infer/e7b2db497b6adfa7a92ea2b56151b98435a0e2e6-ea99264d317ef01a/tmp/clang_command_.tmp.9d866f.txt':
"-cc1" "-load"
"/opt/infer-linux-x86_64-v1.2.0/lib/infer/infer/bin/../../facebook-clang-plugins/libtooling/build/FacebookClangPlugin.dylib"
"-add-plugin" "BiniouASTExporter" "-plugin-arg-BiniouASTExporter" "-"
"-plugin-arg-BiniouASTExporter" "PREPEND_CURRENT_DIR=1"
"-plugin-arg-BiniouASTExporter" "MAX_STRING_SIZE=65535" "-cc1" "-triple"
"x86_64-unknown-linux-gnu" "-emit-obj" "-mrelax-all

... [truncated 1060 characters] ...

lugins/clang/install/lib/clang/18/include"
"-internal-isystem" "/usr/local/include" "-internal-isystem"
"/usr/lib/gcc/x86_64-linux-gnu/12/../../../../x86_64-linux-gnu/include"
"-internal-externc-isystem" "/usr/include/x86_64-linux-gnu"
"-internal-externc-isystem" "/include" "-internal-externc-isystem"
"/usr/include" "-Wno-ignored-optimization-argument" "-Wno-everything"
"-fdeprecated-macro" "-ferror-limit" "19" "-fgnuc-version=4.2.1"
"-fskip-odr-check-in-gmf" "-fcxx-exceptions" "-fexceptions"
"-D__GCC_HAVE_DWARF2_CFI_ASM=1" "-o"
"/tmp/coderabbit-infer/ea99264d317ef01a/file.o" "-x" "c++"
"wled00/xml.cpp" "-O0" "-fno-builtin" "-include"
"/opt/infer-linux-x86_64-v1.2.0/lib/infer/infer/bin/../lib/clang_wrappers/global_defines.h"
"-Wno-everything"


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (2)
pio-scripts/enable_lto_s3.py (1)

47-50: ⚡ Quick win

Derive toolchain prefix from compiler basename, not global string replacement.

Current replacement is applied to the entire CC value and can mangle paths, causing gcc-ar/gcc-ranlib lookup to fail silently.

Suggested fix
-        toolchain_prefix = cc.replace("gcc", "").replace("g++", "")
-        # toolchain_prefix is something like "xtensa-esp32s3-elf-"
-        new_ar     = toolchain_prefix + "gcc-ar"
-        new_ranlib = toolchain_prefix + "gcc-ranlib"
+        cc_name = os.path.basename(cc)
+        # toolchain_prefix is something like "xtensa-esp32s3-elf-"
+        if cc_name.endswith("gcc"):
+            toolchain_prefix = cc_name[:-3]
+        elif cc_name.endswith("g++"):
+            toolchain_prefix = cc_name[:-3]
+        else:
+            toolchain_prefix = ""
+
+        new_ar = f"{toolchain_prefix}gcc-ar"
+        new_ranlib = f"{toolchain_prefix}gcc-ranlib"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pio-scripts/enable_lto_s3.py` around lines 47 - 50, The current code mangles
CC when replacing "gcc"/"g++" across the whole path; instead derive
toolchain_prefix from the compiler basename: get cc_basename =
os.path.basename(cc), strip a trailing "gcc" or "g++" only from cc_basename
(handle possible suffixes like ".exe" first if needed) to produce
toolchain_prefix, then build new_ar and new_ranlib from that prefix (use the
variables cc, toolchain_prefix, new_ar, new_ranlib to locate the change). This
ensures you only remove the compiler name, not parts of the path, preventing
broken paths when constructing gcc-ar/gcc-ranlib.
usermods/M5Stack_CoreS3_Display/usermod_m5stack_s3_display.h (1)

127-251: ⚡ Quick win

Move repeated literals to static const char[] PROGMEM.

This usermod repeatedly uses identical string literals ("M5StackS3Display...", "M5StackS3Display", "M5S3_enabled"). Centralizing them in PROGMEM reduces RAM churn and keeps usermod style consistent.

As per coding guidelines, usermods/**/*.{cpp,h,hpp}: Store repeated strings in usermods as static const char[] PROGMEM.

Also applies to: 356-401

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@usermods/M5Stack_CoreS3_Display/usermod_m5stack_s3_display.h` around lines
127 - 251, The code repeatedly emits identical string literals (e.g.
"M5StackS3Display: starting setup", "M5StackS3Display: setup complete",
"M5StackS3Display: init TFT", "M5StackS3Display", "Loading...", "WLED",
"M5S3_enabled", and the various Serial.printf messages) which should be moved
into static const char[] stored in PROGMEM and referenced instead to reduce RAM
usage; add a small block of static const char[] PROGMEM declarations at the top
of usermod_m5stack_s3_display.h and replace all literal usages in the setup
routine (the Serial.println/printf calls, tft.drawString calls, and any
configuration keys) with references to those PROGMEM constants (using FPSTR or
appropriate pgm functions/macros) so the strings are read from flash rather than
duplicated in RAM.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@usermods/M5Stack_CoreS3_Display/readme.md`:
- Around line 20-34: The Markdown code fence is malformed: replace the opening
line that reads "```build_flags =" with a plain triple-backtick line so the
block starts with ``` followed by the existing lines (including the build_flags
= and USERMOD_M5STACK_CORE_S3_DISPLAY entries) and ensure the block is closed
with a matching triple-backtick after the LovyanGFX URL in the lib_deps section
so the entire build_flags / lib_deps snippet (including keys like build_flags,
lib_deps, USERMOD_M5STACK_CORE_S3_DISPLAY and the LovyanGFX dependency) is
inside a single valid fenced code block.
- Around line 47-50: The README claims "BGR color order" but the usermod sets
cfg.rgb_order = false (commented as RGB); fix the mismatch by either updating
the README to state "RGB color order" to match cfg.rgb_order = false and adjust
the README comment about inversion/backlight if needed, or change cfg.rgb_order
to true (and update the inline comment) if BGR was intended—ensure consistency
between the README entry and the cfg.rgb_order setting and its inline comment.

In `@usermods/M5Stack_CoreS3_Display/usermod_m5stack_s3_display.h`:
- Around line 142-143: The I2C reads call Wire.requestFrom(...) and immediately
use Wire.read() without verifying data was received; update each occurrence that
reads from AXP2101_ADDR (where variables like pwr_orig and similar bytes are
assigned) to first check the return value or Wire.available() >= 1 and only call
Wire.read() when a byte is available, otherwise handle the error path (skip the
read, return/error out, or set a safe default and log the failure) to avoid
using invalid register data from failed I2C transactions; apply this change to
all occurrences that read from AXP2101_ADDR in usermod_m5stack_s3_display.h.
- Around line 225-229: If pinManager.allocateMultiplePins(pins, 4,
PinOwner::UM_Unspecified) fails, abort further initialization so the usermod
does not drive TFT/SPI pins; specifically, inside the same setup/init function
(where the Serial prints occur) stop any subsequent pinMode/digitalWrite/SPI/TFT
initialization, set the module state to disabled (e.g., a flag like _enabled =
false or return false from setupUsermod), and optionally release any partial
allocations via pinManager.releaseMultiplePins if available; ensure no further
calls to functions that drive the pins (TFT.begin, SPI.begin, pinMode for pins
in pins[]) execute when allocation failed.

---

Nitpick comments:
In `@pio-scripts/enable_lto_s3.py`:
- Around line 47-50: The current code mangles CC when replacing "gcc"/"g++"
across the whole path; instead derive toolchain_prefix from the compiler
basename: get cc_basename = os.path.basename(cc), strip a trailing "gcc" or
"g++" only from cc_basename (handle possible suffixes like ".exe" first if
needed) to produce toolchain_prefix, then build new_ar and new_ranlib from that
prefix (use the variables cc, toolchain_prefix, new_ar, new_ranlib to locate the
change). This ensures you only remove the compiler name, not parts of the path,
preventing broken paths when constructing gcc-ar/gcc-ranlib.

In `@usermods/M5Stack_CoreS3_Display/usermod_m5stack_s3_display.h`:
- Around line 127-251: The code repeatedly emits identical string literals (e.g.
"M5StackS3Display: starting setup", "M5StackS3Display: setup complete",
"M5StackS3Display: init TFT", "M5StackS3Display", "Loading...", "WLED",
"M5S3_enabled", and the various Serial.printf messages) which should be moved
into static const char[] stored in PROGMEM and referenced instead to reduce RAM
usage; add a small block of static const char[] PROGMEM declarations at the top
of usermod_m5stack_s3_display.h and replace all literal usages in the setup
routine (the Serial.println/printf calls, tft.drawString calls, and any
configuration keys) with references to those PROGMEM constants (using FPSTR or
appropriate pgm functions/macros) so the strings are read from flash rather than
duplicated in RAM.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f8f180cd-573c-4944-8c1c-a6936cf49480

📥 Commits

Reviewing files that changed from the base of the PR and between f4d15d1 and 4ae70a4.

📒 Files selected for processing (6)
  • pio-scripts/enable_lto_s3.py
  • usermods/M5Stack_CoreS3_Display/readme.md
  • usermods/M5Stack_CoreS3_Display/usermod_m5stack_s3_display.h
  • wled00/const.h
  • wled00/usermods_list.cpp
  • wled00/xml.cpp

Comment thread usermods/M5Stack_CoreS3_Display/readme.md Outdated
Comment thread usermods/M5Stack_CoreS3_Display/readme.md
Comment thread usermods/M5Stack_CoreS3_Display/usermod_m5stack_s3_display.h Outdated
Comment thread usermods/M5Stack_CoreS3_Display/usermod_m5stack_s3_display.h Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
usermods/M5Stack_CoreS3_Display/usermod_m5stack_s3_display.h (1)

316-318: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Validate um_data->u_data[2] before memcpy.

getUMData() returning true confirms the usermod exists, but doesn't guarantee u_data[2] is a valid pointer. If AudioReactive's data layout changes or initialization is incomplete, this memcpy could dereference null.

Suggested fix
         um_data_t *um_data = nullptr;
         if (usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) {
             // Real audio data
-            memcpy(geq, um_data->u_data[2], NUM_GEQ_BANDS);
+            if (um_data && um_data->u_data[2]) {
+                memcpy(geq, um_data->u_data[2], NUM_GEQ_BANDS);
+            } else {
+                memset(geq, 0, NUM_GEQ_BANDS);  // fallback to silence
+            }
         } else {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@usermods/M5Stack_CoreS3_Display/usermod_m5stack_s3_display.h` around lines
316 - 318, The memcpy from um_data->u_data[2] into geq can dereference a null
pointer even when usermods.getUMData(...) returns true; before calling memcpy in
the block that checks USERMOD_ID_AUDIOREACTIVE, verify that um_data is non-null
and that um_data->u_data[2] is a valid pointer (and ideally that it points to at
least NUM_GEQ_BANDS bytes) — if the pointer is null or size is insufficient,
skip the memcpy (or zero/clear geq) and emit a warning/error via the same
logging mechanism so the code does not crash. Ensure you reference
usermods.getUMData, um_data, um_data->u_data[2], memcpy, geq, and NUM_GEQ_BANDS
when implementing the check.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@pio-scripts/enable_lto_s3.py`:
- Around line 47-57: The cc_basename stripping logic leaves a trailing dash and
yields names like "xtensa-...-elf--gcc-ar"; update the removal to drop the
preceding dash as well (e.g., remove "-gcc" or "-g++" rather than just
"gcc"/"g++") when computing cc_basename so new_ar and new_ranlib produce
"xtensa-...-elf-gcc-ar" and "xtensa-...-elf-gcc-ranlib"; locate the code around
the cc_basename manipulation (the block that checks
cc_basename.endswith(".exe"), endswith("gcc") and endswith("g++")) and change
the slice or use a safer approach (e.g., rsplit with "-" or strip the suffix
including the dash) so the generated tool names are correct.

---

Outside diff comments:
In `@usermods/M5Stack_CoreS3_Display/usermod_m5stack_s3_display.h`:
- Around line 316-318: The memcpy from um_data->u_data[2] into geq can
dereference a null pointer even when usermods.getUMData(...) returns true;
before calling memcpy in the block that checks USERMOD_ID_AUDIOREACTIVE, verify
that um_data is non-null and that um_data->u_data[2] is a valid pointer (and
ideally that it points to at least NUM_GEQ_BANDS bytes) — if the pointer is null
or size is insufficient, skip the memcpy (or zero/clear geq) and emit a
warning/error via the same logging mechanism so the code does not crash. Ensure
you reference usermods.getUMData, um_data, um_data->u_data[2], memcpy, geq, and
NUM_GEQ_BANDS when implementing the check.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 0af1bb63-cfb8-4901-970d-114090c625fe

📥 Commits

Reviewing files that changed from the base of the PR and between 4ae70a4 and 208c1e9.

📒 Files selected for processing (3)
  • pio-scripts/enable_lto_s3.py
  • usermods/M5Stack_CoreS3_Display/readme.md
  • usermods/M5Stack_CoreS3_Display/usermod_m5stack_s3_display.h
✅ Files skipped from review due to trivial changes (1)
  • usermods/M5Stack_CoreS3_Display/readme.md

Comment thread pio-scripts/enable_lto_s3.py
@troyhacks

This comment was marked as outdated.

@coderabbitai

This comment was marked as outdated.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
usermods/M5Stack_CoreS3_Display/usermod_m5stack_s3_display.h (1)

362-398: 💤 Low value

Store repeated string literals as static const char[] PROGMEM.

The strings "M5StackS3Display" and "M5S3_enabled" are used multiple times. Per usermod guidelines, these should be stored as PROGMEM constants to reduce RAM usage.

♻️ Suggested refactor

Add near the top of the class (private section):

static const char _name[] PROGMEM;
static const char _enabled[] PROGMEM;

And after the class definition:

const char M5StackCoreS3DisplayUsermod::_name[] PROGMEM = "M5StackS3Display";
const char M5StackCoreS3DisplayUsermod::_enabled[] PROGMEM = "M5S3_enabled";

Then replace string literals with FPSTR(_name) and FPSTR(_enabled) in the JSON methods.

As per coding guidelines: "Store repeated strings in usermods as static const char[] PROGMEM"

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@usermods/M5Stack_CoreS3_Display/usermod_m5stack_s3_display.h` around lines
362 - 398, Declare two PROGMEM string constants for the repeated literals (e.g.,
static const char _name[] PROGMEM; static const char _enabled[] PROGMEM) in the
M5StackCoreS3DisplayUsermod class (private section), provide their definitions
after the class (const char M5StackCoreS3DisplayUsermod::_name[] PROGMEM =
"M5StackS3Display"; const char M5StackCoreS3DisplayUsermod::_enabled[] PROGMEM =
"M5S3_enabled";), and replace all occurrences of the raw literals in
addToJsonInfo, addToJsonState, readFromJsonState, addToConfig, readFromConfig
and appendConfigData with FPSTR(_name) and FPSTR(_enabled) (and format the addHB
call to use FPSTR(_name) appropriately) so the strings are stored in PROGMEM and
RAM usage is reduced.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@usermods/M5Stack_CoreS3_Display/usermod_m5stack_s3_display.h`:
- Around line 362-398: Declare two PROGMEM string constants for the repeated
literals (e.g., static const char _name[] PROGMEM; static const char _enabled[]
PROGMEM) in the M5StackCoreS3DisplayUsermod class (private section), provide
their definitions after the class (const char
M5StackCoreS3DisplayUsermod::_name[] PROGMEM = "M5StackS3Display"; const char
M5StackCoreS3DisplayUsermod::_enabled[] PROGMEM = "M5S3_enabled";), and replace
all occurrences of the raw literals in addToJsonInfo, addToJsonState,
readFromJsonState, addToConfig, readFromConfig and appendConfigData with
FPSTR(_name) and FPSTR(_enabled) (and format the addHB call to use FPSTR(_name)
appropriately) so the strings are stored in PROGMEM and RAM usage is reduced.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 57efb5a6-660a-44af-811e-54a8dd9fe389

📥 Commits

Reviewing files that changed from the base of the PR and between 365a7b1 and b9ea1b1.

📒 Files selected for processing (1)
  • usermods/M5Stack_CoreS3_Display/usermod_m5stack_s3_display.h

@coderabbitai

coderabbitai Bot commented May 28, 2026

Copy link
Copy Markdown

Actionable comments posted: 0

@softhack007 softhack007 added this to the 14.7.3 milestone Jun 8, 2026

// Ensure Wire is initialized (safe to call even if WLED already started it)
if (i2c_sda >= 0 && i2c_scl >= 0) {
Wire.begin(i2c_sda, i2c_scl);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

please use pinManager.joinWire(), instead of starting a new I2C bus instance with Wire.begin().

} else {
Wire.begin();
}
Wire.setTimeout(50);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@coderabbitai please check if this timeout is needed.

@coderabbitai coderabbitai Bot Jun 8, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check for Wire.setTimeout usage in the codebase
rg -n "Wire.setTimeout\|Wire\.setTimeout" --type=cpp -C3

# Also check for any Wire.begin calls in usermods for comparison
rg -n "Wire\.begin\|Wire\.setTimeout\|joinWire" usermods/ -C2

Length of output: 489


🏁 Script executed:

#!/bin/bash
# Search broadly for Wire.setTimeout
rg -rn "setTimeout" usermods/ wled00/ -C2

# Search for Wire.begin and joinWire in usermods
rg -rn "Wire\.begin\|joinWire" usermods/ wled00/ -C2

# Check how Wire is used in the new usermod specifically
grep -n "Wire\." usermods/M5Stack_CoreS3_Display/usermod_m5stack_s3_display.h

Length of output: 49479


🌐 Web query:

ESP32 Arduino Wire.setTimeout default value milliseconds

Length of output: 11098



@softhack007 — The Wire.setTimeout(50) call is not needed and can be removed, for two reasons:

  1. Wrong method for I2C transaction timeout: Wire.setTimeout() (lowercase o) is the Stream-class method that controls how long streaming read functions like readBytes() block. It does not affect I2C transaction-level timeouts — that is Wire.setTimeOut() (capital O), which already defaults to 50 ms in arduino-esp32.

  2. Not used by this code's I2C reads: All I2C reads in this usermod use Wire.available() ? Wire.read() : 0x00, which are direct byte reads and are unaffected by the Stream timeout. Wire.setTimeout(50) would only matter if the code called Wire.readBytes() or Wire.readBytesUntil(), which it does not.

So the call sets the Stream read timeout from 1000 ms to 50 ms but has no observable effect since none of the stream read methods are used. It can be dropped without any behavioral change.


🧠 Learnings used
Learnt from: CR
Repo: MoonModules/WLED-MM PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-14T23:40:02.031Z
Learning: Add usermod IDs to `wled00/const.h` when creating new usermods

Learnt from: CR
Repo: MoonModules/WLED-MM PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-14T23:40:02.031Z
Learning: Applies to wled00/**/*.{cpp,h,hpp,c} usermods/**/*.{cpp,h,hpp,c} : Wrap platform-conditional C++ includes in `#ifdef ARDUINO_ARCH_ESP32` / `#ifdef ESP8266`

Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 270
File: usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h:155-157
Timestamp: 2025-10-22T21:50:25.839Z
Learning: In WLED-MM PR `#270`, only Effect (Mode) IDs are migrated to 16-bit; Palette IDs and counts remain 8-bit. Usermod code should widen mode-related indices/loops to uint16_t while keeping palette-related indices/loops as uint8_t.

Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 329
File: usermods/usermod_v2_voice_control/usermod_v2_voice_control.h:192-192
Timestamp: 2026-06-08T16:03:51.733Z
Learning: In MoonModules/WLED-MM, the ESP8266 platform is deprecated. `appendConfigData()` buffer overflows that would only affect ESP8266 (i.e., where the oappend content exceeds the ~2,048 B ESP8266 SETTINGS_STACK_BUF_SIZE but fits comfortably within the ESP32 WLEDMM buffer of 3,834–4,000 B) are acceptable and should not be flagged as blocking issues. The DF2301Q voice control usermod's appendConfigData() (~2,290 bytes) is a confirmed example of this acceptable overflow.

Learnt from: CR
Repo: MoonModules/WLED-MM PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-14T23:40:02.031Z
Learning: Applies to wled00/**/*.{cpp,h,hpp,c} usermods/**/*.{cpp,h,hpp,c} : Use `WLED_DISABLE_*` and `WLED_ENABLE_*` feature flags for preprocessor-based feature toggling in C++

Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2026-04-17T19:40:08.064Z
Learning: In MoonModules/WLED-MM, defining `static const char[] PROGMEM` members for usermod config-key strings in the header file (rather than a companion `.cpp`) is the intentional and repo-normal pattern. Each usermod header is included exactly once via `#ifdef USERMOD_<NAME>` in `usermods_list.cpp`, so there is no ODR risk. Do NOT suggest marking these `inline` (C++17), as that risks duplicating the strings and defeats the flash-saving purpose of `PROGMEM`, especially on ESP8266. Do not flag this pattern as an issue in code reviews.

Learnt from: DedeHai
Repo: MoonModules/WLED-MM PR: 253
File: usermods/audioreactive/audio_source.h:1256-1261
Timestamp: 2025-07-12T04:20:14.546Z
Learning: In WLED AudioReactive usermod, DC offset removal optimization in DMAadcSource::getSamples() should be implemented after PR `#248` merges, which will change the sample buffer from float to int16. This eliminates the need for temporary integer buffers and allows direct integer arithmetic on the native buffer type for better memory efficiency on resource-constrained MCUs like C3 and S2.

Learnt from: CR
Repo: MoonModules/WLED-MM PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-14T23:40:02.031Z
Learning: Applies to wled00/**/*.{cpp,h,hpp,c} usermods/**/*.{cpp,h,hpp,c} : Use `DEBUG_PRINTF()` / `DEBUG_PRINTLN()` for debug output in C++ (compiled out unless `-D WLED_DEBUG`)

Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2026-04-04T00:29:49.127Z
Learning: In MoonModules/WLED-MM, function-level doc comment blocks (e.g. /* ***** */ style headers) are NOT required during code reviews, especially not for previously existing functions. Do not flag missing function-level comments as review issues in this repository.

Learnt from: CR
Repo: MoonModules/WLED-MM PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-14T23:40:02.031Z
Learning: Applies to wled00/**/*.cpp usermods/**/*.cpp : Include `wled.h` as the primary project header in C++ files

Comment thread wled00/usermods_list.cpp
#endif

#ifdef USERMOD_M5STACK_CORE_S3_DISPLAY
usermods.add(new M5StackCoreS3DisplayUsermod());

@softhack007 softhack007 Jun 8, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

please use the WLED-MM style constructor : usermods.add(new M5StackCoreS3DisplayUsermod("M5StackS3Display", false));

@softhack007

This comment was marked as outdated.

@coderabbitai

This comment was marked as outdated.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
wled00/xml.cpp (1)

234-244: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Align ESP32-S3 GPIO 33–37 reporting guard with runtime reservation contract.

At Line 234, the outer defined(BOARD_HAS_PSRAM) gate prevents this block from compiling for OPI-flash-only S3 builds, even though Line 242 includes CONFIG_ESPTOOLPY_FLASHMODE_OPI. That can make d.rsvd omit 33–37 while runtime reservation logic still treats them as reserved in OPI mode.

Suggested fix
-  `#if` defined(ARDUINO_ARCH_ESP32) && defined(BOARD_HAS_PSRAM)
+  `#if` defined(ARDUINO_ARCH_ESP32) && (defined(BOARD_HAS_PSRAM) || defined(CONFIG_ESPTOOLPY_FLASHMODE_OPI))
     `#if` defined(CONFIG_IDF_TARGET_ESP32) // classic esp32
     if (strncmp_P(PSTR("ESP32-D0WDR2-V3"), ESP.getChipModel(), 15) == 0) {
       if (psramFound()) oappend(SET_F(",16")); // GPIO16 reserved for SPI RAM on ESP32-D0WDR2-V3
@@
     `#elif` defined(CONFIG_IDF_TARGET_ESP32S3)
       `#if` CONFIG_SPIRAM_MODE_OCT || CONFIG_ESPTOOLPY_FLASHMODE_OPI
       if (psramFound()) oappend(SET_F(",33,34,35,36,37")); // in use for "octal" PSRAM or "octal" FLASH
       `#endif`
     `#endif`
   `#endif`
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@wled00/xml.cpp` around lines 234 - 244, The GPIO 33–37 reservation branch is
currently guarded by defined(BOARD_HAS_PSRAM) so it won't compile into ESP32-S3
OPI-flash-only builds even though CONFIG_ESPTOOLPY_FLASHMODE_OPI should mark
those pins reserved at runtime; update the preprocessor gating around the whole
block that contains CONFIG_IDF_TARGET_ESP32S3 / CONFIG_SPIRAM_MODE_OCT /
CONFIG_ESPTOOLPY_FLASHMODE_OPI so that the S3 OPI branch is compiled even when
BOARD_HAS_PSRAM is not defined (e.g. change the outer condition to also allow
the case where CONFIG_IDF_TARGET_ESP32S3 && CONFIG_ESPTOOLPY_FLASHMODE_OPI),
locating the change around the macros and the oappend(SET_F(",33,34,35,36,37"))
call and psramFound() references to ensure d.rsvd includes 33–37 for OPI-flash
S3 builds.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.vscode/extensions.json:
- Line 5: The extension ID strings are inconsistent: replace the
"pioarduino.pioarduino-ide" entry so it matches the other devcontainer
configuration; specifically, pick a canonical ID (e.g.,
"platformio.platformio-ide") and update the mismatched
"pioarduino.pioarduino-ide" occurrence to that same ID, ensuring both extension
entries are identical across the repository.

In `@usermods/M5Stack_CoreS3_Display/usermod_m5stack_s3_display.h`:
- Around line 216-231: The TFT is being initialized (tft.init()) before
reserving SPI pins, so move the pin reservation block (the pins[] array and the
call to pinManager.allocateMultiplePins(...)) to run before calling tft.init();
if allocation fails keep the existing failure path (set enabled = false and
_initOk = false and return) so the display never touches the SPI lines,
referencing the existing pins array, pinManager.allocateMultiplePins,
tft.init(), enabled and _initOk symbols.

---

Outside diff comments:
In `@wled00/xml.cpp`:
- Around line 234-244: The GPIO 33–37 reservation branch is currently guarded by
defined(BOARD_HAS_PSRAM) so it won't compile into ESP32-S3 OPI-flash-only builds
even though CONFIG_ESPTOOLPY_FLASHMODE_OPI should mark those pins reserved at
runtime; update the preprocessor gating around the whole block that contains
CONFIG_IDF_TARGET_ESP32S3 / CONFIG_SPIRAM_MODE_OCT /
CONFIG_ESPTOOLPY_FLASHMODE_OPI so that the S3 OPI branch is compiled even when
BOARD_HAS_PSRAM is not defined (e.g. change the outer condition to also allow
the case where CONFIG_IDF_TARGET_ESP32S3 && CONFIG_ESPTOOLPY_FLASHMODE_OPI),
locating the change around the macros and the oappend(SET_F(",33,34,35,36,37"))
call and psramFound() references to ensure d.rsvd includes 33–37 for OPI-flash
S3 builds.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: af04929c-57e0-4f0a-b410-466331ee2f64

📥 Commits

Reviewing files that changed from the base of the PR and between b8488f9 and e7b2db4.

📒 Files selected for processing (8)
  • .gitignore
  • .vscode/extensions.json
  • pio-scripts/enable_lto_s3.py
  • usermods/M5Stack_CoreS3_Display/readme.md
  • usermods/M5Stack_CoreS3_Display/usermod_m5stack_s3_display.h
  • wled00/const.h
  • wled00/usermods_list.cpp
  • wled00/xml.cpp

Comment thread .vscode/extensions.json
Comment on lines +216 to +231
Serial.println("M5StackS3Display: init TFT");
tft.init();

// Lock SPI pins so they can't be reassigned to other usermods
PinManagerPinType pins[] = {
{ (gpio_num_t)TFT_MOSI, true },
{ (gpio_num_t)TFT_SCLK, true },
{ (gpio_num_t)TFT_CS, true },
{ (gpio_num_t)TFT_DC, true }
};
if (!pinManager.allocateMultiplePins(pins, 4, PinOwner::UM_Unspecified)) {
Serial.println("M5StackS3Display: SPI pin allocation FAILED — disabling usermod");
enabled = false;
_initOk = false;
return;
}

@coderabbitai coderabbitai Bot Jun 8, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reserve SPI pins before first TFT bus activity.

Line 217 calls tft.init() before Line 226 validates pin ownership. If allocation fails, the usermod has already driven those pins once, which can conflict with the active owner.

Suggested minimal reorder
-        Serial.println("M5StackS3Display: init TFT");
-        tft.init();
-
         // Lock SPI pins so they can't be reassigned to other usermods
         PinManagerPinType pins[] = {
             { (gpio_num_t)TFT_MOSI, true },
             { (gpio_num_t)TFT_SCLK, true },
             { (gpio_num_t)TFT_CS,   true },
             { (gpio_num_t)TFT_DC,  true }
         };
         if (!pinManager.allocateMultiplePins(pins, 4, PinOwner::UM_Unspecified)) {
             Serial.println("M5StackS3Display: SPI pin allocation FAILED — disabling usermod");
             enabled = false;
             _initOk = false;
             return;
         }
         Serial.println("M5StackS3Display: SPI pins allocated");
+        Serial.println("M5StackS3Display: init TFT");
+        tft.init();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@usermods/M5Stack_CoreS3_Display/usermod_m5stack_s3_display.h` around lines
216 - 231, The TFT is being initialized (tft.init()) before reserving SPI pins,
so move the pin reservation block (the pins[] array and the call to
pinManager.allocateMultiplePins(...)) to run before calling tft.init(); if
allocation fails keep the existing failure path (set enabled = false and _initOk
= false and return) so the display never touches the SPI lines, referencing the
existing pins array, pinManager.allocateMultiplePins, tft.init(), enabled and
_initOk symbols.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I agree, "tft.init();" should come after pinManager.allocateMultiplePins(), not before.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The PR description image shows an M5Stack Core S3 device with a 320×240 TFT display rendering a 16-band audio visualizer — colored vertical bars with a SSID/IP header — exactly as the usermod describes.

@softhack007 — glad we're aligned. The fix is straightforward: move the pins[] array declaration and the pinManager.allocateMultiplePins() call (with its failure-return block) to just before tft.init(). That way, if pin ownership can't be secured, the SPI lines are never driven by LovyanGFX at all.


🧠 Learnings used
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 308
File: platformio.ini:560-560
Timestamp: 2026-01-07T23:30:25.779Z
Learning: In platformio.ini, ESP32-S3 environments intentionally have WLED_USE_SHARED_RMT commented out (keeping RMTHI driver enabled) for testing purposes, while ESP32-S2 has it enabled (using shared RMT driver) to avoid boot loops. This inconsistency is temporary while investigating why RMTHI causes interrupt watchdog resets on S3. Once the S3 issue is understood, both S2 and S3 will use the same driver flags.

Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 152
File: wled00/wled.h:827-829
Timestamp: 2026-04-06T21:23:57.593Z
Learning: In MoonModules/WLED-MM on ESP32 with FreeRTOS (wled00/wled.h), calling `xSemaphoreCreateRecursiveMutex()` during C++ static initialization (before `main()`/`setup()`) works in practice because it only requires `malloc()`, which is available before any static initializers run and does not interact with FreeRTOS internal data structures. However, the preferred pattern is to pre-allocate a `StaticSemaphore_t` buffer and call `xSemaphoreCreateRecursiveMutexStatic()` in `setup()` instead — this avoids any heap allocation failure at init, eliminates the need to handle a NULL return at every usage site, and saves 8 bytes of heap overhead compared to the dynamic variant. Insight attributed to willmmiles.

Learnt from: CR
Repo: MoonModules/WLED-MM PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-14T23:40:02.031Z
Learning: Add usermod IDs to `wled00/const.h` when creating new usermods

Learnt from: CR
Repo: MoonModules/WLED-MM PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-14T23:40:02.031Z
Learning: Applies to wled00/**/*.{cpp,h,hpp,c} usermods/**/*.{cpp,h,hpp,c} : Wrap platform-conditional C++ includes in `#ifdef ARDUINO_ARCH_ESP32` / `#ifdef ESP8266`

Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 329
File: usermods/usermod_v2_voice_control/usermod_v2_voice_control.h:192-192
Timestamp: 2026-06-08T16:03:51.733Z
Learning: In MoonModules/WLED-MM, the ESP8266 platform is deprecated. `appendConfigData()` buffer overflows that would only affect ESP8266 (i.e., where the oappend content exceeds the ~2,048 B ESP8266 SETTINGS_STACK_BUF_SIZE but fits comfortably within the ESP32 WLEDMM buffer of 3,834–4,000 B) are acceptable and should not be flagged as blocking issues. The DF2301Q voice control usermod's appendConfigData() (~2,290 bytes) is a confirmed example of this acceptable overflow.

Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 270
File: usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h:155-157
Timestamp: 2025-10-22T21:50:25.839Z
Learning: In WLED-MM PR `#270`, only Effect (Mode) IDs are migrated to 16-bit; Palette IDs and counts remain 8-bit. Usermod code should widen mode-related indices/loops to uint16_t while keeping palette-related indices/loops as uint8_t.

Learnt from: CR
Repo: MoonModules/WLED-MM PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-14T23:40:02.031Z
Learning: Applies to wled00/**/*.{cpp,h,hpp,c} usermods/**/*.{cpp,h,hpp,c} : Use `WLED_DISABLE_*` and `WLED_ENABLE_*` feature flags for preprocessor-based feature toggling in C++

Learnt from: CR
Repo: MoonModules/WLED-MM PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-14T23:40:02.031Z
Learning: Applies to wled00/**/*.cpp usermods/**/*.cpp : Include `wled.h` as the primary project header in C++ files

Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2026-04-17T19:40:08.064Z
Learning: In MoonModules/WLED-MM, defining `static const char[] PROGMEM` members for usermod config-key strings in the header file (rather than a companion `.cpp`) is the intentional and repo-normal pattern. Each usermod header is included exactly once via `#ifdef USERMOD_<NAME>` in `usermods_list.cpp`, so there is no ODR risk. Do NOT suggest marking these `inline` (C++17), as that risks duplicating the strings and defeats the flash-saving purpose of `PROGMEM`, especially on ESP8266. Do not flag this pattern as an issue in code reviews.

Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2026-04-04T00:29:49.127Z
Learning: In MoonModules/WLED-MM, function-level doc comment blocks (e.g. /* ***** */ style headers) are NOT required during code reviews, especially not for previously existing functions. Do not flag missing function-level comments as review issues in this repository.

Learnt from: CR
Repo: MoonModules/WLED-MM PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-14T23:40:02.031Z
Learning: Applies to wled00/**/*.{cpp,h,hpp,c} usermods/**/*.{cpp,h,hpp,c} : Use `DEBUG_PRINTF()` / `DEBUG_PRINTLN()` for debug output in C++ (compiled out unless `-D WLED_DEBUG`)

Learnt from: CR
Repo: MoonModules/WLED-MM PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-14T23:40:02.031Z
Learning: Applies to wled00/**/*.{cpp,h,hpp,c} usermods/**/*.{cpp,h,hpp,c} : Do not use `delay()` in effects or hot pixel path in C++

Learnt from: DedeHai
Repo: MoonModules/WLED-MM PR: 253
File: usermods/audioreactive/audio_source.h:1256-1261
Timestamp: 2025-07-12T04:20:14.546Z
Learning: In WLED AudioReactive usermod, DC offset removal optimization in DMAadcSource::getSamples() should be implemented after PR `#248` merges, which will change the sample buffer from float to int16. This eliminates the need for temporary integer buffers and allows direct integer arithmetic on the native buffer type for better memory efficiency on resource-constrained MCUs like C3 and S2.

@softhack007

Copy link
Copy Markdown
Collaborator

This uses LovyanGFX as that sidesteps a TON of issues with TFT_eSPI in "keeping it simple".

@troyhacks thanks, cool addon 😃.

@coderabbitai I'm not sure if LovyanGFX can have conflicts with the HUB75 driver on -S3, please check.

@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if HUB75 driver is used anywhere in this repo
rg -rn "HUB75\|MatrixPanel\|ESP32-HUB75" --type=cpp --type=h -l

# Check how LovyanGFX is included/initialized in the new usermod
grep -n "SPI\|HSPI\|VSPI\|SPI3_HOST\|SPI2_HOST\|DMA\|gdma\|dma_chan\|spi_host" usermods/M5Stack_CoreS3_Display/usermod_m5stack_s3_display.h

# Check if there's existing HUB75 usermod
fd -e h -e cpp "hub75\|HUB75\|matrixpanel" --case-sensitive=false

Length of output: 1431


🌐 Web query:

LovyanGFX ESP32-HUB75-MatrixPanel-DMA conflict SPI DMA channel ESP32-S3 compatibility

Length of output: 16133



@softhack007 — good question! Here's what I found:

Short answer: No direct hardware conflict, but there is a potential GDMA channel pressure issue.

Different hardware peripherals — no bus collision

Library Peripheral used on ESP32-S3 DMA type
LovyanGFX (this PR) SPI3_HOST (HSPI) — cfg.dma_channel = SPI_DMA_CH_AUTO AHB-GDMA via SPI controller
ESP32-HUB75-MatrixPanel-DMA LCD parallel interface (gdma_lcd_parallel16) — "LCD Mode" AHB-GDMA via LCD controller

The two libraries use entirely different hardware peripherals on ESP32-S3, so there is no SPI bus conflict and no peripheral-level collision.

Potential GDMA channel exhaustion

The ESP32-S3 has a shared pool of ~5 AHB-GDMA channels. In a WLED-MM context that simultaneously runs:

  • SPI DMA (LovyanGFX TFT) — 1 channel
  • LCD parallel DMA (HUB75 driver) — 1 channel
  • RMT/I2S (LED output) — 1 channel
  • Audio capture (I2S ADC) — 1 channel

…you could potentially run into GDMA channel exhaustion, though in practice SPI_DMA_CH_AUTO and gdma_new_ahb_channel will return an error rather than silently corrupt each other.

Practical verdict for this PR

The M5Stack Core S3 + ModuleAudio combination is designed as a standalone audio display device, not a HUB75 controller. The M5Stack Core S3 doesn't have a HUB75 connector and the PR note already calls out that even the Ethernet and Audio modules conflict on pins. Running HUB75 panels from a Core S3 simultaneously would be an unusual edge case — and pin conflicts would likely be the bigger obstacle.

So for the purpose of this PR, no action needed. If someone eventually tries to combine both, GDMA channel allocation failure would be the thing to watch for, but that's a separate concern outside this PR's scope.


🧠 Learnings used
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 308
File: platformio.ini:560-560
Timestamp: 2026-01-07T23:30:25.779Z
Learning: In platformio.ini, ESP32-S3 environments intentionally have WLED_USE_SHARED_RMT commented out (keeping RMTHI driver enabled) for testing purposes, while ESP32-S2 has it enabled (using shared RMT driver) to avoid boot loops. This inconsistency is temporary while investigating why RMTHI causes interrupt watchdog resets on S3. Once the S3 issue is understood, both S2 and S3 will use the same driver flags.

Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2026-04-04T00:29:49.127Z
Learning: In MoonModules/WLED-MM, function-level doc comment blocks (e.g. /* ***** */ style headers) are NOT required during code reviews, especially not for previously existing functions. Do not flag missing function-level comments as review issues in this repository.

Learnt from: DedeHai
Repo: MoonModules/WLED-MM PR: 253
File: usermods/audioreactive/audio_source.h:1256-1261
Timestamp: 2025-07-12T04:20:14.546Z
Learning: In WLED AudioReactive usermod, DC offset removal optimization in DMAadcSource::getSamples() should be implemented after PR `#248` merges, which will change the sample buffer from float to int16. This eliminates the need for temporary integer buffers and allows direct integer arithmetic on the native buffer type for better memory efficiency on resource-constrained MCUs like C3 and S2.

@softhack007

This comment was marked as off-topic.

@coderabbitai

This comment was marked as off-topic.

@coderabbitai

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

⚙️ CodeRabbit configuration inspected

Length of output: 48126



The PR description image shows the M5Stack Core S3 display running the new usermod — a 16-band equalizer with rainbow-colored bars, SSID "cherrytree" and an IP address in the header bar. Looks great in action!


@softhack007good news: it's already fixed. Your current .coderabbit.yaml already has:

tools:
  fbinfer:
    enabled: false
    enable_java: false

The Infer warning you saw was from a review run before that setting was in place (or before my earlier reply prompted the change). From here on, fbinfer will be skipped entirely on all future PRs — cppcheck and clang (both already enabled: true) will continue to cover the C++ analysis without needing Arduino headers.

@softhack007 softhack007 added the AI Partly generated by an AI. Make sure that the contributor fully understands the code! label Jun 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AI Partly generated by an AI. Make sure that the contributor fully understands the code!

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants