mirror of
https://github.com/esphome/esphome.git
synced 2026-10-07 11:26:39 +00:00
[mipi_spi] Keep the init sequence in flash (#20089)
This commit is contained in:
@@ -401,7 +401,8 @@ async def to_code(config: ConfigType) -> None:
|
||||
init_sequence = model.get_sequence(config, add_madctl=False, add_reset=True)
|
||||
var_id.type, templateargs = get_instance(config)
|
||||
var = cg.new_Pvariable(var_id, TemplateArguments(*templateargs))
|
||||
cg.add(var.set_init_sequence(init_sequence))
|
||||
table = cg.shared_progmem_array("mipi_spi_init_sequence", cg.uint8, init_sequence)
|
||||
cg.add(var.set_init_sequence(table, len(init_sequence)))
|
||||
cg.add(var.set_model(config[CONF_MODEL]))
|
||||
if enable_pin := config.get(CONF_ENABLE_PIN):
|
||||
enable = [await cg.gpio_pin_expression(pin) for pin in enable_pin]
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "esphome/components/spi/spi.h"
|
||||
#include "esphome/components/display/display.h"
|
||||
#include "esphome/components/display/display_color_utils.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
namespace esphome::mipi_spi {
|
||||
@@ -133,7 +134,10 @@ class MipiSpi : public display::Display,
|
||||
return HEIGHT;
|
||||
}
|
||||
|
||||
void set_init_sequence(const std::vector<uint8_t> &sequence) { this->init_sequence_ = sequence; }
|
||||
void set_init_sequence(const uint8_t *sequence, size_t len) {
|
||||
this->init_sequence_ = sequence;
|
||||
this->init_sequence_len_ = len;
|
||||
}
|
||||
|
||||
// reset the display, and write the init sequence
|
||||
void setup() override {
|
||||
@@ -159,15 +163,16 @@ 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;
|
||||
auto &vec = this->init_sequence_;
|
||||
while (index != vec.size()) {
|
||||
if (vec.size() - index < 2) {
|
||||
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();
|
||||
return;
|
||||
}
|
||||
uint8_t cmd = vec[index++];
|
||||
uint8_t x = vec[index++];
|
||||
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);
|
||||
@@ -176,19 +181,21 @@ class MipiSpi : public display::Display,
|
||||
delay(cmd);
|
||||
} else {
|
||||
uint8_t num_args = x & 0x7F;
|
||||
if (vec.size() - index < num_args) {
|
||||
if (len - index < num_args) {
|
||||
esph_log_e(TAG, "Malformed init sequence");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
const auto *ptr = vec.data() + index;
|
||||
this->write_command_(cmd, ptr, num_args);
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
this->reset_params_();
|
||||
// init sequence no longer needed
|
||||
this->init_sequence_.clear();
|
||||
// Marks init as done, so later commands log at verbose level instead of debug
|
||||
this->init_sequence_len_ = 0;
|
||||
}
|
||||
|
||||
// Drawing operations
|
||||
@@ -236,7 +243,7 @@ class MipiSpi : public display::Display,
|
||||
void write_command_(uint8_t cmd, const uint8_t *bytes, size_t len) {
|
||||
char hex_buf[format_hex_pretty_size(MIPI_SPI_MAX_CMD_LOG_BYTES)];
|
||||
// Don't spam the log after setup
|
||||
if (this->init_sequence_.empty()) {
|
||||
if (this->init_sequence_len_ == 0) {
|
||||
esph_log_v(TAG, "Command %02X, length %d, bytes %s", cmd, len, format_hex_pretty_to(hex_buf, bytes, len));
|
||||
} else {
|
||||
esph_log_d(TAG, "Command %02X, length %d, bytes %s", cmd, len, format_hex_pretty_to(hex_buf, bytes, len));
|
||||
@@ -486,7 +493,9 @@ class MipiSpi : public display::Display,
|
||||
bool invert_colors_{};
|
||||
optional<uint8_t> brightness_{};
|
||||
const char *model_{"Unknown"};
|
||||
std::vector<uint8_t> init_sequence_{};
|
||||
// Shared PROGMEM table
|
||||
const uint8_t *init_sequence_{nullptr};
|
||||
size_t init_sequence_len_{0};
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
import re
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
@@ -33,6 +34,18 @@ from esphome.types import ConfigType
|
||||
from tests.component_tests.types import SetCoreConfigCallable
|
||||
|
||||
|
||||
def _init_sequence(main_cpp: str, display_id: str = r"\w+") -> str:
|
||||
"""Return the shared PROGMEM init sequence table passed to a display, as rendered."""
|
||||
match = re.search(rf"{display_id}->set_init_sequence\((\w+), \d+\);", main_cpp)
|
||||
assert match is not None
|
||||
table = re.search(
|
||||
rf"static constexpr uint8_t {match.group(1)}\[\] PROGMEM = (\{{[^;]*\}});",
|
||||
main_cpp,
|
||||
)
|
||||
assert table is not None
|
||||
return table.group(1)
|
||||
|
||||
|
||||
def run_schema_validation(config: ConfigType) -> None:
|
||||
"""Run schema validation on a configuration."""
|
||||
FINAL_VALIDATE_SCHEMA(CONFIG_SCHEMA(config))
|
||||
@@ -362,7 +375,7 @@ def test_native_generation(
|
||||
in main_cpp
|
||||
)
|
||||
# A 10ms post-reset delay ({10, 255}) is prepended ahead of the model commands.
|
||||
assert "set_init_sequence({10, 255, 240, 1, 8, 242" in main_cpp
|
||||
assert _init_sequence(main_cpp).startswith("{10, 255, 240, 1, 8, 242")
|
||||
assert "show_test_card();" in main_cpp
|
||||
assert "set_write_only(true);" in main_cpp
|
||||
|
||||
@@ -379,7 +392,7 @@ def test_lvgl_generation(
|
||||
in main_cpp
|
||||
)
|
||||
# A 10ms post-reset delay ({10, 255}) is prepended ahead of the model commands.
|
||||
assert "set_init_sequence({10, 255, 177, 3, 1, 44, 45, 178" in main_cpp
|
||||
assert _init_sequence(main_cpp).startswith("{10, 255, 177, 3, 1, 44, 45, 178")
|
||||
assert "show_test_card();" not in main_cpp
|
||||
assert "set_auto_clear(false);" in main_cpp
|
||||
|
||||
@@ -428,7 +441,9 @@ def test_swreset_prepended_without_reset_pin(
|
||||
|
||||
# SWRESET ({1, 0}) followed by a 10ms delay ({10, 255}) is inserted ahead of
|
||||
# the model's own commands.
|
||||
assert "swreset_display->set_init_sequence({1, 0, 10, 255, 160, 1, 1," in main_cpp
|
||||
assert _init_sequence(main_cpp, "swreset_display").startswith(
|
||||
"{1, 0, 10, 255, 160, 1, 1,"
|
||||
)
|
||||
|
||||
|
||||
def test_swreset_not_prepended_with_reset_pin(
|
||||
@@ -449,5 +464,29 @@ def test_swreset_not_prepended_with_reset_pin(
|
||||
main_cpp = generate_main(yaml_file)
|
||||
|
||||
# The delay ({10, 255}) is still present, but no leading SWRESET ({1, 0}).
|
||||
assert "hwreset_display->set_init_sequence({10, 255, 160, 1, 1," in main_cpp
|
||||
assert "hwreset_display->set_init_sequence({1, 0," not in main_cpp
|
||||
assert _init_sequence(main_cpp, "hwreset_display").startswith(
|
||||
"{10, 255, 160, 1, 1,"
|
||||
)
|
||||
|
||||
|
||||
def test_identical_init_sequences_share_one_table(
|
||||
generate_main: Callable[[str | Path], str],
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
"""Two displays with the same init sequence point at one PROGMEM table."""
|
||||
yaml_file = tmp_path / "shared.yaml"
|
||||
display = _SWRESET_YAML.split("display:\n", 1)[1]
|
||||
yaml_file.write_text(
|
||||
_SWRESET_YAML.format(display_id="first", reset_line=" reset_pin: 5")
|
||||
+ display.format(display_id="second", reset_line=" reset_pin: 6")
|
||||
.replace("cs_pin: 8", "cs_pin: 9")
|
||||
.replace("dc_pin: 4", "dc_pin: 7")
|
||||
)
|
||||
|
||||
main_cpp = generate_main(yaml_file)
|
||||
|
||||
first = re.search(r"first->set_init_sequence\((\w+), \d+\);", main_cpp)
|
||||
second = re.search(r"second->set_init_sequence\((\w+), \d+\);", main_cpp)
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user