Summary
Some AtomS3R units (mine purchased early Feb 2026) show a permanently black screen
(backlight on) under every M5GFX-based firmware — UIFlow2 v2.1.4 through v2.4.9 and
Arduino examples — while the AtomS3R User Demo works normally. I traced the root
cause with a logic-level investigation over the MicroPython REPL:
The panel is a normal GC9107 that returns the exact expected ID (0x079100),
but only when the ID register is read at low clock speed. At M5GFX's probe speed
the read returns garbage, so autodetect rejects the panel and never binds a display.
Evidence
Device: AtomS3R (ESP32-S3-PICO-1 v0.2, MAC 98:88:e0:0e:d5:10), UIFlow2 v2.4.9
(MicroPython v1.27.0, 2026-07-10 build).
-
Firmware and peripherals are fine. UIFlow2 boots, Wi-Fi/I2C work.
Internal I2C scan finds the LP5562 backlight driver (0x30, controllable)
and BMI270 IMU (0x68).
-
M5.Lcd is not attached to the bus. All draw calls no-op silently;
register reads through it return inconsistent garbage. After I initialized
the panel externally (see 4), M5.Lcd.fillScreen() left the panel content
untouched — confirming M5GFX bound no panel. This matches the autodetect
logic in M5GFX.cpp (ESP32S3-PICO case): when _read_panel_id() matches
neither 0x079100 (GC9107) nor 0x7683/0x897C (ST7735S), no panel is
created and the failure is silent.
-
The panel returns the correct GC9107 ID at low clock speed. Bit-banging
the 3-wire SPI (SCLK G15, SDA G21, CS G14, DC G42, RST G48) at ~100 kHz,
RDDID (0x04) returns: 1 dummy bit, then 0x00 0x91 0x07 — exactly the
0x079100 M5GFX expects. The readback is timing-marginal: it is only
correct when sampled on the rising edge; other sampling schemes return
all-1s/all-0s (pin pull value, panel not driving). This strongly suggests
this panel batch has a slower ID-readback driver that fails at M5GFX's
freq_read (16 MHz).
-
The panel works perfectly with the standard M5GFX GC9107 init. Replaying
the byte-identical init table from Panel_GC9107::getInitCommands() over
bit-banged GPIO brings the display up. Hardware SPI writes work at both
20 MHz and 40 MHz — writes are unaffected; only ID readback is marginal.
-
The User Demo works because it has an app-level fallback. The demo v0.3
binary contains the strings AtomS3R LCD: GC9107, AtomS3R LCD: ST7735S
and AtomS3R LCD: unknown panel ID 0x{:08x} (expected ST7735S or GC9107) —
it initializes the display even when the ID probe fails. UIFlow2/M5GFX have
no such fallback.
Suggested fix
In the AtomS3R autodetect path, retry _read_panel_id() at a much lower read
frequency (≤1 MHz) when the first read matches no known panel — or adopt the
User Demo's fallback and default to GC9107 on unknown ID for this board.
Either would fix all affected units already in the field.
Workaround for affected users
Until fixed, the panel can be driven directly from MicroPython with a small
framebuf-based driver (GC9107 init + SPI2 at 20 MHz), bypassing M5.Lcd.
I can share the driver on request.
Disclosure: the hands-on debugging and this write-up were done by Claude (Anthropic's AI, via Claude Code) working over the device's USB/MicroPython REPL — bit-banged register reads, init-sequence replays, and binary analysis of the demo/UIFlow images — with me operating the hardware. I've verified the findings on my unit; happy to run further tests on this panel batch if useful.
Summary
Some AtomS3R units (mine purchased early Feb 2026) show a permanently black screen
(backlight on) under every M5GFX-based firmware — UIFlow2 v2.1.4 through v2.4.9 and
Arduino examples — while the AtomS3R User Demo works normally. I traced the root
cause with a logic-level investigation over the MicroPython REPL:
The panel is a normal GC9107 that returns the exact expected ID (
0x079100),but only when the ID register is read at low clock speed. At M5GFX's probe speed
the read returns garbage, so autodetect rejects the panel and never binds a display.
Evidence
Device: AtomS3R (ESP32-S3-PICO-1 v0.2, MAC 98:88:e0:0e:d5:10), UIFlow2 v2.4.9
(MicroPython v1.27.0, 2026-07-10 build).
Firmware and peripherals are fine. UIFlow2 boots, Wi-Fi/I2C work.
Internal I2C scan finds the LP5562 backlight driver (0x30, controllable)
and BMI270 IMU (0x68).
M5.Lcdis not attached to the bus. All draw calls no-op silently;register reads through it return inconsistent garbage. After I initialized
the panel externally (see 4),
M5.Lcd.fillScreen()left the panel contentuntouched — confirming M5GFX bound no panel. This matches the autodetect
logic in
M5GFX.cpp(ESP32S3-PICO case): when_read_panel_id()matchesneither
0x079100(GC9107) nor0x7683/0x897C(ST7735S), no panel iscreated and the failure is silent.
The panel returns the correct GC9107 ID at low clock speed. Bit-banging
the 3-wire SPI (SCLK G15, SDA G21, CS G14, DC G42, RST G48) at ~100 kHz,
RDDID (0x04) returns: 1 dummy bit, then
0x00 0x91 0x07— exactly the0x079100M5GFX expects. The readback is timing-marginal: it is onlycorrect when sampled on the rising edge; other sampling schemes return
all-1s/all-0s (pin pull value, panel not driving). This strongly suggests
this panel batch has a slower ID-readback driver that fails at M5GFX's
freq_read(16 MHz).The panel works perfectly with the standard M5GFX GC9107 init. Replaying
the byte-identical init table from
Panel_GC9107::getInitCommands()overbit-banged GPIO brings the display up. Hardware SPI writes work at both
20 MHz and 40 MHz — writes are unaffected; only ID readback is marginal.
The User Demo works because it has an app-level fallback. The demo v0.3
binary contains the strings
AtomS3R LCD: GC9107,AtomS3R LCD: ST7735Sand
AtomS3R LCD: unknown panel ID 0x{:08x} (expected ST7735S or GC9107)—it initializes the display even when the ID probe fails. UIFlow2/M5GFX have
no such fallback.
Suggested fix
In the AtomS3R autodetect path, retry
_read_panel_id()at a much lower readfrequency (≤1 MHz) when the first read matches no known panel — or adopt the
User Demo's fallback and default to GC9107 on unknown ID for this board.
Either would fix all affected units already in the field.
Workaround for affected users
Until fixed, the panel can be driven directly from MicroPython with a small
framebuf-based driver (GC9107 init + SPI2 at 20 MHz), bypassing
M5.Lcd.I can share the driver on request.
Disclosure: the hands-on debugging and this write-up were done by Claude (Anthropic's AI, via Claude Code) working over the device's USB/MicroPython REPL — bit-banged register reads, init-sequence replays, and binary analysis of the demo/UIFlow images — with me operating the hardware. I've verified the findings on my unit; happy to run further tests on this panel batch if useful.