Skip to content

Commit 4ae70a4

Browse files
committed
Fixes after audit, improved docs.
1 parent 4bd5960 commit 4ae70a4

3 files changed

Lines changed: 96 additions & 77 deletions

File tree

usermods/M5Stack_CoreS3_Display/readme.md

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,20 @@ Reset is controlled via the AW9523B GPIO expander (P1_1). Backlight is powered v
1717

1818
In `platformio_override.ini` for your M5Stack Core S3 environment:
1919

20-
```ini
21-
build_flags =
20+
```build_flags =
2221
-D USERMOD_M5STACK_CORE_S3_DISPLAY
22+
;; For the M5Stack ModuleAudio:
23+
-D SR_ENABLE_DEFAULT
24+
-D SR_DMTYPE=6
25+
-D I2S_SDPIN=13
26+
-D I2S_WSPIN=6
27+
-D I2S_CKPIN=0
28+
-D MCLK_PIN=7
29+
-D HW_SDA_PIN=12
30+
-D HW_SCL_PIN=11
2331
2432
lib_deps =
25-
https://github.com/lovyan03/LovyanGFX
26-
```
33+
https://github.com/lovyan03/LovyanGFX```
2734
2835
## Features
2936
@@ -32,11 +39,25 @@ lib_deps =
3239
- Real audio reactive data when Audioreactive usermod is enabled
3340
- Simulated bouncing bars when no audio data
3441
- Rainbow color per bar (red → violet)
35-
- Auto sleep after 5 minutes of inactivity
42+
- Maximum of 100 FPS to match AudioReactive
43+
- Minimum of 5 FPS so it updates even if you use unlimited FPS mode.
3644
3745
## Display Notes
3846
3947
- Uses LovyanGFX with `SPI3_HOST` (HSPI)
4048
- Native landscape 320x240 resolution
4149
- BGR color order, display inversion enabled
4250
- Backlight always on (controlled by AXP2101 DLDO1)
51+
52+
## TroyHacks Recommended AudioReactive Settings
53+
54+
For the M5Stack Core S3 + ModuleAudio line-in:
55+
56+
- The ModuleAudio uses an ES8388
57+
- Squelch & Gain at 1
58+
- AGC is Normal
59+
- MicLev is Freeze
60+
- Mic Quality is Perfect
61+
- FFT Window is Nutall (or whatever you prefer)
62+
- Profile is Generic Line-In
63+
- Limiter is Enabled, with a rise of 1 and a fall of 250 (to 500).

usermods/M5Stack_CoreS3_Display/usermod_m5stack_s3_display.h

Lines changed: 68 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -72,23 +72,19 @@ class LGFX_ILI9342_M5StackS3 : public lgfx::LGFX_Device {
7272

7373
static LGFX_ILI9342_M5StackS3 tft;
7474

75-
extern int getSignalQuality(int rssi);
76-
7775

7876
//class name. Use something descriptive and leave the ": public Usermod" part :)
7977
class M5StackCoreS3DisplayUsermod : public Usermod {
8078
private:
8179
bool enabled = true;
8280

83-
bool displayTurnedOff = false;
84-
long lastRedraw = 0;
8581
// needRedraw marks if redraw is required to prevent often redrawing.
8682
bool needRedraw = true;
8783
// Next variables hold the previous known values to determine if redraw is required.
8884
String knownSsid = "";
8985
IPAddress knownIp;
9086

91-
long lastUpdate = 0;
87+
unsigned long lastUpdate = 0;
9288

9389
// GEQ display constants
9490
static constexpr uint8_t NUM_GEQ_BANDS = 16;
@@ -102,19 +98,19 @@ class M5StackCoreS3DisplayUsermod : public Usermod {
10298
case 0: return 0xF800; // red
10399
case 1: return 0xFC00; // orange
104100
case 2: return 0xFC40; // amber
105-
case 3: return 0xFCC0; // yellow-orange
106-
case 4: return 0xFFE0; // yellow
107-
case 5: return 0xC700; // yellow-green
108-
case 6: return 0x07E0; // green
109-
case 7: return 0x07EF; // green-cyan
110-
case 8: return 0x07FF; // cyan
111-
case 9: return 0x041F; // cyan-blue
112-
case 10: return 0x001F; // blue
113-
case 11: return 0x281F; // blue-indigo
114-
case 12: return 0x601F; // indigo
115-
case 13: return 0x781F; // violet
116-
case 14: return 0xF81F; // magenta
117-
case 15: return 0xF81F; // magenta
101+
case 3: return 0xFFC0; // yellow
102+
case 4: return 0x07E0; // green
103+
case 5: return 0x07EF; // green-cyan
104+
case 6: return 0x07FF; // cyan
105+
case 7: return 0x001F; // blue
106+
case 8: return 0x281F; // blue-indigo
107+
case 9: return 0x481F; // indigo
108+
case 10: return 0x701F; // violet
109+
case 11: return 0x881F; // violet-magenta
110+
case 12: return 0xF81F; // magenta
111+
case 13: return 0xF81C; // magenta-red
112+
case 14: return 0xF800; // red
113+
case 15: return 0xF808; // red-orange
118114
default: return 0xF800;
119115
}
120116
}
@@ -130,11 +126,13 @@ class M5StackCoreS3DisplayUsermod : public Usermod {
130126
{
131127
Serial.println("M5StackS3Display: starting setup");
132128

133-
// Ensure Wire is initialized
129+
// Ensure Wire is initialized (safe to call even if WLED already started it)
134130
if (i2c_sda >= 0 && i2c_scl >= 0) {
135131
Wire.begin(i2c_sda, i2c_scl);
136-
Wire.setTimeout(50);
132+
} else {
133+
Wire.begin();
137134
}
135+
Wire.setTimeout(50);
138136

139137
// AXP2101 - enable DLDO1 (VCC_BL per user)
140138
// Read current state first
@@ -217,6 +215,19 @@ class M5StackCoreS3DisplayUsermod : public Usermod {
217215
Serial.println("M5StackS3Display: init TFT");
218216
tft.init();
219217

218+
// Lock SPI pins so they can't be reassigned to other usermods
219+
PinManagerPinType pins[] = {
220+
{ (gpio_num_t)TFT_MOSI, true },
221+
{ (gpio_num_t)TFT_SCLK, true },
222+
{ (gpio_num_t)TFT_CS, true },
223+
{ (gpio_num_t)TFT_DC, true }
224+
};
225+
if (pinManager.allocateMultiplePins(pins, 4, PinOwner::UM_Unspecified)) {
226+
Serial.println("M5StackS3Display: SPI pins allocated");
227+
} else {
228+
Serial.println("M5StackS3Display: SPI pin allocation FAILED");
229+
}
230+
220231
Serial.printf("M5StackS3Display: TFT width: %d, height: %d\n", tft.width(), tft.height());
221232

222233
// Dynamic header sizing
@@ -243,59 +254,54 @@ class M5StackCoreS3DisplayUsermod : public Usermod {
243254
* loop() is called continuously. Here you can check for events, read sensors, etc.
244255
*/
245256
void loop() {
246-
// Faster refresh for bouncing bars
247-
if (millis() - lastUpdate < 100) // 50ms = 20fps
248-
{
257+
if (!enabled) return;
258+
259+
unsigned long now = millis();
260+
261+
// Standard max framerate cap (~100 FPS, matches audio reactive update rate)
262+
if (now - lastUpdate < 10) return;
263+
264+
// Fallback: if LEDs are hogging CPU, only block if we refreshed recently enough
265+
// Otherwise punch through to guarantee minimum 5 FPS on display
266+
if (strip.isUpdating() && (now - lastUpdate < 200)) {
249267
return;
250268
}
251-
lastUpdate = millis();
252269

253-
// Turn off display after 5 minutes with no change.
254-
if (!displayTurnedOff && millis() - lastRedraw > 5*60*1000)
255-
{
256-
displayTurnedOff = true;
257-
}
270+
lastUpdate = now;
258271

259-
// Check if values which are shown on display changed from the last time.
260-
#if defined(ESP8266)
272+
// Skip content/header updates while LEDs are being updated
273+
if (!strip.isUpdating()) {
274+
// Check if values which are shown on display changed from the last time.
261275
String currentSsid = apActive ? String(apSSID) : WiFi.SSID();
262-
#else
263-
String currentSsid = WiFi.SSID();
264-
#endif
265-
IPAddress currentIp = apActive ? IPAddress(4, 3, 2, 1) : Network.localIP();
266-
267-
if (currentSsid != knownSsid || currentIp != knownIp)
268-
{
269-
needRedraw = true;
270-
knownSsid = currentSsid;
271-
knownIp = currentIp;
272-
}
276+
IPAddress currentIp = apActive ? IPAddress(4, 3, 2, 1) : Network.localIP();
273277

274-
if (displayTurnedOff)
275-
{
276-
displayTurnedOff = false;
277-
}
278-
lastRedraw = millis();
278+
if (currentSsid != knownSsid || currentIp != knownIp)
279+
{
280+
needRedraw = true;
281+
knownSsid = currentSsid;
282+
knownIp = currentIp;
283+
}
279284

280-
// === HEADER: Only redraw when needed ===
281-
if (needRedraw) {
282-
// Gray background
283-
tft.fillRect(0, 0, tft.width(), HEADER_HEIGHT, TFT_DARKGREY);
285+
// === HEADER: Only redraw when needed ===
286+
if (needRedraw) {
287+
// Gray background
288+
tft.fillRect(0, 0, tft.width(), HEADER_HEIGHT, TFT_DARKGREY);
284289

285-
// Text vertically centered in header
286-
int16_t textY = (HEADER_HEIGHT - tft.fontHeight()) / 2;
287-
tft.setTextSize(2);
288-
tft.setTextColor(TFT_WHITE);
290+
// Text vertically centered in header
291+
int16_t textY = (HEADER_HEIGHT - tft.fontHeight()) / 2;
292+
tft.setTextSize(2);
293+
tft.setTextColor(TFT_WHITE);
289294

290-
// SSID on left
291-
tft.setTextDatum(TL_DATUM);
292-
tft.drawString(knownSsid.c_str(), 10, textY);
295+
// SSID on left
296+
tft.setTextDatum(TL_DATUM);
297+
tft.drawString(knownSsid.c_str(), 10, textY);
293298

294-
// IP on right
295-
tft.setTextDatum(TR_DATUM);
296-
tft.drawString(knownIp.toString().c_str(), tft.width() - 10, textY);
299+
// IP on right
300+
tft.setTextDatum(TR_DATUM);
301+
tft.drawString(knownIp.toString().c_str(), tft.width() - 10, textY);
297302

298-
needRedraw = false;
303+
needRedraw = false;
304+
}
299305
}
300306

301307
// === 16 BOUNCING BARS - differential drawing ===
@@ -369,20 +375,12 @@ class M5StackCoreS3DisplayUsermod : public Usermod {
369375
void addToConfig(JsonObject& root)
370376
{
371377
JsonObject top = root.createNestedObject("M5StackS3Display");
372-
top["CS"] = TFT_CS;
373-
top["DC"] = TFT_DC;
374-
top["MOSI"] = TFT_MOSI;
375-
top["SCLK"] = TFT_SCLK;
376378
top["enabled"] = enabled;
377379
}
378380

379381
void appendConfigData()
380382
{
381383
oappend(SET_F("addHB('M5StackS3Display');"));
382-
oappend(SET_F("addInfo('M5StackS3Display:CS',0,'SPI Chip Select (GPIO3)');"));
383-
oappend(SET_F("addInfo('M5StackS3Display:DC',0,'Data/Command (GPIO35)');"));
384-
oappend(SET_F("addInfo('M5StackS3Display:MOSI',0,'SPI MOSI (GPIO37)');"));
385-
oappend(SET_F("addInfo('M5StackS3Display:SCLK',0,'SPI Clock (GPIO36)');"));
386384
}
387385

388386
/*

wled00/wled.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -491,7 +491,7 @@ void WLED::setup()
491491

492492
#if ARDUINO_USB_CDC_ON_BOOT && (defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32C6) || defined(CONFIG_IDF_TARGET_ESP32P4))
493493
Serial.begin(115200); // WLEDMM avoid "hung devices" when USB_CDC is enabled; see https://github.com/espressif/arduino-esp32/issues/9043
494-
Serial.setTxTimeoutMs(1); // potential side-effect: incomplete debug output, with missing characters whenever TX buffer is full.
494+
Serial.setTxTimeoutMs(0); // potential side-effect: incomplete debug output, with missing characters whenever TX buffer is full.
495495
#else
496496
Serial.begin(115200);
497497
#endif
@@ -525,7 +525,7 @@ void WLED::setup()
525525
#if ARDUINO_USB_CDC_ON_BOOT || ARDUINO_USB_MODE
526526
#if ARDUINO_USB_CDC_ON_BOOT && (defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32C6) || defined(CONFIG_IDF_TARGET_ESP32P4))
527527
// WLEDMM avoid "hung devices" when USB_CDC is enabled; see https://github.com/espressif/arduino-esp32/issues/9043
528-
Serial.setTxTimeoutMs(1);
528+
Serial.setTxTimeoutMs(0);
529529
#endif
530530
#if !defined(WLEDMM_NO_SERIAL_WAIT) || defined(WLED_DEBUG)
531531
if (!Serial) delay(2500); // WLEDMM: always allow CDC USB serial to initialise

0 commit comments

Comments
 (0)