From 594670e44623e0635ecf45eb34b0a43f27e04101 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Fri, 1 Nov 2024 15:57:40 +1100 Subject: [PATCH] esp32/machine_pwm: Restore PWM support for ESP-IDF v5.0.x and v5.1.x. The cleanup in 548babf8 relies on some functions not available in older ESP-IDF. Temporarily restore them, until we drop support for ESP-IDF <5.2. PWM functionality should end up the same regardless of ESP-IDF version, and also no different from MicroPython V1.23. Signed-off-by: Angus Gratton --- ports/esp32/machine_pwm.c | 52 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 2 deletions(-) diff --git a/ports/esp32/machine_pwm.c b/ports/esp32/machine_pwm.c index 61efdfa18548..0134fa2cc8c8 100644 --- a/ports/esp32/machine_pwm.c +++ b/ports/esp32/machine_pwm.c @@ -34,9 +34,12 @@ #include "py/mphal.h" #include "driver/ledc.h" #include "esp_err.h" -#include "esp_clk_tree.h" #include "soc/gpio_sig_map.h" +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 0) +#include "esp_clk_tree.h" +#endif + #define PWM_DBG(...) // #define PWM_DBG(...) mp_printf(&mp_plat_print, __VA_ARGS__); mp_printf(&mp_plat_print, "\n"); @@ -208,7 +211,41 @@ static void configure_channel(machine_pwm_obj_t *self) { } } +// Temporary workaround for ledc_find_suitable_duty_resolution function only being added in IDF V5.2 +#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 2, 0) +static uint32_t ledc_find_suitable_duty_resolution(uint32_t src_clk_freq, uint32_t timer_freq) { + // This implementation is based on the one used in Micropython v1.23 + + // Find the highest bit resolution for the requested frequency + unsigned int freq = src_clk_freq; + + int divider = (freq + timer_freq / 2) / timer_freq; // rounded + if (divider == 0) { + divider = 1; + } + float f = (float)freq / divider; // actual frequency + if (f <= 1.0) { + f = 1.0; + } + freq = (unsigned int)roundf((float)freq / f); + + unsigned int res = 0; + for (; freq > 1; freq >>= 1) { + ++res; + } + if (res == 0) { + res = 1; + } else if (res > HIGHEST_PWM_RES) { + // Limit resolution to HIGHEST_PWM_RES to match units of our duty + res = HIGHEST_PWM_RES; + } + + return res; +} +#endif + static void set_freq(machine_pwm_obj_t *self, unsigned int freq, ledc_timer_config_t *timer) { + esp_err_t err; if (freq != timer->freq_hz) { // Configure the new frequency and resolution timer->freq_hz = freq; @@ -228,10 +265,21 @@ static void set_freq(machine_pwm_obj_t *self, unsigned int freq, ledc_timer_conf } #endif uint32_t src_clk_freq = 0; - esp_err_t err = esp_clk_tree_src_get_freq_hz(timer->clk_cfg, ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED, &src_clk_freq); + #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 0) + err = esp_clk_tree_src_get_freq_hz(timer->clk_cfg, ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED, &src_clk_freq); if (err != ESP_OK) { mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("unable to query source clock frequency %d"), (int)timer->clk_cfg); } + #else + // Simplified fallback logic for IDF V5.0.x, for targets with APB only. + src_clk_freq = APB_CLK_FREQ; // 80 MHz + #if SOC_LEDC_SUPPORT_REF_TICK + if (timer->clk_cfg == LEDC_USE_REF_TICK) { + src_clk_freq = REF_CLK_FREQ; // 1 MHz + } + #endif // SOC_LEDC_SUPPORT_REF_TICK + #endif // ESP_IDF_VERSION + timer->duty_resolution = ledc_find_suitable_duty_resolution(src_clk_freq, timer->freq_hz); // Set frequency