mirror of
https://github.com/esphome/esphome.git
synced 2025-09-09 14:52:20 +01:00
[mipi_rgb] Unified driver for MIPI RGB displays (#9892)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
@@ -299,6 +299,7 @@ esphome/components/mics_4514/* @jesserockz
|
||||
esphome/components/midea/* @dudanov
|
||||
esphome/components/midea_ir/* @dudanov
|
||||
esphome/components/mipi_dsi/* @clydebarrow
|
||||
esphome/components/mipi_rgb/* @clydebarrow
|
||||
esphome/components/mipi_spi/* @clydebarrow
|
||||
esphome/components/mitsubishi/* @RubyBailey
|
||||
esphome/components/mixer/speaker/* @kahrendt
|
||||
|
@@ -222,6 +222,12 @@ def delay(ms):
|
||||
|
||||
|
||||
class DriverChip:
|
||||
"""
|
||||
A class representing a MIPI DBI driver chip model.
|
||||
The parameters supplied as defaults will be used to provide default values for the display configuration.
|
||||
Setting swap_xy to cv.UNDEFINED will indicate that the model does not support swapping X and Y axes.
|
||||
"""
|
||||
|
||||
models: dict[str, Self] = {}
|
||||
|
||||
def __init__(
|
||||
@@ -232,7 +238,7 @@ class DriverChip:
|
||||
):
|
||||
name = name.upper()
|
||||
self.name = name
|
||||
self.initsequence = initsequence or defaults.get("init_sequence")
|
||||
self.initsequence = initsequence
|
||||
self.defaults = defaults
|
||||
DriverChip.models[name] = self
|
||||
|
||||
@@ -246,6 +252,17 @@ class DriverChip:
|
||||
return models
|
||||
|
||||
def extend(self, name, **kwargs) -> "DriverChip":
|
||||
"""
|
||||
Extend the current model with additional parameters or a modified init sequence.
|
||||
Parameters supplied here will override the defaults of the current model.
|
||||
if the initsequence is not provided, the current model's initsequence will be used.
|
||||
If add_init_sequence is provided, it will be appended to the current initsequence.
|
||||
:param name:
|
||||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
initsequence = list(kwargs.pop("initsequence", self.initsequence))
|
||||
initsequence.extend(kwargs.pop("add_init_sequence", ()))
|
||||
defaults = self.defaults.copy()
|
||||
if (
|
||||
CONF_WIDTH in defaults
|
||||
@@ -260,23 +277,39 @@ class DriverChip:
|
||||
):
|
||||
defaults[CONF_NATIVE_HEIGHT] = defaults[CONF_HEIGHT]
|
||||
defaults.update(kwargs)
|
||||
return DriverChip(name, initsequence=self.initsequence, **defaults)
|
||||
return self.__class__(name, initsequence=tuple(initsequence), **defaults)
|
||||
|
||||
def get_default(self, key, fallback: Any = False) -> Any:
|
||||
return self.defaults.get(key, fallback)
|
||||
|
||||
@property
|
||||
def transforms(self) -> set[str]:
|
||||
"""
|
||||
Return the available transforms for this model.
|
||||
"""
|
||||
if self.get_default("no_transform", False):
|
||||
return set()
|
||||
if self.get_default(CONF_SWAP_XY) != cv.UNDEFINED:
|
||||
return {CONF_MIRROR_X, CONF_MIRROR_Y, CONF_SWAP_XY}
|
||||
return {CONF_MIRROR_X, CONF_MIRROR_Y}
|
||||
|
||||
def option(self, name, fallback=False) -> cv.Optional:
|
||||
return cv.Optional(name, default=self.get_default(name, fallback))
|
||||
|
||||
def rotation_as_transform(self, config) -> bool:
|
||||
"""
|
||||
Check if a rotation can be implemented in hardware using the MADCTL register.
|
||||
A rotation of 180 is always possible, 90 and 270 are possible if the model supports swapping X and Y.
|
||||
A rotation of 180 is always possible if x and y mirroring are supported, 90 and 270 are possible if the model supports swapping X and Y.
|
||||
"""
|
||||
transforms = self.transforms
|
||||
rotation = config.get(CONF_ROTATION, 0)
|
||||
return rotation and (
|
||||
self.get_default(CONF_SWAP_XY) != cv.UNDEFINED or rotation == 180
|
||||
)
|
||||
if rotation == 0 or not transforms:
|
||||
return False
|
||||
if rotation == 180:
|
||||
return CONF_MIRROR_X in transforms and CONF_MIRROR_Y in transforms
|
||||
if rotation == 90:
|
||||
return CONF_SWAP_XY in transforms and CONF_MIRROR_X in transforms
|
||||
return CONF_SWAP_XY in transforms and CONF_MIRROR_Y in transforms
|
||||
|
||||
def get_dimensions(self, config) -> tuple[int, int, int, int]:
|
||||
if CONF_DIMENSIONS in config:
|
||||
@@ -301,10 +334,10 @@ class DriverChip:
|
||||
|
||||
# if mirroring axes and there are offsets, also mirror the offsets to cater for situations where
|
||||
# the offset is asymmetric
|
||||
if transform[CONF_MIRROR_X]:
|
||||
if transform.get(CONF_MIRROR_X):
|
||||
native_width = self.get_default(CONF_NATIVE_WIDTH, width + offset_width * 2)
|
||||
offset_width = native_width - width - offset_width
|
||||
if transform[CONF_MIRROR_Y]:
|
||||
if transform.get(CONF_MIRROR_Y):
|
||||
native_height = self.get_default(
|
||||
CONF_NATIVE_HEIGHT, height + offset_height * 2
|
||||
)
|
||||
@@ -314,7 +347,7 @@ class DriverChip:
|
||||
90,
|
||||
270,
|
||||
)
|
||||
if transform[CONF_SWAP_XY] is True or rotated:
|
||||
if transform.get(CONF_SWAP_XY) is True or rotated:
|
||||
width, height = height, width
|
||||
offset_height, offset_width = offset_width, offset_height
|
||||
return width, height, offset_width, offset_height
|
||||
@@ -324,27 +357,50 @@ class DriverChip:
|
||||
transform = config.get(
|
||||
CONF_TRANSFORM,
|
||||
{
|
||||
CONF_MIRROR_X: self.get_default(CONF_MIRROR_X, False),
|
||||
CONF_MIRROR_Y: self.get_default(CONF_MIRROR_Y, False),
|
||||
CONF_SWAP_XY: self.get_default(CONF_SWAP_XY, False),
|
||||
CONF_MIRROR_X: self.get_default(CONF_MIRROR_X),
|
||||
CONF_MIRROR_Y: self.get_default(CONF_MIRROR_Y),
|
||||
CONF_SWAP_XY: self.get_default(CONF_SWAP_XY),
|
||||
},
|
||||
)
|
||||
# fill in defaults if not provided
|
||||
mirror_x = transform.get(CONF_MIRROR_X, self.get_default(CONF_MIRROR_X))
|
||||
mirror_y = transform.get(CONF_MIRROR_Y, self.get_default(CONF_MIRROR_Y))
|
||||
swap_xy = transform.get(CONF_SWAP_XY, self.get_default(CONF_SWAP_XY))
|
||||
transform[CONF_MIRROR_X] = mirror_x
|
||||
transform[CONF_MIRROR_Y] = mirror_y
|
||||
transform[CONF_SWAP_XY] = swap_xy
|
||||
|
||||
# Can we use the MADCTL register to set the rotation?
|
||||
if can_transform and CONF_TRANSFORM not in config:
|
||||
rotation = config[CONF_ROTATION]
|
||||
if rotation == 180:
|
||||
transform[CONF_MIRROR_X] = not transform[CONF_MIRROR_X]
|
||||
transform[CONF_MIRROR_Y] = not transform[CONF_MIRROR_Y]
|
||||
transform[CONF_MIRROR_X] = not mirror_x
|
||||
transform[CONF_MIRROR_Y] = not mirror_y
|
||||
elif rotation == 90:
|
||||
transform[CONF_SWAP_XY] = not transform[CONF_SWAP_XY]
|
||||
transform[CONF_MIRROR_X] = not transform[CONF_MIRROR_X]
|
||||
transform[CONF_SWAP_XY] = not swap_xy
|
||||
transform[CONF_MIRROR_X] = not mirror_x
|
||||
else:
|
||||
transform[CONF_SWAP_XY] = not transform[CONF_SWAP_XY]
|
||||
transform[CONF_MIRROR_Y] = not transform[CONF_MIRROR_Y]
|
||||
transform[CONF_SWAP_XY] = not swap_xy
|
||||
transform[CONF_MIRROR_Y] = not mirror_y
|
||||
transform[CONF_TRANSFORM] = True
|
||||
return transform
|
||||
|
||||
def add_madctl(self, sequence: list, config: dict):
|
||||
# Add the MADCTL command to the sequence based on the configuration.
|
||||
use_flip = config.get(CONF_USE_AXIS_FLIPS)
|
||||
madctl = 0
|
||||
transform = self.get_transform(config)
|
||||
if transform[CONF_MIRROR_X]:
|
||||
madctl |= MADCTL_XFLIP if use_flip else MADCTL_MX
|
||||
if transform[CONF_MIRROR_Y]:
|
||||
madctl |= MADCTL_YFLIP if use_flip else MADCTL_MY
|
||||
if transform.get(CONF_SWAP_XY) is True: # Exclude Undefined
|
||||
madctl |= MADCTL_MV
|
||||
if config[CONF_COLOR_ORDER] == MODE_BGR:
|
||||
madctl |= MADCTL_BGR
|
||||
sequence.append((MADCTL, madctl))
|
||||
return madctl
|
||||
|
||||
def get_sequence(self, config) -> tuple[tuple[int, ...], int]:
|
||||
"""
|
||||
Create the init sequence for the display.
|
||||
@@ -367,21 +423,9 @@ class DriverChip:
|
||||
pixel_mode = PIXEL_MODES[pixel_mode]
|
||||
sequence.append((PIXFMT, pixel_mode))
|
||||
|
||||
# Does the chip use the flipping bits for mirroring rather than the reverse order bits?
|
||||
use_flip = config.get(CONF_USE_AXIS_FLIPS)
|
||||
madctl = 0
|
||||
transform = self.get_transform(config)
|
||||
if self.rotation_as_transform(config):
|
||||
LOGGER.info("Using hardware transform to implement rotation")
|
||||
if transform.get(CONF_MIRROR_X):
|
||||
madctl |= MADCTL_XFLIP if use_flip else MADCTL_MX
|
||||
if transform.get(CONF_MIRROR_Y):
|
||||
madctl |= MADCTL_YFLIP if use_flip else MADCTL_MY
|
||||
if transform.get(CONF_SWAP_XY) is True: # Exclude Undefined
|
||||
madctl |= MADCTL_MV
|
||||
if config[CONF_COLOR_ORDER] == MODE_BGR:
|
||||
madctl |= MADCTL_BGR
|
||||
sequence.append((MADCTL, madctl))
|
||||
madctl = self.add_madctl(sequence, config)
|
||||
if config[CONF_INVERT_COLORS]:
|
||||
sequence.append((INVON,))
|
||||
else:
|
||||
|
2
esphome/components/mipi_rgb/__init__.py
Normal file
2
esphome/components/mipi_rgb/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
CODEOWNERS = ["@clydebarrow"]
|
||||
DOMAIN = "mipi_rgb"
|
321
esphome/components/mipi_rgb/display.py
Normal file
321
esphome/components/mipi_rgb/display.py
Normal file
@@ -0,0 +1,321 @@
|
||||
import importlib
|
||||
import pkgutil
|
||||
|
||||
from esphome import pins
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import display, spi
|
||||
from esphome.components.const import (
|
||||
BYTE_ORDER_BIG,
|
||||
BYTE_ORDER_LITTLE,
|
||||
CONF_BYTE_ORDER,
|
||||
CONF_DRAW_ROUNDING,
|
||||
)
|
||||
from esphome.components.display import CONF_SHOW_TEST_CARD
|
||||
from esphome.components.esp32 import const, only_on_variant
|
||||
from esphome.components.mipi import (
|
||||
COLOR_ORDERS,
|
||||
CONF_DE_PIN,
|
||||
CONF_HSYNC_BACK_PORCH,
|
||||
CONF_HSYNC_FRONT_PORCH,
|
||||
CONF_HSYNC_PULSE_WIDTH,
|
||||
CONF_PCLK_PIN,
|
||||
CONF_PIXEL_MODE,
|
||||
CONF_USE_AXIS_FLIPS,
|
||||
CONF_VSYNC_BACK_PORCH,
|
||||
CONF_VSYNC_FRONT_PORCH,
|
||||
CONF_VSYNC_PULSE_WIDTH,
|
||||
MODE_BGR,
|
||||
PIXEL_MODE_16BIT,
|
||||
PIXEL_MODE_18BIT,
|
||||
DriverChip,
|
||||
dimension_schema,
|
||||
map_sequence,
|
||||
power_of_two,
|
||||
requires_buffer,
|
||||
)
|
||||
from esphome.components.rpi_dpi_rgb.display import (
|
||||
CONF_PCLK_FREQUENCY,
|
||||
CONF_PCLK_INVERTED,
|
||||
)
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_BLUE,
|
||||
CONF_COLOR_ORDER,
|
||||
CONF_CS_PIN,
|
||||
CONF_DATA_PINS,
|
||||
CONF_DATA_RATE,
|
||||
CONF_DC_PIN,
|
||||
CONF_DIMENSIONS,
|
||||
CONF_ENABLE_PIN,
|
||||
CONF_GREEN,
|
||||
CONF_HSYNC_PIN,
|
||||
CONF_ID,
|
||||
CONF_IGNORE_STRAPPING_WARNING,
|
||||
CONF_INIT_SEQUENCE,
|
||||
CONF_INVERT_COLORS,
|
||||
CONF_LAMBDA,
|
||||
CONF_MIRROR_X,
|
||||
CONF_MIRROR_Y,
|
||||
CONF_MODEL,
|
||||
CONF_NUMBER,
|
||||
CONF_RED,
|
||||
CONF_RESET_PIN,
|
||||
CONF_ROTATION,
|
||||
CONF_SPI_ID,
|
||||
CONF_SWAP_XY,
|
||||
CONF_TRANSFORM,
|
||||
CONF_VSYNC_PIN,
|
||||
CONF_WIDTH,
|
||||
)
|
||||
from esphome.final_validate import full_config
|
||||
|
||||
from ..spi import CONF_SPI_MODE, SPI_DATA_RATE_SCHEMA, SPI_MODE_OPTIONS, SPIComponent
|
||||
from . import models
|
||||
|
||||
DEPENDENCIES = ["esp32", "psram"]
|
||||
|
||||
mipi_rgb_ns = cg.esphome_ns.namespace("mipi_rgb")
|
||||
mipi_rgb = mipi_rgb_ns.class_("MipiRgb", display.Display, cg.Component)
|
||||
mipi_rgb_spi = mipi_rgb_ns.class_(
|
||||
"MipiRgbSpi", mipi_rgb, display.Display, cg.Component, spi.SPIDevice
|
||||
)
|
||||
ColorOrder = display.display_ns.enum("ColorMode")
|
||||
|
||||
DATA_PIN_SCHEMA = pins.internal_gpio_output_pin_schema
|
||||
|
||||
DriverChip("CUSTOM")
|
||||
|
||||
# Import all models dynamically from the models package
|
||||
|
||||
for module_info in pkgutil.iter_modules(models.__path__):
|
||||
importlib.import_module(f".models.{module_info.name}", package=__package__)
|
||||
|
||||
MODELS = DriverChip.get_models()
|
||||
|
||||
|
||||
def data_pin_validate(value):
|
||||
"""
|
||||
It is safe to use strapping pins as RGB output data bits, as they are outputs only,
|
||||
and not initialised until after boot.
|
||||
"""
|
||||
if not isinstance(value, dict):
|
||||
try:
|
||||
return DATA_PIN_SCHEMA(
|
||||
{CONF_NUMBER: value, CONF_IGNORE_STRAPPING_WARNING: True}
|
||||
)
|
||||
except cv.Invalid:
|
||||
pass
|
||||
return DATA_PIN_SCHEMA(value)
|
||||
|
||||
|
||||
def data_pin_set(length):
|
||||
return cv.All(
|
||||
[data_pin_validate],
|
||||
cv.Length(min=length, max=length, msg=f"Exactly {length} data pins required"),
|
||||
)
|
||||
|
||||
|
||||
def model_schema(config):
|
||||
model = MODELS[config[CONF_MODEL].upper()]
|
||||
if transforms := model.transforms:
|
||||
transform = cv.Schema({cv.Required(x): cv.boolean for x in transforms})
|
||||
for x in (CONF_SWAP_XY, CONF_MIRROR_X, CONF_MIRROR_Y):
|
||||
if x not in transforms:
|
||||
transform = transform.extend(
|
||||
{cv.Optional(x): cv.invalid(f"{x} not supported by this model")}
|
||||
)
|
||||
else:
|
||||
transform = cv.invalid("This model does not support transforms")
|
||||
|
||||
# RPI model does not use an init sequence, indicates with empty list
|
||||
if model.initsequence is None:
|
||||
# Custom model requires an init sequence
|
||||
iseqconf = cv.Required(CONF_INIT_SEQUENCE)
|
||||
uses_spi = True
|
||||
else:
|
||||
iseqconf = cv.Optional(CONF_INIT_SEQUENCE)
|
||||
uses_spi = CONF_INIT_SEQUENCE in config or len(model.initsequence) != 0
|
||||
swap_xy = config.get(CONF_TRANSFORM, {}).get(CONF_SWAP_XY, False)
|
||||
|
||||
# Dimensions are optional if the model has a default width and the swap_xy transform is not overridden
|
||||
cv_dimensions = (
|
||||
cv.Optional if model.get_default(CONF_WIDTH) and not swap_xy else cv.Required
|
||||
)
|
||||
pixel_modes = (PIXEL_MODE_16BIT, PIXEL_MODE_18BIT, "16", "18")
|
||||
schema = display.FULL_DISPLAY_SCHEMA.extend(
|
||||
{
|
||||
model.option(CONF_RESET_PIN, cv.UNDEFINED): pins.gpio_output_pin_schema,
|
||||
cv.GenerateID(): cv.declare_id(mipi_rgb_spi if uses_spi else mipi_rgb),
|
||||
cv_dimensions(CONF_DIMENSIONS): dimension_schema(
|
||||
model.get_default(CONF_DRAW_ROUNDING, 1)
|
||||
),
|
||||
model.option(CONF_ENABLE_PIN, cv.UNDEFINED): cv.ensure_list(
|
||||
pins.gpio_output_pin_schema
|
||||
),
|
||||
model.option(CONF_COLOR_ORDER, MODE_BGR): cv.enum(COLOR_ORDERS, upper=True),
|
||||
model.option(CONF_DRAW_ROUNDING, 2): power_of_two,
|
||||
model.option(CONF_PIXEL_MODE, PIXEL_MODE_16BIT): cv.one_of(
|
||||
*pixel_modes, lower=True
|
||||
),
|
||||
model.option(CONF_TRANSFORM, cv.UNDEFINED): transform,
|
||||
cv.Required(CONF_MODEL): cv.one_of(model.name, upper=True),
|
||||
model.option(CONF_INVERT_COLORS, False): cv.boolean,
|
||||
model.option(CONF_USE_AXIS_FLIPS, True): cv.boolean,
|
||||
model.option(CONF_PCLK_FREQUENCY, "40MHz"): cv.All(
|
||||
cv.frequency, cv.Range(min=4e6, max=100e6)
|
||||
),
|
||||
model.option(CONF_PCLK_INVERTED, True): cv.boolean,
|
||||
iseqconf: cv.ensure_list(map_sequence),
|
||||
model.option(CONF_BYTE_ORDER, BYTE_ORDER_BIG): cv.one_of(
|
||||
BYTE_ORDER_LITTLE, BYTE_ORDER_BIG, lower=True
|
||||
),
|
||||
model.option(CONF_HSYNC_PULSE_WIDTH): cv.int_,
|
||||
model.option(CONF_HSYNC_BACK_PORCH): cv.int_,
|
||||
model.option(CONF_HSYNC_FRONT_PORCH): cv.int_,
|
||||
model.option(CONF_VSYNC_PULSE_WIDTH): cv.int_,
|
||||
model.option(CONF_VSYNC_BACK_PORCH): cv.int_,
|
||||
model.option(CONF_VSYNC_FRONT_PORCH): cv.int_,
|
||||
model.option(CONF_DATA_PINS): cv.Any(
|
||||
data_pin_set(16),
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_RED): data_pin_set(5),
|
||||
cv.Required(CONF_GREEN): data_pin_set(6),
|
||||
cv.Required(CONF_BLUE): data_pin_set(5),
|
||||
}
|
||||
),
|
||||
),
|
||||
model.option(
|
||||
CONF_DE_PIN, cv.UNDEFINED
|
||||
): pins.internal_gpio_output_pin_schema,
|
||||
model.option(CONF_PCLK_PIN): pins.internal_gpio_output_pin_schema,
|
||||
model.option(CONF_HSYNC_PIN): pins.internal_gpio_output_pin_schema,
|
||||
model.option(CONF_VSYNC_PIN): pins.internal_gpio_output_pin_schema,
|
||||
model.option(CONF_RESET_PIN, cv.UNDEFINED): pins.gpio_output_pin_schema,
|
||||
}
|
||||
)
|
||||
if uses_spi:
|
||||
schema = schema.extend(
|
||||
{
|
||||
cv.GenerateID(CONF_SPI_ID): cv.use_id(SPIComponent),
|
||||
model.option(CONF_DC_PIN, cv.UNDEFINED): pins.gpio_output_pin_schema,
|
||||
model.option(CONF_DATA_RATE, "1MHz"): SPI_DATA_RATE_SCHEMA,
|
||||
model.option(CONF_SPI_MODE, "MODE0"): cv.enum(
|
||||
SPI_MODE_OPTIONS, upper=True
|
||||
),
|
||||
model.option(CONF_CS_PIN, cv.UNDEFINED): pins.gpio_output_pin_schema,
|
||||
}
|
||||
)
|
||||
return schema
|
||||
|
||||
|
||||
def _config_schema(config):
|
||||
config = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_MODEL): cv.one_of(*MODELS, upper=True),
|
||||
},
|
||||
extra=cv.ALLOW_EXTRA,
|
||||
)(config)
|
||||
schema = model_schema(config)
|
||||
return cv.All(
|
||||
schema,
|
||||
only_on_variant(supported=[const.VARIANT_ESP32S3]),
|
||||
cv.only_with_esp_idf,
|
||||
)(config)
|
||||
|
||||
|
||||
CONFIG_SCHEMA = _config_schema
|
||||
|
||||
|
||||
def _final_validate(config):
|
||||
global_config = full_config.get()
|
||||
|
||||
from esphome.components.lvgl import DOMAIN as LVGL_DOMAIN
|
||||
|
||||
if not requires_buffer(config) and LVGL_DOMAIN not in global_config:
|
||||
# If no drawing methods are configured, and LVGL is not enabled, show a test card
|
||||
config[CONF_SHOW_TEST_CARD] = True
|
||||
if CONF_SPI_ID in config:
|
||||
config = spi.final_validate_device_schema(
|
||||
"mipi_rgb", require_miso=False, require_mosi=True
|
||||
)(config)
|
||||
return config
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = _final_validate
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
model = MODELS[config[CONF_MODEL].upper()]
|
||||
width, height, _offset_width, _offset_height = model.get_dimensions(config)
|
||||
var = cg.new_Pvariable(config[CONF_ID], width, height)
|
||||
cg.add(var.set_model(model.name))
|
||||
if enable_pin := config.get(CONF_ENABLE_PIN):
|
||||
enable = [await cg.gpio_pin_expression(pin) for pin in enable_pin]
|
||||
cg.add(var.set_enable_pins(enable))
|
||||
|
||||
if CONF_SPI_ID in config:
|
||||
await spi.register_spi_device(var, config)
|
||||
sequence, madctl = model.get_sequence(config)
|
||||
cg.add(var.set_init_sequence(sequence))
|
||||
cg.add(var.set_madctl(madctl))
|
||||
|
||||
cg.add(var.set_color_mode(COLOR_ORDERS[config[CONF_COLOR_ORDER]]))
|
||||
cg.add(var.set_invert_colors(config[CONF_INVERT_COLORS]))
|
||||
cg.add(var.set_hsync_pulse_width(config[CONF_HSYNC_PULSE_WIDTH]))
|
||||
cg.add(var.set_hsync_back_porch(config[CONF_HSYNC_BACK_PORCH]))
|
||||
cg.add(var.set_hsync_front_porch(config[CONF_HSYNC_FRONT_PORCH]))
|
||||
cg.add(var.set_vsync_pulse_width(config[CONF_VSYNC_PULSE_WIDTH]))
|
||||
cg.add(var.set_vsync_back_porch(config[CONF_VSYNC_BACK_PORCH]))
|
||||
cg.add(var.set_vsync_front_porch(config[CONF_VSYNC_FRONT_PORCH]))
|
||||
cg.add(var.set_pclk_inverted(config[CONF_PCLK_INVERTED]))
|
||||
cg.add(var.set_pclk_frequency(config[CONF_PCLK_FREQUENCY]))
|
||||
index = 0
|
||||
dpins = []
|
||||
if CONF_RED in config[CONF_DATA_PINS]:
|
||||
red_pins = config[CONF_DATA_PINS][CONF_RED]
|
||||
green_pins = config[CONF_DATA_PINS][CONF_GREEN]
|
||||
blue_pins = config[CONF_DATA_PINS][CONF_BLUE]
|
||||
if config[CONF_COLOR_ORDER] == "BGR":
|
||||
dpins.extend(red_pins)
|
||||
dpins.extend(green_pins)
|
||||
dpins.extend(blue_pins)
|
||||
else:
|
||||
dpins.extend(blue_pins)
|
||||
dpins.extend(green_pins)
|
||||
dpins.extend(red_pins)
|
||||
# swap bytes to match big-endian format
|
||||
dpins = dpins[8:16] + dpins[0:8]
|
||||
else:
|
||||
dpins = config[CONF_DATA_PINS]
|
||||
for index, pin in enumerate(dpins):
|
||||
data_pin = await cg.gpio_pin_expression(pin)
|
||||
cg.add(var.add_data_pin(data_pin, index))
|
||||
|
||||
if dc_pin := config.get(CONF_DC_PIN):
|
||||
dc = await cg.gpio_pin_expression(dc_pin)
|
||||
cg.add(var.set_dc_pin(dc))
|
||||
|
||||
if reset_pin := config.get(CONF_RESET_PIN):
|
||||
reset = await cg.gpio_pin_expression(reset_pin)
|
||||
cg.add(var.set_reset_pin(reset))
|
||||
|
||||
if model.rotation_as_transform(config):
|
||||
config[CONF_ROTATION] = 0
|
||||
|
||||
if de_pin := config.get(CONF_DE_PIN):
|
||||
pin = await cg.gpio_pin_expression(de_pin)
|
||||
cg.add(var.set_de_pin(pin))
|
||||
pin = await cg.gpio_pin_expression(config[CONF_PCLK_PIN])
|
||||
cg.add(var.set_pclk_pin(pin))
|
||||
pin = await cg.gpio_pin_expression(config[CONF_HSYNC_PIN])
|
||||
cg.add(var.set_hsync_pin(pin))
|
||||
pin = await cg.gpio_pin_expression(config[CONF_VSYNC_PIN])
|
||||
cg.add(var.set_vsync_pin(pin))
|
||||
|
||||
await display.register_display(var, config)
|
||||
if lamb := config.get(CONF_LAMBDA):
|
||||
lambda_ = await cg.process_lambda(
|
||||
lamb, [(display.DisplayRef, "it")], return_type=cg.void
|
||||
)
|
||||
cg.add(var.set_writer(lambda_))
|
388
esphome/components/mipi_rgb/mipi_rgb.cpp
Normal file
388
esphome/components/mipi_rgb/mipi_rgb.cpp
Normal file
@@ -0,0 +1,388 @@
|
||||
#ifdef USE_ESP32_VARIANT_ESP32S3
|
||||
#include "mipi_rgb.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esp_lcd_panel_rgb.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace mipi_rgb {
|
||||
|
||||
static const uint8_t DELAY_FLAG = 0xFF;
|
||||
static constexpr uint8_t MADCTL_MY = 0x80; // Bit 7 Bottom to top
|
||||
static constexpr uint8_t MADCTL_MX = 0x40; // Bit 6 Right to left
|
||||
static constexpr uint8_t MADCTL_MV = 0x20; // Bit 5 Swap axes
|
||||
static constexpr uint8_t MADCTL_ML = 0x10; // Bit 4 Refresh bottom to top
|
||||
static constexpr uint8_t MADCTL_BGR = 0x08; // Bit 3 Blue-Green-Red pixel order
|
||||
static constexpr uint8_t MADCTL_XFLIP = 0x02; // Mirror the display horizontally
|
||||
static constexpr uint8_t MADCTL_YFLIP = 0x01; // Mirror the display vertically
|
||||
|
||||
void MipiRgb::setup_enables_() {
|
||||
if (!this->enable_pins_.empty()) {
|
||||
for (auto *pin : this->enable_pins_) {
|
||||
pin->setup();
|
||||
pin->digital_write(true);
|
||||
}
|
||||
delay(10);
|
||||
}
|
||||
if (this->reset_pin_ != nullptr) {
|
||||
this->reset_pin_->setup();
|
||||
this->reset_pin_->digital_write(true);
|
||||
delay(5);
|
||||
this->reset_pin_->digital_write(false);
|
||||
delay(5);
|
||||
this->reset_pin_->digital_write(true);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef USE_SPI
|
||||
void MipiRgbSpi::setup() {
|
||||
this->setup_enables_();
|
||||
this->spi_setup();
|
||||
this->write_init_sequence_();
|
||||
this->common_setup_();
|
||||
}
|
||||
void MipiRgbSpi::write_command_(uint8_t value) {
|
||||
this->enable();
|
||||
if (this->dc_pin_ == nullptr) {
|
||||
this->write(value, 9);
|
||||
} else {
|
||||
this->dc_pin_->digital_write(false);
|
||||
this->write_byte(value);
|
||||
this->dc_pin_->digital_write(true);
|
||||
}
|
||||
this->disable();
|
||||
}
|
||||
|
||||
void MipiRgbSpi::write_data_(uint8_t value) {
|
||||
this->enable();
|
||||
if (this->dc_pin_ == nullptr) {
|
||||
this->write(value | 0x100, 9);
|
||||
} else {
|
||||
this->dc_pin_->digital_write(true);
|
||||
this->write_byte(value);
|
||||
}
|
||||
this->disable();
|
||||
}
|
||||
|
||||
/**
|
||||
* this relies upon the init sequence being well-formed, which is guaranteed by the Python init code.
|
||||
*/
|
||||
|
||||
void MipiRgbSpi::write_init_sequence_() {
|
||||
size_t index = 0;
|
||||
auto &vec = this->init_sequence_;
|
||||
while (index != vec.size()) {
|
||||
if (vec.size() - index < 2) {
|
||||
this->mark_failed("Malformed init sequence");
|
||||
return;
|
||||
}
|
||||
uint8_t cmd = vec[index++];
|
||||
uint8_t x = vec[index++];
|
||||
if (x == DELAY_FLAG) {
|
||||
ESP_LOGD(TAG, "Delay %dms", cmd);
|
||||
delay(cmd);
|
||||
} else {
|
||||
uint8_t num_args = x & 0x7F;
|
||||
if (vec.size() - index < num_args) {
|
||||
this->mark_failed("Malformed init sequence");
|
||||
return;
|
||||
}
|
||||
if (cmd == SLEEP_OUT) {
|
||||
delay(120); // NOLINT
|
||||
}
|
||||
const auto *ptr = vec.data() + index;
|
||||
ESP_LOGD(TAG, "Write command %02X, length %d, byte(s) %s", cmd, num_args,
|
||||
format_hex_pretty(ptr, num_args, '.', false).c_str());
|
||||
index += num_args;
|
||||
this->write_command_(cmd);
|
||||
while (num_args-- != 0)
|
||||
this->write_data_(*ptr++);
|
||||
if (cmd == SLEEP_OUT)
|
||||
delay(10);
|
||||
}
|
||||
}
|
||||
// this->spi_teardown(); // SPI not needed after this
|
||||
this->init_sequence_.clear();
|
||||
delay(10);
|
||||
}
|
||||
|
||||
void MipiRgbSpi::dump_config() {
|
||||
MipiRgb::dump_config();
|
||||
LOG_PIN(" CS Pin: ", this->cs_);
|
||||
LOG_PIN(" DC Pin: ", this->dc_pin_);
|
||||
ESP_LOGCONFIG(TAG,
|
||||
" SPI Data rate: %uMHz"
|
||||
"\n Mirror X: %s"
|
||||
"\n Mirror Y: %s"
|
||||
"\n Swap X/Y: %s"
|
||||
"\n Color Order: %s",
|
||||
(unsigned) (this->data_rate_ / 1000000), YESNO(this->madctl_ & (MADCTL_XFLIP | MADCTL_MX)),
|
||||
YESNO(this->madctl_ & (MADCTL_YFLIP | MADCTL_MY | MADCTL_ML)), YESNO(this->madctl_ & MADCTL_MV),
|
||||
this->madctl_ & MADCTL_BGR ? "BGR" : "RGB");
|
||||
}
|
||||
|
||||
#endif // USE_SPI
|
||||
|
||||
void MipiRgb::setup() {
|
||||
this->setup_enables_();
|
||||
this->common_setup_();
|
||||
}
|
||||
|
||||
void MipiRgb::common_setup_() {
|
||||
esp_lcd_rgb_panel_config_t config{};
|
||||
config.flags.fb_in_psram = 1;
|
||||
config.bounce_buffer_size_px = this->width_ * 10;
|
||||
config.num_fbs = 1;
|
||||
config.timings.h_res = this->width_;
|
||||
config.timings.v_res = this->height_;
|
||||
config.timings.hsync_pulse_width = this->hsync_pulse_width_;
|
||||
config.timings.hsync_back_porch = this->hsync_back_porch_;
|
||||
config.timings.hsync_front_porch = this->hsync_front_porch_;
|
||||
config.timings.vsync_pulse_width = this->vsync_pulse_width_;
|
||||
config.timings.vsync_back_porch = this->vsync_back_porch_;
|
||||
config.timings.vsync_front_porch = this->vsync_front_porch_;
|
||||
config.timings.flags.pclk_active_neg = this->pclk_inverted_;
|
||||
config.timings.pclk_hz = this->pclk_frequency_;
|
||||
config.clk_src = LCD_CLK_SRC_PLL160M;
|
||||
size_t data_pin_count = sizeof(this->data_pins_) / sizeof(this->data_pins_[0]);
|
||||
for (size_t i = 0; i != data_pin_count; i++) {
|
||||
config.data_gpio_nums[i] = this->data_pins_[i]->get_pin();
|
||||
}
|
||||
config.data_width = data_pin_count;
|
||||
config.disp_gpio_num = -1;
|
||||
config.hsync_gpio_num = this->hsync_pin_->get_pin();
|
||||
config.vsync_gpio_num = this->vsync_pin_->get_pin();
|
||||
if (this->de_pin_) {
|
||||
config.de_gpio_num = this->de_pin_->get_pin();
|
||||
} else {
|
||||
config.de_gpio_num = -1;
|
||||
}
|
||||
config.pclk_gpio_num = this->pclk_pin_->get_pin();
|
||||
esp_err_t err = esp_lcd_new_rgb_panel(&config, &this->handle_);
|
||||
if (err == ESP_OK)
|
||||
err = esp_lcd_panel_reset(this->handle_);
|
||||
if (err == ESP_OK)
|
||||
err = esp_lcd_panel_init(this->handle_);
|
||||
if (err != ESP_OK) {
|
||||
auto msg = str_sprintf("lcd setup failed: %s", esp_err_to_name(err));
|
||||
this->mark_failed(msg.c_str());
|
||||
}
|
||||
ESP_LOGCONFIG(TAG, "MipiRgb setup complete");
|
||||
}
|
||||
|
||||
void MipiRgb::loop() {
|
||||
if (this->handle_ != nullptr)
|
||||
esp_lcd_rgb_panel_restart(this->handle_);
|
||||
}
|
||||
|
||||
void MipiRgb::update() {
|
||||
if (this->is_failed())
|
||||
return;
|
||||
if (this->auto_clear_enabled_) {
|
||||
this->clear();
|
||||
}
|
||||
if (this->show_test_card_) {
|
||||
this->test_card();
|
||||
} else if (this->page_ != nullptr) {
|
||||
this->page_->get_writer()(*this);
|
||||
} else if (this->writer_.has_value()) {
|
||||
(*this->writer_)(*this);
|
||||
} else {
|
||||
this->stop_poller();
|
||||
}
|
||||
if (this->buffer_ == nullptr || this->x_low_ > this->x_high_ || this->y_low_ > this->y_high_)
|
||||
return;
|
||||
ESP_LOGV(TAG, "x_low %d, y_low %d, x_high %d, y_high %d", this->x_low_, this->y_low_, this->x_high_, this->y_high_);
|
||||
int w = this->x_high_ - this->x_low_ + 1;
|
||||
int h = this->y_high_ - this->y_low_ + 1;
|
||||
this->write_to_display_(this->x_low_, this->y_low_, w, h, reinterpret_cast<const uint8_t *>(this->buffer_),
|
||||
this->x_low_, this->y_low_, this->width_ - w - this->x_low_);
|
||||
// invalidate watermarks
|
||||
this->x_low_ = this->width_;
|
||||
this->y_low_ = this->height_;
|
||||
this->x_high_ = 0;
|
||||
this->y_high_ = 0;
|
||||
}
|
||||
|
||||
void MipiRgb::draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorOrder order,
|
||||
display::ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) {
|
||||
if (w <= 0 || h <= 0 || this->is_failed())
|
||||
return;
|
||||
// if color mapping is required, pass the buck.
|
||||
// note that endianness is not considered here - it is assumed to match!
|
||||
if (bitness != display::COLOR_BITNESS_565) {
|
||||
Display::draw_pixels_at(x_start, y_start, w, h, ptr, order, bitness, big_endian, x_offset, y_offset, x_pad);
|
||||
this->write_to_display_(x_start, y_start, w, h, reinterpret_cast<const uint8_t *>(this->buffer_), x_start, y_start,
|
||||
this->width_ - w - x_start);
|
||||
} else {
|
||||
this->write_to_display_(x_start, y_start, w, h, ptr, x_offset, y_offset, x_pad);
|
||||
}
|
||||
}
|
||||
|
||||
void MipiRgb::write_to_display_(int x_start, int y_start, int w, int h, const uint8_t *ptr, int x_offset, int y_offset,
|
||||
int x_pad) {
|
||||
esp_err_t err = ESP_OK;
|
||||
auto stride = (x_offset + w + x_pad) * 2;
|
||||
ptr += y_offset * stride + x_offset * 2; // skip to the first pixel
|
||||
// x_ and y_offset are offsets into the source buffer, unrelated to our own offsets into the display.
|
||||
if (x_offset == 0 && x_pad == 0) {
|
||||
err = esp_lcd_panel_draw_bitmap(this->handle_, x_start, y_start, x_start + w, y_start + h, ptr);
|
||||
} else {
|
||||
// draw line by line
|
||||
for (int y = 0; y != h; y++) {
|
||||
err = esp_lcd_panel_draw_bitmap(this->handle_, x_start, y + y_start, x_start + w, y + y_start + 1, ptr);
|
||||
if (err != ESP_OK)
|
||||
break;
|
||||
ptr += stride; // next line
|
||||
}
|
||||
}
|
||||
if (err != ESP_OK)
|
||||
ESP_LOGE(TAG, "lcd_lcd_panel_draw_bitmap failed: %s", esp_err_to_name(err));
|
||||
}
|
||||
|
||||
bool MipiRgb::check_buffer_() {
|
||||
if (this->is_failed())
|
||||
return false;
|
||||
if (this->buffer_ != nullptr)
|
||||
return true;
|
||||
// this is dependent on the enum values.
|
||||
RAMAllocator<uint16_t> allocator;
|
||||
this->buffer_ = allocator.allocate(this->height_ * this->width_);
|
||||
if (this->buffer_ == nullptr) {
|
||||
this->mark_failed("Could not allocate buffer for display!");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void MipiRgb::draw_pixel_at(int x, int y, Color color) {
|
||||
if (!this->get_clipping().inside(x, y) || this->is_failed())
|
||||
return;
|
||||
|
||||
switch (this->rotation_) {
|
||||
case display::DISPLAY_ROTATION_0_DEGREES:
|
||||
break;
|
||||
case display::DISPLAY_ROTATION_90_DEGREES:
|
||||
std::swap(x, y);
|
||||
x = this->width_ - x - 1;
|
||||
break;
|
||||
case display::DISPLAY_ROTATION_180_DEGREES:
|
||||
x = this->width_ - x - 1;
|
||||
y = this->height_ - y - 1;
|
||||
break;
|
||||
case display::DISPLAY_ROTATION_270_DEGREES:
|
||||
std::swap(x, y);
|
||||
y = this->height_ - y - 1;
|
||||
break;
|
||||
}
|
||||
if (x >= this->get_width_internal() || x < 0 || y >= this->get_height_internal() || y < 0) {
|
||||
return;
|
||||
}
|
||||
if (!this->check_buffer_())
|
||||
return;
|
||||
size_t pos = (y * this->width_) + x;
|
||||
uint8_t hi_byte = static_cast<uint8_t>(color.r & 0xF8) | (color.g >> 5);
|
||||
uint8_t lo_byte = static_cast<uint8_t>((color.g & 0x1C) << 3) | (color.b >> 3);
|
||||
uint16_t new_color = hi_byte | (lo_byte << 8); // big endian
|
||||
if (this->buffer_[pos] == new_color)
|
||||
return;
|
||||
this->buffer_[pos] = new_color;
|
||||
// low and high watermark may speed up drawing from buffer
|
||||
if (x < this->x_low_)
|
||||
this->x_low_ = x;
|
||||
if (y < this->y_low_)
|
||||
this->y_low_ = y;
|
||||
if (x > this->x_high_)
|
||||
this->x_high_ = x;
|
||||
if (y > this->y_high_)
|
||||
this->y_high_ = y;
|
||||
}
|
||||
void MipiRgb::fill(Color color) {
|
||||
if (!this->check_buffer_())
|
||||
return;
|
||||
auto *ptr_16 = reinterpret_cast<uint16_t *>(this->buffer_);
|
||||
uint8_t hi_byte = static_cast<uint8_t>(color.r & 0xF8) | (color.g >> 5);
|
||||
uint8_t lo_byte = static_cast<uint8_t>((color.g & 0x1C) << 3) | (color.b >> 3);
|
||||
uint16_t new_color = lo_byte | (hi_byte << 8); // little endian
|
||||
std::fill_n(ptr_16, this->width_ * this->height_, new_color);
|
||||
}
|
||||
|
||||
int MipiRgb::get_width() {
|
||||
switch (this->rotation_) {
|
||||
case display::DISPLAY_ROTATION_90_DEGREES:
|
||||
case display::DISPLAY_ROTATION_270_DEGREES:
|
||||
return this->get_height_internal();
|
||||
case display::DISPLAY_ROTATION_0_DEGREES:
|
||||
case display::DISPLAY_ROTATION_180_DEGREES:
|
||||
default:
|
||||
return this->get_width_internal();
|
||||
}
|
||||
}
|
||||
|
||||
int MipiRgb::get_height() {
|
||||
switch (this->rotation_) {
|
||||
case display::DISPLAY_ROTATION_0_DEGREES:
|
||||
case display::DISPLAY_ROTATION_180_DEGREES:
|
||||
return this->get_height_internal();
|
||||
case display::DISPLAY_ROTATION_90_DEGREES:
|
||||
case display::DISPLAY_ROTATION_270_DEGREES:
|
||||
default:
|
||||
return this->get_width_internal();
|
||||
}
|
||||
}
|
||||
|
||||
static std::string get_pin_name(GPIOPin *pin) {
|
||||
if (pin == nullptr)
|
||||
return "None";
|
||||
return pin->dump_summary();
|
||||
}
|
||||
|
||||
void MipiRgb::dump_pins_(uint8_t start, uint8_t end, const char *name, uint8_t offset) {
|
||||
for (uint8_t i = start; i != end; i++) {
|
||||
ESP_LOGCONFIG(TAG, " %s pin %d: %s", name, offset++, this->data_pins_[i]->dump_summary().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void MipiRgb::dump_config() {
|
||||
ESP_LOGCONFIG(TAG,
|
||||
"MIPI_RGB LCD"
|
||||
"\n Model: %s"
|
||||
"\n Width: %u"
|
||||
"\n Height: %u"
|
||||
"\n Rotation: %d degrees"
|
||||
"\n HSync Pulse Width: %u"
|
||||
"\n HSync Back Porch: %u"
|
||||
"\n HSync Front Porch: %u"
|
||||
"\n VSync Pulse Width: %u"
|
||||
"\n VSync Back Porch: %u"
|
||||
"\n VSync Front Porch: %u"
|
||||
"\n Invert Colors: %s"
|
||||
"\n Pixel Clock: %dMHz"
|
||||
"\n Reset Pin: %s"
|
||||
"\n DE Pin: %s"
|
||||
"\n PCLK Pin: %s"
|
||||
"\n HSYNC Pin: %s"
|
||||
"\n VSYNC Pin: %s",
|
||||
this->model_, this->width_, this->height_, this->rotation_, this->hsync_pulse_width_,
|
||||
this->hsync_back_porch_, this->hsync_front_porch_, this->vsync_pulse_width_, this->vsync_back_porch_,
|
||||
this->vsync_front_porch_, YESNO(this->invert_colors_), this->pclk_frequency_ / 1000000,
|
||||
get_pin_name(this->reset_pin_).c_str(), get_pin_name(this->de_pin_).c_str(),
|
||||
get_pin_name(this->pclk_pin_).c_str(), get_pin_name(this->hsync_pin_).c_str(),
|
||||
get_pin_name(this->vsync_pin_).c_str());
|
||||
|
||||
if (this->madctl_ & MADCTL_BGR) {
|
||||
this->dump_pins_(8, 13, "Blue", 0);
|
||||
this->dump_pins_(13, 16, "Green", 0);
|
||||
this->dump_pins_(0, 3, "Green", 3);
|
||||
this->dump_pins_(3, 8, "Red", 0);
|
||||
} else {
|
||||
this->dump_pins_(8, 13, "Red", 0);
|
||||
this->dump_pins_(13, 16, "Green", 0);
|
||||
this->dump_pins_(0, 3, "Green", 3);
|
||||
this->dump_pins_(3, 8, "Blue", 0);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mipi_rgb
|
||||
} // namespace esphome
|
||||
#endif // USE_ESP32_VARIANT_ESP32S3
|
127
esphome/components/mipi_rgb/mipi_rgb.h
Normal file
127
esphome/components/mipi_rgb/mipi_rgb.h
Normal file
@@ -0,0 +1,127 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_ESP32_VARIANT_ESP32S3
|
||||
#include "esphome/core/gpio.h"
|
||||
#include "esphome/components/display/display.h"
|
||||
#include "esp_lcd_panel_ops.h"
|
||||
#ifdef USE_SPI
|
||||
#include "esphome/components/spi/spi.h"
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace mipi_rgb {
|
||||
|
||||
constexpr static const char *const TAG = "display.mipi_rgb";
|
||||
const uint8_t SW_RESET_CMD = 0x01;
|
||||
const uint8_t SLEEP_OUT = 0x11;
|
||||
const uint8_t SDIR_CMD = 0xC7;
|
||||
const uint8_t MADCTL_CMD = 0x36;
|
||||
const uint8_t INVERT_OFF = 0x20;
|
||||
const uint8_t INVERT_ON = 0x21;
|
||||
const uint8_t DISPLAY_ON = 0x29;
|
||||
const uint8_t CMD2_BKSEL = 0xFF;
|
||||
const uint8_t CMD2_BK0[5] = {0x77, 0x01, 0x00, 0x00, 0x10};
|
||||
|
||||
class MipiRgb : public display::Display {
|
||||
public:
|
||||
MipiRgb(int width, int height) : width_(width), height_(height) {}
|
||||
void setup() override;
|
||||
void loop() override;
|
||||
void update() override;
|
||||
void fill(Color color);
|
||||
void draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorOrder order,
|
||||
display::ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) override;
|
||||
void write_to_display_(int x_start, int y_start, int w, int h, const uint8_t *ptr, int x_offset, int y_offset,
|
||||
int x_pad);
|
||||
bool check_buffer_();
|
||||
|
||||
display::ColorOrder get_color_mode() { return this->color_mode_; }
|
||||
void set_color_mode(display::ColorOrder color_mode) { this->color_mode_ = color_mode; }
|
||||
void set_invert_colors(bool invert_colors) { this->invert_colors_ = invert_colors; }
|
||||
void set_madctl(uint8_t madctl) { this->madctl_ = madctl; }
|
||||
|
||||
void add_data_pin(InternalGPIOPin *data_pin, size_t index) { this->data_pins_[index] = data_pin; };
|
||||
void set_de_pin(InternalGPIOPin *de_pin) { this->de_pin_ = de_pin; }
|
||||
void set_pclk_pin(InternalGPIOPin *pclk_pin) { this->pclk_pin_ = pclk_pin; }
|
||||
void set_vsync_pin(InternalGPIOPin *vsync_pin) { this->vsync_pin_ = vsync_pin; }
|
||||
void set_hsync_pin(InternalGPIOPin *hsync_pin) { this->hsync_pin_ = hsync_pin; }
|
||||
void set_reset_pin(GPIOPin *reset_pin) { this->reset_pin_ = reset_pin; }
|
||||
void set_width(uint16_t width) { this->width_ = width; }
|
||||
void set_pclk_frequency(uint32_t pclk_frequency) { this->pclk_frequency_ = pclk_frequency; }
|
||||
void set_pclk_inverted(bool inverted) { this->pclk_inverted_ = inverted; }
|
||||
void set_model(const char *model) { this->model_ = model; }
|
||||
int get_width() override;
|
||||
int get_height() override;
|
||||
void set_hsync_back_porch(uint16_t hsync_back_porch) { this->hsync_back_porch_ = hsync_back_porch; }
|
||||
void set_hsync_front_porch(uint16_t hsync_front_porch) { this->hsync_front_porch_ = hsync_front_porch; }
|
||||
void set_hsync_pulse_width(uint16_t hsync_pulse_width) { this->hsync_pulse_width_ = hsync_pulse_width; }
|
||||
void set_vsync_pulse_width(uint16_t vsync_pulse_width) { this->vsync_pulse_width_ = vsync_pulse_width; }
|
||||
void set_vsync_back_porch(uint16_t vsync_back_porch) { this->vsync_back_porch_ = vsync_back_porch; }
|
||||
void set_vsync_front_porch(uint16_t vsync_front_porch) { this->vsync_front_porch_ = vsync_front_porch; }
|
||||
void set_enable_pins(std::vector<GPIOPin *> enable_pins) { this->enable_pins_ = std::move(enable_pins); }
|
||||
display::DisplayType get_display_type() override { return display::DisplayType::DISPLAY_TYPE_COLOR; }
|
||||
int get_width_internal() override { return this->width_; }
|
||||
int get_height_internal() override { return this->height_; }
|
||||
void dump_pins_(uint8_t start, uint8_t end, const char *name, uint8_t offset);
|
||||
void dump_config() override;
|
||||
void draw_pixel_at(int x, int y, Color color) override;
|
||||
|
||||
// this will be horribly slow.
|
||||
protected:
|
||||
void setup_enables_();
|
||||
void common_setup_();
|
||||
InternalGPIOPin *de_pin_{nullptr};
|
||||
InternalGPIOPin *pclk_pin_{nullptr};
|
||||
InternalGPIOPin *hsync_pin_{nullptr};
|
||||
InternalGPIOPin *vsync_pin_{nullptr};
|
||||
GPIOPin *reset_pin_{nullptr};
|
||||
InternalGPIOPin *data_pins_[16] = {};
|
||||
uint16_t hsync_pulse_width_ = 10;
|
||||
uint16_t hsync_back_porch_ = 10;
|
||||
uint16_t hsync_front_porch_ = 20;
|
||||
uint16_t vsync_pulse_width_ = 10;
|
||||
uint16_t vsync_back_porch_ = 10;
|
||||
uint16_t vsync_front_porch_ = 10;
|
||||
uint32_t pclk_frequency_ = 16 * 1000 * 1000;
|
||||
bool pclk_inverted_{true};
|
||||
uint8_t madctl_{};
|
||||
const char *model_{"Unknown"};
|
||||
bool invert_colors_{};
|
||||
display::ColorOrder color_mode_{display::COLOR_ORDER_BGR};
|
||||
size_t width_;
|
||||
size_t height_;
|
||||
uint16_t *buffer_{nullptr};
|
||||
std::vector<GPIOPin *> enable_pins_{};
|
||||
uint16_t x_low_{1};
|
||||
uint16_t y_low_{1};
|
||||
uint16_t x_high_{0};
|
||||
uint16_t y_high_{0};
|
||||
|
||||
esp_lcd_panel_handle_t handle_{};
|
||||
};
|
||||
|
||||
#ifdef USE_SPI
|
||||
class MipiRgbSpi : public MipiRgb,
|
||||
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW, spi::CLOCK_PHASE_LEADING,
|
||||
spi::DATA_RATE_1MHZ> {
|
||||
public:
|
||||
MipiRgbSpi(int width, int height) : MipiRgb(width, height) {}
|
||||
|
||||
void set_init_sequence(const std::vector<uint8_t> &init_sequence) { this->init_sequence_ = init_sequence; }
|
||||
void set_dc_pin(GPIOPin *dc_pin) { this->dc_pin_ = dc_pin; }
|
||||
void setup() override;
|
||||
|
||||
protected:
|
||||
void write_command_(uint8_t value);
|
||||
void write_data_(uint8_t value);
|
||||
void write_init_sequence_();
|
||||
void dump_config();
|
||||
|
||||
GPIOPin *dc_pin_{nullptr};
|
||||
std::vector<uint8_t> init_sequence_;
|
||||
};
|
||||
#endif
|
||||
|
||||
} // namespace mipi_rgb
|
||||
} // namespace esphome
|
||||
#endif
|
24
esphome/components/mipi_rgb/models/guition.py
Normal file
24
esphome/components/mipi_rgb/models/guition.py
Normal file
@@ -0,0 +1,24 @@
|
||||
from .st7701s import st7701s
|
||||
|
||||
st7701s.extend(
|
||||
"GUITION-4848S040",
|
||||
width=480,
|
||||
height=480,
|
||||
data_rate="2MHz",
|
||||
cs_pin=39,
|
||||
de_pin=18,
|
||||
hsync_pin=16,
|
||||
vsync_pin=17,
|
||||
pclk_pin=21,
|
||||
pclk_frequency="12MHz",
|
||||
pixel_mode="18bit",
|
||||
mirror_x=True,
|
||||
mirror_y=True,
|
||||
data_pins={
|
||||
"red": [11, 12, 13, 14, 0],
|
||||
"green": [8, 20, 3, 46, 9, 10],
|
||||
"blue": [4, 5, 6, 7, 15],
|
||||
},
|
||||
# Additional configuration for Guition 4848S040, 16 bit bus config
|
||||
add_init_sequence=((0xCD, 0x00),),
|
||||
)
|
@@ -0,0 +1,228 @@
|
||||
from esphome.config_validation import UNDEFINED
|
||||
|
||||
from .st7701s import ST7701S
|
||||
|
||||
# fmt: off
|
||||
ST7701S(
|
||||
"T-PANEL-S3",
|
||||
width=480,
|
||||
height=480,
|
||||
color_order="BGR",
|
||||
invert_colors=False,
|
||||
swap_xy=UNDEFINED,
|
||||
spi_mode="MODE3",
|
||||
cs_pin={"xl9535": None, "number": 17},
|
||||
reset_pin={"xl9535": None, "number": 5},
|
||||
hsync_pin=39,
|
||||
vsync_pin=40,
|
||||
pclk_pin=41,
|
||||
data_pins={
|
||||
"red": [12, 13, 42, 46, 45],
|
||||
"green": [6, 7, 8, 9, 10, 11],
|
||||
"blue": [1, 2, 3, 4, 5],
|
||||
},
|
||||
hsync_front_porch=20,
|
||||
hsync_back_porch=0,
|
||||
hsync_pulse_width=2,
|
||||
vsync_front_porch=30,
|
||||
vsync_back_porch=1,
|
||||
vsync_pulse_width=8,
|
||||
pclk_frequency="6MHz",
|
||||
pclk_inverted=False,
|
||||
initsequence=(
|
||||
(0xFF, 0x77, 0x01, 0x00, 0x00, 0x13), (0xEF, 0x08), (0xFF, 0x77, 0x01, 0x00, 0x00, 0x10),
|
||||
(0xC0, 0x3B, 0x00), (0xC1, 0x0B, 0x02), (0xC2, 0x30, 0x02, 0x37), (0xCC, 0x10),
|
||||
(0xB0, 0x00, 0x0F, 0x16, 0x0E, 0x11, 0x07, 0x09, 0x09, 0x08, 0x23, 0x05, 0x11, 0x0F, 0x28, 0x2D, 0x18),
|
||||
(0xB1, 0x00, 0x0F, 0x16, 0x0E, 0x11, 0x07, 0x09, 0x08, 0x09, 0x23, 0x05, 0x11, 0x0F, 0x28, 0x2D, 0x18),
|
||||
(0xFF, 0x77, 0x01, 0x00, 0x00, 0x11),
|
||||
(0xB0, 0x4D), (0xB1, 0x33), (0xB2, 0x87), (0xB5, 0x4B), (0xB7, 0x8C), (0xB8, 0x20), (0xC1, 0x78),
|
||||
(0xC2, 0x78), (0xD0, 0x88), (0xE0, 0x00, 0x00, 0x02),
|
||||
(0xE1, 0x02, 0xF0, 0x00, 0x00, 0x03, 0xF0, 0x00, 0x00, 0x00, 0x44, 0x44),
|
||||
(0xE2, 0x10, 0x10, 0x40, 0x40, 0xF2, 0xF0, 0x00, 0x00, 0xF2, 0xF0, 0x00, 0x00),
|
||||
(0xE3, 0x00, 0x00, 0x11, 0x11), (0xE4, 0x44, 0x44),
|
||||
(0xE5, 0x07, 0xEF, 0xF0, 0xF0, 0x09, 0xF1, 0xF0, 0xF0, 0x03, 0xF3, 0xF0, 0xF0, 0x05, 0xED, 0xF0, 0xF0),
|
||||
(0xE6, 0x00, 0x00, 0x11, 0x11), (0xE7, 0x44, 0x44),
|
||||
(0xE8, 0x08, 0xF0, 0xF0, 0xF0, 0x0A, 0xF2, 0xF0, 0xF0, 0x04, 0xF4, 0xF0, 0xF0, 0x06, 0xEE, 0xF0, 0xF0),
|
||||
(0xEB, 0x00, 0x00, 0xE4, 0xE4, 0x44, 0x88, 0x40),
|
||||
(0xEC, 0x78, 0x00),
|
||||
(0xED, 0x20, 0xF9, 0x87, 0x76, 0x65, 0x54, 0x4F, 0xFF, 0xFF, 0xF4, 0x45, 0x56, 0x67, 0x78, 0x9F, 0x02),
|
||||
(0xEF, 0x10, 0x0D, 0x04, 0x08, 0x3F, 0x1F),
|
||||
(0xFF, 0x77, 0x01, 0x00, 0x00, 0x10),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
t_rgb = ST7701S(
|
||||
"T-RGB-2.1",
|
||||
width=480,
|
||||
height=480,
|
||||
color_order="BGR",
|
||||
pixel_mode="18bit",
|
||||
invert_colors=False,
|
||||
swap_xy=UNDEFINED,
|
||||
spi_mode="MODE3",
|
||||
cs_pin={"xl9535": None, "number": 3},
|
||||
de_pin=45,
|
||||
hsync_pin=47,
|
||||
vsync_pin=41,
|
||||
pclk_pin=42,
|
||||
data_pins={
|
||||
"red": [7, 6, 5, 3, 2],
|
||||
"green": [14, 13, 12, 11, 10, 9],
|
||||
"blue": [21, 18, 17, 16, 15],
|
||||
},
|
||||
hsync_front_porch=50,
|
||||
hsync_pulse_width=1,
|
||||
hsync_back_porch=30,
|
||||
vsync_front_porch=20,
|
||||
vsync_pulse_width=1,
|
||||
vsync_back_porch=30,
|
||||
pclk_frequency="12MHz",
|
||||
pclk_inverted=False,
|
||||
initsequence=(
|
||||
(0xFF, 0x77, 0x01, 0x00, 0x00, 0x10),
|
||||
|
||||
(0xC0, 0x3B, 0x00),
|
||||
(0xC1, 0x0B, 0x02),
|
||||
(0xC2, 0x07, 0x02),
|
||||
(0xCC, 0x10),
|
||||
(0xCD, 0x08),
|
||||
|
||||
(0xB0,
|
||||
0x00, 0x11, 0x16, 0x0e,
|
||||
0x11, 0x06, 0x05, 0x09,
|
||||
0x08, 0x21, 0x06, 0x13,
|
||||
0x10, 0x29, 0x31, 0x18),
|
||||
|
||||
(0xB1,
|
||||
0x00, 0x11, 0x16, 0x0e,
|
||||
0x11, 0x07, 0x05, 0x09,
|
||||
0x09, 0x21, 0x05, 0x13,
|
||||
0x11, 0x2a, 0x31, 0x18),
|
||||
|
||||
(0xFF, 0x77, 0x01, 0x00, 0x00, 0x11),
|
||||
|
||||
(0xB0, 0x6D),
|
||||
(0xB1, 0x37),
|
||||
(0xB2, 0x81),
|
||||
(0xB3, 0x80),
|
||||
(0xB5, 0x43),
|
||||
(0xB7, 0x85),
|
||||
(0xB8, 0x20),
|
||||
|
||||
(0xC1, 0x78),
|
||||
(0xC2, 0x78),
|
||||
(0xC3, 0x8C),
|
||||
|
||||
(0xD0, 0x88),
|
||||
|
||||
(0xE0, 0x00, 0x00, 0x02),
|
||||
(0xE1,
|
||||
0x03, 0xA0, 0x00, 0x00,
|
||||
0x04, 0xA0, 0x00, 0x00,
|
||||
0x00, 0x20, 0x20),
|
||||
|
||||
(0xE2,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00),
|
||||
|
||||
(0xE3, 0x00, 0x00, 0x11, 0x00),
|
||||
(0xE4, 0x22, 0x00),
|
||||
|
||||
(0xE5,
|
||||
0x05, 0xEC, 0xA0, 0xA0,
|
||||
0x07, 0xEE, 0xA0, 0xA0,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00),
|
||||
|
||||
(0xE6, 0x00, 0x00, 0x11, 0x00),
|
||||
(0xE7, 0x22, 0x00),
|
||||
|
||||
(0xE8,
|
||||
0x06, 0xED, 0xA0, 0xA0,
|
||||
0x08, 0xEF, 0xA0, 0xA0,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00),
|
||||
|
||||
(0xEB, 0x00, 0x00, 0x40, 0x40, 0x00, 0x00, 0x10),
|
||||
|
||||
(0xED,
|
||||
0xFF, 0xFF, 0xFF, 0xBA,
|
||||
0x0A, 0xBF, 0x45, 0xFF,
|
||||
0xFF, 0x54, 0xFB, 0xA0,
|
||||
0xAB, 0xFF, 0xFF, 0xFF),
|
||||
|
||||
(0xEF, 0x10, 0x0D, 0x04, 0x08, 0x3F, 0x1F),
|
||||
|
||||
(0xFF, 0x77, 0x01, 0x00, 0x00, 0x13),
|
||||
(0xEF, 0x08),
|
||||
(0xFF, 0x77, 0x01, 0x00, 0x00, 0x10)
|
||||
)
|
||||
|
||||
)
|
||||
t_rgb.extend(
|
||||
"T-RGB-2.8",
|
||||
initsequence=(
|
||||
(0xFF, 0x77, 0x01, 0x00, 0x00, 0x13),
|
||||
(0xEF, 0x08),
|
||||
(0xFF, 0x77, 0x01, 0x00, 0x00, 0x10),
|
||||
(0xC0, 0x3B, 0x00),
|
||||
(0xC1, 0x10, 0x0C),
|
||||
(0xC2, 0x07, 0x0A),
|
||||
(0xC7, 0x00),
|
||||
(0xC7, 0x10),
|
||||
(0xCD, 0x08),
|
||||
(0xB0,
|
||||
0x05, 0x12, 0x98, 0x0e, 0x0F,
|
||||
0x07, 0x07, 0x09, 0x09, 0x23,
|
||||
0x05, 0x52, 0x0F, 0x67, 0x2C, 0x11),
|
||||
(0xB1,
|
||||
0x0B, 0x11, 0x97, 0x0C, 0x12,
|
||||
0x06, 0x06, 0x08, 0x08, 0x22,
|
||||
0x03, 0x51, 0x11, 0x66, 0x2B, 0x0F),
|
||||
(0xFF, 0x77, 0x01, 0x00, 0x00, 0x11),
|
||||
(0xB0, 0x5D),
|
||||
(0xB1, 0x2D),
|
||||
(0xB2, 0x81),
|
||||
(0xB3, 0x80),
|
||||
(0xB5, 0x4E),
|
||||
(0xB7, 0x85),
|
||||
(0xB8, 0x20),
|
||||
(0xC1, 0x78),
|
||||
(0xC2, 0x78),
|
||||
(0xD0, 0x88),
|
||||
(0xE0, 0x00, 0x00, 0x02),
|
||||
(0xE1,
|
||||
0x06, 0x30, 0x08, 0x30, 0x05,
|
||||
0x30, 0x07, 0x30, 0x00, 0x33,
|
||||
0x33),
|
||||
(0xE2,
|
||||
0x11, 0x11, 0x33, 0x33, 0xf4,
|
||||
0x00, 0x00, 0x00, 0xf4, 0x00,
|
||||
0x00, 0x00),
|
||||
(0xE3, 0x00, 0x00, 0x11, 0x11),
|
||||
(0xE4, 0x44, 0x44),
|
||||
(0xE5,
|
||||
0x0d, 0xf5, 0x30, 0xf0, 0x0f,
|
||||
0xf7, 0x30, 0xf0, 0x09, 0xf1,
|
||||
0x30, 0xf0, 0x0b, 0xf3, 0x30, 0xf0),
|
||||
(0xE6, 0x00, 0x00, 0x11, 0x11),
|
||||
(0xE7, 0x44, 0x44),
|
||||
(0xE8,
|
||||
0x0c, 0xf4, 0x30, 0xf0,
|
||||
0x0e, 0xf6, 0x30, 0xf0,
|
||||
0x08, 0xf0, 0x30, 0xf0,
|
||||
0x0a, 0xf2, 0x30, 0xf0),
|
||||
(0xe9, 0x36),
|
||||
(0xEB, 0x00, 0x01, 0xe4, 0xe4, 0x44, 0x88, 0x40),
|
||||
(0xED,
|
||||
0xff, 0x10, 0xaf, 0x76,
|
||||
0x54, 0x2b, 0xcf, 0xff,
|
||||
0xff, 0xfc, 0xb2, 0x45,
|
||||
0x67, 0xfa, 0x01, 0xff),
|
||||
(0xEF, 0x08, 0x08, 0x08, 0x45, 0x3f, 0x54),
|
||||
(0xFF, 0x77, 0x01, 0x00, 0x00, 0x10),
|
||||
)
|
||||
)
|
||||
|
9
esphome/components/mipi_rgb/models/rpi.py
Normal file
9
esphome/components/mipi_rgb/models/rpi.py
Normal file
@@ -0,0 +1,9 @@
|
||||
from esphome.components.mipi import DriverChip
|
||||
from esphome.config_validation import UNDEFINED
|
||||
|
||||
# A driver chip for Raspberry Pi MIPI RGB displays. These require no init sequence
|
||||
DriverChip(
|
||||
"RPI",
|
||||
swap_xy=UNDEFINED,
|
||||
initsequence=(),
|
||||
)
|
214
esphome/components/mipi_rgb/models/st7701s.py
Normal file
214
esphome/components/mipi_rgb/models/st7701s.py
Normal file
@@ -0,0 +1,214 @@
|
||||
from esphome.components.mipi import (
|
||||
MADCTL,
|
||||
MADCTL_ML,
|
||||
MADCTL_XFLIP,
|
||||
MODE_BGR,
|
||||
DriverChip,
|
||||
)
|
||||
from esphome.config_validation import UNDEFINED
|
||||
from esphome.const import CONF_COLOR_ORDER, CONF_HEIGHT, CONF_MIRROR_X, CONF_MIRROR_Y
|
||||
|
||||
SDIR_CMD = 0xC7
|
||||
|
||||
|
||||
class ST7701S(DriverChip):
|
||||
# The ST7701s does not use the standard MADCTL bits for x/y mirroring
|
||||
def add_madctl(self, sequence: list, config: dict):
|
||||
transform = self.get_transform(config)
|
||||
madctl = 0x00
|
||||
if config[CONF_COLOR_ORDER] == MODE_BGR:
|
||||
madctl |= 0x08
|
||||
if transform.get(CONF_MIRROR_Y):
|
||||
madctl |= MADCTL_ML
|
||||
sequence.append((MADCTL, madctl))
|
||||
sdir = 0
|
||||
if transform.get(CONF_MIRROR_X):
|
||||
sdir |= 0x04
|
||||
madctl |= MADCTL_XFLIP
|
||||
sequence.append((SDIR_CMD, sdir))
|
||||
return madctl
|
||||
|
||||
@property
|
||||
def transforms(self) -> set[str]:
|
||||
"""
|
||||
The ST7701 never supports axis swapping, and mirroring the y-axis only works for full height.
|
||||
"""
|
||||
if self.get_default(CONF_HEIGHT) != 864:
|
||||
return {CONF_MIRROR_X}
|
||||
return {CONF_MIRROR_X, CONF_MIRROR_Y}
|
||||
|
||||
|
||||
# fmt: off
|
||||
st7701s = ST7701S(
|
||||
"ST7701S",
|
||||
width=480,
|
||||
height=864,
|
||||
swap_xy=UNDEFINED,
|
||||
hsync_front_porch=20,
|
||||
hsync_back_porch=10,
|
||||
hsync_pulse_width=10,
|
||||
vsync_front_porch=10,
|
||||
vsync_back_porch=10,
|
||||
vsync_pulse_width=10,
|
||||
pclk_frequency="16MHz",
|
||||
pclk_inverted=True,
|
||||
initsequence=(
|
||||
(0xFF, 0x77, 0x01, 0x00, 0x00, 0x10), # Page 0
|
||||
(0xC0, 0x3B, 0x00), (0xC1, 0x0D, 0x02), (0xC2, 0x31, 0x05),
|
||||
(0xB0, 0x00, 0x11, 0x18, 0x0E, 0x11, 0x06, 0x07, 0x08, 0x07, 0x22, 0x04, 0x12, 0x0F, 0xAA, 0x31, 0x18,),
|
||||
(0xB1, 0x00, 0x11, 0x19, 0x0E, 0x12, 0x07, 0x08, 0x08, 0x08, 0x22, 0x04, 0x11, 0x11, 0xA9, 0x32, 0x18,),
|
||||
(0xFF, 0x77, 0x01, 0x00, 0x00, 0x11), # page 1
|
||||
(0xB0, 0x60), (0xB1, 0x32), (0xB2, 0x07), (0xB3, 0x80), (0xB5, 0x49), (0xB7, 0x85), (0xB8, 0x21), (0xC1, 0x78),
|
||||
(0xC2, 0x78), (0xE0, 0x00, 0x1B, 0x02),
|
||||
(0xE1, 0x08, 0xA0, 0x00, 0x00, 0x07, 0xA0, 0x00, 0x00, 0x00, 0x44, 0x44),
|
||||
(0xE2, 0x11, 0x11, 0x44, 0x44, 0xED, 0xA0, 0x00, 0x00, 0xEC, 0xA0, 0x00, 0x00),
|
||||
(0xE3, 0x00, 0x00, 0x11, 0x11),
|
||||
(0xE4, 0x44, 0x44),
|
||||
(0xE5, 0x0A, 0xE9, 0xD8, 0xA0, 0x0C, 0xEB, 0xD8, 0xA0, 0x0E, 0xED, 0xD8, 0xA0, 0x10, 0xEF, 0xD8, 0xA0,),
|
||||
(0xE6, 0x00, 0x00, 0x11, 0x11), (0xE7, 0x44, 0x44),
|
||||
(0xE8, 0x09, 0xE8, 0xD8, 0xA0, 0x0B, 0xEA, 0xD8, 0xA0, 0x0D, 0xEC, 0xD8, 0xA0, 0x0F, 0xEE, 0xD8, 0xA0,),
|
||||
(0xEB, 0x02, 0x00, 0xE4, 0xE4, 0x88, 0x00, 0x40), (0xEC, 0x3C, 0x00),
|
||||
(0xED, 0xAB, 0x89, 0x76, 0x54, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x20, 0x45, 0x67, 0x98, 0xBA,),
|
||||
(0xFF, 0x77, 0x01, 0x00, 0x00, 0x13), # Page 3
|
||||
(0xE5, 0xE4),
|
||||
(0xFF, 0x77, 0x01, 0x00, 0x00, 0x10), # Page 0
|
||||
(0xCD, 0x08),
|
||||
)
|
||||
)
|
||||
|
||||
st7701s.extend(
|
||||
"MAKERFABS-4",
|
||||
width=480,
|
||||
height=480,
|
||||
color_order="RGB",
|
||||
invert_colors=True,
|
||||
pixel_mode="18bit",
|
||||
cs_pin=1,
|
||||
de_pin={
|
||||
"number": 45,
|
||||
"ignore_strapping_warning": True
|
||||
},
|
||||
hsync_pin=5,
|
||||
vsync_pin=4,
|
||||
pclk_pin=21,
|
||||
data_pins={
|
||||
"red": [39, 40, 41, 42, 2],
|
||||
"green": [0, 9, 14, 47, 48, 3],
|
||||
"blue": [6, 7, 15, 16, 8]
|
||||
}
|
||||
)
|
||||
|
||||
st7701s.extend(
|
||||
"SEEED-INDICATOR-D1",
|
||||
width=480,
|
||||
height=480,
|
||||
mirror_x=True,
|
||||
mirror_y=True,
|
||||
invert_colors=True,
|
||||
pixel_mode="18bit",
|
||||
spi_mode="MODE3",
|
||||
data_rate="2MHz",
|
||||
hsync_front_porch=10,
|
||||
hsync_pulse_width=8,
|
||||
hsync_back_porch=50,
|
||||
vsync_front_porch=10,
|
||||
vsync_pulse_width=8,
|
||||
vsync_back_porch=20,
|
||||
cs_pin={"pca9554": None, "number": 4},
|
||||
de_pin=18,
|
||||
hsync_pin=16,
|
||||
vsync_pin=17,
|
||||
pclk_pin=21,
|
||||
pclk_inverted=False,
|
||||
data_pins={
|
||||
"red": [4, 3, 2, 1, 0],
|
||||
"green": [10, 9, 8, 7, 6, 5],
|
||||
"blue": [15, 14, 13, 12, 11]
|
||||
},
|
||||
)
|
||||
|
||||
st7701s.extend(
|
||||
"UEDX48480021-MD80ET",
|
||||
width=480,
|
||||
height=480,
|
||||
pixel_mode="18bit",
|
||||
cs_pin=18,
|
||||
reset_pin=8,
|
||||
de_pin=17,
|
||||
vsync_pin={"number": 3, "ignore_strapping_warning": True},
|
||||
hsync_pin={"number": 46, "ignore_strapping_warning": True},
|
||||
pclk_pin=9,
|
||||
data_pins={
|
||||
"red": [40, 41, 42, 2, 1],
|
||||
"green": [21, 47, 48, 45, 38, 39],
|
||||
"blue": [10, 11, {"number": 12, "allow_other_uses": True}, {"number": 13, "allow_other_uses": True}, 14]
|
||||
},
|
||||
initsequence=(
|
||||
(0xFF, 0x77, 0x01, 0x00, 0x00, 0x13), (0xEF, 0x08), (0xFF, 0x77, 0x01, 0x00, 0x00, 0x10),
|
||||
(0xC0, 0x3B, 0x00), (0xC1, 0x0B, 0x02), (0xC2, 0x07, 0x02), (0xC7, 0x00), (0xCC, 0x10), (0xCD, 0x08),
|
||||
(0xB0, 0x00, 0x11, 0x16, 0x0E, 0x11, 0x06, 0x05, 0x09, 0x08, 0x21, 0x06, 0x13, 0x10, 0x29, 0x31, 0x18),
|
||||
(0xB1, 0x00, 0x11, 0x16, 0x0E, 0x11, 0x07, 0x05, 0x09, 0x09, 0x21, 0x05, 0x13, 0x11, 0x2A, 0x31, 0x18),
|
||||
(0xFF, 0x77, 0x01, 0x00, 0x00, 0x11),
|
||||
(0xB0, 0x6D), (0xB1, 0x37), (0xB2, 0x8B), (0xB3, 0x80), (0xB5, 0x43), (0xB7, 0x85),
|
||||
(0xB8, 0x20), (0xC0, 0x09), (0xC1, 0x78), (0xC2, 0x78), (0xD0, 0x88),
|
||||
(0xE0, 0x00, 0x00, 0x02),
|
||||
(0xE1, 0x03, 0xA0, 0x00, 0x00, 0x04, 0xA0, 0x00, 0x00, 0x00, 0x20, 0x20),
|
||||
(0xE2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00),
|
||||
(0xE3, 0x00, 0x00, 0x11, 0x00),
|
||||
(0xE4, 0x22, 0x00),
|
||||
(0xE5, 0x05, 0xEC, 0xF6, 0xCA, 0x07, 0xEE, 0xF6, 0xCA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00),
|
||||
(0xE6, 0x00, 0x00, 0x11, 0x00),
|
||||
(0xE7, 0x22, 0x00),
|
||||
(0xE8, 0x06, 0xED, 0xF6, 0xCA, 0x08, 0xEF, 0xF6, 0xCA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00),
|
||||
(0xE9, 0x36, 0x00),
|
||||
(0xEB, 0x00, 0x00, 0x40, 0x40, 0x00, 0x00, 0x00),
|
||||
(0xED, 0xFF, 0xFF, 0xFF, 0xBA, 0x0A, 0xFF, 0x45, 0xFF, 0xFF, 0x54, 0xFF, 0xA0, 0xAB, 0xFF, 0xFF, 0xFF),
|
||||
(0xEF, 0x08, 0x08, 0x08, 0x45, 0x3F, 0x54),
|
||||
(0xFF, 0x77, 0x01, 0x00, 0x00, 0x13), (0xE8, 0x00, 0x0E), (0xFF, 0x77, 0x01, 0x00, 0x00, 0x00),
|
||||
(0x11, 0x00), (0xFF, 0x77, 0x01, 0x00, 0x00, 0x13), (0xE8, 0x00, 0x0C),
|
||||
(0xE8, 0x00, 0x00), (0xFF, 0x77, 0x01, 0x00, 0x00, 0x00)
|
||||
)
|
||||
)
|
||||
|
||||
st7701s.extend(
|
||||
"ZX2D10GE01R-V4848",
|
||||
width=480,
|
||||
height=480,
|
||||
pixel_mode="18bit",
|
||||
cs_pin=21,
|
||||
de_pin=39,
|
||||
vsync_pin=48,
|
||||
hsync_pin=40,
|
||||
pclk_pin={"number": 45, "ignore_strapping_warning": True},
|
||||
pclk_frequency="15MHz",
|
||||
pclk_inverted=True,
|
||||
hsync_pulse_width=10,
|
||||
hsync_back_porch=10,
|
||||
hsync_front_porch=10,
|
||||
vsync_pulse_width=2,
|
||||
vsync_back_porch=12,
|
||||
vsync_front_porch=14,
|
||||
data_pins={
|
||||
"red": [10, 16, 9, 15, 46],
|
||||
"green": [8, 13, 18, 12, 11, 17],
|
||||
"blue": [{"number": 47, "allow_other_uses": True}, {"number": 41, "allow_other_uses": True}, 0, 42, 14]
|
||||
},
|
||||
initsequence=(
|
||||
(0xFF, 0x77, 0x01, 0x00, 0x00, 0x13), (0xEF, 0x08), (0xFF, 0x77, 0x01, 0x00, 0x00, 0x10), (0xC0, 0x3B, 0x00),
|
||||
(0xC1, 0x0B, 0x02), (0xC2, 0x07, 0x02), (0xCC, 0x10), (0xCD, 0x08),
|
||||
(0xB0, 0x00, 0x11, 0x16, 0x0e, 0x11, 0x06, 0x05, 0x09, 0x08, 0x21, 0x06, 0x13, 0x10, 0x29, 0x31, 0x18),
|
||||
(0xB1, 0x00, 0x11, 0x16, 0x0e, 0x11, 0x07, 0x05, 0x09, 0x09, 0x21, 0x05, 0x13, 0x11, 0x2a, 0x31, 0x18),
|
||||
(0xFF, 0x77, 0x01, 0x00, 0x00, 0x11), (0xB0, 0x6d), (0xB1, 0x37), (0xB2, 0x81), (0xB3, 0x80), (0xB5, 0x43),
|
||||
(0xB7, 0x85), (0xB8, 0x20), (0xC1, 0x78), (0xC2, 0x78), (0xD0, 0x88), (0xE0, 0x00, 0x00, 0x02),
|
||||
(0xE1, 0x03, 0xA0, 0x00, 0x00, 0x04, 0xA0, 0x00, 0x00, 0x00, 0x20, 0x20),
|
||||
(0xE2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00),
|
||||
(0xE3, 0x00, 0x00, 0x11, 0x00), (0xE4, 0x22, 0x00),
|
||||
(0xE5, 0x05, 0xEC, 0xA0, 0xA0, 0x07, 0xEE, 0xA0, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00),
|
||||
(0xE6, 0x00, 0x00, 0x11, 0x00), (0xE7, 0x22, 0x00),
|
||||
(0xE8, 0x06, 0xED, 0xA0, 0xA0, 0x08, 0xEF, 0xA0, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00),
|
||||
(0xEB, 0x00, 0x00, 0x40, 0x40, 0x00, 0x00, 0x00),
|
||||
(0xED, 0xFF, 0xFF, 0xFF, 0xBA, 0x0A, 0xBF, 0x45, 0xFF, 0xFF, 0x54, 0xFB, 0xA0, 0xAB, 0xFF, 0xFF, 0xFF),
|
||||
(0xEF, 0x10, 0x0D, 0x04, 0x08, 0x3F, 0x1F),
|
||||
(0xFF, 0x77, 0x01, 0x00, 0x00, 0x13), (0xEF, 0x08), (0xFF, 0x77, 0x01, 0x00, 0x00, 0x00)
|
||||
)
|
||||
)
|
64
esphome/components/mipi_rgb/models/waveshare.py
Normal file
64
esphome/components/mipi_rgb/models/waveshare.py
Normal file
@@ -0,0 +1,64 @@
|
||||
from esphome.components.mipi import DriverChip
|
||||
from esphome.config_validation import UNDEFINED
|
||||
|
||||
from .st7701s import st7701s
|
||||
|
||||
wave_4_3 = DriverChip(
|
||||
"ESP32-S3-TOUCH-LCD-4.3",
|
||||
swap_xy=UNDEFINED,
|
||||
initsequence=(),
|
||||
width=800,
|
||||
height=480,
|
||||
pclk_frequency="16MHz",
|
||||
reset_pin={"ch422g": None, "number": 3},
|
||||
enable_pin={"ch422g": None, "number": 2},
|
||||
de_pin=5,
|
||||
hsync_pin={"number": 46, "ignore_strapping_warning": True},
|
||||
vsync_pin={"number": 3, "ignore_strapping_warning": True},
|
||||
pclk_pin=7,
|
||||
pclk_inverted=True,
|
||||
hsync_front_porch=210,
|
||||
hsync_pulse_width=30,
|
||||
hsync_back_porch=30,
|
||||
vsync_front_porch=4,
|
||||
vsync_pulse_width=4,
|
||||
vsync_back_porch=4,
|
||||
data_pins={
|
||||
"red": [1, 2, 42, 41, 40],
|
||||
"green": [39, 0, 45, 48, 47, 21],
|
||||
"blue": [14, 38, 18, 17, 10],
|
||||
},
|
||||
)
|
||||
wave_4_3.extend(
|
||||
"ESP32-S3-TOUCH-LCD-7-800X480",
|
||||
enable_pin=[{"ch422g": None, "number": 2}, {"ch422g": None, "number": 6}],
|
||||
hsync_back_porch=8,
|
||||
hsync_front_porch=8,
|
||||
hsync_pulse_width=4,
|
||||
vsync_back_porch=16,
|
||||
vsync_front_porch=16,
|
||||
vsync_pulse_width=4,
|
||||
)
|
||||
|
||||
st7701s.extend(
|
||||
"WAVESHARE-4-480x480",
|
||||
data_rate="2MHz",
|
||||
spi_mode="MODE3",
|
||||
color_order="BGR",
|
||||
pixel_mode="18bit",
|
||||
width=480,
|
||||
height=480,
|
||||
invert_colors=True,
|
||||
cs_pin=42,
|
||||
de_pin=40,
|
||||
hsync_pin=38,
|
||||
vsync_pin=39,
|
||||
pclk_pin=41,
|
||||
pclk_frequency="12MHz",
|
||||
pclk_inverted=False,
|
||||
data_pins={
|
||||
"red": [46, 3, 8, 18, 17],
|
||||
"green": [14, 13, 12, 11, 10, 9],
|
||||
"blue": [5, 45, 48, 47, 21],
|
||||
},
|
||||
)
|
67
tests/components/mipi_rgb/test.esp32-s3-idf.yaml
Normal file
67
tests/components/mipi_rgb/test.esp32-s3-idf.yaml
Normal file
@@ -0,0 +1,67 @@
|
||||
psram:
|
||||
mode: octal
|
||||
|
||||
spi:
|
||||
- clk_pin:
|
||||
number: 47
|
||||
allow_other_uses: true
|
||||
mosi_pin:
|
||||
number: 41
|
||||
allow_other_uses: true
|
||||
|
||||
display:
|
||||
- platform: mipi_rgb
|
||||
model: ZX2D10GE01R-V4848
|
||||
update_interval: 1s
|
||||
color_order: BGR
|
||||
draw_rounding: 2
|
||||
pixel_mode: 18bit
|
||||
invert_colors: false
|
||||
use_axis_flips: true
|
||||
pclk_frequency: 15000000.0
|
||||
pclk_inverted: true
|
||||
byte_order: big_endian
|
||||
hsync_pulse_width: 10
|
||||
hsync_back_porch: 10
|
||||
hsync_front_porch: 10
|
||||
vsync_pulse_width: 2
|
||||
vsync_back_porch: 12
|
||||
vsync_front_porch: 14
|
||||
data_pins:
|
||||
red:
|
||||
- number: 10
|
||||
- number: 16
|
||||
- number: 9
|
||||
- number: 15
|
||||
- number: 46
|
||||
ignore_strapping_warning: true
|
||||
green:
|
||||
- number: 8
|
||||
- number: 13
|
||||
- number: 18
|
||||
- number: 12
|
||||
- number: 11
|
||||
- number: 17
|
||||
blue:
|
||||
- number: 47
|
||||
allow_other_uses: true
|
||||
- number: 41
|
||||
allow_other_uses: true
|
||||
- number: 0
|
||||
ignore_strapping_warning: true
|
||||
- number: 42
|
||||
- number: 14
|
||||
de_pin:
|
||||
number: 39
|
||||
pclk_pin:
|
||||
number: 45
|
||||
ignore_strapping_warning: true
|
||||
hsync_pin:
|
||||
number: 40
|
||||
vsync_pin:
|
||||
number: 48
|
||||
data_rate: 1000000.0
|
||||
spi_mode: MODE0
|
||||
cs_pin:
|
||||
number: 21
|
||||
show_test_card: true
|
Reference in New Issue
Block a user