Touchscreen driver POC

Example documentation
This commit is contained in:
2026-05-07 16:28:08 -07:00
parent c0ad8306a7
commit a125f45b34
201 changed files with 19783 additions and 34 deletions
+51 -31
View File
@@ -1,14 +1,30 @@
# Waveshare 2.9" E-Paper LVGL Project
## Goal Summary
- Extend the native waveshare_epaper component of esphome to support 2-bit grayscale
- Only concerned with this one target board: `2.90inv2-r2`
- Waveshare 2.9" Touch ePaper HAT driving LVGL UI via ESPHome on ESP32
- 2-bit grayscale display support implemented via custom ESPHome component
- Touchscreen integration in progress
## Hardware
- ESP32-WROOM-32 (nodemcu-32s board)
- Waveshare 2.9inch Touch ePaper HAT (296x128, 2-bit grayscale)
- Display model string: `2.90inv2-r2` (SSD1680 controller)
- Pin mapping: SCLK=GPIO23, MOSI=GPIO22, CS=GPIO19, DC=GPIO18, BUSY=GPIO4, RST=GPIO5
- Waveshare 2.9inch Touch ePaper HAT (296x128, SSD1680 display, GT1151 touchscreen)
### Pin Mapping
| Function | ESP Pin | HAT Pin |
|--- |--- |--- |
| Display SCLK | GPIO18 | Pin23/GPIO11/SPI0_SCLK |
| Display MOSI | GPIO23 | Pin19/GPIO10/SPI0_MOSI |
| Display CS | GPIO19 | Pin24/GPIO8/SPI0_CE0 |
| Display DC | GPIO17 | Pin22/GPIO25 |
| Display BUSY | GPIO4 | Pin24/GPIO24 |
| Display RST | GPIO5 | Pin11/GPIO17/SPI1_CE1 |
| Touch SDA | GPIO21 | Pin3/GPIO2/I2C1_SDA |
| Touch SCL | GPIO22 | Pin5/GPIO3/I2C1_SCL |
| Touch RST | GPIO15 | Pin13/GPIO27 |
| Touch INT | GPIO16 | Pin15/GPIO22 |
Note: GPIO15 (Touch RST) is an ESP32 strapping pin, but only affects boot log
output (LOW silences it). No impact on UART download mode or OTA flashing.
## Software
- ESPHome 2026.1.0
@@ -16,37 +32,41 @@
- Project directory: ~/Projects/waveshare-panel
## Current Status
- Display working correctly via ESPHome's native display library
- LVGL rendering confirmed working (black background visible)
- Custom component `waveshare_epaper_2bit` created and flashed, working identically to built-in driver
- Custom model name: `2.90inv2-r2-2bpp`
- 2-bit grayscale fully working via custom component `waveshare_epaper_2bit`
- LVGL rendering working, 4-shade colorspace confirmed
- Bayer ordered dithering implemented for gradient smoothing
- Touchscreen wiring complete, driver integration in progress
## Known Issues / Quirks
- LVGL color depth is hardcoded to 16-bit (RGB565) in ESPHome — only supported value
- Display renders in 1-bit monochrome despite panel supporting 2-bit grayscale
- Colors are inverted by default (bg_color: 0x000000 produces white background)
- Resolved: original timeout errors were caused by on_draw_end hammering the display
## Grayscale Implementation
Custom component model name: `2.90inv2-r2-2bpp`
## Working LVGL Config
- refer to the file waveshare-test.yaml
## Goal: 2-bit Grayscale Support
The SSD1680 controller supports 4-shade grayscale via two bitplanes:
- Register 0x24: bitplane 1
- Register 0x26: bitplane 2
Key implementation details:
- Gray4 waveform LUT loaded via register 0x32 on every display() call
- Activation uses 0xC7 (custom LUT), not 0xF7 (which reloads OTP LUT)
- Always does full refresh — partial update removed from 2bpp class
- Second bitplane buffer (`buffer2_`) allocated in `initialize()`
Confirmed bitplane table for this panel with Gray4 LUT:
| 0x24 | 0x26 | Result |
|------|------|------------|
| 1 | 1 | White |
| 1 | 0 | Light grey |
| 0 | 1 | Dark grey |
| 0 | 0 | Black |
| 0 | 0 | White |
| 1 | 0 | Light grey |
| 0 | 1 | Dark grey |
| 1 | 1 | Black |
The ESPHome driver only writes to 0x24, discarding grayscale information.
The real problem is upstream — RGB565 is converted to 1-bit before display() is
called, so grayscale data is lost before the driver even sees it. Next step is to
find where this conversion happens in the ESPHome display pipeline so the patch
can preserve grayscale information through to the two-bitplane write.
Color convention: 0xFFFFFF=white, 0x000000=black (no inversion).
LVGL 4-shade palette: 0xFFFFFF, 0xAAAAAA, 0x555555, 0x000000
Dithering: 4×4 Bayer ordered dither, bias = bayer*2 - 15 (±15 range).
Amplitude chosen to keep 0xAAAAAA and 0x555555 as solid fills.
## Touchscreen
- Controller: ICNT86X (I2C address 0x48; 0x30 also ACKs, likely DFU/bootloader interface)
- Custom component: `custom_components/icnt86x/` (ESPHome has no native ICNT86X driver)
- Touch count at register 0x1001, touch data at 0x1002 (7 bytes/point), clear by writing 0x00 to 0x1001
- Waveshare's own driver inverts both axes: X = 295 - raw_x, Y = 127 - raw_y
- INT pulse is very brief (sub-ms); component works in polling mode regardless
- Coordinate transform (mirror_x, mirror_y, swap_xy) to be confirmed after first flash
## Relevant Files
- Custom component: `custom_components/waveshare_epaper_2bit/`
@@ -54,6 +74,6 @@ can preserve grayscale information through to the two-bitplane write.
- `display.py` — component schema and model registration
- `waveshare_epaper_2bit.cpp` — driver implementation
- `waveshare_epaper_2bit.h` — header
- Main config: `waveshare-test.yaml` (pin table here is source of truth)
- Build cache: `.esphome/build/waveshare-epaper-test/src/esphome/components/`
- ESPHome source (venv): ~/Code/esphome-2026.1.0/
- Applicable ESPHome source is copied into .esphome/build by the build system at compile time