mirror of
https://github.com/esphome/esphome.git
synced 2024-11-21 22:48:10 +01:00
Drivers for RGB 16 bit parallel displays (#5872)
Co-authored-by: clydebarrow <366188+clydebarrow@users.noreply.github.com>
This commit is contained in:
parent
8cb689b58c
commit
1dd14254b3
12 changed files with 1420 additions and 0 deletions
|
@ -282,6 +282,7 @@ esphome/components/rgbct/* @jesserockz
|
|||
esphome/components/rp2040/* @jesserockz
|
||||
esphome/components/rp2040_pio_led_strip/* @Papa-DMan
|
||||
esphome/components/rp2040_pwm/* @jesserockz
|
||||
esphome/components/rpi_dpi_rgb/* @clydebarrow
|
||||
esphome/components/rtl87xx/* @kuba2k2
|
||||
esphome/components/rtttl/* @glmnet
|
||||
esphome/components/safe_mode/* @jsuanet @paulmonigatti
|
||||
|
@ -333,6 +334,7 @@ esphome/components/ssd1351_spi/* @kbx81
|
|||
esphome/components/st7567_base/* @latonita
|
||||
esphome/components/st7567_i2c/* @latonita
|
||||
esphome/components/st7567_spi/* @latonita
|
||||
esphome/components/st7701s/* @clydebarrow
|
||||
esphome/components/st7735/* @SenexCrenshaw
|
||||
esphome/components/st7789v/* @kbx81
|
||||
esphome/components/st7920/* @marsjan155
|
||||
|
|
1
esphome/components/rpi_dpi_rgb/__init__.py
Normal file
1
esphome/components/rpi_dpi_rgb/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
CODEOWNERS = ["@clydebarrow"]
|
197
esphome/components/rpi_dpi_rgb/display.py
Normal file
197
esphome/components/rpi_dpi_rgb/display.py
Normal 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))
|
116
esphome/components/rpi_dpi_rgb/rpi_dpi_rgb.cpp
Normal file
116
esphome/components/rpi_dpi_rgb/rpi_dpi_rgb.cpp
Normal file
|
@ -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
|
92
esphome/components/rpi_dpi_rgb/rpi_dpi_rgb.h
Normal file
92
esphome/components/rpi_dpi_rgb/rpi_dpi_rgb.h
Normal file
|
@ -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
|
1
esphome/components/st7701s/__init__.py
Normal file
1
esphome/components/st7701s/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
CODEOWNERS = ["@clydebarrow"]
|
253
esphome/components/st7701s/display.py
Normal file
253
esphome/components/st7701s/display.py
Normal file
|
@ -0,0 +1,253 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import pins
|
||||
from esphome.components import (
|
||||
spi,
|
||||
display,
|
||||
)
|
||||
from esphome.const import (
|
||||
CONF_DC_PIN,
|
||||
CONF_RESET_PIN,
|
||||
CONF_DATA_PINS,
|
||||
CONF_ID,
|
||||
CONF_DIMENSIONS,
|
||||
CONF_WIDTH,
|
||||
CONF_HEIGHT,
|
||||
CONF_LAMBDA,
|
||||
CONF_MIRROR_X,
|
||||
CONF_MIRROR_Y,
|
||||
CONF_COLOR_ORDER,
|
||||
CONF_TRANSFORM,
|
||||
CONF_OFFSET_HEIGHT,
|
||||
CONF_OFFSET_WIDTH,
|
||||
CONF_INVERT_COLORS,
|
||||
CONF_RED,
|
||||
CONF_GREEN,
|
||||
CONF_BLUE,
|
||||
CONF_NUMBER,
|
||||
CONF_IGNORE_STRAPPING_WARNING,
|
||||
)
|
||||
|
||||
from esphome.components.esp32 import (
|
||||
only_on_variant,
|
||||
const,
|
||||
)
|
||||
from esphome.components.rpi_dpi_rgb.display import (
|
||||
CONF_PCLK_FREQUENCY,
|
||||
CONF_PCLK_INVERTED,
|
||||
)
|
||||
from .init_sequences import (
|
||||
ST7701S_INITS,
|
||||
cmd,
|
||||
)
|
||||
|
||||
CONF_INIT_SEQUENCE = "init_sequence"
|
||||
CONF_DE_PIN = "de_pin"
|
||||
CONF_PCLK_PIN = "pclk_pin"
|
||||
CONF_HSYNC_PIN = "hsync_pin"
|
||||
CONF_VSYNC_PIN = "vsync_pin"
|
||||
|
||||
CONF_HSYNC_PULSE_WIDTH = "hsync_pulse_width"
|
||||
CONF_HSYNC_BACK_PORCH = "hsync_back_porch"
|
||||
CONF_HSYNC_FRONT_PORCH = "hsync_front_porch"
|
||||
CONF_VSYNC_PULSE_WIDTH = "vsync_pulse_width"
|
||||
CONF_VSYNC_BACK_PORCH = "vsync_back_porch"
|
||||
CONF_VSYNC_FRONT_PORCH = "vsync_front_porch"
|
||||
|
||||
DEPENDENCIES = ["spi", "esp32"]
|
||||
|
||||
st7701s_ns = cg.esphome_ns.namespace("st7701s")
|
||||
ST7701S = st7701s_ns.class_("ST7701S", display.Display, cg.Component, spi.SPIDevice)
|
||||
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"),
|
||||
)
|
||||
|
||||
|
||||
def map_sequence(value):
|
||||
"""
|
||||
An initialisation sequence can be selected from one of the pre-defined sequences in init_sequences.py,
|
||||
or can be a literal array of data bytes.
|
||||
The format is a repeated sequence of [CMD, LEN, <data>] where <data> is LEN bytes.
|
||||
"""
|
||||
if not isinstance(value, list):
|
||||
value = cv.int_(value)
|
||||
value = cv.one_of(*ST7701S_INITS)(value)
|
||||
return ST7701S_INITS[value]
|
||||
# value = cv.ensure_list(cv.uint8_t)(value)
|
||||
data_length = len(value)
|
||||
if data_length == 0:
|
||||
raise cv.Invalid("Empty sequence")
|
||||
value = cmd(*value)
|
||||
return value
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
display.FULL_DISPLAY_SCHEMA.extend(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(ST7701S),
|
||||
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_TRANSFORM): cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_MIRROR_X, default=False): cv.boolean,
|
||||
cv.Optional(CONF_MIRROR_Y, default=False): 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_INIT_SEQUENCE, default=1): cv.ensure_list(
|
||||
map_sequence
|
||||
),
|
||||
cv.Optional(CONF_COLOR_ORDER): cv.one_of(
|
||||
*COLOR_ORDERS.keys(), upper=True
|
||||
),
|
||||
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.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_DC_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_,
|
||||
}
|
||||
).extend(spi.spi_device_schema(cs_pin_required=False, default_data_rate=1e6))
|
||||
),
|
||||
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)
|
||||
await spi.register_spi_device(var, config)
|
||||
|
||||
sequence = []
|
||||
for seq in config[CONF_INIT_SEQUENCE]:
|
||||
sequence.extend(seq)
|
||||
cg.add(var.set_init_sequence(sequence))
|
||||
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 dc_pin := config.get(CONF_DC_PIN):
|
||||
dc = await cg.gpio_pin_expression(dc_pin)
|
||||
cg.add(var.set_dc_pin(dc))
|
||||
|
||||
if reset_pin := config.get(CONF_RESET_PIN):
|
||||
reset = await cg.gpio_pin_expression(reset_pin)
|
||||
cg.add(var.set_reset_pin(reset))
|
||||
|
||||
if transform := config.get(CONF_TRANSFORM):
|
||||
cg.add(var.set_mirror_x(transform[CONF_MIRROR_X]))
|
||||
cg.add(var.set_mirror_y(transform[CONF_MIRROR_Y]))
|
||||
|
||||
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))
|
363
esphome/components/st7701s/init_sequences.py
Normal file
363
esphome/components/st7701s/init_sequences.py
Normal file
|
@ -0,0 +1,363 @@
|
|||
# These are initialisation sequences for ST7701S displays. The contents are somewhat arcane.
|
||||
|
||||
|
||||
def cmd(c, *args):
|
||||
"""
|
||||
Create a command sequence
|
||||
:param c: The command (8 bit)
|
||||
:param args: zero or more arguments (8 bit values)
|
||||
:return: a list with the command, the argument count and the arguments
|
||||
"""
|
||||
return [c, len(args)] + list(args)
|
||||
|
||||
|
||||
ST7701S_1_INIT = (
|
||||
cmd(0x01)
|
||||
+ cmd(0xFF, 0x77, 0x01, 0x00, 0x00, 0x10)
|
||||
+ cmd(0xC0, 0x3B, 0x00)
|
||||
+ cmd(0xC1, 0x0D, 0x02)
|
||||
+ cmd(0xC2, 0x31, 0x05)
|
||||
+ cmd(0xCD, 0x08)
|
||||
+ cmd(
|
||||
0xB0,
|
||||
0x00,
|
||||
0x11,
|
||||
0x18,
|
||||
0x0E,
|
||||
0x11,
|
||||
0x06,
|
||||
0x07,
|
||||
0x08,
|
||||
0x07,
|
||||
0x22,
|
||||
0x04,
|
||||
0x12,
|
||||
0x0F,
|
||||
0xAA,
|
||||
0x31,
|
||||
0x18,
|
||||
)
|
||||
+ cmd(
|
||||
0xB1,
|
||||
0x00,
|
||||
0x11,
|
||||
0x19,
|
||||
0x0E,
|
||||
0x12,
|
||||
0x07,
|
||||
0x08,
|
||||
0x08,
|
||||
0x08,
|
||||
0x22,
|
||||
0x04,
|
||||
0x11,
|
||||
0x11,
|
||||
0xA9,
|
||||
0x32,
|
||||
0x18,
|
||||
)
|
||||
+ cmd(0xFF, 0x77, 0x01, 0x00, 0x00, 0x11)
|
||||
+ cmd(0xB0, 0x60)
|
||||
+ cmd(0xB1, 0x32)
|
||||
+ cmd(0xB2, 0x07)
|
||||
+ cmd(0xB3, 0x80)
|
||||
+ cmd(0xB5, 0x49)
|
||||
+ cmd(0xB7, 0x85)
|
||||
+ cmd(0xB8, 0x21)
|
||||
+ cmd(0xC1, 0x78)
|
||||
+ cmd(0xC2, 0x78)
|
||||
+ cmd(0xE0, 0x00, 0x1B, 0x02)
|
||||
+ cmd(0xE1, 0x08, 0xA0, 0x00, 0x00, 0x07, 0xA0, 0x00, 0x00, 0x00, 0x44, 0x44)
|
||||
+ cmd(0xE2, 0x11, 0x11, 0x44, 0x44, 0xED, 0xA0, 0x00, 0x00, 0xEC, 0xA0, 0x00, 0x00)
|
||||
+ cmd(0xE3, 0x00, 0x00, 0x11, 0x11)
|
||||
+ cmd(0xE4, 0x44, 0x44)
|
||||
+ cmd(
|
||||
0xE5,
|
||||
0x0A,
|
||||
0xE9,
|
||||
0xD8,
|
||||
0xA0,
|
||||
0x0C,
|
||||
0xEB,
|
||||
0xD8,
|
||||
0xA0,
|
||||
0x0E,
|
||||
0xED,
|
||||
0xD8,
|
||||
0xA0,
|
||||
0x10,
|
||||
0xEF,
|
||||
0xD8,
|
||||
0xA0,
|
||||
)
|
||||
+ cmd(0xE6, 0x00, 0x00, 0x11, 0x11)
|
||||
+ cmd(0xE7, 0x44, 0x44)
|
||||
+ cmd(
|
||||
0xE8,
|
||||
0x09,
|
||||
0xE8,
|
||||
0xD8,
|
||||
0xA0,
|
||||
0x0B,
|
||||
0xEA,
|
||||
0xD8,
|
||||
0xA0,
|
||||
0x0D,
|
||||
0xEC,
|
||||
0xD8,
|
||||
0xA0,
|
||||
0x0F,
|
||||
0xEE,
|
||||
0xD8,
|
||||
0xA0,
|
||||
)
|
||||
+ cmd(0xEB, 0x02, 0x00, 0xE4, 0xE4, 0x88, 0x00, 0x40)
|
||||
+ cmd(0xEC, 0x3C, 0x00)
|
||||
+ cmd(
|
||||
0xED,
|
||||
0xAB,
|
||||
0x89,
|
||||
0x76,
|
||||
0x54,
|
||||
0x02,
|
||||
0xFF,
|
||||
0xFF,
|
||||
0xFF,
|
||||
0xFF,
|
||||
0xFF,
|
||||
0xFF,
|
||||
0x20,
|
||||
0x45,
|
||||
0x67,
|
||||
0x98,
|
||||
0xBA,
|
||||
)
|
||||
+ cmd(0xFF, 0x77, 0x01, 0x00, 0x00, 0x13)
|
||||
+ cmd(0xE5, 0xE4)
|
||||
+ cmd(0x3A, 0x60)
|
||||
)
|
||||
|
||||
# This is untested
|
||||
ST7701S_7_INIT = (
|
||||
cmd(
|
||||
0xFF,
|
||||
0x77,
|
||||
0x01,
|
||||
0x00,
|
||||
0x00,
|
||||
0x10,
|
||||
)
|
||||
+ cmd(0xC0, 0x3B, 0x00)
|
||||
+ cmd(0xC1, 0x0B, 0x02)
|
||||
+ cmd(0xC2, 0x07, 0x02)
|
||||
+ cmd(0xCC, 0x10)
|
||||
+ cmd(0xCD, 0x08)
|
||||
+ cmd(
|
||||
0xB0,
|
||||
0x00,
|
||||
0x11,
|
||||
0x16,
|
||||
0x0E,
|
||||
0x11,
|
||||
0x06,
|
||||
0x05,
|
||||
0x09,
|
||||
0x08,
|
||||
0x21,
|
||||
0x06,
|
||||
0x13,
|
||||
0x10,
|
||||
0x29,
|
||||
0x31,
|
||||
0x18,
|
||||
)
|
||||
+ cmd(
|
||||
0xB1,
|
||||
0x00,
|
||||
0x11,
|
||||
0x16,
|
||||
0x0E,
|
||||
0x11,
|
||||
0x07,
|
||||
0x05,
|
||||
0x09,
|
||||
0x09,
|
||||
0x21,
|
||||
0x05,
|
||||
0x13,
|
||||
0x11,
|
||||
0x2A,
|
||||
0x31,
|
||||
0x18,
|
||||
)
|
||||
+ cmd(
|
||||
0xFF,
|
||||
0x77,
|
||||
0x01,
|
||||
0x00,
|
||||
0x00,
|
||||
0x11,
|
||||
)
|
||||
+ cmd(0xB0, 0x6D)
|
||||
+ cmd(0xB1, 0x37)
|
||||
+ cmd(0xB2, 0x81)
|
||||
+ cmd(0xB3, 0x80)
|
||||
+ cmd(0xB5, 0x43)
|
||||
+ cmd(0xB7, 0x85)
|
||||
+ cmd(0xB8, 0x20)
|
||||
+ cmd(0xC1, 0x78)
|
||||
+ cmd(0xC2, 0x78)
|
||||
+ cmd(0xD0, 0x88)
|
||||
+ cmd(
|
||||
0xE0,
|
||||
3,
|
||||
0x00,
|
||||
0x00,
|
||||
0x02,
|
||||
)
|
||||
+ cmd(
|
||||
0xE1,
|
||||
0x03,
|
||||
0xA0,
|
||||
0x00,
|
||||
0x00,
|
||||
0x04,
|
||||
0xA0,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x20,
|
||||
0x20,
|
||||
)
|
||||
+ cmd(
|
||||
0xE2,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
)
|
||||
+ cmd(
|
||||
0xE3,
|
||||
0x00,
|
||||
0x00,
|
||||
0x11,
|
||||
0x00,
|
||||
)
|
||||
+ cmd(0xE4, 0x22, 0x00)
|
||||
+ cmd(
|
||||
0xE5,
|
||||
0x05,
|
||||
0xEC,
|
||||
0xA0,
|
||||
0xA0,
|
||||
0x07,
|
||||
0xEE,
|
||||
0xA0,
|
||||
0xA0,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
)
|
||||
+ cmd(
|
||||
0xE6,
|
||||
0x00,
|
||||
0x00,
|
||||
0x11,
|
||||
0x00,
|
||||
)
|
||||
+ cmd(0xE7, 0x22, 0x00)
|
||||
+ cmd(
|
||||
0xE8,
|
||||
0x06,
|
||||
0xED,
|
||||
0xA0,
|
||||
0xA0,
|
||||
0x08,
|
||||
0xEF,
|
||||
0xA0,
|
||||
0xA0,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
)
|
||||
+ cmd(
|
||||
0xEB,
|
||||
0x00,
|
||||
0x00,
|
||||
0x40,
|
||||
0x40,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
)
|
||||
+ cmd(
|
||||
0xED,
|
||||
0xFF,
|
||||
0xFF,
|
||||
0xFF,
|
||||
0xBA,
|
||||
0x0A,
|
||||
0xBF,
|
||||
0x45,
|
||||
0xFF,
|
||||
0xFF,
|
||||
0x54,
|
||||
0xFB,
|
||||
0xA0,
|
||||
0xAB,
|
||||
0xFF,
|
||||
0xFF,
|
||||
0xFF,
|
||||
)
|
||||
+ cmd(
|
||||
0xEF,
|
||||
0x10,
|
||||
0x0D,
|
||||
0x04,
|
||||
0x08,
|
||||
0x3F,
|
||||
0x1F,
|
||||
)
|
||||
+ cmd(
|
||||
0xFF,
|
||||
0x77,
|
||||
0x01,
|
||||
0x00,
|
||||
0x00,
|
||||
0x13,
|
||||
)
|
||||
+ cmd(0xEF, 0x08)
|
||||
+ cmd(
|
||||
0xFF,
|
||||
0x77,
|
||||
0x01,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
)
|
||||
+ cmd(0x3A, 0x66)
|
||||
)
|
||||
|
||||
ST7701S_INITS = {
|
||||
1: ST7701S_1_INIT,
|
||||
# 7: ST7701S_7_INIT,
|
||||
}
|
180
esphome/components/st7701s/st7701s.cpp
Normal file
180
esphome/components/st7701s/st7701s.cpp
Normal file
|
@ -0,0 +1,180 @@
|
|||
#ifdef USE_ESP32_VARIANT_ESP32S3
|
||||
#include "st7701s.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace st7701s {
|
||||
|
||||
void ST7701S::setup() {
|
||||
esph_log_config(TAG, "Setting up ST7701S");
|
||||
this->spi_setup();
|
||||
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_));
|
||||
this->write_init_sequence_();
|
||||
esph_log_config(TAG, "ST7701S setup complete");
|
||||
}
|
||||
|
||||
void ST7701S::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 ST7701S::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 ST7701S::write_command_(uint8_t value) {
|
||||
this->enable();
|
||||
if (this->dc_pin_ == nullptr) {
|
||||
this->write(value, 9);
|
||||
} else {
|
||||
this->dc_pin_->digital_write(false);
|
||||
this->write_byte(value);
|
||||
this->dc_pin_->digital_write(true);
|
||||
}
|
||||
this->disable();
|
||||
}
|
||||
|
||||
void ST7701S::write_data_(uint8_t value) {
|
||||
this->enable();
|
||||
if (this->dc_pin_ == nullptr) {
|
||||
this->write(value | 0x100, 9);
|
||||
} else {
|
||||
this->dc_pin_->digital_write(true);
|
||||
this->write_byte(value);
|
||||
}
|
||||
this->disable();
|
||||
}
|
||||
|
||||
/**
|
||||
* this relies upon the init sequence being well-formed, which is guaranteed by the Python init code.
|
||||
*/
|
||||
|
||||
void ST7701S::write_sequence_(uint8_t cmd, size_t len, const uint8_t *bytes) {
|
||||
this->write_command_(cmd);
|
||||
while (len-- != 0)
|
||||
this->write_data_(*bytes++);
|
||||
}
|
||||
|
||||
void ST7701S::write_init_sequence_() {
|
||||
for (size_t i = 0; i != this->init_sequence_.size();) {
|
||||
uint8_t cmd = this->init_sequence_[i++];
|
||||
size_t len = this->init_sequence_[i++];
|
||||
this->write_sequence_(cmd, len, &this->init_sequence_[i]);
|
||||
i += len;
|
||||
esph_log_v(TAG, "Command %X, %d bytes", cmd, len);
|
||||
if (cmd == SW_RESET_CMD)
|
||||
delay(6);
|
||||
}
|
||||
// st7701 does not appear to support axis swapping
|
||||
this->write_sequence_(CMD2_BKSEL, sizeof(CMD2_BK0), CMD2_BK0);
|
||||
this->write_command_(SDIR_CMD); // this is in the BK0 command set
|
||||
this->write_data_(this->mirror_x_ ? 0x04 : 0x00);
|
||||
uint8_t val = this->color_mode_ == display::COLOR_ORDER_BGR ? 0x08 : 0x00;
|
||||
if (this->mirror_y_)
|
||||
val |= 0x10;
|
||||
this->write_command_(MADCTL_CMD);
|
||||
this->write_data_(val);
|
||||
esph_log_d(TAG, "write MADCTL %X", val);
|
||||
this->write_command_(this->invert_colors_ ? INVERT_ON : INVERT_OFF);
|
||||
this->set_timeout(120, [this] {
|
||||
this->write_command_(SLEEP_OUT);
|
||||
this->write_command_(DISPLAY_ON);
|
||||
});
|
||||
}
|
||||
|
||||
void ST7701S::dump_config() {
|
||||
ESP_LOGCONFIG("", "ST7701S RGB LCD");
|
||||
ESP_LOGCONFIG(TAG, " Height: %u", this->height_);
|
||||
ESP_LOGCONFIG(TAG, " Width: %u", this->width_);
|
||||
LOG_PIN(" CS Pin: ", this->cs_);
|
||||
LOG_PIN(" DC Pin: ", this->dc_pin_);
|
||||
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());
|
||||
ESP_LOGCONFIG(TAG, " SPI Data rate: %dMHz", (unsigned) (this->data_rate_ / 1000000));
|
||||
}
|
||||
|
||||
} // namespace st7701s
|
||||
} // namespace esphome
|
||||
#endif // USE_ESP32_VARIANT_ESP32S3
|
115
esphome/components/st7701s/st7701s.h
Normal file
115
esphome/components/st7701s/st7701s.h
Normal file
|
@ -0,0 +1,115 @@
|
|||
//
|
||||
// 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/components/spi/spi.h"
|
||||
#include "esphome/components/display/display.h"
|
||||
#include "esp_lcd_panel_ops.h"
|
||||
|
||||
#include "esp_lcd_panel_rgb.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace st7701s {
|
||||
|
||||
constexpr static const char *const TAG = "display.st7701s";
|
||||
const uint8_t SW_RESET_CMD = 0x01;
|
||||
const uint8_t SLEEP_OUT = 0x11;
|
||||
const uint8_t SDIR_CMD = 0xC7;
|
||||
const uint8_t MADCTL_CMD = 0x36;
|
||||
const uint8_t INVERT_OFF = 0x20;
|
||||
const uint8_t INVERT_ON = 0x21;
|
||||
const uint8_t DISPLAY_ON = 0x29;
|
||||
const uint8_t CMD2_BKSEL = 0xFF;
|
||||
const uint8_t CMD2_BK0[5] = {0x77, 0x01, 0x00, 0x00, 0x10};
|
||||
|
||||
class ST7701S : public display::Display,
|
||||
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW, spi::CLOCK_PHASE_LEADING,
|
||||
spi::DATA_RATE_1MHZ> {
|
||||
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;
|
||||
|
||||
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_dc_pin(GPIOPin *dc_pin) { this->dc_pin_ = dc_pin; }
|
||||
void set_reset_pin(GPIOPin *reset_pin) { this->reset_pin_ = reset_pin; }
|
||||
void set_width(uint16_t width) { this->width_ = width; }
|
||||
void set_pclk_frequency(uint32_t pclk_frequency) { this->pclk_frequency_ = pclk_frequency; }
|
||||
void set_pclk_inverted(bool inverted) { this->pclk_inverted_ = inverted; }
|
||||
void set_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_init_sequence(const std::vector<uint8_t> &init_sequence) { this->init_sequence_ = init_sequence; }
|
||||
void set_mirror_x(bool mirror_x) { this->mirror_x_ = mirror_x; }
|
||||
void set_mirror_y(bool mirror_y) { this->mirror_y_ = mirror_y; }
|
||||
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; }
|
||||
int get_width_internal() override { return this->width_; }
|
||||
int get_height_internal() override { return this->height_; }
|
||||
void dump_config() override;
|
||||
void draw_pixel_at(int x, int y, Color color) override;
|
||||
|
||||
// this will be horribly slow.
|
||||
protected:
|
||||
void write_command_(uint8_t value);
|
||||
void write_data_(uint8_t value);
|
||||
void write_sequence_(uint8_t cmd, size_t len, const uint8_t *bytes);
|
||||
void write_init_sequence_();
|
||||
|
||||
InternalGPIOPin *de_pin_{nullptr};
|
||||
InternalGPIOPin *pclk_pin_{nullptr};
|
||||
InternalGPIOPin *hsync_pin_{nullptr};
|
||||
InternalGPIOPin *vsync_pin_{nullptr};
|
||||
GPIOPin *reset_pin_{nullptr};
|
||||
GPIOPin *dc_pin_{nullptr};
|
||||
InternalGPIOPin *data_pins_[16] = {};
|
||||
uint16_t hsync_pulse_width_ = 10;
|
||||
uint16_t hsync_back_porch_ = 10;
|
||||
uint16_t hsync_front_porch_ = 20;
|
||||
uint16_t vsync_pulse_width_ = 10;
|
||||
uint16_t vsync_back_porch_ = 10;
|
||||
uint16_t vsync_front_porch_ = 10;
|
||||
std::vector<uint8_t> init_sequence_;
|
||||
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};
|
||||
bool mirror_x_{};
|
||||
bool mirror_y_{};
|
||||
|
||||
esp_lcd_panel_handle_t handle_{};
|
||||
};
|
||||
|
||||
} // namespace st7701s
|
||||
} // namespace esphome
|
||||
#endif
|
40
tests/components/rpi_dpi_rgb/test.esp32-s3-idf.yaml
Normal file
40
tests/components/rpi_dpi_rgb/test.esp32-s3-idf.yaml
Normal file
|
@ -0,0 +1,40 @@
|
|||
psram:
|
||||
mode: octal
|
||||
speed: 80MHz
|
||||
display:
|
||||
- platform: rpi_dpi_rgb
|
||||
update_interval: never
|
||||
auto_clear_enabled: false
|
||||
id: rpi_display
|
||||
color_order: RGB
|
||||
rotation: 90
|
||||
dimensions:
|
||||
width: 800
|
||||
height: 480
|
||||
de_pin:
|
||||
number: 40
|
||||
hsync_pin: 39
|
||||
vsync_pin: 41
|
||||
pclk_pin: 42
|
||||
data_pins:
|
||||
red:
|
||||
- number: 45 # r1
|
||||
ignore_strapping_warning: true
|
||||
- 48 # r2
|
||||
- 47 # r3
|
||||
- 21 # r4
|
||||
- number: 14 # r5
|
||||
ignore_strapping_warning: false
|
||||
green:
|
||||
- 5 # g0
|
||||
- 6 # g1
|
||||
- 7 # g2
|
||||
- 15 # g3
|
||||
- 16 # g4
|
||||
- 4 # g5
|
||||
blue:
|
||||
- 8 # b1
|
||||
- 3 # b2
|
||||
- 46 # b3
|
||||
- 9 # b4
|
||||
- 1 # b5
|
60
tests/components/st7701s/test.esp32-s3-idf.yaml
Normal file
60
tests/components/st7701s/test.esp32-s3-idf.yaml
Normal file
|
@ -0,0 +1,60 @@
|
|||
psram:
|
||||
mode: octal
|
||||
speed: 80MHz
|
||||
spi:
|
||||
- id: lcd_spi
|
||||
clk_pin: 41
|
||||
mosi_pin: 48
|
||||
|
||||
i2c:
|
||||
sda: 39
|
||||
scl: 40
|
||||
scan: false
|
||||
id: bus_a
|
||||
|
||||
pca9554:
|
||||
- id: p_c_a
|
||||
pin_count: 16
|
||||
address: 0x20
|
||||
|
||||
display:
|
||||
- platform: st7701s
|
||||
spi_mode: MODE3
|
||||
color_order: RGB
|
||||
dimensions:
|
||||
width: 480
|
||||
height: 480
|
||||
invert_colors: true
|
||||
transform:
|
||||
mirror_x: true
|
||||
mirror_y: true
|
||||
cs_pin:
|
||||
pca9554: p_c_a
|
||||
number: 4
|
||||
reset_pin:
|
||||
pca9554: p_c_a
|
||||
number: 5
|
||||
de_pin: 18
|
||||
hsync_pin: 16
|
||||
vsync_pin: 17
|
||||
pclk_pin: 21
|
||||
init_sequence: 1
|
||||
data_pins:
|
||||
- number: 0
|
||||
ignore_strapping_warning: true
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
- number: 4
|
||||
ignore_strapping_warning: false
|
||||
- 5
|
||||
- 6
|
||||
- 7
|
||||
- 8
|
||||
- 9
|
||||
- 10
|
||||
- 11
|
||||
- 12
|
||||
- 13
|
||||
- 14
|
||||
- 15
|
Loading…
Reference in a new issue