diff --git a/esphome/components/ledc/ledc_output.cpp b/esphome/components/ledc/ledc_output.cpp index 34cf4cd1f6..d6241dd5b0 100644 --- a/esphome/components/ledc/ledc_output.cpp +++ b/esphome/components/ledc/ledc_output.cpp @@ -8,6 +8,20 @@ #endif #include +#define CLOCK_FREQUENCY 80e6f + +#ifdef USE_ARDUINO +#ifdef SOC_LEDC_SUPPORT_XTAL_CLOCK +#undef CLOCK_FREQUENCY +// starting with ESP32 Arduino 2.0.2, the 40MHz crystal is used as clock by default if supported +#define CLOCK_FREQUENCY 40e6f +#endif +#else +#define DEFAULT_CLK LEDC_USE_APB_CLK +#endif + +static const uint8_t SETUP_ATTEMPT_COUNT_MAX = 5; + namespace esphome { namespace ledc { @@ -26,11 +40,11 @@ inline ledc_mode_t get_speed_mode(uint8_t) { return LEDC_LOW_SPEED_MODE; } #endif #endif -float ledc_max_frequency_for_bit_depth(uint8_t bit_depth) { return 80e6f / float(1 << bit_depth); } +float ledc_max_frequency_for_bit_depth(uint8_t bit_depth) { return CLOCK_FREQUENCY / float(1 << bit_depth); } float ledc_min_frequency_for_bit_depth(uint8_t bit_depth, bool low_frequency) { const float max_div_num = ((1 << MAX_RES_BITS) - 1) / (low_frequency ? 32.0f : 256.0f); - return 80e6f / (max_div_num * float(1 << bit_depth)); + return CLOCK_FREQUENCY / (max_div_num * float(1 << bit_depth)); } optional ledc_bit_depth_for_frequency(float frequency) { @@ -46,6 +60,38 @@ optional ledc_bit_depth_for_frequency(float frequency) { return {}; } +#ifdef USE_ESP_IDF +esp_err_t configure_timer_frequency(ledc_mode_t speed_mode, ledc_timer_t timer_num, ledc_channel_t chan_num, + uint8_t channel, uint8_t &bit_depth, float frequency) { + bit_depth = *ledc_bit_depth_for_frequency(frequency); + if (bit_depth < 1) { + ESP_LOGE(TAG, "Frequency %f can't be achieved with any bit depth", frequency); + } + + ledc_timer_config_t timer_conf{}; + timer_conf.speed_mode = speed_mode; + timer_conf.duty_resolution = static_cast(bit_depth); + timer_conf.timer_num = timer_num; + timer_conf.freq_hz = (uint32_t) frequency; + timer_conf.clk_cfg = DEFAULT_CLK; + + // Configure the time with fallback in case of error + int attempt_count_max = SETUP_ATTEMPT_COUNT_MAX; + esp_err_t init_result = ESP_FAIL; + while (attempt_count_max > 0 && init_result != ESP_OK) { + init_result = ledc_timer_config(&timer_conf); + if (init_result != ESP_OK) { + ESP_LOGW(TAG, "Unable to initialize timer with frequency %.1f and bit depth of %u", frequency, bit_depth); + // try again with a lower bit depth + timer_conf.duty_resolution = static_cast(--bit_depth); + } + attempt_count_max--; + } + + return init_result; +} +#endif + void LEDCOutput::write_state(float state) { if (!initialized_) { ESP_LOGW(TAG, "LEDC output hasn't been initialized yet!"); @@ -61,6 +107,7 @@ void LEDCOutput::write_state(float state) { auto duty = static_cast(duty_rounded); #ifdef USE_ARDUINO + ESP_LOGV(TAG, "Setting duty: %u on channel %u", duty, this->channel_); ledcWrite(this->channel_, duty); #endif #ifdef USE_ESP_IDF @@ -72,6 +119,7 @@ void LEDCOutput::write_state(float state) { } void LEDCOutput::setup() { + ESP_LOGV(TAG, "Entering setup..."); #ifdef USE_ARDUINO this->update_frequency(this->frequency_); this->turn_off(); @@ -83,19 +131,16 @@ void LEDCOutput::setup() { auto timer_num = static_cast((channel_ % 8) / 2); auto chan_num = static_cast(channel_ % 8); - bit_depth_ = *ledc_bit_depth_for_frequency(frequency_); - if (bit_depth_ < 1) { - ESP_LOGW(TAG, "Frequency %f can't be achieved with any bit depth", frequency_); - this->status_set_warning(); + esp_err_t timer_init_result = + configure_timer_frequency(speed_mode, timer_num, chan_num, this->channel_, this->bit_depth_, this->frequency_); + + if (timer_init_result != ESP_OK) { + ESP_LOGE(TAG, "Frequency %f can't be achieved with computed bit depth %u", this->frequency_, this->bit_depth_); + this->status_set_error(); + return; } - ledc_timer_config_t timer_conf{}; - timer_conf.speed_mode = speed_mode; - timer_conf.duty_resolution = static_cast(bit_depth_); - timer_conf.timer_num = timer_num; - timer_conf.freq_hz = (uint32_t) frequency_; - timer_conf.clk_cfg = LEDC_AUTO_CLK; - ledc_timer_config(&timer_conf); + ESP_LOGV(TAG, "Configured frequency %f with a bit depth of %u bits", this->frequency_, this->bit_depth_); ledc_channel_config_t chan_conf{}; chan_conf.gpio_num = pin_->get_pin(); @@ -107,6 +152,7 @@ void LEDCOutput::setup() { chan_conf.hpoint = 0; ledc_channel_config(&chan_conf); initialized_ = true; + this->status_clear_error(); #endif } @@ -114,36 +160,80 @@ void LEDCOutput::dump_config() { ESP_LOGCONFIG(TAG, "LEDC Output:"); LOG_PIN(" Pin ", this->pin_); ESP_LOGCONFIG(TAG, " LEDC Channel: %u", this->channel_); - ESP_LOGCONFIG(TAG, " Frequency: %.1f Hz", this->frequency_); + ESP_LOGCONFIG(TAG, " PWM Frequency: %.1f Hz", this->frequency_); + ESP_LOGCONFIG(TAG, " Bit depth: %u", this->bit_depth_); + ESP_LOGV(TAG, " Max frequency for bit depth: %f", ledc_max_frequency_for_bit_depth(this->bit_depth_)); + ESP_LOGV(TAG, " Min frequency for bit depth: %f", + ledc_min_frequency_for_bit_depth(this->bit_depth_, (this->frequency_ < 100))); + ESP_LOGV(TAG, " Max frequency for bit depth-1: %f", ledc_max_frequency_for_bit_depth(this->bit_depth_ - 1)); + ESP_LOGV(TAG, " Min frequency for bit depth-1: %f", + ledc_min_frequency_for_bit_depth(this->bit_depth_ - 1, (this->frequency_ < 100))); + ESP_LOGV(TAG, " Max frequency for bit depth+1: %f", ledc_max_frequency_for_bit_depth(this->bit_depth_ + 1)); + ESP_LOGV(TAG, " Min frequency for bit depth+1: %f", + ledc_min_frequency_for_bit_depth(this->bit_depth_ + 1, (this->frequency_ < 100))); + ESP_LOGV(TAG, " Max res bits: %d", MAX_RES_BITS); + ESP_LOGV(TAG, " Clock frequency: %f", CLOCK_FREQUENCY); } void LEDCOutput::update_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(); + ESP_LOGE(TAG, "Frequency %f can't be achieved with any bit depth", this->frequency_); + this->status_set_error(); } this->bit_depth_ = bit_depth_opt.value_or(8); this->frequency_ = frequency; #ifdef USE_ARDUINO - ledcSetup(this->channel_, frequency, this->bit_depth_); - initialized_ = true; + ESP_LOGV(TAG, "Using Arduino API - Trying to define channel, frequency and bit depth..."); + u_int32_t configured_frequency = 0; + + // Configure LEDC channel, frequency and bit depth with fallback + int attempt_count_max = SETUP_ATTEMPT_COUNT_MAX; + while (attempt_count_max > 0 && configured_frequency == 0) { + ESP_LOGV(TAG, "Trying initialize channel %u with frequency %.1f and bit depth of %u...", this->channel_, + this->frequency_, this->bit_depth_); + configured_frequency = ledcSetup(this->channel_, frequency, this->bit_depth_); + if (configured_frequency != 0) { + initialized_ = true; + this->status_clear_error(); + ESP_LOGV(TAG, "Configured frequency: %u with bit depth: %u", configured_frequency, this->bit_depth_); + } else { + ESP_LOGW(TAG, "Unable to initialize channel %u with frequency %.1f and bit depth of %u", this->channel_, + this->frequency_, this->bit_depth_); + // try again with a lower bit depth + this->bit_depth_--; + } + attempt_count_max--; + } + + if (configured_frequency == 0) { + ESP_LOGE(TAG, "Permanently failed to initialize channel %u with frequency %.1f and bit depth of %u", this->channel_, + this->frequency_, this->bit_depth_); + this->status_set_error(); + return; + } + #endif // USE_ARDUINO #ifdef USE_ESP_IDF if (!initialized_) { ESP_LOGW(TAG, "LEDC output hasn't been initialized yet!"); return; } + auto speed_mode = get_speed_mode(channel_); auto timer_num = static_cast((channel_ % 8) / 2); + auto chan_num = static_cast(channel_ % 8); - ledc_timer_config_t timer_conf{}; - timer_conf.speed_mode = speed_mode; - timer_conf.duty_resolution = static_cast(bit_depth_); - timer_conf.timer_num = timer_num; - timer_conf.freq_hz = (uint32_t) frequency_; - timer_conf.clk_cfg = LEDC_AUTO_CLK; - ledc_timer_config(&timer_conf); + esp_err_t timer_init_result = + configure_timer_frequency(speed_mode, timer_num, chan_num, this->channel_, this->bit_depth_, this->frequency_); + + if (timer_init_result != ESP_OK) { + ESP_LOGE(TAG, "Frequency %f can't be achieved with computed bit depth %u", this->frequency_, this->bit_depth_); + this->status_set_error(); + return; + } + + this->status_clear_error(); #endif // re-apply duty this->write_state(this->duty_);