diff --git a/components/driver/ledc/include/driver/ledc.h b/components/driver/ledc/include/driver/ledc.h index c0e2f14530..6cccf3276c 100644 --- a/components/driver/ledc/include/driver/ledc.h +++ b/components/driver/ledc/include/driver/ledc.h @@ -51,7 +51,7 @@ typedef struct { } ledc_channel_config_t; /** - * @brief Configuration parameters of LEDC Timer timer for ledc_timer_config function + * @brief Configuration parameters of LEDC timer for ledc_timer_config function */ typedef struct { ledc_mode_t speed_mode; /*!< LEDC speed speed_mode, high-speed mode or low-speed mode */ @@ -65,6 +65,11 @@ typedef struct { as its clock source. All chips except esp32 and esp32s2 do not have timer-specific clock sources, which means clock source for all timers must be the same one. */ + bool deconfigure; /*!< Set this field to de-configure a LEDC timer which has been configured before + Note that it will not check whether the timer wants to be de-configured + is binded to any channel. Also, the timer has to be paused first before + it can be de-configured. + When this field is set, duty_resolution, freq_hz, clk_cfg fields are ignored. */ } ledc_timer_config_t; typedef intr_handle_t ledc_isr_handle_t; @@ -124,6 +129,7 @@ esp_err_t ledc_channel_config(const ledc_channel_config_t *ledc_conf); * - ESP_OK Success * - ESP_ERR_INVALID_ARG Parameter error * - ESP_FAIL Can not find a proper pre-divider number base on the given frequency and the current duty_resolution. + * - ESP_ERR_INVALID_STATE Timer cannot be de-configured because timer is not configured or is not paused */ esp_err_t ledc_timer_config(const ledc_timer_config_t *timer_conf); diff --git a/components/driver/ledc/ledc.c b/components/driver/ledc/ledc.c index 929a35ea6d..000af6eec7 100644 --- a/components/driver/ledc/ledc.c +++ b/components/driver/ledc/ledc.c @@ -29,6 +29,7 @@ static __attribute__((unused)) const char *LEDC_TAG = "ledc"; #define LEDC_CLK_NOT_FOUND 0 #define LEDC_SLOW_CLK_UNINIT -1 +#define LEDC_TIMER_SPECIFIC_CLK_UNINIT -1 // Precision degree only affects RC_FAST, other clock sources' frequences are fixed values // For targets that do not support RC_FAST calibration, can only use its approx. value. Precision degree other than @@ -64,13 +65,20 @@ typedef struct { } ledc_fade_t; typedef struct { - ledc_hal_context_t ledc_hal; /*!< LEDC hal context*/ + ledc_hal_context_t ledc_hal; /*!< LEDC hal context */ + ledc_slow_clk_sel_t glb_clk; /*!< LEDC global clock selection */ + bool timer_is_stopped[LEDC_TIMER_MAX]; /*!< Indicates whether each timer has been stopped */ + bool glb_clk_is_acquired[LEDC_TIMER_MAX]; /*!< Tracks whether the global clock is being acquired by each timer */ +#if SOC_LEDC_HAS_TIMER_SPECIFIC_MUX + ledc_clk_src_t timer_specific_clk[LEDC_TIMER_MAX]; /*!< Tracks the timer-specific clock selection for each timer */ +#endif } ledc_obj_t; static ledc_obj_t *p_ledc_obj[LEDC_SPEED_MODE_MAX] = {0}; static ledc_fade_t *s_ledc_fade_rec[LEDC_SPEED_MODE_MAX][LEDC_CHANNEL_MAX]; static ledc_isr_handle_t s_ledc_fade_isr_handle = NULL; static portMUX_TYPE ledc_spinlock = portMUX_INITIALIZER_UNLOCKED; +static _lock_t s_ledc_mutex[LEDC_SPEED_MODE_MAX]; #define LEDC_VAL_NO_CHANGE (-1) #define LEDC_DUTY_NUM_MAX LEDC_LL_DUTY_NUM_MAX // Maximum steps per hardware fade @@ -186,9 +194,10 @@ esp_err_t ledc_timer_set(ledc_mode_t speed_mode, ledc_timer_t timer_sel, uint32_ portENTER_CRITICAL(&ledc_spinlock); ledc_hal_set_clock_divider(&(p_ledc_obj[speed_mode]->ledc_hal), timer_sel, clock_divider); #if SOC_LEDC_HAS_TIMER_SPECIFIC_MUX - /* Clock source can only be configured on boards which support timer-specific - * source clock. */ + /* Clock source can only be configured on targets which support timer-specific source clock. */ ledc_hal_set_clock_source(&(p_ledc_obj[speed_mode]->ledc_hal), timer_sel, clk_src); + // TODO: acquire clk_src, and release old clk_src if initialized and different than new one [clk_tree] + p_ledc_obj[speed_mode]->timer_specific_clk[timer_sel] = clk_src; #endif ledc_hal_set_duty_resolution(&(p_ledc_obj[speed_mode]->ledc_hal), timer_sel, duty_resolution); ledc_ls_timer_update(speed_mode, timer_sel); @@ -245,6 +254,7 @@ esp_err_t ledc_timer_pause(ledc_mode_t speed_mode, ledc_timer_t timer_sel) LEDC_ARG_CHECK(timer_sel < LEDC_TIMER_MAX, "timer_select"); LEDC_CHECK(p_ledc_obj[speed_mode] != NULL, LEDC_NOT_INIT, ESP_ERR_INVALID_STATE); portENTER_CRITICAL(&ledc_spinlock); + p_ledc_obj[speed_mode]->timer_is_stopped[timer_sel] = true; ledc_hal_timer_pause(&(p_ledc_obj[speed_mode]->ledc_hal), timer_sel); portEXIT_CRITICAL(&ledc_spinlock); return ESP_OK; @@ -256,6 +266,7 @@ esp_err_t ledc_timer_resume(ledc_mode_t speed_mode, ledc_timer_t timer_sel) LEDC_ARG_CHECK(timer_sel < LEDC_TIMER_MAX, "timer_select"); LEDC_CHECK(p_ledc_obj[speed_mode] != NULL, LEDC_NOT_INIT, ESP_ERR_INVALID_STATE); portENTER_CRITICAL(&ledc_spinlock); + p_ledc_obj[speed_mode]->timer_is_stopped[timer_sel] = false; ledc_hal_timer_resume(&(p_ledc_obj[speed_mode]->ledc_hal), timer_sel); portEXIT_CRITICAL(&ledc_spinlock); return ESP_OK; @@ -271,6 +282,31 @@ esp_err_t ledc_isr_register(void (*fn)(void *), void *arg, int intr_alloc_flags, return ret; } +static bool ledc_speed_mode_ctx_create(ledc_mode_t speed_mode) +{ + bool new_ctx = false; + + // Prevent p_ledc_obj malloc concurrently + _lock_acquire(&s_ledc_mutex[speed_mode]); + if (!p_ledc_obj[speed_mode]) { + ledc_obj_t *ledc_new_mode_obj = (ledc_obj_t *) heap_caps_calloc(1, sizeof(ledc_obj_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); + if (ledc_new_mode_obj) { + new_ctx = true; + ledc_hal_init(&(ledc_new_mode_obj->ledc_hal), speed_mode); + ledc_new_mode_obj->glb_clk = LEDC_SLOW_CLK_UNINIT; +#if SOC_LEDC_HAS_TIMER_SPECIFIC_MUX + memset(ledc_new_mode_obj->timer_specific_clk, LEDC_TIMER_SPECIFIC_CLK_UNINIT, sizeof(ledc_clk_src_t) * LEDC_TIMER_MAX); +#endif + p_ledc_obj[speed_mode] = ledc_new_mode_obj; + // Enable APB access to LEDC registers + periph_module_enable(PERIPH_LEDC_MODULE); + } + } + _lock_release(&s_ledc_mutex[speed_mode]); + + return new_ctx; +} + static inline uint32_t ledc_calculate_divisor(uint32_t src_clk_freq, int freq_hz, uint32_t precision) { /** @@ -501,16 +537,31 @@ static esp_err_t ledc_set_timer_div(ledc_mode_t speed_mode, ledc_timer_t timer_n #endif // Arriving here, variable glb_clk must have been assigned to one of the ledc_slow_clk_sel_t enum values assert(glb_clk != LEDC_SLOW_CLK_UNINIT); + + portENTER_CRITICAL(&ledc_spinlock); + if (p_ledc_obj[speed_mode]->glb_clk != LEDC_SLOW_CLK_UNINIT && p_ledc_obj[speed_mode]->glb_clk != glb_clk) { + for (int i = 0; i < LEDC_TIMER_MAX; i++) { + if (i != timer_num && p_ledc_obj[speed_mode]->glb_clk_is_acquired[i]) { + portEXIT_CRITICAL(&ledc_spinlock); + ESP_RETURN_ON_FALSE(false, ESP_FAIL, LEDC_TAG, + "timer clock conflict, already is %d but attempt to %d", p_ledc_obj[speed_mode]->glb_clk, glb_clk); + } + } + } + p_ledc_obj[speed_mode]->glb_clk_is_acquired[timer_num] = true; + if (p_ledc_obj[speed_mode]->glb_clk != glb_clk) { + // TODO: release old glb_clk (if not UNINIT), and acquire new glb_clk [clk_tree] + p_ledc_obj[speed_mode]->glb_clk = glb_clk; + ledc_hal_set_slow_clk_sel(&(p_ledc_obj[speed_mode]->ledc_hal), glb_clk); + } + portEXIT_CRITICAL(&ledc_spinlock); + ESP_LOGD(LEDC_TAG, "In slow speed mode, global clk set: %d", glb_clk); /* keep ESP_PD_DOMAIN_RC_FAST on during light sleep */ #if !CONFIG_IDF_TARGET_ESP32H2 // TODO: IDF-6267 Remove when H2 light sleep supported esp_sleep_periph_use_8m(glb_clk == LEDC_SLOW_CLK_RC_FAST); #endif - - portENTER_CRITICAL(&ledc_spinlock); - ledc_hal_set_slow_clk_sel(&(p_ledc_obj[speed_mode]->ledc_hal), glb_clk); - portEXIT_CRITICAL(&ledc_spinlock); } /* The divisor is correct, we can write in the hardware. */ @@ -522,6 +573,28 @@ error: return ESP_FAIL; } +static esp_err_t ledc_timer_del(ledc_mode_t speed_mode, ledc_timer_t timer_sel) +{ + LEDC_CHECK(p_ledc_obj[speed_mode] != NULL, LEDC_NOT_INIT, ESP_ERR_INVALID_STATE); + bool is_configured = true; + bool is_deleted = false; + portENTER_CRITICAL(&ledc_spinlock); + if (p_ledc_obj[speed_mode]->glb_clk_is_acquired[timer_sel] == false +#if SOC_LEDC_HAS_TIMER_SPECIFIC_MUX + && p_ledc_obj[speed_mode]->timer_specific_clk[timer_sel] == LEDC_TIMER_SPECIFIC_CLK_UNINIT +#endif + ) { + is_configured = false; + } else if (p_ledc_obj[speed_mode]->timer_is_stopped[timer_sel] == true) { + is_deleted = true; + p_ledc_obj[speed_mode]->glb_clk_is_acquired[timer_sel] = false; + // TODO: release timer specific clk and global clk if possible [clk_tree] + } + portEXIT_CRITICAL(&ledc_spinlock); + ESP_RETURN_ON_FALSE(is_configured && is_deleted, ESP_ERR_INVALID_STATE, LEDC_TAG, "timer hasn't been configured, or it is still running, please stop it with ledc_timer_pause first"); + return ESP_OK; +} + esp_err_t ledc_timer_config(const ledc_timer_config_t *timer_conf) { LEDC_ARG_CHECK(timer_conf != NULL, "timer_conf"); @@ -530,28 +603,24 @@ esp_err_t ledc_timer_config(const ledc_timer_config_t *timer_conf) uint32_t timer_num = timer_conf->timer_num; uint32_t speed_mode = timer_conf->speed_mode; LEDC_ARG_CHECK(speed_mode < LEDC_SPEED_MODE_MAX, "speed_mode"); + LEDC_ARG_CHECK(timer_num < LEDC_TIMER_MAX, "timer_num"); + if (timer_conf->deconfigure) { + return ledc_timer_del(speed_mode, timer_num); + } LEDC_ARG_CHECK(!((timer_conf->clk_cfg == LEDC_USE_RC_FAST_CLK) && (speed_mode != LEDC_LOW_SPEED_MODE)), "Only low speed channel support RC_FAST_CLK"); - periph_module_enable(PERIPH_LEDC_MODULE); if (freq_hz == 0 || duty_resolution == 0 || duty_resolution >= LEDC_TIMER_BIT_MAX) { ESP_LOGE(LEDC_TAG, "freq_hz=%"PRIu32" duty_resolution=%"PRIu32, freq_hz, duty_resolution); return ESP_ERR_INVALID_ARG; } - if (timer_num > LEDC_TIMER_3) { - ESP_LOGE(LEDC_TAG, "invalid timer #%"PRIu32, timer_num); - return ESP_ERR_INVALID_ARG; - } - if (p_ledc_obj[speed_mode] == NULL) { - p_ledc_obj[speed_mode] = (ledc_obj_t *) heap_caps_calloc(1, sizeof(ledc_obj_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); - if (p_ledc_obj[speed_mode] == NULL) { - return ESP_ERR_NO_MEM; - } - ledc_hal_init(&(p_ledc_obj[speed_mode]->ledc_hal), speed_mode); + if (!ledc_speed_mode_ctx_create(speed_mode) && !p_ledc_obj[speed_mode]) { + return ESP_ERR_NO_MEM; } esp_err_t ret = ledc_set_timer_div(speed_mode, timer_num, timer_conf->clk_cfg, freq_hz, duty_resolution); if (ret == ESP_OK) { - /* Reset the timer. */ + /* Make sure timer is running and reset the timer. */ + ledc_timer_resume(speed_mode, timer_num); ledc_timer_rst(speed_mode, timer_num); } return ret; @@ -585,23 +654,26 @@ esp_err_t ledc_channel_config(const ledc_channel_config_t *ledc_conf) LEDC_ARG_CHECK(timer_select < LEDC_TIMER_MAX, "timer_select"); LEDC_ARG_CHECK(intr_type < LEDC_INTR_MAX, "intr_type"); - periph_module_enable(PERIPH_LEDC_MODULE); esp_err_t ret = ESP_OK; - if (p_ledc_obj[speed_mode] == NULL) { - p_ledc_obj[speed_mode] = (ledc_obj_t *) heap_caps_calloc(1, sizeof(ledc_obj_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); - if (p_ledc_obj[speed_mode] == NULL) { - return ESP_ERR_NO_MEM; - } - ledc_hal_init(&(p_ledc_obj[speed_mode]->ledc_hal), speed_mode); -#if !(CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32H2) - // On such targets, the default ledc core(global) clock does not connect to any clock source - // Set channel configurations and update bits before core clock is on could lead to error - // Therefore, we should connect the core clock to a real clock source to make it on before any ledc register operation - // It can be switched to the other desired clock sources to meet the output pwm freq requirement later at timer configuration - ledc_hal_set_slow_clk_sel(&(p_ledc_obj[speed_mode]->ledc_hal), LEDC_LL_GLOBAL_CLK_DEFAULT); -#endif + bool new_speed_mode_ctx_created = ledc_speed_mode_ctx_create(speed_mode); + if (!new_speed_mode_ctx_created && !p_ledc_obj[speed_mode]) { + return ESP_ERR_NO_MEM; } +#if !(CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32H2) + // On such targets, the default ledc core(global) clock does not connect to any clock source + // Set channel configurations and update bits before core clock is on could lead to error + // Therefore, we should connect the core clock to a real clock source to make it on before any ledc register operation + // It can be switched to the other desired clock sources to meet the output pwm freq requirement later at timer configuration + // So we consider the glb_clk still as LEDC_SLOW_CLK_UNINIT + else if (new_speed_mode_ctx_created) { + portENTER_CRITICAL(&ledc_spinlock); + if (p_ledc_obj[speed_mode]->glb_clk == LEDC_SLOW_CLK_UNINIT) { + ledc_hal_set_slow_clk_sel(&(p_ledc_obj[speed_mode]->ledc_hal), LEDC_LL_GLOBAL_CLK_DEFAULT); + } + portEXIT_CRITICAL(&ledc_spinlock); + } +#endif /*set channel parameters*/ /* channel parameters decide how the waveform looks like in one period*/ diff --git a/components/driver/test_apps/ledc/main/test_ledc.c b/components/driver/test_apps/ledc/main/test_ledc.c index 3e3beee44a..9b72359bdb 100644 --- a/components/driver/test_apps/ledc/main/test_ledc.c +++ b/components/driver/test_apps/ledc/main/test_ledc.c @@ -546,8 +546,8 @@ static void timer_frequency_test(ledc_channel_t channel, ledc_timer_bit_t timer_ }; TEST_ESP_OK(ledc_channel_config(&ledc_ch_config)); TEST_ESP_OK(ledc_timer_config(&ledc_time_config)); - frequency_set_get(ledc_ch_config.speed_mode, ledc_ch_config.timer_sel, 100, 100, 20); - frequency_set_get(ledc_ch_config.speed_mode, ledc_ch_config.timer_sel, 5000, 5000, 50); + frequency_set_get(speed_mode, timer, 100, 100, 20); + frequency_set_get(speed_mode, timer, 5000, 5000, 50); // Try a frequency that couldn't be exactly achieved, requires rounding uint32_t theoretical_freq = 9000; uint32_t clk_src_freq = 0; @@ -557,7 +557,12 @@ static void timer_frequency_test(ledc_channel_t channel, ledc_timer_bit_t timer_ } else if (clk_src_freq == 96 * 1000 * 1000) { theoretical_freq = 9009; } - frequency_set_get(ledc_ch_config.speed_mode, ledc_ch_config.timer_sel, 9000, theoretical_freq, 50); + frequency_set_get(speed_mode, timer, 9000, theoretical_freq, 50); + + // Pause and de-configure the timer so that it won't affect the following test cases + TEST_ESP_OK(ledc_timer_pause(speed_mode, timer)); + ledc_time_config.deconfigure = 1; + TEST_ESP_OK(ledc_timer_config(&ledc_time_config)); } TEST_CASE("LEDC set and get frequency", "[ledc][timeout=60]") diff --git a/docs/en/api-reference/peripherals/ledc.rst b/docs/en/api-reference/peripherals/ledc.rst index c843e85e68..916b76d316 100644 --- a/docs/en/api-reference/peripherals/ledc.rst +++ b/docs/en/api-reference/peripherals/ledc.rst @@ -60,7 +60,7 @@ Setting the timer is done by calling the function :cpp:func:`ledc_timer_config` :esp32: - Speed mode :cpp:type:`ledc_mode_t` :not esp32: - Speed mode (value must be ``LEDC_LOW_SPEED_MODE``) - Timer number :cpp:type:`ledc_timer_t` - - PWM signal frequency + - PWM signal frequency in Hz - Resolution of PWM duty - Source clock :cpp:type:`ledc_clk_cfg_t` @@ -222,6 +222,14 @@ The source clock can also limit the PWM frequency. The higher the source clock f 2. For {IDF_TARGET_NAME}, all timers share one clock source. In other words, it is impossible to use different clock sources for different timers. +When a timer is no longer needed by any channel, it can be deconfigured by calling the same function :cpp:func:`ledc_timer_config`. The configuration structure :cpp:type:`ledc_timer_config_t` passes in should be: + +- :cpp:member:`ledc_timer_config_t::speed_mode` The speed mode of the timer which wants to be deconfigured belongs to (:cpp:type:`ledc_mode_t`) + +- :cpp:member:`ledc_timer_config_t::timer_num` The ID of the timers which wants to be deconfigured (:cpp:type:`ledc_timer_t`) + +- :cpp:member:`ledc_timer_config_t::deconfigure` Set this to true so that the timer specified can be deconfigured + .. _ledc-api-configure-channel: diff --git a/docs/zh_CN/api-reference/peripherals/ledc.rst b/docs/zh_CN/api-reference/peripherals/ledc.rst index 4782079b6b..ec552ea7db 100644 --- a/docs/zh_CN/api-reference/peripherals/ledc.rst +++ b/docs/zh_CN/api-reference/peripherals/ledc.rst @@ -60,7 +60,7 @@ LED PWM 控制器可在无需 CPU 干预的情况下自动改变占空比,实 :esp32: - 速度模式 :cpp:type:`ledc_mode_t` :not esp32: - 速度模式(值必须为 ``LEDC_LOW_SPEED_MODE``) - 定时器索引 :cpp:type:`ledc_timer_t` - - PWM 信号频率 + - PWM 信号频率(Hz) - PWM 占空比分辨率 - 时钟源 :cpp:type:`ledc_clk_cfg_t` @@ -222,6 +222,14 @@ LED PWM 控制器可在无需 CPU 干预的情况下自动改变占空比,实 2. {IDF_TARGET_NAME} 的所有定时器共用一个时钟源。因此 {IDF_TARGET_NAME} 不支持给不同的定时器配置不同的时钟源。 +当一个定时器不再被任何通道所需要时,可以通过调用相同的函数 :cpp:func:`ledc_timer_config` 来重置这个定时器。此时,函数入参的配置结构体需要指定: + +- :cpp:member:`ledc_timer_config_t::speed_mode` 重置定时器的所属速度模式 (:cpp:type:`ledc_mode_t`) + +- :cpp:member:`ledc_timer_config_t::timer_num` 重置定时器的索引 (:cpp:type:`ledc_timer_t`) + +- :cpp:member:`ledc_timer_config_t::deconfigure` 将指定定时器重置必须配置此项为 true + .. _ledc-api-configure-channel: