[mipi_spi] Add Waveshare ESP32-S3-Touch-LCD-1.46 SPD2010 model (#19056)

Co-authored-by: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com>
This commit is contained in:
Guillaume Rischard
2026-10-07 13:24:08 +11:00
committed by GitHub
co-authored by Clyde Stubbs
parent 923700dcc9
commit f3f79c301b
4 changed files with 569 additions and 2 deletions
@@ -0,0 +1,418 @@
# SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
# Adapted from esp_lcd_spd2010 2.0.0~1 for ESPHome.
# https://github.com/espressif/esp-iot-solution/tree/master/components/display/lcd/esp_lcd_spd2010
from esphome.components.mipi import MODE_RGB, DriverChip, delay
from esphome.components.spi import TYPE_QUAD
from esphome.const import CONF_MIRROR_X, CONF_MIRROR_Y
# fmt: off
DriverChip(
"WAVESHARE-ESP32-S3-TOUCH-LCD-1.46",
width=412,
height=412,
bus_mode=TYPE_QUAD,
spi_mode="MODE0",
data_rate="40MHz",
cs_pin=21,
reset_pin={"pca9554": None, "number": 1},
color_order=MODE_RGB,
draw_rounding=4,
transforms={CONF_MIRROR_X, CONF_MIRROR_Y},
use_axis_flips=True,
reset_delay=20,
requires={"pca9554"},
initsequence=(
delay(100),
(0xFF, 0x20, 0x10, 0x10),
(0x0C, 0x11),
(0x10, 0x02),
(0x11, 0x11),
(0x15, 0x42),
(0x16, 0x11),
(0x1A, 0x02),
(0x1B, 0x11),
(0x61, 0x80),
(0x62, 0x80),
(0x54, 0x44),
(0x58, 0x88),
(0x5C, 0xCC),
(0x20, 0x80),
(0x21, 0x81),
(0x22, 0x31),
(0x23, 0x20),
(0x24, 0x11),
(0x25, 0x11),
(0x26, 0x12),
(0x27, 0x12),
(0x30, 0x80),
(0x31, 0x81),
(0x32, 0x31),
(0x33, 0x20),
(0x34, 0x11),
(0x35, 0x11),
(0x36, 0x12),
(0x37, 0x12),
(0x41, 0x11),
(0x42, 0x22),
(0x43, 0x33),
(0x49, 0x11),
(0x4A, 0x22),
(0x4B, 0x33),
(0xFF, 0x20, 0x10, 0x15),
(0x00, 0x00),
(0x01, 0x00),
(0x02, 0x00),
(0x03, 0x00),
(0x04, 0x10),
(0x05, 0x0C),
(0x06, 0x23),
(0x07, 0x22),
(0x08, 0x21),
(0x09, 0x20),
(0x0A, 0x33),
(0x0B, 0x32),
(0x0C, 0x34),
(0x0D, 0x35),
(0x0E, 0x01),
(0x0F, 0x01),
(0x20, 0x00),
(0x21, 0x00),
(0x22, 0x00),
(0x23, 0x00),
(0x24, 0x0C),
(0x25, 0x10),
(0x26, 0x20),
(0x27, 0x21),
(0x28, 0x22),
(0x29, 0x23),
(0x2A, 0x33),
(0x2B, 0x32),
(0x2C, 0x34),
(0x2D, 0x35),
(0x2E, 0x01),
(0x2F, 0x01),
(0xFF, 0x20, 0x10, 0x16),
(0x00, 0x00),
(0x01, 0x00),
(0x02, 0x00),
(0x03, 0x00),
(0x04, 0x08),
(0x05, 0x04),
(0x06, 0x19),
(0x07, 0x18),
(0x08, 0x17),
(0x09, 0x16),
(0x0A, 0x33),
(0x0B, 0x32),
(0x0C, 0x34),
(0x0D, 0x35),
(0x0E, 0x01),
(0x0F, 0x01),
(0x20, 0x00),
(0x21, 0x00),
(0x22, 0x00),
(0x23, 0x00),
(0x24, 0x04),
(0x25, 0x08),
(0x26, 0x16),
(0x27, 0x17),
(0x28, 0x18),
(0x29, 0x19),
(0x2A, 0x33),
(0x2B, 0x32),
(0x2C, 0x34),
(0x2D, 0x35),
(0x2E, 0x01),
(0x2F, 0x01),
(0xFF, 0x20, 0x10, 0x12),
(0x00, 0x99),
(0x2A, 0x28),
(0x2B, 0x0F),
(0x2C, 0x16),
(0x2D, 0x28),
(0x2E, 0x0F),
(0xFF, 0x20, 0x10, 0xA0),
(0x08, 0xDC),
(0xFF, 0x20, 0x10, 0x45),
(0x01, 0x9C),
(0x03, 0x9C),
(0xFF, 0x20, 0x10, 0x42),
(0x05, 0x2C),
(0xFF, 0x20, 0x10, 0x11),
(0x50, 0x01),
(0xFF, 0x20, 0x10, 0x40),
(0x86, 0x00),
(0xFF, 0x20, 0x10, 0x12),
(0x0D, 0x66),
(0xFF, 0x20, 0x10, 0x17),
(0x39, 0x3C),
(0xFF, 0x20, 0x10, 0x31),
(0x38, 0x03),
(0x39, 0xF0),
(0x36, 0x03),
(0x37, 0xE8),
(0x34, 0x03),
(0x35, 0xCF),
(0x32, 0x03),
(0x33, 0xBA),
(0x30, 0x03),
(0x31, 0xA2),
(0x2E, 0x03),
(0x2F, 0x95),
(0x2C, 0x03),
(0x2D, 0x7E),
(0x2A, 0x03),
(0x2B, 0x62),
(0x28, 0x03),
(0x29, 0x44),
(0x26, 0x02),
(0x27, 0xFC),
(0x24, 0x02),
(0x25, 0xD0),
(0x22, 0x02),
(0x23, 0x98),
(0x20, 0x02),
(0x21, 0x6F),
(0x1E, 0x02),
(0x1F, 0x32),
(0x1C, 0x01),
(0x1D, 0xF6),
(0x1A, 0x01),
(0x1B, 0xB8),
(0x18, 0x01),
(0x19, 0x6E),
(0x16, 0x01),
(0x17, 0x41),
(0x14, 0x00),
(0x15, 0xFD),
(0x12, 0x00),
(0x13, 0xCF),
(0x10, 0x00),
(0x11, 0x98),
(0x0E, 0x00),
(0x0F, 0x89),
(0x0C, 0x00),
(0x0D, 0x79),
(0x0A, 0x00),
(0x0B, 0x67),
(0x08, 0x00),
(0x09, 0x55),
(0x06, 0x00),
(0x07, 0x3F),
(0x04, 0x00),
(0x05, 0x28),
(0x02, 0x00),
(0x03, 0x0E),
(0xFF, 0x20, 0x10, 0x32),
(0x38, 0x03),
(0x39, 0xF0),
(0x36, 0x03),
(0x37, 0xE8),
(0x34, 0x03),
(0x35, 0xCF),
(0x32, 0x03),
(0x33, 0xBA),
(0x30, 0x03),
(0x31, 0xA2),
(0x2E, 0x03),
(0x2F, 0x95),
(0x2C, 0x03),
(0x2D, 0x7E),
(0x2A, 0x03),
(0x2B, 0x62),
(0x28, 0x03),
(0x29, 0x44),
(0x26, 0x02),
(0x27, 0xFC),
(0x24, 0x02),
(0x25, 0xD0),
(0x22, 0x02),
(0x23, 0x98),
(0x20, 0x02),
(0x21, 0x6F),
(0x1E, 0x02),
(0x1F, 0x32),
(0x1C, 0x01),
(0x1D, 0xF6),
(0x1A, 0x01),
(0x1B, 0xB8),
(0x18, 0x01),
(0x19, 0x6E),
(0x16, 0x01),
(0x17, 0x41),
(0x14, 0x00),
(0x15, 0xFD),
(0x12, 0x00),
(0x13, 0xCF),
(0x10, 0x00),
(0x11, 0x98),
(0x0E, 0x00),
(0x0F, 0x89),
(0x0C, 0x00),
(0x0D, 0x79),
(0x0A, 0x00),
(0x0B, 0x67),
(0x08, 0x00),
(0x09, 0x55),
(0x06, 0x00),
(0x07, 0x3F),
(0x04, 0x00),
(0x05, 0x28),
(0x02, 0x00),
(0x03, 0x0E),
(0xFF, 0x20, 0x10, 0x11),
(0x60, 0x01),
(0x65, 0x03),
(0x66, 0x38),
(0x67, 0x04),
(0x68, 0x34),
(0x69, 0x03),
(0x61, 0x03),
(0x62, 0x38),
(0x63, 0x04),
(0x64, 0x34),
(0x0A, 0x11),
(0x0B, 0x20),
(0x0C, 0x20),
(0x55, 0x06),
(0xFF, 0x20, 0x10, 0x42),
(0x05, 0x3D),
(0x06, 0x03),
(0xFF, 0x20, 0x10, 0x12),
(0x1F, 0xDC),
(0xFF, 0x20, 0x10, 0x17),
(0x11, 0xAA),
(0x16, 0x12),
(0x0B, 0xC3),
(0x10, 0x0E),
(0x14, 0xAA),
(0x18, 0xA0),
(0x1A, 0x80),
(0x1F, 0x80),
(0xFF, 0x20, 0x10, 0x11),
(0x30, 0xEE),
(0xFF, 0x20, 0x10, 0x12),
(0x15, 0x0F),
(0xFF, 0x20, 0x10, 0x2D),
(0x01, 0x3E),
(0xFF, 0x20, 0x10, 0x40),
(0x83, 0xC4),
(0xFF, 0x20, 0x10, 0x12),
(0x00, 0xCC),
(0x36, 0xA0),
(0x2A, 0x2D),
(0x2B, 0x1E),
(0x2C, 0x26),
(0x2D, 0x2D),
(0x2E, 0x1E),
(0x1F, 0xE6),
(0xFF, 0x20, 0x10, 0xA0),
(0x08, 0xE6),
(0xFF, 0x20, 0x10, 0x12),
(0x10, 0x0F),
(0xFF, 0x20, 0x10, 0x18),
(0x01, 0x01),
(0x00, 0x1E),
(0xFF, 0x20, 0x10, 0x43),
(0x03, 0x04),
(0xFF, 0x20, 0x10, 0x18),
(0x3A, 0x01),
(0xFF, 0x20, 0x10, 0x50),
(0x05, 0x08),
(0xFF, 0x20, 0x10, 0x50),
(0x00, 0xA6),
(0x01, 0xA6),
(0xFF, 0x20, 0x10, 0x50),
(0x08, 0x55),
(0xFF, 0x20, 0x10, 0x10),
(0x0B, 0x43),
(0x0C, 0x12),
(0x10, 0x01),
(0x11, 0x12),
(0x15, 0x00),
(0x16, 0x00),
(0x1A, 0x00),
(0x1B, 0x00),
(0x61, 0x00),
(0x62, 0x00),
(0x51, 0x11),
(0x55, 0x55),
(0x58, 0x00),
(0x5C, 0x00),
(0xFF, 0x20, 0x10, 0x10),
(0x20, 0x81),
(0x21, 0x82),
(0x22, 0x72),
(0x30, 0x00),
(0x31, 0x00),
(0x32, 0x00),
(0xFF, 0x20, 0x10, 0x10),
(0x44, 0x44),
(0x45, 0x55),
(0x46, 0x66),
(0x47, 0x77),
(0x49, 0x00),
(0x4A, 0x00),
(0x4B, 0x00),
(0xFF, 0x20, 0x10, 0x17),
(0x37, 0x00),
(0xFF, 0x20, 0x10, 0x15),
(0x04, 0x08),
(0x05, 0x04),
(0x06, 0x1C),
(0x07, 0x1A),
(0x08, 0x18),
(0x09, 0x16),
(0x24, 0x05),
(0x25, 0x09),
(0x26, 0x17),
(0x27, 0x19),
(0x28, 0x1B),
(0x29, 0x1D),
(0xFF, 0x20, 0x10, 0x16),
(0x04, 0x09),
(0x05, 0x05),
(0x06, 0x1D),
(0x07, 0x1B),
(0x08, 0x19),
(0x09, 0x17),
(0x24, 0x04),
(0x25, 0x08),
(0x26, 0x16),
(0x27, 0x18),
(0x28, 0x1A),
(0x29, 0x1C),
(0xFF, 0x20, 0x10, 0x18),
(0x1F, 0x02),
(0xFF, 0x20, 0x10, 0x11),
(0x15, 0x99),
(0x16, 0x99),
(0x1C, 0x88),
(0x1D, 0x88),
(0x1E, 0x88),
(0x13, 0xF0),
(0x14, 0x34),
(0xFF, 0x20, 0x10, 0x12),
(0x12, 0x89),
(0x06, 0x06),
(0x18, 0x00),
(0xFF, 0x20, 0x10, 0x11),
(0x0A, 0x00),
(0x0B, 0xF0),
(0x0C, 0xF0),
(0x6A, 0x10),
(0xFF, 0x20, 0x10, 0x11),
(0x08, 0x70),
(0x09, 0x00),
(0xFF, 0x20, 0x10, 0x00),
(0x35, 0x00),
(0xFF, 0x20, 0x10, 0x12),
(0x21, 0x70),
(0xFF, 0x20, 0x10, 0x2D),
(0x02, 0x00),
(0xFF, 0x20, 0x10, 0x00),
),
)
# fmt: on
+12 -2
View File
@@ -119,17 +119,19 @@ def set_component_config() -> Callable[[str, Any], None]:
@pytest.fixture
def choose_variant_with_pins() -> Generator[Callable[[list], None]]:
"""Set the ESP32 variant to the first one on which all the given pins are valid.
"""Set the ESP32 variant to the first one on which all internal pins are valid.
For ESP32 only, since the other platforms do not have variants. The core
configuration must already have been set up for an ESP32 target.
Pins supplied by external components are validated by the caller's component
schema and do not affect ESP32 variant selection.
Using local imports to avoid importing when ESP32 is not the target.
"""
from esphome import config_validation as cv
from esphome.components.esp32 import KEY_ESP32, KEY_VARIANT, VARIANTS
from esphome.components.esp32.gpio import validate_gpio_pin
from esphome.const import CONF_INPUT, CONF_OUTPUT
from esphome.pins import gpio_pin_schema
from esphome.pins import PIN_SCHEMA_REGISTRY, gpio_pin_schema
def chooser(pins: list) -> None:
for variant in VARIANTS:
@@ -137,6 +139,14 @@ def choose_variant_with_pins() -> Generator[Callable[[list], None]]:
CORE.data[KEY_ESP32][KEY_VARIANT] = variant
for pin in pins:
if pin is not None:
# Pins provided by external components do not constrain
# the ESP32 variant. Their own schemas are exercised by
# the component configuration test.
if (
isinstance(pin, dict)
and PIN_SCHEMA_REGISTRY.get_key(pin) != CORE.target_platform
):
continue
pin = gpio_pin_schema(
{
CONF_INPUT: True,
@@ -0,0 +1,23 @@
esphome:
name: waveshare-spd2010
esp32:
board: esp32-s3-devkitc-1
framework:
type: esp-idf
i2c:
sda: 11
scl: 10
pca9554:
id: board_expander
spi:
type: quad
clk_pin: 40
data_pins: [46, 45, 42, 41]
display:
- platform: mipi_spi
model: WAVESHARE-ESP32-S3-TOUCH-LCD-1.46
+116
View File
@@ -22,6 +22,10 @@ from esphome.components.mipi_spi.display import (
MODELS,
dimension_schema,
)
# Register the external pin schema used by the SPD2010 model's reset pin.
import esphome.components.pca9554 # noqa: F401
from esphome.config import validate_config
from esphome.const import (
CONF_DC_PIN,
CONF_DIMENSIONS,
@@ -397,6 +401,118 @@ def test_lvgl_generation(
assert "set_auto_clear(false);" in main_cpp
def test_spd2010_expander_default_generation(
generate_main: Callable[[str | Path], str],
component_fixture_path: Callable[[str], Path],
) -> None:
"""The SPD2010 default reset pin resolves through a configured PCA9554."""
main_cpp = generate_main(component_fixture_path("spd2010.yaml"))
assert "new(pca9554_pca9554gpiopin_id) pca9554::PCA9554GPIOPin();" in main_cpp
assert "pca9554_pca9554gpiopin_id->set_parent(board_expander);" in main_cpp
assert "pca9554_pca9554gpiopin_id->set_pin(1);" in main_cpp
assert "mipi_spi_mipispi_id->set_reset_pin(pca9554_pca9554gpiopin_id);" in main_cpp
def test_spd2010_default_reset_requires_expander() -> None:
"""A registered pin schema alone cannot satisfy the default expander reference."""
result = validate_config(
{
"esphome": {"name": "missing-expander"},
"esp32": {
"board": "esp32-s3-devkitc-1",
"framework": {"type": "esp-idf"},
},
"spi": {
"type": "quad",
"clk_pin": 40,
"data_pins": [46, 45, 42, 41],
},
"display": [
{
"platform": "mipi_spi",
"model": "WAVESHARE-ESP32-S3-TOUCH-LCD-1.46",
}
],
},
{},
)
assert len(result.errors) == 1
error = result.errors[0]
assert (
"WAVESHARE-ESP32-S3-TOUCH-LCD-1.46 requires component 'pca9554' "
"to be configured" in str(error)
)
assert error.path == ["display", 0]
def test_spd2010_rejects_invalid_expander_pin() -> None:
"""The reset pin must exist on the configured PCA9554 variant."""
result = validate_config(
{
"esphome": {"name": "invalid-expander-pin"},
"esp32": {
"board": "esp32-s3-devkitc-1",
"framework": {"type": "esp-idf"},
},
"i2c": {"sda": 11, "scl": 10},
"pca9554": {"id": "board_expander"},
"spi": {
"type": "quad",
"clk_pin": 40,
"data_pins": [46, 45, 42, 41],
},
"display": [
{
"platform": "mipi_spi",
"model": "WAVESHARE-ESP32-S3-TOUCH-LCD-1.46",
"reset_pin": {
"pca9554": "board_expander",
"number": 8,
},
}
],
},
{},
)
assert len(result.errors) == 1
error = result.errors[0]
assert "Pin number must be in range 0-7" in str(error)
assert error.path == ["display", 0]
def test_spd2010_rejects_invalid_internal_pin() -> None:
"""An invalid internal reset pin is still checked by the ESP32 schema."""
result = validate_config(
{
"esphome": {"name": "invalid-internal-pin"},
"esp32": {
"board": "esp32-s3-devkitc-1",
"framework": {"type": "esp-idf"},
},
"i2c": {"sda": 11, "scl": 10},
"pca9554": {"id": "board_expander"},
"spi": {
"type": "quad",
"clk_pin": 40,
"data_pins": [46, 45, 42, 41],
},
"display": [
{
"platform": "mipi_spi",
"model": "WAVESHARE-ESP32-S3-TOUCH-LCD-1.46",
"reset_pin": 99,
}
],
},
{},
)
assert len(result.errors) == 1
error = result.errors[0]
assert "Invalid pin number: 99" in str(error)
assert error.path == ["display", 0, "reset_pin"]
# A 10ms delay (flattened to {10, 0xFF}, where 0xFF is the delay marker byte) is
# always prepended to the init sequence, since both a software and a hardware reset
# need to settle before further commands. A custom model has no reset_pin default