diff --git a/CODEOWNERS b/CODEOWNERS index e6c149012a..ddd0494a3c 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -282,6 +282,7 @@ esphome/components/microphone/* @jesserockz @kahrendt esphome/components/mics_4514/* @jesserockz esphome/components/midea/* @dudanov esphome/components/midea_ir/* @dudanov +esphome/components/mipi_spi/* @clydebarrow esphome/components/mitsubishi/* @RubyBailey esphome/components/mixer/speaker/* @kahrendt esphome/components/mlx90393/* @functionpointer diff --git a/esphome/components/mipi_spi/__init__.py b/esphome/components/mipi_spi/__init__.py new file mode 100644 index 0000000000..46b0206a1f --- /dev/null +++ b/esphome/components/mipi_spi/__init__.py @@ -0,0 +1,15 @@ +CODEOWNERS = ["@clydebarrow"] + +DOMAIN = "mipi_spi" + +CONF_DRAW_FROM_ORIGIN = "draw_from_origin" +CONF_SPI_16 = "spi_16" +CONF_PIXEL_MODE = "pixel_mode" +CONF_COLOR_DEPTH = "color_depth" +CONF_BUS_MODE = "bus_mode" +CONF_USE_AXIS_FLIPS = "use_axis_flips" +CONF_NATIVE_WIDTH = "native_width" +CONF_NATIVE_HEIGHT = "native_height" + +MODE_RGB = "RGB" +MODE_BGR = "BGR" diff --git a/esphome/components/mipi_spi/display.py b/esphome/components/mipi_spi/display.py new file mode 100644 index 0000000000..e9ed97a2a2 --- /dev/null +++ b/esphome/components/mipi_spi/display.py @@ -0,0 +1,474 @@ +import logging + +from esphome import pins +import esphome.codegen as cg +from esphome.components import display, spi +from esphome.components.spi import TYPE_OCTAL, TYPE_QUAD, TYPE_SINGLE +import esphome.config_validation as cv +from esphome.config_validation import ALLOW_EXTRA +from esphome.const import ( + CONF_BRIGHTNESS, + CONF_COLOR_ORDER, + CONF_CS_PIN, + CONF_DATA_RATE, + CONF_DC_PIN, + CONF_DIMENSIONS, + CONF_ENABLE_PIN, + CONF_HEIGHT, + CONF_ID, + CONF_INIT_SEQUENCE, + CONF_INVERT_COLORS, + CONF_LAMBDA, + CONF_MIRROR_X, + CONF_MIRROR_Y, + CONF_MODEL, + CONF_OFFSET_HEIGHT, + CONF_OFFSET_WIDTH, + CONF_RESET_PIN, + CONF_ROTATION, + CONF_SWAP_XY, + CONF_TRANSFORM, + CONF_WIDTH, +) +from esphome.core import TimePeriod + +from ..const import CONF_DRAW_ROUNDING +from ..lvgl.defines import CONF_COLOR_DEPTH +from . import ( + CONF_BUS_MODE, + CONF_DRAW_FROM_ORIGIN, + CONF_NATIVE_HEIGHT, + CONF_NATIVE_WIDTH, + CONF_PIXEL_MODE, + CONF_SPI_16, + CONF_USE_AXIS_FLIPS, + DOMAIN, + MODE_BGR, + MODE_RGB, +) +from .models import ( + DELAY_FLAG, + MADCTL_BGR, + MADCTL_MV, + MADCTL_MX, + MADCTL_MY, + MADCTL_XFLIP, + MADCTL_YFLIP, + DriverChip, + amoled, + cyd, + ili, + jc, + lanbon, + lilygo, + waveshare, +) +from .models.commands import BRIGHTNESS, DISPON, INVOFF, INVON, MADCTL, PIXFMT, SLPOUT + +DEPENDENCIES = ["spi"] + +LOGGER = logging.getLogger(DOMAIN) +mipi_spi_ns = cg.esphome_ns.namespace("mipi_spi") +MipiSpi = mipi_spi_ns.class_( + "MipiSpi", display.Display, display.DisplayBuffer, cg.Component, spi.SPIDevice +) +ColorOrder = display.display_ns.enum("ColorMode") +ColorBitness = display.display_ns.enum("ColorBitness") +Model = mipi_spi_ns.enum("Model") + +COLOR_ORDERS = { + MODE_RGB: ColorOrder.COLOR_ORDER_RGB, + MODE_BGR: ColorOrder.COLOR_ORDER_BGR, +} + +COLOR_DEPTHS = { + 8: ColorBitness.COLOR_BITNESS_332, + 16: ColorBitness.COLOR_BITNESS_565, +} +DATA_PIN_SCHEMA = pins.internal_gpio_output_pin_schema + + +DriverChip("CUSTOM", initsequence={}) + +MODELS = DriverChip.models +# These statements are noops, but serve to suppress linting of side-effect-only imports +for _ in (ili, jc, amoled, lilygo, lanbon, cyd, waveshare): + pass + +PixelMode = mipi_spi_ns.enum("PixelMode") + +PIXEL_MODE_18BIT = "18bit" +PIXEL_MODE_16BIT = "16bit" + +PIXEL_MODES = { + PIXEL_MODE_16BIT: 0x55, + PIXEL_MODE_18BIT: 0x66, +} + + +def validate_dimension(rounding): + def validator(value): + value = cv.positive_int(value) + if value % rounding != 0: + raise cv.Invalid(f"Dimensions and offsets must be divisible by {rounding}") + return value + + return validator + + +def map_sequence(value): + """ + The format is a repeated sequence of [CMD, ] where is s a sequence of bytes. The length is inferred + from the length of the sequence and should not be explicit. + A delay can be inserted by specifying "- delay N" where N is in ms + """ + if isinstance(value, str) and value.lower().startswith("delay "): + value = value.lower()[6:] + delay = cv.All( + cv.positive_time_period_milliseconds, + cv.Range(TimePeriod(milliseconds=1), TimePeriod(milliseconds=255)), + )(value) + return DELAY_FLAG, delay.total_milliseconds + if isinstance(value, int): + return (value,) + value = cv.All(cv.ensure_list(cv.int_range(0, 255)), cv.Length(1, 254))(value) + return tuple(value) + + +def power_of_two(value): + value = cv.int_range(1, 128)(value) + if value & (value - 1) != 0: + raise cv.Invalid("value must be a power of two") + return value + + +def dimension_schema(rounding): + return cv.Any( + cv.dimensions, + cv.Schema( + { + cv.Required(CONF_WIDTH): validate_dimension(rounding), + cv.Required(CONF_HEIGHT): validate_dimension(rounding), + cv.Optional(CONF_OFFSET_HEIGHT, default=0): validate_dimension( + rounding + ), + cv.Optional(CONF_OFFSET_WIDTH, default=0): validate_dimension(rounding), + } + ), + ) + + +def model_schema(bus_mode, model: DriverChip, swapsies: bool): + transform = cv.Schema( + { + cv.Required(CONF_MIRROR_X): cv.boolean, + cv.Required(CONF_MIRROR_Y): cv.boolean, + } + ) + if model.get_default(CONF_SWAP_XY, False) == cv.UNDEFINED: + transform = transform.extend( + { + cv.Optional(CONF_SWAP_XY): cv.invalid( + "Axis swapping not supported by this model" + ) + } + ) + else: + transform = transform.extend( + { + cv.Required(CONF_SWAP_XY): cv.boolean, + } + ) + # CUSTOM model will need to provide a custom init sequence + iseqconf = ( + cv.Required(CONF_INIT_SEQUENCE) + if model.initsequence is None + else cv.Optional(CONF_INIT_SEQUENCE) + ) + # Dimensions are optional if the model has a default width and the transform is not overridden + cv_dimensions = ( + cv.Optional if model.get_default(CONF_WIDTH) and not swapsies else cv.Required + ) + pixel_modes = PIXEL_MODES if bus_mode == TYPE_SINGLE else (PIXEL_MODE_16BIT,) + color_depth = ( + ("16", "8", "16bit", "8bit") if bus_mode == TYPE_SINGLE else ("16", "16bit") + ) + schema = ( + display.FULL_DISPLAY_SCHEMA.extend( + spi.spi_device_schema( + cs_pin_required=False, + default_mode="MODE3" if bus_mode == TYPE_OCTAL else "MODE0", + default_data_rate=model.get_default(CONF_DATA_RATE, 10_000_000), + mode=bus_mode, + ) + ) + .extend( + { + model.option(pin, cv.UNDEFINED): pins.gpio_output_pin_schema + for pin in (CONF_RESET_PIN, CONF_CS_PIN, CONF_DC_PIN) + } + ) + .extend( + { + cv.GenerateID(): cv.declare_id(MipiSpi), + 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_COLOR_DEPTH, 16): cv.one_of(*color_depth, lower=True), + model.option(CONF_DRAW_ROUNDING, 2): power_of_two, + model.option(CONF_PIXEL_MODE, PIXEL_MODE_16BIT): cv.Any( + cv.one_of(*pixel_modes, lower=True), + cv.int_range(0, 255, min_included=True, max_included=True), + ), + cv.Optional(CONF_TRANSFORM): transform, + cv.Optional(CONF_BUS_MODE, default=bus_mode): cv.one_of( + bus_mode, lower=True + ), + cv.Required(CONF_MODEL): cv.one_of(model.name, upper=True), + iseqconf: cv.ensure_list(map_sequence), + } + ) + .extend( + { + model.option(x): cv.boolean + for x in [ + CONF_DRAW_FROM_ORIGIN, + CONF_SPI_16, + CONF_INVERT_COLORS, + CONF_USE_AXIS_FLIPS, + ] + } + ) + ) + if brightness := model.get_default(CONF_BRIGHTNESS): + schema = schema.extend( + { + cv.Optional(CONF_BRIGHTNESS, default=brightness): cv.int_range( + 0, 0xFF, min_included=True, max_included=True + ), + } + ) + if bus_mode != TYPE_SINGLE: + return cv.All(schema, cv.only_with_esp_idf) + return schema + + +def rotation_as_transform(model, config): + """ + 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. + """ + rotation = config.get(CONF_ROTATION, 0) + return rotation and ( + model.get_default(CONF_SWAP_XY) != cv.UNDEFINED or rotation == 180 + ) + + +def config_schema(config): + # First get the model and bus mode + config = cv.Schema( + { + cv.Required(CONF_MODEL): cv.one_of(*MODELS, upper=True), + }, + extra=ALLOW_EXTRA, + )(config) + model = MODELS[config[CONF_MODEL]] + bus_modes = model.modes + config = cv.Schema( + { + model.option(CONF_BUS_MODE, TYPE_SINGLE): cv.one_of(*bus_modes, lower=True), + cv.Required(CONF_MODEL): cv.one_of(*MODELS, upper=True), + }, + extra=ALLOW_EXTRA, + )(config) + bus_mode = config.get(CONF_BUS_MODE, model.modes[0]) + swapsies = config.get(CONF_TRANSFORM, {}).get(CONF_SWAP_XY) is True + config = model_schema(bus_mode, model, swapsies)(config) + # Check for invalid combinations of MADCTL config + if init_sequence := config.get(CONF_INIT_SEQUENCE): + if MADCTL in [x[0] for x in init_sequence] and CONF_TRANSFORM in config: + raise cv.Invalid( + f"transform is not supported when MADCTL ({MADCTL:#X}) is in the init sequence" + ) + + if bus_mode == TYPE_QUAD and CONF_DC_PIN in config: + raise cv.Invalid("DC pin is not supported in quad mode") + if config[CONF_PIXEL_MODE] == PIXEL_MODE_18BIT and bus_mode != TYPE_SINGLE: + raise cv.Invalid("18-bit pixel mode is not supported on a quad or octal bus") + if bus_mode != TYPE_QUAD and CONF_DC_PIN not in config: + raise cv.Invalid(f"DC pin is required in {bus_mode} mode") + return config + + +CONFIG_SCHEMA = config_schema + + +def get_transform(model, config): + can_transform = rotation_as_transform(model, config) + transform = config.get( + CONF_TRANSFORM, + { + CONF_MIRROR_X: model.get_default(CONF_MIRROR_X, False), + CONF_MIRROR_Y: model.get_default(CONF_MIRROR_Y, False), + CONF_SWAP_XY: model.get_default(CONF_SWAP_XY, False), + }, + ) + + # 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] + elif rotation == 90: + transform[CONF_SWAP_XY] = not transform[CONF_SWAP_XY] + transform[CONF_MIRROR_X] = not transform[CONF_MIRROR_X] + else: + transform[CONF_SWAP_XY] = not transform[CONF_SWAP_XY] + transform[CONF_MIRROR_Y] = not transform[CONF_MIRROR_Y] + transform[CONF_TRANSFORM] = True + return transform + + +def get_sequence(model, config): + """ + Create the init sequence for the display. + Use the default sequence from the model, if any, and append any custom sequence provided in the config. + Append SLPOUT (if not already in the sequence) and DISPON to the end of the sequence + Pixel format, color order, and orientation will be set. + """ + sequence = list(model.initsequence) + custom_sequence = config.get(CONF_INIT_SEQUENCE, []) + sequence.extend(custom_sequence) + # Ensure each command is a tuple + sequence = [x if isinstance(x, tuple) else (x,) for x in sequence] + commands = [x[0] for x in sequence] + # Set pixel format if not already in the custom sequence + if PIXFMT not in commands: + pixel_mode = config[CONF_PIXEL_MODE] + if not isinstance(pixel_mode, int): + 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[CONF_USE_AXIS_FLIPS] + if MADCTL not in commands: + madctl = 0 + transform = get_transform(model, config) + if transform.get(CONF_TRANSFORM): + 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)) + if INVON not in commands and INVOFF not in commands: + if config[CONF_INVERT_COLORS]: + sequence.append((INVON,)) + else: + sequence.append((INVOFF,)) + if BRIGHTNESS not in commands: + if brightness := config.get( + CONF_BRIGHTNESS, model.get_default(CONF_BRIGHTNESS) + ): + sequence.append((BRIGHTNESS, brightness)) + if SLPOUT not in commands: + sequence.append((SLPOUT,)) + sequence.append((DISPON,)) + + # Flatten the sequence into a list of bytes, with the length of each command + # or the delay flag inserted where needed + return sum( + tuple( + (x[1], 0xFF) if x[0] == DELAY_FLAG else (x[0], len(x) - 1) + x[1:] + for x in sequence + ), + (), + ) + + +async def to_code(config): + model = MODELS[config[CONF_MODEL]] + transform = get_transform(model, config) + if CONF_DIMENSIONS in config: + # Explicit dimensions, just use as is + dimensions = config[CONF_DIMENSIONS] + if isinstance(dimensions, dict): + width = dimensions[CONF_WIDTH] + height = dimensions[CONF_HEIGHT] + offset_width = dimensions[CONF_OFFSET_WIDTH] + offset_height = dimensions[CONF_OFFSET_HEIGHT] + else: + (width, height) = dimensions + offset_width = 0 + offset_height = 0 + else: + # Default dimensions, use model defaults and transform if needed + width = model.get_default(CONF_WIDTH) + height = model.get_default(CONF_HEIGHT) + offset_width = model.get_default(CONF_OFFSET_WIDTH, 0) + offset_height = model.get_default(CONF_OFFSET_HEIGHT, 0) + + # 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]: + native_width = model.get_default( + CONF_NATIVE_WIDTH, width + offset_width * 2 + ) + offset_width = native_width - width - offset_width + if transform[CONF_MIRROR_Y]: + native_height = model.get_default( + CONF_NATIVE_HEIGHT, height + offset_height * 2 + ) + offset_height = native_height - height - offset_height + # Swap default dimensions if swap_xy is set + if transform[CONF_SWAP_XY] is True: + width, height = height, width + offset_height, offset_width = offset_width, offset_height + + color_depth = config[CONF_COLOR_DEPTH] + if color_depth.endswith("bit"): + color_depth = color_depth[:-3] + color_depth = COLOR_DEPTHS[int(color_depth)] + + var = cg.new_Pvariable( + config[CONF_ID], width, height, offset_width, offset_height, color_depth + ) + cg.add(var.set_init_sequence(get_sequence(model, config))) + if rotation_as_transform(model, config): + if CONF_TRANSFORM in config: + LOGGER.warning("Use of 'transform' with 'rotation' is not recommended") + else: + config[CONF_ROTATION] = 0 + cg.add(var.set_model(config[CONF_MODEL])) + cg.add(var.set_draw_from_origin(config[CONF_DRAW_FROM_ORIGIN])) + cg.add(var.set_draw_rounding(config[CONF_DRAW_ROUNDING])) + cg.add(var.set_spi_16(config[CONF_SPI_16])) + 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 reset_pin := config.get(CONF_RESET_PIN): + reset = await cg.gpio_pin_expression(reset_pin) + cg.add(var.set_reset_pin(reset)) + + if dc_pin := config.get(CONF_DC_PIN): + dc_pin = await cg.gpio_pin_expression(dc_pin) + cg.add(var.set_dc_pin(dc_pin)) + + 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_)) + await display.register_display(var, config) + await spi.register_spi_device(var, config) diff --git a/esphome/components/mipi_spi/mipi_spi.cpp b/esphome/components/mipi_spi/mipi_spi.cpp new file mode 100644 index 0000000000..2d393ac349 --- /dev/null +++ b/esphome/components/mipi_spi/mipi_spi.cpp @@ -0,0 +1,481 @@ +#include "mipi_spi.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace mipi_spi { + +void MipiSpi::setup() { + ESP_LOGCONFIG(TAG, "Setting up MIPI SPI"); + this->spi_setup(); + if (this->dc_pin_ != nullptr) { + this->dc_pin_->setup(); + this->dc_pin_->digital_write(false); + } + for (auto *pin : this->enable_pins_) { + pin->setup(); + pin->digital_write(true); + } + 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); + } + this->bus_width_ = this->parent_->get_bus_width(); + + // need to know when the display is ready for SLPOUT command - will be 120ms after reset + auto when = millis() + 120; + delay(10); + size_t index = 0; + auto &vec = this->init_sequence_; + while (index != vec.size()) { + if (vec.size() - index < 2) { + ESP_LOGE(TAG, "Malformed init sequence"); + this->mark_failed(); + 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) { + ESP_LOGE(TAG, "Malformed init sequence"); + this->mark_failed(); + return; + } + auto arg_byte = vec[index]; + switch (cmd) { + case SLEEP_OUT: { + // are we ready, boots? + int duration = when - millis(); + if (duration > 0) { + ESP_LOGD(TAG, "Sleep %dms", duration); + delay(duration); + } + } break; + + case INVERT_ON: + this->invert_colors_ = true; + break; + case MADCTL_CMD: + this->madctl_ = arg_byte; + break; + case PIXFMT: + this->pixel_mode_ = arg_byte & 0x11 ? PIXEL_MODE_16 : PIXEL_MODE_18; + break; + case BRIGHTNESS: + this->brightness_ = arg_byte; + break; + + default: + break; + } + const auto *ptr = vec.data() + index; + ESP_LOGD(TAG, "Command %02X, length %d, byte %02X", cmd, num_args, arg_byte); + this->write_command_(cmd, ptr, num_args); + index += num_args; + if (cmd == SLEEP_OUT) + delay(10); + } + } + this->setup_complete_ = true; + if (this->draw_from_origin_) + check_buffer_(); + ESP_LOGCONFIG(TAG, "MIPI SPI setup complete"); +} + +void MipiSpi::update() { + if (!this->setup_complete_ || this->is_failed()) { + return; + } + this->do_update_(); + 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_); + // Some chips require that the drawing window be aligned on certain boundaries + auto dr = this->draw_rounding_; + this->x_low_ = this->x_low_ / dr * dr; + this->y_low_ = this->y_low_ / dr * dr; + this->x_high_ = (this->x_high_ + dr) / dr * dr - 1; + this->y_high_ = (this->y_high_ + dr) / dr * dr - 1; + if (this->draw_from_origin_) { + this->x_low_ = 0; + this->y_low_ = 0; + this->x_high_ = this->width_ - 1; + } + 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, 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 MipiSpi::fill(Color color) { + if (!this->check_buffer_()) + return; + this->x_low_ = 0; + this->y_low_ = 0; + this->x_high_ = this->get_width_internal() - 1; + this->y_high_ = this->get_height_internal() - 1; + switch (this->color_depth_) { + case display::COLOR_BITNESS_332: { + auto new_color = display::ColorUtil::color_to_332(color, display::ColorOrder::COLOR_ORDER_RGB); + memset(this->buffer_, (uint8_t) new_color, this->buffer_bytes_); + break; + } + default: { + auto new_color = display::ColorUtil::color_to_565(color); + if (((uint8_t) (new_color >> 8)) == ((uint8_t) new_color)) { + // Upper and lower is equal can use quicker memset operation. Takes ~20ms. + memset(this->buffer_, (uint8_t) new_color, this->buffer_bytes_); + } else { + auto *ptr_16 = reinterpret_cast(this->buffer_); + auto len = this->buffer_bytes_ / 2; + while (len--) { + *ptr_16++ = new_color; + } + } + } + } +} + +void MipiSpi::draw_absolute_pixel_internal(int x, int y, Color color) { + 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; + switch (this->color_depth_) { + case display::COLOR_BITNESS_332: { + uint8_t new_color = display::ColorUtil::color_to_332(color); + if (this->buffer_[pos] == new_color) + return; + this->buffer_[pos] = new_color; + break; + } + + case display::COLOR_BITNESS_565: { + auto *ptr_16 = reinterpret_cast(this->buffer_); + uint8_t hi_byte = static_cast(color.r & 0xF8) | (color.g >> 5); + uint8_t lo_byte = static_cast((color.g & 0x1C) << 3) | (color.b >> 3); + uint16_t new_color = hi_byte | (lo_byte << 8); // big endian + if (ptr_16[pos] == new_color) + return; + ptr_16[pos] = new_color; + break; + } + default: + return; + } + // 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 MipiSpi::reset_params_() { + if (!this->is_ready()) + return; + this->write_command_(this->invert_colors_ ? INVERT_ON : INVERT_OFF); + if (this->brightness_.has_value()) + this->write_command_(BRIGHTNESS, this->brightness_.value()); +} + +void MipiSpi::write_init_sequence_() { + size_t index = 0; + auto &vec = this->init_sequence_; + while (index != vec.size()) { + if (vec.size() - index < 2) { + ESP_LOGE(TAG, "Malformed init sequence"); + this->mark_failed(); + return; + } + uint8_t cmd = vec[index++]; + uint8_t x = vec[index++]; + if (x == DELAY_FLAG) { + ESP_LOGV(TAG, "Delay %dms", cmd); + delay(cmd); + } else { + uint8_t num_args = x & 0x7F; + if (vec.size() - index < num_args) { + ESP_LOGE(TAG, "Malformed init sequence"); + this->mark_failed(); + return; + } + const auto *ptr = vec.data() + index; + this->write_command_(cmd, ptr, num_args); + index += num_args; + } + } + this->setup_complete_ = true; + ESP_LOGCONFIG(TAG, "MIPI SPI setup complete"); +} + +void MipiSpi::set_addr_window_(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2) { + ESP_LOGVV(TAG, "Set addr %d/%d, %d/%d", x1, y1, x2, y2); + uint8_t buf[4]; + x1 += this->offset_width_; + x2 += this->offset_width_; + y1 += this->offset_height_; + y2 += this->offset_height_; + put16_be(buf, y1); + put16_be(buf + 2, y2); + this->write_command_(RASET, buf, sizeof buf); + put16_be(buf, x1); + put16_be(buf + 2, x2); + this->write_command_(CASET, buf, sizeof buf); +} + +void MipiSpi::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 (!this->setup_complete_ || this->is_failed()) + return; + if (w <= 0 || h <= 0) + return; + if (bitness != this->color_depth_ || big_endian != (this->bit_order_ == spi::BIT_ORDER_MSB_FIRST)) { + Display::draw_pixels_at(x_start, y_start, w, h, ptr, order, bitness, big_endian, x_offset, y_offset, x_pad); + return; + } + if (this->draw_from_origin_) { + auto stride = x_offset + w + x_pad; + for (int y = 0; y != h; y++) { + memcpy(this->buffer_ + ((y + y_start) * this->width_ + x_start) * 2, + ptr + ((y + y_offset) * stride + x_offset) * 2, w * 2); + } + ptr = this->buffer_; + w = this->width_; + h += y_start; + x_start = 0; + y_start = 0; + x_offset = 0; + y_offset = 0; + } + this->write_to_display_(x_start, y_start, w, h, ptr, x_offset, y_offset, x_pad); +} + +void MipiSpi::write_18_from_16_bit_(const uint16_t *ptr, size_t w, size_t h, size_t stride) { + stride -= w; + uint8_t transfer_buffer[6 * 256]; + size_t idx = 0; // index into transfer_buffer + while (h-- != 0) { + for (auto x = w; x-- != 0;) { + auto color_val = *ptr++; + // deal with byte swapping + transfer_buffer[idx++] = (color_val & 0xF8); // Blue + transfer_buffer[idx++] = ((color_val & 0x7) << 5) | ((color_val & 0xE000) >> 11); // Green + transfer_buffer[idx++] = (color_val >> 5) & 0xF8; // Red + if (idx == sizeof(transfer_buffer)) { + this->write_array(transfer_buffer, idx); + idx = 0; + } + } + ptr += stride; + } + if (idx != 0) + this->write_array(transfer_buffer, idx); +} + +void MipiSpi::write_18_from_8_bit_(const uint8_t *ptr, size_t w, size_t h, size_t stride) { + stride -= w; + uint8_t transfer_buffer[6 * 256]; + size_t idx = 0; // index into transfer_buffer + while (h-- != 0) { + for (auto x = w; x-- != 0;) { + auto color_val = *ptr++; + transfer_buffer[idx++] = color_val & 0xE0; // Red + transfer_buffer[idx++] = (color_val << 3) & 0xE0; // Green + transfer_buffer[idx++] = color_val << 6; // Blue + if (idx == sizeof(transfer_buffer)) { + this->write_array(transfer_buffer, idx); + idx = 0; + } + } + ptr += stride; + } + if (idx != 0) + this->write_array(transfer_buffer, idx); +} + +void MipiSpi::write_16_from_8_bit_(const uint8_t *ptr, size_t w, size_t h, size_t stride) { + stride -= w; + uint8_t transfer_buffer[6 * 256]; + size_t idx = 0; // index into transfer_buffer + while (h-- != 0) { + for (auto x = w; x-- != 0;) { + auto color_val = *ptr++; + transfer_buffer[idx++] = (color_val & 0xE0) | ((color_val & 0x1C) >> 2); + transfer_buffer[idx++] = (color_val & 0x3) << 3; + if (idx == sizeof(transfer_buffer)) { + this->write_array(transfer_buffer, idx); + idx = 0; + } + } + ptr += stride; + } + if (idx != 0) + this->write_array(transfer_buffer, idx); +} + +void MipiSpi::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) { + this->set_addr_window_(x_start, y_start, x_start + w - 1, y_start + h - 1); + auto stride = x_offset + w + x_pad; + const auto *offset_ptr = ptr; + if (this->color_depth_ == display::COLOR_BITNESS_332) { + offset_ptr += y_offset * stride + x_offset; + } else { + stride *= 2; + offset_ptr += y_offset * stride + x_offset * 2; + } + + switch (this->bus_width_) { + case 4: + this->enable(); + if (x_offset == 0 && x_pad == 0 && y_offset == 0) { + // we could deal here with a non-zero y_offset, but if x_offset is zero, y_offset probably will be so don't + // bother + this->write_cmd_addr_data(8, 0x32, 24, WDATA << 8, ptr, w * h * 2, 4); + } else { + this->write_cmd_addr_data(8, 0x32, 24, WDATA << 8, nullptr, 0, 4); + for (int y = 0; y != h; y++) { + this->write_cmd_addr_data(0, 0, 0, 0, offset_ptr, w * 2, 4); + offset_ptr += stride; + } + } + break; + + case 8: + this->write_command_(WDATA); + this->enable(); + if (x_offset == 0 && x_pad == 0 && y_offset == 0) { + this->write_cmd_addr_data(0, 0, 0, 0, ptr, w * h * 2, 8); + } else { + for (int y = 0; y != h; y++) { + this->write_cmd_addr_data(0, 0, 0, 0, offset_ptr, w * 2, 8); + offset_ptr += stride; + } + } + break; + + default: + this->write_command_(WDATA); + this->enable(); + + if (this->color_depth_ == display::COLOR_BITNESS_565) { + // Source buffer is 16-bit RGB565 + if (this->pixel_mode_ == PIXEL_MODE_18) { + // Convert RGB565 to RGB666 + this->write_18_from_16_bit_(reinterpret_cast(offset_ptr), w, h, stride / 2); + } else { + // Direct RGB565 output + if (x_offset == 0 && x_pad == 0 && y_offset == 0) { + this->write_array(ptr, w * h * 2); + } else { + for (int y = 0; y != h; y++) { + this->write_array(offset_ptr, w * 2); + offset_ptr += stride; + } + } + } + } else { + // Source buffer is 8-bit RGB332 + if (this->pixel_mode_ == PIXEL_MODE_18) { + // Convert RGB332 to RGB666 + this->write_18_from_8_bit_(offset_ptr, w, h, stride); + } else { + this->write_16_from_8_bit_(offset_ptr, w, h, stride); + } + break; + } + } + this->disable(); +} + +void MipiSpi::write_command_(uint8_t cmd, const uint8_t *bytes, size_t len) { + ESP_LOGV(TAG, "Command %02X, length %d, bytes %s", cmd, len, format_hex_pretty(bytes, len).c_str()); + if (this->bus_width_ == 4) { + this->enable(); + this->write_cmd_addr_data(8, 0x02, 24, cmd << 8, bytes, len); + this->disable(); + } else if (this->bus_width_ == 8) { + this->dc_pin_->digital_write(false); + this->enable(); + this->write_cmd_addr_data(0, 0, 0, 0, &cmd, 1, 8); + this->disable(); + this->dc_pin_->digital_write(true); + if (len != 0) { + this->enable(); + this->write_cmd_addr_data(0, 0, 0, 0, bytes, len, 8); + this->disable(); + } + } else { + this->dc_pin_->digital_write(false); + this->enable(); + this->write_byte(cmd); + this->disable(); + this->dc_pin_->digital_write(true); + if (len != 0) { + if (this->spi_16_) { + for (size_t i = 0; i != len; i++) { + this->enable(); + this->write_byte(0); + this->write_byte(bytes[i]); + this->disable(); + } + } else { + this->enable(); + this->write_array(bytes, len); + this->disable(); + } + } + } +} + +void MipiSpi::dump_config() { + ESP_LOGCONFIG(TAG, "MIPI_SPI Display"); + ESP_LOGCONFIG(TAG, " Model: %s", this->model_); + ESP_LOGCONFIG(TAG, " Width: %u", this->width_); + ESP_LOGCONFIG(TAG, " Height: %u", this->height_); + if (this->offset_width_ != 0) + ESP_LOGCONFIG(TAG, " Offset width: %u", this->offset_width_); + if (this->offset_height_ != 0) + ESP_LOGCONFIG(TAG, " Offset height: %u", this->offset_height_); + ESP_LOGCONFIG(TAG, " Swap X/Y: %s", YESNO(this->madctl_ & MADCTL_MV)); + ESP_LOGCONFIG(TAG, " Mirror X: %s", YESNO(this->madctl_ & (MADCTL_MX | MADCTL_XFLIP))); + ESP_LOGCONFIG(TAG, " Mirror Y: %s", YESNO(this->madctl_ & (MADCTL_MY | MADCTL_YFLIP))); + ESP_LOGCONFIG(TAG, " Color depth: %d bits", this->color_depth_ == display::COLOR_BITNESS_565 ? 16 : 8); + ESP_LOGCONFIG(TAG, " Invert colors: %s", YESNO(this->invert_colors_)); + ESP_LOGCONFIG(TAG, " Color order: %s", this->madctl_ & MADCTL_BGR ? "BGR" : "RGB"); + ESP_LOGCONFIG(TAG, " Pixel mode: %s", this->pixel_mode_ == PIXEL_MODE_18 ? "18bit" : "16bit"); + if (this->brightness_.has_value()) + ESP_LOGCONFIG(TAG, " Brightness: %u", this->brightness_.value()); + if (this->spi_16_) + ESP_LOGCONFIG(TAG, " SPI 16bit: YES"); + ESP_LOGCONFIG(TAG, " Draw rounding: %u", this->draw_rounding_); + if (this->draw_from_origin_) + ESP_LOGCONFIG(TAG, " Draw from origin: YES"); + LOG_PIN(" CS Pin: ", this->cs_); + LOG_PIN(" Reset Pin: ", this->reset_pin_); + LOG_PIN(" DC Pin: ", this->dc_pin_); + ESP_LOGCONFIG(TAG, " SPI Mode: %d", this->mode_); + ESP_LOGCONFIG(TAG, " SPI Data rate: %dMHz", static_cast(this->data_rate_ / 1000000)); + ESP_LOGCONFIG(TAG, " SPI Bus width: %d", this->bus_width_); +} + +} // namespace mipi_spi +} // namespace esphome diff --git a/esphome/components/mipi_spi/mipi_spi.h b/esphome/components/mipi_spi/mipi_spi.h new file mode 100644 index 0000000000..052ebe3a6b --- /dev/null +++ b/esphome/components/mipi_spi/mipi_spi.h @@ -0,0 +1,171 @@ +#pragma once + +#include + +#include "esphome/components/spi/spi.h" +#include "esphome/components/display/display.h" +#include "esphome/components/display/display_buffer.h" +#include "esphome/components/display/display_color_utils.h" + +namespace esphome { +namespace mipi_spi { + +constexpr static const char *const TAG = "display.mipi_spi"; +static const uint8_t SW_RESET_CMD = 0x01; +static const uint8_t SLEEP_OUT = 0x11; +static const uint8_t NORON = 0x13; +static const uint8_t INVERT_OFF = 0x20; +static const uint8_t INVERT_ON = 0x21; +static const uint8_t ALL_ON = 0x23; +static const uint8_t WRAM = 0x24; +static const uint8_t MIPI = 0x26; +static const uint8_t DISPLAY_ON = 0x29; +static const uint8_t RASET = 0x2B; +static const uint8_t CASET = 0x2A; +static const uint8_t WDATA = 0x2C; +static const uint8_t TEON = 0x35; +static const uint8_t MADCTL_CMD = 0x36; +static const uint8_t PIXFMT = 0x3A; +static const uint8_t BRIGHTNESS = 0x51; +static const uint8_t SWIRE1 = 0x5A; +static const uint8_t SWIRE2 = 0x5B; +static const uint8_t PAGESEL = 0xFE; + +static const uint8_t MADCTL_MY = 0x80; // Bit 7 Bottom to top +static const uint8_t MADCTL_MX = 0x40; // Bit 6 Right to left +static const uint8_t MADCTL_MV = 0x20; // Bit 5 Swap axes +static const uint8_t MADCTL_RGB = 0x00; // Bit 3 Red-Green-Blue pixel order +static const uint8_t MADCTL_BGR = 0x08; // Bit 3 Blue-Green-Red pixel order +static const uint8_t MADCTL_XFLIP = 0x02; // Mirror the display horizontally +static const uint8_t MADCTL_YFLIP = 0x01; // Mirror the display vertically + +static const uint8_t DELAY_FLAG = 0xFF; +// store a 16 bit value in a buffer, big endian. +static inline void put16_be(uint8_t *buf, uint16_t value) { + buf[0] = value >> 8; + buf[1] = value; +} + +enum PixelMode { + PIXEL_MODE_16, + PIXEL_MODE_18, +}; + +class MipiSpi : public display::DisplayBuffer, + public spi::SPIDevice { + public: + MipiSpi(size_t width, size_t height, int16_t offset_width, int16_t offset_height, display::ColorBitness color_depth) + : width_(width), + height_(height), + offset_width_(offset_width), + offset_height_(offset_height), + color_depth_(color_depth) {} + void set_model(const char *model) { this->model_ = model; } + void update() override; + void setup() override; + display::ColorOrder get_color_mode() { + return this->madctl_ & MADCTL_BGR ? display::COLOR_ORDER_BGR : display::COLOR_ORDER_RGB; + } + + void set_reset_pin(GPIOPin *reset_pin) { this->reset_pin_ = reset_pin; } + void set_enable_pins(std::vector enable_pins) { this->enable_pins_ = std::move(enable_pins); } + void set_dc_pin(GPIOPin *dc_pin) { this->dc_pin_ = dc_pin; } + void set_invert_colors(bool invert_colors) { + this->invert_colors_ = invert_colors; + this->reset_params_(); + } + void set_brightness(uint8_t brightness) { + this->brightness_ = brightness; + this->reset_params_(); + } + + void set_draw_from_origin(bool draw_from_origin) { this->draw_from_origin_ = draw_from_origin; } + display::DisplayType get_display_type() override { return display::DisplayType::DISPLAY_TYPE_COLOR; } + void dump_config() override; + + int get_width_internal() override { return this->width_; } + int get_height_internal() override { return this->height_; } + bool can_proceed() override { return this->setup_complete_; } + void set_init_sequence(const std::vector &sequence) { this->init_sequence_ = sequence; } + void set_draw_rounding(unsigned rounding) { this->draw_rounding_ = rounding; } + void set_spi_16(bool spi_16) { this->spi_16_ = spi_16; } + + protected: + bool check_buffer_() { + if (this->is_failed()) + return false; + if (this->buffer_ != nullptr) + return true; + auto bytes_per_pixel = this->color_depth_ == display::COLOR_BITNESS_565 ? 2 : 1; + this->init_internal_(this->width_ * this->height_ * bytes_per_pixel); + if (this->buffer_ == nullptr) { + this->mark_failed(); + return false; + } + this->buffer_bytes_ = this->width_ * this->height_ * bytes_per_pixel; + return true; + } + void fill(Color color) override; + void draw_absolute_pixel_internal(int x, int y, Color color) override; + 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_18_from_16_bit_(const uint16_t *ptr, size_t w, size_t h, size_t stride); + void write_18_from_8_bit_(const uint8_t *ptr, size_t w, size_t h, size_t stride); + void write_16_from_8_bit_(const uint8_t *ptr, size_t w, size_t h, size_t stride); + 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); + /** + * the RM67162 in quad SPI mode seems to work like this (not in the datasheet, this is deduced from the + * sample code.) + * + * Immediately after enabling /CS send 4 bytes in single-dataline SPI mode: + * 0: either 0x2 or 0x32. The first indicates that any subsequent data bytes after the initial 4 will be + * sent in 1-dataline SPI. The second indicates quad mode. + * 1: 0x00 + * 2: The command (register address) byte. + * 3: 0x00 + * + * This is followed by zero or more data bytes in either 1-wire or 4-wire mode, depending on the first byte. + * At the conclusion of the write, de-assert /CS. + * + * @param cmd + * @param bytes + * @param len + */ + void write_command_(uint8_t cmd, const uint8_t *bytes, size_t len); + + void write_command_(uint8_t cmd, uint8_t data) { this->write_command_(cmd, &data, 1); } + void write_command_(uint8_t cmd) { this->write_command_(cmd, &cmd, 0); } + void reset_params_(); + void write_init_sequence_(); + void set_addr_window_(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2); + + GPIOPin *reset_pin_{nullptr}; + std::vector enable_pins_{}; + GPIOPin *dc_pin_{nullptr}; + uint16_t x_low_{1}; + uint16_t y_low_{1}; + uint16_t x_high_{0}; + uint16_t y_high_{0}; + bool setup_complete_{}; + + bool invert_colors_{}; + size_t width_; + size_t height_; + int16_t offset_width_; + int16_t offset_height_; + size_t buffer_bytes_{0}; + display::ColorBitness color_depth_; + PixelMode pixel_mode_{PIXEL_MODE_16}; + uint8_t bus_width_{}; + bool spi_16_{}; + uint8_t madctl_{}; + bool draw_from_origin_{false}; + unsigned draw_rounding_{2}; + optional brightness_{}; + const char *model_{"Unknown"}; + std::vector init_sequence_{}; +}; +} // namespace mipi_spi +} // namespace esphome diff --git a/esphome/components/mipi_spi/models/__init__.py b/esphome/components/mipi_spi/models/__init__.py new file mode 100644 index 0000000000..e9726032d4 --- /dev/null +++ b/esphome/components/mipi_spi/models/__init__.py @@ -0,0 +1,65 @@ +from esphome.components.spi import TYPE_OCTAL, TYPE_QUAD, TYPE_SINGLE +import esphome.config_validation as cv +from esphome.const import CONF_HEIGHT, CONF_OFFSET_HEIGHT, CONF_OFFSET_WIDTH, CONF_WIDTH + +from .. import CONF_NATIVE_HEIGHT, CONF_NATIVE_WIDTH + +MADCTL_MY = 0x80 # Bit 7 Bottom to top +MADCTL_MX = 0x40 # Bit 6 Right to left +MADCTL_MV = 0x20 # Bit 5 Reverse Mode +MADCTL_ML = 0x10 # Bit 4 LCD refresh Bottom to top +MADCTL_RGB = 0x00 # Bit 3 Red-Green-Blue pixel order +MADCTL_BGR = 0x08 # Bit 3 Blue-Green-Red pixel order +MADCTL_MH = 0x04 # Bit 2 LCD refresh right to left + +# These bits are used instead of the above bits on some chips, where using MX and MY results in incorrect +# partial updates. +MADCTL_XFLIP = 0x02 # Mirror the display horizontally +MADCTL_YFLIP = 0x01 # Mirror the display vertically + +DELAY_FLAG = 0xFFF # Special flag to indicate a delay + + +def delay(ms): + return DELAY_FLAG, ms + + +class DriverChip: + models = {} + + def __init__( + self, + name: str, + modes=(TYPE_SINGLE, TYPE_QUAD, TYPE_OCTAL), + initsequence=None, + **defaults, + ): + name = name.upper() + self.name = name + self.modes = modes + self.initsequence = initsequence + self.defaults = defaults + DriverChip.models[name] = self + + def extend(self, name, **kwargs): + defaults = self.defaults.copy() + if ( + CONF_WIDTH in defaults + and CONF_OFFSET_WIDTH in kwargs + and CONF_NATIVE_WIDTH not in defaults + ): + defaults[CONF_NATIVE_WIDTH] = defaults[CONF_WIDTH] + if ( + CONF_HEIGHT in defaults + and CONF_OFFSET_HEIGHT in kwargs + and CONF_NATIVE_HEIGHT not in defaults + ): + defaults[CONF_NATIVE_HEIGHT] = defaults[CONF_HEIGHT] + defaults.update(kwargs) + return DriverChip(name, self.modes, initsequence=self.initsequence, **defaults) + + def get_default(self, key, fallback=False): + return self.defaults.get(key, fallback) + + def option(self, name, fallback=False): + return cv.Optional(name, default=self.get_default(name, fallback)) diff --git a/esphome/components/mipi_spi/models/amoled.py b/esphome/components/mipi_spi/models/amoled.py new file mode 100644 index 0000000000..14277b243f --- /dev/null +++ b/esphome/components/mipi_spi/models/amoled.py @@ -0,0 +1,72 @@ +from esphome.components.spi import TYPE_QUAD + +from .. import MODE_RGB +from . import DriverChip, delay +from .commands import MIPI, NORON, PAGESEL, PIXFMT, SLPOUT, SWIRE1, SWIRE2, TEON, WRAM + +DriverChip( + "T-DISPLAY-S3-AMOLED", + width=240, + height=536, + cs_pin=6, + reset_pin=17, + enable_pin=38, + bus_mode=TYPE_QUAD, + brightness=0xD0, + color_order=MODE_RGB, + initsequence=(SLPOUT,), # Requires early SLPOUT +) + +DriverChip( + name="T-DISPLAY-S3-AMOLED-PLUS", + width=240, + height=536, + cs_pin=6, + reset_pin=17, + dc_pin=7, + enable_pin=38, + data_rate="40MHz", + brightness=0xD0, + color_order=MODE_RGB, + initsequence=( + (PAGESEL, 4), + (0x6A, 0x00), + (PAGESEL, 0x05), + (PAGESEL, 0x07), + (0x07, 0x4F), + (PAGESEL, 0x01), + (0x2A, 0x02), + (0x2B, 0x73), + (PAGESEL, 0x0A), + (0x29, 0x10), + (PAGESEL, 0x00), + (0x53, 0x20), + (TEON, 0x00), + (PIXFMT, 0x75), + (0xC4, 0x80), + ), +) + +RM690B0 = DriverChip( + "RM690B0", + brightness=0xD0, + color_order=MODE_RGB, + width=480, + height=600, + initsequence=( + (PAGESEL, 0x20), + (MIPI, 0x0A), + (WRAM, 0x80), + (SWIRE1, 0x51), + (SWIRE2, 0x2E), + (PAGESEL, 0x00), + (0xC2, 0x00), + delay(10), + (TEON, 0x00), + (NORON,), + ), +) + +T4_S3_AMOLED = RM690B0.extend("T4-S3", width=450, offset_width=16, bus_mode=TYPE_QUAD) + +models = {} diff --git a/esphome/components/mipi_spi/models/commands.py b/esphome/components/mipi_spi/models/commands.py new file mode 100644 index 0000000000..032a6e6b2b --- /dev/null +++ b/esphome/components/mipi_spi/models/commands.py @@ -0,0 +1,82 @@ +# MIPI DBI commands + +NOP = 0x00 +SWRESET = 0x01 +RDDID = 0x04 +RDDST = 0x09 +RDMODE = 0x0A +RDMADCTL = 0x0B +RDPIXFMT = 0x0C +RDIMGFMT = 0x0D +RDSELFDIAG = 0x0F +SLEEP_IN = 0x10 +SLPIN = 0x10 +SLEEP_OUT = 0x11 +SLPOUT = 0x11 +PTLON = 0x12 +NORON = 0x13 +INVERT_OFF = 0x20 +INVOFF = 0x20 +INVERT_ON = 0x21 +INVON = 0x21 +ALL_ON = 0x23 +WRAM = 0x24 +GAMMASET = 0x26 +MIPI = 0x26 +DISPOFF = 0x28 +DISPON = 0x29 +CASET = 0x2A +PASET = 0x2B +RASET = 0x2B +RAMWR = 0x2C +WDATA = 0x2C +RAMRD = 0x2E +PTLAR = 0x30 +VSCRDEF = 0x33 +TEON = 0x35 +MADCTL = 0x36 +MADCTL_CMD = 0x36 +VSCRSADD = 0x37 +IDMOFF = 0x38 +IDMON = 0x39 +COLMOD = 0x3A +PIXFMT = 0x3A +GETSCANLINE = 0x45 +BRIGHTNESS = 0x51 +WRDISBV = 0x51 +RDDISBV = 0x52 +WRCTRLD = 0x53 +SWIRE1 = 0x5A +SWIRE2 = 0x5B +IFMODE = 0xB0 +FRMCTR1 = 0xB1 +FRMCTR2 = 0xB2 +FRMCTR3 = 0xB3 +INVCTR = 0xB4 +DFUNCTR = 0xB6 +ETMOD = 0xB7 +PWCTR1 = 0xC0 +PWCTR2 = 0xC1 +PWCTR3 = 0xC2 +PWCTR4 = 0xC3 +PWCTR5 = 0xC4 +VMCTR1 = 0xC5 +IFCTR = 0xC6 +VMCTR2 = 0xC7 +GMCTR = 0xC8 +SETEXTC = 0xC8 +PWSET = 0xD0 +VMCTR = 0xD1 +PWSETN = 0xD2 +RDID4 = 0xD3 +RDINDEX = 0xD9 +RDID1 = 0xDA +RDID2 = 0xDB +RDID3 = 0xDC +RDIDX = 0xDD +GMCTRP1 = 0xE0 +GMCTRN1 = 0xE1 +CSCON = 0xF0 +PWCTR6 = 0xF6 +ADJCTL3 = 0xF7 +PAGESEL = 0xFE diff --git a/esphome/components/mipi_spi/models/cyd.py b/esphome/components/mipi_spi/models/cyd.py new file mode 100644 index 0000000000..a25ecf33a8 --- /dev/null +++ b/esphome/components/mipi_spi/models/cyd.py @@ -0,0 +1,10 @@ +from .ili import ILI9341 + +ILI9341.extend( + "ESP32-2432S028", + data_rate="40MHz", + cs_pin=15, + dc_pin=2, +) + +models = {} diff --git a/esphome/components/mipi_spi/models/ili.py b/esphome/components/mipi_spi/models/ili.py new file mode 100644 index 0000000000..cc12b38f5d --- /dev/null +++ b/esphome/components/mipi_spi/models/ili.py @@ -0,0 +1,749 @@ +from esphome.components.spi import TYPE_OCTAL + +from .. import MODE_RGB +from . import DriverChip, delay +from .commands import ( + ADJCTL3, + CSCON, + DFUNCTR, + ETMOD, + FRMCTR1, + FRMCTR2, + FRMCTR3, + GAMMASET, + GMCTR, + GMCTRN1, + GMCTRP1, + IDMOFF, + IFCTR, + IFMODE, + INVCTR, + NORON, + PWCTR1, + PWCTR2, + PWCTR3, + PWCTR4, + PWCTR5, + PWSET, + PWSETN, + SETEXTC, + SWRESET, + VMCTR, + VMCTR1, + VMCTR2, + VSCRSADD, +) + +DriverChip( + "M5CORE", + width=320, + height=240, + cs_pin=14, + dc_pin=27, + reset_pin=33, + initsequence=( + (SETEXTC, 0xFF, 0x93, 0x42), + (PWCTR1, 0x12, 0x12), + (PWCTR2, 0x03), + (VMCTR1, 0xF2), + (IFMODE, 0xE0), + (0xF6, 0x01, 0x00, 0x00), + ( + GMCTRP1, + 0x00, + 0x0C, + 0x11, + 0x04, + 0x11, + 0x08, + 0x37, + 0x89, + 0x4C, + 0x06, + 0x0C, + 0x0A, + 0x2E, + 0x34, + 0x0F, + ), + ( + GMCTRN1, + 0x00, + 0x0B, + 0x11, + 0x05, + 0x13, + 0x09, + 0x33, + 0x67, + 0x48, + 0x07, + 0x0E, + 0x0B, + 0x2E, + 0x33, + 0x0F, + ), + (DFUNCTR, 0x08, 0x82, 0x1D, 0x04), + (IDMOFF,), + ), +) +ILI9341 = DriverChip( + "ILI9341", + mirror_x=True, + width=240, + height=320, + initsequence=( + (0xEF, 0x03, 0x80, 0x02), + (0xCF, 0x00, 0xC1, 0x30), + (0xED, 0x64, 0x03, 0x12, 0x81), + (0xE8, 0x85, 0x00, 0x78), + (0xCB, 0x39, 0x2C, 0x00, 0x34, 0x02), + (0xF7, 0x20), + (0xEA, 0x00, 0x00), + (PWCTR1, 0x23), + (PWCTR2, 0x10), + (VMCTR1, 0x3E, 0x28), + (VMCTR2, 0x86), + (VSCRSADD, 0x00), + (FRMCTR1, 0x00, 0x18), + (DFUNCTR, 0x08, 0x82, 0x27), + (0xF2, 0x00), + (GAMMASET, 0x01), + ( + GMCTRP1, + 0x0F, + 0x31, + 0x2B, + 0x0C, + 0x0E, + 0x08, + 0x4E, + 0xF1, + 0x37, + 0x07, + 0x10, + 0x03, + 0x0E, + 0x09, + 0x00, + ), + ( + GMCTRN1, + 0x00, + 0x0E, + 0x14, + 0x03, + 0x11, + 0x07, + 0x31, + 0xC1, + 0x48, + 0x08, + 0x0F, + 0x0C, + 0x31, + 0x36, + 0x0F, + ), + ), +) +DriverChip( + "ILI9481", + mirror_x=True, + width=320, + height=480, + use_axis_flips=True, + initsequence=( + (PWSET, 0x07, 0x42, 0x18), + (VMCTR, 0x00, 0x07, 0x10), + (PWSETN, 0x01, 0x02), + (PWCTR1, 0x10, 0x3B, 0x00, 0x02, 0x11), + (VMCTR1, 0x03), + (IFCTR, 0x83), + (GMCTR, 0x32, 0x36, 0x45, 0x06, 0x16, 0x37, 0x75, 0x77, 0x54, 0x0C, 0x00), + ), +) +DriverChip( + "ILI9486", + mirror_x=True, + width=320, + height=480, + initsequence=( + (PWCTR3, 0x44), + (VMCTR1, 0x00, 0x00, 0x00, 0x00), + ( + GMCTRP1, + 0x0F, + 0x1F, + 0x1C, + 0x0C, + 0x0F, + 0x08, + 0x48, + 0x98, + 0x37, + 0x0A, + 0x13, + 0x04, + 0x11, + 0x0D, + 0x00, + ), + ( + GMCTRN1, + 0x0F, + 0x32, + 0x2E, + 0x0B, + 0x0D, + 0x05, + 0x47, + 0x75, + 0x37, + 0x06, + 0x10, + 0x03, + 0x24, + 0x20, + 0x00, + ), + ), +) +DriverChip( + "ILI9488", + width=320, + height=480, + pixel_mode="18bit", + initsequence=( + ( + GMCTRP1, + 0x0F, + 0x24, + 0x1C, + 0x0A, + 0x0F, + 0x08, + 0x43, + 0x88, + 0x32, + 0x0F, + 0x10, + 0x06, + 0x0F, + 0x07, + 0x00, + ), + ( + GMCTRN1, + 0x0F, + 0x38, + 0x30, + 0x09, + 0x0F, + 0x0F, + 0x4E, + 0x77, + 0x3C, + 0x07, + 0x10, + 0x05, + 0x23, + 0x1B, + 0x00, + ), + (PWCTR1, 0x17, 0x15), + (PWCTR2, 0x41), + (VMCTR1, 0x00, 0x12, 0x80), + (IFMODE, 0x00), + (FRMCTR1, 0xA0), + (INVCTR, 0x02), + (0xE9, 0x00), + (ADJCTL3, 0xA9, 0x51, 0x2C, 0x82), + ), +) +ILI9488_A = DriverChip( + "ILI9488_A", + width=320, + height=480, + invert_colors=False, + pixel_mode="18bit", + mirror_x=True, + initsequence=( + ( + GMCTRP1, + 0x00, + 0x03, + 0x09, + 0x08, + 0x16, + 0x0A, + 0x3F, + 0x78, + 0x4C, + 0x09, + 0x0A, + 0x08, + 0x16, + 0x1A, + 0x0F, + ), + ( + GMCTRN1, + 0x00, + 0x16, + 0x19, + 0x03, + 0x0F, + 0x05, + 0x32, + 0x45, + 0x46, + 0x04, + 0x0E, + 0x0D, + 0x35, + 0x37, + 0x0F, + ), + (PWCTR1, 0x17, 0x15), + (PWCTR2, 0x41), + (VMCTR1, 0x00, 0x12, 0x80), + (IFMODE, 0x00), + (FRMCTR1, 0xA0), + (INVCTR, 0x02), + (DFUNCTR, 0x02, 0x02), + (0xE9, 0x00), + (ADJCTL3, 0xA9, 0x51, 0x2C, 0x82), + ), +) +ST7796 = DriverChip( + "ST7796", + mirror_x=True, + width=320, + height=480, + initsequence=( + (SWRESET,), + (CSCON, 0xC3), + (CSCON, 0x96), + (VMCTR1, 0x1C), + (IFMODE, 0x80), + (INVCTR, 0x01), + (DFUNCTR, 0x80, 0x02, 0x3B), + (ETMOD, 0xC6), + (CSCON, 0x69), + (CSCON, 0x3C), + ), +) +DriverChip( + "S3BOX", + width=320, + height=240, + mirror_x=True, + mirror_y=True, + invert_colors=False, + data_rate="40MHz", + dc_pin=4, + cs_pin=5, + # reset_pin={CONF_INVERTED: True, CONF_NUMBER: 48}, + initsequence=( + (0xEF, 0x03, 0x80, 0x02), + (0xCF, 0x00, 0xC1, 0x30), + (0xED, 0x64, 0x03, 0x12, 0x81), + (0xE8, 0x85, 0x00, 0x78), + (0xCB, 0x39, 0x2C, 0x00, 0x34, 0x02), + (0xF7, 0x20), + (0xEA, 0x00, 0x00), + (PWCTR1, 0x23), + (PWCTR2, 0x10), + (VMCTR1, 0x3E, 0x28), + (VMCTR2, 0x86), + (VSCRSADD, 0x00), + (FRMCTR1, 0x00, 0x18), + (DFUNCTR, 0x08, 0x82, 0x27), + (0xF2, 0x00), + (GAMMASET, 0x01), + ( + GMCTRP1, + 0x0F, + 0x31, + 0x2B, + 0x0C, + 0x0E, + 0x08, + 0x4E, + 0xF1, + 0x37, + 0x07, + 0x10, + 0x03, + 0x0E, + 0x09, + 0x00, + ), + ( + GMCTRN1, + 0x00, + 0x0E, + 0x14, + 0x03, + 0x11, + 0x07, + 0x31, + 0xC1, + 0x48, + 0x08, + 0x0F, + 0x0C, + 0x31, + 0x36, + 0x0F, + ), + ), +) +DriverChip( + "S3BOXLITE", + mirror_x=True, + color_order=MODE_RGB, + width=320, + height=240, + cs_pin=5, + dc_pin=4, + reset_pin=48, + initsequence=( + (0xEF, 0x03, 0x80, 0x02), + (0xCF, 0x00, 0xC1, 0x30), + (0xED, 0x64, 0x03, 0x12, 0x81), + (0xE8, 0x85, 0x00, 0x78), + (0xCB, 0x39, 0x2C, 0x00, 0x34, 0x02), + (0xF7, 0x20), + (0xEA, 0x00, 0x00), + (PWCTR1, 0x23), + (PWCTR2, 0x10), + (VMCTR1, 0x3E, 0x28), + (VMCTR2, 0x86), + (VSCRSADD, 0x00), + (FRMCTR1, 0x00, 0x18), + (DFUNCTR, 0x08, 0x82, 0x27), + (0xF2, 0x00), + (GAMMASET, 0x01), + ( + GMCTRP1, + 0xF0, + 0x09, + 0x0B, + 0x06, + 0x04, + 0x15, + 0x2F, + 0x54, + 0x42, + 0x3C, + 0x17, + 0x14, + 0x18, + 0x1B, + ), + ( + GMCTRN1, + 0xE0, + 0x09, + 0x0B, + 0x06, + 0x04, + 0x03, + 0x2B, + 0x43, + 0x42, + 0x3B, + 0x16, + 0x14, + 0x17, + 0x1B, + ), + ), +) +ST7789V = DriverChip( + "ST7789V", + width=240, + height=320, + initsequence=( + (DFUNCTR, 0x0A, 0x82), + (FRMCTR2, 0x0C, 0x0C, 0x00, 0x33, 0x33), + (ETMOD, 0x35), + (0xBB, 0x28), + (PWCTR1, 0x0C), + (PWCTR3, 0x01, 0xFF), + (PWCTR4, 0x10), + (PWCTR5, 0x20), + (IFCTR, 0x0F), + (PWSET, 0xA4, 0xA1), + ( + GMCTRP1, + 0xD0, + 0x00, + 0x02, + 0x07, + 0x0A, + 0x28, + 0x32, + 0x44, + 0x42, + 0x06, + 0x0E, + 0x12, + 0x14, + 0x17, + ), + ( + GMCTRN1, + 0xD0, + 0x00, + 0x02, + 0x07, + 0x0A, + 0x28, + 0x31, + 0x54, + 0x47, + 0x0E, + 0x1C, + 0x17, + 0x1B, + 0x1E, + ), + ), +) +DriverChip( + "GC9A01A", + mirror_x=True, + width=240, + height=240, + initsequence=( + (0xEF,), + (0xEB, 0x14), + (0xFE,), + (0xEF,), + (0xEB, 0x14), + (0x84, 0x40), + (0x85, 0xFF), + (0x86, 0xFF), + (0x87, 0xFF), + (0x88, 0x0A), + (0x89, 0x21), + (0x8A, 0x00), + (0x8B, 0x80), + (0x8C, 0x01), + (0x8D, 0x01), + (0x8E, 0xFF), + (0x8F, 0xFF), + (0xB6, 0x00, 0x00), + (0x90, 0x08, 0x08, 0x08, 0x08), + (0xBD, 0x06), + (0xBC, 0x00), + (0xFF, 0x60, 0x01, 0x04), + (0xC3, 0x13), + (0xC4, 0x13), + (0xF9, 0x22), + (0xBE, 0x11), + (0xE1, 0x10, 0x0E), + (0xDF, 0x21, 0x0C, 0x02), + (0xF0, 0x45, 0x09, 0x08, 0x08, 0x26, 0x2A), + (0xF1, 0x43, 0x70, 0x72, 0x36, 0x37, 0x6F), + (0xF2, 0x45, 0x09, 0x08, 0x08, 0x26, 0x2A), + (0xF3, 0x43, 0x70, 0x72, 0x36, 0x37, 0x6F), + (0xED, 0x1B, 0x0B), + (0xAE, 0x77), + (0xCD, 0x63), + (0xE8, 0x34), + ( + 0x62, + 0x18, + 0x0D, + 0x71, + 0xED, + 0x70, + 0x70, + 0x18, + 0x0F, + 0x71, + 0xEF, + 0x70, + 0x70, + ), + ( + 0x63, + 0x18, + 0x11, + 0x71, + 0xF1, + 0x70, + 0x70, + 0x18, + 0x13, + 0x71, + 0xF3, + 0x70, + 0x70, + ), + (0x64, 0x28, 0x29, 0xF1, 0x01, 0xF1, 0x00, 0x07), + (0x66, 0x3C, 0x00, 0xCD, 0x67, 0x45, 0x45, 0x10, 0x00, 0x00, 0x00), + (0x67, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x01, 0x54, 0x10, 0x32, 0x98), + (0x74, 0x10, 0x85, 0x80, 0x00, 0x00, 0x4E, 0x00), + (0x98, 0x3E, 0x07), + (0x35,), + ), +) +DriverChip( + "GC9D01N", + width=160, + height=160, + initsequence=( + (0xFE,), + (0xEF,), + (0x80, 0xFF), + (0x81, 0xFF), + (0x82, 0xFF), + (0x83, 0xFF), + (0x84, 0xFF), + (0x85, 0xFF), + (0x86, 0xFF), + (0x87, 0xFF), + (0x88, 0xFF), + (0x89, 0xFF), + (0x8A, 0xFF), + (0x8B, 0xFF), + (0x8C, 0xFF), + (0x8D, 0xFF), + (0x8E, 0xFF), + (0x8F, 0xFF), + (0x3A, 0x05), + (0xEC, 0x01), + (0x74, 0x02, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00), + (0x98, 0x3E), + (0x99, 0x3E), + (0xB5, 0x0D, 0x0D), + (0x60, 0x38, 0x0F, 0x79, 0x67), + (0x61, 0x38, 0x11, 0x79, 0x67), + (0x64, 0x38, 0x17, 0x71, 0x5F, 0x79, 0x67), + (0x65, 0x38, 0x13, 0x71, 0x5B, 0x79, 0x67), + (0x6A, 0x00, 0x00), + (0x6C, 0x22, 0x02, 0x22, 0x02, 0x22, 0x22, 0x50), + ( + 0x6E, + 0x03, + 0x03, + 0x01, + 0x01, + 0x00, + 0x00, + 0x0F, + 0x0F, + 0x0D, + 0x0D, + 0x0B, + 0x0B, + 0x09, + 0x09, + 0x00, + 0x00, + 0x00, + 0x00, + 0x0A, + 0x0A, + 0x0C, + 0x0C, + 0x0E, + 0x0E, + 0x10, + 0x10, + 0x00, + 0x00, + 0x02, + 0x02, + 0x04, + 0x04, + ), + (0xBF, 0x01), + (0xF9, 0x40), + (0x9B, 0x3B, 0x93, 0x33, 0x7F, 0x00), + (0x7E, 0x30), + (0x70, 0x0D, 0x02, 0x08, 0x0D, 0x02, 0x08), + (0x71, 0x0D, 0x02, 0x08), + (0x91, 0x0E, 0x09), + (0xC3, 0x19, 0xC4, 0x19, 0xC9, 0x3C), + (0xF0, 0x53, 0x15, 0x0A, 0x04, 0x00, 0x3E), + (0xF1, 0x56, 0xA8, 0x7F, 0x33, 0x34, 0x5F), + (0xF2, 0x53, 0x15, 0x0A, 0x04, 0x00, 0x3A), + (0xF3, 0x52, 0xA4, 0x7F, 0x33, 0x34, 0xDF), + ), +) +DriverChip( + "ST7735", + color_order=MODE_RGB, + width=128, + height=160, + initsequence=( + SWRESET, + delay(10), + (FRMCTR1, 0x01, 0x2C, 0x2D), + (FRMCTR2, 0x01, 0x2C, 0x2D), + (FRMCTR3, 0x01, 0x2C, 0x2D, 0x01, 0x2C, 0x2D), + (INVCTR, 0x07), + (PWCTR1, 0xA2, 0x02, 0x84), + (PWCTR2, 0xC5), + (PWCTR3, 0x0A, 0x00), + (PWCTR4, 0x8A, 0x2A), + (PWCTR5, 0x8A, 0xEE), + (VMCTR1, 0x0E), + ( + GMCTRP1, + 0x02, + 0x1C, + 0x07, + 0x12, + 0x37, + 0x32, + 0x29, + 0x2D, + 0x29, + 0x25, + 0x2B, + 0x39, + 0x00, + 0x01, + 0x03, + 0x10, + ), + ( + GMCTRN1, + 0x03, + 0x1D, + 0x07, + 0x06, + 0x2E, + 0x2C, + 0x29, + 0x2D, + 0x2E, + 0x2E, + 0x37, + 0x3F, + 0x00, + 0x00, + 0x02, + 0x10, + ), + NORON, + ), +) +ST7796.extend( + "WT32-SC01-PLUS", + bus_mode=TYPE_OCTAL, + mirror_x=True, + reset_pin=4, + dc_pin=0, + invert_colors=True, +) + +models = {} diff --git a/esphome/components/mipi_spi/models/jc.py b/esphome/components/mipi_spi/models/jc.py new file mode 100644 index 0000000000..449c5b87ae --- /dev/null +++ b/esphome/components/mipi_spi/models/jc.py @@ -0,0 +1,260 @@ +from esphome.components.spi import TYPE_QUAD +import esphome.config_validation as cv +from esphome.const import CONF_IGNORE_STRAPPING_WARNING, CONF_NUMBER + +from .. import MODE_RGB +from . import DriverChip + +AXS15231 = DriverChip( + "AXS15231", + draw_rounding=8, + swap_xy=cv.UNDEFINED, + color_order=MODE_RGB, + bus_mode=TYPE_QUAD, + initsequence=( + (0xBB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5A, 0xA5), + (0xC1, 0x33), + (0xBB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00), + ), +) + +AXS15231.extend( + "JC3248W535", + width=320, + height=480, + cs_pin={CONF_NUMBER: 45, CONF_IGNORE_STRAPPING_WARNING: True}, + data_rate="40MHz", +) + +DriverChip( + "JC3636W518", + height=360, + width=360, + offset_height=1, + draw_rounding=1, + cs_pin=10, + reset_pin=47, + invert_colors=True, + color_order=MODE_RGB, + bus_mode=TYPE_QUAD, + data_rate="40MHz", + initsequence=( + (0xF0, 0x08), + (0xF2, 0x08), + (0x9B, 0x51), + (0x86, 0x53), + (0xF2, 0x80), + (0xF0, 0x00), + (0xF0, 0x01), + (0xF1, 0x01), + (0xB0, 0x54), + (0xB1, 0x3F), + (0xB2, 0x2A), + (0xB4, 0x46), + (0xB5, 0x34), + (0xB6, 0xD5), + (0xB7, 0x30), + (0xBA, 0x00), + (0xBB, 0x08), + (0xBC, 0x08), + (0xBD, 0x00), + (0xC0, 0x80), + (0xC1, 0x10), + (0xC2, 0x37), + (0xC3, 0x80), + (0xC4, 0x10), + (0xC5, 0x37), + (0xC6, 0xA9), + (0xC7, 0x41), + (0xC8, 0x51), + (0xC9, 0xA9), + (0xCA, 0x41), + (0xCB, 0x51), + (0xD0, 0x91), + (0xD1, 0x68), + (0xD2, 0x69), + (0xF5, 0x00, 0xA5), + (0xDD, 0x3F), + (0xDE, 0x3F), + (0xF1, 0x10), + (0xF0, 0x00), + (0xF0, 0x02), + ( + 0xE0, + 0x70, + 0x09, + 0x12, + 0x0C, + 0x0B, + 0x27, + 0x38, + 0x54, + 0x4E, + 0x19, + 0x15, + 0x15, + 0x2C, + 0x2F, + ), + ( + 0xE1, + 0x70, + 0x08, + 0x11, + 0x0C, + 0x0B, + 0x27, + 0x38, + 0x43, + 0x4C, + 0x18, + 0x14, + 0x14, + 0x2B, + 0x2D, + ), + (0xF0, 0x10), + (0xF3, 0x10), + (0xE0, 0x08), + (0xE1, 0x00), + (0xE2, 0x00), + (0xE3, 0x00), + (0xE4, 0xE0), + (0xE5, 0x06), + (0xE6, 0x21), + (0xE7, 0x00), + (0xE8, 0x05), + (0xE9, 0x82), + (0xEA, 0xDF), + (0xEB, 0x89), + (0xEC, 0x20), + (0xED, 0x14), + (0xEE, 0xFF), + (0xEF, 0x00), + (0xF8, 0xFF), + (0xF9, 0x00), + (0xFA, 0x00), + (0xFB, 0x30), + (0xFC, 0x00), + (0xFD, 0x00), + (0xFE, 0x00), + (0xFF, 0x00), + (0x60, 0x42), + (0x61, 0xE0), + (0x62, 0x40), + (0x63, 0x40), + (0x64, 0x02), + (0x65, 0x00), + (0x66, 0x40), + (0x67, 0x03), + (0x68, 0x00), + (0x69, 0x00), + (0x6A, 0x00), + (0x6B, 0x00), + (0x70, 0x42), + (0x71, 0xE0), + (0x72, 0x40), + (0x73, 0x40), + (0x74, 0x02), + (0x75, 0x00), + (0x76, 0x40), + (0x77, 0x03), + (0x78, 0x00), + (0x79, 0x00), + (0x7A, 0x00), + (0x7B, 0x00), + (0x80, 0x48), + (0x81, 0x00), + (0x82, 0x05), + (0x83, 0x02), + (0x84, 0xDD), + (0x85, 0x00), + (0x86, 0x00), + (0x87, 0x00), + (0x88, 0x48), + (0x89, 0x00), + (0x8A, 0x07), + (0x8B, 0x02), + (0x8C, 0xDF), + (0x8D, 0x00), + (0x8E, 0x00), + (0x8F, 0x00), + (0x90, 0x48), + (0x91, 0x00), + (0x92, 0x09), + (0x93, 0x02), + (0x94, 0xE1), + (0x95, 0x00), + (0x96, 0x00), + (0x97, 0x00), + (0x98, 0x48), + (0x99, 0x00), + (0x9A, 0x0B), + (0x9B, 0x02), + (0x9C, 0xE3), + (0x9D, 0x00), + (0x9E, 0x00), + (0x9F, 0x00), + (0xA0, 0x48), + (0xA1, 0x00), + (0xA2, 0x04), + (0xA3, 0x02), + (0xA4, 0xDC), + (0xA5, 0x00), + (0xA6, 0x00), + (0xA7, 0x00), + (0xA8, 0x48), + (0xA9, 0x00), + (0xAA, 0x06), + (0xAB, 0x02), + (0xAC, 0xDE), + (0xAD, 0x00), + (0xAE, 0x00), + (0xAF, 0x00), + (0xB0, 0x48), + (0xB1, 0x00), + (0xB2, 0x08), + (0xB3, 0x02), + (0xB4, 0xE0), + (0xB5, 0x00), + (0xB6, 0x00), + (0xB7, 0x00), + (0xB8, 0x48), + (0xB9, 0x00), + (0xBA, 0x0A), + (0xBB, 0x02), + (0xBC, 0xE2), + (0xBD, 0x00), + (0xBE, 0x00), + (0xBF, 0x00), + (0xC0, 0x12), + (0xC1, 0xAA), + (0xC2, 0x65), + (0xC3, 0x74), + (0xC4, 0x47), + (0xC5, 0x56), + (0xC6, 0x00), + (0xC7, 0x88), + (0xC8, 0x99), + (0xC9, 0x33), + (0xD0, 0x21), + (0xD1, 0xAA), + (0xD2, 0x65), + (0xD3, 0x74), + (0xD4, 0x47), + (0xD5, 0x56), + (0xD6, 0x00), + (0xD7, 0x88), + (0xD8, 0x99), + (0xD9, 0x33), + (0xF3, 0x01), + (0xF0, 0x00), + (0xF0, 0x01), + (0xF1, 0x01), + (0xA0, 0x0B), + (0xA3, 0x2A), + (0xA5, 0xC3), + ), +) + +models = {} diff --git a/esphome/components/mipi_spi/models/lanbon.py b/esphome/components/mipi_spi/models/lanbon.py new file mode 100644 index 0000000000..6f9aa58674 --- /dev/null +++ b/esphome/components/mipi_spi/models/lanbon.py @@ -0,0 +1,15 @@ +from .ili import ST7789V + +ST7789V.extend( + "LANBON-L8", + width=240, + height=320, + mirror_x=True, + mirror_y=True, + data_rate="80MHz", + cs_pin=22, + dc_pin=21, + reset_pin=18, +) + +models = {} diff --git a/esphome/components/mipi_spi/models/lilygo.py b/esphome/components/mipi_spi/models/lilygo.py new file mode 100644 index 0000000000..dd6f9c02f7 --- /dev/null +++ b/esphome/components/mipi_spi/models/lilygo.py @@ -0,0 +1,60 @@ +from esphome.components.spi import TYPE_OCTAL + +from .. import MODE_BGR +from .ili import ST7789V, ST7796 + +ST7789V.extend( + "T-EMBED", + width=170, + height=320, + offset_width=35, + color_order=MODE_BGR, + invert_colors=True, + draw_rounding=1, + cs_pin=10, + dc_pin=13, + reset_pin=9, + data_rate="80MHz", +) + +ST7789V.extend( + "T-DISPLAY", + height=240, + width=135, + offset_width=52, + offset_height=40, + draw_rounding=1, + cs_pin=5, + dc_pin=16, + invert_colors=True, +) +ST7789V.extend( + "T-DISPLAY-S3", + height=320, + width=170, + offset_width=35, + color_order=MODE_BGR, + invert_colors=True, + draw_rounding=1, + dc_pin=7, + cs_pin=6, + reset_pin=5, + enable_pin=[9, 15], + data_rate="10MHz", + bus_mode=TYPE_OCTAL, +) + +ST7796.extend( + "T-DISPLAY-S3-PRO", + width=222, + height=480, + offset_width=49, + draw_rounding=1, + cs_pin=39, + reset_pin=47, + dc_pin=9, + backlight_pin=48, + invert_colors=True, +) + +models = {} diff --git a/esphome/components/mipi_spi/models/waveshare.py b/esphome/components/mipi_spi/models/waveshare.py new file mode 100644 index 0000000000..6d14f56fc6 --- /dev/null +++ b/esphome/components/mipi_spi/models/waveshare.py @@ -0,0 +1,139 @@ +from . import DriverChip +from .ili import ILI9488_A + +DriverChip( + "WAVESHARE-4-TFT", + width=320, + height=480, + invert_colors=True, + spi_16=True, + initsequence=( + ( + 0xF9, + 0x00, + 0x08, + ), + ( + 0xC0, + 0x19, + 0x1A, + ), + ( + 0xC1, + 0x45, + 0x00, + ), + ( + 0xC2, + 0x33, + ), + ( + 0xC5, + 0x00, + 0x28, + ), + ( + 0xB1, + 0xA0, + 0x11, + ), + ( + 0xB4, + 0x02, + ), + ( + 0xB6, + 0x00, + 0x42, + 0x3B, + ), + ( + 0xB7, + 0x07, + ), + ( + 0xE0, + 0x1F, + 0x25, + 0x22, + 0x0B, + 0x06, + 0x0A, + 0x4E, + 0xC6, + 0x39, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + ), + ( + 0xE1, + 0x1F, + 0x3F, + 0x3F, + 0x0F, + 0x1F, + 0x0F, + 0x46, + 0x49, + 0x31, + 0x05, + 0x09, + 0x03, + 0x1C, + 0x1A, + 0x00, + ), + ( + 0xF1, + 0x36, + 0x04, + 0x00, + 0x3C, + 0x0F, + 0x0F, + 0xA4, + 0x02, + ), + ( + 0xF2, + 0x18, + 0xA3, + 0x12, + 0x02, + 0x32, + 0x12, + 0xFF, + 0x32, + 0x00, + ), + ( + 0xF4, + 0x40, + 0x00, + 0x08, + 0x91, + 0x04, + ), + ( + 0xF8, + 0x21, + 0x04, + ), + ), +) + +ILI9488_A.extend( + "PICO-RESTOUCH-LCD-3.5", + spi_16=True, + pixel_mode="16bit", + mirror_x=True, + dc_pin=33, + cs_pin=34, + reset_pin=40, + data_rate="20MHz", + invert_colors=True, +) diff --git a/esphome/components/spi/spi.h b/esphome/components/spi/spi.h index 7cdffafdb5..f96d3da251 100644 --- a/esphome/components/spi/spi.h +++ b/esphome/components/spi/spi.h @@ -355,6 +355,12 @@ class SPIComponent : public Component { void setup() override; void dump_config() override; + size_t get_bus_width() const { + if (this->data_pins_.empty()) { + return 1; + } + return this->data_pins_.size(); + } protected: GPIOPin *clk_pin_{nullptr}; diff --git a/tests/components/mipi_spi/common.yaml b/tests/components/mipi_spi/common.yaml new file mode 100644 index 0000000000..e4b1e2b30c --- /dev/null +++ b/tests/components/mipi_spi/common.yaml @@ -0,0 +1,38 @@ +spi: + - id: spi_single + clk_pin: + number: ${clk_pin} + allow_other_uses: true + mosi_pin: + number: ${mosi_pin} + +display: + - platform: mipi_spi + spi_16: true + pixel_mode: 18bit + model: ili9488 + dc_pin: ${dc_pin} + cs_pin: ${cs_pin} + reset_pin: ${reset_pin} + data_rate: 20MHz + invert_colors: true + show_test_card: true + spi_mode: mode0 + draw_rounding: 8 + use_axis_flips: true + init_sequence: + - [0xd0, 1, 2, 3] + - delay 10ms + transform: + swap_xy: true + mirror_x: false + mirror_y: true + dimensions: + width: 100 + height: 200 + enable_pin: + - number: ${clk_pin} + allow_other_uses: true + - number: ${enable_pin} + bus_mode: single + diff --git a/tests/components/mipi_spi/test-esp32-2432s028.esp32-s3-idf.yaml b/tests/components/mipi_spi/test-esp32-2432s028.esp32-s3-idf.yaml new file mode 100644 index 0000000000..a28776798c --- /dev/null +++ b/tests/components/mipi_spi/test-esp32-2432s028.esp32-s3-idf.yaml @@ -0,0 +1,41 @@ +spi: + - id: quad_spi + type: quad + interface: spi3 + clk_pin: + number: 47 + data_pins: + - allow_other_uses: true + number: 40 + - allow_other_uses: true + number: 41 + - allow_other_uses: true + number: 42 + - allow_other_uses: true + number: 43 + - id: octal_spi + type: octal + interface: hardware + clk_pin: + number: 0 + data_pins: + - 36 + - 37 + - 38 + - 39 + - allow_other_uses: true + number: 40 + - allow_other_uses: true + number: 41 + - allow_other_uses: true + number: 42 + - allow_other_uses: true + number: 43 + - id: spi_id_3 + interface: any + clk_pin: 8 + mosi_pin: 9 + +display: + - platform: mipi_spi + model: ESP32-2432S028 diff --git a/tests/components/mipi_spi/test-jc3248w535.esp32-s3-idf.yaml b/tests/components/mipi_spi/test-jc3248w535.esp32-s3-idf.yaml new file mode 100644 index 0000000000..02b8f78d58 --- /dev/null +++ b/tests/components/mipi_spi/test-jc3248w535.esp32-s3-idf.yaml @@ -0,0 +1,41 @@ +spi: + - id: quad_spi + type: quad + interface: spi3 + clk_pin: + number: 47 + data_pins: + - allow_other_uses: true + number: 40 + - allow_other_uses: true + number: 41 + - allow_other_uses: true + number: 42 + - allow_other_uses: true + number: 43 + - id: octal_spi + type: octal + interface: hardware + clk_pin: + number: 0 + data_pins: + - 36 + - 37 + - 38 + - 39 + - allow_other_uses: true + number: 40 + - allow_other_uses: true + number: 41 + - allow_other_uses: true + number: 42 + - allow_other_uses: true + number: 43 + - id: spi_id_3 + interface: any + clk_pin: 8 + mosi_pin: 9 + +display: + - platform: mipi_spi + model: JC3248W535 diff --git a/tests/components/mipi_spi/test-jc3636w518.esp32-s3-idf.yaml b/tests/components/mipi_spi/test-jc3636w518.esp32-s3-idf.yaml new file mode 100644 index 0000000000..147d4833ac --- /dev/null +++ b/tests/components/mipi_spi/test-jc3636w518.esp32-s3-idf.yaml @@ -0,0 +1,19 @@ +spi: + - id: quad_spi + type: quad + interface: spi3 + clk_pin: + number: 36 + data_pins: + - number: 40 + - number: 41 + - number: 42 + - number: 43 + - id: spi_id_3 + interface: any + clk_pin: 8 + mosi_pin: 9 + +display: + - platform: mipi_spi + model: JC3636W518 diff --git a/tests/components/mipi_spi/test-pico-restouch-lcd-35.esp32-s3-idf.yaml b/tests/components/mipi_spi/test-pico-restouch-lcd-35.esp32-s3-idf.yaml new file mode 100644 index 0000000000..8d96f31fd5 --- /dev/null +++ b/tests/components/mipi_spi/test-pico-restouch-lcd-35.esp32-s3-idf.yaml @@ -0,0 +1,9 @@ +spi: + - id: spi_id_3 + interface: any + clk_pin: 8 + mosi_pin: 9 + +display: + - platform: mipi_spi + model: Pico-ResTouch-LCD-3.5 diff --git a/tests/components/mipi_spi/test-s3box.esp32-s3-idf.yaml b/tests/components/mipi_spi/test-s3box.esp32-s3-idf.yaml new file mode 100644 index 0000000000..98f6955bf3 --- /dev/null +++ b/tests/components/mipi_spi/test-s3box.esp32-s3-idf.yaml @@ -0,0 +1,41 @@ +spi: + - id: quad_spi + type: quad + interface: spi3 + clk_pin: + number: 47 + data_pins: + - allow_other_uses: true + number: 40 + - allow_other_uses: true + number: 41 + - allow_other_uses: true + number: 42 + - allow_other_uses: true + number: 43 + - id: octal_spi + type: octal + interface: hardware + clk_pin: + number: 0 + data_pins: + - 36 + - 37 + - 38 + - 39 + - allow_other_uses: true + number: 40 + - allow_other_uses: true + number: 41 + - allow_other_uses: true + number: 42 + - allow_other_uses: true + number: 43 + - id: spi_id_3 + interface: any + clk_pin: 8 + mosi_pin: 9 + +display: + - platform: mipi_spi + model: S3BOX diff --git a/tests/components/mipi_spi/test-s3boxlite.esp32-s3-idf.yaml b/tests/components/mipi_spi/test-s3boxlite.esp32-s3-idf.yaml new file mode 100644 index 0000000000..11ad869d54 --- /dev/null +++ b/tests/components/mipi_spi/test-s3boxlite.esp32-s3-idf.yaml @@ -0,0 +1,41 @@ +spi: + - id: quad_spi + type: quad + interface: spi3 + clk_pin: + number: 47 + data_pins: + - allow_other_uses: true + number: 40 + - allow_other_uses: true + number: 41 + - allow_other_uses: true + number: 42 + - allow_other_uses: true + number: 43 + - id: octal_spi + type: octal + interface: hardware + clk_pin: + number: 0 + data_pins: + - 36 + - 37 + - 38 + - 39 + - allow_other_uses: true + number: 40 + - allow_other_uses: true + number: 41 + - allow_other_uses: true + number: 42 + - allow_other_uses: true + number: 43 + - id: spi_id_3 + interface: any + clk_pin: 8 + mosi_pin: 9 + +display: + - platform: mipi_spi + model: S3BOXLITE diff --git a/tests/components/mipi_spi/test-t-display-s3-amoled-plus.esp32-s3-idf.yaml b/tests/components/mipi_spi/test-t-display-s3-amoled-plus.esp32-s3-idf.yaml new file mode 100644 index 0000000000..dc328f950c --- /dev/null +++ b/tests/components/mipi_spi/test-t-display-s3-amoled-plus.esp32-s3-idf.yaml @@ -0,0 +1,9 @@ +spi: + - id: spi_id_3 + interface: any + clk_pin: 8 + mosi_pin: 9 + +display: + - platform: mipi_spi + model: T-DISPLAY-S3-AMOLED-PLUS diff --git a/tests/components/mipi_spi/test-t-display-s3-amoled.esp32-s3-idf.yaml b/tests/components/mipi_spi/test-t-display-s3-amoled.esp32-s3-idf.yaml new file mode 100644 index 0000000000..f0432270dc --- /dev/null +++ b/tests/components/mipi_spi/test-t-display-s3-amoled.esp32-s3-idf.yaml @@ -0,0 +1,15 @@ +spi: + - id: quad_spi + type: quad + interface: spi3 + clk_pin: + number: 47 + data_pins: + - number: 40 + - number: 41 + - number: 42 + - number: 43 + +display: + - platform: mipi_spi + model: T-DISPLAY-S3-AMOLED diff --git a/tests/components/mipi_spi/test-t-display-s3-pro.esp32-s3-idf.yaml b/tests/components/mipi_spi/test-t-display-s3-pro.esp32-s3-idf.yaml new file mode 100644 index 0000000000..5cda38e096 --- /dev/null +++ b/tests/components/mipi_spi/test-t-display-s3-pro.esp32-s3-idf.yaml @@ -0,0 +1,9 @@ +spi: + - id: spi_id_3 + interface: any + clk_pin: 8 + mosi_pin: 40 + +display: + - platform: mipi_spi + model: T-DISPLAY-S3-PRO diff --git a/tests/components/mipi_spi/test-t-display-s3.esp32-s3-idf.yaml b/tests/components/mipi_spi/test-t-display-s3.esp32-s3-idf.yaml new file mode 100644 index 0000000000..144bde8366 --- /dev/null +++ b/tests/components/mipi_spi/test-t-display-s3.esp32-s3-idf.yaml @@ -0,0 +1,37 @@ +spi: + - id: quad_spi + type: quad + interface: spi3 + clk_pin: + number: 47 + data_pins: + - allow_other_uses: true + number: 40 + - allow_other_uses: true + number: 41 + - allow_other_uses: true + number: 42 + - allow_other_uses: true + number: 43 + - id: octal_spi + type: octal + interface: hardware + clk_pin: + number: 0 + data_pins: + - 36 + - 37 + - 38 + - 39 + - allow_other_uses: true + number: 40 + - allow_other_uses: true + number: 41 + - allow_other_uses: true + number: 42 + - allow_other_uses: true + number: 43 + +display: + - platform: mipi_spi + model: T-DISPLAY-S3 diff --git a/tests/components/mipi_spi/test-t-display.esp32-s3-idf.yaml b/tests/components/mipi_spi/test-t-display.esp32-s3-idf.yaml new file mode 100644 index 0000000000..39339b5ae2 --- /dev/null +++ b/tests/components/mipi_spi/test-t-display.esp32-s3-idf.yaml @@ -0,0 +1,41 @@ +spi: + - id: quad_spi + type: quad + interface: spi3 + clk_pin: + number: 47 + data_pins: + - allow_other_uses: true + number: 40 + - allow_other_uses: true + number: 41 + - allow_other_uses: true + number: 42 + - allow_other_uses: true + number: 43 + - id: octal_spi + type: octal + interface: hardware + clk_pin: + number: 0 + data_pins: + - 36 + - 37 + - 38 + - 39 + - allow_other_uses: true + number: 40 + - allow_other_uses: true + number: 41 + - allow_other_uses: true + number: 42 + - allow_other_uses: true + number: 43 + - id: spi_id_3 + interface: any + clk_pin: 8 + mosi_pin: 9 + +display: + - platform: mipi_spi + model: T-DISPLAY diff --git a/tests/components/mipi_spi/test-t-embed.esp32-s3-idf.yaml b/tests/components/mipi_spi/test-t-embed.esp32-s3-idf.yaml new file mode 100644 index 0000000000..6c9edb25b3 --- /dev/null +++ b/tests/components/mipi_spi/test-t-embed.esp32-s3-idf.yaml @@ -0,0 +1,9 @@ +spi: + - id: spi_id_3 + interface: any + clk_pin: 8 + mosi_pin: 40 + +display: + - platform: mipi_spi + model: T-EMBED diff --git a/tests/components/mipi_spi/test-t4-s3.esp32-s3-idf.yaml b/tests/components/mipi_spi/test-t4-s3.esp32-s3-idf.yaml new file mode 100644 index 0000000000..46eaedb7cb --- /dev/null +++ b/tests/components/mipi_spi/test-t4-s3.esp32-s3-idf.yaml @@ -0,0 +1,41 @@ +spi: + - id: quad_spi + type: quad + interface: spi3 + clk_pin: + number: 47 + data_pins: + - allow_other_uses: true + number: 40 + - allow_other_uses: true + number: 41 + - allow_other_uses: true + number: 42 + - allow_other_uses: true + number: 43 + - id: octal_spi + type: octal + interface: hardware + clk_pin: + number: 0 + data_pins: + - 36 + - 37 + - 38 + - 39 + - allow_other_uses: true + number: 40 + - allow_other_uses: true + number: 41 + - allow_other_uses: true + number: 42 + - allow_other_uses: true + number: 43 + - id: spi_id_3 + interface: any + clk_pin: 8 + mosi_pin: 9 + +display: + - platform: mipi_spi + model: T4-S3 diff --git a/tests/components/mipi_spi/test-wt32-sc01-plus.esp32-s3-idf.yaml b/tests/components/mipi_spi/test-wt32-sc01-plus.esp32-s3-idf.yaml new file mode 100644 index 0000000000..3efb05ec89 --- /dev/null +++ b/tests/components/mipi_spi/test-wt32-sc01-plus.esp32-s3-idf.yaml @@ -0,0 +1,37 @@ +spi: + - id: quad_spi + type: quad + interface: spi3 + clk_pin: + number: 47 + data_pins: + - allow_other_uses: true + number: 40 + - allow_other_uses: true + number: 41 + - allow_other_uses: true + number: 42 + - allow_other_uses: true + number: 43 + - id: octal_spi + type: octal + interface: hardware + clk_pin: + number: 9 + data_pins: + - 36 + - 37 + - 38 + - 39 + - allow_other_uses: true + number: 40 + - allow_other_uses: true + number: 41 + - allow_other_uses: true + number: 42 + - allow_other_uses: true + number: 43 + +display: + - platform: mipi_spi + model: WT32-SC01-PLUS diff --git a/tests/components/mipi_spi/test.esp32-ard.yaml b/tests/components/mipi_spi/test.esp32-ard.yaml new file mode 100644 index 0000000000..a5ef77dabc --- /dev/null +++ b/tests/components/mipi_spi/test.esp32-ard.yaml @@ -0,0 +1,15 @@ +substitutions: + clk_pin: GPIO16 + mosi_pin: GPIO17 + miso_pin: GPIO15 + dc_pin: GPIO14 + cs_pin: GPIO13 + enable_pin: GPIO19 + reset_pin: GPIO20 + +display: + - platform: mipi_spi + model: LANBON-L8 + +packages: + display: !include common.yaml diff --git a/tests/components/mipi_spi/test.esp32-c3-ard.yaml b/tests/components/mipi_spi/test.esp32-c3-ard.yaml new file mode 100644 index 0000000000..c17748c569 --- /dev/null +++ b/tests/components/mipi_spi/test.esp32-c3-ard.yaml @@ -0,0 +1,10 @@ +substitutions: + clk_pin: GPIO6 + mosi_pin: GPIO7 + miso_pin: GPIO5 + dc_pin: GPIO21 + cs_pin: GPIO18 + enable_pin: GPIO19 + reset_pin: GPIO20 + +<<: !include common.yaml diff --git a/tests/components/mipi_spi/test.esp32-c3-idf.yaml b/tests/components/mipi_spi/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..c17748c569 --- /dev/null +++ b/tests/components/mipi_spi/test.esp32-c3-idf.yaml @@ -0,0 +1,10 @@ +substitutions: + clk_pin: GPIO6 + mosi_pin: GPIO7 + miso_pin: GPIO5 + dc_pin: GPIO21 + cs_pin: GPIO18 + enable_pin: GPIO19 + reset_pin: GPIO20 + +<<: !include common.yaml diff --git a/tests/components/mipi_spi/test.esp32-idf.yaml b/tests/components/mipi_spi/test.esp32-idf.yaml new file mode 100644 index 0000000000..653ccb4910 --- /dev/null +++ b/tests/components/mipi_spi/test.esp32-idf.yaml @@ -0,0 +1,15 @@ +substitutions: + clk_pin: GPIO16 + mosi_pin: GPIO17 + miso_pin: GPIO15 + dc_pin: GPIO21 + cs_pin: GPIO18 + enable_pin: GPIO19 + reset_pin: GPIO20 + +packages: + display: !include common.yaml + +display: + - platform: mipi_spi + model: m5core diff --git a/tests/components/mipi_spi/test.rp2040-ard.yaml b/tests/components/mipi_spi/test.rp2040-ard.yaml new file mode 100644 index 0000000000..5d7333853b --- /dev/null +++ b/tests/components/mipi_spi/test.rp2040-ard.yaml @@ -0,0 +1,10 @@ +substitutions: + clk_pin: GPIO2 + mosi_pin: GPIO3 + miso_pin: GPIO4 + dc_pin: GPIO14 + cs_pin: GPIO13 + enable_pin: GPIO19 + reset_pin: GPIO20 + +<<: !include common.yaml