mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 15:12:06 +00: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/* @dudanov | ||||||
| esphome/components/midea_ir/* @dudanov | esphome/components/midea_ir/* @dudanov | ||||||
| esphome/components/mipi_dsi/* @clydebarrow | esphome/components/mipi_dsi/* @clydebarrow | ||||||
|  | esphome/components/mipi_rgb/* @clydebarrow | ||||||
| esphome/components/mipi_spi/* @clydebarrow | esphome/components/mipi_spi/* @clydebarrow | ||||||
| esphome/components/mitsubishi/* @RubyBailey | esphome/components/mitsubishi/* @RubyBailey | ||||||
| esphome/components/mixer/speaker/* @kahrendt | esphome/components/mixer/speaker/* @kahrendt | ||||||
|   | |||||||
| @@ -222,6 +222,12 @@ def delay(ms): | |||||||
|  |  | ||||||
|  |  | ||||||
| class DriverChip: | 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] = {} |     models: dict[str, Self] = {} | ||||||
|  |  | ||||||
|     def __init__( |     def __init__( | ||||||
| @@ -232,7 +238,7 @@ class DriverChip: | |||||||
|     ): |     ): | ||||||
|         name = name.upper() |         name = name.upper() | ||||||
|         self.name = name |         self.name = name | ||||||
|         self.initsequence = initsequence or defaults.get("init_sequence") |         self.initsequence = initsequence | ||||||
|         self.defaults = defaults |         self.defaults = defaults | ||||||
|         DriverChip.models[name] = self |         DriverChip.models[name] = self | ||||||
|  |  | ||||||
| @@ -246,6 +252,17 @@ class DriverChip: | |||||||
|         return models |         return models | ||||||
|  |  | ||||||
|     def extend(self, name, **kwargs) -> "DriverChip": |     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() |         defaults = self.defaults.copy() | ||||||
|         if ( |         if ( | ||||||
|             CONF_WIDTH in defaults |             CONF_WIDTH in defaults | ||||||
| @@ -260,23 +277,39 @@ class DriverChip: | |||||||
|         ): |         ): | ||||||
|             defaults[CONF_NATIVE_HEIGHT] = defaults[CONF_HEIGHT] |             defaults[CONF_NATIVE_HEIGHT] = defaults[CONF_HEIGHT] | ||||||
|         defaults.update(kwargs) |         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: |     def get_default(self, key, fallback: Any = False) -> Any: | ||||||
|         return self.defaults.get(key, fallback) |         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: |     def option(self, name, fallback=False) -> cv.Optional: | ||||||
|         return cv.Optional(name, default=self.get_default(name, fallback)) |         return cv.Optional(name, default=self.get_default(name, fallback)) | ||||||
|  |  | ||||||
|     def rotation_as_transform(self, config) -> bool: |     def rotation_as_transform(self, config) -> bool: | ||||||
|         """ |         """ | ||||||
|         Check if a rotation can be implemented in hardware using the MADCTL register. |         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) |         rotation = config.get(CONF_ROTATION, 0) | ||||||
|         return rotation and ( |         if rotation == 0 or not transforms: | ||||||
|             self.get_default(CONF_SWAP_XY) != cv.UNDEFINED or rotation == 180 |             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]: |     def get_dimensions(self, config) -> tuple[int, int, int, int]: | ||||||
|         if CONF_DIMENSIONS in config: |         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 |         # if mirroring axes and there are offsets, also mirror the offsets to cater for situations where | ||||||
|         # the offset is asymmetric |         # 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) |             native_width = self.get_default(CONF_NATIVE_WIDTH, width + offset_width * 2) | ||||||
|             offset_width = native_width - width - offset_width |             offset_width = native_width - width - offset_width | ||||||
|         if transform[CONF_MIRROR_Y]: |         if transform.get(CONF_MIRROR_Y): | ||||||
|             native_height = self.get_default( |             native_height = self.get_default( | ||||||
|                 CONF_NATIVE_HEIGHT, height + offset_height * 2 |                 CONF_NATIVE_HEIGHT, height + offset_height * 2 | ||||||
|             ) |             ) | ||||||
| @@ -314,7 +347,7 @@ class DriverChip: | |||||||
|             90, |             90, | ||||||
|             270, |             270, | ||||||
|         ) |         ) | ||||||
|         if transform[CONF_SWAP_XY] is True or rotated: |         if transform.get(CONF_SWAP_XY) is True or rotated: | ||||||
|             width, height = height, width |             width, height = height, width | ||||||
|             offset_height, offset_width = offset_width, offset_height |             offset_height, offset_width = offset_width, offset_height | ||||||
|         return width, height, offset_width, offset_height |         return width, height, offset_width, offset_height | ||||||
| @@ -324,27 +357,50 @@ class DriverChip: | |||||||
|         transform = config.get( |         transform = config.get( | ||||||
|             CONF_TRANSFORM, |             CONF_TRANSFORM, | ||||||
|             { |             { | ||||||
|                 CONF_MIRROR_X: self.get_default(CONF_MIRROR_X, False), |                 CONF_MIRROR_X: self.get_default(CONF_MIRROR_X), | ||||||
|                 CONF_MIRROR_Y: self.get_default(CONF_MIRROR_Y, False), |                 CONF_MIRROR_Y: self.get_default(CONF_MIRROR_Y), | ||||||
|                 CONF_SWAP_XY: self.get_default(CONF_SWAP_XY, False), |                 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? |         # Can we use the MADCTL register to set the rotation? | ||||||
|         if can_transform and CONF_TRANSFORM not in config: |         if can_transform and CONF_TRANSFORM not in config: | ||||||
|             rotation = config[CONF_ROTATION] |             rotation = config[CONF_ROTATION] | ||||||
|             if rotation == 180: |             if rotation == 180: | ||||||
|                 transform[CONF_MIRROR_X] = not transform[CONF_MIRROR_X] |                 transform[CONF_MIRROR_X] = not mirror_x | ||||||
|                 transform[CONF_MIRROR_Y] = not transform[CONF_MIRROR_Y] |                 transform[CONF_MIRROR_Y] = not mirror_y | ||||||
|             elif rotation == 90: |             elif rotation == 90: | ||||||
|                 transform[CONF_SWAP_XY] = not transform[CONF_SWAP_XY] |                 transform[CONF_SWAP_XY] = not swap_xy | ||||||
|                 transform[CONF_MIRROR_X] = not transform[CONF_MIRROR_X] |                 transform[CONF_MIRROR_X] = not mirror_x | ||||||
|             else: |             else: | ||||||
|                 transform[CONF_SWAP_XY] = not transform[CONF_SWAP_XY] |                 transform[CONF_SWAP_XY] = not swap_xy | ||||||
|                 transform[CONF_MIRROR_Y] = not transform[CONF_MIRROR_Y] |                 transform[CONF_MIRROR_Y] = not mirror_y | ||||||
|             transform[CONF_TRANSFORM] = True |             transform[CONF_TRANSFORM] = True | ||||||
|         return transform |         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]: |     def get_sequence(self, config) -> tuple[tuple[int, ...], int]: | ||||||
|         """ |         """ | ||||||
|         Create the init sequence for the display. |         Create the init sequence for the display. | ||||||
| @@ -367,21 +423,9 @@ class DriverChip: | |||||||
|             pixel_mode = PIXEL_MODES[pixel_mode] |             pixel_mode = PIXEL_MODES[pixel_mode] | ||||||
|         sequence.append((PIXFMT, 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): |         if self.rotation_as_transform(config): | ||||||
|             LOGGER.info("Using hardware transform to implement rotation") |             LOGGER.info("Using hardware transform to implement rotation") | ||||||
|         if transform.get(CONF_MIRROR_X): |         madctl = self.add_madctl(sequence, config) | ||||||
|             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)) |  | ||||||
|         if config[CONF_INVERT_COLORS]: |         if config[CONF_INVERT_COLORS]: | ||||||
|             sequence.append((INVON,)) |             sequence.append((INVON,)) | ||||||
|         else: |         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