mirror of
https://github.com/esphome/esphome.git
synced 2025-07-27 21:56:34 +00:00
[mipi_dsi] New display driver for P4 DSI (#9403)
Co-authored-by: J. Nick Koston <nick@koston.org> Co-authored-by: Keith Burzinski <kbx81x@gmail.com> Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Co-authored-by: Samuel Sieb <samuel-github@sieb.net> Co-authored-by: Adam Liddell <git@aliddell.com> Co-authored-by: DT-art1 <81360462+DT-art1@users.noreply.github.com> Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
This commit is contained in:
parent
b7ce8c116b
commit
b6e0188c42
@ -294,6 +294,7 @@ esphome/components/microphone/* @jesserockz @kahrendt
|
||||
esphome/components/mics_4514/* @jesserockz
|
||||
esphome/components/midea/* @dudanov
|
||||
esphome/components/midea_ir/* @dudanov
|
||||
esphome/components/mipi_dsi/* @clydebarrow
|
||||
esphome/components/mipi_spi/* @clydebarrow
|
||||
esphome/components/mitsubishi/* @RubyBailey
|
||||
esphome/components/mixer/speaker/* @kahrendt
|
||||
|
@ -230,7 +230,7 @@ class DriverChip:
|
||||
):
|
||||
name = name.upper()
|
||||
self.name = name
|
||||
self.initsequence = initsequence
|
||||
self.initsequence = initsequence or defaults.get("init_sequence")
|
||||
self.defaults = defaults
|
||||
DriverChip.models[name] = self
|
||||
|
||||
@ -347,7 +347,7 @@ class DriverChip:
|
||||
Pixel format, color order, and orientation will be set.
|
||||
Returns a tuple of the init sequence and the computed MADCTL value.
|
||||
"""
|
||||
sequence = list(self.initsequence)
|
||||
sequence = list(self.initsequence or ())
|
||||
custom_sequence = config.get(CONF_INIT_SEQUENCE, [])
|
||||
sequence.extend(custom_sequence)
|
||||
# Ensure each command is a tuple
|
||||
@ -356,6 +356,8 @@ class DriverChip:
|
||||
# Set pixel format if not already in the custom sequence
|
||||
pixel_mode = config[CONF_PIXEL_MODE]
|
||||
if not isinstance(pixel_mode, int):
|
||||
if not pixel_mode.endswith("bit"):
|
||||
pixel_mode = f"{pixel_mode}bit"
|
||||
pixel_mode = PIXEL_MODES[pixel_mode]
|
||||
sequence.append((PIXFMT, pixel_mode))
|
||||
|
||||
|
5
esphome/components/mipi_dsi/__init__.py
Normal file
5
esphome/components/mipi_dsi/__init__.py
Normal file
@ -0,0 +1,5 @@
|
||||
import esphome.codegen as cg
|
||||
|
||||
CODEOWNERS = ["@clydebarrow"]
|
||||
|
||||
mipi_dsi_ns = cg.esphome_ns.namespace("mipi_dsi")
|
232
esphome/components/mipi_dsi/display.py
Normal file
232
esphome/components/mipi_dsi/display.py
Normal file
@ -0,0 +1,232 @@
|
||||
import importlib
|
||||
import logging
|
||||
import pkgutil
|
||||
|
||||
from esphome import pins
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import display
|
||||
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_COLOR_DEPTH,
|
||||
CONF_HSYNC_BACK_PORCH,
|
||||
CONF_HSYNC_FRONT_PORCH,
|
||||
CONF_HSYNC_PULSE_WIDTH,
|
||||
CONF_PCLK_FREQUENCY,
|
||||
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_24BIT,
|
||||
DriverChip,
|
||||
dimension_schema,
|
||||
get_color_depth,
|
||||
map_sequence,
|
||||
power_of_two,
|
||||
requires_buffer,
|
||||
)
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_COLOR_ORDER,
|
||||
CONF_DIMENSIONS,
|
||||
CONF_ENABLE_PIN,
|
||||
CONF_ID,
|
||||
CONF_INIT_SEQUENCE,
|
||||
CONF_INVERT_COLORS,
|
||||
CONF_LAMBDA,
|
||||
CONF_MIRROR_X,
|
||||
CONF_MIRROR_Y,
|
||||
CONF_MODEL,
|
||||
CONF_RESET_PIN,
|
||||
CONF_ROTATION,
|
||||
CONF_SWAP_XY,
|
||||
CONF_TRANSFORM,
|
||||
CONF_WIDTH,
|
||||
)
|
||||
from esphome.final_validate import full_config
|
||||
|
||||
from . import mipi_dsi_ns, models
|
||||
|
||||
DEPENDENCIES = ["esp32"]
|
||||
DOMAIN = "mipi_dsi"
|
||||
|
||||
LOGGER = logging.getLogger(DOMAIN)
|
||||
|
||||
MIPI_DSI = mipi_dsi_ns.class_("MIPI_DSI", display.Display, cg.Component)
|
||||
ColorOrder = display.display_ns.enum("ColorMode")
|
||||
ColorBitness = display.display_ns.enum("ColorBitness")
|
||||
|
||||
CONF_LANE_BIT_RATE = "lane_bit_rate"
|
||||
CONF_LANES = "lanes"
|
||||
|
||||
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()
|
||||
|
||||
COLOR_DEPTHS = {
|
||||
16: ColorBitness.COLOR_BITNESS_565,
|
||||
24: ColorBitness.COLOR_BITNESS_888,
|
||||
}
|
||||
|
||||
|
||||
def model_schema(config):
|
||||
model = MODELS[config[CONF_MODEL].upper()]
|
||||
transform = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_MIRROR_X): cv.boolean,
|
||||
cv.Required(CONF_MIRROR_Y): cv.boolean,
|
||||
}
|
||||
)
|
||||
if model.get_default(CONF_SWAP_XY) != 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)
|
||||
)
|
||||
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_24BIT, "16", "24")
|
||||
schema = display.FULL_DISPLAY_SCHEMA.extend(
|
||||
{
|
||||
model.option(CONF_RESET_PIN, cv.UNDEFINED): pins.gpio_output_pin_schema,
|
||||
cv.GenerateID(): cv.declare_id(MIPI_DSI),
|
||||
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_COLOR_DEPTH, "16"): cv.one_of(
|
||||
*[str(d) for d in COLOR_DEPTHS],
|
||||
*[f"{d}bit" for d in COLOR_DEPTHS],
|
||||
lower=True,
|
||||
),
|
||||
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_LANES, 2): cv.int_range(1, 2),
|
||||
model.option(CONF_LANE_BIT_RATE, None): cv.All(
|
||||
cv.bps, cv.Range(min=100e6, max=3200e6)
|
||||
),
|
||||
iseqconf: cv.ensure_list(map_sequence),
|
||||
model.option(CONF_BYTE_ORDER, BYTE_ORDER_LITTLE): 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_,
|
||||
}
|
||||
)
|
||||
return cv.All(
|
||||
schema,
|
||||
only_on_variant(supported=[const.VARIANT_ESP32P4]),
|
||||
cv.only_with_esp_idf,
|
||||
)
|
||||
|
||||
|
||||
def _config_schema(config):
|
||||
config = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_MODEL): cv.one_of(*MODELS, upper=True),
|
||||
},
|
||||
extra=cv.ALLOW_EXTRA,
|
||||
)(config)
|
||||
return model_schema(config)(config)
|
||||
|
||||
|
||||
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
|
||||
return config
|
||||
|
||||
|
||||
CONFIG_SCHEMA = _config_schema
|
||||
FINAL_VALIDATE_SCHEMA = _final_validate
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
model = MODELS[config[CONF_MODEL].upper()]
|
||||
color_depth = COLOR_DEPTHS[get_color_depth(config)]
|
||||
pixel_mode = int(config[CONF_PIXEL_MODE].removesuffix("bit"))
|
||||
width, height, _offset_width, _offset_height = model.get_dimensions(config)
|
||||
var = cg.new_Pvariable(config[CONF_ID], width, height, color_depth, pixel_mode)
|
||||
|
||||
sequence, madctl = model.get_sequence(config)
|
||||
cg.add(var.set_model(config[CONF_MODEL]))
|
||||
cg.add(var.set_init_sequence(sequence))
|
||||
cg.add(var.set_madctl(madctl))
|
||||
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_frequency(int(config[CONF_PCLK_FREQUENCY] / 1e6)))
|
||||
cg.add(var.set_lanes(int(config[CONF_LANES])))
|
||||
cg.add(var.set_lane_bit_rate(int(config[CONF_LANE_BIT_RATE] / 1e6)))
|
||||
if reset_pin := config.get(CONF_RESET_PIN):
|
||||
reset = await cg.gpio_pin_expression(reset_pin)
|
||||
cg.add(var.set_reset_pin(reset))
|
||||
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 model.rotation_as_transform(config):
|
||||
config[CONF_ROTATION] = 0
|
||||
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_))
|
379
esphome/components/mipi_dsi/mipi_dsi.cpp
Normal file
379
esphome/components/mipi_dsi/mipi_dsi.cpp
Normal file
@ -0,0 +1,379 @@
|
||||
#ifdef USE_ESP32_VARIANT_ESP32P4
|
||||
#include <utility>
|
||||
#include "mipi_dsi.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace mipi_dsi {
|
||||
|
||||
static bool notify_refresh_ready(esp_lcd_panel_handle_t panel, esp_lcd_dpi_panel_event_data_t *edata, void *user_ctx) {
|
||||
auto *sem = static_cast<SemaphoreHandle_t *>(user_ctx);
|
||||
BaseType_t need_yield = pdFALSE;
|
||||
xSemaphoreGiveFromISR(sem, &need_yield);
|
||||
return (need_yield == pdTRUE);
|
||||
}
|
||||
void MIPI_DSI::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Running Setup");
|
||||
|
||||
if (!this->enable_pins_.empty()) {
|
||||
for (auto *pin : this->enable_pins_) {
|
||||
pin->setup();
|
||||
pin->digital_write(true);
|
||||
}
|
||||
delay(10);
|
||||
}
|
||||
|
||||
esp_lcd_dsi_bus_config_t bus_config = {
|
||||
.bus_id = 0, // index from 0, specify the DSI host to use
|
||||
.num_data_lanes =
|
||||
this->lanes_, // Number of data lanes to use, can't set a value that exceeds the chip's capability
|
||||
.phy_clk_src = MIPI_DSI_PHY_CLK_SRC_DEFAULT, // Clock source for the DPHY
|
||||
.lane_bit_rate_mbps = this->lane_bit_rate_, // Bit rate of the data lanes, in Mbps
|
||||
};
|
||||
auto err = esp_lcd_new_dsi_bus(&bus_config, &this->bus_handle_);
|
||||
if (err != ESP_OK) {
|
||||
this->smark_failed("lcd_new_dsi_bus failed", err);
|
||||
return;
|
||||
}
|
||||
esp_lcd_dbi_io_config_t dbi_config = {
|
||||
.virtual_channel = 0,
|
||||
.lcd_cmd_bits = 8, // according to the LCD spec
|
||||
.lcd_param_bits = 8, // according to the LCD spec
|
||||
};
|
||||
err = esp_lcd_new_panel_io_dbi(this->bus_handle_, &dbi_config, &this->io_handle_);
|
||||
if (err != ESP_OK) {
|
||||
this->smark_failed("new_panel_io_dbi failed", err);
|
||||
return;
|
||||
}
|
||||
auto pixel_format = LCD_COLOR_PIXEL_FORMAT_RGB565;
|
||||
if (this->color_depth_ == display::COLOR_BITNESS_888) {
|
||||
pixel_format = LCD_COLOR_PIXEL_FORMAT_RGB888;
|
||||
}
|
||||
esp_lcd_dpi_panel_config_t dpi_config = {.virtual_channel = 0,
|
||||
.dpi_clk_src = MIPI_DSI_DPI_CLK_SRC_DEFAULT,
|
||||
.dpi_clock_freq_mhz = this->pclk_frequency_,
|
||||
.pixel_format = pixel_format,
|
||||
.num_fbs = 1, // number of frame buffers to allocate
|
||||
.video_timing =
|
||||
{
|
||||
.h_size = this->width_,
|
||||
.v_size = this->height_,
|
||||
.hsync_pulse_width = this->hsync_pulse_width_,
|
||||
.hsync_back_porch = this->hsync_back_porch_,
|
||||
.hsync_front_porch = this->hsync_front_porch_,
|
||||
.vsync_pulse_width = this->vsync_pulse_width_,
|
||||
.vsync_back_porch = this->vsync_back_porch_,
|
||||
.vsync_front_porch = this->vsync_front_porch_,
|
||||
},
|
||||
.flags = {
|
||||
.use_dma2d = true,
|
||||
}};
|
||||
err = esp_lcd_new_panel_dpi(this->bus_handle_, &dpi_config, &this->handle_);
|
||||
if (err != ESP_OK) {
|
||||
this->smark_failed("esp_lcd_new_panel_dpi failed", err);
|
||||
return;
|
||||
}
|
||||
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);
|
||||
} else {
|
||||
esp_lcd_panel_io_tx_param(this->io_handle_, SW_RESET_CMD, nullptr, 0);
|
||||
}
|
||||
// need to know when the display is ready for SLPOUT command - will be 120ms after reset
|
||||
auto when = millis() + 120;
|
||||
err = esp_lcd_panel_init(this->handle_);
|
||||
if (err != ESP_OK) {
|
||||
this->smark_failed("esp_lcd_init failed", err);
|
||||
return;
|
||||
}
|
||||
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) {
|
||||
// are we ready, boots?
|
||||
int duration = when - millis();
|
||||
if (duration > 0) {
|
||||
delay(duration);
|
||||
}
|
||||
}
|
||||
const auto *ptr = vec.data() + index;
|
||||
ESP_LOGVV(TAG, "Command %02X, length %d, byte(s) %s", cmd, num_args,
|
||||
format_hex_pretty(ptr, num_args, '.', false).c_str());
|
||||
err = esp_lcd_panel_io_tx_param(this->io_handle_, cmd, ptr, num_args);
|
||||
if (err != ESP_OK) {
|
||||
this->smark_failed("lcd_panel_io_tx_param failed", err);
|
||||
return;
|
||||
}
|
||||
index += num_args;
|
||||
if (cmd == SLEEP_OUT)
|
||||
delay(10);
|
||||
}
|
||||
}
|
||||
this->io_lock_ = xSemaphoreCreateBinary();
|
||||
esp_lcd_dpi_panel_event_callbacks_t cbs = {
|
||||
.on_color_trans_done = notify_refresh_ready,
|
||||
};
|
||||
|
||||
err = (esp_lcd_dpi_panel_register_event_callbacks(this->handle_, &cbs, this->io_lock_));
|
||||
if (err != ESP_OK) {
|
||||
this->smark_failed("Failed to register callbacks", err);
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGCONFIG(TAG, "MIPI DSI setup complete");
|
||||
}
|
||||
|
||||
void MIPI_DSI::update() {
|
||||
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, 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 MIPI_DSI::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)
|
||||
return;
|
||||
// if color mapping is required, pass the buck.
|
||||
// note that endianness is not considered here - it is assumed to match!
|
||||
if (bitness != this->color_depth_) {
|
||||
display::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, ptr, x_offset, y_offset, x_pad);
|
||||
}
|
||||
|
||||
void MIPI_DSI::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 bytes_per_pixel = 3 - this->color_depth_;
|
||||
auto stride = (x_offset + w + x_pad) * bytes_per_pixel;
|
||||
ptr += y_offset * stride + x_offset * bytes_per_pixel; // 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);
|
||||
xSemaphoreTake(this->io_lock_, portMAX_DELAY);
|
||||
|
||||
} 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
|
||||
xSemaphoreTake(this->io_lock_, portMAX_DELAY);
|
||||
}
|
||||
}
|
||||
if (err != ESP_OK)
|
||||
ESP_LOGE(TAG, "lcd_lcd_panel_draw_bitmap failed: %s", esp_err_to_name(err));
|
||||
}
|
||||
|
||||
bool MIPI_DSI::check_buffer_() {
|
||||
if (this->is_failed())
|
||||
return false;
|
||||
if (this->buffer_ != nullptr)
|
||||
return true;
|
||||
// this is dependent on the enum values.
|
||||
auto bytes_per_pixel = 3 - this->color_depth_;
|
||||
RAMAllocator<uint8_t> allocator;
|
||||
this->buffer_ = allocator.allocate(this->height_ * this->width_ * bytes_per_pixel);
|
||||
if (this->buffer_ == nullptr) {
|
||||
this->mark_failed("Could not allocate buffer for display!");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void MIPI_DSI::draw_pixel_at(int x, int y, Color color) {
|
||||
if (!this->get_clipping().inside(x, y))
|
||||
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;
|
||||
}
|
||||
auto pixel = convert_big_endian(display::ColorUtil::color_to_565(color));
|
||||
if (!this->check_buffer_())
|
||||
return;
|
||||
size_t pos = (y * this->width_) + x;
|
||||
switch (this->color_depth_) {
|
||||
case display::COLOR_BITNESS_565: {
|
||||
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
|
||||
if (ptr_16[pos] == new_color)
|
||||
return;
|
||||
ptr_16[pos] = new_color;
|
||||
break;
|
||||
}
|
||||
case display::COLOR_BITNESS_888:
|
||||
if (this->color_mode_ == display::COLOR_ORDER_BGR) {
|
||||
this->buffer_[pos * 3] = color.b;
|
||||
this->buffer_[pos * 3 + 1] = color.g;
|
||||
this->buffer_[pos * 3 + 2] = color.r;
|
||||
} else {
|
||||
this->buffer_[pos * 3] = color.r;
|
||||
this->buffer_[pos * 3 + 1] = color.g;
|
||||
this->buffer_[pos * 3 + 2] = color.b;
|
||||
}
|
||||
break;
|
||||
case display::COLOR_BITNESS_332:
|
||||
break;
|
||||
}
|
||||
// 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 MIPI_DSI::fill(Color color) {
|
||||
if (!this->check_buffer_())
|
||||
return;
|
||||
switch (this->color_depth_) {
|
||||
case display::COLOR_BITNESS_565: {
|
||||
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);
|
||||
break;
|
||||
}
|
||||
|
||||
case display::COLOR_BITNESS_888:
|
||||
if (this->color_mode_ == display::COLOR_ORDER_BGR) {
|
||||
for (size_t i = 0; i != this->width_ * this->height_; i++) {
|
||||
this->buffer_[i * 3 + 0] = color.b;
|
||||
this->buffer_[i * 3 + 1] = color.g;
|
||||
this->buffer_[i * 3 + 2] = color.r;
|
||||
}
|
||||
} else {
|
||||
for (size_t i = 0; i != this->width_ * this->height_; i++) {
|
||||
this->buffer_[i * 3 + 0] = color.r;
|
||||
this->buffer_[i * 3 + 1] = color.g;
|
||||
this->buffer_[i * 3 + 2] = color.b;
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int MIPI_DSI::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 MIPI_DSI::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 const uint8_t PIXEL_MODES[] = {0, 16, 18, 24};
|
||||
|
||||
void MIPI_DSI::dump_config() {
|
||||
ESP_LOGCONFIG(TAG,
|
||||
"MIPI_DSI RGB LCD"
|
||||
"\n Model: %s"
|
||||
"\n Width: %u"
|
||||
"\n Height: %u"
|
||||
"\n Mirror X: %s"
|
||||
"\n Mirror Y: %s"
|
||||
"\n Swap X/Y: %s"
|
||||
"\n Rotation: %d degrees"
|
||||
"\n DSI Lanes: %u"
|
||||
"\n Lane Bit Rate: %uMbps"
|
||||
"\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 Buffer Color Depth: %d bit"
|
||||
"\n Display Pixel Mode: %d bit"
|
||||
"\n Color Order: %s"
|
||||
"\n Invert Colors: %s"
|
||||
"\n Pixel Clock: %dMHz",
|
||||
this->model_, this->width_, this->height_, YESNO(this->madctl_ & (MADCTL_XFLIP | MADCTL_MX)),
|
||||
YESNO(this->madctl_ & (MADCTL_YFLIP | MADCTL_MY)), YESNO(this->madctl_ & MADCTL_MV), this->rotation_,
|
||||
this->lanes_, this->lane_bit_rate_, this->hsync_pulse_width_, this->hsync_back_porch_,
|
||||
this->hsync_front_porch_, this->vsync_pulse_width_, this->vsync_back_porch_, this->vsync_front_porch_,
|
||||
(3 - this->color_depth_) * 8, this->pixel_mode_, this->madctl_ & MADCTL_BGR ? "BGR" : "RGB",
|
||||
YESNO(this->invert_colors_), this->pclk_frequency_);
|
||||
LOG_PIN(" Reset Pin ", this->reset_pin_);
|
||||
}
|
||||
} // namespace mipi_dsi
|
||||
} // namespace esphome
|
||||
#endif // USE_ESP32_VARIANT_ESP32P4
|
123
esphome/components/mipi_dsi/mipi_dsi.h
Normal file
123
esphome/components/mipi_dsi/mipi_dsi.h
Normal file
@ -0,0 +1,123 @@
|
||||
//
|
||||
// Created by Clyde Stubbs on 29/10/2023.
|
||||
//
|
||||
#pragma once
|
||||
|
||||
// only applicable on ESP32-P4
|
||||
#ifdef USE_ESP32_VARIANT_ESP32P4
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/gpio.h"
|
||||
|
||||
#include "esphome/components/display/display.h"
|
||||
#include "esp_lcd_panel_ops.h"
|
||||
#include "esp_lcd_panel_io.h"
|
||||
|
||||
#include "esp_lcd_mipi_dsi.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace mipi_dsi {
|
||||
|
||||
constexpr static const char *const TAG = "display.mipi_dsi";
|
||||
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 DELAY_FLAG = 0xFF;
|
||||
const uint8_t MADCTL_BGR = 0x08;
|
||||
const uint8_t MADCTL_MX = 0x40;
|
||||
const uint8_t MADCTL_MY = 0x80;
|
||||
const uint8_t MADCTL_MV = 0x20; // row/column swap
|
||||
const uint8_t MADCTL_XFLIP = 0x02; // Mirror the display horizontally
|
||||
const uint8_t MADCTL_YFLIP = 0x01; // Mirror the display vertically
|
||||
|
||||
class MIPI_DSI : public display::Display {
|
||||
public:
|
||||
MIPI_DSI(size_t width, size_t height, display::ColorBitness color_depth, uint8_t pixel_mode)
|
||||
: width_(width), height_(height), color_depth_(color_depth), pixel_mode_(pixel_mode) {}
|
||||
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; }
|
||||
display::DisplayType get_display_type() override { return display::DisplayType::DISPLAY_TYPE_COLOR; }
|
||||
|
||||
void set_reset_pin(GPIOPin *reset_pin) { this->reset_pin_ = reset_pin; }
|
||||
void set_enable_pins(std::vector<GPIOPin *> enable_pins) { this->enable_pins_ = std::move(enable_pins); }
|
||||
void set_pclk_frequency(uint32_t pclk_frequency) { this->pclk_frequency_ = pclk_frequency; }
|
||||
int get_width_internal() override { return this->width_; }
|
||||
int get_height_internal() override { return this->height_; }
|
||||
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_init_sequence(const std::vector<uint8_t> &init_sequence) { this->init_sequence_ = init_sequence; }
|
||||
void set_model(const char *model) { this->model_ = model; }
|
||||
void set_lane_bit_rate(uint16_t lane_bit_rate) { this->lane_bit_rate_ = lane_bit_rate; }
|
||||
void set_lanes(uint8_t lanes) { this->lanes_ = lanes; }
|
||||
void set_madctl(uint8_t madctl) { this->madctl_ = madctl; }
|
||||
|
||||
void smark_failed(const char *message, esp_err_t err) {
|
||||
auto str = str_sprintf("Setup failed: %s: %s", message, esp_err_to_name(err));
|
||||
this->mark_failed(str.c_str());
|
||||
}
|
||||
|
||||
void update() override;
|
||||
|
||||
void setup() 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 draw_pixel_at(int x, int y, Color color) override;
|
||||
void fill(Color color) override;
|
||||
int get_width() override;
|
||||
int get_height() override;
|
||||
|
||||
void dump_config() override;
|
||||
|
||||
protected:
|
||||
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_();
|
||||
GPIOPin *reset_pin_{nullptr};
|
||||
std::vector<GPIOPin *> enable_pins_{};
|
||||
size_t width_{};
|
||||
size_t height_{};
|
||||
uint8_t madctl_{};
|
||||
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;
|
||||
const char *model_{"Unknown"};
|
||||
std::vector<uint8_t> init_sequence_{};
|
||||
uint16_t pclk_frequency_ = 16; // in MHz
|
||||
uint16_t lane_bit_rate_{1500}; // in Mbps
|
||||
uint8_t lanes_{2}; // 1, 2, 3 or 4 lanes
|
||||
|
||||
bool invert_colors_{};
|
||||
display::ColorOrder color_mode_{display::COLOR_ORDER_BGR};
|
||||
display::ColorBitness color_depth_;
|
||||
uint8_t pixel_mode_{};
|
||||
|
||||
esp_lcd_panel_handle_t handle_{};
|
||||
esp_lcd_dsi_bus_handle_t bus_handle_{};
|
||||
esp_lcd_panel_io_handle_t io_handle_{};
|
||||
SemaphoreHandle_t io_lock_{};
|
||||
uint8_t *buffer_{nullptr};
|
||||
uint16_t x_low_{1};
|
||||
uint16_t y_low_{1};
|
||||
uint16_t x_high_{0};
|
||||
uint16_t y_high_{0};
|
||||
};
|
||||
|
||||
} // namespace mipi_dsi
|
||||
} // namespace esphome
|
||||
#endif
|
0
esphome/components/mipi_dsi/models/__init__.py
Normal file
0
esphome/components/mipi_dsi/models/__init__.py
Normal file
38
esphome/components/mipi_dsi/models/guition.py
Normal file
38
esphome/components/mipi_dsi/models/guition.py
Normal file
@ -0,0 +1,38 @@
|
||||
from esphome.components.mipi import DriverChip
|
||||
import esphome.config_validation as cv
|
||||
|
||||
# fmt: off
|
||||
DriverChip(
|
||||
"JC1060P470",
|
||||
width=1024,
|
||||
height=600,
|
||||
hsync_back_porch=160,
|
||||
hsync_pulse_width=40,
|
||||
hsync_front_porch=160,
|
||||
vsync_back_porch=23,
|
||||
vsync_pulse_width=10,
|
||||
vsync_front_porch=12,
|
||||
pclk_frequency="54MHz",
|
||||
lane_bit_rate="750Mbps",
|
||||
swap_xy=cv.UNDEFINED,
|
||||
color_order="RGB",
|
||||
reset_pin=27,
|
||||
initsequence=[
|
||||
(0x30, 0x00), (0xF7, 0x49, 0x61, 0x02, 0x00), (0x30, 0x01), (0x04, 0x0C), (0x05, 0x00), (0x06, 0x00),
|
||||
(0x0B, 0x11), (0x17, 0x00), (0x20, 0x04), (0x1F, 0x05), (0x23, 0x00), (0x25, 0x19), (0x28, 0x18), (0x29, 0x04), (0x2A, 0x01),
|
||||
(0x2B, 0x04), (0x2C, 0x01), (0x30, 0x02), (0x01, 0x22), (0x03, 0x12), (0x04, 0x00), (0x05, 0x64), (0x0A, 0x08),
|
||||
(0x0B, 0x0A, 0x1A, 0x0B, 0x0D, 0x0D, 0x11, 0x10, 0x06, 0x08, 0x1F, 0x1D),
|
||||
(0x0C, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D),
|
||||
(0x0D, 0x16, 0x1B, 0x0B, 0x0D, 0x0D, 0x11, 0x10, 0x07, 0x09, 0x1E, 0x1C),
|
||||
(0x0E, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D),
|
||||
(0x0F, 0x16, 0x1B, 0x0D, 0x0B, 0x0D, 0x11, 0x10, 0x1C, 0x1E, 0x09, 0x07),
|
||||
(0x10, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D),
|
||||
(0x11, 0x0A, 0x1A, 0x0D, 0x0B, 0x0D, 0x11, 0x10, 0x1D, 0x1F, 0x08, 0x06),
|
||||
(0x12, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D),
|
||||
(0x14, 0x00, 0x00, 0x11, 0x11), (0x18, 0x99), (0x30, 0x06),
|
||||
(0x12, 0x36, 0x2C, 0x2E, 0x3C, 0x38, 0x35, 0x35, 0x32, 0x2E, 0x1D, 0x2B, 0x21, 0x16, 0x29,),
|
||||
(0x13, 0x36, 0x2C, 0x2E, 0x3C, 0x38, 0x35, 0x35, 0x32, 0x2E, 0x1D, 0x2B, 0x21, 0x16, 0x29,),
|
||||
(0x30, 0x0A), (0x02, 0x4F), (0x0B, 0x40), (0x12, 0x3E), (0x13, 0x78), (0x30, 0x0D), (0x0D, 0x04),
|
||||
(0x10, 0x0C), (0x11, 0x0C), (0x12, 0x0C), (0x13, 0x0C), (0x30, 0x00),
|
||||
],
|
||||
)
|
57
esphome/components/mipi_dsi/models/m5stack.py
Normal file
57
esphome/components/mipi_dsi/models/m5stack.py
Normal file
@ -0,0 +1,57 @@
|
||||
from esphome.components.mipi import DriverChip
|
||||
import esphome.config_validation as cv
|
||||
|
||||
# fmt: off
|
||||
DriverChip(
|
||||
"M5STACK-TAB5",
|
||||
height=1280,
|
||||
width=720,
|
||||
hsync_back_porch=140,
|
||||
hsync_pulse_width=40,
|
||||
hsync_front_porch=40,
|
||||
vsync_back_porch=20,
|
||||
vsync_pulse_width=4,
|
||||
vsync_front_porch=20,
|
||||
pclk_frequency="60MHz",
|
||||
lane_bit_rate="730Mbps",
|
||||
swap_xy=cv.UNDEFINED,
|
||||
color_order="RGB",
|
||||
initsequence=[
|
||||
(0xFF, 0x98, 0x81, 0x01), # Select Page 1
|
||||
(0xB7, 0x03), # Pad control - 2 lane
|
||||
(0xFF, 0x98, 0x81, 0x00), # Select Page 0
|
||||
# CMD_Page 3
|
||||
(0xFF, 0x98, 0x81, 0x03), # Select Page 3
|
||||
(0x01, 0x00), (0x02, 0x00), (0x03, 0x73), (0x04, 0x00), (0x05, 0x00), (0x06, 0x08), (0x07, 0x00), (0x08, 0x00),
|
||||
(0x09, 0x1B), (0x0A, 0x01), (0x0B, 0x01), (0x0C, 0x0D), (0x0D, 0x01), (0x0E, 0x01), (0x0F, 0x26), (0x10, 0x26),
|
||||
(0x11, 0x00), (0x12, 0x00), (0x13, 0x02), (0x14, 0x00), (0x15, 0x00), (0x16, 0x00), (0x17, 0x00), (0x18, 0x00),
|
||||
(0x19, 0x00), (0x1A, 0x00), (0x1B, 0x00), (0x1C, 0x00), (0x1D, 0x00), (0x1E, 0x40), (0x1F, 0x00), (0x20, 0x06),
|
||||
(0x21, 0x01), (0x22, 0x00), (0x23, 0x00), (0x24, 0x00), (0x25, 0x00), (0x26, 0x00), (0x27, 0x00), (0x28, 0x33),
|
||||
(0x29, 0x03), (0x2A, 0x00), (0x2B, 0x00), (0x2C, 0x00), (0x2D, 0x00), (0x2E, 0x00), (0x2F, 0x00), (0x30, 0x00),
|
||||
(0x31, 0x00), (0x32, 0x00), (0x33, 0x00), (0x34, 0x00), (0x35, 0x00), (0x36, 0x00), (0x37, 0x00), (0x38, 0x00),
|
||||
(0x39, 0x00), (0x3A, 0x00), (0x3B, 0x00), (0x3C, 0x00), (0x3D, 0x00), (0x3E, 0x00), (0x3F, 0x00), (0x40, 0x00),
|
||||
(0x41, 0x00), (0x42, 0x00), (0x43, 0x00), (0x44, 0x00), (0x50, 0x01), (0x51, 0x23), (0x52, 0x45), (0x53, 0x67),
|
||||
(0x54, 0x89), (0x55, 0xAB), (0x56, 0x01), (0x57, 0x23), (0x58, 0x45), (0x59, 0x67), (0x5A, 0x89), (0x5B, 0xAB),
|
||||
(0x5C, 0xCD), (0x5D, 0xEF), (0x5E, 0x11), (0x5F, 0x02), (0x60, 0x00), (0x61, 0x07), (0x62, 0x06), (0x63, 0x0E),
|
||||
(0x64, 0x0F), (0x65, 0x0C), (0x66, 0x0D), (0x67, 0x02), (0x68, 0x02), (0x69, 0x02), (0x6A, 0x02), (0x6B, 0x02),
|
||||
(0x6C, 0x02), (0x6D, 0x02), (0x6E, 0x02), (0x6F, 0x02), (0x70, 0x02), (0x71, 0x02), (0x72, 0x02), (0x73, 0x05),
|
||||
(0x74, 0x01), (0x75, 0x02), (0x76, 0x00), (0x77, 0x07), (0x78, 0x06), (0x79, 0x0E), (0x7A, 0x0F), (0x7B, 0x0C),
|
||||
(0x7C, 0x0D), (0x7D, 0x02), (0x7E, 0x02), (0x7F, 0x02), (0x80, 0x02), (0x81, 0x02), (0x82, 0x02), (0x83, 0x02),
|
||||
(0x84, 0x02), (0x85, 0x02), (0x86, 0x02), (0x87, 0x02), (0x88, 0x02), (0x89, 0x05), (0x8A, 0x01),
|
||||
(0xFF, 0x98, 0x81, 0x04), # Select Page 4
|
||||
(0x38, 0x01), (0x39, 0x00), (0x6C, 0x15), (0x6E, 0x1A), (0x6F, 0x25), (0x3A, 0xA4), (0x8D, 0x20), (0x87, 0xBA), (0x3B, 0x98),
|
||||
(0xFF, 0x98, 0x81, 0x01), # Select Page 1
|
||||
(0x22, 0x0A), (0x31, 0x00), (0x50, 0x6B), (0x51, 0x66), (0x53, 0x73), (0x55, 0x8B), (0x60, 0x1B), (0x61, 0x01), (0x62, 0x0C), (0x63, 0x00),
|
||||
# Gamma P
|
||||
(0xA0, 0x00), (0xA1, 0x15), (0xA2, 0x1F), (0xA3, 0x13), (0xA4, 0x11), (0xA5, 0x21), (0xA6, 0x17), (0xA7, 0x1B),
|
||||
(0xA8, 0x6B), (0xA9, 0x1E), (0xAA, 0x2B), (0xAB, 0x5D), (0xAC, 0x19), (0xAD, 0x14), (0xAE, 0x4B), (0xAF, 0x1D),
|
||||
(0xB0, 0x27), (0xB1, 0x49), (0xB2, 0x5D), (0xB3, 0x39),
|
||||
# Gamma N
|
||||
(0xC0, 0x00), (0xC1, 0x01), (0xC2, 0x0C), (0xC3, 0x11), (0xC4, 0x15), (0xC5, 0x28), (0xC6, 0x1B), (0xC7, 0x1C),
|
||||
(0xC8, 0x62), (0xC9, 0x1C), (0xCA, 0x29), (0xCB, 0x60), (0xCC, 0x16), (0xCD, 0x17), (0xCE, 0x4A), (0xCF, 0x23),
|
||||
(0xD0, 0x24), (0xD1, 0x4F), (0xD2, 0x5F), (0xD3, 0x39),
|
||||
# CMD_Page 0
|
||||
(0xFF, 0x98, 0x81, 0x00), # Select Page 0
|
||||
(0x35,), (0xFE,),
|
||||
],
|
||||
)
|
105
esphome/components/mipi_dsi/models/waveshare.py
Normal file
105
esphome/components/mipi_dsi/models/waveshare.py
Normal file
@ -0,0 +1,105 @@
|
||||
from esphome.components.mipi import DriverChip
|
||||
import esphome.config_validation as cv
|
||||
|
||||
# fmt: off
|
||||
DriverChip(
|
||||
"WAVESHARE-P4-NANO-10.1",
|
||||
height=1280,
|
||||
width=800,
|
||||
hsync_back_porch=20,
|
||||
hsync_pulse_width=20,
|
||||
hsync_front_porch=40,
|
||||
vsync_back_porch=12,
|
||||
vsync_pulse_width=4,
|
||||
vsync_front_porch=30,
|
||||
pclk_frequency="80MHz",
|
||||
lane_bit_rate="1.5Gbps",
|
||||
swap_xy=cv.UNDEFINED,
|
||||
color_order="RGB",
|
||||
initsequence=[
|
||||
(0xE0, 0x00), # select userpage
|
||||
(0xE1, 0x93), (0xE2, 0x65), (0xE3, 0xF8),
|
||||
(0x80, 0x01), # Select number of lanes (2)
|
||||
(0xE0, 0x01), # select page 1
|
||||
(0x00, 0x00), (0x01, 0x38), (0x03, 0x10), (0x04, 0x38), (0x0C, 0x74), (0x17, 0x00), (0x18, 0xAF), (0x19, 0x00),
|
||||
(0x1A, 0x00), (0x1B, 0xAF), (0x1C, 0x00), (0x35, 0x26), (0x37, 0x09), (0x38, 0x04), (0x39, 0x00), (0x3C, 0x78),
|
||||
(0x3D, 0xFF), (0x3E, 0xFF), (0x3F, 0x7F), (0x40, 0x06), (0x41, 0xA0), (0x42, 0x81), (0x43, 0x1E), (0x44, 0x0D),
|
||||
(0x45, 0x28), (0x55, 0x02), (0x57, 0x69), (0x59, 0x0A), (0x5A, 0x2A), (0x5B, 0x17), (0x5D, 0x7F), (0x5E, 0x6A),
|
||||
(0x5F, 0x5B), (0x60, 0x4F), (0x61, 0x4A), (0x62, 0x3D), (0x63, 0x41), (0x64, 0x2A), (0x65, 0x44), (0x66, 0x43),
|
||||
(0x67, 0x44), (0x68, 0x62), (0x69, 0x52), (0x6A, 0x59), (0x6B, 0x4C), (0x6C, 0x48), (0x6D, 0x3A), (0x6E, 0x26),
|
||||
(0x6F, 0x00), (0x70, 0x7F), (0x71, 0x6A), (0x72, 0x5B), (0x73, 0x4F), (0x74, 0x4A), (0x75, 0x3D), (0x76, 0x41),
|
||||
(0x77, 0x2A), (0x78, 0x44), (0x79, 0x43), (0x7A, 0x44), (0x7B, 0x62), (0x7C, 0x52), (0x7D, 0x59), (0x7E, 0x4C),
|
||||
(0x7F, 0x48), (0x80, 0x3A), (0x81, 0x26), (0x82, 0x00),
|
||||
(0xE0, 0x02), # select page 2
|
||||
(0x00, 0x42), (0x01, 0x42), (0x02, 0x40), (0x03, 0x40), (0x04, 0x5E), (0x05, 0x5E), (0x06, 0x5F), (0x07, 0x5F),
|
||||
(0x08, 0x5F), (0x09, 0x57), (0x0A, 0x57), (0x0B, 0x77), (0x0C, 0x77), (0x0D, 0x47), (0x0E, 0x47), (0x0F, 0x45),
|
||||
(0x10, 0x45), (0x11, 0x4B), (0x12, 0x4B), (0x13, 0x49), (0x14, 0x49), (0x15, 0x5F), (0x16, 0x41), (0x17, 0x41),
|
||||
(0x18, 0x40), (0x19, 0x40), (0x1A, 0x5E), (0x1B, 0x5E), (0x1C, 0x5F), (0x1D, 0x5F), (0x1E, 0x5F), (0x1F, 0x57),
|
||||
(0x20, 0x57), (0x21, 0x77), (0x22, 0x77), (0x23, 0x46), (0x24, 0x46), (0x25, 0x44), (0x26, 0x44), (0x27, 0x4A),
|
||||
(0x28, 0x4A), (0x29, 0x48), (0x2A, 0x48), (0x2B, 0x5F), (0x2C, 0x01), (0x2D, 0x01), (0x2E, 0x00), (0x2F, 0x00),
|
||||
(0x30, 0x1F), (0x31, 0x1F), (0x32, 0x1E), (0x33, 0x1E), (0x34, 0x1F), (0x35, 0x17), (0x36, 0x17), (0x37, 0x37),
|
||||
(0x38, 0x37), (0x39, 0x08), (0x3A, 0x08), (0x3B, 0x0A), (0x3C, 0x0A), (0x3D, 0x04), (0x3E, 0x04), (0x3F, 0x06),
|
||||
(0x40, 0x06), (0x41, 0x1F), (0x42, 0x02), (0x43, 0x02), (0x44, 0x00), (0x45, 0x00), (0x46, 0x1F), (0x47, 0x1F),
|
||||
(0x48, 0x1E), (0x49, 0x1E), (0x4A, 0x1F), (0x4B, 0x17), (0x4C, 0x17), (0x4D, 0x37), (0x4E, 0x37), (0x4F, 0x09),
|
||||
(0x50, 0x09), (0x51, 0x0B), (0x52, 0x0B), (0x53, 0x05), (0x54, 0x05), (0x55, 0x07), (0x56, 0x07), (0x57, 0x1F),
|
||||
(0x58, 0x40), (0x5B, 0x30), (0x5C, 0x00), (0x5D, 0x34), (0x5E, 0x05), (0x5F, 0x02), (0x63, 0x00), (0x64, 0x6A),
|
||||
(0x67, 0x73), (0x68, 0x07), (0x69, 0x08), (0x6A, 0x6A), (0x6B, 0x08), (0x6C, 0x00), (0x6D, 0x00), (0x6E, 0x00),
|
||||
(0x6F, 0x88), (0x75, 0xFF), (0x77, 0xDD), (0x78, 0x2C), (0x79, 0x15), (0x7A, 0x17), (0x7D, 0x14), (0x7E, 0x82),
|
||||
(0xE0, 0x04), # select page 4
|
||||
(0x00, 0x0E), (0x02, 0xB3), (0x09, 0x61), (0x0E, 0x48), (0x37, 0x58), (0x2B, 0x0F),
|
||||
(0xE0, 0x00), # Select userpage
|
||||
(0xE6, 0x02), (0xE7, 0x0C),
|
||||
],
|
||||
)
|
||||
|
||||
DriverChip(
|
||||
"WAVESHARE-P4-86-PANEL",
|
||||
height=720,
|
||||
width=720,
|
||||
hsync_back_porch=80,
|
||||
hsync_pulse_width=20,
|
||||
hsync_front_porch=80,
|
||||
vsync_back_porch=12,
|
||||
vsync_pulse_width=4,
|
||||
vsync_front_porch=30,
|
||||
pclk_frequency="46MHz",
|
||||
lane_bit_rate="1Gbps",
|
||||
swap_xy=cv.UNDEFINED,
|
||||
color_order="RGB",
|
||||
reset_pin=27,
|
||||
initsequence=[
|
||||
(0xB9, 0xF1, 0x12, 0x83),
|
||||
(
|
||||
0xBA, 0x31, 0x81, 0x05, 0xF9, 0x0E, 0x0E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0x25, 0x00,
|
||||
0x90, 0x0A, 0x00, 0x00, 0x01, 0x4F, 0x01, 0x00, 0x00, 0x37,
|
||||
),
|
||||
(0xB8, 0x25, 0x22, 0xF0, 0x63),
|
||||
(0xBF, 0x02, 0x11, 0x00),
|
||||
(0xB3, 0x10, 0x10, 0x28, 0x28, 0x03, 0xFF, 0x00, 0x00, 0x00, 0x00),
|
||||
(0xC0, 0x73, 0x73, 0x50, 0x50, 0x00, 0x00, 0x12, 0x70, 0x00),
|
||||
(0xBC, 0x46), (0xCC, 0x0B), (0xB4, 0x80), (0xB2, 0x3C, 0x12, 0x30),
|
||||
(0xE3, 0x07, 0x07, 0x0B, 0x0B, 0x03, 0x0B, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0xC0, 0x10,),
|
||||
(0xC1, 0x36, 0x00, 0x32, 0x32, 0x77, 0xF1, 0xCC, 0xCC, 0x77, 0x77, 0x33, 0x33),
|
||||
(0xB5, 0x0A, 0x0A),
|
||||
(0xB6, 0xB2, 0xB2),
|
||||
(
|
||||
0xE9, 0xC8, 0x10, 0x0A, 0x10, 0x0F, 0xA1, 0x80, 0x12, 0x31, 0x23, 0x47, 0x86, 0xA1, 0x80,
|
||||
0x47, 0x08, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x48,
|
||||
0x02, 0x8B, 0xAF, 0x46, 0x02, 0x88, 0x88, 0x88, 0x88, 0x88, 0x48, 0x13, 0x8B, 0xAF, 0x57,
|
||||
0x13, 0x88, 0x88, 0x88, 0x88, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
),
|
||||
(
|
||||
0xEA, 0x96, 0x12, 0x01, 0x01, 0x01, 0x78, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4F, 0x31,
|
||||
0x8B, 0xA8, 0x31, 0x75, 0x88, 0x88, 0x88, 0x88, 0x88, 0x4F, 0x20, 0x8B, 0xA8, 0x20, 0x64,
|
||||
0x88, 0x88, 0x88, 0x88, 0x88, 0x23, 0x00, 0x00, 0x01, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0xA1, 0x80, 0x00, 0x00,
|
||||
0x00, 0x00,
|
||||
),
|
||||
(
|
||||
0xE0, 0x00, 0x0A, 0x0F, 0x29, 0x3B, 0x3F, 0x42, 0x39, 0x06, 0x0D, 0x10, 0x13, 0x15, 0x14,
|
||||
0x15, 0x10, 0x17, 0x00, 0x0A, 0x0F, 0x29, 0x3B, 0x3F, 0x42, 0x39, 0x06, 0x0D, 0x10, 0x13,
|
||||
0x15, 0x14, 0x15, 0x10, 0x17,
|
||||
),
|
||||
],
|
||||
)
|
0
esphome/components/mipi_rgb/models/lilygo.py
Normal file
0
esphome/components/mipi_rgb/models/lilygo.py
Normal file
28
tests/component_tests/mipi_dsi/fixtures/mipi_dsi.yaml
Normal file
28
tests/component_tests/mipi_dsi/fixtures/mipi_dsi.yaml
Normal file
@ -0,0 +1,28 @@
|
||||
esphome:
|
||||
name: p4-test
|
||||
|
||||
esp32:
|
||||
board: esp32-p4-evboard
|
||||
framework:
|
||||
type: esp-idf
|
||||
|
||||
psram:
|
||||
speed: 200MHz
|
||||
|
||||
esp_ldo:
|
||||
- channel: 3
|
||||
voltage: 2.5V
|
||||
|
||||
display:
|
||||
- platform: mipi_dsi
|
||||
model: WAVESHARE-P4-NANO-10.1
|
||||
|
||||
i2c:
|
||||
sda: GPIO7
|
||||
scl: GPIO8
|
||||
scan: true
|
||||
frequency: 400kHz
|
||||
|
||||
#light:
|
||||
#- platform: mipi_dsi
|
||||
#id: backlight_id
|
127
tests/component_tests/mipi_dsi/test_mipi_dsi_config.py
Normal file
127
tests/component_tests/mipi_dsi/test_mipi_dsi_config.py
Normal file
@ -0,0 +1,127 @@
|
||||
"""Tests for mpi_dsi configuration validation."""
|
||||
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from esphome import config_validation as cv
|
||||
from esphome.components.esp32 import KEY_BOARD, VARIANT_ESP32P4
|
||||
from esphome.const import (
|
||||
CONF_DIMENSIONS,
|
||||
CONF_HEIGHT,
|
||||
CONF_INIT_SEQUENCE,
|
||||
CONF_WIDTH,
|
||||
KEY_VARIANT,
|
||||
PlatformFramework,
|
||||
)
|
||||
from tests.component_tests.types import SetCoreConfigCallable
|
||||
|
||||
|
||||
def test_configuration_errors(set_core_config: SetCoreConfigCallable) -> None:
|
||||
"""Test detection of invalid configuration"""
|
||||
set_core_config(
|
||||
PlatformFramework.ESP32_IDF,
|
||||
platform_data={KEY_BOARD: "esp32-p4-evboard", KEY_VARIANT: VARIANT_ESP32P4},
|
||||
)
|
||||
|
||||
from esphome.components.mipi_dsi.display import CONFIG_SCHEMA
|
||||
|
||||
with pytest.raises(cv.Invalid, match="expected a dictionary"):
|
||||
CONFIG_SCHEMA("a string")
|
||||
|
||||
with pytest.raises(
|
||||
cv.Invalid, match=r"required key not provided @ data\['model'\]"
|
||||
):
|
||||
CONFIG_SCHEMA({"id": "display_id"})
|
||||
|
||||
with pytest.raises(
|
||||
cv.Invalid,
|
||||
match=r"string value is None for dictionary value @ data\['lane_bit_rate'\]",
|
||||
):
|
||||
CONFIG_SCHEMA(
|
||||
{"id": "display_id", "model": "custom", "init_sequence": [[0x36, 0x01]]}
|
||||
)
|
||||
|
||||
with pytest.raises(
|
||||
cv.Invalid, match=r"required key not provided @ data\['dimensions'\]"
|
||||
):
|
||||
CONFIG_SCHEMA(
|
||||
{
|
||||
"id": "display_id",
|
||||
"model": "custom",
|
||||
"init_sequence": [[0x36, 0x01]],
|
||||
"lane_bit_rate": "1.5Gbps",
|
||||
}
|
||||
)
|
||||
|
||||
with pytest.raises(
|
||||
cv.Invalid, match=r"required key not provided @ data\['init_sequence'\]"
|
||||
):
|
||||
CONFIG_SCHEMA(
|
||||
{
|
||||
"model": "custom",
|
||||
"lane_bit_rate": "1.5Gbps",
|
||||
"dimensions": {"width": 320, "height": 240},
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def test_configuration_success(set_core_config: SetCoreConfigCallable) -> None:
|
||||
"""Test successful configuration validation."""
|
||||
set_core_config(
|
||||
PlatformFramework.ESP32_IDF,
|
||||
platform_data={KEY_BOARD: "esp32-p4-evboard", KEY_VARIANT: VARIANT_ESP32P4},
|
||||
)
|
||||
|
||||
from esphome.components.mipi_dsi.display import CONFIG_SCHEMA, MODELS
|
||||
|
||||
# Custom model with all options
|
||||
CONFIG_SCHEMA(
|
||||
{
|
||||
"model": "custom",
|
||||
"pixel_mode": "16bit",
|
||||
"id": "display_id",
|
||||
"byte_order": "little_endian",
|
||||
"color_order": "rgb",
|
||||
"reset_pin": 12,
|
||||
"init_sequence": [[0xA0, 0x01]],
|
||||
"dimensions": {
|
||||
"width": 320,
|
||||
"height": 240,
|
||||
},
|
||||
"invert_colors": True,
|
||||
"transform": {"mirror_x": True, "mirror_y": True},
|
||||
"pclk_frequency": "40MHz",
|
||||
"lane_bit_rate": "1.5Gbps",
|
||||
"lanes": 2,
|
||||
"use_axis_flips": True,
|
||||
}
|
||||
)
|
||||
|
||||
# Test all models, providing default values where necessary
|
||||
for name, model in MODELS.items():
|
||||
config = {"model": name}
|
||||
if model.initsequence is None:
|
||||
config[CONF_INIT_SEQUENCE] = [[0xA0, 0x01]]
|
||||
if not model.get_default(CONF_DIMENSIONS):
|
||||
config[CONF_DIMENSIONS] = {CONF_WIDTH: 400, CONF_HEIGHT: 300}
|
||||
if not model.get_default("lane_bit_rate"):
|
||||
config["lane_bit_rate"] = "1.5Gbps"
|
||||
CONFIG_SCHEMA(config)
|
||||
|
||||
|
||||
def test_code_generation(
|
||||
generate_main: Callable[[str | Path], str],
|
||||
component_fixture_path: Callable[[str], Path],
|
||||
) -> None:
|
||||
"""Test code generation for display."""
|
||||
|
||||
main_cpp = generate_main(component_fixture_path("mipi_dsi.yaml"))
|
||||
assert (
|
||||
"mipi_dsi_mipi_dsi_id = new mipi_dsi::MIPI_DSI(800, 1280, display::COLOR_BITNESS_565, 16);"
|
||||
in main_cpp
|
||||
)
|
||||
assert "set_init_sequence({224, 1, 0, 225, 1, 147, 226, 1," in main_cpp
|
||||
assert "mipi_dsi_mipi_dsi_id->set_lane_bit_rate(1500);" in main_cpp
|
||||
# assert "backlight_id = new light::LightState(mipi_dsi_dsibacklight_id);" in main_cpp
|
19
tests/components/mipi_dsi/test.esp32-p4-idf.yaml
Normal file
19
tests/components/mipi_dsi/test.esp32-p4-idf.yaml
Normal file
@ -0,0 +1,19 @@
|
||||
esp_ldo:
|
||||
- id: ldo_id
|
||||
channel: 3
|
||||
voltage: 2.5V
|
||||
|
||||
display:
|
||||
- platform: mipi_dsi
|
||||
model: JC1060P470
|
||||
enable_pin: GPIO22
|
||||
|
||||
#light:
|
||||
#- platform: mipi_dsi
|
||||
#id: backlight_id
|
||||
|
||||
i2c:
|
||||
sda: GPIO7
|
||||
scl: GPIO8
|
||||
scan: true
|
||||
frequency: 400kHz
|
Loading…
x
Reference in New Issue
Block a user