mirror of
https://github.com/esphome/esphome.git
synced 2026-10-14 10:12:52 +00:00
Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f91b023422 | ||
|
|
ed639a9378 | ||
|
|
bac7799d9b | ||
|
|
40a4f6640a | ||
|
|
77ddc89b79 |
@@ -40,6 +40,7 @@ from esphome.cpp_generator import ( # noqa: F401
|
||||
progmem_array,
|
||||
safe_exp,
|
||||
set_cpp_standard,
|
||||
shared_progmem_array,
|
||||
statement,
|
||||
static_const_array,
|
||||
static_function,
|
||||
|
||||
@@ -173,13 +173,12 @@ async def to_code(config: ConfigType) -> None:
|
||||
await spi.register_spi_device(var, config, write_only=True)
|
||||
|
||||
chip = DriverChip.chips[config[CONF_MODEL]]
|
||||
if chip.initsequence:
|
||||
cg.add(var.add_init_sequence(chip.initsequence))
|
||||
if init_sequences := config.get(CONF_INIT_SEQUENCE):
|
||||
sequence = []
|
||||
for seq in init_sequences:
|
||||
sequence.extend(seq)
|
||||
cg.add(var.add_init_sequence(sequence))
|
||||
sequence = list(chip.initsequence)
|
||||
for seq in config.get(CONF_INIT_SEQUENCE, []):
|
||||
sequence.extend(seq)
|
||||
if sequence:
|
||||
table = cg.shared_progmem_array("qspi_dbi_init_sequence", cg.uint8, sequence)
|
||||
cg.add(var.set_init_sequence(table, len(sequence)))
|
||||
|
||||
cg.add(var.set_color_mode(config[CONF_COLOR_ORDER]))
|
||||
cg.add(var.set_invert_colors(config[CONF_INVERT_COLORS]))
|
||||
|
||||
@@ -110,8 +110,31 @@ void QspiDbi::reset_params_(bool ready) {
|
||||
}
|
||||
|
||||
void QspiDbi::write_init_sequence_() {
|
||||
for (const auto &seq : this->init_sequences_) {
|
||||
this->write_sequence_(seq);
|
||||
const uint8_t *seq = this->init_sequence_;
|
||||
const size_t len = this->init_sequence_len_;
|
||||
size_t index = 0;
|
||||
while (index != len) {
|
||||
if (len - index < 2) {
|
||||
ESP_LOGE(TAG, "Malformed init sequence");
|
||||
break;
|
||||
}
|
||||
uint8_t cmd = seq[index++];
|
||||
uint8_t x = seq[index++];
|
||||
if (x == DELAY_FLAG) {
|
||||
ESP_LOGV(TAG, "Delay %dms", cmd);
|
||||
delay(cmd);
|
||||
} else {
|
||||
uint8_t num_args = x & 0x7F;
|
||||
if (len - index < num_args) {
|
||||
ESP_LOGE(TAG, "Malformed init sequence");
|
||||
break;
|
||||
}
|
||||
// The sequence is in flash, which SPI DMA cannot read
|
||||
uint8_t args[0x80];
|
||||
memcpy(args, seq + index, num_args);
|
||||
this->write_command_(cmd, args, num_args);
|
||||
index += num_args;
|
||||
}
|
||||
}
|
||||
this->reset_params_(true);
|
||||
this->setup_complete_ = true;
|
||||
@@ -187,31 +210,6 @@ void QspiDbi::write_command_(uint8_t cmd, const uint8_t *bytes, size_t len) {
|
||||
this->disable();
|
||||
}
|
||||
|
||||
void QspiDbi::write_sequence_(const std::vector<uint8_t> &vec) {
|
||||
size_t index = 0;
|
||||
while (index != vec.size()) {
|
||||
if (vec.size() - index < 2) {
|
||||
ESP_LOGE(TAG, "Malformed init sequence");
|
||||
return;
|
||||
}
|
||||
uint8_t cmd = vec[index++];
|
||||
uint8_t x = vec[index++];
|
||||
if (x == DELAY_FLAG) {
|
||||
ESP_LOGV(TAG, "Delay %dms", cmd);
|
||||
delay(cmd);
|
||||
} else {
|
||||
uint8_t num_args = x & 0x7F;
|
||||
if (vec.size() - index < num_args) {
|
||||
ESP_LOGE(TAG, "Malformed init sequence");
|
||||
return;
|
||||
}
|
||||
const auto *ptr = vec.data() + index;
|
||||
this->write_command_(cmd, ptr, num_args);
|
||||
index += num_args;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void QspiDbi::dump_config() {
|
||||
ESP_LOGCONFIG("", "QSPI_DBI Display");
|
||||
ESP_LOGCONFIG("", "Model: %s", this->model_);
|
||||
|
||||
@@ -101,7 +101,10 @@ class QspiDbi final : public display::DisplayBuffer,
|
||||
int get_width_internal() override { return this->width_; }
|
||||
int get_height_internal() override { return this->height_; }
|
||||
bool can_proceed() override { return this->setup_complete_; }
|
||||
void add_init_sequence(const std::vector<uint8_t> &sequence) { this->init_sequences_.push_back(sequence); }
|
||||
void set_init_sequence(const uint8_t *sequence, size_t len) {
|
||||
this->init_sequence_ = sequence;
|
||||
this->init_sequence_len_ = len;
|
||||
}
|
||||
void set_draw_rounding(unsigned rounding) { this->draw_rounding_ = rounding; }
|
||||
|
||||
protected:
|
||||
@@ -109,7 +112,6 @@ class QspiDbi final : public display::DisplayBuffer,
|
||||
if (this->buffer_ == nullptr)
|
||||
this->init_internal_(this->width_ * this->height_ * 2);
|
||||
}
|
||||
void write_sequence_(const std::vector<uint8_t> &vec);
|
||||
void draw_absolute_pixel_internal(int x, int y, Color color) 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;
|
||||
@@ -162,7 +164,9 @@ class QspiDbi final : public display::DisplayBuffer,
|
||||
unsigned draw_rounding_{2};
|
||||
uint8_t brightness_{0xD0};
|
||||
const char *model_{"Unknown"};
|
||||
std::vector<std::vector<uint8_t>> init_sequences_{};
|
||||
// Shared PROGMEM table: the model's sequence followed by any custom one
|
||||
const uint8_t *init_sequence_{nullptr};
|
||||
size_t init_sequence_len_{0};
|
||||
|
||||
esp_lcd_panel_handle_t handle_{};
|
||||
};
|
||||
|
||||
@@ -19,7 +19,12 @@ from esphome.core import (
|
||||
TimePeriodNanoseconds,
|
||||
TimePeriodSeconds,
|
||||
)
|
||||
from esphome.helpers import cpp_string_escape, indent, indent_all_but_first_and_last
|
||||
from esphome.helpers import (
|
||||
cpp_string_escape,
|
||||
ensure_unique_string,
|
||||
indent,
|
||||
indent_all_but_first_and_last,
|
||||
)
|
||||
from esphome.types import Expression, SafeExpType, TemplateArgsType
|
||||
from esphome.util import OrderedDict
|
||||
from esphome.yaml_util import ESPHomeDataBase
|
||||
@@ -463,6 +468,36 @@ def progmem_array(id_, rhs) -> "MockObj":
|
||||
return obj
|
||||
|
||||
|
||||
def shared_progmem_array(
|
||||
name: str, type_: "MockObjClass", rhs: SafeExpType, *, share: bool = True
|
||||
) -> "MockObj":
|
||||
"""Emit a global PROGMEM array once per distinct type and contents; later calls reuse it.
|
||||
|
||||
The array is ``static constexpr``, so elements must be constant expressions and lambdas
|
||||
must be captureless. Its name is made unique against every config id and variable.
|
||||
``share=False`` always emits a new array, e.g. for lambdas that may keep static state.
|
||||
"""
|
||||
from esphome.config import iter_ids
|
||||
from esphome.config_validation import RESERVED_IDS
|
||||
|
||||
arrays: dict[str, MockObj] = CORE.data.setdefault("shared_progmem_array", {})
|
||||
rhs = safe_exp(rhs)
|
||||
key = f"{type_} {rhs}"
|
||||
if share and (array := arrays.get(key)) is not None:
|
||||
return array
|
||||
used = {str(i) for i, _ in iter_ids(CORE.config)}
|
||||
used |= {str(i) for i in CORE.variables}
|
||||
used |= set(RESERVED_IDS) | CORE.loaded_integrations
|
||||
id_ = ID(ensure_unique_string(name, used), is_declaration=True, type=type_)
|
||||
# Global, so any scope can use it; anything a lambda references is already declared.
|
||||
CORE.add_global(ProgmemAssignmentExpression(type_, id_, rhs))
|
||||
array = MockObj(id_, ".")
|
||||
CORE.register_variable(id_, array)
|
||||
if share:
|
||||
arrays[key] = array
|
||||
return array
|
||||
|
||||
|
||||
def static_const_array(id_, rhs) -> "MockObj":
|
||||
rhs = safe_exp(rhs)
|
||||
obj = MockObj(id_, ".")
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
"""Tests for the qspi_dbi init sequence codegen."""
|
||||
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
import re
|
||||
|
||||
_YAML = """
|
||||
esphome:
|
||||
name: qspi-dbi-test
|
||||
esp32:
|
||||
board: esp32-s3-devkitc-1
|
||||
framework:
|
||||
type: esp-idf
|
||||
psram:
|
||||
mode: octal
|
||||
spi:
|
||||
type: quad
|
||||
clk_pin: 47
|
||||
data_pins: [18, 7, 48, 5]
|
||||
display:
|
||||
- platform: qspi_dbi
|
||||
id: builtin
|
||||
model: RM690B0
|
||||
dimensions:
|
||||
width: 450
|
||||
height: 600
|
||||
cs_pin: 11
|
||||
- platform: qspi_dbi
|
||||
id: builtin_too
|
||||
model: RM690B0
|
||||
dimensions:
|
||||
width: 450
|
||||
height: 600
|
||||
cs_pin: 12
|
||||
- platform: qspi_dbi
|
||||
id: custom
|
||||
model: CUSTOM
|
||||
dimensions:
|
||||
width: 536
|
||||
height: 240
|
||||
cs_pin: 6
|
||||
init_sequence:
|
||||
- [0x3A, 0x66]
|
||||
- delay 120ms
|
||||
"""
|
||||
|
||||
|
||||
def test_init_sequence_is_shared_progmem_table(
|
||||
generate_main: Callable[[str | Path], str],
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
"""Model and custom commands form one PROGMEM table; identical displays share it."""
|
||||
yaml_file = tmp_path / "qspi_dbi.yaml"
|
||||
yaml_file.write_text(_YAML)
|
||||
|
||||
main_cpp = generate_main(yaml_file)
|
||||
|
||||
calls = {
|
||||
m[0]: (m[1], int(m[2]))
|
||||
for m in re.findall(r"(\w+)->set_init_sequence\((\w+), (\d+)\);", main_cpp)
|
||||
}
|
||||
assert calls["builtin"] == calls["builtin_too"]
|
||||
assert calls["custom"][0] != calls["builtin"][0]
|
||||
custom = re.search(
|
||||
rf"static constexpr uint8_t {calls['custom'][0]}\[\] PROGMEM = \{{([^;]*)\}};",
|
||||
main_cpp,
|
||||
)
|
||||
assert custom is not None
|
||||
assert custom.group(1).replace(" ", "") == "58,1,102,120,255"
|
||||
assert "add_init_sequence" not in main_cpp
|
||||
@@ -4,6 +4,7 @@ import math
|
||||
import pytest
|
||||
|
||||
from esphome import cpp_generator as cg, cpp_types as ct
|
||||
from esphome.core import CORE, ID
|
||||
|
||||
|
||||
class TestExpressions:
|
||||
@@ -808,3 +809,39 @@ async def test_templatable__lambda_with_std_string() -> None:
|
||||
result = await cg.templatable(lambda_obj, [], ct.std_string)
|
||||
|
||||
assert isinstance(result, cg.LambdaExpression)
|
||||
|
||||
|
||||
class TestSharedProgmemArray:
|
||||
def test_identical_contents_share_one_array(self) -> None:
|
||||
CORE.config = {}
|
||||
a = cg.shared_progmem_array("table", ct.uint8, [1, 2, 3])
|
||||
b = cg.shared_progmem_array("table", ct.uint8, [1, 2, 3])
|
||||
c = cg.shared_progmem_array("table", ct.uint8, [4])
|
||||
assert a is b
|
||||
assert str(a) != str(c)
|
||||
assert sum("PROGMEM" in str(st) for st in CORE.global_statements) == 2
|
||||
assert not any("PROGMEM" in str(st) for st in CORE.main_statements)
|
||||
|
||||
def test_share_false_always_emits_a_new_array(self) -> None:
|
||||
CORE.config = {}
|
||||
a = cg.shared_progmem_array("table", ct.uint8, [1], share=False)
|
||||
b = cg.shared_progmem_array("table", ct.uint8, [1], share=False)
|
||||
assert str(a) != str(b)
|
||||
assert sum("PROGMEM" in str(st) for st in CORE.global_statements) == 2
|
||||
|
||||
def test_same_contents_different_type_are_separate(self) -> None:
|
||||
CORE.config = {}
|
||||
a = cg.shared_progmem_array("table", ct.uint8, [1])
|
||||
b = cg.shared_progmem_array("table", ct.uint16, [1])
|
||||
assert str(a) != str(b)
|
||||
|
||||
def test_name_avoids_config_ids(self) -> None:
|
||||
CORE.config = {"sensor": [{"id": ID("table", is_declaration=True)}]}
|
||||
array = cg.shared_progmem_array("table", ct.uint8, [1])
|
||||
assert str(array) == "table_2"
|
||||
|
||||
def test_name_avoids_registered_variables(self) -> None:
|
||||
CORE.config = {}
|
||||
CORE.register_variable(ID("table", is_declaration=True), cg.MockObj("table"))
|
||||
array = cg.shared_progmem_array("table", ct.uint8, [1])
|
||||
assert str(array) == "table_2"
|
||||
|
||||
Reference in New Issue
Block a user