From 0bdff04e6e7ddbe73985eefbbde2c6524946cba3 Mon Sep 17 00:00:00 2001 From: NP v/d Spek Date: Fri, 19 Apr 2024 00:53:11 +0200 Subject: [PATCH 01/11] I8080 (#352) * Implement i80 bus for ili9xxx displays * clang-tidy * yamllint * Fix spi_device test --------- Co-authored-by: clydebarrow <2366188+clydebarrow@users.noreply.github.com> --- CODEOWNERS | 2 + esphome/components/byte_bus/__init__.py | 6 + esphome/components/byte_bus/byte_bus.h | 71 ++++++++ esphome/components/i80/__init__.py | 85 ++++++++++ esphome/components/i80/i80_component.cpp | 18 ++ esphome/components/i80/i80_component.h | 103 ++++++++++++ esphome/components/i80/i80_esp_idf.cpp | 158 ++++++++++++++++++ esphome/components/ili9xxx/display.py | 142 +++++++++------- .../components/ili9xxx/ili9xxx_display.cpp | 124 +++++--------- esphome/components/ili9xxx/ili9xxx_display.h | 72 ++++---- esphome/components/ili9xxx/ili9xxx_init.h | 5 +- esphome/components/spi/__init__.py | 32 +++- esphome/components/spi/spi.cpp | 28 +++- esphome/components/spi/spi.h | 158 +++++++++--------- esphome/components/spi_device/__init__.py | 27 +-- esphome/core/defines.h | 1 + tests/components/i80/test.esp32-s3-idf.yaml | 27 +++ .../components/ili9xxx/test.esp32-s3-idf.yaml | 67 ++++++++ tests/components/spi_device/test.esp32.yaml | 19 +++ tests/test8.1.yaml | 6 - tests/test8.2.yaml | 7 - tests/test8.yaml | 7 - 22 files changed, 851 insertions(+), 314 deletions(-) create mode 100644 esphome/components/byte_bus/__init__.py create mode 100644 esphome/components/byte_bus/byte_bus.h create mode 100644 esphome/components/i80/__init__.py create mode 100644 esphome/components/i80/i80_component.cpp create mode 100644 esphome/components/i80/i80_component.h create mode 100644 esphome/components/i80/i80_esp_idf.cpp create mode 100644 tests/components/i80/test.esp32-s3-idf.yaml create mode 100644 tests/components/ili9xxx/test.esp32-s3-idf.yaml create mode 100644 tests/components/spi_device/test.esp32.yaml diff --git a/CODEOWNERS b/CODEOWNERS index 9f770d4efc11..20971b4f1b13 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -68,6 +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/canbus/* @danielschramm @mvturnho esphome/components/cap1188/* @mreditor97 esphome/components/captive_portal/* @OttoWinter @@ -164,6 +165,7 @@ esphome/components/i2s_audio/* @jesserockz esphome/components/i2s_audio/media_player/* @jesserockz esphome/components/i2s_audio/microphone/* @jesserockz esphome/components/i2s_audio/speaker/* @jesserockz +esphome/components/i80/* @clydebarrow esphome/components/iaqcore/* @yozik04 esphome/components/ili9xxx/* @clydebarrow @nielsnl68 esphome/components/improv_base/* @esphome/core diff --git a/esphome/components/byte_bus/__init__.py b/esphome/components/byte_bus/__init__.py new file mode 100644 index 000000000000..ef9dae04d1fd --- /dev/null +++ b/esphome/components/byte_bus/__init__.py @@ -0,0 +1,6 @@ +import esphome.codegen as cg + +CODEOWNERS = ["@clydebarrow"] + +byte_bus_ns = cg.esphome_ns.namespace("byte_bus") +ByteBus = byte_bus_ns.class_("ByteBus") diff --git a/esphome/components/byte_bus/byte_bus.h b/esphome/components/byte_bus/byte_bus.h new file mode 100644 index 000000000000..381478ee38da --- /dev/null +++ b/esphome/components/byte_bus/byte_bus.h @@ -0,0 +1,71 @@ +#pragma once + +#include "esphome/core/log.h" +#include "esphome/core/gpio.h" + +namespace esphome { +namespace byte_bus { + +/** + * A pin to replace those that don't exist. + */ +class NullPin : public GPIOPin { + public: + void setup() override {} + + void pin_mode(gpio::Flags _) override {} + + bool digital_read() override { return false; } + + void digital_write(bool _) override {} + + std::string dump_summary() const override { return {"Not used"}; } +}; + +// https://bugs.llvm.org/show_bug.cgi?id=48040 +static GPIOPin *const NULL_PIN = new NullPin(); // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) + +/** + * A class that can write a byte-oriented bus interface with CS and DC controls, typically used for + * LCD display controller interfacing. + */ +class ByteBus { + public: + virtual void bus_setup() = 0; + + virtual void bus_teardown() = 0; + + /** + * Write a data array. Will not control dc or cs pins. + * @param data + * @param length + */ + virtual void write_array(const uint8_t *data, size_t length) = 0; + + /** + * Write a command followed by data. CS and DC will be controlled automatically. + * On return, the DC pin will be in DATA mode. + * @param cmd + * @param data + * @param length + */ + virtual void write_cmd_data(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 set_dc_pin(GPIOPin *dc_pin) { this->dc_pin_ = dc_pin; } + + /** + * Select data (true) or control mode. + * @param data True for data mode, false for control + */ + void set_dc_data(bool data) { this->dc_pin_->digital_write(data); } + + protected: + GPIOPin *dc_pin_{byte_bus::NULL_PIN}; +}; +} // namespace byte_bus +} // namespace esphome diff --git a/esphome/components/i80/__init__.py b/esphome/components/i80/__init__.py new file mode 100644 index 000000000000..50a56d863f30 --- /dev/null +++ b/esphome/components/i80/__init__.py @@ -0,0 +1,85 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import byte_bus +from esphome import pins + +from esphome.const import ( + CONF_DATA_PINS, + CONF_ID, + CONF_DATA_RATE, + CONF_CS_PIN, + CONF_CLIENT_ID, + CONF_DC_PIN, +) + +CODEOWNERS = ["@clydebarrow"] +AUTO_LOAD = ["byte_bus"] + +i80_ns = cg.esphome_ns.namespace("i80") +I80Component = i80_ns.class_("I80Component", cg.Component) +I80Client = i80_ns.class_("I80Client", byte_bus.ByteBus) + +CONF_RD_PIN = "rd_pin" +CONF_WR_PIN = "wr_pin" +CONF_I80_ID = "i80_id" + +CONFIG_SCHEMA = cv.All( + cv.ensure_list( + cv.Schema( + { + 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.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, + } + ) + ), + cv.only_with_esp_idf, +) + + +def i80_client_schema( + cs_pin_required=False, + default_data_rate="2MHz", +): + """Create a schema for an i80 device + :param cs_pin_required: If true, make the CS_PIN required in the config. + :param default_data_rate: Optional data_rate to use as default + :return: The i80 client schema, `extend` this in your config 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 + else: + schema[cv.Optional(CONF_CS_PIN)] = pins.gpio_output_pin_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)) + + +async def create_i80_client(config): + id = config[CONF_CLIENT_ID] + var = cg.new_Pvariable(id) + cg.add(var.set_parent(await cg.get_variable(config[CONF_I80_ID]))) + 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 new file mode 100644 index 000000000000..0811f5559ef5 --- /dev/null +++ b/esphome/components/i80/i80_component.cpp @@ -0,0 +1,18 @@ +#include "i80_component.h" +#ifdef USE_I80 + +namespace esphome { +namespace i80 { +void I80Component::dump_config() { + ESP_LOGCONFIG(TAG, "I80 bus:"); + LOG_PIN(" WR Pin: ", this->wr_pin_) + LOG_PIN(" DC Pin: ", this->dc_pin_) + for (unsigned i = 0; i != this->data_pins_.size(); i++) { + ESP_LOGCONFIG(TAG, " Data pin %u: GPIO%d", i, this->data_pins_[i]); + } +} + +} // namespace i80 +} // namespace esphome + +#endif // USE_I80 diff --git a/esphome/components/i80/i80_component.h b/esphome/components/i80/i80_component.h new file mode 100644 index 000000000000..1f0bfb3dd03d --- /dev/null +++ b/esphome/components/i80/i80_component.h @@ -0,0 +1,103 @@ +#pragma once + +#include "esphome/core/defines.h" + +#ifdef USE_I80 +#include "esphome/core/gpio.h" +#include "esphome/core/component.h" +#include "esphome/components/byte_bus/byte_bus.h" +#include +#include +#include + +namespace esphome { +namespace i80 { + +static constexpr const char *TAG = "i80"; + +class I80Client; + +class I80Delegate { + public: + I80Delegate() = default; + // enable CS if configured. + virtual void begin_transaction() {} + + // end the transaction + virtual void end_transaction() {} + + virtual void write_cmd_data(int cmd, const uint8_t *ptr, size_t length) {} + virtual void write_array(const uint8_t *data, size_t length){}; + + virtual ~I80Delegate() = default; +}; + +class I80DelegateDummy : public I80Delegate { + void begin_transaction() override { esph_log_e(TAG, "I80 bus not initialised - did you call bus_setup()?"); } +}; + +static I80Delegate *const NULL_DELEGATE = new I80DelegateDummy(); // NOLINT + +class I80Bus { + public: + I80Bus() = default; + + virtual I80Delegate *get_delegate(GPIOPin *cs_pin, unsigned int data_rate) = 0; +}; + +class I80Client; + +class I80Component : public Component { + public: + I80Component(InternalGPIOPin *wr_pin, InternalGPIOPin *dc_pin, std::vector data_pins) + : wr_pin_(wr_pin), dc_pin_(dc_pin), data_pins_(std::move(data_pins)) {} + + void setup() override; + + 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); + float get_setup_priority() const override { return setup_priority::BUS; } + + protected: + I80Bus *bus_{}; + InternalGPIOPin *wr_pin_{}; + InternalGPIOPin *rd_pin_{}; + InternalGPIOPin *dc_pin_{}; + std::vector data_pins_{}; + std::map devices_{}; +}; + +class I80Client : 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 { + this->parent_->unregister_device(this); + 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_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 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: + I80Delegate *delegate_{NULL_DELEGATE}; + I80Component *parent_{}; + GPIOPin *cs_{byte_bus::NULL_PIN}; + uint32_t data_rate_{1000000}; +}; + +} // namespace i80 +} // namespace esphome +#endif diff --git a/esphome/components/i80/i80_esp_idf.cpp b/esphome/components/i80/i80_esp_idf.cpp new file mode 100644 index 000000000000..d9571030171c --- /dev/null +++ b/esphome/components/i80/i80_esp_idf.cpp @@ -0,0 +1,158 @@ +#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 +#include + +namespace esphome { +namespace i80 { + +static const size_t MAX_TRANSFER = 4092; + +static volatile bool write_complete = true; // NOLINT +static bool trans_done(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx) { + write_complete = true; + return true; +} + +class I80DelegateIdf : public I80Delegate { + public: + I80DelegateIdf(esp_lcd_panel_io_t *handle, GPIOPin *cs_pin) : handle_(handle) { + cs_pin->setup(); + cs_pin->digital_write(true); + this->cs_pin_ = cs_pin; + } + void begin_transaction() override { this->cs_pin_->digital_write(false); } + + void end_transaction() override { this->cs_pin_->digital_write(true); } + + void write_array(const uint8_t *data, size_t length) override { + while (length != 0) { + write_complete = false; + auto chunk = length; + if (chunk > MAX_TRANSFER) + chunk = MAX_TRANSFER; + auto err = esp_lcd_panel_io_tx_color(this->handle_, -1, data, chunk); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Data write failed- err %X", err); + return; + } + length -= chunk; + data += chunk; + while (!write_complete) + continue; + } + } + + void write_cmd_data(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); + auto err = esp_lcd_panel_io_tx_param(this->handle_, cmd, data, length); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Data write failed- err %X", err); + } + } else { + auto err = esp_lcd_panel_io_tx_param(this->handle_, cmd, nullptr, 0); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Command write failed- err %X", err); + return; + } + this->write_array(data, length); + } + this->end_transaction(); + } + + protected: + esp_lcd_panel_io_handle_t handle_; + GPIOPin *cs_pin_; +}; + +class I80BusIdf : public I80Bus { + public: + I80BusIdf(InternalGPIOPin *wr_pin, InternalGPIOPin *dc_pin, std::vector data_pins) { + esp_lcd_i80_bus_config_t config = {}; + config.bus_width = 8; + config.dc_gpio_num = dc_pin->get_pin(); + config.wr_gpio_num = wr_pin->get_pin(); + config.clk_src = LCD_CLK_SRC_PLL160M; + config.max_transfer_bytes = MAX_TRANSFER; + config.psram_trans_align = 16; + config.sram_trans_align = 16; + for (size_t i = 0; i != data_pins.size(); i++) { + config.data_gpio_nums[i] = data_pins[i]; + } + auto err = esp_lcd_new_i80_bus(&config, &this->handle_); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Bus init failed - err %X", err); + this->handle_ = nullptr; + } + } + + I80Delegate *get_delegate(GPIOPin *cs_pin, uint32_t data_rate) override { + esp_lcd_panel_io_i80_config_t config = {}; + config.on_color_trans_done = trans_done; + config.cs_gpio_num = -1; + config.pclk_hz = data_rate; + config.trans_queue_depth = 1; + config.lcd_cmd_bits = 8; + config.lcd_param_bits = 8; + config.dc_levels.dc_cmd_level = 0; + config.dc_levels.dc_data_level = 1; + config.flags.pclk_active_neg = false; + config.flags.pclk_idle_low = false; + + esp_lcd_panel_io_handle_t d_handle; + auto err = esp_lcd_new_panel_io_i80(this->handle_, &config, &d_handle); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Panel init failed - err %X", err); + return NULL_DELEGATE; + } + return new I80DelegateIdf(d_handle, cs_pin); // NOLINT + } + + bool is_failed() { return this->handle_ == nullptr; } + + protected: + 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 3aaf76d6f8ba..4a2531675e95 100644 --- a/esphome/components/ili9xxx/display.py +++ b/esphome/components/ili9xxx/display.py @@ -1,9 +1,19 @@ import esphome.codegen as cg import esphome.config_validation as cv -from esphome import core, pins -from esphome.components import display, spi, font +from esphome import ( + core, + pins, +) +from esphome.components import ( + display, + spi, + i80, +) from esphome.components.display import validate_rotation -from esphome.core import CORE, HexInt +from esphome.core import ( + CORE, + HexInt, +) from esphome.const import ( CONF_COLOR_PALETTE, CONF_DC_PIN, @@ -25,16 +35,10 @@ CONF_OFFSET_WIDTH, CONF_TRANSFORM, CONF_INVERT_COLORS, + CONF_DATA_RATE, ) -DEPENDENCIES = ["spi"] - - -def AUTO_LOAD(): - if CORE.is_esp32: - return ["psram"] - return [] - +DEPENDENCIES = [] CODEOWNERS = ["@nielsnl68", "@clydebarrow"] @@ -42,7 +46,6 @@ def AUTO_LOAD(): ILI9XXXDisplay = ili9xxx_ns.class_( "ILI9XXXDisplay", cg.PollingComponent, - spi.SPIDevice, display.Display, display.DisplayBuffer, ) @@ -80,6 +83,8 @@ def AUTO_LOAD(): CONF_LED_PIN = "led_pin" CONF_COLOR_PALETTE_IMAGES = "color_palette_images" CONF_INVERT_DISPLAY = "invert_display" +CONF_BUS_TYPE = "bus_type" +CONF_BYTE_BUS_ID = "byte_bus_id" def _validate(config): @@ -104,56 +109,73 @@ def _validate(config): "ILI9342", "ST7789V", ]: - raise cv.Invalid( - "Provided model can't run on ESP8266. Use an ESP32 with PSRAM onboard" - ) + raise cv.Invalid("Selected model can't run on ESP8266; use an ESP32 with PSRAM") return config +BASE_SCHEMA = display.FULL_DISPLAY_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(ILI9XXXDisplay), + cv.Required(CONF_MODEL): cv.enum(MODELS, upper=True, space="_"), + cv.Optional(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_RESET_PIN): pins.gpio_output_pin_schema, + cv.Optional(CONF_LED_PIN): cv.invalid( + "This property is removed. To use the backlight use proper light component." + ), + cv.Optional(CONF_COLOR_PALETTE, default="NONE"): COLOR_PALETTE, + cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_id(cg.uint8), + cv.Optional(CONF_COLOR_PALETTE_IMAGES, default=[]): cv.ensure_list(cv.file_), + cv.Optional(CONF_INVERT_DISPLAY): cv.invalid( + "'invert_display' has been replaced by 'invert_colors'" + ), + cv.Optional(CONF_INVERT_COLORS): cv.boolean, + cv.Optional(CONF_COLOR_ORDER): cv.one_of(*COLOR_ORDERS.keys(), upper=True), + cv.Exclusive(CONF_ROTATION, CONF_ROTATION): validate_rotation, + cv.Exclusive(CONF_TRANSFORM, CONF_ROTATION): cv.Schema( + { + cv.Optional(CONF_SWAP_XY, default=False): cv.boolean, + cv.Optional(CONF_MIRROR_X, default=False): cv.boolean, + cv.Optional(CONF_MIRROR_Y, default=False): cv.boolean, + } + ), + } +).extend(cv.polling_component_schema("1s")) + +TYPE_SPI = "spi" +TYPE_I80 = "i80" + CONFIG_SCHEMA = cv.All( - font.validate_pillow_installed, - display.FULL_DISPLAY_SCHEMA.extend( + cv.typed_schema( { - cv.GenerateID(): cv.declare_id(ILI9XXXDisplay), - cv.Required(CONF_MODEL): cv.enum(MODELS, upper=True, space="_"), - cv.Optional(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.Required(CONF_DC_PIN): pins.gpio_output_pin_schema, - cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema, - cv.Optional(CONF_LED_PIN): cv.invalid( - "This property is removed. To use the backlight use proper light component." - ), - cv.Optional(CONF_COLOR_PALETTE, default="NONE"): COLOR_PALETTE, - cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_id(cg.uint8), - cv.Optional(CONF_COLOR_PALETTE_IMAGES, default=[]): cv.ensure_list( - cv.file_ - ), - cv.Optional(CONF_INVERT_DISPLAY): cv.invalid( - "'invert_display' has been replaced by 'invert_colors'" + 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), + } ), - cv.Optional(CONF_INVERT_COLORS): cv.boolean, - cv.Optional(CONF_COLOR_ORDER): cv.one_of(*COLOR_ORDERS.keys(), upper=True), - cv.Exclusive(CONF_ROTATION, CONF_ROTATION): validate_rotation, - cv.Exclusive(CONF_TRANSFORM, CONF_ROTATION): cv.Schema( + TYPE_I80: BASE_SCHEMA.extend(i80.i80_client_schema()).extend( { - cv.Optional(CONF_SWAP_XY, default=False): cv.boolean, - cv.Optional(CONF_MIRROR_X, default=False): cv.boolean, - cv.Optional(CONF_MIRROR_Y, default=False): cv.boolean, + cv.Optional(CONF_DC_PIN): cv.invalid( + "DC pin should be specified in the i80 component only" + ) } ), - } - ) - .extend(cv.polling_component_schema("1s")) - .extend(spi.spi_device_schema(False, "40MHz")), + }, + default_type=TYPE_SPI, + key=CONF_BUS_TYPE, + ), cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA), _validate, ) @@ -163,10 +185,18 @@ async def to_code(config): rhs = MODELS[config[CONF_MODEL]].new() var = cg.Pvariable(config[CONF_ID], rhs) + data_rate = int(max(config[CONF_DATA_RATE] / 1e6, 1)) await display.register_display(var, config) - await spi.register_spi_device(var, config) - dc = await cg.gpio_pin_expression(config[CONF_DC_PIN]) - cg.add(var.set_dc_pin(dc)) + 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) + 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 e292906a9330..8c33984b0c3c 100644 --- a/esphome/components/ili9xxx/ili9xxx_display.cpp +++ b/esphome/components/ili9xxx/ili9xxx_display.cpp @@ -25,19 +25,23 @@ void ILI9XXXDisplay::set_madctl() { mad |= MADCTL_MX; if (this->mirror_y_) mad |= MADCTL_MY; - this->command(ILI9XXX_MADCTL); - this->data(mad); - esph_log_d(TAG, "Wrote MADCTL 0x%02X", mad); + this->send_command(ILI9XXX_MADCTL, &mad, 1); + ESP_LOGD(TAG, "Wrote MADCTL 0x%02X", mad); } void ILI9XXXDisplay::setup() { ESP_LOGD(TAG, "Setting up ILI9xxx"); - this->setup_pins_(); + this->reset_pin_->setup(); // OUTPUT + this->reset_pin_->digital_write(true); + this->bus_->bus_setup(); + this->reset_pin_->digital_write(false); + delay(20); + this->reset_pin_->digital_write(true); + delay(20); this->init_lcd_(); - this->set_madctl(); - this->command(this->pre_invertcolors_ ? ILI9XXX_INVON : ILI9XXX_INVOFF); + this->send_command(this->pre_invertcolors_ ? ILI9XXX_INVON : ILI9XXX_INVOFF); this->x_low_ = this->width_; this->y_low_ = this->height_; this->x_high_ = 0; @@ -58,19 +62,6 @@ void ILI9XXXDisplay::alloc_buffer_() { } } -void ILI9XXXDisplay::setup_pins_() { - this->dc_pin_->setup(); // OUTPUT - this->dc_pin_->digital_write(false); - if (this->reset_pin_ != nullptr) { - this->reset_pin_->setup(); // OUTPUT - this->reset_pin_->digital_write(true); - } - - this->spi_setup(); - - this->reset_(); -} - void ILI9XXXDisplay::dump_config() { LOG_DISPLAY("", "ili9xxx", this); ESP_LOGCONFIG(TAG, " Width Offset: %u", this->offset_x_); @@ -89,17 +80,14 @@ void ILI9XXXDisplay::dump_config() { if (this->is_18bitdisplay_) { ESP_LOGCONFIG(TAG, " 18-Bit Mode: YES"); } - ESP_LOGCONFIG(TAG, " Data rate: %dMHz", (unsigned) (this->data_rate_ / 1000000)); LOG_PIN(" Reset Pin: ", this->reset_pin_); - LOG_PIN(" CS Pin: ", this->cs_); - LOG_PIN(" DC Pin: ", this->dc_pin_); - LOG_PIN(" Busy Pin: ", this->busy_pin_); ESP_LOGCONFIG(TAG, " Color order: %s", this->color_order_ == display::COLOR_ORDER_BGR ? "BGR" : "RGB"); ESP_LOGCONFIG(TAG, " Swap_xy: %s", YESNO(this->swap_xy_)); ESP_LOGCONFIG(TAG, " Mirror_x: %s", YESNO(this->mirror_x_)); ESP_LOGCONFIG(TAG, " Mirror_y: %s", YESNO(this->mirror_y_)); + this->bus_->dump_config(); if (this->is_failed()) { ESP_LOGCONFIG(TAG, " => Failed to init Memory: YES!"); } @@ -111,7 +99,7 @@ float ILI9XXXDisplay::get_setup_priority() const { return setup_priority::HARDWA void ILI9XXXDisplay::fill(Color color) { if (!this->check_buffer_()) return; - uint16_t new_color = 0; + uint16_t new_color; this->x_low_ = 0; this->y_low_ = 0; this->x_high_ = this->get_width_internal() - 1; @@ -135,7 +123,6 @@ void ILI9XXXDisplay::fill(Color color) { } } return; - break; default: new_color = display::ColorUtil::color_to_332(color, display::ColorOrder::COLOR_ORDER_RGB); break; @@ -213,11 +200,11 @@ void ILI9XXXDisplay::display_() { size_t const w = this->x_high_ - this->x_low_ + 1; size_t const h = this->y_high_ - this->y_low_ + 1; - size_t mhz = this->data_rate_ / 1000000; // estimate time for a single write - size_t sw_time = this->width_ * h * 16 / mhz + this->width_ * h * 2 / SPI_MAX_BLOCK_SIZE * SPI_SETUP_US * 2; + size_t sw_time = + this->width_ * h * 2 / this->data_rate_ + this->width_ * h * 2 / SPI_MAX_BLOCK_SIZE * SPI_SETUP_US * 2; // estimate time for multiple writes - size_t mw_time = (w * h * 16) / mhz + w * h * 2 / ILI9XXX_TRANSFER_BUFFER_SIZE * SPI_SETUP_US; + size_t mw_time = (w * h * 2) / this->data_rate_ + w * h * 2 / ILI9XXX_TRANSFER_BUFFER_SIZE * SPI_SETUP_US; ESP_LOGV(TAG, "Start display(xlow:%d, ylow:%d, xhigh:%d, yhigh:%d, width:%d, " "height:%zu, mode=%d, 18bit=%d, sw_time=%zuus, mw_time=%zuus)", @@ -228,7 +215,7 @@ void ILI9XXXDisplay::display_() { // 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->write_array(this->buffer_ + this->y_low_ * this->width_ * 2, h * this->width_ * 2); + 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 @@ -260,7 +247,7 @@ void ILI9XXXDisplay::display_() { idx += 2; } if (idx == ILI9XXX_TRANSFER_BUFFER_SIZE) { - this->write_array(transfer_buffer, idx); + this->bus_->write_array(transfer_buffer, idx); idx = 0; App.feed_wdt(); } @@ -272,10 +259,10 @@ void ILI9XXXDisplay::display_() { } // flush any balance. if (idx != 0) { - this->write_array(transfer_buffer, idx); + this->bus_->write_array(transfer_buffer, idx); } } - this->end_data_(); + this->bus_->end_transaction(); ESP_LOGV(TAG, "Data write took %dms", (unsigned) (millis() - now)); // invalidate watermarks this->x_low_ = this->width_; @@ -302,58 +289,22 @@ void ILI9XXXDisplay::draw_pixels_at(int x_start, int y_start, int w, int h, cons // 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 - this->write_array(ptr, w * h * 2); + this->bus_->write_array(ptr, w * h * 2); } else { auto stride = x_offset + w + x_pad; for (size_t y = 0; y != h; y++) { - this->write_array(ptr + (y + y_offset) * stride + x_offset, w * 2); + this->bus_->write_array(ptr + (y + y_offset) * stride + x_offset, w * 2); } } - this->end_data_(); + this->bus_->end_transaction(); } // should return the total size: return this->get_width_internal() * this->get_height_internal() * 2 // 16bit color // values per bit is huge uint32_t ILI9XXXDisplay::get_buffer_length_() { return this->get_width_internal() * this->get_height_internal(); } -void ILI9XXXDisplay::command(uint8_t value) { - this->start_command_(); - this->write_byte(value); - this->end_command_(); -} - -void ILI9XXXDisplay::data(uint8_t value) { - this->start_data_(); - this->write_byte(value); - this->end_data_(); -} - -void ILI9XXXDisplay::send_command(uint8_t command_byte, const uint8_t *data_bytes, uint8_t num_data_bytes) { - this->command(command_byte); // Send the command byte - this->start_data_(); - this->write_array(data_bytes, num_data_bytes); - this->end_data_(); -} - -void ILI9XXXDisplay::start_command_() { - this->dc_pin_->digital_write(false); - this->enable(); -} -void ILI9XXXDisplay::start_data_() { - this->dc_pin_->digital_write(true); - this->enable(); -} - -void ILI9XXXDisplay::end_command_() { this->disable(); } -void ILI9XXXDisplay::end_data_() { this->disable(); } - -void ILI9XXXDisplay::reset_() { - if (this->reset_pin_ != nullptr) { - this->reset_pin_->digital_write(false); - delay(20); - this->reset_pin_->digital_write(true); - delay(20); - } +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_() { @@ -375,24 +326,25 @@ void ILI9XXXDisplay::set_addr_window_(uint16_t x1, uint16_t y1, uint16_t x2, uin x2 += this->offset_x_; y1 += this->offset_y_; y2 += this->offset_y_; - this->command(ILI9XXX_CASET); - this->data(x1 >> 8); - this->data(x1 & 0xFF); - this->data(x2 >> 8); - this->data(x2 & 0xFF); - this->command(ILI9XXX_PASET); // Page address set - this->data(y1 >> 8); - this->data(y1 & 0xFF); - this->data(y2 >> 8); - this->data(y2 & 0xFF); - this->command(ILI9XXX_RAMWR); // Write to RAM - this->start_data_(); + uint8_t buf[4]; + buf[0] = x1 >> 8; + buf[1] = x1; + buf[2] = x2 >> 8; + buf[3] = x2; + this->send_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(); } void ILI9XXXDisplay::invert_colors(bool invert) { this->pre_invertcolors_ = invert; if (is_ready()) { - this->command(invert ? ILI9XXX_INVON : ILI9XXX_INVOFF); + this->send_command(invert ? ILI9XXX_INVON : ILI9XXX_INVOFF); } } diff --git a/esphome/components/ili9xxx/ili9xxx_display.h b/esphome/components/ili9xxx/ili9xxx_display.h index 11a90e142f51..60bee46e01cf 100644 --- a/esphome/components/ili9xxx/ili9xxx_display.h +++ b/esphome/components/ili9xxx/ili9xxx_display.h @@ -1,5 +1,5 @@ #pragma once -#include "esphome/components/spi/spi.h" +#include "esphome/components/byte_bus/byte_bus.h" #include "esphome/components/display/display_buffer.h" #include "esphome/components/display/display_color_utils.h" #include "ili9xxx_defines.h" @@ -17,9 +17,7 @@ enum ILI9XXXColorMode { BITS_16 = 0x10, }; -class ILI9XXXDisplay : public display::DisplayBuffer, - public spi::SPIDevice { +class ILI9XXXDisplay : public display::DisplayBuffer { public: ILI9XXXDisplay() = default; ILI9XXXDisplay(uint8_t const *init_sequence, int16_t width, int16_t height, bool invert_colors) @@ -29,7 +27,7 @@ class ILI9XXXDisplay : public display::DisplayBuffer, while ((cmd = *addr++) != 0) { num_args = *addr++ & 0x7F; bits = *addr; - esph_log_d(TAG, "Command %02X, length %d, bits %02X", cmd, num_args, bits); + esph_log_v(TAG, "Command %02X, length %d, bits %02X", cmd, num_args, bits); switch (cmd) { case ILI9XXX_MADCTL: { this->swap_xy_ = (bits & MADCTL_MV) != 0; @@ -52,7 +50,6 @@ class ILI9XXXDisplay : public display::DisplayBuffer, } } - void set_dc_pin(GPIOPin *dc_pin) { dc_pin_ = dc_pin; } float get_setup_priority() const override; void set_reset_pin(GPIOPin *reset) { this->reset_pin_ = reset; } void set_palette(const uint8_t *palette) { this->palette_ = palette; } @@ -66,13 +63,13 @@ class ILI9XXXDisplay : public display::DisplayBuffer, this->offset_y_ = offset_y; } void invert_colors(bool invert); - virtual void command(uint8_t value); - virtual void data(uint8_t value); - void send_command(uint8_t command_byte, const uint8_t *data_bytes, uint8_t num_data_bytes); + 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; } void set_mirror_y(bool mirror_y) { this->mirror_y_ = mirror_y; } + void set_bus(byte_bus::ByteBus *bus) { this->bus_ = bus; } void update() override; @@ -85,6 +82,8 @@ class ILI9XXXDisplay : public display::DisplayBuffer, 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 set_data_rate(int data_rate) { this->data_rate_ = data_rate; } + protected: inline bool check_buffer_() { if (this->buffer_ == nullptr) { @@ -95,14 +94,13 @@ class ILI9XXXDisplay : public display::DisplayBuffer, } void draw_absolute_pixel_internal(int x, int y, Color color) override; - void setup_pins_(); virtual void set_madctl(); void display_(); void init_lcd_(); void set_addr_window_(uint16_t x, uint16_t y, uint16_t x2, uint16_t y2); - void reset_(); + byte_bus::ByteBus *bus_{nullptr}; uint8_t const *init_sequence_{}; int16_t width_{0}; ///< Display width as modified by current rotation int16_t height_{0}; ///< Display height as modified by current rotation @@ -112,7 +110,8 @@ class ILI9XXXDisplay : public display::DisplayBuffer, uint16_t y_low_{0}; uint16_t x_high_{0}; uint16_t y_high_{0}; - const uint8_t *palette_; + const uint8_t *palette_{}; + uint32_t data_rate_{4000000}; ILI9XXXColorMode buffer_color_mode_{BITS_16}; @@ -120,15 +119,9 @@ class ILI9XXXDisplay : public display::DisplayBuffer, int get_width_internal() override; int get_height_internal() override; - void start_command_(); - void end_command_(); - void start_data_(); - void end_data_(); void alloc_buffer_(); - GPIOPin *reset_pin_{nullptr}; - GPIOPin *dc_pin_{nullptr}; - GPIOPin *busy_pin_{nullptr}; + GPIOPin *reset_pin_{byte_bus::NULL_PIN}; bool prossing_update_ = false; bool need_update_ = false; @@ -195,40 +188,51 @@ class ILI9XXXILI9488 : public ILI9XXXDisplay { protected: void set_madctl() override { uint8_t mad = this->color_order_ == display::COLOR_ORDER_BGR ? MADCTL_BGR : MADCTL_RGB; - uint8_t dfun = 0x22; + uint8_t dfun[2] = {0, 0x22}; this->width_ = 320; this->height_ = 480; if (!(this->swap_xy_ || this->mirror_x_ || this->mirror_y_)) { // no transforms } else if (this->mirror_y_ && this->mirror_x_) { // rotate 180 - dfun = 0x42; + dfun[1] = 0x42; } else if (this->swap_xy_) { this->width_ = 480; this->height_ = 320; mad |= 0x20; if (this->mirror_x_) { - dfun = 0x02; + dfun[1] = 0x02; } else { - dfun = 0x62; + dfun[1] = 0x62; } } - this->command(ILI9XXX_DFUNCTR); - this->data(0); - this->data(dfun); - this->command(ILI9XXX_MADCTL); - this->data(mad); + this->send_command(ILI9XXX_DFUNCTR, dfun, sizeof dfun); + this->send_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) {} - void data(uint8_t value) override { - this->start_data_(); - this->write_byte(0); - this->write_byte(value); - this->end_data_(); + /* + * 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(); + } } }; @@ -241,7 +245,7 @@ class ILI9XXXILI9488A : public ILI9XXXDisplay { //----------- ILI9XXX_35_TFT rotated display -------------- class ILI9XXXST7796 : public ILI9XXXDisplay { public: - ILI9XXXST7796() : ILI9XXXDisplay(INITCMD_ST7796, 320, 480, false) {} + ILI9XXXST7796() : ILI9XXXDisplay(INITCMD_ST7796, 480, 320, false) {} }; class ILI9XXXS3Box : public ILI9XXXDisplay { diff --git a/esphome/components/ili9xxx/ili9xxx_init.h b/esphome/components/ili9xxx/ili9xxx_init.h index ea90f83f30fd..ca662656f3a4 100644 --- a/esphome/components/ili9xxx/ili9xxx_init.h +++ b/esphome/components/ili9xxx/ili9xxx_init.h @@ -6,6 +6,7 @@ namespace esphome { namespace ili9xxx { +#define PROGMEM // clang-format off static const uint8_t PROGMEM INITCMD_M5STACK[] = { 0xEF, 3, 0x03, 0x80, 0x02, @@ -215,15 +216,11 @@ static const uint8_t PROGMEM INITCMD_ST7796[] = { ILI9XXX_CSCON, 1, 0xC3, // ?? Unlock Manufacturer ILI9XXX_CSCON, 1, 0x96, ILI9XXX_VMCTR1, 1, 0x1C, //VCOM Control 1 [1C] - ILI9XXX_MADCTL, 1, 0x48, //Memory Access [00] ILI9XXX_PIXFMT, 1, 0x55, //565 ILI9XXX_IFMODE, 1, 0x80, //Interface [00] ILI9XXX_INVCTR, 1, 0x01, //Inversion Control [01] - ILI9XXX_DFUNCTR, 3, 0x80, 0x02, 0x3B, // Display Function Control [80 02 3B] .kbv SS=1, NL=480 ILI9XXX_ETMOD, 1, 0xC6, //Entry Mode [06] - ILI9XXX_CSCON, 1, 0x69, //?? lock manufacturer commands - ILI9XXX_CSCON, 1, 0x3C, // ILI9XXX_SLPOUT, 0x80, // Exit Sleep, then delay 150 ms ILI9XXX_DISPON, 0x80, // Main screen turn on, delay 150 ms 0x00 // End of list diff --git a/esphome/components/spi/__init__.py b/esphome/components/spi/__init__.py index fdf19bb56e4c..a939e795a86d 100644 --- a/esphome/components/spi/__init__.py +++ b/esphome/components/spi/__init__.py @@ -3,6 +3,7 @@ import esphome.codegen as cg import esphome.config_validation as cv import esphome.final_validate as fv +from esphome.components import byte_bus from esphome.components.esp32.const import ( KEY_ESP32, VARIANT_ESP32S2, @@ -30,6 +31,7 @@ PLATFORM_ESP8266, PLATFORM_RP2040, CONF_DATA_PINS, + CONF_CLIENT_ID, ) from esphome.core import ( coroutine_with_priority, @@ -37,12 +39,17 @@ ) CODEOWNERS = ["@esphome/core", "@clydebarrow"] +AUTO_LOAD = ["byte_bus"] + spi_ns = cg.esphome_ns.namespace("spi") SPIComponent = spi_ns.class_("SPIComponent", cg.Component) QuadSPIComponent = spi_ns.class_("QuadSPIComponent", cg.Component) SPIDevice = spi_ns.class_("SPIDevice") +SPIClient = spi_ns.class_("SPIClient") +SPIByteBus = spi_ns.class_("SPIByteBus", byte_bus.ByteBus) SPIDataRate = spi_ns.enum("SPIDataRate") SPIMode = spi_ns.enum("SPIMode") +BitOrder = spi_ns.enum("SPIBitOrder") SPI_DATA_RATE_OPTIONS = { 80e6: SPIDataRate.DATA_RATE_80MHZ, @@ -71,6 +78,11 @@ 3: SPIMode.MODE3, } +ORDERS = { + "msb_first": BitOrder.BIT_ORDER_MSB_FIRST, + "lsb_first": BitOrder.BIT_ORDER_LSB_FIRST, +} +CONF_BIT_ORDER = "bit_order" CONF_SPI_MODE = "spi_mode" CONF_FORCE_SW = "force_sw" CONF_INTERFACE = "interface" @@ -361,9 +373,11 @@ def spi_device_schema( QuadSPIComponent if quad else SPIComponent ), cv.Optional(CONF_DATA_RATE, default=default_data_rate): SPI_DATA_RATE_SCHEMA, + cv.Optional(CONF_BIT_ORDER, default="msb_first"): cv.enum(ORDERS, lower=True), cv.Optional(CONF_SPI_MODE, default=default_mode): cv.enum( SPI_MODE_OPTIONS, upper=True ), + cv.GenerateID(CONF_CLIENT_ID): cv.declare_id(SPIClient), } if cs_pin_required: schema[cv.Required(CONF_CS_PIN)] = pins.gpio_output_pin_schema @@ -373,8 +387,7 @@ def spi_device_schema( async def register_spi_device(var, config): - parent = await cg.get_variable(config[CONF_SPI_ID]) - cg.add(var.set_spi_parent(parent)) + await cg.register_parented(var, config[CONF_SPI_ID]) if CONF_CS_PIN in config: pin = await cg.gpio_pin_expression(config[CONF_CS_PIN]) cg.add(var.set_cs_pin(pin)) @@ -384,6 +397,21 @@ async def register_spi_device(var, config): cg.add(var.set_mode(config[CONF_SPI_MODE])) +async def create_spi_client(config): + """ + Create an SPIClient object. Note that this requires a data_rate, so the call to spi_device_schema must specify + a default data rate, or make it required. + """ + client_id = config[CONF_CLIENT_ID] + var = cg.new_Pvariable( + client_id, config[CONF_BIT_ORDER], config[CONF_SPI_MODE], config[CONF_DATA_RATE] + ) + cg.add(var.set_parent(await cg.get_variable(config[CONF_SPI_ID]))) + if cs_pin := config.get(CONF_CS_PIN): + cg.add(var.set_cs_pin(await cg.gpio_pin_expression(cs_pin))) + return var + + def final_validate_device_schema(name: str, *, require_mosi: bool, require_miso: bool): hub_schema = {} if require_miso: diff --git a/esphome/components/spi/spi.cpp b/esphome/components/spi/spi.cpp index b13826c4430b..9ce91c4b850f 100644 --- a/esphome/components/spi/spi.cpp +++ b/esphome/components/spi/spi.cpp @@ -1,5 +1,6 @@ #include "spi.h" #include "esphome/core/log.h" +#include "esphome/core/gpio.h" #include "esphome/core/application.h" namespace esphome { @@ -13,8 +14,6 @@ SPIDelegate *const SPIDelegate::NULL_DELEGATE = // NOLINT(cppcoreguidelines-avo bool SPIDelegate::is_ready() { return true; } -GPIOPin *const NullPin::NULL_PIN = new NullPin(); // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) - SPIDelegate *SPIComponent::register_device(SPIClient *device, SPIMode mode, SPIBitOrder bit_order, uint32_t data_rate, GPIOPin *cs_pin) { if (this->devices_.count(device) != 0) { @@ -39,9 +38,9 @@ void SPIComponent::setup() { ESP_LOGD(TAG, "Setting up SPI bus..."); if (this->sdo_pin_ == nullptr) - this->sdo_pin_ = NullPin::NULL_PIN; + this->sdo_pin_ = byte_bus::NULL_PIN; if (this->sdi_pin_ == nullptr) - this->sdi_pin_ = NullPin::NULL_PIN; + this->sdi_pin_ = byte_bus::NULL_PIN; if (this->clk_pin_ == nullptr) { ESP_LOGE(TAG, "No clock pin for SPI"); this->mark_failed(); @@ -64,6 +63,27 @@ void SPIComponent::setup() { } } +void SPIByteBus::write_cmd_data(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) { + this->dc_pin_->digital_write(false); + this->client_->write_byte(cmd); + } + this->dc_pin_->digital_write(true); + if (length != 0) { + this->write_array(data, length); + } + this->end_transaction(); +} + +void SPIByteBus::dump_config() { + ESP_LOGCONFIG(TAG, " SPI Mode: %u", (unsigned) this->client_->mode_); + ESP_LOGCONFIG(TAG, " Data rate: %dMHz", (unsigned) (this->client_->data_rate_ / 1000000)); + LOG_PIN(" CS Pin: ", this->client_->cs_); + LOG_PIN(" DC Pin: ", this->dc_pin_); +} + void SPIComponent::dump_config() { ESP_LOGCONFIG(TAG, "SPI bus:"); LOG_PIN(" CLK Pin: ", this->clk_pin_) diff --git a/esphome/components/spi/spi.h b/esphome/components/spi/spi.h index f581dc3f569f..bd2f42a2390a 100644 --- a/esphome/components/spi/spi.h +++ b/esphome/components/spi/spi.h @@ -4,6 +4,7 @@ #include "esphome/core/component.h" #include "esphome/core/hal.h" #include "esphome/core/log.h" +#include "esphome/components/byte_bus/byte_bus.h" #include #include #include @@ -99,32 +100,6 @@ enum SPIDataRate : uint32_t { DATA_RATE_80MHZ = 80000000, }; -/** - * A pin to replace those that don't exist. - */ -class NullPin : public GPIOPin { - friend class SPIComponent; - - friend class SPIDelegate; - - friend class Utility; - - public: - void setup() override {} - - void pin_mode(gpio::Flags flags) override {} - - bool digital_read() override { return false; } - - void digital_write(bool value) override {} - - std::string dump_summary() const override { return std::string(); } - - protected: - static GPIOPin *const NULL_PIN; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) - // https://bugs.llvm.org/show_bug.cgi?id=48040 -}; - class Utility { public: static int get_pin_no(GPIOPin *pin) { @@ -176,7 +151,7 @@ class SPIDelegate { SPIDelegate(uint32_t data_rate, SPIBitOrder bit_order, SPIMode mode, GPIOPin *cs_pin) : bit_order_(bit_order), data_rate_(data_rate), mode_(mode), cs_pin_(cs_pin) { if (this->cs_pin_ == nullptr) - this->cs_pin_ = NullPin::NULL_PIN; + this->cs_pin_ = byte_bus::NULL_PIN; this->cs_pin_->setup(); this->cs_pin_->digital_write(true); } @@ -249,7 +224,7 @@ class SPIDelegate { SPIBitOrder bit_order_{BIT_ORDER_MSB_FIRST}; uint32_t data_rate_{1000000}; SPIMode mode_{MODE0}; - GPIOPin *cs_pin_{NullPin::NULL_PIN}; + GPIOPin *cs_pin_{byte_bus::NULL_PIN}; static SPIDelegate *const NULL_DELEGATE; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) }; @@ -367,66 +342,33 @@ class SPIComponent : public Component { }; using QuadSPIComponent = SPIComponent; + /** * Base class for SPIDevice, un-templated. */ class SPIClient { + friend class SPIByteBus; + public: + SPIClient() = default; SPIClient(SPIBitOrder bit_order, SPIMode mode, uint32_t data_rate) : bit_order_(bit_order), mode_(mode), data_rate_(data_rate) {} - virtual void spi_setup() { + void spi_setup() { esph_log_d("spi_device", "mode %u, data_rate %ukHz", (unsigned) this->mode_, (unsigned) (this->data_rate_ / 1000)); this->delegate_ = this->parent_->register_device(this, this->mode_, this->bit_order_, this->data_rate_, this->cs_); } - virtual void spi_teardown() { + // backwards compatibility + + void spi_teardown() { this->parent_->unregister_device(this); this->delegate_ = SPIDelegate::NULL_DELEGATE; } - bool spi_is_ready() { return this->delegate_->is_ready(); } - - protected: - SPIBitOrder bit_order_{BIT_ORDER_MSB_FIRST}; - SPIMode mode_{MODE0}; - uint32_t data_rate_{1000000}; - SPIComponent *parent_{nullptr}; - GPIOPin *cs_{nullptr}; - SPIDelegate *delegate_{SPIDelegate::NULL_DELEGATE}; -}; - -/** - * The SPIDevice is what components using the SPI will create. - * - * @tparam BIT_ORDER - * @tparam CLOCK_POLARITY - * @tparam CLOCK_PHASE - * @tparam DATA_RATE - */ -template -class SPIDevice : public SPIClient { - public: - SPIDevice() : SPIClient(BIT_ORDER, Utility::get_mode(CLOCK_POLARITY, CLOCK_PHASE), DATA_RATE) {} - - SPIDevice(SPIComponent *parent, GPIOPin *cs_pin) { - this->set_spi_parent(parent); - this->set_cs_pin(cs_pin); - } - - void spi_setup() override { SPIClient::spi_setup(); } - - void spi_teardown() override { SPIClient::spi_teardown(); } - - void set_spi_parent(SPIComponent *parent) { this->parent_ = parent; } - - void set_cs_pin(GPIOPin *cs) { this->cs_ = cs; } - - void set_data_rate(uint32_t data_rate) { this->data_rate_ = data_rate; } + void set_parent(SPIComponent *parent) { this->parent_ = parent; } - void set_bit_order(SPIBitOrder order) { this->bit_order_ = order; } - - void set_mode(SPIMode mode) { this->mode_ = mode; } + void set_spi_parent(SPIComponent *parent) { this->set_parent(parent); } uint8_t read_byte() { return this->delegate_->transfer(0); } @@ -456,15 +398,6 @@ class SPIDevice : public SPIClient { void write_byte(uint8_t data) { this->delegate_->write_array(&data, 1); } - /** - * Write the array data, replace with received data. - * @param data - * @param length - */ - void transfer_array(uint8_t *data, size_t length) { this->delegate_->transfer(data, length); } - - uint8_t transfer_byte(uint8_t data) { return this->delegate_->transfer(data); } - /** Write 16 bit data. The driver will byte-swap if required. */ void write_byte16(uint16_t data) { this->delegate_->write16(data); } @@ -477,18 +410,77 @@ class SPIDevice : public SPIClient { */ void write_array16(const uint16_t *data, size_t length) { this->delegate_->write_array16(data, length); } + /** + * Write the array data, replace with received data. + * @param data + * @param length + */ + void transfer_array(uint8_t *data, size_t length) { this->delegate_->transfer(data, length); } + + uint8_t transfer_byte(uint8_t data) { return this->delegate_->transfer(data); } + void enable() { this->delegate_->begin_transaction(); } void disable() { this->delegate_->end_transaction(); } - void write_array(const uint8_t *data, size_t length) { this->delegate_->write_array(data, length); } + // legacy functions - template void write_array(const std::array &data) { this->write_array(data.data(), N); } + void write_array(const uint8_t *data, size_t length) { this->delegate_->write_array(data, length); } void write_array(const std::vector &data) { this->write_array(data.data(), data.size()); } - template void transfer_array(std::array &data) { this->transfer_array(data.data(), N); } + void set_bit_order(SPIBitOrder order) { this->bit_order_ = order; } + + void set_cs_pin(GPIOPin *cs) { this->cs_ = cs; } + + void set_mode(SPIMode mode) { this->mode_ = mode; } + void set_data_rate(int data_rate) { this->data_rate_ = data_rate; } + + protected: + SPIBitOrder bit_order_{BIT_ORDER_MSB_FIRST}; + SPIMode mode_{MODE0}; + SPIDelegate *delegate_{SPIDelegate::NULL_DELEGATE}; + SPIComponent *parent_{}; + GPIOPin *cs_{byte_bus::NULL_PIN}; + uint32_t data_rate_{1000000}; +}; + +/** + * Templated version of SPIClient + * + * @tparam BIT_ORDER + * @tparam CLOCK_POLARITY + * @tparam CLOCK_PHASE + * @tparam DATA_RATE + */ +template +class SPIDevice : public SPIClient { + public: + SPIDevice() : SPIClient(BIT_ORDER, Utility::get_mode(CLOCK_POLARITY, CLOCK_PHASE), DATA_RATE) {} }; +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 dump_config() override; + void set_dc_pin(GPIOPin *dc_pin) { this->dc_pin_ = dc_pin; } + + protected: + SPIClient *client_; + GPIOPin *dc_pin_{byte_bus::NULL_PIN}; +}; } // namespace spi } // namespace esphome diff --git a/esphome/components/spi_device/__init__.py b/esphome/components/spi_device/__init__.py index 65e7ee6fc630..89e65e1ea976 100644 --- a/esphome/components/spi_device/__init__.py +++ b/esphome/components/spi_device/__init__.py @@ -1,7 +1,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import spi -from esphome.const import CONF_ID, CONF_MODE +from esphome.const import CONF_ID DEPENDENCIES = ["spi"] CODEOWNERS = ["@clydebarrow"] @@ -11,37 +11,14 @@ spi_device = spi_device_ns.class_("SPIDeviceComponent", cg.Component, spi.SPIDevice) -Mode = spi.spi_ns.enum("SPIMode") -MODES = { - "0": Mode.MODE0, - "1": Mode.MODE1, - "2": Mode.MODE2, - "3": Mode.MODE3, - "MODE0": Mode.MODE0, - "MODE1": Mode.MODE1, - "MODE2": Mode.MODE2, - "MODE3": Mode.MODE3, -} - -BitOrder = spi.spi_ns.enum("SPIBitOrder") -ORDERS = { - "msb_first": BitOrder.BIT_ORDER_MSB_FIRST, - "lsb_first": BitOrder.BIT_ORDER_LSB_FIRST, -} -CONF_BIT_ORDER = "bit_order" - CONFIG_SCHEMA = cv.Schema( { cv.GenerateID(CONF_ID): cv.declare_id(spi_device), - cv.Optional(CONF_BIT_ORDER, default="msb_first"): cv.enum(ORDERS, lower=True), - cv.Optional(CONF_MODE, default="0"): cv.enum(MODES, upper=True), } -).extend(spi.spi_device_schema(False, "1MHz")) +).extend(spi.spi_device_schema(False, "1MHz", default_mode="MODE0")) async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) - cg.add(var.set_mode(config[CONF_MODE])) - cg.add(var.set_bit_order(config[CONF_BIT_ORDER])) await spi.register_spi_device(var, config) diff --git a/esphome/core/defines.h b/esphome/core/defines.h index f13ae968f0aa..a6fcffaabf34 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -86,6 +86,7 @@ #define USE_MICROPHONE #define USE_SPEAKER #define USE_SPI +#define USE_I80 #ifdef USE_ARDUINO #define USE_ARDUINO_VERSION_CODE VERSION_CODE(2, 0, 5) diff --git a/tests/components/i80/test.esp32-s3-idf.yaml b/tests/components/i80/test.esp32-s3-idf.yaml new file mode 100644 index 000000000000..d7dfc2337c1e --- /dev/null +++ b/tests/components/i80/test.esp32-s3-idf.yaml @@ -0,0 +1,27 @@ +spi: + - id: spi_id_1 + type: single + clk_pin: + number: GPIO0 + ignore_strapping_warning: true + allow_other_uses: false + mosi_pin: GPIO6 + interface: hardware + +i80: + dc_pin: 7 + wr_pin: 8 + rd_pin: 9 + data_pins: + - 39 + - 40 + - 41 + - 42 + - + ignore_strapping_warning: true + number: 45 + - + ignore_strapping_warning: true + number: 46 + - 47 + - 48 diff --git a/tests/components/ili9xxx/test.esp32-s3-idf.yaml b/tests/components/ili9xxx/test.esp32-s3-idf.yaml new file mode 100644 index 000000000000..f3fb10ab9610 --- /dev/null +++ b/tests/components/ili9xxx/test.esp32-s3-idf.yaml @@ -0,0 +1,67 @@ +spi: + - id: spi_id_1 + type: single + clk_pin: + number: GPIO0 + ignore_strapping_warning: true + allow_other_uses: false + mosi_pin: GPIO6 + interface: hardware + +i80: + dc_pin: 7 + wr_pin: 8 + rd_pin: 9 + data_pins: + - 39 + - 40 + - 41 + - 42 + - + ignore_strapping_warning: true + number: 45 + - + ignore_strapping_warning: true + number: 46 + - 47 + - 48 + +display: + - platform: ili9xxx + bus_type: i80 + cs_pin: 11 + reset_pin: 10 + id: w32_disp + model: st7789v + data_rate: 2MHz + dimensions: + height: 320 + width: 170 + offset_width: 35 + offset_height: 0 + transform: + mirror_y: false + mirror_x: false + swap_xy: false + color_order: bgr + invert_colors: true + auto_clear_enabled: false + update_interval: never + + - platform: ili9xxx + id: wave_disp + model: WAVESHARE_RES_3_5 + transform: + mirror_y: true + swap_xy: true + cs_pin: GPIO5 + dc_pin: GPIO12 + reset_pin: GPIO21 + spi_mode: mode0 + data_rate: 20MHz + color_order: bgr + auto_clear_enabled: false + lambda: !lambda |- + it.filled_rectangle(0, 0, 40, 40, Color(0x80, 0, 0)); + it.filled_rectangle(0, 60, 40, 40, Color(0x40, 0, 0)); + it.filled_rectangle(0, 120, 40, 40, Color(0x20, 0, 0)); diff --git a/tests/components/spi_device/test.esp32.yaml b/tests/components/spi_device/test.esp32.yaml new file mode 100644 index 000000000000..f110af65192d --- /dev/null +++ b/tests/components/spi_device/test.esp32.yaml @@ -0,0 +1,19 @@ +spi: + - id: spi0 + clk_pin: GPIO18 + mosi_pin: GPIO23 + miso_pin: GPIO19 + interface: hardware + +spi_device: + - id: spidev + data_rate: 2MHz + spi_id: spi0 + spi_mode: mode3 + bit_order: lsb_first + - id: spidev1 + data_rate: 2MHz + spi_id: spi0 + spi_mode: mode0 + bit_order: msb_first + diff --git a/tests/test8.1.yaml b/tests/test8.1.yaml index ab3d0d44aadc..d7a4b23d5c1c 100644 --- a/tests/test8.1.yaml +++ b/tests/test8.1.yaml @@ -29,12 +29,6 @@ spi: allow_other_uses: false mosi_pin: GPIO6 interface: hardware -spi_device: - id: spidev - data_rate: 2MHz - spi_id: spi_id_1 - mode: 3 - bit_order: lsb_first display: - platform: ili9xxx diff --git a/tests/test8.2.yaml b/tests/test8.2.yaml index ae892559e588..c48e4424d6a7 100644 --- a/tests/test8.2.yaml +++ b/tests/test8.2.yaml @@ -29,13 +29,6 @@ spi: mosi_pin: GPIO6 interface: any -spi_device: - id: spidev - data_rate: 2MHz - spi_id: spi_id_1 - mode: 3 - bit_order: lsb_first - display: - platform: ili9xxx id: displ8 diff --git a/tests/test8.yaml b/tests/test8.yaml index 5a8ae77468eb..15e52a80ce2d 100644 --- a/tests/test8.yaml +++ b/tests/test8.yaml @@ -45,13 +45,6 @@ spi: mosi_pin: GPIO6 interface: any -spi_device: - id: spidev - data_rate: 2MHz - spi_id: spi_id_1 - mode: 3 - bit_order: lsb_first - font: - file: "gfonts://Roboto" id: roboto From 889771a6b266bccf4cbfa4d0acf650d079b9533e Mon Sep 17 00:00:00 2001 From: NP v/d Spek Date: Fri, 19 Apr 2024 12:26:18 +0200 Subject: [PATCH 02/11] add bus registery --- esphome/components/byte_bus/__init__.py | 55 ++++++++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/esphome/components/byte_bus/__init__.py b/esphome/components/byte_bus/__init__.py index ef9dae04d1fd..99d2fed37901 100644 --- a/esphome/components/byte_bus/__init__.py +++ b/esphome/components/byte_bus/__init__.py @@ -1,6 +1,59 @@ import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.util import Registry +from esphome.schema_extractors import schema_extractor_registry -CODEOWNERS = ["@clydebarrow"] +CODEOWNERS = ["@clydebarrow", "nielsnl68"] + +CONF_BUS_TYPE = "bus_type" byte_bus_ns = cg.esphome_ns.namespace("byte_bus") ByteBus = byte_bus_ns.class_("ByteBus") + + +DATABUS_REGISTRY = Registry() + + +def validate_bytebus_registry(base_schema, **kwargs): + registry_key = kwargs.pop("key", CONF_BUS_TYPE) + default_schema_option = kwargs.pop("default_type", None) + + base_schema = cv.ensure_schema(base_schema).extend( + { + cv.Optional(registry_key): cv.valid, + }, + extra=cv.ALLOW_EXTRA, + ) + + @schema_extractor_registry(DATABUS_REGISTRY) + def validator(value): + if not isinstance(value, dict): + raise cv.Invalid("This value must be dict !!") + value = value.copy() + key = value.pop(registry_key, default_schema_option) + if key is None: + raise cv.Invalid(f"{registry_key} 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[registry_key] = key_v + return value + + return validator + + +async def load_display_driver(key): + registry_item = DATABUS_REGISTRY[key] + return [registry_item.type_id, registry_item.fun] + + +def register_databus(name, condition_type, schema): + return DATABUS_REGISTRY.register(name, condition_type, schema) From 6e491643a840a9d4c491c8add9951fc344d383d2 Mon Sep 17 00:00:00 2001 From: NP v/d Spek Date: Fri, 19 Apr 2024 19:34:03 +0200 Subject: [PATCH 03/11] List of changes: - allow dynamicly add new byte_busses via esphome's registry() method. And makes it possible to detach the ili9xxx from the different databus chooses - make transactions nestable so you can call multiple times begin_transaction and end it - and write_component() inline methods. --- esphome/components/byte_bus/__init__.py | 43 ++++++---- esphome/components/byte_bus/byte_bus.h | 34 +++++++- esphome/components/i80/__init__.py | 33 ++++---- esphome/components/i80/i80_component.h | 5 +- esphome/components/ili9xxx/display.py | 78 +++++++------------ .../components/ili9xxx/ili9xxx_display.cpp | 29 +++---- esphome/components/ili9xxx/ili9xxx_display.h | 23 +----- esphome/components/spi/__init__.py | 50 +++++++++++- esphome/components/spi/spi.cpp | 10 ++- esphome/components/spi/spi.h | 11 ++- 10 files changed, 184 insertions(+), 132 deletions(-) diff --git a/esphome/components/byte_bus/__init__.py b/esphome/components/byte_bus/__init__.py index 99d2fed37901..2dbe62413496 100644 --- a/esphome/components/byte_bus/__init__.py +++ b/esphome/components/byte_bus/__init__.py @@ -3,24 +3,39 @@ from esphome.util import Registry from esphome.schema_extractors import schema_extractor_registry +from esphome.const import ( + CONF_CLIENT_ID, +) + CODEOWNERS = ["@clydebarrow", "nielsnl68"] CONF_BUS_TYPE = "bus_type" +CONF_BUS_ID = "bus_id" byte_bus_ns = cg.esphome_ns.namespace("byte_bus") ByteBus = byte_bus_ns.class_("ByteBus") - DATABUS_REGISTRY = Registry() -def validate_bytebus_registry(base_schema, **kwargs): - registry_key = kwargs.pop("key", CONF_BUS_TYPE) - default_schema_option = kwargs.pop("default_type", None) +def include_databus(name, bus_class, bus_component, schema, *extra_validators): + schema = cv.Schema(schema).extend( + { + cv.GenerateID(CONF_BUS_ID): cv.use_id(bus_component), + cv.GenerateID(CONF_CLIENT_ID): cv.declare_id(bus_class), + } + ) + validator = cv.All(schema, *extra_validators) + + return DATABUS_REGISTRY.register(name, bus_class, validator) + + +def validate_databus_registry(base_schema, **kwargs): + default_schema_option = kwargs.pop("default", None) base_schema = cv.ensure_schema(base_schema).extend( { - cv.Optional(registry_key): cv.valid, + cv.Optional(CONF_BUS_TYPE): cv.valid, }, extra=cv.ALLOW_EXTRA, ) @@ -30,9 +45,9 @@ def validator(value): if not isinstance(value, dict): raise cv.Invalid("This value must be dict !!") value = value.copy() - key = value.pop(registry_key, default_schema_option) + key = value.pop(CONF_BUS_TYPE, default_schema_option) if key is None: - raise cv.Invalid(f"{registry_key} not specified!") + raise cv.Invalid(f"{CONF_BUS_TYPE} not specified!") models = cv.extract_keys(DATABUS_REGISTRY) key_validator = cv.one_of(*models, **kwargs) @@ -44,16 +59,16 @@ def validator(value): new_schema = base_schema.extend(DATABUS_REGISTRY[key_v].raw_schema) value = new_schema(value) - value[registry_key] = key_v + value[CONF_BUS_TYPE] = key_v return value return validator -async def load_display_driver(key): - registry_item = DATABUS_REGISTRY[key] - return [registry_item.type_id, registry_item.fun] - +async def register_databus(config): + databus = DATABUS_REGISTRY[config[CONF_BUS_TYPE]] + rhs = databus.type_id.new() + var = cg.Pvariable(config[CONF_CLIENT_ID], rhs) + cg.add(var.set_parent(await cg.get_variable(config[CONF_BUS_ID]))) -def register_databus(name, condition_type, schema): - return DATABUS_REGISTRY.register(name, condition_type, schema) + return await databus.fun(config, var) diff --git a/esphome/components/byte_bus/byte_bus.h b/esphome/components/byte_bus/byte_bus.h index 381478ee38da..c2a9cdd589c6 100644 --- a/esphome/components/byte_bus/byte_bus.h +++ b/esphome/components/byte_bus/byte_bus.h @@ -51,10 +51,36 @@ class ByteBus { */ virtual void write_cmd_data(int cmd, const uint8_t *data, size_t length) = 0; + inline void write_command(int cmd) { + write_cmd_data(cmd, nullptr, 0); + } + inline void write_command(int cmd, const uint8_t data) { + write_cmd_data(cmd, &data, 1); + } + inline void write_command16(int cmd, const uint16_t data) { + write_cmd_data(cmd, &data, 2); + } + inline void write_command(int cmd, const uint8_t *data, size_t length) { + write_cmd_data(cmd, data, length); + } + 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 == 0) { + ESP_LOGE("Transaction is already Ended") + } else 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 +91,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..380d2c3495fe 100644 --- a/esphome/components/i80/__init__.py +++ b/esphome/components/i80/__init__.py @@ -8,7 +8,6 @@ CONF_ID, CONF_DATA_RATE, CONF_CS_PIN, - CONF_CLIENT_ID, CONF_DC_PIN, ) @@ -42,6 +41,18 @@ ) +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", @@ -52,9 +63,7 @@ def i80_client_schema( :return: The i80 client schema, `extend` this in your config 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,22 +72,8 @@ 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)) - - -async def create_i80_client(config): - id = config[CONF_CLIENT_ID] - var = cg.new_Pvariable(id) - cg.add(var.set_parent(await cg.get_variable(config[CONF_I80_ID]))) +@byte_bus.include_databus("i80", I80Client, I80Component, i80_client_schema()) +async def create_i80_client(config, var): 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])) diff --git a/esphome/components/i80/i80_component.h b/esphome/components/i80/i80_component.h index 1f0bfb3dd03d..37bc0e835f1b 100644 --- a/esphome/components/i80/i80_component.h +++ b/esphome/components/i80/i80_component.h @@ -82,8 +82,6 @@ class I80Client : public byte_bus::ByteBus { } 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; } @@ -92,6 +90,9 @@ class I80Client : public byte_bus::ByteBus { 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/ili9xxx/display.py b/esphome/components/ili9xxx/display.py index 4a2531675e95..f48cd0179fb5 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,26 @@ 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), ["SPI"]], + "M5STACK": [ili9xxx_ns.class_("ILI9XXXM5Stack", ILI9XXXDisplay), ["SPI"]], + "M5CORE": [ili9xxx_ns.class_("ILI9XXXM5CORE", ILI9XXXDisplay), ["SPI"]], + "TFT_2.4": [ili9xxx_ns.class_("ILI9XXXILI9341", ILI9XXXDisplay), ["SPI"]], + "TFT_2.4R": [ili9xxx_ns.class_("ILI9XXXILI9342", ILI9XXXDisplay), ["SPI"]], + "ILI9341": [ili9xxx_ns.class_("ILI9XXXILI9341", ILI9XXXDisplay), ["SPI"]], + "ILI9342": [ili9xxx_ns.class_("ILI9XXXILI9342", ILI9XXXDisplay), ["SPI"]], + "ILI9481": [ili9xxx_ns.class_("ILI9XXXILI9481", ILI9XXXDisplay), ["SPI"]], + "ILI9481-18": [ili9xxx_ns.class_("ILI9XXXILI948118", ILI9XXXDisplay), ["SPI"]], + "ILI9486": [ili9xxx_ns.class_("ILI9XXXILI9486", ILI9XXXDisplay), ["SPI"]], + "ILI9488": [ili9xxx_ns.class_("ILI9XXXILI9488", ILI9XXXDisplay), ["SPI"]], + "ILI9488_A": [ili9xxx_ns.class_("ILI9XXXILI9488A", ILI9XXXDisplay), ["SPI"]], + "ST7796": [ili9xxx_ns.class_("ILI9XXXST7796", ILI9XXXDisplay), ["SPI"]], + "ST7789V": [ili9xxx_ns.class_("ILI9XXXST7789V", ILI9XXXDisplay), ["SPI"]], + "S3BOX": [ili9xxx_ns.class_("ILI9XXXS3Box", ILI9XXXDisplay), ["SPI"]], + "S3BOX_LITE": [ili9xxx_ns.class_("ILI9XXXS3BoxLite", ILI9XXXDisplay), ["SPI"]], + "WAVESHARE_RES_3_5": [ + ili9xxx_ns.class_("WAVESHARERES35", ILI9XXXDisplay), + ["SPI16D"], + ], } COLOR_ORDERS = { @@ -151,52 +152,25 @@ def _validate(config): } ).extend(cv.polling_component_schema("1s")) -TYPE_SPI = "spi" -TYPE_I80 = "i80" +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=TYPE_SPI), cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA), _validate, ) 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 = 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..270875be96b4 100644 --- a/esphome/components/ili9xxx/ili9xxx_display.cpp +++ b/esphome/components/ili9xxx/ili9xxx_display.cpp @@ -25,7 +25,7 @@ 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, 1); ESP_LOGD(TAG, "Wrote MADCTL 0x%02X", mad); } @@ -39,9 +39,11 @@ 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 +213,16 @@ 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 +288,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 +307,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 +331,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..f40c56f2c2d1 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,8 +204,8 @@ 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 */ @@ -218,22 +216,7 @@ class WAVESHARERES35 : public ILI9XXXILI9488 { * 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..a1b9df02aef3 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,7 @@ def spi_device_schema( default_data_rate=cv.UNDEFINED, default_mode=cv.UNDEFINED, quad=False, + is_databus_schema=False, ): """Create a schema for an SPI device. :param cs_pin_required: If true, make the CS_PIN required in the config. @@ -369,16 +371,19 @@ def spi_device_schema( :return: The SPI device schema, `extend` this in your config schema. """ schema = { - cv.GenerateID(CONF_SPI_ID): cv.use_id( - QuadSPIComponent if quad else SPIComponent - ), cv.Optional(CONF_DATA_RATE, default=default_data_rate): SPI_DATA_RATE_SCHEMA, cv.Optional(CONF_BIT_ORDER, default="msb_first"): cv.enum(ORDERS, lower=True), cv.Optional(CONF_SPI_MODE, default=default_mode): cv.enum( SPI_MODE_OPTIONS, upper=True ), - cv.GenerateID(CONF_CLIENT_ID): cv.declare_id(SPIClient), } + if not is_databus_schema: + schema[cv.GenerateID(CONF_SPI_ID)] = ( + cv.use_id(QuadSPIComponent if quad else SPIComponent), + ) + schema[cv.GenerateID(CONF_CLIENT_ID)] = cv.declare_id(SPIClient) + else: + 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: @@ -433,3 +438,40 @@ 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, ) + + +@byte_bus.include_databus( + "SPI", + SPIByteBus, + SPIComponent, + spi_device_schema(False, "40MHz", "mode0", False, True), +) +@byte_bus.include_databus( + "SPI16D", + SPIByteBus, + SPIComponent, + spi_device_schema(False, "40MHz", "mode0", False, True), +) +@byte_bus.include_databus( + "QSPI", + SPIByteBus, + SPIComponent, + spi_device_schema(False, "40MHz", "mode0", True, True), +) +async def create_i80_client(config, var, databus_type): + 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])) + if databus_type == "SPI16D": + cg.add(var.set_16bit_data(True)) + rhs = SPIClient.new( + config[CONF_BIT_ORDER], config[CONF_SPI_MODE], config[CONF_DATA_RATE] + ) + client = cg.Pvariable(config[CONF_CLIENT_ID] + "_client", rhs) + + cg.add(client.set_parent(await cg.get_variable(config[byte_bus.CONF_BUS_ID]))) + if cs_pin := config.get(CONF_CS_PIN): + cg.add(client.set_cs_pin(await cg.gpio_pin_expression(cs_pin))) + cg.add(var.set_spi_client(client)) + + return var diff --git a/esphome/components/spi/spi.cpp b/esphome/components/spi/spi.cpp index 9ce91c4b850f..0473ac2edae1 100644 --- a/esphome/components/spi/spi.cpp +++ b/esphome/components/spi/spi.cpp @@ -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..ec4afede1d01 100644 --- a/esphome/components/spi/spi.h +++ b/esphome/components/spi/spi.h @@ -461,26 +461,29 @@ 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 dump_config() override; - void set_dc_pin(GPIOPin *dc_pin) { this->dc_pin_ = dc_pin; } + 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 From 2975333939aaa75a8e4d1c1e1050a318e47935bd Mon Sep 17 00:00:00 2001 From: NP v/d Spek Date: Sat, 20 Apr 2024 02:28:00 +0200 Subject: [PATCH 04/11] byte_bus: - remove unneeded code - renamed write_cmd_data to write_command i80: - renamed i80Client to i80ByteBus conform SPIByteBus - tried to making running - ERROR: ID esp32_esp32internalgpiopin_2 is already registered (NOT FIXED) - allowing to be used in arduino setup. - save rd_pin SPI: - moving creation of SPIClient inside c++ ili9xxx: - add missing await --- esphome/components/byte_bus/__init__.py | 9 ++++---- esphome/components/byte_bus/byte_bus.h | 23 +++++-------------- esphome/components/i80/__init__.py | 10 ++++---- esphome/components/i80/i80_component.h | 21 +++++++++-------- esphome/components/ili9xxx/display.py | 2 +- .../components/ili9xxx/ili9xxx_display.cpp | 2 +- esphome/components/spi/__init__.py | 23 +++++++++---------- esphome/components/spi/spi.cpp | 2 +- esphome/components/spi/spi.h | 13 +++++++++-- 9 files changed, 51 insertions(+), 54 deletions(-) diff --git a/esphome/components/byte_bus/__init__.py b/esphome/components/byte_bus/__init__.py index 2dbe62413496..a2083065de59 100644 --- a/esphome/components/byte_bus/__init__.py +++ b/esphome/components/byte_bus/__init__.py @@ -18,16 +18,15 @@ DATABUS_REGISTRY = Registry() -def include_databus(name, bus_class, bus_component, schema, *extra_validators): - schema = cv.Schema(schema).extend( +def include_databus(name, bus_class, bus_component, schema): + schema = schema.extend( { cv.GenerateID(CONF_BUS_ID): cv.use_id(bus_component), cv.GenerateID(CONF_CLIENT_ID): cv.declare_id(bus_class), } ) - validator = cv.All(schema, *extra_validators) - return DATABUS_REGISTRY.register(name, bus_class, validator) + return DATABUS_REGISTRY.register(name, bus_class, schema) def validate_databus_registry(base_schema, **kwargs): @@ -71,4 +70,4 @@ async def register_databus(config): var = cg.Pvariable(config[CONF_CLIENT_ID], rhs) cg.add(var.set_parent(await cg.get_variable(config[CONF_BUS_ID]))) - return await databus.fun(config, var) + return await databus.fun(config, var, config[CONF_BUS_TYPE]) diff --git a/esphome/components/byte_bus/byte_bus.h b/esphome/components/byte_bus/byte_bus.h index c2a9cdd589c6..c5540a273312 100644 --- a/esphome/components/byte_bus/byte_bus.h +++ b/esphome/components/byte_bus/byte_bus.h @@ -49,20 +49,11 @@ 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_cmd_data(cmd, nullptr, 0); - } - inline void write_command(int cmd, const uint8_t data) { - write_cmd_data(cmd, &data, 1); - } - inline void write_command16(int cmd, const uint16_t data) { - write_cmd_data(cmd, &data, 2); - } - inline void write_command(int cmd, const uint8_t *data, size_t length) { - write_cmd_data(cmd, data, length); - } + 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); } + inline void write_command16(int cmd, const uint16_t data) { write_command(cmd, (const uint8_t *) &data, 2); } + virtual void write_command(int cmd, const uint8_t *data, size_t length) = 0; virtual void dump_config(){}; @@ -71,10 +62,8 @@ class ByteBus { do_begin_transaction(); } } - void end_transaction(){ - if (this->transaction_counter == 0) { - ESP_LOGE("Transaction is already Ended") - } else if (this->transaction_counter == 1) { + void end_transaction() { + if (this->transaction_counter <= 1) { do_end_transaction(); this->transaction_counter = 0; } else { diff --git a/esphome/components/i80/__init__.py b/esphome/components/i80/__init__.py index 380d2c3495fe..e29e5a823870 100644 --- a/esphome/components/i80/__init__.py +++ b/esphome/components/i80/__init__.py @@ -16,7 +16,7 @@ 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" @@ -37,7 +37,6 @@ } ) ), - cv.only_with_esp_idf, ) @@ -45,8 +44,9 @@ async def to_code(configs): cg.add_define("USE_I80") for conf in configs: wr = await cg.gpio_pin_expression(conf[CONF_WR_PIN]) + rd = 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]) + var = cg.new_Pvariable(conf[CONF_ID], wr, rd, 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) @@ -72,8 +72,8 @@ def i80_client_schema( return cv.Schema(schema) -@byte_bus.include_databus("i80", I80Client, I80Component, i80_client_schema()) -async def create_i80_client(config, var): +@byte_bus.include_databus("i80", I80ByteBus, I80Component, i80_client_schema()) +async def create_i80_client(config, var, databus_type): 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])) diff --git a/esphome/components/i80/i80_component.h b/esphome/components/i80/i80_component.h index 37bc0e835f1b..097b76848cf0 100644 --- a/esphome/components/i80/i80_component.h +++ b/esphome/components/i80/i80_component.h @@ -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; @@ -49,15 +49,16 @@ class I80Client; class I80Component : public Component { public: - I80Component(InternalGPIOPin *wr_pin, InternalGPIOPin *dc_pin, std::vector data_pins) - : wr_pin_(wr_pin), dc_pin_(dc_pin), data_pins_(std::move(data_pins)) {} + I80Component(InternalGPIOPin *wr_pin, InternalGPIOPin *rd_pin, InternalGPIOPin *dc_pin, + std::vector data_pins) + : wr_pin_(wr_pin), rd_pin_(rd_pin), dc_pin_(dc_pin), data_pins_(std::move(data_pins)) {} - void setup() override; + void setup() override {} 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(I80Client *device, GPIOPin *cs_pin, unsigned int data_rate) {} + void unregister_device(I80Client *device){}; float get_setup_priority() const override { return setup_priority::BUS; } protected: @@ -69,7 +70,7 @@ class I80Component : public Component { 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,8 +78,8 @@ 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); } @@ -86,7 +87,7 @@ class I80Client : public byte_bus::ByteBus { void set_parent(I80Component *parent) { this->parent_ = parent; } void set_cs_pin(GPIOPin *cs) { this->cs_ = cs; } - void dump_config() override; + void dump_config() override {}; void set_data_rate(int data_rate) { this->data_rate_ = data_rate; } protected: diff --git a/esphome/components/ili9xxx/display.py b/esphome/components/ili9xxx/display.py index f48cd0179fb5..194b6bbacb3b 100644 --- a/esphome/components/ili9xxx/display.py +++ b/esphome/components/ili9xxx/display.py @@ -168,7 +168,7 @@ async def to_code(config): data_rate = int(max(config[CONF_DATA_RATE] / 1e6, 1)) await display.register_display(var, config) - bus_client = byte_bus.register_databus(config) + bus_client = await byte_bus.register_databus(config) cg.add(var.set_bus(bus_client)) cg.add(var.set_data_rate(data_rate)) if CONF_COLOR_ORDER in config: diff --git a/esphome/components/ili9xxx/ili9xxx_display.cpp b/esphome/components/ili9xxx/ili9xxx_display.cpp index 270875be96b4..199fb3341518 100644 --- a/esphome/components/ili9xxx/ili9xxx_display.cpp +++ b/esphome/components/ili9xxx/ili9xxx_display.cpp @@ -25,7 +25,7 @@ void ILI9XXXDisplay::set_madctl() { mad |= MADCTL_MX; if (this->mirror_y_) mad |= MADCTL_MY; - this->bus_->write_command(ILI9XXX_MADCTL, &mad, 1); + this->bus_->write_command(ILI9XXX_MADCTL, mad); ESP_LOGD(TAG, "Wrote MADCTL 0x%02X", mad); } diff --git a/esphome/components/spi/__init__.py b/esphome/components/spi/__init__.py index a1b9df02aef3..94ee01368958 100644 --- a/esphome/components/spi/__init__.py +++ b/esphome/components/spi/__init__.py @@ -455,23 +455,22 @@ def final_validate_device_schema(name: str, *, require_mosi: bool, require_miso: @byte_bus.include_databus( "QSPI", SPIByteBus, - SPIComponent, + QuadSPIComponent, spi_device_schema(False, "40MHz", "mode0", True, True), ) async def create_i80_client(config, var, databus_type): - 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])) - if databus_type == "SPI16D": - cg.add(var.set_16bit_data(True)) - rhs = SPIClient.new( - config[CONF_BIT_ORDER], config[CONF_SPI_MODE], config[CONF_DATA_RATE] + cg.add( + var.set_spi_client( + config[CONF_BIT_ORDER], config[CONF_SPI_MODE], config[CONF_DATA_RATE] + ) ) - client = cg.Pvariable(config[CONF_CLIENT_ID] + "_client", rhs) - cg.add(client.set_parent(await cg.get_variable(config[byte_bus.CONF_BUS_ID]))) + cg.add(var.set_data_rate(config[CONF_DATA_RATE])) if cs_pin := config.get(CONF_CS_PIN): - cg.add(client.set_cs_pin(await cg.gpio_pin_expression(cs_pin))) - cg.add(var.set_spi_client(client)) + cg.add(var.set_cs_pin(await cg.gpio_pin_expression(cs_pin))) + if pin := config.get(CONF_DC_PIN): + cg.add(var.set_dc_pin(await cg.gpio_pin_expression(pin))) + if databus_type == "SPI16D": + cg.add(var.set_16bit_data(True)) return var diff --git a/esphome/components/spi/spi.cpp b/esphome/components/spi/spi.cpp index 0473ac2edae1..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) { diff --git a/esphome/components/spi/spi.h b/esphome/components/spi/spi.h index ec4afede1d01..03bf56dac882 100644 --- a/esphome/components/spi/spi.h +++ b/esphome/components/spi/spi.h @@ -470,13 +470,21 @@ class SPIByteBus : public byte_bus::ByteBus { 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; } + void set_parent(SPIComponent *parent) { this->parent_ = parent; } + + void set_spi_client(SPIBitOrder bit_order, SPIMode mode, uint32_t data_rate) { + this->client_ = new SPIClient(bit_order, mode, data_rate); + this->client_->set_parent(this->parent_); + } + void set_cs_pin(GPIOPin *cs_pin) { this->client_->set_cs_pin(cs_pin); } + void set_data_rate(int data_rate) { this->client_->set_data_rate(data_rate); } + protected: void do_begin_transaction() override { this->client_->enable(); } void do_end_transaction() override { this->client_->disable(); } @@ -484,6 +492,7 @@ class SPIByteBus : public byte_bus::ByteBus { SPIClient *client_; GPIOPin *dc_pin_{byte_bus::NULL_PIN}; bool is_16bit_data_{false}; + SPIComponent *parent_{}; }; } // namespace spi } // namespace esphome From 61906060930a1e677110e62439660bfe7cd95d28 Mon Sep 17 00:00:00 2001 From: NP v/d Spek Date: Sat, 20 Apr 2024 14:01:30 +0200 Subject: [PATCH 05/11] Fixing the last issues with the i80 component --- esphome/components/i80/__init__.py | 4 +-- esphome/components/i80/i80_component.cpp | 36 ++++++++++++++++++++++ esphome/components/i80/i80_component.h | 18 +++++------ esphome/components/i80/i80_esp_idf.cpp | 39 ++---------------------- 4 files changed, 50 insertions(+), 47 deletions(-) diff --git a/esphome/components/i80/__init__.py b/esphome/components/i80/__init__.py index e29e5a823870..5e3eef9e1270 100644 --- a/esphome/components/i80/__init__.py +++ b/esphome/components/i80/__init__.py @@ -44,9 +44,9 @@ async def to_code(configs): cg.add_define("USE_I80") for conf in configs: wr = await cg.gpio_pin_expression(conf[CONF_WR_PIN]) - rd = 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, rd, dc, conf[CONF_DATA_PINS]) + + 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) diff --git a/esphome/components/i80/i80_component.cpp b/esphome/components/i80/i80_component.cpp index 0811f5559ef5..d39b8d74a2a5 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,41 @@ 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 097b76848cf0..f1b15c91c287 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: @@ -45,20 +45,20 @@ class I80Bus { virtual I80Delegate *get_delegate(GPIOPin *cs_pin, unsigned int data_rate) = 0; }; -class I80Client; +class I80ByteBus; class I80Component : public Component { public: - I80Component(InternalGPIOPin *wr_pin, InternalGPIOPin *rd_pin, InternalGPIOPin *dc_pin, + I80Component(InternalGPIOPin *wr_pin, InternalGPIOPin *dc_pin, std::vector data_pins) - : wr_pin_(wr_pin), rd_pin_(rd_pin), dc_pin_(dc_pin), data_pins_(std::move(data_pins)) {} + : wr_pin_(wr_pin),dc_pin_(dc_pin), data_pins_(std::move(data_pins)) {} - void setup() override {} + void setup() override; 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: @@ -67,7 +67,7 @@ class I80Component : public Component { InternalGPIOPin *rd_pin_{}; InternalGPIOPin *dc_pin_{}; std::vector data_pins_{}; - std::map devices_{}; + std::map devices_{}; }; class I80ByteBus : public byte_bus::ByteBus { @@ -87,7 +87,7 @@ class I80ByteBus : public byte_bus::ByteBus { void set_parent(I80Component *parent) { this->parent_ = parent; } void set_cs_pin(GPIOPin *cs) { this->cs_ = cs; } - void dump_config() override {}; + void dump_config() override; void set_data_rate(int data_rate) { this->data_rate_ = data_rate; } protected: diff --git a/esphome/components/i80/i80_esp_idf.cpp b/esphome/components/i80/i80_esp_idf.cpp index d9571030171c..1c1e78532ce4 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 +//#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,7 @@ 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_ESP_IDF #endif // USE_I80 From 87b1c2240d5eb70acb1e930b2a80850eacd64aed Mon Sep 17 00:00:00 2001 From: NP v/d Spek Date: Sat, 20 Apr 2024 15:29:52 +0200 Subject: [PATCH 06/11] remove not used write_command() method because it does not work as intended. --- esphome/components/byte_bus/byte_bus.h | 1 - 1 file changed, 1 deletion(-) diff --git a/esphome/components/byte_bus/byte_bus.h b/esphome/components/byte_bus/byte_bus.h index 5bc36092d5a9..17b6260771af 100644 --- a/esphome/components/byte_bus/byte_bus.h +++ b/esphome/components/byte_bus/byte_bus.h @@ -51,7 +51,6 @@ class ByteBus { */ 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); } - inline void write_command16(int cmd, const uint16_t data) { write_command(cmd, (const uint8_t *) &data, 2); } virtual void write_command(int cmd, const uint8_t *data, size_t length) = 0; virtual void dump_config(){}; From 122b0727857814ec29010447213a0f423d8a32e5 Mon Sep 17 00:00:00 2001 From: NP v/d Spek Date: Sat, 20 Apr 2024 15:56:51 +0200 Subject: [PATCH 07/11] fix CI reports --- esphome/components/byte_bus/__init__.py | 2 +- esphome/components/i80/__init__.py | 1 - esphome/components/i80/i80_component.h | 6 ++---- esphome/components/ili9xxx/display.py | 1 - 4 files changed, 3 insertions(+), 7 deletions(-) diff --git a/esphome/components/byte_bus/__init__.py b/esphome/components/byte_bus/__init__.py index a2083065de59..9020a2246299 100644 --- a/esphome/components/byte_bus/__init__.py +++ b/esphome/components/byte_bus/__init__.py @@ -7,7 +7,7 @@ CONF_CLIENT_ID, ) -CODEOWNERS = ["@clydebarrow", "nielsnl68"] +CODEOWNERS = ["@clydebarrow", "@nielsnl68"] CONF_BUS_TYPE = "bus_type" CONF_BUS_ID = "bus_id" diff --git a/esphome/components/i80/__init__.py b/esphome/components/i80/__init__.py index bf688b7f676f..5e3eef9e1270 100644 --- a/esphome/components/i80/__init__.py +++ b/esphome/components/i80/__init__.py @@ -72,7 +72,6 @@ def i80_client_schema( return cv.Schema(schema) - @byte_bus.include_databus("i80", I80ByteBus, I80Component, i80_client_schema()) async def create_i80_client(config, var, databus_type): if pin := config.get(CONF_CS_PIN): diff --git a/esphome/components/i80/i80_component.h b/esphome/components/i80/i80_component.h index 2a402590dc63..41bda29a78be 100644 --- a/esphome/components/i80/i80_component.h +++ b/esphome/components/i80/i80_component.h @@ -15,7 +15,6 @@ namespace i80 { static constexpr const char *TAG = "i80"; - class I80ByteBus; class I80Delegate { @@ -50,9 +49,8 @@ class I80ByteBus; class I80Component : public Component { public: - I80Component(InternalGPIOPin *wr_pin, InternalGPIOPin *dc_pin, - std::vector data_pins) - : wr_pin_(wr_pin),dc_pin_(dc_pin), data_pins_(std::move(data_pins)) {} + I80Component(InternalGPIOPin *wr_pin, InternalGPIOPin *dc_pin, std::vector data_pins) + : wr_pin_(wr_pin), dc_pin_(dc_pin), data_pins_(std::move(data_pins)) {} void setup() override; diff --git a/esphome/components/ili9xxx/display.py b/esphome/components/ili9xxx/display.py index 471346acf09a..dff3e5a6cc25 100644 --- a/esphome/components/ili9xxx/display.py +++ b/esphome/components/ili9xxx/display.py @@ -157,7 +157,6 @@ def _validate(config): CONFIG_SCHEMA = cv.All( byte_bus.validate_databus_registry(BASE_SCHEMA, default=TYPE_SPI), - cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA), _validate, ) From ee7875c3d297dcac2224478316a1bbca96aec142 Mon Sep 17 00:00:00 2001 From: NP v/d Spek Date: Mon, 22 Apr 2024 19:18:32 +0200 Subject: [PATCH 08/11] New futures: - allow to change default databus via callback function - adding final_validation option - restore part of the I80 and SPI configuration settings - update list of models with info about screensize, available data busses and if esp8266 is supported. --- esphome/components/byte_bus/__init__.py | 93 +++++++-- esphome/components/i80/__init__.py | 55 ++++- esphome/components/i80/i80_component.h | 4 +- esphome/components/ili9xxx/display.py | 267 +++++++++++++++++++++--- esphome/components/spi/__init__.py | 74 ++++--- esphome/components/spi/spi.h | 11 +- 6 files changed, 415 insertions(+), 89 deletions(-) diff --git a/esphome/components/byte_bus/__init__.py b/esphome/components/byte_bus/__init__.py index 9020a2246299..063445d461a9 100644 --- a/esphome/components/byte_bus/__init__.py +++ b/esphome/components/byte_bus/__init__.py @@ -1,37 +1,70 @@ +import types import esphome.codegen as cg import esphome.config_validation as cv -from esphome.util import Registry +import esphome.final_validate as fv + from esphome.schema_extractors import schema_extractor_registry from esphome.const import ( - CONF_CLIENT_ID, + CONF_ID, ) CODEOWNERS = ["@clydebarrow", "@nielsnl68"] CONF_BUS_TYPE = "bus_type" -CONF_BUS_ID = "bus_id" 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, bus_component, schema): - schema = schema.extend( - { - cv.GenerateID(CONF_BUS_ID): cv.use_id(bus_component), - cv.GenerateID(CONF_CLIENT_ID): cv.declare_id(bus_class), - } +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 DATABUS_REGISTRY.register(name, bus_class, schema) + 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, @@ -41,10 +74,18 @@ def validate_databus_registry(base_schema, **kwargs): @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_schema_option) + + key = value.pop(CONF_BUS_TYPE, default) if key is None: raise cv.Invalid(f"{CONF_BUS_TYPE} not specified!") @@ -67,7 +108,27 @@ def validator(value): async def register_databus(config): databus = DATABUS_REGISTRY[config[CONF_BUS_TYPE]] rhs = databus.type_id.new() - var = cg.Pvariable(config[CONF_CLIENT_ID], rhs) - cg.add(var.set_parent(await cg.get_variable(config[CONF_BUS_ID]))) + 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 await databus.fun(config, var, config[CONF_BUS_TYPE]) + return validate diff --git a/esphome/components/i80/__init__.py b/esphome/components/i80/__init__.py index 5e3eef9e1270..c85cfb58571d 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 @@ -22,6 +23,11 @@ CONF_WR_PIN = "wr_pin" CONF_I80_ID = "i80_id" + +def validate_data_pins(): + pass + + CONFIG_SCHEMA = cv.All( cv.ensure_list( cv.Schema( @@ -29,12 +35,13 @@ 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, ) ), ) @@ -63,6 +70,7 @@ def i80_client_schema( :return: The i80 client schema, `extend` this in your config schema. """ schema = { + cv.GenerateID(CONF_I80_ID): cv.use_id(I80Component), cv.Optional(CONF_DATA_RATE, default=default_data_rate): cv.frequency, } if cs_pin_required: @@ -72,9 +80,46 @@ def i80_client_schema( return cv.Schema(schema) -@byte_bus.include_databus("i80", I80ByteBus, I80Component, i80_client_schema()) -async def create_i80_client(config, var, databus_type): +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 + + +@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]), config["i80_pin_count"] + ) + ) + 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.h b/esphome/components/i80/i80_component.h index 41bda29a78be..dce036eb8fe5 100644 --- a/esphome/components/i80/i80_component.h +++ b/esphome/components/i80/i80_component.h @@ -85,10 +85,10 @@ class I80ByteBus : public byte_bus::ByteBus { void write_array(const uint8_t *data, size_t length) override { this->delegate_->write_array(data, length); } - void set_parent(I80Component *parent) { this->parent_ = parent; } + void dump_config() override; + void set_parent(I80Component *parent, uint8_t pin_count) { 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: diff --git a/esphome/components/ili9xxx/display.py b/esphome/components/ili9xxx/display.py index dff3e5a6cc25..e95dabc90aa4 100644 --- a/esphome/components/ili9xxx/display.py +++ b/esphome/components/ili9xxx/display.py @@ -52,25 +52,229 @@ ColorOrder = display.display_ns.enum("ColorMode") MODELS = { - "GC9A01A": [ili9xxx_ns.class_("ILI9XXXGC9A01A", ILI9XXXDisplay), ["SPI"]], - "M5STACK": [ili9xxx_ns.class_("ILI9XXXM5Stack", ILI9XXXDisplay), ["SPI"]], - "M5CORE": [ili9xxx_ns.class_("ILI9XXXM5CORE", ILI9XXXDisplay), ["SPI"]], - "TFT_2.4": [ili9xxx_ns.class_("ILI9XXXILI9341", ILI9XXXDisplay), ["SPI"]], - "TFT_2.4R": [ili9xxx_ns.class_("ILI9XXXILI9342", ILI9XXXDisplay), ["SPI"]], - "ILI9341": [ili9xxx_ns.class_("ILI9XXXILI9341", ILI9XXXDisplay), ["SPI"]], - "ILI9342": [ili9xxx_ns.class_("ILI9XXXILI9342", ILI9XXXDisplay), ["SPI"]], - "ILI9481": [ili9xxx_ns.class_("ILI9XXXILI9481", ILI9XXXDisplay), ["SPI"]], - "ILI9481-18": [ili9xxx_ns.class_("ILI9XXXILI948118", ILI9XXXDisplay), ["SPI"]], - "ILI9486": [ili9xxx_ns.class_("ILI9XXXILI9486", ILI9XXXDisplay), ["SPI"]], - "ILI9488": [ili9xxx_ns.class_("ILI9XXXILI9488", ILI9XXXDisplay), ["SPI"]], - "ILI9488_A": [ili9xxx_ns.class_("ILI9XXXILI9488A", ILI9XXXDisplay), ["SPI"]], - "ST7796": [ili9xxx_ns.class_("ILI9XXXST7796", ILI9XXXDisplay), ["SPI"]], - "ST7789V": [ili9xxx_ns.class_("ILI9XXXST7789V", ILI9XXXDisplay), ["SPI"]], - "S3BOX": [ili9xxx_ns.class_("ILI9XXXS3Box", ILI9XXXDisplay), ["SPI"]], - "S3BOX_LITE": [ili9xxx_ns.class_("ILI9XXXS3BoxLite", ILI9XXXDisplay), ["SPI"]], + "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), - ["SPI16D"], + False, + ["spi16d"], ], } @@ -102,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), @@ -152,16 +360,19 @@ def _validate(config): } ).extend(cv.polling_component_schema("1s")) -TYPE_SPI = "SPI" -# TYPE_I80 = "i80" CONFIG_SCHEMA = cv.All( - byte_bus.validate_databus_registry(BASE_SCHEMA, default=TYPE_SPI), + 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]][0].new() var = cg.Pvariable(config[CONF_ID], rhs) diff --git a/esphome/components/spi/__init__.py b/esphome/components/spi/__init__.py index 5eb27e4d8c3d..d257790ffe29 100644 --- a/esphome/components/spi/__init__.py +++ b/esphome/components/spi/__init__.py @@ -361,7 +361,8 @@ def spi_device_schema( default_data_rate=cv.UNDEFINED, default_mode=cv.UNDEFINED, quad=False, - is_databus_schema=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. @@ -371,6 +372,9 @@ def spi_device_schema( :return: The SPI device schema, `extend` this in your config schema. """ schema = { + cv.GenerateID(CONF_SPI_ID): cv.use_id( + QuadSPIComponent if quad else SPIComponent + ), cv.Optional(CONF_DATA_RATE, default=default_data_rate): SPI_DATA_RATE_SCHEMA, cv.Optional(CONF_BIT_ORDER, default="msb_first"): cv.enum(ORDERS, lower=True), cv.Optional(CONF_SPI_MODE, default=default_mode): cv.enum( @@ -378,17 +382,14 @@ def spi_device_schema( ), cv.GenerateID(CONF_CLIENT_ID): cv.declare_id(SPIClient), } - if not is_databus_schema: - schema[cv.GenerateID(CONF_SPI_ID)] = ( - cv.use_id(QuadSPIComponent if quad else SPIComponent), - ) - schema[cv.GenerateID(CONF_CLIENT_ID)] = cv.declare_id(SPIClient) - else: + 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) @@ -441,37 +442,50 @@ def final_validate_device_schema(name: str, *, require_mosi: bool, require_miso: ) +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_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." + ) + + @byte_bus.include_databus( - "SPI", - SPIByteBus, - SPIComponent, - spi_device_schema(False, "40MHz", "mode0", False, True), + "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", - SPIByteBus, - SPIComponent, - spi_device_schema(False, "40MHz", "mode0", False, True), + "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", - SPIByteBus, - QuadSPIComponent, - spi_device_schema(False, "40MHz", "mode0", True, True), + "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_i80_client(config, var, databus_type): - cg.add( - var.set_spi_client( - config[CONF_BIT_ORDER], config[CONF_SPI_MODE], config[CONF_DATA_RATE] - ) - ) +async def create_spi_databus(config, var, databus): + + cg.add(var.set_spi_client(create_spi_client(config))) - cg.add(var.set_data_rate(config[CONF_DATA_RATE])) - if cs_pin := config.get(CONF_CS_PIN): - cg.add(var.set_cs_pin(await cg.gpio_pin_expression(cs_pin))) if pin := config.get(CONF_DC_PIN): cg.add(var.set_dc_pin(await cg.gpio_pin_expression(pin))) - if databus_type == "SPI16D": - cg.add(var.set_16bit_data(True)) + if "use_16BitData" in config: + cg.add(var.set_16bit_data(config["use_16BitData"])) return var diff --git a/esphome/components/spi/spi.h b/esphome/components/spi/spi.h index 03bf56dac882..f458990bbeaf 100644 --- a/esphome/components/spi/spi.h +++ b/esphome/components/spi/spi.h @@ -474,16 +474,11 @@ class SPIByteBus : public byte_bus::ByteBus { 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; } - void set_parent(SPIComponent *parent) { this->parent_ = parent; } - void set_spi_client(SPIBitOrder bit_order, SPIMode mode, uint32_t data_rate) { - this->client_ = new SPIClient(bit_order, mode, data_rate); - this->client_->set_parent(this->parent_); - } - void set_cs_pin(GPIOPin *cs_pin) { this->client_->set_cs_pin(cs_pin); } - void set_data_rate(int data_rate) { this->client_->set_data_rate(data_rate); } protected: void do_begin_transaction() override { this->client_->enable(); } @@ -492,7 +487,7 @@ class SPIByteBus : public byte_bus::ByteBus { SPIClient *client_; GPIOPin *dc_pin_{byte_bus::NULL_PIN}; bool is_16bit_data_{false}; - SPIComponent *parent_{}; + }; } // namespace spi } // namespace esphome From cfe2fc0a8fed3570c15878ee2c5c69a415a54b92 Mon Sep 17 00:00:00 2001 From: NP v/d Spek Date: Mon, 22 Apr 2024 19:31:09 +0200 Subject: [PATCH 09/11] fixed some compile errors --- esphome/components/i80/__init__.py | 6 +----- esphome/components/i80/i80_component.h | 2 +- esphome/components/spi/__init__.py | 6 +++--- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/esphome/components/i80/__init__.py b/esphome/components/i80/__init__.py index c85cfb58571d..e440d7075d44 100644 --- a/esphome/components/i80/__init__.py +++ b/esphome/components/i80/__init__.py @@ -113,11 +113,7 @@ def final_validate_databus_schema(name: str, pin_count, config=None): 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]), config["i80_pin_count"] - ) - ) + 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))) diff --git a/esphome/components/i80/i80_component.h b/esphome/components/i80/i80_component.h index dce036eb8fe5..8b60096b46d2 100644 --- a/esphome/components/i80/i80_component.h +++ b/esphome/components/i80/i80_component.h @@ -87,7 +87,7 @@ class I80ByteBus : public byte_bus::ByteBus { void dump_config() override; - void set_parent(I80Component *parent, uint8_t pin_count) { this->parent_ = parent; } + void set_parent(I80Component *parent) { this->parent_ = parent; } void set_cs_pin(GPIOPin *cs) { this->cs_ = cs; } void set_data_rate(int data_rate) { this->data_rate_ = data_rate; } diff --git a/esphome/components/spi/__init__.py b/esphome/components/spi/__init__.py index d257790ffe29..aaf3ac1124f9 100644 --- a/esphome/components/spi/__init__.py +++ b/esphome/components/spi/__init__.py @@ -452,9 +452,9 @@ def final_validate_databus_schema( 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_MISO_PIN not in spi): + 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_MISO_PIN}' declared." + f"The {bus_type} bus type requires that the {spi_id} component has the '{CONF_MOSI_PIN}' declared." ) @@ -481,7 +481,7 @@ def final_validate_databus_schema( ) async def create_spi_databus(config, var, databus): - cg.add(var.set_spi_client(create_spi_client(config))) + 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))) From f97db7f47b1a7e02d643da351ce0d87f8084d1dd Mon Sep 17 00:00:00 2001 From: NP v/d Spek Date: Mon, 22 Apr 2024 20:39:16 +0200 Subject: [PATCH 10/11] fixed a small Clang fix --- esphome/components/spi/spi.h | 1 - 1 file changed, 1 deletion(-) diff --git a/esphome/components/spi/spi.h b/esphome/components/spi/spi.h index f458990bbeaf..fa5158f7b9b2 100644 --- a/esphome/components/spi/spi.h +++ b/esphome/components/spi/spi.h @@ -479,7 +479,6 @@ class SPIByteBus : public byte_bus::ByteBus { 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(); } From 8827bd26a3760d8849be8a4f061418ca3c6cebdd Mon Sep 17 00:00:00 2001 From: NP v/d Spek Date: Mon, 22 Apr 2024 20:51:47 +0200 Subject: [PATCH 11/11] fixed a small Clang fix --- CODEOWNERS | 2 +- esphome/components/spi/spi.h | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) 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/spi/spi.h b/esphome/components/spi/spi.h index fa5158f7b9b2..789f9fd17a81 100644 --- a/esphome/components/spi/spi.h +++ b/esphome/components/spi/spi.h @@ -486,7 +486,6 @@ class SPIByteBus : public byte_bus::ByteBus { SPIClient *client_; GPIOPin *dc_pin_{byte_bus::NULL_PIN}; bool is_16bit_data_{false}; - }; } // namespace spi } // namespace esphome