From 582d90ad72498af404bdf52da34d4a23d4387384 Mon Sep 17 00:00:00 2001 From: Stanislav Habich Date: Wed, 18 Jan 2023 01:00:35 +0100 Subject: [PATCH] PCA9685, fix reset device and add option EXTCLK (#3845) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/pca9685/__init__.py | 28 +++++++++++++--- esphome/components/pca9685/pca9685_output.cpp | 33 ++++++++++++++----- esphome/components/pca9685/pca9685_output.h | 6 ++-- esphome/const.py | 1 + 4 files changed, 52 insertions(+), 16 deletions(-) diff --git a/esphome/components/pca9685/__init__.py b/esphome/components/pca9685/__init__.py index 1a5ccc3247..b22577bf9f 100644 --- a/esphome/components/pca9685/__init__.py +++ b/esphome/components/pca9685/__init__.py @@ -1,7 +1,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import i2c -from esphome.const import CONF_FREQUENCY, CONF_ID +from esphome.const import CONF_FREQUENCY, CONF_ID, CONF_EXTERNAL_CLOCK_INPUT DEPENDENCIES = ["i2c"] MULTI_CONF = True @@ -9,21 +9,39 @@ MULTI_CONF = True pca9685_ns = cg.esphome_ns.namespace("pca9685") PCA9685Output = pca9685_ns.class_("PCA9685Output", cg.Component, i2c.I2CDevice) -CONFIG_SCHEMA = ( + +def validate_frequency(config): + if config[CONF_EXTERNAL_CLOCK_INPUT]: + if CONF_FREQUENCY in config: + raise cv.Invalid( + "Frequency cannot be set when using an external clock input" + ) + return config + if CONF_FREQUENCY not in config: + raise cv.Invalid("Frequency is required") + return config + + +CONFIG_SCHEMA = cv.All( cv.Schema( { cv.GenerateID(): cv.declare_id(PCA9685Output), - cv.Required(CONF_FREQUENCY): cv.All( + cv.Optional(CONF_FREQUENCY): cv.All( cv.frequency, cv.Range(min=23.84, max=1525.88) ), + cv.Optional(CONF_EXTERNAL_CLOCK_INPUT, default=False): cv.boolean, } ) .extend(cv.COMPONENT_SCHEMA) - .extend(i2c.i2c_device_schema(0x40)) + .extend(i2c.i2c_device_schema(0x40)), + validate_frequency, ) async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID], config[CONF_FREQUENCY]) + var = cg.new_Pvariable(config[CONF_ID]) + if CONF_FREQUENCY in config: + cg.add(var.set_frequency(config[CONF_FREQUENCY])) + cg.add(var.set_extclk(config[CONF_EXTERNAL_CLOCK_INPUT])) await cg.register_component(var, config) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/pca9685/pca9685_output.cpp b/esphome/components/pca9685/pca9685_output.cpp index 957f4062fc..c61251b66f 100644 --- a/esphome/components/pca9685/pca9685_output.cpp +++ b/esphome/components/pca9685/pca9685_output.cpp @@ -21,6 +21,7 @@ static const uint8_t PCA9685_REGISTER_LED0 = 0x06; static const uint8_t PCA9685_REGISTER_PRE_SCALE = 0xFE; static const uint8_t PCA9685_MODE1_RESTART = 0b10000000; +static const uint8_t PCA9685_MODE1_EXTCLK = 0b01000000; static const uint8_t PCA9685_MODE1_AUTOINC = 0b00100000; static const uint8_t PCA9685_MODE1_SLEEP = 0b00010000; @@ -28,10 +29,13 @@ void PCA9685Output::setup() { ESP_LOGCONFIG(TAG, "Setting up PCA9685OutputComponent..."); ESP_LOGV(TAG, " Resetting devices..."); + uint8_t address_tmp = this->address_; + this->set_i2c_address(0x00); if (!this->write_bytes(PCA9685_REGISTER_SOFTWARE_RESET, nullptr, 0)) { this->mark_failed(); return; } + this->set_i2c_address(address_tmp); if (!this->write_byte(PCA9685_REGISTER_MODE1, PCA9685_MODE1_RESTART | PCA9685_MODE1_AUTOINC)) { this->mark_failed(); @@ -42,14 +46,6 @@ void PCA9685Output::setup() { return; } - int pre_scaler = static_cast((25000000 / (4096 * this->frequency_)) - 1); - if (pre_scaler > 255) - pre_scaler = 255; - if (pre_scaler < 3) - pre_scaler = 3; - - ESP_LOGV(TAG, " -> Prescaler: %d", pre_scaler); - uint8_t mode1; if (!this->read_byte(PCA9685_REGISTER_MODE1, &mode1)) { this->mark_failed(); @@ -60,6 +56,20 @@ void PCA9685Output::setup() { this->mark_failed(); return; } + + int pre_scaler = 3; + if (this->extclk_) { + mode1 = mode1 | PCA9685_MODE1_EXTCLK; + if (!this->write_byte(PCA9685_REGISTER_MODE1, mode1)) { + this->mark_failed(); + return; + } + } else { + pre_scaler = static_cast((25000000 / (4096 * this->frequency_)) - 1); + pre_scaler = clamp(pre_scaler, 3, 255); + + ESP_LOGV(TAG, " -> Prescaler: %d", pre_scaler); + } if (!this->write_byte(PCA9685_REGISTER_PRE_SCALE, pre_scaler)) { this->mark_failed(); return; @@ -78,7 +88,12 @@ void PCA9685Output::setup() { void PCA9685Output::dump_config() { ESP_LOGCONFIG(TAG, "PCA9685:"); ESP_LOGCONFIG(TAG, " Mode: 0x%02X", this->mode_); - ESP_LOGCONFIG(TAG, " Frequency: %.0f Hz", this->frequency_); + if (this->extclk_) { + ESP_LOGCONFIG(TAG, " EXTCLK: enabled"); + } else { + ESP_LOGCONFIG(TAG, " EXTCLK: disabled"); + ESP_LOGCONFIG(TAG, " Frequency: %.0f Hz", this->frequency_); + } if (this->is_failed()) { ESP_LOGE(TAG, "Setting up PCA9685 failed!"); } diff --git a/esphome/components/pca9685/pca9685_output.h b/esphome/components/pca9685/pca9685_output.h index 5dd52b5510..8e547d0032 100644 --- a/esphome/components/pca9685/pca9685_output.h +++ b/esphome/components/pca9685/pca9685_output.h @@ -37,8 +37,7 @@ class PCA9685Channel : public output::FloatOutput { /// PCA9685 float output component. class PCA9685Output : public Component, public i2c::I2CDevice { public: - PCA9685Output(float frequency, uint8_t mode = PCA9685_MODE_OUTPUT_ONACK | PCA9685_MODE_OUTPUT_TOTEM_POLE) - : frequency_(frequency), mode_(mode) {} + PCA9685Output(uint8_t mode = PCA9685_MODE_OUTPUT_ONACK | PCA9685_MODE_OUTPUT_TOTEM_POLE) : mode_(mode) {} void register_channel(PCA9685Channel *channel); @@ -46,6 +45,8 @@ class PCA9685Output : public Component, public i2c::I2CDevice { void dump_config() override; float get_setup_priority() const override { return setup_priority::HARDWARE; } void loop() override; + void set_extclk(bool extclk) { this->extclk_ = extclk; } + void set_frequency(float frequency) { this->frequency_ = frequency; } protected: friend PCA9685Channel; @@ -58,6 +59,7 @@ class PCA9685Output : public Component, public i2c::I2CDevice { float frequency_; uint8_t mode_; + bool extclk_ = false; uint8_t min_channel_{0xFF}; uint8_t max_channel_{0x00}; diff --git a/esphome/const.py b/esphome/const.py index 6c898ff3ec..6739271de0 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -219,6 +219,7 @@ CONF_EVENT = "event" CONF_EXPIRE_AFTER = "expire_after" CONF_EXPORT_ACTIVE_ENERGY = "export_active_energy" CONF_EXPORT_REACTIVE_ENERGY = "export_reactive_energy" +CONF_EXTERNAL_CLOCK_INPUT = "external_clock_input" CONF_EXTERNAL_COMPONENTS = "external_components" CONF_EXTERNAL_VCC = "external_vcc" CONF_FALLING_EDGE = "falling_edge"