Compare commits

...
15 changed files with 139 additions and 172 deletions

No files matched your search

+1 -4
View File
@@ -220,10 +220,7 @@ def get_display_metadata(display_id: ID) -> DisplayMetaData:
height=dimensions[1],
has_hardware_rotation=False,
byte_order=disp_config.get(CONF_BYTE_ORDER, cv.UNDEFINED),
has_writer=disp_config.get(CONF_AUTO_CLEAR_ENABLED) is True
or disp_config.get(CONF_PAGES) is not None
or disp_config.get(CONF_LAMBDA) is not None
or disp_config.get(CONF_SHOW_TEST_CARD) is True,
has_writer=config_has_writer(disp_config),
rotation=disp_config.get(CONF_ROTATION, 0),
draw_rounding=disp_config.get(CONF_DRAW_ROUNDING, 0),
)
+1 -5
View File
@@ -15,7 +15,6 @@ from esphome.components.mipi import (
import esphome.config_validation as cv
from esphome.config_validation import update_interval
from esphome.const import (
CONF_AUTO_CLEAR_ENABLED,
CONF_BUSY_PIN,
CONF_CS_PIN,
CONF_DATA_RATE,
@@ -189,10 +188,7 @@ def customise_schema(config):
height,
has_hardware_rotation=True,
byte_order=cv.UNDEFINED,
has_writer=config.get(CONF_AUTO_CLEAR_ENABLED) is True
or config.get(CONF_PAGES) is not None
or config.get(CONF_LAMBDA) is not None
or config.get(CONF_SHOW_TEST_CARD) is True,
has_writer=display.config_has_writer(config),
rotation=config.get(CONF_ROTATION, 0),
draw_rounding=0,
)
+9 -16
View File
@@ -1,4 +1,5 @@
#include "epaper_spi.h"
#include "esphome/components/display/init_sequence.h"
#include <cinttypes>
#include "esphome/core/application.h"
#include "esphome/core/helpers.h"
@@ -315,26 +316,18 @@ void EPaperBase::start_data_() {
void EPaperBase::on_safe_shutdown() { this->deep_sleep(); }
void EPaperBase::send_init_sequence_(const uint8_t *sequence, size_t length) {
size_t index = 0;
while (index != length) {
if (length - index < 2) {
display::InitSequenceReader reader(sequence, length);
display::InitSequenceEntry entry;
while (auto step = reader.next(entry)) {
if (step == display::INIT_SEQUENCE_STEP_MALFORMED) {
this->mark_failed(LOG_STR("Malformed init sequence"));
return;
}
const uint8_t cmd = sequence[index++];
if (const uint8_t x = sequence[index++]; x == DELAY_FLAG) {
ESP_LOGV(TAG, "Delay %dms", cmd);
delay(cmd);
if (step == display::INIT_SEQUENCE_STEP_DELAY) {
ESP_LOGV(TAG, "Delay %dms", entry.value);
delay(entry.value);
} else {
const uint8_t num_args = x & 0x7F;
if (length - index < num_args) {
ESP_LOGE(TAG, "Malformed init sequence, cmd = %X, num_args = %u", cmd, num_args);
this->mark_failed();
return;
}
this->cmd_data(cmd, sequence + index, num_args);
index += num_args;
this->cmd_data(entry.value, entry.args, entry.count);
}
}
}
@@ -34,7 +34,6 @@ static constexpr uint32_t MAX_TRANSFER_TIME = 10; // Transfer in 10ms blocks to
static constexpr size_t MAX_TRANSFER_SIZE = 128;
// Longer busy waits are the waveform or a stuck pin, and are polled at the normal loop pace
static constexpr uint32_t FAST_BUSY_POLL_MS = 50;
static constexpr uint8_t DELAY_FLAG = 0xFF;
class EPaperBase : public Display,
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW, spi::CLOCK_PHASE_LEADING,
+1 -3
View File
@@ -38,7 +38,6 @@ from esphome.components.mipi import (
)
import esphome.config_validation as cv
from esphome.const import (
CONF_AUTO_CLEAR_ENABLED,
CONF_COLOR_ORDER,
CONF_DIMENSIONS,
CONF_ENABLE_PIN,
@@ -167,8 +166,7 @@ def _config_schema(config: ConfigType) -> ConfigType:
height,
has_hardware_rotation=False,
byte_order=config[CONF_BYTE_ORDER],
has_writer=requires_buffer(config)
or config.get(CONF_AUTO_CLEAR_ENABLED) is True,
has_writer=display.config_has_writer(config),
rotation=config.get(CONF_ROTATION, 0),
draw_rounding=config.get(CONF_DRAW_ROUNDING, 0),
)
+29 -37
View File
@@ -1,6 +1,7 @@
#ifdef USE_ESP32_VARIANT_ESP32P4
#include <utility>
#include "mipi_dsi.h"
#include "esphome/components/display/init_sequence.h"
#include "esphome/core/helpers.h"
#include <algorithm>
@@ -123,47 +124,38 @@ void MipiDsi::setup() {
this->smark_failed(LOG_STR("esp_lcd_init failed"), err);
return;
}
size_t index = 0;
const uint8_t *seq = this->init_sequence_;
const size_t len = this->init_sequence_len_;
while (index != len) {
if (len - index < 2) {
display::InitSequenceReader reader(this->init_sequence_, this->init_sequence_len_);
display::InitSequenceEntry entry;
while (auto step = reader.next(entry)) {
if (step == display::INIT_SEQUENCE_STEP_MALFORMED) {
this->mark_failed(LOG_STR("Malformed init sequence"));
return;
}
uint8_t cmd = seq[index++];
uint8_t x = seq[index++];
if (x == DELAY_FLAG) {
ESP_LOGD(TAG, "Delay %dms", cmd);
delay(cmd);
} else {
uint8_t num_args = x & 0x7F;
if (len - index < num_args) {
this->mark_failed(LOG_STR("Malformed init sequence"));
return;
}
if (cmd == SLEEP_OUT) {
// are we ready, boots?
int duration = when - millis();
if (duration > 0) {
delay(duration);
}
}
const auto *ptr = seq + index;
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
char hex_buf[format_hex_pretty_size(MIPI_DSI_MAX_CMD_LOG_BYTES)];
#endif
ESP_LOGVV(TAG, "Command %02X, length %d, byte(s) %s", cmd, num_args,
format_hex_pretty_to(hex_buf, ptr, num_args, '.'));
err = esp_lcd_panel_io_tx_param(this->io_handle_, cmd, ptr, num_args);
if (err != ESP_OK) {
this->smark_failed(LOG_STR("lcd_panel_io_tx_param failed"), err);
return;
}
index += num_args;
if (cmd == SLEEP_OUT)
delay(10);
if (step == display::INIT_SEQUENCE_STEP_DELAY) {
ESP_LOGD(TAG, "Delay %dms", entry.value);
delay(entry.value);
continue;
}
const uint8_t cmd = entry.value;
if (cmd == SLEEP_OUT) {
// are we ready, boots?
int duration = when - millis();
if (duration > 0) {
delay(duration);
}
}
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
char hex_buf[format_hex_pretty_size(MIPI_DSI_MAX_CMD_LOG_BYTES)];
#endif
ESP_LOGVV(TAG, "Command %02X, length %d, byte(s) %s", cmd, entry.count,
format_hex_pretty_to(hex_buf, entry.args, entry.count, '.'));
err = esp_lcd_panel_io_tx_param(this->io_handle_, cmd, entry.args, entry.count);
if (err != ESP_OK) {
this->smark_failed(LOG_STR("lcd_panel_io_tx_param failed"), err);
return;
}
if (cmd == SLEEP_OUT)
delay(10);
}
this->io_lock_ = xSemaphoreCreateBinary();
esp_lcd_dpi_panel_event_callbacks_t cbs = {
-1
View File
@@ -31,7 +31,6 @@ 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;
+1 -3
View File
@@ -50,7 +50,6 @@ from esphome.components.spi import (
)
import esphome.config_validation as cv
from esphome.const import (
CONF_AUTO_CLEAR_ENABLED,
CONF_BLUE,
CONF_COLOR_ORDER,
CONF_CS_PIN,
@@ -243,8 +242,7 @@ def _config_schema(config: ConfigType) -> ConfigType:
height,
has_hardware_rotation=True,
byte_order=config[CONF_BYTE_ORDER],
has_writer=requires_buffer(config)
or config.get(CONF_AUTO_CLEAR_ENABLED) is True,
has_writer=display.config_has_writer(config),
rotation=config.get(CONF_ROTATION, 0),
draw_rounding=config.get(CONF_DRAW_ROUNDING, 0),
)
+20 -35
View File
@@ -1,5 +1,6 @@
#if defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32P4) || defined(USE_ESP32_VARIANT_ESP32S31)
#include "mipi_rgb.h"
#include "esphome/components/display/init_sequence.h"
#include "esphome/core/gpio.h"
#include "esphome/core/hal.h"
#include "esphome/core/helpers.h"
@@ -13,8 +14,6 @@
namespace esphome::mipi_rgb {
static const uint8_t DELAY_FLAG = 0xFF;
// Maximum bytes to log for init commands (truncated if larger)
static constexpr size_t MIPI_RGB_MAX_CMD_LOG_BYTES = 64;
static constexpr uint8_t MADCTL_MY = 0x80; // Bit 7 Bottom to top
@@ -75,44 +74,30 @@ void MipiRgbSpi::write_data_(uint8_t value) {
this->disable();
}
/**
* this relies upon the init sequence being well-formed, which is guaranteed by the Python init code.
*/
void MipiRgbSpi::write_init_sequence_() {
size_t index = 0;
const uint8_t *seq = this->init_sequence_;
const size_t len = this->init_sequence_len_;
while (index != len) {
if (len - index < 2) {
display::InitSequenceReader reader(this->init_sequence_, this->init_sequence_len_);
display::InitSequenceEntry entry;
while (auto step = reader.next(entry)) {
if (step == display::INIT_SEQUENCE_STEP_MALFORMED) {
this->mark_failed(LOG_STR("Malformed init sequence"));
return;
}
uint8_t cmd = seq[index++];
uint8_t x = seq[index++];
if (x == DELAY_FLAG) {
ESP_LOGD(TAG, "Delay %dms", cmd);
delay(cmd);
} else {
uint8_t num_args = x & 0x7F;
if (len - index < num_args) {
this->mark_failed(LOG_STR("Malformed init sequence"));
return;
}
if (cmd == SLEEP_OUT) {
delay(120); // NOLINT
}
const auto *ptr = seq + index;
char hex_buf[format_hex_pretty_size(MIPI_RGB_MAX_CMD_LOG_BYTES)];
ESP_LOGD(TAG, "Write command %02X, length %d, byte(s) %s", cmd, num_args,
format_hex_pretty_to(hex_buf, ptr, num_args, '.'));
index += num_args;
this->write_command_(cmd);
while (num_args-- != 0)
this->write_data_(*ptr++);
if (cmd == SLEEP_OUT)
delay(10);
if (step == display::INIT_SEQUENCE_STEP_DELAY) {
ESP_LOGD(TAG, "Delay %dms", entry.value);
delay(entry.value);
continue;
}
const uint8_t cmd = entry.value;
if (cmd == SLEEP_OUT)
delay(120); // NOLINT
char hex_buf[format_hex_pretty_size(MIPI_RGB_MAX_CMD_LOG_BYTES)];
ESP_LOGD(TAG, "Write command %02X, length %d, byte(s) %s", cmd, entry.count,
format_hex_pretty_to(hex_buf, entry.args, entry.count, '.'));
this->write_command_(cmd);
for (uint8_t i = 0; i < entry.count; i++)
this->write_data_(entry.args[i]);
if (cmd == SLEEP_OUT)
delay(10);
}
// this->spi_teardown(); // SPI not needed after this
delay(10);
+14 -4
View File
@@ -33,7 +33,6 @@ from esphome.components.spi import CONF_SPI_MODE, TYPE_OCTAL, TYPE_QUAD, TYPE_SI
import esphome.config_validation as cv
from esphome.config_validation import ALLOW_EXTRA
from esphome.const import (
CONF_AUTO_CLEAR_ENABLED,
CONF_BRIGHTNESS,
CONF_BUFFER_SIZE,
CONF_COLOR_ORDER,
@@ -73,6 +72,10 @@ Model = mipi_spi_ns.enum("Model")
PixelMode = mipi_spi_ns.enum("PixelMode")
BusType = mipi_spi_ns.enum("BusType")
# Matches INIT_SEQUENCE_MAX_ARGS, the flash copy buffer in mipi_spi.h
INIT_SEQUENCE_MAX_ARGS = 128
COLOR_ORDERS = {
MODE_RGB: ColorOrder.COLOR_ORDER_RGB,
MODE_BGR: ColorOrder.COLOR_ORDER_BGR,
@@ -216,7 +219,15 @@ def model_schema(config: ConfigType) -> cv.All | cv.Schema:
bus_mode, lower=True
),
cv.Required(CONF_MODEL): cv.one_of(model.name, upper=True),
iseqconf: cv.ensure_list(map_sequence),
iseqconf: cv.ensure_list(
cv.All(
map_sequence,
cv.Length(
max=INIT_SEQUENCE_MAX_ARGS + 1,
msg=f"An init sequence command can have at most {INIT_SEQUENCE_MAX_ARGS} data bytes",
),
)
),
cv.Optional(CONF_BUFFER_SIZE): cv.All(
cv.percentage, cv.Range(0.12, 1.0)
),
@@ -290,8 +301,7 @@ def customise_schema(config: ConfigType) -> ConfigType:
height,
has_hardware_transform,
byte_order=config[CONF_BYTE_ORDER],
has_writer=requires_buffer(config)
or config.get(CONF_AUTO_CLEAR_ENABLED) is True,
has_writer=display.config_has_writer(config),
rotation=config.get(CONF_ROTATION, 0),
draw_rounding=config.get(CONF_DRAW_ROUNDING, 0),
)
+18 -28
View File
@@ -7,12 +7,16 @@
#include "esphome/components/spi/spi.h"
#include "esphome/components/display/display.h"
#include "esphome/components/display/display_color_utils.h"
#include "esphome/components/display/init_sequence.h"
#include "esphome/core/hal.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
namespace esphome::mipi_spi {
/// Largest argument count in one init command; display.py rejects longer ones.
static constexpr size_t INIT_SEQUENCE_MAX_ARGS = 128;
ESPHOME_LOG_TAG(TAG, "display.mipi_spi");
// Maximum bytes to log for commands (truncated if larger)
@@ -48,7 +52,6 @@ static constexpr uint8_t MADCTL_XFLIP = 0x02; // Mirror the display horiz
static constexpr uint8_t MADCTL_YFLIP = 0x01; // Mirror the display vertically
static constexpr uint16_t MADCTL_FLIP_FLAG = 0x100; // controller uses axis flip bits
static constexpr uint8_t DELAY_FLAG = 0xFF;
// store a 16 bit value in a buffer, big endian.
static inline void put16_be(uint8_t *buf, uint16_t value) {
buf[0] = value >> 8;
@@ -163,36 +166,23 @@ class MipiSpi : public display::Display,
// need to know when the display is ready for SLPOUT command - will be 120ms after reset
auto when = millis() + 120;
size_t index = 0;
const uint8_t *seq = this->init_sequence_;
const size_t len = this->init_sequence_len_;
while (index != len) {
if (len - index < 2) {
esph_log_e(TAG, "Malformed init sequence");
this->mark_failed();
display::InitSequenceReader reader(this->init_sequence_, this->init_sequence_len_);
display::InitSequenceEntry entry;
// The sequence is in flash, which SPI DMA (and ESP8266 byte loads) cannot read
uint8_t args[INIT_SEQUENCE_MAX_ARGS];
while (auto step = reader.next(entry, sizeof(args))) {
if (step == display::INIT_SEQUENCE_STEP_MALFORMED) {
this->mark_failed(LOG_STR("Malformed init sequence"));
return;
}
uint8_t cmd = progmem_read_byte(seq + index++);
uint8_t x = progmem_read_byte(seq + index++);
if (x == DELAY_FLAG) {
if (cmd == 0) {
cmd = clamp_at_least((int) (when - millis()), 0);
}
esph_log_d(TAG, "Delay %dms", cmd);
delay(cmd);
} else {
uint8_t num_args = x & 0x7F;
if (len - index < num_args) {
esph_log_e(TAG, "Malformed init sequence");
this->mark_failed();
return;
}
// The sequence is in flash, which SPI DMA (and ESP8266 byte loads) cannot read
uint8_t args[0x80];
progmem_memcpy(args, seq + index, num_args);
this->write_command_(cmd, args, num_args);
index += num_args;
if (step == display::INIT_SEQUENCE_STEP_DELAY) {
const uint8_t ms = entry.value != 0 ? entry.value : clamp_at_least((int) (when - millis()), 0);
esph_log_d(TAG, "Delay %dms", ms);
delay(ms);
continue;
}
progmem_memcpy(args, entry.args, entry.count);
this->write_command_(entry.value, args, entry.count);
}
this->reset_params_();
// Marks init as done, so later commands log at verbose level instead of debug
+1 -8
View File
@@ -4,7 +4,6 @@ import pkgutil
from esphome import pins
import esphome.codegen as cg
from esphome.components import display, spi
from esphome.components.display import CONF_SHOW_TEST_CARD
from esphome.components.mipi import (
DriverChip,
flatten_sequence,
@@ -13,16 +12,13 @@ from esphome.components.mipi import (
)
import esphome.config_validation as cv
from esphome.const import (
CONF_AUTO_CLEAR_ENABLED,
CONF_CS_PIN,
CONF_DATA_RATE,
CONF_DC_PIN,
CONF_HEIGHT,
CONF_ID,
CONF_INIT_SEQUENCE,
CONF_LAMBDA,
CONF_MODEL,
CONF_PAGES,
CONF_RESET_PIN,
CONF_ROTATION,
CONF_WIDTH,
@@ -182,10 +178,7 @@ def customise_schema(config: ConfigType) -> ConfigType:
config[CONF_HEIGHT],
has_hardware_rotation=False,
byte_order=cv.UNDEFINED,
has_writer=config.get(CONF_AUTO_CLEAR_ENABLED) is True
or config.get(CONF_PAGES) is not None
or config.get(CONF_LAMBDA) is not None
or config.get(CONF_SHOW_TEST_CARD) is True,
has_writer=display.config_has_writer(config),
rotation=config.get(CONF_ROTATION, 0),
)
return config
+15 -22
View File
@@ -56,33 +56,26 @@ void ST7305::reset_() {
}
void ST7305::send_init_sequence_() {
while (this->init_index_ < this->init_sequence_length_) {
if (this->init_sequence_length_ - this->init_index_ < 2) {
this->mark_failed(LOG_STR("Malformed init sequence"));
return;
}
const uint8_t command = progmem_read_byte(&this->init_sequence_[this->init_index_++]);
const uint8_t count = progmem_read_byte(&this->init_sequence_[this->init_index_++]);
if (count == 0xFF) {
// Resume the sequence after the delay instead of blocking the main loop.
this->set_timeout(STARTUP_TIMEOUT_ID, command, [this]() { this->send_init_sequence_(); });
return;
}
if (this->init_sequence_length_ - this->init_index_ < count) {
this->mark_failed(LOG_STR("Malformed init sequence"));
return;
}
display::InitSequenceEntry entry;
display::InitSequenceStep step;
while ((step = this->init_reader_.next(entry)) == display::INIT_SEQUENCE_STEP_COMMAND) {
this->enable();
this->send_command_(command);
if (count != 0) {
this->send_command_(entry.value);
if (entry.count != 0) {
this->dc_pin_->digital_write(true);
for (uint8_t i = 0; i < count; i++)
this->write_byte(progmem_read_byte(&this->init_sequence_[this->init_index_ + i]));
for (uint8_t i = 0; i < entry.count; i++)
this->write_byte(progmem_read_byte(entry.args + i));
}
this->disable();
this->init_index_ += count;
}
this->finish_init_();
if (step == display::INIT_SEQUENCE_STEP_DELAY) {
// Resume the sequence after the delay instead of blocking the main loop.
this->set_timeout(STARTUP_TIMEOUT_ID, entry.value, [this]() { this->send_init_sequence_(); });
} else if (step == display::INIT_SEQUENCE_STEP_MALFORMED) {
this->mark_failed(LOG_STR("Malformed init sequence"));
} else {
this->finish_init_();
}
}
void ST7305::finish_init_() {
+3 -5
View File
@@ -4,6 +4,7 @@
#include <cstdint>
#include "esphome/components/display/display_buffer.h"
#include "esphome/components/display/init_sequence.h"
#include "esphome/components/spi/spi.h"
#include "esphome/core/component.h"
#include "esphome/core/hal.h"
@@ -35,8 +36,7 @@ class ST7305 : public display::DisplayBuffer,
row_start_(row_start),
row_end_(row_end),
buffer_length_(static_cast<size_t>(width) * static_cast<size_t>(height) / 8),
init_sequence_(init_sequence),
init_sequence_length_(init_sequence_length) {}
init_reader_(init_sequence, init_sequence_length) {}
void setup() override;
void update() override;
@@ -113,9 +113,7 @@ class ST7305 : public display::DisplayBuffer,
uint8_t row_start_;
uint8_t row_end_;
size_t buffer_length_;
const uint8_t *init_sequence_;
size_t init_sequence_length_;
size_t init_index_{0};
display::InitSequenceReader init_reader_;
bool ready_{false};
int x_low_{0};
@@ -93,6 +93,16 @@ def run_schema_validation(config: ConfigType) -> None:
r"value must be a power of two for dictionary value @ data\['draw_rounding'\]",
id="invalid_draw_rounding",
),
pytest.param(
{
"model": "custom",
"dc_pin": 18,
"dimensions": {"width": 320, "height": 240},
"init_sequence": [[0xE0] + [0x00] * 129],
},
r"at most 128 data bytes",
id="init_command_too_long",
),
],
)
def test_basic_configuration_errors(
@@ -606,3 +616,19 @@ def test_identical_init_sequences_share_one_table(
assert first is not None and second is not None
assert first.group(1) == second.group(1)
assert main_cpp.count("mipi_spi_init_sequence[] PROGMEM") == 1
def test_init_command_at_buffer_limit(set_core_config: SetCoreConfigCallable) -> None:
"""A command with exactly 128 data bytes fits the C++ copy buffer."""
set_core_config(
PlatformFramework.ESP32_IDF,
platform_data={KEY_BOARD: "esp32dev", KEY_VARIANT: VARIANT_ESP32},
)
run_schema_validation(
{
"model": "custom",
"dc_pin": 18,
"dimensions": {"width": 320, "height": 240},
"init_sequence": [[0xE0] + [0x00] * 128],
}
)