diff --git a/esphome/components/ledc/ledc_output.cpp b/esphome/components/ledc/ledc_output.cpp index 64094478c..b3652c84d 100644 --- a/esphome/components/ledc/ledc_output.cpp +++ b/esphome/components/ledc/ledc_output.cpp @@ -11,10 +11,10 @@ namespace ledc { static const char *TAG = "ledc.output"; void LEDCOutput::write_state(float state) { - if (this->pin_->is_inverted()) { + if (this->pin_->is_inverted()) state = 1.0f - state; - } + this->duty_ = state; const uint32_t max_duty = (uint32_t(1) << this->bit_depth_) - 1; const float duty_rounded = roundf(state * max_duty); auto duty = static_cast(duty_rounded); @@ -22,18 +22,45 @@ void LEDCOutput::write_state(float state) { } void LEDCOutput::setup() { - ledcSetup(this->channel_, this->frequency_, this->bit_depth_); - ledcAttachPin(this->pin_->get_pin(), this->channel_); - + this->apply_frequency(this->frequency_); this->turn_off(); + // Attach pin after setting default value + ledcAttachPin(this->pin_->get_pin(), this->channel_); } void LEDCOutput::dump_config() { ESP_LOGCONFIG(TAG, "LEDC Output:"); - LOG_PIN(" Pin", this->pin_); + LOG_PIN(" Pin ", this->pin_); ESP_LOGCONFIG(TAG, " LEDC Channel: %u", this->channel_); ESP_LOGCONFIG(TAG, " Frequency: %.1f Hz", this->frequency_); - ESP_LOGCONFIG(TAG, " Bit Depth: %u", this->bit_depth_); +} + +float ledc_max_frequency_for_bit_depth(uint8_t bit_depth) { return 80e6f / float(1 << bit_depth); } +float ledc_min_frequency_for_bit_depth(uint8_t bit_depth) { + const float max_div_num = ((1 << 20) - 1) / 256.0f; + return 80e6f / (max_div_num * float(1 << bit_depth)); +} +optional ledc_bit_depth_for_frequency(float frequency) { + for (int i = 20; i >= 1; i--) { + const float min_frequency = ledc_min_frequency_for_bit_depth(frequency); + const float max_frequency = ledc_max_frequency_for_bit_depth(frequency); + if (min_frequency <= frequency && frequency <= max_frequency) + return i; + } + return {}; +} + +void LEDCOutput::apply_frequency(float frequency) { + auto bit_depth_opt = ledc_bit_depth_for_frequency(frequency); + if (!bit_depth_opt.has_value()) { + ESP_LOGW(TAG, "Frequency %f can't be achieved with any bit depth", frequency); + this->status_set_warning(); + } + this->bit_depth_ = *bit_depth_opt; + this->frequency_ = frequency; + ledcSetup(this->channel_, frequency, this->bit_depth_); + // re-apply duty + this->write_state(this->duty_); } uint8_t next_ledc_channel = 0; diff --git a/esphome/components/ledc/ledc_output.h b/esphome/components/ledc/ledc_output.h index d1b9b099e..3f56f502b 100644 --- a/esphome/components/ledc/ledc_output.h +++ b/esphome/components/ledc/ledc_output.h @@ -2,6 +2,7 @@ #include "esphome/core/component.h" #include "esphome/core/esphal.h" +#include "esphome/core/automation.h" #include "esphome/components/output/float_output.h" #ifdef ARDUINO_ARCH_ESP32 @@ -16,11 +17,10 @@ class LEDCOutput : public output::FloatOutput, public Component { explicit LEDCOutput(GPIOPin *pin) : pin_(pin) { this->channel_ = next_ledc_channel++; } void set_channel(uint8_t channel) { this->channel_ = channel; } - void set_bit_depth(uint8_t bit_depth) { this->bit_depth_ = bit_depth; } void set_frequency(float frequency) { this->frequency_ = frequency; } + /// Dynamically change frequency at runtime + void apply_frequency(float frequency); - // ========== INTERNAL METHODS ========== - // (In most use cases you won't need these) /// Setup LEDC. void setup() override; void dump_config() override; @@ -28,13 +28,28 @@ class LEDCOutput : public output::FloatOutput, public Component { float get_setup_priority() const override { return setup_priority::HARDWARE; } /// Override FloatOutput's write_state. - void write_state(float adjusted_value) override; + void write_state(float state) override; protected: GPIOPin *pin_; uint8_t channel_{}; uint8_t bit_depth_{}; float frequency_{}; + float duty_{0.0f}; +}; + +template class SetFrequencyAction : public Action { + public: + SetFrequencyAction(LEDCOutput *parent) : parent_(parent) {} + TEMPLATABLE_VALUE(float, frequency); + + void play(Ts... x) { + float freq = this->frequency_.value(x...); + this->parent_->apply_frequency(freq); + } + + protected: + LEDCOutput *parent_; }; } // namespace ledc diff --git a/esphome/components/ledc/output.py b/esphome/components/ledc/output.py index c507465ff..b608e9bbf 100644 --- a/esphome/components/ledc/output.py +++ b/esphome/components/ledc/output.py @@ -1,6 +1,4 @@ -import math - -from esphome import pins +from esphome import pins, automation from esphome.components import output import esphome.config_validation as cv import esphome.codegen as cg @@ -15,53 +13,36 @@ def calc_max_frequency(bit_depth): def calc_min_frequency(bit_depth): - # LEDC_DIV_NUM_HSTIMER is 15-bit unsigned integer - # lower 8 bits represent fractional part - max_div_num = ((1 << 16) - 1) / 256.0 + max_div_num = ((2**20) - 1) / 256.0 return 80e6 / (max_div_num * (2**bit_depth)) -def validate_frequency_bit_depth(obj): - frequency = obj[CONF_FREQUENCY] - if CONF_BIT_DEPTH not in obj: - obj = obj.copy() - for bit_depth in range(15, 0, -1): - if calc_min_frequency(bit_depth) <= frequency <= calc_max_frequency(bit_depth): - obj[CONF_BIT_DEPTH] = bit_depth - break - else: - min_freq = min(calc_min_frequency(x) for x in range(1, 16)) - max_freq = max(calc_max_frequency(x) for x in range(1, 16)) - if frequency < min_freq: - raise cv.Invalid("This frequency setting is not possible, please choose a higher " - "frequency (at least {}Hz)".format(int(min_freq))) - if frequency > max_freq: - raise cv.Invalid("This frequency setting is not possible, please choose a lower " - "frequency (at most {}Hz)".format(int(max_freq))) - raise cv.Invalid("Invalid frequency!") - - bit_depth = obj[CONF_BIT_DEPTH] - min_freq = calc_min_frequency(bit_depth) - max_freq = calc_max_frequency(bit_depth) - if frequency > max_freq: - raise cv.Invalid('Maximum frequency for bit depth {} is {}Hz. Please decrease the ' - 'bit_depth.'.format(bit_depth, int(math.floor(max_freq)))) - if frequency < calc_min_frequency(bit_depth): - raise cv.Invalid('Minimum frequency for bit depth {} is {}Hz. Please increase the ' - 'bit_depth.'.format(bit_depth, int(math.ceil(min_freq)))) - return obj +def validate_frequency(value): + value = cv.frequency(value) + min_freq = calc_min_frequency(20) + max_freq = calc_max_frequency(1) + if value < min_freq: + raise cv.Invalid("This frequency setting is not possible, please choose a higher " + "frequency (at least {}Hz)".format(int(min_freq))) + if value > max_freq: + raise cv.Invalid("This frequency setting is not possible, please choose a lower " + "frequency (at most {}Hz)".format(int(max_freq))) + return value ledc_ns = cg.esphome_ns.namespace('ledc') LEDCOutput = ledc_ns.class_('LEDCOutput', output.FloatOutput, cg.Component) +SetFrequencyAction = ledc_ns.class_('SetFrequencyAction', automation.Action) -CONFIG_SCHEMA = cv.All(output.FLOAT_OUTPUT_SCHEMA.extend({ +CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend({ cv.Required(CONF_ID): cv.declare_id(LEDCOutput), cv.Required(CONF_PIN): pins.internal_gpio_output_pin_schema, cv.Optional(CONF_FREQUENCY, default='1kHz'): cv.frequency, - cv.Optional(CONF_BIT_DEPTH): cv.int_range(min=1, max=15), cv.Optional(CONF_CHANNEL): cv.int_range(min=0, max=15), -}).extend(cv.COMPONENT_SCHEMA), validate_frequency_bit_depth) + + cv.Optional(CONF_BIT_DEPTH): cv.invalid("The bit_depth option has been removed in v1.14, the " + "best bit depth is now automatically calculated."), +}).extend(cv.COMPONENT_SCHEMA) def to_code(config): @@ -72,4 +53,15 @@ def to_code(config): if CONF_CHANNEL in config: cg.add(var.set_channel(config[CONF_CHANNEL])) cg.add(var.set_frequency(config[CONF_FREQUENCY])) - cg.add(var.set_bit_depth(config[CONF_BIT_DEPTH])) + + +@automation.register_action('output.ledc.set_frequency', SetFrequencyAction, cv.Schema({ + cv.Required(CONF_ID): cv.use_id(LEDCOutput), + cv.Required(CONF_FREQUENCY): cv.templatable(validate_frequency), +})) +def ledc_set_frequency_to_code(config, action_id, template_arg, args): + paren = yield cg.get_variable(config[CONF_ID]) + var = cg.new_Pvariable(action_id, template_arg, paren) + template_ = yield cg.templatable(config[CONF_FREQUENCY], args, float) + cg.add(var.set_frequency(template_)) + yield var diff --git a/platformio.ini b/platformio.ini index 98f837a57..5094ae3d8 100644 --- a/platformio.ini +++ b/platformio.ini @@ -29,14 +29,6 @@ build_flags = ; log messages src_filter = + -[env:livingroom32] -platform = espressif32@1.6.0 -board = nodemcu-32s -framework = arduino -lib_deps = ${common.lib_deps} -build_flags = ${common.build_flags} -DUSE_ETHERNET -src_filter = ${common.src_filter} + - [env:livingroom8266] platform = espressif8266@1.8.0 board = nodemcuv2 @@ -47,3 +39,11 @@ lib_deps = Hash build_flags = ${common.build_flags} src_filter = ${common.src_filter} + + +[env:livingroom32] +platform = espressif32@1.6.0 +board = nodemcu-32s +framework = arduino +lib_deps = ${common.lib_deps} +build_flags = ${common.build_flags} -DUSE_ETHERNET +src_filter = ${common.src_filter} + diff --git a/tests/test1.yaml b/tests/test1.yaml index b2293969c..ac199a0a4 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -722,6 +722,12 @@ binary_sensor: - binary_sensor.template.publish: id: garage_door state: OFF + - output.ledc.set_frequency: + id: gpio_19 + frequency: 500.0Hz + - output.ledc.set_frequency: + id: gpio_19 + frequency: !lambda 'return 500.0;' - platform: pn532 uid: 74-10-37-94 name: "PN532 NFC Tag" @@ -789,7 +795,6 @@ output: pin: 19 id: gpio_19 frequency: 1500Hz - bit_depth: 8 channel: 14 max_power: 0.5 - platform: pca9685