Drivers for RGB 16 bit parallel displays (#5872)

Co-authored-by: clydebarrow <366188+clydebarrow@users.noreply.github.com>
This commit is contained in:
Clyde Stubbs
2024-03-12 09:55:23 +11:00
committed by GitHub
parent 8cb689b58c
commit 1dd14254b3
12 changed files with 1420 additions and 0 deletions
@@ -0,0 +1 @@
CODEOWNERS = ["@clydebarrow"]
+197
View File
@@ -0,0 +1,197 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import pins
from esphome.components import display
from esphome.const import (
CONF_RESET_PIN,
CONF_DATA_PINS,
CONF_ID,
CONF_IGNORE_STRAPPING_WARNING,
CONF_DIMENSIONS,
CONF_WIDTH,
CONF_HEIGHT,
CONF_LAMBDA,
CONF_COLOR_ORDER,
CONF_RED,
CONF_GREEN,
CONF_BLUE,
CONF_NUMBER,
CONF_OFFSET_HEIGHT,
CONF_OFFSET_WIDTH,
CONF_INVERT_COLORS,
)
from esphome.components.esp32 import (
only_on_variant,
const,
)
DEPENDENCIES = ["esp32"]
CONF_DE_PIN = "de_pin"
CONF_PCLK_PIN = "pclk_pin"
CONF_HSYNC_PIN = "hsync_pin"
CONF_VSYNC_PIN = "vsync_pin"
CONF_HSYNC_FRONT_PORCH = "hsync_front_porch"
CONF_HSYNC_PULSE_WIDTH = "hsync_pulse_width"
CONF_HSYNC_BACK_PORCH = "hsync_back_porch"
CONF_VSYNC_FRONT_PORCH = "vsync_front_porch"
CONF_VSYNC_PULSE_WIDTH = "vsync_pulse_width"
CONF_VSYNC_BACK_PORCH = "vsync_back_porch"
CONF_PCLK_FREQUENCY = "pclk_frequency"
CONF_PCLK_INVERTED = "pclk_inverted"
rpi_dpi_rgb_ns = cg.esphome_ns.namespace("rpi_dpi_rgb")
RPI_DPI_RGB = rpi_dpi_rgb_ns.class_("RpiDpiRgb", display.Display, cg.Component)
ColorOrder = display.display_ns.enum("ColorMode")
COLOR_ORDERS = {
"RGB": ColorOrder.COLOR_ORDER_RGB,
"BGR": ColorOrder.COLOR_ORDER_BGR,
}
DATA_PIN_SCHEMA = pins.internal_gpio_output_pin_schema
def data_pin_validate(value):
"""
It is safe to use strapping pins as RGB output data bits, as they are outputs only,
and not initialised until after boot.
"""
if not isinstance(value, dict):
try:
return DATA_PIN_SCHEMA(
{CONF_NUMBER: value, CONF_IGNORE_STRAPPING_WARNING: True}
)
except cv.Invalid:
pass
return DATA_PIN_SCHEMA(value)
def data_pin_set(length):
return cv.All(
[data_pin_validate],
cv.Length(min=length, max=length, msg=f"Exactly {length} data pins required"),
)
CONFIG_SCHEMA = cv.All(
display.FULL_DISPLAY_SCHEMA.extend(
cv.Schema(
{
cv.GenerateID(): cv.declare_id(RPI_DPI_RGB),
cv.Required(CONF_DIMENSIONS): cv.Any(
cv.dimensions,
cv.Schema(
{
cv.Required(CONF_WIDTH): cv.int_,
cv.Required(CONF_HEIGHT): cv.int_,
cv.Optional(CONF_OFFSET_HEIGHT, default=0): cv.int_,
cv.Optional(CONF_OFFSET_WIDTH, default=0): cv.int_,
}
),
),
cv.Optional(CONF_PCLK_FREQUENCY, default="16MHz"): cv.All(
cv.frequency, cv.Range(min=4e6, max=30e6)
),
cv.Optional(CONF_PCLK_INVERTED, default=True): cv.boolean,
cv.Required(CONF_DATA_PINS): cv.Any(
data_pin_set(16),
cv.Schema(
{
cv.Required(CONF_RED): data_pin_set(5),
cv.Required(CONF_GREEN): data_pin_set(6),
cv.Required(CONF_BLUE): data_pin_set(5),
}
),
),
cv.Optional(CONF_COLOR_ORDER): cv.one_of(
*COLOR_ORDERS.keys(), upper=True
),
cv.Optional(CONF_INVERT_COLORS, default=False): cv.boolean,
cv.Required(CONF_DE_PIN): pins.internal_gpio_output_pin_schema,
cv.Required(CONF_PCLK_PIN): pins.internal_gpio_output_pin_schema,
cv.Required(CONF_HSYNC_PIN): pins.internal_gpio_output_pin_schema,
cv.Required(CONF_VSYNC_PIN): pins.internal_gpio_output_pin_schema,
cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema,
cv.Optional(CONF_HSYNC_PULSE_WIDTH, default=10): cv.int_,
cv.Optional(CONF_HSYNC_BACK_PORCH, default=10): cv.int_,
cv.Optional(CONF_HSYNC_FRONT_PORCH, default=20): cv.int_,
cv.Optional(CONF_VSYNC_PULSE_WIDTH, default=10): cv.int_,
cv.Optional(CONF_VSYNC_BACK_PORCH, default=10): cv.int_,
cv.Optional(CONF_VSYNC_FRONT_PORCH, default=10): cv.int_,
}
)
),
only_on_variant(supported=[const.VARIANT_ESP32S3]),
cv.only_with_esp_idf,
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await display.register_display(var, config)
cg.add(var.set_color_mode(COLOR_ORDERS[config[CONF_COLOR_ORDER]]))
cg.add(var.set_invert_colors(config[CONF_INVERT_COLORS]))
cg.add(var.set_hsync_pulse_width(config[CONF_HSYNC_PULSE_WIDTH]))
cg.add(var.set_hsync_back_porch(config[CONF_HSYNC_BACK_PORCH]))
cg.add(var.set_hsync_front_porch(config[CONF_HSYNC_FRONT_PORCH]))
cg.add(var.set_vsync_pulse_width(config[CONF_VSYNC_PULSE_WIDTH]))
cg.add(var.set_vsync_back_porch(config[CONF_VSYNC_BACK_PORCH]))
cg.add(var.set_vsync_front_porch(config[CONF_VSYNC_FRONT_PORCH]))
cg.add(var.set_pclk_inverted(config[CONF_PCLK_INVERTED]))
cg.add(var.set_pclk_frequency(config[CONF_PCLK_FREQUENCY]))
index = 0
dpins = []
if CONF_RED in config[CONF_DATA_PINS]:
red_pins = config[CONF_DATA_PINS][CONF_RED]
green_pins = config[CONF_DATA_PINS][CONF_GREEN]
blue_pins = config[CONF_DATA_PINS][CONF_BLUE]
if config[CONF_COLOR_ORDER] == "BGR":
dpins.extend(red_pins)
dpins.extend(green_pins)
dpins.extend(blue_pins)
else:
dpins.extend(blue_pins)
dpins.extend(green_pins)
dpins.extend(red_pins)
# swap bytes to match big-endian format
dpins = dpins[8:16] + dpins[0:8]
else:
dpins = config[CONF_DATA_PINS]
for pin in dpins:
data_pin = await cg.gpio_pin_expression(pin)
cg.add(var.add_data_pin(data_pin, index))
index += 1
if reset_pin := config.get(CONF_RESET_PIN):
reset = await cg.gpio_pin_expression(reset_pin)
cg.add(var.set_reset_pin(reset))
if CONF_DIMENSIONS in config:
dimensions = config[CONF_DIMENSIONS]
if isinstance(dimensions, dict):
cg.add(var.set_dimensions(dimensions[CONF_WIDTH], dimensions[CONF_HEIGHT]))
cg.add(
var.set_offsets(
dimensions[CONF_OFFSET_WIDTH], dimensions[CONF_OFFSET_HEIGHT]
)
)
else:
(width, height) = dimensions
cg.add(var.set_dimensions(width, height))
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_))
pin = await cg.gpio_pin_expression(config[CONF_DE_PIN])
cg.add(var.set_de_pin(pin))
pin = await cg.gpio_pin_expression(config[CONF_PCLK_PIN])
cg.add(var.set_pclk_pin(pin))
pin = await cg.gpio_pin_expression(config[CONF_HSYNC_PIN])
cg.add(var.set_hsync_pin(pin))
pin = await cg.gpio_pin_expression(config[CONF_VSYNC_PIN])
cg.add(var.set_vsync_pin(pin))
@@ -0,0 +1,116 @@
#ifdef USE_ESP32_VARIANT_ESP32S3
#include "rpi_dpi_rgb.h"
#include "esphome/core/log.h"
namespace esphome {
namespace rpi_dpi_rgb {
void RpiDpiRgb::setup() {
esph_log_config(TAG, "Setting up RPI_DPI_RGB");
esp_lcd_rgb_panel_config_t config{};
config.flags.fb_in_psram = 1;
config.timings.h_res = this->width_;
config.timings.v_res = this->height_;
config.timings.hsync_pulse_width = this->hsync_pulse_width_;
config.timings.hsync_back_porch = this->hsync_back_porch_;
config.timings.hsync_front_porch = this->hsync_front_porch_;
config.timings.vsync_pulse_width = this->vsync_pulse_width_;
config.timings.vsync_back_porch = this->vsync_back_porch_;
config.timings.vsync_front_porch = this->vsync_front_porch_;
config.timings.flags.pclk_active_neg = this->pclk_inverted_;
config.timings.pclk_hz = this->pclk_frequency_;
config.clk_src = LCD_CLK_SRC_PLL160M;
config.sram_trans_align = 64;
config.psram_trans_align = 64;
size_t data_pin_count = sizeof(this->data_pins_) / sizeof(this->data_pins_[0]);
for (size_t i = 0; i != data_pin_count; i++) {
config.data_gpio_nums[i] = this->data_pins_[i]->get_pin();
}
config.data_width = data_pin_count;
config.disp_gpio_num = -1;
config.hsync_gpio_num = this->hsync_pin_->get_pin();
config.vsync_gpio_num = this->vsync_pin_->get_pin();
config.de_gpio_num = this->de_pin_->get_pin();
config.pclk_gpio_num = this->pclk_pin_->get_pin();
esp_err_t err = esp_lcd_new_rgb_panel(&config, &this->handle_);
if (err != ESP_OK) {
esph_log_e(TAG, "lcd_new_rgb_panel failed: %s", esp_err_to_name(err));
}
ESP_ERROR_CHECK(esp_lcd_panel_reset(this->handle_));
ESP_ERROR_CHECK(esp_lcd_panel_init(this->handle_));
esph_log_config(TAG, "RPI_DPI_RGB setup complete");
}
void RpiDpiRgb::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 != display::COLOR_BITNESS_565) {
return display::Display::draw_pixels_at(x_start, y_start, w, h, ptr, order, bitness, big_endian, x_offset, y_offset,
x_pad);
}
x_start += this->offset_x_;
y_start += this->offset_y_;
esp_err_t err;
// 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 && 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
err = esp_lcd_panel_draw_bitmap(this->handle_, x_start, y_start, x_start + w, y_start + h, ptr);
} else {
// draw line by line
auto stride = x_offset + w + x_pad;
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 + ((y + y_offset) * stride + x_offset) * 2);
if (err != ESP_OK)
break;
}
}
if (err != ESP_OK)
esph_log_e(TAG, "lcd_lcd_panel_draw_bitmap failed: %s", esp_err_to_name(err));
}
void RpiDpiRgb::draw_pixel_at(int x, int y, Color color) {
if (!this->get_clipping().inside(x, y))
return; // NOLINT
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;
}
auto pixel = convert_big_endian(display::ColorUtil::color_to_565(color));
this->draw_pixels_at(x, y, 1, 1, (const uint8_t *) &pixel, display::COLOR_ORDER_RGB, display::COLOR_BITNESS_565, true,
0, 0, 0);
App.feed_wdt();
}
void RpiDpiRgb::dump_config() {
ESP_LOGCONFIG("", "RPI_DPI_RGB LCD");
ESP_LOGCONFIG(TAG, " Height: %u", this->height_);
ESP_LOGCONFIG(TAG, " Width: %u", this->width_);
LOG_PIN(" DE Pin: ", this->de_pin_);
LOG_PIN(" Reset Pin: ", this->reset_pin_);
size_t data_pin_count = sizeof(this->data_pins_) / sizeof(this->data_pins_[0]);
for (size_t i = 0; i != data_pin_count; i++)
ESP_LOGCONFIG(TAG, " Data pin %d: %s", i, (this->data_pins_[i])->dump_summary().c_str());
}
} // namespace rpi_dpi_rgb
} // namespace esphome
#endif // USE_ESP32_VARIANT_ESP32S3
@@ -0,0 +1,92 @@
//
// Created by Clyde Stubbs on 29/10/2023.
//
#pragma once
// only applicable on ESP32-S3
#ifdef USE_ESP32_VARIANT_ESP32S3
#include "esphome/core/component.h"
#include "esphome/core/gpio.h"
#include "esphome/core/log.h"
#include "esphome/core/application.h"
#include "esphome/components/display/display.h"
#include "esp_lcd_panel_ops.h"
#include "esp_lcd_panel_rgb.h"
namespace esphome {
namespace rpi_dpi_rgb {
constexpr static const char *const TAG = "rpi_dpi_rgb";
class RpiDpiRgb : public display::Display {
public:
void update() override { this->do_update_(); }
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;
display::ColorOrder get_color_mode() { return this->color_mode_; }
void set_color_mode(display::ColorOrder color_mode) { this->color_mode_ = color_mode; }
void set_invert_colors(bool invert_colors) { this->invert_colors_ = invert_colors; }
void add_data_pin(InternalGPIOPin *data_pin, size_t index) { this->data_pins_[index] = data_pin; };
void set_de_pin(InternalGPIOPin *de_pin) { this->de_pin_ = de_pin; }
void set_pclk_pin(InternalGPIOPin *pclk_pin) { this->pclk_pin_ = pclk_pin; }
void set_vsync_pin(InternalGPIOPin *vsync_pin) { this->vsync_pin_ = vsync_pin; }
void set_hsync_pin(InternalGPIOPin *hsync_pin) { this->hsync_pin_ = hsync_pin; }
void set_reset_pin(GPIOPin *reset_pin) { this->reset_pin_ = reset_pin; }
void set_width(uint16_t width) { this->width_ = width; }
void set_dimensions(uint16_t width, uint16_t height) {
this->width_ = width;
this->height_ = height;
}
int get_width() override { return this->width_; }
int get_height() 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_pclk_frequency(uint32_t pclk_frequency) { this->pclk_frequency_ = pclk_frequency; }
void set_pclk_inverted(bool inverted) { this->pclk_inverted_ = inverted; }
void set_offsets(int16_t offset_x, int16_t offset_y) {
this->offset_x_ = offset_x;
this->offset_y_ = offset_y;
}
display::DisplayType get_display_type() override { return display::DisplayType::DISPLAY_TYPE_COLOR; }
void dump_config() override;
protected:
int get_width_internal() override { return this->width_; }
int get_height_internal() override { return this->height_; }
InternalGPIOPin *de_pin_{nullptr};
InternalGPIOPin *pclk_pin_{nullptr};
InternalGPIOPin *hsync_pin_{nullptr};
InternalGPIOPin *vsync_pin_{nullptr};
GPIOPin *reset_pin_{nullptr};
InternalGPIOPin *data_pins_[16] = {};
uint16_t hsync_front_porch_ = 8;
uint16_t hsync_pulse_width_ = 4;
uint16_t hsync_back_porch_ = 8;
uint16_t vsync_front_porch_ = 8;
uint16_t vsync_pulse_width_ = 4;
uint16_t vsync_back_porch_ = 8;
uint32_t pclk_frequency_ = 16 * 1000 * 1000;
bool pclk_inverted_{true};
bool invert_colors_{};
display::ColorOrder color_mode_{display::COLOR_ORDER_BGR};
size_t width_{};
size_t height_{};
int16_t offset_x_{0};
int16_t offset_y_{0};
esp_lcd_panel_handle_t handle_{};
};
} // namespace rpi_dpi_rgb
} // namespace esphome
#endif // USE_ESP32_VARIANT_ESP32S3