first commit

This commit is contained in:
matt committed 2026-08-03 20:31:28 -07:00
commit 0ef2017d18
28 files changed
+1882

No files matched your search

+136
View File
@@ -0,0 +1,136 @@
# 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
```bash
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
```bash
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. Do **not** enable the RGB
bounce buffer either — its refill ISR reads PSRAM, which shares the cache with
flash on the S3, so any NVS write faults it with *"Cache disabled but cached
memory region accessed"*.