mirror of
https://github.com/esphome/esphome.git
synced 2026-06-24 16:20:42 +00:00
[ledc] Fix high-pressure crash & recovery (#14720)
This commit is contained in:
@@ -5,6 +5,10 @@
|
||||
|
||||
#include <driver/ledc.h>
|
||||
#include <cinttypes>
|
||||
#include <esp_private/periph_ctrl.h>
|
||||
#if !defined(SOC_LEDC_SUPPORT_FADE_STOP)
|
||||
#include <hal/ledc_ll.h>
|
||||
#endif
|
||||
|
||||
#define CLOCK_FREQUENCY 80e6f
|
||||
|
||||
@@ -16,10 +20,10 @@
|
||||
|
||||
static const uint8_t SETUP_ATTEMPT_COUNT_MAX = 5;
|
||||
|
||||
namespace esphome {
|
||||
namespace ledc {
|
||||
namespace esphome::ledc {
|
||||
|
||||
static const char *const TAG = "ledc.output";
|
||||
static bool ledc_peripheral_reset_done = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
static const int MAX_RES_BITS = LEDC_TIMER_BIT_MAX - 1;
|
||||
#if SOC_LEDC_SUPPORT_HS_MODE
|
||||
@@ -32,6 +36,28 @@ inline ledc_mode_t get_speed_mode(uint8_t channel) { return channel < 8 ? LEDC_H
|
||||
inline ledc_mode_t get_speed_mode(uint8_t) { return LEDC_LOW_SPEED_MODE; }
|
||||
#endif
|
||||
|
||||
#if !defined(SOC_LEDC_SUPPORT_FADE_STOP)
|
||||
// Classic ESP32 (currently the only target without SOC_LEDC_SUPPORT_FADE_STOP) can block in
|
||||
// ledc_ll_set_duty_start() while duty_start is set. We check the same conf1.duty_start bit here
|
||||
// to defer updates and avoid entering IDF's unbounded wait loop.
|
||||
//
|
||||
// This intentionally depends on the classic ESP32 LEDC register layout used by IDF's own LL HAL.
|
||||
// If another target without SOC_LEDC_SUPPORT_FADE_STOP is introduced, revisit this helper.
|
||||
static_assert(
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32)
|
||||
true,
|
||||
#else
|
||||
false,
|
||||
#endif
|
||||
"LEDC duty_start pending check assumes classic ESP32 register layout; "
|
||||
"re-evaluate for this target");
|
||||
|
||||
static bool ledc_duty_update_pending(ledc_mode_t speed_mode, ledc_channel_t chan_num) {
|
||||
auto *hw = LEDC_LL_GET_HW();
|
||||
return hw->channel_group[speed_mode].channel[chan_num].conf1.duty_start != 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
float ledc_max_frequency_for_bit_depth(uint8_t bit_depth) {
|
||||
return static_cast<float>(CLOCK_FREQUENCY) / static_cast<float>(1 << bit_depth);
|
||||
}
|
||||
@@ -105,21 +131,40 @@ void LEDCOutput::write_state(float 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<uint32_t>(duty_rounded);
|
||||
if (duty == this->last_duty_) {
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGV(TAG, "Setting duty: %" PRIu32 " on channel %u", duty, this->channel_);
|
||||
auto speed_mode = get_speed_mode(this->channel_);
|
||||
auto chan_num = static_cast<ledc_channel_t>(this->channel_ % 8);
|
||||
int hpoint = ledc_angle_to_htop(this->phase_angle_, this->bit_depth_);
|
||||
if (duty == max_duty) {
|
||||
ledc_stop(speed_mode, chan_num, 1);
|
||||
this->last_duty_ = duty;
|
||||
} else if (duty == 0) {
|
||||
ledc_stop(speed_mode, chan_num, 0);
|
||||
this->last_duty_ = duty;
|
||||
} else {
|
||||
#if !defined(SOC_LEDC_SUPPORT_FADE_STOP)
|
||||
if (ledc_duty_update_pending(speed_mode, chan_num)) {
|
||||
ESP_LOGV(TAG, "Skipping LEDC duty update on channel %u while previous duty_start is still set", this->channel_);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
ledc_set_duty_with_hpoint(speed_mode, chan_num, duty, hpoint);
|
||||
ledc_update_duty(speed_mode, chan_num);
|
||||
this->last_duty_ = duty;
|
||||
}
|
||||
}
|
||||
|
||||
void LEDCOutput::setup() {
|
||||
if (!ledc_peripheral_reset_done) {
|
||||
ESP_LOGV(TAG, "Resetting LEDC peripheral to clear stale state after reboot");
|
||||
periph_module_reset(PERIPH_LEDC_MODULE);
|
||||
ledc_peripheral_reset_done = true;
|
||||
}
|
||||
|
||||
auto speed_mode = get_speed_mode(this->channel_);
|
||||
auto timer_num = static_cast<ledc_timer_t>((this->channel_ % 8) / 2);
|
||||
auto chan_num = static_cast<ledc_channel_t>(this->channel_ % 8);
|
||||
@@ -207,12 +252,12 @@ void LEDCOutput::update_frequency(float frequency) {
|
||||
this->status_clear_error();
|
||||
|
||||
// re-apply duty
|
||||
this->last_duty_ = UINT32_MAX;
|
||||
this->write_state(this->duty_);
|
||||
}
|
||||
|
||||
uint8_t next_ledc_channel = 0; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
} // namespace ledc
|
||||
} // namespace esphome
|
||||
} // namespace esphome::ledc
|
||||
|
||||
#endif
|
||||
|
||||
@@ -4,11 +4,11 @@
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/components/output/float_output.h"
|
||||
#include <cstdint>
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace ledc {
|
||||
namespace esphome::ledc {
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
extern uint8_t next_ledc_channel;
|
||||
@@ -39,6 +39,7 @@ class LEDCOutput : public output::FloatOutput, public Component {
|
||||
float phase_angle_{0.0f};
|
||||
float frequency_{};
|
||||
float duty_{0.0f};
|
||||
uint32_t last_duty_{UINT32_MAX};
|
||||
bool initialized_ = false;
|
||||
};
|
||||
|
||||
@@ -56,7 +57,6 @@ template<typename... Ts> class SetFrequencyAction : public Action<Ts...> {
|
||||
LEDCOutput *parent_;
|
||||
};
|
||||
|
||||
} // namespace ledc
|
||||
} // namespace esphome
|
||||
} // namespace esphome::ledc
|
||||
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user