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
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
# 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.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=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: init` → `module: start waveshare-esp32-s3-rlcd` → `module: 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 i2c0` → `esp32_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: BootInitHalBegin` → `gpio46 HIGH` amp enable
|
||||
9. `WaveshareRLCD: ES8311 codec initialized` (after 3770ac89 fix) vs previously missing or failed
|
||||
10. `Devices: Registering ST7305` → `Hal: ST7305 starting` → `ST7305: Starting SPI panel IO creation` → `Hal: ST7305 started` (2160ms with delays vs 1828ms before)
|
||||
11. `Tactility: DEBUG: Listing /sdcard` → `NTP: Restoring last known time`
|
||||
12. `Bluetooth: Auto-enabling BLE` → `BLE host task started` + `GAP procedure...` + `startAdvertising: set_fields failed rc=4` (benign)
|
||||
13. `ServiceRegistration: Adding Gps/wifi/Development/EspNow/WebServer` → `WebServerService: 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 task` → `Started 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 backlight` → `Tactility: Registering internal apps` → `Loader: Start by id Launcher` → `GuiService: Showing Launcher`
|
||||
18. `WifiBootSplashInit: 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:**
|
||||
```dts
|
||||
#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):**
|
||||
```dts
|
||||
#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:**
|
||||
```cpp
|
||||
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:**
|
||||
```cpp
|
||||
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:**
|
||||
```cpp
|
||||
esp_lcd_panel_reset(panelHandle);
|
||||
esp_lcd_panel_init(panelHandle); // no delay
|
||||
```
|
||||
|
||||
**After (Drivers/ST7305/Source/St7305Display.cpp):**
|
||||
```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):**
|
||||
```cpp
|
||||
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
|
||||
|
||||
## 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 `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
|
||||
Reference in New Issue
Block a user