Touchscreen driver POC
Example documentation
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user