mirror of
https://github.com/esphome/esphome.git
synced 2024-12-22 13:34:54 +01: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:
parent
72d6471ab8
commit
68d0d045c0
5 changed files with 98 additions and 59 deletions
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue