mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-30 22:53:59 +00:00 
			
		
		
		
	Add LEDC set_frequency action (#754)
* Add LEDC set_frequency Fixes https://github.com/esphome/feature-requests/issues/380 * Fix log * Fixes * Format * Update test1.yaml * Update test1.yaml * Fix
This commit is contained in:
		| @@ -11,10 +11,10 @@ namespace ledc { | |||||||
| static const char *TAG = "ledc.output"; | static const char *TAG = "ledc.output"; | ||||||
|  |  | ||||||
| void LEDCOutput::write_state(float state) { | void LEDCOutput::write_state(float state) { | ||||||
|   if (this->pin_->is_inverted()) { |   if (this->pin_->is_inverted()) | ||||||
|     state = 1.0f - state; |     state = 1.0f - state; | ||||||
|   } |  | ||||||
|  |  | ||||||
|  |   this->duty_ = state; | ||||||
|   const uint32_t max_duty = (uint32_t(1) << this->bit_depth_) - 1; |   const uint32_t max_duty = (uint32_t(1) << this->bit_depth_) - 1; | ||||||
|   const float duty_rounded = roundf(state * max_duty); |   const float duty_rounded = roundf(state * max_duty); | ||||||
|   auto duty = static_cast<uint32_t>(duty_rounded); |   auto duty = static_cast<uint32_t>(duty_rounded); | ||||||
| @@ -22,10 +22,10 @@ void LEDCOutput::write_state(float state) { | |||||||
| } | } | ||||||
|  |  | ||||||
| void LEDCOutput::setup() { | void LEDCOutput::setup() { | ||||||
|   ledcSetup(this->channel_, this->frequency_, this->bit_depth_); |   this->apply_frequency(this->frequency_); | ||||||
|   ledcAttachPin(this->pin_->get_pin(), this->channel_); |  | ||||||
|  |  | ||||||
|   this->turn_off(); |   this->turn_off(); | ||||||
|  |   // Attach pin after setting default value | ||||||
|  |   ledcAttachPin(this->pin_->get_pin(), this->channel_); | ||||||
| } | } | ||||||
|  |  | ||||||
| void LEDCOutput::dump_config() { | void LEDCOutput::dump_config() { | ||||||
| @@ -33,7 +33,34 @@ void LEDCOutput::dump_config() { | |||||||
|   LOG_PIN("  Pin ", this->pin_); |   LOG_PIN("  Pin ", this->pin_); | ||||||
|   ESP_LOGCONFIG(TAG, "  LEDC Channel: %u", this->channel_); |   ESP_LOGCONFIG(TAG, "  LEDC Channel: %u", this->channel_); | ||||||
|   ESP_LOGCONFIG(TAG, "  Frequency: %.1f Hz", this->frequency_); |   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<uint8_t> 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; | uint8_t next_ledc_channel = 0; | ||||||
|   | |||||||
| @@ -2,6 +2,7 @@ | |||||||
|  |  | ||||||
| #include "esphome/core/component.h" | #include "esphome/core/component.h" | ||||||
| #include "esphome/core/esphal.h" | #include "esphome/core/esphal.h" | ||||||
|  | #include "esphome/core/automation.h" | ||||||
| #include "esphome/components/output/float_output.h" | #include "esphome/components/output/float_output.h" | ||||||
|  |  | ||||||
| #ifdef ARDUINO_ARCH_ESP32 | #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++; } |   explicit LEDCOutput(GPIOPin *pin) : pin_(pin) { this->channel_ = next_ledc_channel++; } | ||||||
|  |  | ||||||
|   void set_channel(uint8_t channel) { this->channel_ = 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; } |   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. |   /// Setup LEDC. | ||||||
|   void setup() override; |   void setup() override; | ||||||
|   void dump_config() 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; } |   float get_setup_priority() const override { return setup_priority::HARDWARE; } | ||||||
|  |  | ||||||
|   /// Override FloatOutput's write_state. |   /// Override FloatOutput's write_state. | ||||||
|   void write_state(float adjusted_value) override; |   void write_state(float state) override; | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   GPIOPin *pin_; |   GPIOPin *pin_; | ||||||
|   uint8_t channel_{}; |   uint8_t channel_{}; | ||||||
|   uint8_t bit_depth_{}; |   uint8_t bit_depth_{}; | ||||||
|   float frequency_{}; |   float frequency_{}; | ||||||
|  |   float duty_{0.0f}; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | template<typename... Ts> class SetFrequencyAction : public Action<Ts...> { | ||||||
|  |  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 | }  // namespace ledc | ||||||
|   | |||||||
| @@ -1,6 +1,4 @@ | |||||||
| import math | from esphome import pins, automation | ||||||
|  |  | ||||||
| from esphome import pins |  | ||||||
| from esphome.components import output | from esphome.components import output | ||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
| @@ -15,53 +13,36 @@ def calc_max_frequency(bit_depth): | |||||||
|  |  | ||||||
|  |  | ||||||
| def calc_min_frequency(bit_depth): | def calc_min_frequency(bit_depth): | ||||||
|     # LEDC_DIV_NUM_HSTIMER is 15-bit unsigned integer |     max_div_num = ((2**20) - 1) / 256.0 | ||||||
|     # lower 8 bits represent fractional part |  | ||||||
|     max_div_num = ((1 << 16) - 1) / 256.0 |  | ||||||
|     return 80e6 / (max_div_num * (2**bit_depth)) |     return 80e6 / (max_div_num * (2**bit_depth)) | ||||||
|  |  | ||||||
|  |  | ||||||
| def validate_frequency_bit_depth(obj): | def validate_frequency(value): | ||||||
|     frequency = obj[CONF_FREQUENCY] |     value = cv.frequency(value) | ||||||
|     if CONF_BIT_DEPTH not in obj: |     min_freq = calc_min_frequency(20) | ||||||
|         obj = obj.copy() |     max_freq = calc_max_frequency(1) | ||||||
|         for bit_depth in range(15, 0, -1): |     if value < min_freq: | ||||||
|             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 " |         raise cv.Invalid("This frequency setting is not possible, please choose a higher " | ||||||
|                          "frequency (at least {}Hz)".format(int(min_freq))) |                          "frequency (at least {}Hz)".format(int(min_freq))) | ||||||
|             if frequency > max_freq: |     if value > max_freq: | ||||||
|         raise cv.Invalid("This frequency setting is not possible, please choose a lower " |         raise cv.Invalid("This frequency setting is not possible, please choose a lower " | ||||||
|                          "frequency (at most {}Hz)".format(int(max_freq))) |                          "frequency (at most {}Hz)".format(int(max_freq))) | ||||||
|             raise cv.Invalid("Invalid frequency!") |     return value | ||||||
|  |  | ||||||
|     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 |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ledc_ns = cg.esphome_ns.namespace('ledc') | ledc_ns = cg.esphome_ns.namespace('ledc') | ||||||
| LEDCOutput = ledc_ns.class_('LEDCOutput', output.FloatOutput, cg.Component) | 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_ID): cv.declare_id(LEDCOutput), | ||||||
|     cv.Required(CONF_PIN): pins.internal_gpio_output_pin_schema, |     cv.Required(CONF_PIN): pins.internal_gpio_output_pin_schema, | ||||||
|     cv.Optional(CONF_FREQUENCY, default='1kHz'): cv.frequency, |     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), |     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): | def to_code(config): | ||||||
| @@ -72,4 +53,15 @@ def to_code(config): | |||||||
|     if CONF_CHANNEL in config: |     if CONF_CHANNEL in config: | ||||||
|         cg.add(var.set_channel(config[CONF_CHANNEL])) |         cg.add(var.set_channel(config[CONF_CHANNEL])) | ||||||
|     cg.add(var.set_frequency(config[CONF_FREQUENCY])) |     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 | ||||||
|   | |||||||
| @@ -29,14 +29,6 @@ build_flags = | |||||||
| ; log messages | ; log messages | ||||||
| src_filter = +<esphome> | src_filter = +<esphome> | ||||||
|  |  | ||||||
| [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} +<tests/livingroom32.cpp> |  | ||||||
|  |  | ||||||
| [env:livingroom8266] | [env:livingroom8266] | ||||||
| platform = espressif8266@1.8.0 | platform = espressif8266@1.8.0 | ||||||
| board = nodemcuv2 | board = nodemcuv2 | ||||||
| @@ -47,3 +39,11 @@ lib_deps = | |||||||
|     Hash |     Hash | ||||||
| build_flags = ${common.build_flags} | build_flags = ${common.build_flags} | ||||||
| src_filter = ${common.src_filter} +<tests/livingroom8266.cpp> | src_filter = ${common.src_filter} +<tests/livingroom8266.cpp> | ||||||
|  |  | ||||||
|  | [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} +<tests/livingroom32.cpp> | ||||||
|   | |||||||
| @@ -722,6 +722,12 @@ binary_sensor: | |||||||
|       - binary_sensor.template.publish: |       - binary_sensor.template.publish: | ||||||
|           id: garage_door |           id: garage_door | ||||||
|           state: OFF |           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 |   - platform: pn532 | ||||||
|     uid: 74-10-37-94 |     uid: 74-10-37-94 | ||||||
|     name: "PN532 NFC Tag" |     name: "PN532 NFC Tag" | ||||||
| @@ -789,7 +795,6 @@ output: | |||||||
|     pin: 19 |     pin: 19 | ||||||
|     id: gpio_19 |     id: gpio_19 | ||||||
|     frequency: 1500Hz |     frequency: 1500Hz | ||||||
|     bit_depth: 8 |  | ||||||
|     channel: 14 |     channel: 14 | ||||||
|     max_power: 0.5 |     max_power: 0.5 | ||||||
|   - platform: pca9685 |   - platform: pca9685 | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user