Files
tactility/.claude/skills/tactility-firmware/references/rlcd-initialization.md
T
Adolfo Reyna 12035f2f39 chore: move firmware skills in-repo + AGENTS.md
- 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
2026-07-17 09:51:08 -04:00

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, gpio0
  • Devices/waveshare-esp32-s3-rlcd/Source/Configuration.cppinitBoot() 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=0
  • Tactility/Source/service/displayidle/DisplayIdle.cpp — idle timer + wake logic (50d86de4 fix: run for all displays)

Initialization Sequence (from serial log)

  1. Bootloader 2nd stage → PSRAM 8MB 120MHz detected
  2. kernel: initmodule: start waveshare-esp32-s3-rlcdmodule: start platform-esp32
  3. driver: add esp32_gpio, esp32_i2c, esp32_i2c_master, esp32_i2s, esp32_sdmmc, ... — both legacy and NG I2C drivers registered
  4. device: start i2c0esp32_i2c_master: start i2c0 (after fix) vs previously esp32_i2c: start i2c0
  5. E (907) i2c: CONFLICT! driver_ng is not allowed to be used with this old driver — IDF 5.x forbids mixing old i2c_driver_install and new i2c_new_master_bus on 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.
  6. gpio: GPIO[13] InputEn:1 OutputEn:1 OpenDrain:1 / GPIO14 — I2C pins
  7. esp32_sdmmc_fs: Mounted /sdcard — SD 1-bit, CLK 38, CMD 21, D0 39, D3 17
  8. SystemEvents: BootInitHalBegingpio46 HIGH amp enable
  9. WaveshareRLCD: ES8311 codec initialized (after 3770ac89 fix) vs previously missing or failed
  10. Devices: Registering ST7305Hal: ST7305 startingST7305: Starting SPI panel IO creationHal: ST7305 started (2160ms with delays vs 1828ms before)
  11. Tactility: DEBUG: Listing /sdcardNTP: Restoring last known time
  12. Bluetooth: Auto-enabling BLEBLE host task started + GAP procedure... + startAdvertising: set_fields failed rc=4 (benign)
  13. ServiceRegistration: Adding Gps/wifi/Development/EspNow/WebServerWebServerService: AP started SSID Tactility-5C7D 192.168.4.1 + HttpServer Started port 80
  14. McpSystem: Mono TCP Video stream server started 8081 + Color 8083
  15. LVGL: Starting LVGL taskStarted ST7305 + ButtonControl: Start
  16. DisplayIdle: Monochrome/RLCD display detected: idle timer will run but auto-backlight off is disabled
  17. Boot: Setup display / Boot: No backlightTactility: Registering internal appsLoader: Start by id LauncherGuiService: Showing Launcher
  18. WifiBootSplashInit: Auto-enabling WiFiWifiService: EnabledSTA_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) and esp32_i2c_master (new) drivers
  • When i2c0 used old driver, IDF warns CONFLICT if any component uses driver_ng (i2c_master internal) — happens because esp32_i2c_master driver 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 i2c0 in 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_array blocks 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 started time 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-bclk before reusing)
  • I2C0 SDA 13 / SCL 14 — shared with nothing else on RLCD, but must be i2c-master compatible
  • SPI0: CS 40, MOSI 12, SCLK 11, DC 5, RST 41 — exclusive
  • SDMMC: CLK 38, CMD 21, D0 39, D3 17, 1-bit

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 CONFLICT gone or benign, ES8311 codec initialized, Hal: ST7305 started ~2160ms, Monochrome/RLCD detected, no Guru Meditation, AP Tactility-5C7D up
  • Ad-hoc script 16 checks PASS (DTS master, probe, delays, wake logic, MCP guard, build artifact)
  • Build: waveshare-esp32-s3-rlcd 0x2b79c0 32% free flashed /dev/cu.usbmodem101

Recommendations

  1. For RLCD, default displayTimeout = 0 (Never) or ScreensaverType::None to avoid heavy bouncing balls full_refresh (120k pixels * SPI 10MHz = ~96ms per frame blocks UI)
  2. Upstream: remove legacy esp32_i2c driver registration from platform-esp32 if all boards migrated to master, to eliminate CONFLICT warning entirely
  3. Consider trans_size or partial refresh for ST7305 to reduce LVGL hold time, or lower SPI queue depth from 10 to 3