diff --git a/CODEOWNERS b/CODEOWNERS index 20971b4f1b13..509b7e398c0c 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -68,7 +68,7 @@ esphome/components/bmp581/* @kahrendt esphome/components/bp1658cj/* @Cossid esphome/components/bp5758d/* @Cossid esphome/components/button/* @esphome/core -esphome/components/byte_bus/* @clydebarrow +esphome/components/byte_bus/* @clydebarrow @nielsnl68 esphome/components/canbus/* @danielschramm @mvturnho esphome/components/cap1188/* @mreditor97 esphome/components/captive_portal/* @OttoWinter diff --git a/esphome/components/byte_bus/__init__.py b/esphome/components/byte_bus/__init__.py index ef9dae04d1fd..063445d461a9 100644 --- a/esphome/components/byte_bus/__init__.py +++ b/esphome/components/byte_bus/__init__.py @@ -1,6 +1,134 @@ +import types import esphome.codegen as cg +import esphome.config_validation as cv +import esphome.final_validate as fv -CODEOWNERS = ["@clydebarrow"] +from esphome.schema_extractors import schema_extractor_registry + +from esphome.const import ( + CONF_ID, +) + +CODEOWNERS = ["@clydebarrow", "@nielsnl68"] + +CONF_BUS_TYPE = "bus_type" byte_bus_ns = cg.esphome_ns.namespace("byte_bus") ByteBus = byte_bus_ns.class_("ByteBus") + + +class DatabusEntry: + def __init__(self, name, fun, type_id, schema, final=None, final_args=None): + self.name = name + self.fun = fun + self.type_id = type_id + self.raw_schema = schema + self.final = final + self.final_args = final_args + + @property + def coroutine_fun(self): + from esphome.core import coroutine + + return coroutine(self.fun) + + @property + def schema(self): + from esphome.config_validation import Schema + + return Schema(self.raw_schema) + + +class Registry(dict[str, DatabusEntry]): + def __init__(self, base_schema=None, type_id_key=None): + super().__init__() + self.base_schema = base_schema or {} + self.type_id_key = type_id_key + + def register(self, name, type_id, schema, final=None, final_args=None): + def decorator(fun): + self[name] = DatabusEntry(name, fun, type_id, schema, final, final_args) + return fun + + return decorator + + +DATABUS_REGISTRY = Registry() + + +def include_databus(name, *, bus_class, schema, final_validate=None, final_args=None): + result = DATABUS_REGISTRY.register( + name, bus_class, schema, final_validate, final_args + ) + return result + + +def validate_databus_registry(base_schema, **kwargs): + default_schema_option = kwargs.pop("default", None) + base_schema = cv.ensure_schema(base_schema).extend( + { + cv.Optional(CONF_BUS_TYPE): cv.valid, + }, + extra=cv.ALLOW_EXTRA, + ) + + @schema_extractor_registry(DATABUS_REGISTRY) + def validator(value): + + default = ( + default_schema_option + if not callable(default_schema_option) + else default_schema_option(value) + ) + + if not isinstance(value, dict): + raise cv.Invalid("This value must be dict !!") + value = value.copy() + + key = value.pop(CONF_BUS_TYPE, default) + if key is None: + raise cv.Invalid(f"{CONF_BUS_TYPE} not specified!") + + models = cv.extract_keys(DATABUS_REGISTRY) + key_validator = cv.one_of(*models, **kwargs) + key_v = key_validator(key) + + if not isinstance(base_schema, cv.Schema): + raise cv.Invalid("base_schema must be a schema !!") + + new_schema = base_schema.extend(DATABUS_REGISTRY[key_v].raw_schema) + + value = new_schema(value) + value[CONF_BUS_TYPE] = key_v + return value + + return validator + + +async def register_databus(config): + databus = DATABUS_REGISTRY[config[CONF_BUS_TYPE]] + rhs = databus.type_id.new() + bus = config[CONF_ID].copy() + bus.id = bus.id + "_bus" + var = cg.Pvariable(bus, rhs, databus.type_id) + databus.bus_id = bus.id + return await databus.fun(config, var, databus) + + +def get_config_from_id(value): + fconf = fv.full_config.get() + path = fconf.get_path_for_id(value)[:-1] + return fconf.get_config_for_path(path) + + +def final_validate_databus_schema(name: str): + def validate(config): + databus = DATABUS_REGISTRY[config[CONF_BUS_TYPE]] + + if isinstance(databus.final, types.FunctionType): + kargs = {} if databus.final_args is None else databus.final_args + kargs["config"] = config + return databus.final(name, **kargs) + return config + + return validate diff --git a/esphome/components/byte_bus/byte_bus.h b/esphome/components/byte_bus/byte_bus.h index 381478ee38da..17b6260771af 100644 --- a/esphome/components/byte_bus/byte_bus.h +++ b/esphome/components/byte_bus/byte_bus.h @@ -49,12 +49,25 @@ class ByteBus { * @param data * @param length */ - virtual void write_cmd_data(int cmd, const uint8_t *data, size_t length) = 0; + inline void write_command(int cmd) { write_command(cmd, nullptr, 0); } + inline void write_command(int cmd, const uint8_t data) { write_command(cmd, &data, 1); } + virtual void write_command(int cmd, const uint8_t *data, size_t length) = 0; virtual void dump_config(){}; - virtual void begin_transaction() = 0; - virtual void end_transaction() = 0; + void begin_transaction() { + if (++this->transaction_counter == 0) { + do_begin_transaction(); + } + } + void end_transaction() { + if (this->transaction_counter <= 1) { + do_end_transaction(); + this->transaction_counter = 0; + } else { + this->transaction_counter--; + } + } void set_dc_pin(GPIOPin *dc_pin) { this->dc_pin_ = dc_pin; } @@ -65,7 +78,11 @@ class ByteBus { void set_dc_data(bool data) { this->dc_pin_->digital_write(data); } protected: + virtual void do_begin_transaction() = 0; + virtual void do_end_transaction() = 0; + GPIOPin *dc_pin_{byte_bus::NULL_PIN}; + int transaction_counter{0}; }; } // namespace byte_bus } // namespace esphome diff --git a/esphome/components/i80/__init__.py b/esphome/components/i80/__init__.py index 50a56d863f30..e440d7075d44 100644 --- a/esphome/components/i80/__init__.py +++ b/esphome/components/i80/__init__.py @@ -1,5 +1,6 @@ import esphome.codegen as cg import esphome.config_validation as cv + from esphome.components import byte_bus from esphome import pins @@ -8,7 +9,6 @@ CONF_ID, CONF_DATA_RATE, CONF_CS_PIN, - CONF_CLIENT_ID, CONF_DC_PIN, ) @@ -17,12 +17,17 @@ i80_ns = cg.esphome_ns.namespace("i80") I80Component = i80_ns.class_("I80Component", cg.Component) -I80Client = i80_ns.class_("I80Client", byte_bus.ByteBus) +I80ByteBus = i80_ns.class_("I80ByteBus", byte_bus.ByteBus) CONF_RD_PIN = "rd_pin" CONF_WR_PIN = "wr_pin" CONF_I80_ID = "i80_id" + +def validate_data_pins(): + pass + + CONFIG_SCHEMA = cv.All( cv.ensure_list( cv.Schema( @@ -30,18 +35,31 @@ cv.GenerateID(): cv.declare_id(I80Component), cv.Required(CONF_DATA_PINS): cv.All( cv.ensure_list(pins.internal_gpio_output_pin_number), - cv.Length(min=8, max=8), + cv.Length(min=8, max=16), ), cv.Required(CONF_WR_PIN): pins.internal_gpio_output_pin_schema, cv.Optional(CONF_RD_PIN): pins.internal_gpio_output_pin_schema, cv.Required(CONF_DC_PIN): pins.internal_gpio_output_pin_schema, - } + }, + validate_data_pins, ) ), - cv.only_with_esp_idf, ) +async def to_code(configs): + cg.add_define("USE_I80") + for conf in configs: + wr = await cg.gpio_pin_expression(conf[CONF_WR_PIN]) + dc = await cg.gpio_pin_expression(conf[CONF_DC_PIN]) + + var = cg.new_Pvariable(conf[CONF_ID], wr, dc, conf[CONF_DATA_PINS]) + await cg.register_component(var, conf) + if rd := conf.get(CONF_RD_PIN): + rd = await cg.gpio_pin_expression(rd) + cg.add(var.set_rd_pin(rd)) + + def i80_client_schema( cs_pin_required=False, default_data_rate="2MHz", @@ -54,7 +72,6 @@ def i80_client_schema( schema = { cv.GenerateID(CONF_I80_ID): cv.use_id(I80Component), cv.Optional(CONF_DATA_RATE, default=default_data_rate): cv.frequency, - cv.GenerateID(CONF_CLIENT_ID): cv.declare_id(I80Client), } if cs_pin_required: schema[cv.Required(CONF_CS_PIN)] = pins.gpio_output_pin_schema @@ -63,23 +80,42 @@ def i80_client_schema( return cv.Schema(schema) -async def to_code(configs): - cg.add_define("USE_I80") - for conf in configs: - wr = await cg.gpio_pin_expression(conf[CONF_WR_PIN]) - dc = await cg.gpio_pin_expression(conf[CONF_DC_PIN]) - var = cg.new_Pvariable(conf[CONF_ID], wr, dc, conf[CONF_DATA_PINS]) - await cg.register_component(var, conf) - if rd := conf.get(CONF_RD_PIN): - rd = await cg.gpio_pin_expression(rd) - cg.add(var.set_rd_pin(rd)) +def final_validate_databus_schema(name: str, pin_count, config=None): + data_pins = byte_bus.get_config_from_id(config[CONF_I80_ID])[CONF_DATA_PINS] + if len(data_pins) != pin_count: + i80_id = config[CONF_I80_ID].id + bus_type = config["bus_type"] + raise cv.Invalid( + f"The {bus_type} bus type requires that the {i80_id} component has {pin_count} pins declared." + ) + return config -async def create_i80_client(config): - id = config[CONF_CLIENT_ID] - var = cg.new_Pvariable(id) +@byte_bus.include_databus( + "i80", + bus_class=I80ByteBus, + schema=i80_client_schema(), + final_validate=final_validate_databus_schema, + final_args={"pin_count": 8}, +) +@byte_bus.include_databus( + "par8", + bus_class=I80ByteBus, + schema=i80_client_schema(), + final_validate=final_validate_databus_schema, + final_args={"pin_count": 8}, +) +@byte_bus.include_databus( + "par16", + bus_class=I80ByteBus, + schema=i80_client_schema(), + final_validate=final_validate_databus_schema, + final_args={"pin_count": 16}, +) +async def create_i80_databus(config, var, bustype): cg.add(var.set_parent(await cg.get_variable(config[CONF_I80_ID]))) + cg.add(var.set_data_rate(config[CONF_DATA_RATE])) if pin := config.get(CONF_CS_PIN): cg.add(var.set_cs_pin(await cg.gpio_pin_expression(pin))) - cg.add(var.set_data_rate(config[CONF_DATA_RATE])) + return var diff --git a/esphome/components/i80/i80_component.cpp b/esphome/components/i80/i80_component.cpp index 0811f5559ef5..4e972d6de019 100644 --- a/esphome/components/i80/i80_component.cpp +++ b/esphome/components/i80/i80_component.cpp @@ -1,4 +1,5 @@ #include "i80_component.h" +#include "i80_esp_idf.cpp" #ifdef USE_I80 namespace esphome { @@ -12,6 +13,40 @@ void I80Component::dump_config() { } } +void I80Component::setup() { + auto *bus = new I80BusIdf(this->wr_pin_, this->dc_pin_, this->data_pins_); // NOLINT + // write pin is unused, but pulled high (inactive) if present + if (this->rd_pin_ != nullptr) { + this->rd_pin_->setup(); + this->rd_pin_->digital_write(true); + } + if (bus->is_failed()) + this->mark_failed(); + this->bus_ = bus; +} +I80Delegate *I80Component::register_device(I80ByteBus *device, GPIOPin *cs_pin, unsigned int data_rate) { + if (this->devices_.count(device) != 0) { + ESP_LOGE(TAG, "i80 device already registered"); + return this->devices_[device]; + } + auto *delegate = this->bus_->get_delegate(cs_pin, data_rate); // NOLINT + this->devices_[device] = delegate; + return delegate; +} +void I80Component::unregister_device(I80ByteBus *device) { + if (this->devices_.count(device) == 0) { + esph_log_e(TAG, "i80 device not registered"); + return; + } + delete this->devices_[device]; // NOLINT + this->devices_.erase(device); +} + +void I80ByteBus::dump_config() { + ESP_LOGCONFIG(TAG, " Data rate: %dMHz", (unsigned) (this->data_rate_ / 1000000)); + LOG_PIN(" CS Pin: ", this->cs_); +} + } // namespace i80 } // namespace esphome diff --git a/esphome/components/i80/i80_component.h b/esphome/components/i80/i80_component.h index 1f0bfb3dd03d..8b60096b46d2 100644 --- a/esphome/components/i80/i80_component.h +++ b/esphome/components/i80/i80_component.h @@ -15,7 +15,7 @@ namespace i80 { static constexpr const char *TAG = "i80"; -class I80Client; +class I80ByteBus; class I80Delegate { public: @@ -26,7 +26,7 @@ class I80Delegate { // end the transaction virtual void end_transaction() {} - virtual void write_cmd_data(int cmd, const uint8_t *ptr, size_t length) {} + virtual void write_command(int cmd, const uint8_t *ptr, size_t length) {} virtual void write_array(const uint8_t *data, size_t length){}; virtual ~I80Delegate() = default; @@ -45,7 +45,7 @@ class I80Bus { virtual I80Delegate *get_delegate(GPIOPin *cs_pin, unsigned int data_rate) = 0; }; -class I80Client; +class I80ByteBus; class I80Component : public Component { public: @@ -56,8 +56,10 @@ class I80Component : public Component { void set_rd_pin(InternalGPIOPin *rd_pin) { this->rd_pin_ = rd_pin; } void dump_config() override; - I80Delegate *register_device(I80Client *device, GPIOPin *cs_pin, unsigned int data_rate); - void unregister_device(I80Client *device); + + I80Delegate *register_device(I80ByteBus *device, GPIOPin *cs_pin, unsigned int data_rate); + void unregister_device(I80ByteBus *device); + float get_setup_priority() const override { return setup_priority::BUS; } protected: @@ -66,10 +68,10 @@ class I80Component : public Component { InternalGPIOPin *rd_pin_{}; InternalGPIOPin *dc_pin_{}; std::vector data_pins_{}; - std::map devices_{}; + std::map devices_{}; }; -class I80Client : public byte_bus::ByteBus { +class I80ByteBus : public byte_bus::ByteBus { public: void bus_setup() override { this->delegate_ = this->parent_->register_device(this, this->cs_, this->data_rate_); } void bus_teardown() override { @@ -77,21 +79,22 @@ class I80Client : public byte_bus::ByteBus { this->delegate_ = NULL_DELEGATE; } - void write_cmd_data(int cmd, const uint8_t *data, size_t length) override { - this->delegate_->write_cmd_data(cmd, data, length); + void write_command(int cmd, const uint8_t *data, size_t length) override { + this->delegate_->write_command(cmd, data, length); } void write_array(const uint8_t *data, size_t length) override { this->delegate_->write_array(data, length); } - void end_transaction() override { this->delegate_->end_transaction(); } - void begin_transaction() override { this->delegate_->begin_transaction(); } - void set_parent(I80Component *parent) { this->parent_ = parent; } + void dump_config() override; + void set_parent(I80Component *parent) { this->parent_ = parent; } void set_cs_pin(GPIOPin *cs) { this->cs_ = cs; } - void dump_config() override; void set_data_rate(int data_rate) { this->data_rate_ = data_rate; } protected: + void do_end_transaction() override { this->delegate_->end_transaction(); } + void do_begin_transaction() override { this->delegate_->begin_transaction(); } + I80Delegate *delegate_{NULL_DELEGATE}; I80Component *parent_{}; GPIOPin *cs_{byte_bus::NULL_PIN}; diff --git a/esphome/components/i80/i80_esp_idf.cpp b/esphome/components/i80/i80_esp_idf.cpp index d9571030171c..37a9ca52e378 100644 --- a/esphome/components/i80/i80_esp_idf.cpp +++ b/esphome/components/i80/i80_esp_idf.cpp @@ -1,6 +1,6 @@ #include "i80_component.h" #ifdef USE_I80 -#ifdef USE_ESP_IDF + #include "esp_lcd_panel_io.h" #include "esp_lcd_panel_io_interface.h" #include @@ -46,7 +46,7 @@ class I80DelegateIdf : public I80Delegate { } } - void write_cmd_data(int cmd, const uint8_t *data, size_t length) override { + void write_command(int cmd, const uint8_t *data, size_t length) override { this->begin_transaction(); if (length <= CONFIG_LCD_PANEL_IO_FORMAT_BUF_SIZE) { ESP_LOGV(TAG, "Send command %X, len %u", cmd, length); @@ -119,40 +119,6 @@ class I80BusIdf : public I80Bus { esp_lcd_i80_bus_handle_t handle_{}; }; -void I80Component::setup() { - auto *bus = new I80BusIdf(this->wr_pin_, this->dc_pin_, this->data_pins_); // NOLINT - // write pin is unused, but pulled high (inactive) if present - if (this->rd_pin_ != nullptr) { - this->rd_pin_->setup(); - this->rd_pin_->digital_write(true); - } - if (bus->is_failed()) - this->mark_failed(); - this->bus_ = bus; -} -I80Delegate *I80Component::register_device(I80Client *device, GPIOPin *cs_pin, unsigned int data_rate) { - if (this->devices_.count(device) != 0) { - ESP_LOGE(TAG, "i80 device already registered"); - return this->devices_[device]; - } - auto *delegate = this->bus_->get_delegate(cs_pin, data_rate); // NOLINT - this->devices_[device] = delegate; - return delegate; -} -void I80Component::unregister_device(I80Client *device) { - if (this->devices_.count(device) == 0) { - esph_log_e(TAG, "i80 device not registered"); - return; - } - delete this->devices_[device]; // NOLINT - this->devices_.erase(device); -} - -void I80Client::dump_config() { - ESP_LOGCONFIG(TAG, " Data rate: %dMHz", (unsigned) (this->data_rate_ / 1000000)); - LOG_PIN(" CS Pin: ", this->cs_); -} } // namespace i80 } // namespace esphome -#endif // USE_ESP_IDF #endif // USE_I80 diff --git a/esphome/components/ili9xxx/display.py b/esphome/components/ili9xxx/display.py index 4a2531675e95..e95dabc90aa4 100644 --- a/esphome/components/ili9xxx/display.py +++ b/esphome/components/ili9xxx/display.py @@ -6,8 +6,7 @@ ) from esphome.components import ( display, - spi, - i80, + byte_bus, ) from esphome.components.display import validate_rotation from esphome.core import ( @@ -16,7 +15,6 @@ ) from esphome.const import ( CONF_COLOR_PALETTE, - CONF_DC_PIN, CONF_ID, CONF_LAMBDA, CONF_MODEL, @@ -54,23 +52,230 @@ ColorOrder = display.display_ns.enum("ColorMode") MODELS = { - "GC9A01A": ili9xxx_ns.class_("ILI9XXXGC9A01A", ILI9XXXDisplay), - "M5STACK": ili9xxx_ns.class_("ILI9XXXM5Stack", ILI9XXXDisplay), - "M5CORE": ili9xxx_ns.class_("ILI9XXXM5CORE", ILI9XXXDisplay), - "TFT_2.4": ili9xxx_ns.class_("ILI9XXXILI9341", ILI9XXXDisplay), - "TFT_2.4R": ili9xxx_ns.class_("ILI9XXXILI9342", ILI9XXXDisplay), - "ILI9341": ili9xxx_ns.class_("ILI9XXXILI9341", ILI9XXXDisplay), - "ILI9342": ili9xxx_ns.class_("ILI9XXXILI9342", ILI9XXXDisplay), - "ILI9481": ili9xxx_ns.class_("ILI9XXXILI9481", ILI9XXXDisplay), - "ILI9481-18": ili9xxx_ns.class_("ILI9XXXILI948118", ILI9XXXDisplay), - "ILI9486": ili9xxx_ns.class_("ILI9XXXILI9486", ILI9XXXDisplay), - "ILI9488": ili9xxx_ns.class_("ILI9XXXILI9488", ILI9XXXDisplay), - "ILI9488_A": ili9xxx_ns.class_("ILI9XXXILI9488A", ILI9XXXDisplay), - "ST7796": ili9xxx_ns.class_("ILI9XXXST7796", ILI9XXXDisplay), - "ST7789V": ili9xxx_ns.class_("ILI9XXXST7789V", ILI9XXXDisplay), - "S3BOX": ili9xxx_ns.class_("ILI9XXXS3Box", ILI9XXXDisplay), - "S3BOX_LITE": ili9xxx_ns.class_("ILI9XXXS3BoxLite", ILI9XXXDisplay), - "WAVESHARE_RES_3_5": ili9xxx_ns.class_("WAVESHARERES35", ILI9XXXDisplay), + "GC9A01A": [ + ili9xxx_ns.class_("ILI9XXXGC9A01A", ILI9XXXDisplay), + True, + [ + "spi", + "dspi", + "i80", + "par8", + "par9", + "par12", + "par16", + "par18", + "rgb6", + "rgb12", + "rgb16", + "rgb18", + ], + [240, 240], + ], + "ILI9341": [ + ili9xxx_ns.class_("ILI9XXXILI9341", ILI9XXXDisplay), + True, + [ + "spi", + "i80", + "par8", + "par9", + "par16", + "par18", + "rgb6", + "rgb16", + "rgb18", + ], + [320, 240], + ], + "ILI9342": [ + ili9xxx_ns.class_("ILI9XXXILI9342", ILI9XXXDisplay), + True, + [ + "spi", + "i80", + "par8", + "par9", + "par16", + "par18", + "rgb6", + "rgb16", + "rgb18", + ], + [240, 320], + ], + "ILI9342C": [ + ili9xxx_ns.class_("ILI9XXXILI9342", ILI9XXXDisplay), + True, + [ + "spi", + "i80", + "par8", + "par9", + "par16", + "par18", + "rgb6", + "rgb16", + "rgb18", + ], + [240, 320], + ], + "ILI9481": [ + ili9xxx_ns.class_("ILI9XXXILI9481", ILI9XXXDisplay), + True, + [ + "spi", + "i80", + "qspi", + "par8", + "par9", + "par16", + "par18", + "rgb16", + "rgb18", + ], + [480, 320], + ], + "ILI9486": [ + ili9xxx_ns.class_("ILI9XXXILI9486", ILI9XXXDisplay), + False, + [ + "spi", + "i80", + "par8", + "par9", + "par16", + "par18", + "rgb16", + "rgb18", + ], + [480, 320], + ], + "ILI9488": [ + ili9xxx_ns.class_("ILI9XXXILI9488", ILI9XXXDisplay), + False, + [ + "spi", + "i80", + "par8", + "par9", + "par16", + "par18", + "par24", + "rgb16", + "rgb18", + "rgb24", + ], + [480, 320], + ], + "ILI9488_A": [ + ili9xxx_ns.class_("ILI9XXXILI9488A", ILI9XXXDisplay), + False, + [ + "spi", + "i80", + "par8", + "par9", + "par16", + "par18", + "par24", + "rgb16", + "rgb18", + "rgb24", + ], + [480, 320], + ], + "ST7796": [ + ili9xxx_ns.class_("ILI9XXXST7796", ILI9XXXDisplay), + True, + [ + "spi", + "i80", + "par8", + "par9", + "par16", + "par18", + "rgb16", + "rgb18", + ], + [480, 320], + ], + "ST7796S": [ + ili9xxx_ns.class_("ILI9XXXST7796", ILI9XXXDisplay), + True, + [ + "spi", + "i80", + "par8", + "par9", + "par16", + "par18", + "rgb16", + "rgb18", + "rgb24", + ], + [480, 320], + ], + "ST7796U": [ + ili9xxx_ns.class_("ILI9XXXST7796", ILI9XXXDisplay), + False, + [ + "spi", + "i80", + "par8", + "par9", + "par16", + "par18", + "rgb16", + "rgb18", + "rgb24", + ], + [480, 320], + ], + "ST7789V": [ + ili9xxx_ns.class_("ILI9XXXST7789V", ILI9XXXDisplay), + True, + [ + "spi", + "i80", + "par8", + "par9", + "par16", + "par18", + "rgb6", + "rgb16", + "rgb18", + "rgb24", + ], + [320, 240], + ], + "M5STACK": [ili9xxx_ns.class_("ILI9XXXM5Stack", ILI9XXXDisplay), False, ["spi"]], + "M5CORE": [ili9xxx_ns.class_("ILI9XXXM5CORE", ILI9XXXDisplay), True, ["spi"]], + "TFT_2.4": [ + ili9xxx_ns.class_("ILI9XXXILI9341", ILI9XXXDisplay), + True, + ["spi", "i80", "rgb"], + ], + "TFT_2.4R": [ + ili9xxx_ns.class_("ILI9XXXILI9342", ILI9XXXDisplay), + True, + ["spi", "i80", "rgb"], + ], + "S3BOX": [ili9xxx_ns.class_("ILI9XXXS3Box", ILI9XXXDisplay), False, ["spi"]], + "S3BOX_LITE": [ + ili9xxx_ns.class_("ILI9XXXS3BoxLite", ILI9XXXDisplay), + False, + ["spi"], + ], + "W32-SC01-Plus": [ + ili9xxx_ns.class_("ILI9XXXST7796", ILI9XXXDisplay), + False, + ["i80", "par8"], + ], + "WAVESHARE_RES_3_5": [ + ili9xxx_ns.class_("WAVESHARERES35", ILI9XXXDisplay), + False, + ["spi16d"], + ], } COLOR_ORDERS = { @@ -101,18 +306,22 @@ def _validate(config): raise cv.Invalid( "Providing color palette images requires palette mode to be 'IMAGE_ADAPTIVE'" ) - if CORE.is_esp8266 and config.get(CONF_MODEL) not in [ - "M5STACK", - "TFT_2.4", - "TFT_2.4R", - "ILI9341", - "ILI9342", - "ST7789V", - ]: + + model = config.get(CONF_MODEL) + if CORE.is_esp8266 and not MODELS[model][1]: raise cv.Invalid("Selected model can't run on ESP8266; use an ESP32 with PSRAM") + if config[CONF_BUS_TYPE] not in MODELS[model][2]: + raise cv.Invalid("Selected bus_type can't be used with selected model") return config +def give_default_bus_type(config): + model = config.get(CONF_MODEL, None) + if model is None: + return None + return MODELS[model][2][0] + + BASE_SCHEMA = display.FULL_DISPLAY_SCHEMA.extend( { cv.GenerateID(): cv.declare_id(ILI9XXXDisplay), @@ -151,52 +360,30 @@ def _validate(config): } ).extend(cv.polling_component_schema("1s")) -TYPE_SPI = "spi" -TYPE_I80 = "i80" CONFIG_SCHEMA = cv.All( - cv.typed_schema( - { - TYPE_SPI: BASE_SCHEMA.extend( - spi.spi_device_schema(False, "40MHz", "mode0") - ).extend( - { - cv.Required(CONF_DC_PIN): pins.gpio_output_pin_schema, - cv.GenerateID(CONF_BYTE_BUS_ID): cv.declare_id(spi.SPIByteBus), - } - ), - TYPE_I80: BASE_SCHEMA.extend(i80.i80_client_schema()).extend( - { - cv.Optional(CONF_DC_PIN): cv.invalid( - "DC pin should be specified in the i80 component only" - ) - } - ), - }, - default_type=TYPE_SPI, - key=CONF_BUS_TYPE, + byte_bus.validate_databus_registry( + BASE_SCHEMA, default=give_default_bus_type, lower=True ), cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA), _validate, ) +FINAL_VALIDATE_SCHEMA = byte_bus.final_validate_databus_schema("Display") + + async def to_code(config): - rhs = MODELS[config[CONF_MODEL]].new() + rhs = MODELS[config[CONF_MODEL]][0].new() var = cg.Pvariable(config[CONF_ID], rhs) data_rate = int(max(config[CONF_DATA_RATE] / 1e6, 1)) await display.register_display(var, config) - if config[CONF_BUS_TYPE] == TYPE_I80: - bus_client = await i80.create_i80_client(config) - data_rate = data_rate * 8 - else: - spi_client = await spi.create_spi_client(config) - bus_client = cg.new_Pvariable(config[CONF_BYTE_BUS_ID], spi_client) + + bus_client = await byte_bus.register_databus(config) cg.add(var.set_bus(bus_client)) cg.add(var.set_data_rate(data_rate)) - if dc := config.get(CONF_DC_PIN): - cg.add(bus_client.set_dc_pin(await cg.gpio_pin_expression(dc))) + if CONF_COLOR_ORDER in config: cg.add(var.set_color_order(COLOR_ORDERS[config[CONF_COLOR_ORDER]])) if CONF_TRANSFORM in config: diff --git a/esphome/components/ili9xxx/ili9xxx_display.cpp b/esphome/components/ili9xxx/ili9xxx_display.cpp index 8c33984b0c3c..7a07f3470cb6 100644 --- a/esphome/components/ili9xxx/ili9xxx_display.cpp +++ b/esphome/components/ili9xxx/ili9xxx_display.cpp @@ -25,7 +25,8 @@ void ILI9XXXDisplay::set_madctl() { mad |= MADCTL_MX; if (this->mirror_y_) mad |= MADCTL_MY; - this->send_command(ILI9XXX_MADCTL, &mad, 1); + + this->bus_->write_command(ILI9XXX_MADCTL, mad); ESP_LOGD(TAG, "Wrote MADCTL 0x%02X", mad); } @@ -39,9 +40,12 @@ void ILI9XXXDisplay::setup() { delay(20); this->reset_pin_->digital_write(true); delay(20); + + this->bus_->begin_transaction(); this->init_lcd_(); this->set_madctl(); - this->send_command(this->pre_invertcolors_ ? ILI9XXX_INVON : ILI9XXX_INVOFF); + this->bus_->write_command(this->pre_invertcolors_ ? ILI9XXX_INVON : ILI9XXX_INVOFF); + this->bus_->end_transaction(); this->x_low_ = this->width_; this->y_low_ = this->height_; this->x_high_ = 0; @@ -211,15 +215,17 @@ void ILI9XXXDisplay::display_() { this->x_low_, this->y_low_, this->x_high_, this->y_high_, w, h, this->buffer_color_mode_, this->is_18bitdisplay_, sw_time, mw_time); auto now = millis(); + this->bus_->begin_transaction(); if (this->buffer_color_mode_ == BITS_16 && !this->is_18bitdisplay_ && sw_time < mw_time) { // 16 bit mode maps directly to display format ESP_LOGV(TAG, "Doing single write of %zu bytes", this->width_ * h * 2); - set_addr_window_(0, this->y_low_, this->width_ - 1, this->y_high_); + + this->set_addr_window_(0, this->y_low_, this->width_ - 1, this->y_high_); this->bus_->write_array(this->buffer_ + this->y_low_ * this->width_ * 2, h * this->width_ * 2); } else { ESP_LOGV(TAG, "Doing multiple write"); size_t rem = h * w; // remaining number of pixels to write - set_addr_window_(this->x_low_, this->y_low_, this->x_high_, this->y_high_); + this->set_addr_window_(this->x_low_, this->y_low_, this->x_high_, this->y_high_); size_t idx = 0; // index into transfer_buffer size_t pixel = 0; // pixel number offset size_t pos = this->y_low_ * this->width_ + this->x_low_; @@ -285,6 +291,7 @@ void ILI9XXXDisplay::draw_pixels_at(int x_start, int y_start, int w, int h, cons return display::Display::draw_pixels_at(x_start, y_start, w, h, ptr, order, bitness, big_endian, x_offset, y_offset, x_pad); } + this->bus_->begin_transaction(); this->set_addr_window_(x_start, y_start, x_start + w - 1, y_start + h - 1); // 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) { @@ -303,17 +310,13 @@ void ILI9XXXDisplay::draw_pixels_at(int x_start, int y_start, int w, int h, cons // values per bit is huge uint32_t ILI9XXXDisplay::get_buffer_length_() { return this->get_width_internal() * this->get_height_internal(); } -void ILI9XXXDisplay::send_command(uint8_t command_byte, const uint8_t *data_bytes, uint8_t length) { - this->bus_->write_cmd_data(command_byte, data_bytes, length); // Send the command byte -} - void ILI9XXXDisplay::init_lcd_() { uint8_t cmd, x, num_args; const uint8_t *addr = this->init_sequence_; while ((cmd = *addr++) > 0) { x = *addr++; num_args = x & 0x7F; - this->send_command(cmd, addr, num_args); + this->bus_->write_command(cmd, addr, num_args); addr += num_args; if (x & 0x80) delay(150); // NOLINT @@ -331,20 +334,21 @@ void ILI9XXXDisplay::set_addr_window_(uint16_t x1, uint16_t y1, uint16_t x2, uin buf[1] = x1; buf[2] = x2 >> 8; buf[3] = x2; - this->send_command(ILI9XXX_CASET, buf, sizeof buf); + this->bus_->begin_transaction(); + this->bus_->write_command(ILI9XXX_CASET, buf, sizeof buf); buf[0] = y1 >> 8; buf[1] = y1; buf[2] = y2 >> 8; buf[3] = y2; - this->send_command(ILI9XXX_PASET, buf, sizeof buf); - this->send_command(ILI9XXX_RAMWR); - this->bus_->begin_transaction(); + this->bus_->write_command(ILI9XXX_PASET, buf, sizeof buf); + this->bus_->write_command(ILI9XXX_RAMWR); + this->bus_->end_transaction(); } void ILI9XXXDisplay::invert_colors(bool invert) { this->pre_invertcolors_ = invert; if (is_ready()) { - this->send_command(invert ? ILI9XXX_INVON : ILI9XXX_INVOFF); + this->bus_->write_command(invert ? ILI9XXX_INVON : ILI9XXX_INVOFF); } } diff --git a/esphome/components/ili9xxx/ili9xxx_display.h b/esphome/components/ili9xxx/ili9xxx_display.h index 60bee46e01cf..26d6b8a6a4f4 100644 --- a/esphome/components/ili9xxx/ili9xxx_display.h +++ b/esphome/components/ili9xxx/ili9xxx_display.h @@ -63,8 +63,6 @@ class ILI9XXXDisplay : public display::DisplayBuffer { this->offset_y_ = offset_y; } void invert_colors(bool invert); - virtual void send_command(uint8_t command_byte, const uint8_t *data_bytes, uint8_t length); - void send_command(uint8_t command_byte) { this->send_command(command_byte, nullptr, 0); } void set_color_order(display::ColorOrder color_order) { this->color_order_ = color_order; } void set_swap_xy(bool swap_xy) { this->swap_xy_ = swap_xy; } void set_mirror_x(bool mirror_x) { this->mirror_x_ = mirror_x; } @@ -206,34 +204,14 @@ class ILI9XXXILI9488 : public ILI9XXXDisplay { dfun[1] = 0x62; } } - this->send_command(ILI9XXX_DFUNCTR, dfun, sizeof dfun); - this->send_command(ILI9XXX_MADCTL, &mad, 1); + this->bus_->write_command(ILI9XXX_DFUNCTR, dfun, sizeof dfun); + this->bus_->write_command(ILI9XXX_MADCTL, &mad, 1); } }; //----------- Waveshare 3.5 Res Touch - ILI9488 interfaced via 16 bit shift register to parallel */ class WAVESHARERES35 : public ILI9XXXILI9488 { public: WAVESHARERES35() : ILI9XXXILI9488(INITCMD_WAVESHARE_RES_3_5) {} - /* - * This board uses a 16 bit serial-parallel chip to implement SPI. It requires a CS transition between command - * and data phases, and DC must be set before CS is enabled. - */ - void send_command(uint8_t command_byte, const uint8_t *data_bytes, uint8_t length) override { - this->bus_->set_dc_data(false); - this->bus_->begin_transaction(); - this->bus_->write_array(&command_byte, 1); - this->bus_->end_transaction(); - this->bus_->set_dc_data(true); - uint8_t buf[2]{}; - if (length != 0) { - this->bus_->begin_transaction(); - for (size_t i = 0; i != length; i++) { - buf[1] = *data_bytes++; - this->bus_->write_array(buf, 2); - } - this->bus_->end_transaction(); - } - } }; //----------- ILI9XXX_35_TFT origin colors rotated display -------------- diff --git a/esphome/components/spi/__init__.py b/esphome/components/spi/__init__.py index a939e795a86d..aaf3ac1124f9 100644 --- a/esphome/components/spi/__init__.py +++ b/esphome/components/spi/__init__.py @@ -21,6 +21,7 @@ CONF_MOSI_PIN, CONF_SPI_ID, CONF_CS_PIN, + CONF_DC_PIN, CONF_NUMBER, CONF_INVERTED, KEY_CORE, @@ -360,6 +361,8 @@ def spi_device_schema( default_data_rate=cv.UNDEFINED, default_mode=cv.UNDEFINED, quad=False, + dc_pin_required=True, + use_16BitData=False, ): """Create a schema for an SPI device. :param cs_pin_required: If true, make the CS_PIN required in the config. @@ -379,10 +382,14 @@ def spi_device_schema( ), cv.GenerateID(CONF_CLIENT_ID): cv.declare_id(SPIClient), } + if dc_pin_required: + schema[cv.Required(CONF_DC_PIN)] = pins.gpio_output_pin_schema if cs_pin_required: schema[cv.Required(CONF_CS_PIN)] = pins.gpio_output_pin_schema else: schema[cv.Optional(CONF_CS_PIN)] = pins.gpio_output_pin_schema + if use_16BitData: + schema[cv.Optional("use_16BitData", default=True)] = cv.valid return cv.Schema(schema) @@ -433,3 +440,52 @@ def final_validate_device_schema(name: str, *, require_mosi: bool, require_miso: {cv.Required(CONF_SPI_ID): fv.id_declaration_match_schema(hub_schema)}, extra=cv.ALLOW_EXTRA, ) + + +def final_validate_databus_schema( + name: str, require_mosi: bool, require_miso: bool, config=None +): + spi = byte_bus.get_config_from_id(config[CONF_SPI_ID]) + spi_id = config[CONF_SPI_ID].id + bus_type = config["bus_type"] + if require_miso and (CONF_MISO_PIN not in spi): + raise cv.Invalid( + f"The {bus_type} bus type requires that the {spi_id} component has the '{CONF_MISO_PIN}' declared." + ) + if require_mosi and (CONF_MOSI_PIN not in spi): + raise cv.Invalid( + f"The {bus_type} bus type requires that the {spi_id} component has the '{CONF_MOSI_PIN}' declared." + ) + + +@byte_bus.include_databus( + "spi", + bus_class=SPIByteBus, + schema=spi_device_schema(False, "40MHz", "mode0", False, True), + final_validate=final_validate_databus_schema, + final_args={"require_mosi": True, "require_miso": False}, +) +@byte_bus.include_databus( + "spi16d", + bus_class=SPIByteBus, + schema=spi_device_schema(False, "40MHz", "mode0", False, True, True), + final_validate=final_validate_databus_schema, + final_args={"require_mosi": True, "require_miso": False}, +) +@byte_bus.include_databus( + "qspi", + bus_class=SPIByteBus, + schema=spi_device_schema(False, "40MHz", "mode0", True, True), + final_validate=final_validate_device_schema, + final_args={"require_mosi": True, "require_miso": False}, +) +async def create_spi_databus(config, var, databus): + + cg.add(var.set_spi_client(await create_spi_client(config))) + + if pin := config.get(CONF_DC_PIN): + cg.add(var.set_dc_pin(await cg.gpio_pin_expression(pin))) + if "use_16BitData" in config: + cg.add(var.set_16bit_data(config["use_16BitData"])) + + return var diff --git a/esphome/components/spi/spi.cpp b/esphome/components/spi/spi.cpp index 9ce91c4b850f..590b606bad61 100644 --- a/esphome/components/spi/spi.cpp +++ b/esphome/components/spi/spi.cpp @@ -63,7 +63,7 @@ void SPIComponent::setup() { } } -void SPIByteBus::write_cmd_data(int cmd, const uint8_t *data, size_t length) { +void SPIByteBus::write_command(int cmd, const uint8_t *data, size_t length) { ESP_LOGV(TAG, "Write cmd %X, length %d", cmd, (unsigned) length); this->begin_transaction(); if (cmd != -1) { @@ -72,7 +72,15 @@ void SPIByteBus::write_cmd_data(int cmd, const uint8_t *data, size_t length) { } this->dc_pin_->digital_write(true); if (length != 0) { - this->write_array(data, length); + if (this->is_16bit_data_) { + uint8_t buf[2]{}; + for (size_t i = 0; i != length; i++) { + buf[1] = *data++; + this->write_array(buf, 2); + } + } else { + this->write_array(data, length); + } } this->end_transaction(); } diff --git a/esphome/components/spi/spi.h b/esphome/components/spi/spi.h index bd2f42a2390a..789f9fd17a81 100644 --- a/esphome/components/spi/spi.h +++ b/esphome/components/spi/spi.h @@ -461,26 +461,31 @@ class SPIDevice : public SPIClient { class SPIByteBus : public byte_bus::ByteBus { public: - SPIByteBus(SPIClient *client) : client_(client) {} void bus_setup() override { this->dc_pin_->setup(); // OUTPUT this->dc_pin_->digital_write(false); this->client_->spi_setup(); } - void begin_transaction() override { this->client_->enable(); } - void end_transaction() override { this->client_->disable(); } void write_array(const uint8_t *data, size_t length) override { this->client_->write_array(data, length); } void bus_teardown() override { this->client_->spi_teardown(); } - void write_cmd_data(int cmd, const uint8_t *data, size_t length) override; + void write_command(int cmd, const uint8_t *data, size_t length) override; void dump_config() override; + + void set_spi_client(SPIClient *client) { this->client_ = client; } + void set_dc_pin(GPIOPin *dc_pin) { this->dc_pin_ = dc_pin; } + void set_16bit_data(bool data) { this->is_16bit_data_ = data; } protected: + void do_begin_transaction() override { this->client_->enable(); } + void do_end_transaction() override { this->client_->disable(); } + SPIClient *client_; GPIOPin *dc_pin_{byte_bus::NULL_PIN}; + bool is_16bit_data_{false}; }; } // namespace spi } // namespace esphome