mirror of
https://github.com/esphome/esphome.git
synced 2026-01-09 19:50:49 -07:00
[ac_dimmer] Added support for ESP-IDF (5+) (#7072)
Co-authored-by: J. Nick Koston <nick@koston.org> Co-authored-by: J. Nick Koston <nick@home-assistant.io> Co-authored-by: J. Nick Koston <nick+github@koston.org> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -1,5 +1,3 @@
|
|||||||
#ifdef USE_ARDUINO
|
|
||||||
|
|
||||||
#include "ac_dimmer.h"
|
#include "ac_dimmer.h"
|
||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
@@ -9,12 +7,12 @@
|
|||||||
#ifdef USE_ESP8266
|
#ifdef USE_ESP8266
|
||||||
#include <core_esp8266_waveform.h>
|
#include <core_esp8266_waveform.h>
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_ESP32_FRAMEWORK_ARDUINO
|
|
||||||
#include <esp32-hal-timer.h>
|
#ifdef USE_ESP32
|
||||||
|
#include "hw_timer_esp_idf.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome::ac_dimmer {
|
||||||
namespace ac_dimmer {
|
|
||||||
|
|
||||||
static const char *const TAG = "ac_dimmer";
|
static const char *const TAG = "ac_dimmer";
|
||||||
|
|
||||||
@@ -27,7 +25,14 @@ static AcDimmerDataStore *all_dimmers[32]; // NOLINT(cppcoreguidelines-avoid-no
|
|||||||
/// However other factors like gate driver propagation time
|
/// However other factors like gate driver propagation time
|
||||||
/// are also considered and a really low value is not important
|
/// are also considered and a really low value is not important
|
||||||
/// See also: https://github.com/esphome/issues/issues/1632
|
/// See also: https://github.com/esphome/issues/issues/1632
|
||||||
static const uint32_t GATE_ENABLE_TIME = 50;
|
static constexpr uint32_t GATE_ENABLE_TIME = 50;
|
||||||
|
|
||||||
|
#ifdef USE_ESP32
|
||||||
|
/// Timer frequency in Hz (1 MHz = 1µs resolution)
|
||||||
|
static constexpr uint32_t TIMER_FREQUENCY_HZ = 1000000;
|
||||||
|
/// Timer interrupt interval in microseconds
|
||||||
|
static constexpr uint64_t TIMER_INTERVAL_US = 50;
|
||||||
|
#endif
|
||||||
|
|
||||||
/// Function called from timer interrupt
|
/// Function called from timer interrupt
|
||||||
/// Input is current time in microseconds (micros())
|
/// Input is current time in microseconds (micros())
|
||||||
@@ -154,7 +159,7 @@ void IRAM_ATTR HOT AcDimmerDataStore::s_gpio_intr(AcDimmerDataStore *store) {
|
|||||||
#ifdef USE_ESP32
|
#ifdef USE_ESP32
|
||||||
// ESP32 implementation, uses basically the same code but needs to wrap
|
// ESP32 implementation, uses basically the same code but needs to wrap
|
||||||
// timer_interrupt() function to auto-reschedule
|
// timer_interrupt() function to auto-reschedule
|
||||||
static hw_timer_t *dimmer_timer = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
static HWTimer *dimmer_timer = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||||
void IRAM_ATTR HOT AcDimmerDataStore::s_timer_intr() { timer_interrupt(); }
|
void IRAM_ATTR HOT AcDimmerDataStore::s_timer_intr() { timer_interrupt(); }
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -194,15 +199,15 @@ void AcDimmer::setup() {
|
|||||||
setTimer1Callback(&timer_interrupt);
|
setTimer1Callback(&timer_interrupt);
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_ESP32
|
#ifdef USE_ESP32
|
||||||
// timer frequency of 1mhz
|
dimmer_timer = timer_begin(TIMER_FREQUENCY_HZ);
|
||||||
dimmer_timer = timerBegin(1000000);
|
timer_attach_interrupt(dimmer_timer, &AcDimmerDataStore::s_timer_intr);
|
||||||
timerAttachInterrupt(dimmer_timer, &AcDimmerDataStore::s_timer_intr);
|
|
||||||
// For ESP32, we can't use dynamic interval calculation because the timerX functions
|
// For ESP32, we can't use dynamic interval calculation because the timerX functions
|
||||||
// are not callable from ISR (placed in flash storage).
|
// are not callable from ISR (placed in flash storage).
|
||||||
// Here we just use an interrupt firing every 50 µs.
|
// Here we just use an interrupt firing every 50 µs.
|
||||||
timerAlarm(dimmer_timer, 50, true, 0);
|
timer_alarm(dimmer_timer, TIMER_INTERVAL_US, true, 0);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void AcDimmer::write_state(float state) {
|
void AcDimmer::write_state(float state) {
|
||||||
state = std::acos(1 - (2 * state)) / std::numbers::pi; // RMS power compensation
|
state = std::acos(1 - (2 * state)) / std::numbers::pi; // RMS power compensation
|
||||||
auto new_value = static_cast<uint16_t>(roundf(state * 65535));
|
auto new_value = static_cast<uint16_t>(roundf(state * 65535));
|
||||||
@@ -210,6 +215,7 @@ void AcDimmer::write_state(float state) {
|
|||||||
this->store_.init_cycle = this->init_with_half_cycle_;
|
this->store_.init_cycle = this->init_with_half_cycle_;
|
||||||
this->store_.value = new_value;
|
this->store_.value = new_value;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AcDimmer::dump_config() {
|
void AcDimmer::dump_config() {
|
||||||
ESP_LOGCONFIG(TAG,
|
ESP_LOGCONFIG(TAG,
|
||||||
"AcDimmer:\n"
|
"AcDimmer:\n"
|
||||||
@@ -230,7 +236,4 @@ void AcDimmer::dump_config() {
|
|||||||
ESP_LOGV(TAG, " Estimated Frequency: %.3fHz", 1e6f / this->store_.cycle_time_us / 2);
|
ESP_LOGV(TAG, " Estimated Frequency: %.3fHz", 1e6f / this->store_.cycle_time_us / 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace ac_dimmer
|
} // namespace esphome::ac_dimmer
|
||||||
} // namespace esphome
|
|
||||||
|
|
||||||
#endif // USE_ARDUINO
|
|
||||||
|
|||||||
@@ -1,13 +1,10 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#ifdef USE_ARDUINO
|
|
||||||
|
|
||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
#include "esphome/core/hal.h"
|
#include "esphome/core/hal.h"
|
||||||
#include "esphome/components/output/float_output.h"
|
#include "esphome/components/output/float_output.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome::ac_dimmer {
|
||||||
namespace ac_dimmer {
|
|
||||||
|
|
||||||
enum DimMethod { DIM_METHOD_LEADING_PULSE = 0, DIM_METHOD_LEADING, DIM_METHOD_TRAILING };
|
enum DimMethod { DIM_METHOD_LEADING_PULSE = 0, DIM_METHOD_LEADING, DIM_METHOD_TRAILING };
|
||||||
|
|
||||||
@@ -64,7 +61,4 @@ class AcDimmer : public output::FloatOutput, public Component {
|
|||||||
DimMethod method_;
|
DimMethod method_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ac_dimmer
|
} // namespace esphome::ac_dimmer
|
||||||
} // namespace esphome
|
|
||||||
|
|
||||||
#endif // USE_ARDUINO
|
|
||||||
|
|||||||
152
esphome/components/ac_dimmer/hw_timer_esp_idf.cpp
Normal file
152
esphome/components/ac_dimmer/hw_timer_esp_idf.cpp
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
#ifdef USE_ESP32
|
||||||
|
|
||||||
|
#include "hw_timer_esp_idf.h"
|
||||||
|
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
#include "driver/gptimer.h"
|
||||||
|
#include "esp_clk_tree.h"
|
||||||
|
#include "soc/clk_tree_defs.h"
|
||||||
|
|
||||||
|
static const char *const TAG = "hw_timer_esp_idf";
|
||||||
|
|
||||||
|
namespace esphome::ac_dimmer {
|
||||||
|
|
||||||
|
// GPTimer divider constraints from ESP-IDF documentation
|
||||||
|
static constexpr uint32_t GPTIMER_DIVIDER_MIN = 2;
|
||||||
|
static constexpr uint32_t GPTIMER_DIVIDER_MAX = 65536;
|
||||||
|
|
||||||
|
using voidFuncPtr = void (*)();
|
||||||
|
using voidFuncPtrArg = void (*)(void *);
|
||||||
|
|
||||||
|
struct InterruptConfigT {
|
||||||
|
voidFuncPtr fn{nullptr};
|
||||||
|
void *arg{nullptr};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct HWTimer {
|
||||||
|
gptimer_handle_t timer_handle{nullptr};
|
||||||
|
InterruptConfigT interrupt_handle{};
|
||||||
|
bool timer_started{false};
|
||||||
|
};
|
||||||
|
|
||||||
|
HWTimer *timer_begin(uint32_t frequency) {
|
||||||
|
esp_err_t err = ESP_OK;
|
||||||
|
uint32_t counter_src_hz = 0;
|
||||||
|
uint32_t divider = 0;
|
||||||
|
soc_module_clk_t clk;
|
||||||
|
for (auto clk_candidate : SOC_GPTIMER_CLKS) {
|
||||||
|
clk = clk_candidate;
|
||||||
|
esp_clk_tree_src_get_freq_hz(clk, ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED, &counter_src_hz);
|
||||||
|
divider = counter_src_hz / frequency;
|
||||||
|
if ((divider >= GPTIMER_DIVIDER_MIN) && (divider <= GPTIMER_DIVIDER_MAX)) {
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
divider = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (divider == 0) {
|
||||||
|
ESP_LOGE(TAG, "Resolution not possible; aborting");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
gptimer_config_t config = {
|
||||||
|
.clk_src = static_cast<gptimer_clock_source_t>(clk),
|
||||||
|
.direction = GPTIMER_COUNT_UP,
|
||||||
|
.resolution_hz = frequency,
|
||||||
|
.flags = {.intr_shared = true},
|
||||||
|
};
|
||||||
|
|
||||||
|
HWTimer *timer = new HWTimer();
|
||||||
|
|
||||||
|
err = gptimer_new_timer(&config, &timer->timer_handle);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "GPTimer creation failed; error %d", err);
|
||||||
|
delete timer;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = gptimer_enable(timer->timer_handle);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "GPTimer enable failed; error %d", err);
|
||||||
|
gptimer_del_timer(timer->timer_handle);
|
||||||
|
delete timer;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = gptimer_start(timer->timer_handle);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "GPTimer start failed; error %d", err);
|
||||||
|
gptimer_disable(timer->timer_handle);
|
||||||
|
gptimer_del_timer(timer->timer_handle);
|
||||||
|
delete timer;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
timer->timer_started = true;
|
||||||
|
return timer;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IRAM_ATTR timer_fn_wrapper(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *args) {
|
||||||
|
auto *isr = static_cast<InterruptConfigT *>(args);
|
||||||
|
if (isr->fn) {
|
||||||
|
if (isr->arg) {
|
||||||
|
reinterpret_cast<voidFuncPtrArg>(isr->fn)(isr->arg);
|
||||||
|
} else {
|
||||||
|
isr->fn();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Return false to indicate that no higher-priority task was woken and no context switch is requested.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void timer_attach_interrupt_functional_arg(HWTimer *timer, void (*user_func)(void *), void *arg) {
|
||||||
|
if (timer == nullptr) {
|
||||||
|
ESP_LOGE(TAG, "Timer handle is nullptr");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
gptimer_event_callbacks_t cbs = {
|
||||||
|
.on_alarm = timer_fn_wrapper,
|
||||||
|
};
|
||||||
|
|
||||||
|
timer->interrupt_handle.fn = reinterpret_cast<voidFuncPtr>(user_func);
|
||||||
|
timer->interrupt_handle.arg = arg;
|
||||||
|
|
||||||
|
if (timer->timer_started) {
|
||||||
|
gptimer_stop(timer->timer_handle);
|
||||||
|
}
|
||||||
|
gptimer_disable(timer->timer_handle);
|
||||||
|
esp_err_t err = gptimer_register_event_callbacks(timer->timer_handle, &cbs, &timer->interrupt_handle);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "Timer Attach Interrupt failed; error %d", err);
|
||||||
|
}
|
||||||
|
gptimer_enable(timer->timer_handle);
|
||||||
|
if (timer->timer_started) {
|
||||||
|
gptimer_start(timer->timer_handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void timer_attach_interrupt(HWTimer *timer, voidFuncPtr user_func) {
|
||||||
|
timer_attach_interrupt_functional_arg(timer, reinterpret_cast<voidFuncPtrArg>(user_func), nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void timer_alarm(HWTimer *timer, uint64_t alarm_value, bool autoreload, uint64_t reload_count) {
|
||||||
|
if (timer == nullptr) {
|
||||||
|
ESP_LOGE(TAG, "Timer handle is nullptr");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
gptimer_alarm_config_t alarm_cfg = {
|
||||||
|
.alarm_count = alarm_value,
|
||||||
|
.reload_count = reload_count,
|
||||||
|
.flags = {.auto_reload_on_alarm = autoreload},
|
||||||
|
};
|
||||||
|
esp_err_t err = gptimer_set_alarm_action(timer->timer_handle, &alarm_cfg);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "Timer Alarm Write failed; error %d", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace esphome::ac_dimmer
|
||||||
|
#endif
|
||||||
17
esphome/components/ac_dimmer/hw_timer_esp_idf.h
Normal file
17
esphome/components/ac_dimmer/hw_timer_esp_idf.h
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
#pragma once
|
||||||
|
#ifdef USE_ESP32
|
||||||
|
|
||||||
|
#include "driver/gptimer_types.h"
|
||||||
|
|
||||||
|
namespace esphome::ac_dimmer {
|
||||||
|
|
||||||
|
struct HWTimer;
|
||||||
|
|
||||||
|
HWTimer *timer_begin(uint32_t frequency);
|
||||||
|
|
||||||
|
void timer_attach_interrupt(HWTimer *timer, void (*user_func)());
|
||||||
|
void timer_alarm(HWTimer *timer, uint64_t alarm_value, bool autoreload, uint64_t reload_count);
|
||||||
|
|
||||||
|
} // namespace esphome::ac_dimmer
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -32,7 +32,6 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
),
|
),
|
||||||
}
|
}
|
||||||
).extend(cv.COMPONENT_SCHEMA),
|
).extend(cv.COMPONENT_SCHEMA),
|
||||||
cv.only_with_arduino,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
substitutions:
|
|
||||||
gate_pin: GPIO4
|
|
||||||
zero_cross_pin: GPIO5
|
|
||||||
|
|
||||||
<<: !include common.yaml
|
|
||||||
5
tests/components/ac_dimmer/test.esp32-idf.yaml
Normal file
5
tests/components/ac_dimmer/test.esp32-idf.yaml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
substitutions:
|
||||||
|
gate_pin: GPIO18
|
||||||
|
zero_cross_pin: GPIO19
|
||||||
|
|
||||||
|
<<: !include common.yaml
|
||||||
Reference in New Issue
Block a user