mattandClaude Fable 5 5bbb50c4eb Add TODO.md handoff notes for the remaining review items
Records the design-level suggestions from the 2026-08-03 review that were
not part of dd2d428 (UI freeze during fetches, stale-data indication,
debug readout removal, dead utils.c, OTA partitions, small cleanups),
plus the facts already verified and questions already answered so a
future agent does not re-derive or re-ask them.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 20:51:46 -07:00
2026-08-03 20:31:28 -07:00
2026-08-03 20:31:28 -07:00
2026-08-03 20:31:28 -07:00
2026-08-03 20:31:28 -07:00

Thermostat HMI

ESP-IDF firmware for the Elecrow CrowPanel 7" Advance (ESP32-S3-WROOM-1-N16R8, 16MB flash + 8MB octal PSRAM, 800×480 IPS, GT911 capacitive touch).

Board variant matters. Elecrow sells a Basic CrowPanel 7" and an Advance CrowPanel 7". They share a form factor and almost nothing else: every RGB data pin, every sync pin, the pixel clock, the porches, the sync polarities and the touch I2C pins are different, and the Advance drives its backlight over I2C instead of from a GPIO. This firmware targets the Advance. Building it against Basic documentation yields a black screen and a silent I2C bus.

Currently displays outdoor conditions from an Ambient Weather personal weather station. Thermostat control (setpoint, HVAC relays, scheduling) is not implemented yet.

Components

Path Purpose
main/drivers/display RGB parallel panel via esp_lcd, double-buffered in PSRAM
main/drivers/touch GT911 capacitive touch over I2C, registered as an LVGL input device
main/ui/lvgl_port.c LVGL tick source, service task, and locking
main/ui/ui_manager.c Screen layout and update entry points
main/wifi WiFi station bring-up
main/api Ambient Weather REST client
main/utils Small helpers

Setup

cp main/app_config.h.example main/app_config.h
# edit main/app_config.h: WiFi credentials + Ambient Weather keys

main/app_config.h is gitignored — it holds your WiFi password and API keys.

Build & Flash

idf.py set-target esp32s3
idf.py build flash monitor

Requires ESP-IDF v5.3 or newer (the code uses the i2c_master driver API and dma_burst_size).

Hardware (Advance)

RGB panel (16-bit RGB565)

Signal GPIO
DE 42
VSYNC 41
HSYNC 40
PCLK 39
R0–R4 7, 17, 18, 3, 46
G0–G5 9, 10, 11, 12, 13, 14
B0–B4 21, 47, 48, 45, 38

Timings: 18 MHz pixel clock, HFP 8 / HPW 4 / HBP 8, VFP 8 / VPW 4 / VBP 8. Both syncs idle high; PCLK is pclk_active_neg.

I2C bus — GPIO 15 (SDA) / 16 (SCL)

Address Device
0x30 STC8H1K28 board controller — backlight and GT911 reset
0x51 RTC
0x5D GT911 touch

The GT911 has no ESP32 reset or interrupt GPIO on this board. The STC8H1K28 holds it in reset, so backlight_init() must run before touch_init() or the touch controller NACKs every address.

STC8H1K28 protocol is single raw bytes, no register addressing: 0x19 wake, then 0x10 and 0x18 to configure and release the GT911. Brightness is 0x00–0xF5 inverted (0x00 = full) on v1.3; v1.2 uses a narrower 0x05–0x10 range instead.

Display architecture

Two 800×480×2 framebuffers in PSRAM, LVGL in RENDER_MODE_DIRECT. LVGL draws into whichever buffer the LCD is not scanning; the last flush of a refresh hands that buffer to the panel, which switches to it. Nothing ever writes into the buffer being displayed — copying into the live framebuffer races the LCD's own reads, and the resulting corrupt pixels persist indefinitely because LVGL does not know they are wrong and never reinvalidates them.

A 16-scanline bounce buffer stages each chunk in internal RAM. 20 scanlines would be better but needs 64KB of internal RAM and fails to allocate once WiFi is up.

Known limitation: occasional left-edge artefacts

Short stale lines sometimes appear at the left edge, most often right after a weather fetch. The panel needs ~34 MB/s sustained from PSRAM at the 18MHz pixel clock, and 80MHz octal PSRAM is marginal for that. When the bounce refill ISR runs late — WiFi interrupt activity is enough — the panel scans out whatever was left in the buffer.

The pixel clock is not a usable lever: below roughly 40Hz refresh (about 15MHz) the panel stops locking and the image rolls vertically instead.

The real fix is 120MHz PSRAM, which is what the vendor's Arduino build uses (-DCONFIG_SPIRAM_SPEED_120M=1). On ESP-IDF 5.3 that is not safely reachable: selecting it raises the shared MSPI core clock, and IDF only ships flash timing tables pairing 120MHz PSRAM with 120MHz flash. Flash above 80MHz requires High Performance Mode and a DC-aware bootloader.

Gotchas

  • LVGL does not bubble events to parents. An event handler on the screen object never sees taps that land on a child widget, which looks exactly like dead touch zones. The touch readout polls the input device from an lv_timer instead.
  • lv_label_set_text() invalidates unconditionally, even when the text is identical — so setting an unchanged value still forces a repaint. All label updates go through label_set_if_changed().
  • WiFi must be brought up before display_init(). The bounce refill ISR reads PSRAM, which shares the cache with flash; an NVS write while the panel is running faults it with "Cache disabled but cached memory region accessed".

Memory notes

Two 800×480×2 framebuffers (750 KB each) live in PSRAM. The 8 MB octal PSRAM at 80 MHz is required — QSPI cannot sustain the RGB refresh.

Do not enable CONFIG_SPIRAM_FETCH_INSTRUCTIONS / CONFIG_SPIRAM_RODATA: they contend with the panel for PSRAM bandwidth. The bounce buffer is enabled (the panel cannot stream straight from PSRAM), which is exactly why flash writes are dangerous while the panel runs: the refill ISR reads PSRAM, which shares the cache with flash on the S3, so an NVS write faults it with "Cache disabled but cached memory region accessed". All flash writes happen before display_init() and CONFIG_ESP_WIFI_NVS_ENABLED is off to keep the WiFi stack from writing later.

S
Description
No description provided
Readme
476 KiB
0 Stars 1 Watchers 0 Forks
Languages
C 93.9%
Python 4.7%
CMake 1.4%