- Migrates tactility-firmware and tactility-bluetooth from ~/.hermes/skills/ into .claude/skills/ for repo co-location - Adds AGENTS.md with local workstation context, board table, board-direct build rule, Hermes PYTHONPATH pitfall, common Tactility pitfalls, and in-repo skill index Refs: personal Gitea mirror
8.2 KiB
Waveshare ESP32-S3-RLCD-4.2 Initialization Race & Freeze Audit (2026-07-14)
Overview
RLCD board (waveshare-esp32-s3-rlcd) suffered random freeze "stuck on weird state" requiring hard reset. Serial log triage via pyserial (15s capture) showed no Guru Meditation, only warnings. Root causes found in 3 layers: I2C driver CONFLICT, ES8311 codec blocking boot, ST7305 reset timing.
File Map
Devices/waveshare-esp32-s3-rlcd/waveshare,esp32-s3-rlcd.dts— devicetree, defines i2c0, spi0, sdmmc0, i2s0, gpio0Devices/waveshare-esp32-s3-rlcd/Source/Configuration.cpp—initBoot()powers amp GPIO46 + ES8311 init,createDevices()display + TwoButtonControl(GPIO18 KEY, GPIO0 BOOT)Drivers/ST7305/Source/St7305Display.h/.cpp— SPI display driver, reset pin GPIO41, CS 40, DC 5, SPI2 10MHz,full_refresh=true, buffer 120k (400x300),monochrome=true,trans_size=0Tactility/Source/service/displayidle/DisplayIdle.cpp— idle timer + wake logic (50d86de4fix: run for all displays)
Initialization Sequence (from serial log)
- Bootloader 2nd stage → PSRAM 8MB 120MHz detected
kernel: init→module: start waveshare-esp32-s3-rlcd→module: start platform-esp32driver: add esp32_gpio, esp32_i2c, esp32_i2c_master, esp32_i2s, esp32_sdmmc, ...— both legacy and NG I2C drivers registereddevice: start i2c0→esp32_i2c_master: start i2c0(after fix) vs previouslyesp32_i2c: start i2c0E (907) i2c: CONFLICT! driver_ng is not allowed to be used with this old driver— IDF 5.x forbids mixing oldi2c_driver_installand newi2c_new_master_buson same peripheral. Warning appears even when only one instance uses I2C0, because platform-esp32 registers both driver types globally. Benign after migration but indicates legacy driver still loaded.gpio: GPIO[13] InputEn:1 OutputEn:1 OpenDrain:1/ GPIO14 — I2C pinsesp32_sdmmc_fs: Mounted /sdcard— SD 1-bit, CLK 38, CMD 21, D0 39, D3 17SystemEvents: BootInitHalBegin→gpio46 HIGHamp enableWaveshareRLCD: ES8311 codec initialized(after3770ac89fix) vs previously missing or failedDevices: Registering ST7305→Hal: ST7305 starting→ST7305: Starting SPI panel IO creation→Hal: ST7305 started(2160ms with delays vs 1828ms before)Tactility: DEBUG: Listing /sdcard→NTP: Restoring last known timeBluetooth: Auto-enabling BLE→BLE host task started+GAP procedure...+startAdvertising: set_fields failed rc=4(benign)ServiceRegistration: Adding Gps/wifi/Development/EspNow/WebServer→WebServerService: AP started SSID Tactility-5C7D 192.168.4.1+HttpServer Started port 80McpSystem: Mono TCP Video stream server started 8081+ Color 8083LVGL: Starting LVGL task→Started ST7305+ButtonControl: StartDisplayIdle: Monochrome/RLCD display detected: idle timer will run but auto-backlight off is disabledBoot: Setup display/Boot: No backlight→Tactility: Registering internal apps→Loader: Start by id Launcher→GuiService: Showing LauncherWifiBootSplashInit: Auto-enabling WiFi→WifiService: Enabled→STA_START— auto-connects if SD has credentials
Root Causes & Fixes (3770ac89 + 50d86de4)
1. I2C CONFLICT — legacy vs NG driver
Before:
#include <tactility/bindings/esp32_i2c.h>
i2c0 { compatible = "espressif,esp32-i2c"; port = <I2C_NUM_0>; ... }
- Platform registers both
esp32_i2c(old) andesp32_i2c_master(new) drivers - When i2c0 used old driver, IDF warns CONFLICT if any component uses
driver_ng(i2c_master internal) — happens becauseesp32_i2c_masterdriver also calls NG API - Could stall I2C bus if codec init fails
After (3770ac89):
#include <tactility/bindings/esp32_i2c_master.h>
i2c0 { compatible = "espressif,esp32-i2c-master"; port = <I2C_NUM_0>; clock-frequency = <400000>; pin-sda = <&gpio0 13>; pin-scl = <&gpio0 14>; }
- Now uses master driver,
esp32_i2c_master: start i2c0in log, ES8311 initializes - Warning
E i2c: CONFLICT!still appears because platform still registers legacy driver type globally — upstream should remove legacy registration if all boards migrated, but not fatal after this fix - Commit
3770ac89: 0x2b79c0 32% free
2. ES8311 Codec Init Blocking Boot
Before:
auto* i2c_bus = device_find_by_name("i2c0");
if (i2c_bus) { error_t e = initCodec(i2c_bus); if (e!=NONE) LOG_E(...); }
- If ES8311 at 0x18 not responding (no pullups, power not stable),
i2c_controller_write_register_arrayblocks 1s timeout per transaction, could lock bus → freeze
After:
vTaskDelay(100ms); // after GPIO46 amp HIGH, power stabilize
if (i2c_controller_has_device_at_address(bus, 0x18, 200ms)==NONE) {
initCodec(bus); // non-fatal, logs success
} else {
LOG_W("ES8311 not found at 0x18, skipping");
}
- Probe first, non-fatal, 100ms delay for amp power, 200ms probe timeout
- Prevents bus lock if codec missing
3. ST7305 Reset Race — SPI Too Early
Before:
esp_lcd_panel_reset(panelHandle);
esp_lcd_panel_init(panelHandle); // no delay
After (Drivers/ST7305/Source/St7305Display.cpp):
esp_lcd_panel_reset(panelHandle);
vTaskDelay(150ms); // ST7305 datasheet needs >120ms after reset
esp_lcd_panel_init(panelHandle);
vTaskDelay(50ms);
- Prevents freeze on fast boot where SPI transaction sent before panel ready, leaving display stuck on garbage / weird state
- Observed
Hal: ST7305 startedtime 1828ms → 2160ms (+330ms)
4. DisplayIdle Wake Logic Gated by supportsBacklight (50d86de4)
Before (after MCP flash fix):
if (supportsBacklight) { if (!timeoutEnabled) restore else { if (inactive>=timeout) activate else if (dimmed) wake } }
RLCD supportsBacklight==false → entire idle/wake state machine skipped → once displayDimmed=true, never wakes → appears locked.
After:
Timeout logic runs for ALL displays, only setBacklightDuty() guarded by supportsBacklight && display != nullptr. See references/display-idle.md.
Pin Conflicts to Watch
- GPIO9: ES3C28P BAT_ADC (ADC1_CH8) vs RLCD I2S BCLK pin 5 (not 9, but ES3C uses GPIO9 free, check DTS
pin-bclkbefore reusing) - I2C0 SDA 13 / SCL 14 — shared with nothing else on RLCD, but must be
i2c-mastercompatible - SPI0: CS 40, MOSI 12, SCLK 11, DC 5, RST 41 — exclusive
- SDMMC: CLK 38, CMD 21, D0 39, D3 17, 1-bit
Screenshot Lock Contention + Permanent Freeze Related (fixed 6f095081)
See references/mcp-system.md + references/display-idle.md Screenshot Lock Contention + 50d86de4/6f095081 sections. Same root: ST7305 full_refresh heavy SPI copy holds LVGL lock 100ms, handleApiScreenshot 100ms timeout too short → could not acquire LVGL lock error. Also "screen is still showing the freezed image" even after 3770ac89 init fixes — cause was screensaver heavy animation auto-enter + video stream 5ms hog.
Fixes 6f095081 (build 0x2b79e0, same size, flashed):
- Screenshot API 100→500ms (WebServerService) + error msg "within 500ms"
- ScreenshotTask 50→300ms
- Video stream 5ms→30ms streaming, 50ms→100ms idle with comment "RLCD ST7305 SPI is slow and shared"
- DisplayIdle: for !supportsBacklight (RLCD) don't auto-enter screensaver, only wake — prevents heavy bouncing balls full_refresh that freezes
- Power cycle required to clear ST7305 RAM (RTS reset doesn't clear controller RAM, frozen image persists until USB unplug 3s)
- Verification 14 checks PASS hermes-verify-jw_55oxm.py
Verification
- Serial log via pyserial 15-20s: check
CONFLICTgone or benign,ES8311 codec initialized,Hal: ST7305 started~2160ms,Monochrome/RLCD detected, noGuru Meditation, APTactility-5C7Dup - Ad-hoc script 16 checks PASS (DTS master, probe, delays, wake logic, MCP guard, build artifact)
- Build:
waveshare-esp32-s3-rlcd 0x2b79c0 32% freeflashed/dev/cu.usbmodem101
Recommendations
- For RLCD, default
displayTimeout = 0 (Never)orScreensaverType::Noneto avoid heavy bouncing balls full_refresh (120k pixels * SPI 10MHz = ~96ms per frame blocks UI) - Upstream: remove legacy
esp32_i2cdriver registration from platform-esp32 if all boards migrated to master, to eliminate CONFLICT warning entirely - Consider
trans_sizeor partial refresh for ST7305 to reduce LVGL hold time, or lower SPI queue depth from 10 to 3