From 68e12f7092d4ab9f07108ed54c3c83e74f9ce73d Mon Sep 17 00:00:00 2001 From: songruojing Date: Mon, 10 Jan 2022 21:07:58 +0800 Subject: [PATCH] ledc: Add ledc_fade_stop API to support stopping the fade operation. Note that ESP32 cannot support this new feature. --- components/driver/include/driver/ledc.h | 36 ++- components/driver/ledc.c | 212 ++++++++++++++---- components/driver/test/test_ledc.c | 30 ++- .../esp32c2/include/soc/Kconfig.soc_caps.in | 4 + components/soc/esp32c2/include/soc/soc_caps.h | 1 + .../esp32c3/include/soc/Kconfig.soc_caps.in | 4 + components/soc/esp32c3/include/soc/soc_caps.h | 3 +- .../esp32h2/include/soc/Kconfig.soc_caps.in | 4 + components/soc/esp32h2/include/soc/soc_caps.h | 3 +- .../esp32s2/include/soc/Kconfig.soc_caps.in | 4 + components/soc/esp32s2/include/soc/soc_caps.h | 1 + .../esp32s3/include/soc/Kconfig.soc_caps.in | 4 + .../soc/esp32s3/include/soc/ledc_caps.h | 3 +- .../ledc_fade/main/ledc_fade_example_main.c | 3 +- 14 files changed, 249 insertions(+), 63 deletions(-) diff --git a/components/driver/include/driver/ledc.h b/components/driver/include/driver/ledc.h index 4471deba6b..0e231b249c 100644 --- a/components/driver/include/driver/ledc.h +++ b/components/driver/include/driver/ledc.h @@ -190,7 +190,7 @@ uint32_t ledc_get_freq(ledc_mode_t speed_mode, ledc_timer_t timer_num); * @note ledc_set_duty, ledc_set_duty_with_hpoint and ledc_update_duty are not thread-safe, do not call these functions to * control one LEDC channel in different tasks at the same time. * A thread-safe version of API is ledc_set_duty_and_update - * @note If a fade operation is running in progress on that channel, the driver would not allow it to be stopped. + * @note For ESP32, hardware does not support any duty change while a fade operation is running in progress on that channel. * Other duty operations will have to wait until the fade operation has finished. * @param speed_mode Select the LEDC channel group with specified speed mode. Note that not all targets support high speed mode. * @param channel LEDC channel (0 - LEDC_CHANNEL_MAX-1), select from ledc_channel_t @@ -221,7 +221,7 @@ int ledc_get_hpoint(ledc_mode_t speed_mode, ledc_channel_t channel); * @note ledc_set_duty, ledc_set_duty_with_hpoint and ledc_update_duty are not thread-safe, do not call these functions to * control one LEDC channel in different tasks at the same time. * A thread-safe version of API is ledc_set_duty_and_update. - * @note If a fade operation is running in progress on that channel, the driver would not allow it to be stopped. + * @note For ESP32, hardware does not support any duty change while a fade operation is running in progress on that channel. * Other duty operations will have to wait until the fade operation has finished. * @param speed_mode Select the LEDC channel group with specified speed mode. Note that not all targets support high speed mode. * @param channel LEDC channel (0 - LEDC_CHANNEL_MAX-1), select from ledc_channel_t @@ -251,7 +251,7 @@ uint32_t ledc_get_duty(ledc_mode_t speed_mode, ledc_channel_t channel); /** * @brief LEDC set gradient * Set LEDC gradient, After the function calls the ledc_update_duty function, the function can take effect. - * @note If a fade operation is running in progress on that channel, the driver would not allow it to be stopped. + * @note For ESP32, hardware does not support any duty change while a fade operation is running in progress on that channel. * Other duty operations will have to wait until the fade operation has finished. * @param speed_mode Select the LEDC channel group with specified speed mode. Note that not all targets support high speed mode. * @param channel LEDC channel (0 - LEDC_CHANNEL_MAX-1), select from ledc_channel_t @@ -357,7 +357,7 @@ esp_err_t ledc_bind_channel_timer(ledc_mode_t speed_mode, ledc_channel_t channel * @note ledc_set_fade_with_step, ledc_set_fade_with_time and ledc_fade_start are not thread-safe, do not call these functions to * control one LEDC channel in different tasks at the same time. * A thread-safe version of API is ledc_set_fade_step_and_start - * @note If a fade operation is running in progress on that channel, the driver would not allow it to be stopped. + * @note For ESP32, hardware does not support any duty change while a fade operation is running in progress on that channel. * Other duty operations will have to wait until the fade operation has finished. * @param speed_mode Select the LEDC channel group with specified speed mode. Note that not all targets support high speed mode. , * @param channel LEDC channel index (0 - LEDC_CHANNEL_MAX-1), select from ledc_channel_t @@ -380,7 +380,7 @@ esp_err_t ledc_set_fade_with_step(ledc_mode_t speed_mode, ledc_channel_t channel * @note ledc_set_fade_with_step, ledc_set_fade_with_time and ledc_fade_start are not thread-safe, do not call these functions to * control one LEDC channel in different tasks at the same time. * A thread-safe version of API is ledc_set_fade_step_and_start - * @note If a fade operation is running in progress on that channel, the driver would not allow it to be stopped. + * @note For ESP32, hardware does not support any duty change while a fade operation is running in progress on that channel. * Other duty operations will have to wait until the fade operation has finished. * @param speed_mode Select the LEDC channel group with specified speed mode. Note that not all targets support high speed mode. , * @param channel LEDC channel index (0 - LEDC_CHANNEL_MAX-1), select from ledc_channel_t @@ -416,7 +416,8 @@ void ledc_fade_func_uninstall(void); * @brief Start LEDC fading. * @note Call ledc_fade_func_install() once before calling this function. * Call this API right after ledc_set_fade_with_time or ledc_set_fade_with_step before to start fading. - * @note If a fade operation is running in progress on that channel, the driver would not allow it to be stopped. + * @note Starting fade operation with this API is not thread-safe, use with care. + * @note For ESP32, hardware does not support any duty change while a fade operation is running in progress on that channel. * Other duty operations will have to wait until the fade operation has finished. * @param speed_mode Select the LEDC channel group with specified speed mode. Note that not all targets support high speed mode. * @param channel LEDC channel number @@ -430,9 +431,26 @@ void ledc_fade_func_uninstall(void); */ esp_err_t ledc_fade_start(ledc_mode_t speed_mode, ledc_channel_t channel, ledc_fade_mode_t fade_mode); +#if SOC_LEDC_SUPPORT_FADE_STOP +/** + * @brief Stop LEDC fading. Duty of the channel will stay at its present vlaue. + * @note This API can be called if a new fixed duty or a new fade want to be set while the last fade operation is still running in progress. + * @note Call this API will abort the fading operation only if it was started by calling ledc_fade_start with LEDC_FADE_NO_WAIT mode. + * @note If a fade was started with LEDC_FADE_WAIT_DONE mode, calling this API afterwards is no use in stopping the fade. Fade will continue until it reachs the target duty. + * @param speed_mode Select the LEDC channel group with specified speed mode. Note that not all targets support high speed mode. + * @param channel LEDC channel number + * + * @return + * - ESP_OK Success + * - ESP_ERR_INVALID_STATE Fade function not installed. + * - ESP_ERR_INVALID_ARG Parameter error. + */ +esp_err_t ledc_fade_stop(ledc_mode_t speed_mode, ledc_channel_t channel); +#endif + /** * @brief A thread-safe API to set duty for LEDC channel and return when duty updated. - * @note If a fade operation is running in progress on that channel, the driver would not allow it to be stopped. + * @note For ESP32, hardware does not support any duty change while a fade operation is running in progress on that channel. * Other duty operations will have to wait until the fade operation has finished. * * @param speed_mode Select the LEDC channel group with specified speed mode. Note that not all targets support high speed mode. @@ -446,7 +464,7 @@ esp_err_t ledc_set_duty_and_update(ledc_mode_t speed_mode, ledc_channel_t channe /** * @brief A thread-safe API to set and start LEDC fade function, with a limited time. * @note Call ledc_fade_func_install() once, before calling this function. - * @note If a fade operation is running in progress on that channel, the driver would not allow it to be stopped. + * @note For ESP32, hardware does not support any duty change while a fade operation is running in progress on that channel. * Other duty operations will have to wait until the fade operation has finished. * @param speed_mode Select the LEDC channel group with specified speed mode. Note that not all targets support high speed mode. * @param channel LEDC channel index (0 - LEDC_CHANNEL_MAX-1), select from ledc_channel_t @@ -464,7 +482,7 @@ esp_err_t ledc_set_fade_time_and_start(ledc_mode_t speed_mode, ledc_channel_t ch /** * @brief A thread-safe API to set and start LEDC fade function. * @note Call ledc_fade_func_install() once before calling this function. - * @note If a fade operation is running in progress on that channel, the driver would not allow it to be stopped. + * @note For ESP32, hardware does not support any duty change while a fade operation is running in progress on that channel. * Other duty operations will have to wait until the fade operation has finished. * @param speed_mode Select the LEDC channel group with specified speed mode. Note that not all targets support high speed mode. * @param channel LEDC channel index (0 - LEDC_CHANNEL_MAX-1), select from ledc_channel_t diff --git a/components/driver/ledc.c b/components/driver/ledc.c index caa80a4659..332b7841b9 100644 --- a/components/driver/ledc.c +++ b/components/driver/ledc.c @@ -26,6 +26,13 @@ static __attribute__((unused)) const char *LEDC_TAG = "ledc"; #define LEDC_CHECK(a, str, ret_val) ESP_RETURN_ON_FALSE(a, ret_val, LEDC_TAG, "%s", str) #define LEDC_ARG_CHECK(a, param) ESP_RETURN_ON_FALSE(a, ESP_ERR_INVALID_ARG, LEDC_TAG, param " argument is invalid") +typedef enum { + LEDC_FSM_IDLE, + LEDC_FSM_HW_FADE, + LEDC_FSM_ISR_CAL, + LEDC_FSM_KILLED_PENDING, +} ledc_fade_fsm_t; + typedef struct { ledc_mode_t speed_mode; ledc_duty_direction_t direction; @@ -40,6 +47,7 @@ typedef struct { #endif ledc_cb_t ledc_fade_callback; void *cb_user_arg; + volatile ledc_fade_fsm_t fsm; } ledc_fade_t; typedef struct { @@ -143,13 +151,11 @@ static uint32_t ledc_get_glb_clk_freq(ledc_slow_clk_sel_t clk_cfg) static esp_err_t ledc_enable_intr_type(ledc_mode_t speed_mode, ledc_channel_t channel, ledc_intr_type_t type) { - portENTER_CRITICAL(&ledc_spinlock); if (type == LEDC_INTR_FADE_END) { ledc_hal_set_fade_end_intr(&(p_ledc_obj[speed_mode]->ledc_hal), channel, true); } else { ledc_hal_set_fade_end_intr(&(p_ledc_obj[speed_mode]->ledc_hal), channel, false); } - portEXIT_CRITICAL(&ledc_spinlock); return ESP_OK; } @@ -158,7 +164,9 @@ static void _ledc_fade_hw_acquire(ledc_mode_t mode, ledc_channel_t channel) ledc_fade_t *fade = s_ledc_fade_rec[mode][channel]; if (fade) { xSemaphoreTake(fade->ledc_fade_sem, portMAX_DELAY); + portENTER_CRITICAL(&ledc_spinlock); ledc_enable_intr_type(mode, channel, LEDC_INTR_DISABLE); + portEXIT_CRITICAL(&ledc_spinlock); } } @@ -213,10 +221,9 @@ esp_err_t ledc_timer_set(ledc_mode_t speed_mode, ledc_timer_t timer_sel, uint32_ return ESP_OK; } -static IRAM_ATTR esp_err_t ledc_duty_config(ledc_mode_t speed_mode, ledc_channel_t channel, int hpoint_val, int duty_val, - ledc_duty_direction_t duty_direction, uint32_t duty_num, uint32_t duty_cycle, uint32_t duty_scale) +static IRAM_ATTR esp_err_t ledc_duty_config(ledc_mode_t speed_mode, ledc_channel_t channel, int hpoint_val, +int duty_val, ledc_duty_direction_t duty_direction, uint32_t duty_num, uint32_t duty_cycle, uint32_t duty_scale) { - portENTER_CRITICAL_SAFE(&ledc_spinlock); if (hpoint_val >= 0) { ledc_hal_set_hpoint(&(p_ledc_obj[speed_mode]->ledc_hal), channel, hpoint_val); } @@ -228,7 +235,6 @@ static IRAM_ATTR esp_err_t ledc_duty_config(ledc_mode_t speed_mode, ledc_channel ledc_hal_set_duty_cycle(&(p_ledc_obj[speed_mode]->ledc_hal), channel, duty_cycle); ledc_hal_set_duty_scale(&(p_ledc_obj[speed_mode]->ledc_hal), channel, duty_scale); ledc_ls_channel_update(speed_mode, channel); - portEXIT_CRITICAL_SAFE(&ledc_spinlock); return ESP_OK; } @@ -624,7 +630,9 @@ esp_err_t ledc_channel_config(const ledc_channel_config_t *ledc_conf) /*bind the channel with the timer*/ ledc_bind_channel_timer(speed_mode, ledc_channel, timer_select); /*set interrupt type*/ + portENTER_CRITICAL(&ledc_spinlock); ledc_enable_intr_type(speed_mode, ledc_channel, intr_type); + portEXIT_CRITICAL(&ledc_spinlock); ESP_LOGD(LEDC_TAG, "LEDC_PWM CHANNEL %1u|GPIO %02u|Duty %04u|Time %01u", ledc_channel, gpio_num, duty, timer_select ); @@ -636,15 +644,20 @@ esp_err_t ledc_channel_config(const ledc_channel_config_t *ledc_conf) return ret; } +static void _ledc_update_duty(ledc_mode_t speed_mode, ledc_channel_t channel) +{ + ledc_hal_set_sig_out_en(&(p_ledc_obj[speed_mode]->ledc_hal), channel, true); + ledc_hal_set_duty_start(&(p_ledc_obj[speed_mode]->ledc_hal), channel, true); + ledc_ls_channel_update(speed_mode, channel); +} + esp_err_t ledc_update_duty(ledc_mode_t speed_mode, ledc_channel_t channel) { LEDC_ARG_CHECK(speed_mode < LEDC_SPEED_MODE_MAX, "speed_mode"); LEDC_ARG_CHECK(channel < LEDC_CHANNEL_MAX, "channel"); LEDC_CHECK(p_ledc_obj[speed_mode] != NULL, LEDC_NOT_INIT, ESP_ERR_INVALID_STATE); portENTER_CRITICAL(&ledc_spinlock); - ledc_hal_set_sig_out_en(&(p_ledc_obj[speed_mode]->ledc_hal), channel, true); - ledc_hal_set_duty_start(&(p_ledc_obj[speed_mode]->ledc_hal), channel, true); - ledc_ls_channel_update(speed_mode, channel); + _ledc_update_duty(speed_mode, channel); portEXIT_CRITICAL(&ledc_spinlock); return ESP_OK; } @@ -674,6 +687,7 @@ esp_err_t ledc_set_fade(ledc_mode_t speed_mode, ledc_channel_t channel, uint32_t LEDC_ARG_CHECK(duty_scale <= LEDC_DUTY_SCALE_MAX, "duty_scale"); LEDC_CHECK(p_ledc_obj[speed_mode] != NULL, LEDC_NOT_INIT, ESP_ERR_INVALID_STATE); _ledc_fade_hw_acquire(speed_mode, channel); + portENTER_CRITICAL(&ledc_spinlock); ledc_duty_config(speed_mode, channel, //uint32_t chan_num, LEDC_VAL_NO_CHANGE, @@ -683,6 +697,7 @@ esp_err_t ledc_set_fade(ledc_mode_t speed_mode, ledc_channel_t channel, uint32_t duty_cyle_num, //uint32_t duty_cycle, duty_scale //uint32_t duty_scale ); + portEXIT_CRITICAL(&ledc_spinlock); _ledc_fade_hw_release(speed_mode, channel); return ESP_OK; } @@ -695,6 +710,7 @@ esp_err_t ledc_set_duty_with_hpoint(ledc_mode_t speed_mode, ledc_channel_t chann LEDC_CHECK(p_ledc_obj[speed_mode] != NULL, LEDC_NOT_INIT, ESP_ERR_INVALID_STATE); /* The channel configuration should not be changed before the fade operation is done. */ _ledc_fade_hw_acquire(speed_mode, channel); + portENTER_CRITICAL(&ledc_spinlock); ledc_duty_config(speed_mode, channel, //uint32_t chan_num, hpoint, //uint32_t hpoint_val, @@ -704,6 +720,7 @@ esp_err_t ledc_set_duty_with_hpoint(ledc_mode_t speed_mode, ledc_channel_t chann 0, //uint32_t duty_cycle, 0 //uint32_t duty_scale ); + portEXIT_CRITICAL(&ledc_spinlock); _ledc_fade_hw_release(speed_mode, channel); return ESP_OK; } @@ -715,6 +732,7 @@ esp_err_t ledc_set_duty(ledc_mode_t speed_mode, ledc_channel_t channel, uint32_t LEDC_CHECK(p_ledc_obj[speed_mode] != NULL, LEDC_NOT_INIT, ESP_ERR_INVALID_STATE); /* The channel configuration should not be changed before the fade operation is done. */ _ledc_fade_hw_acquire(speed_mode, channel); + portENTER_CRITICAL(&ledc_spinlock); ledc_duty_config(speed_mode, channel, //uint32_t chan_num, LEDC_VAL_NO_CHANGE, @@ -724,6 +742,7 @@ esp_err_t ledc_set_duty(ledc_mode_t speed_mode, ledc_channel_t channel, uint32_t 0, //uint32_t duty_cycle, 0 //uint32_t duty_scale ); + portEXIT_CRITICAL(&ledc_spinlock); _ledc_fade_hw_release(speed_mode, channel); return ESP_OK; } @@ -792,6 +811,7 @@ void IRAM_ATTR ledc_fade_isr(void *arg) uint32_t speed_mode = 0; uint32_t channel = 0; uint32_t intr_status = 0; + ledc_fade_fsm_t state; for (speed_mode = 0; speed_mode < LEDC_SPEED_MODE_MAX; speed_mode++) { if (p_ledc_obj[speed_mode] == NULL) { @@ -802,61 +822,92 @@ void IRAM_ATTR ledc_fade_isr(void *arg) ledc_calc_fade_end_channel(&intr_status, &channel); // clear interrupt - portENTER_CRITICAL_ISR(&ledc_spinlock); ledc_hal_clear_fade_end_intr_status(&(p_ledc_obj[speed_mode]->ledc_hal), channel); - portEXIT_CRITICAL_ISR(&ledc_spinlock); if (s_ledc_fade_rec[speed_mode][channel] == NULL) { //fade object not initialized yet. continue; } + // Switch fade state to ISR_CAL if current state is HW_FADE + bool already_stopped = false; + portENTER_CRITICAL_ISR(&ledc_spinlock); + state = s_ledc_fade_rec[speed_mode][channel]->fsm; + assert(state != LEDC_FSM_ISR_CAL && state != LEDC_FSM_KILLED_PENDING); + if (state == LEDC_FSM_HW_FADE) { + s_ledc_fade_rec[speed_mode][channel]->fsm = LEDC_FSM_ISR_CAL; + } else if (state == LEDC_FSM_IDLE) { + // interrupt seen, but has already been stopped by task + already_stopped = true; + } + portEXIT_CRITICAL_ISR(&ledc_spinlock); + if (already_stopped) { + continue; + } + + bool set_to_idle = false; + int cycle = 0; + int delta = 0; + int step = 0; + int next_duty = 0; uint32_t duty_cur = 0; ledc_hal_get_duty(&(p_ledc_obj[speed_mode]->ledc_hal), channel, &duty_cur); uint32_t duty_tar = s_ledc_fade_rec[speed_mode][channel]->target_duty; int scale = s_ledc_fade_rec[speed_mode][channel]->scale; if (duty_cur == duty_tar || scale == 0) { - xSemaphoreGiveFromISR(s_ledc_fade_rec[speed_mode][channel]->ledc_fade_sem, &HPTaskAwoken); + // Target duty has reached + set_to_idle = true; + } else { + // Calculate new duty config parameters + delta = (s_ledc_fade_rec[speed_mode][channel]->direction == LEDC_DUTY_DIR_DECREASE) ? + (duty_cur - duty_tar) : (duty_tar - duty_cur); + if (delta > scale) { + next_duty = duty_cur; + step = (delta / scale > LEDC_STEP_NUM_MAX) ? LEDC_STEP_NUM_MAX : (delta / scale); + cycle = s_ledc_fade_rec[speed_mode][channel]->cycle_num; + } else { + next_duty = duty_tar; + step = 1; + cycle = 1; + scale = 0; + } + } - ledc_cb_param_t param = { - .event = LEDC_FADE_END_EVT, - .speed_mode = speed_mode, - .channel = channel, - .duty = duty_cur - }; + bool finished = false; + portENTER_CRITICAL_ISR(&ledc_spinlock); + state = s_ledc_fade_rec[speed_mode][channel]->fsm; + assert(state != LEDC_FSM_IDLE && state != LEDC_FSM_HW_FADE); + if (set_to_idle || state == LEDC_FSM_KILLED_PENDING) { + // Either fade has completed or has been killed, skip HW duty config + finished = true; + s_ledc_fade_rec[speed_mode][channel]->fsm = LEDC_FSM_IDLE; + } else if (state == LEDC_FSM_ISR_CAL) { + // Loading new fade to start + ledc_duty_config(speed_mode, + channel, + LEDC_VAL_NO_CHANGE, + next_duty, + s_ledc_fade_rec[speed_mode][channel]->direction, + step, + cycle, + scale); + s_ledc_fade_rec[speed_mode][channel]->fsm = LEDC_FSM_HW_FADE; + ledc_hal_set_duty_start(&(p_ledc_obj[speed_mode]->ledc_hal), channel, true); + } + portEXIT_CRITICAL_ISR(&ledc_spinlock); + if (finished) { + xSemaphoreGiveFromISR(s_ledc_fade_rec[speed_mode][channel]->ledc_fade_sem, &HPTaskAwoken); ledc_cb_t fade_cb = s_ledc_fade_rec[speed_mode][channel]->ledc_fade_callback; if (fade_cb) { + ledc_cb_param_t param = { + .event = LEDC_FADE_END_EVT, + .speed_mode = speed_mode, + .channel = channel, + .duty = duty_cur + }; cb_yield |= fade_cb(¶m, s_ledc_fade_rec[speed_mode][channel]->cb_user_arg); } - continue; } - int cycle = s_ledc_fade_rec[speed_mode][channel]->cycle_num; - int delta = s_ledc_fade_rec[speed_mode][channel]->direction == LEDC_DUTY_DIR_DECREASE ? duty_cur - duty_tar : duty_tar - duty_cur; - int step = delta / scale > LEDC_STEP_NUM_MAX ? LEDC_STEP_NUM_MAX : delta / scale; - if (delta > scale) { - ledc_duty_config( - speed_mode, - channel, - LEDC_VAL_NO_CHANGE, - duty_cur, - s_ledc_fade_rec[speed_mode][channel]->direction, - step, - cycle, - scale); - } else { - ledc_duty_config( - speed_mode, - channel, - LEDC_VAL_NO_CHANGE, - duty_tar, - s_ledc_fade_rec[speed_mode][channel]->direction, - 1, - 1, - 0); - } - portENTER_CRITICAL_ISR(&ledc_spinlock); - ledc_hal_set_duty_start(&(p_ledc_obj[speed_mode]->ledc_hal), channel, true); - portEXIT_CRITICAL_ISR(&ledc_spinlock); } } if (HPTaskAwoken == pdTRUE || cb_yield) { @@ -903,6 +954,7 @@ static esp_err_t ledc_fade_channel_init_check(ledc_mode_t speed_mode, ledc_chann #endif s_ledc_fade_rec[speed_mode][channel]->ledc_fade_mux = xSemaphoreCreateMutex(); xSemaphoreGive(s_ledc_fade_rec[speed_mode][channel]->ledc_fade_sem); + s_ledc_fade_rec[speed_mode][channel]->fsm = LEDC_FSM_IDLE; } if (s_ledc_fade_rec[speed_mode][channel] && s_ledc_fade_rec[speed_mode][channel]->ledc_fade_mux @@ -944,11 +996,15 @@ static esp_err_t _ledc_set_fade_with_step(ledc_mode_t speed_mode, ledc_channel_t portEXIT_CRITICAL(&ledc_spinlock); if (scale > 0 && step_num > 0) { + portENTER_CRITICAL(&ledc_spinlock); ledc_duty_config(speed_mode, channel, LEDC_VAL_NO_CHANGE, duty_cur, dir, step_num, cycle_num, scale); + portEXIT_CRITICAL(&ledc_spinlock); ESP_LOGD(LEDC_TAG, "cur duty: %d; target: %d, step: %d, cycle: %d; scale: %d; dir: %d\n", duty_cur, target_duty, step_num, cycle_num, scale, dir); } else { + portENTER_CRITICAL(&ledc_spinlock); ledc_duty_config(speed_mode, channel, LEDC_VAL_NO_CHANGE, target_duty, dir, 0, 1, 0); + portEXIT_CRITICAL(&ledc_spinlock); ESP_LOGD(LEDC_TAG, "Set to target duty: %d", target_duty); } return ESP_OK; @@ -992,11 +1048,18 @@ static esp_err_t _ledc_set_fade_with_time(ledc_mode_t speed_mode, ledc_channel_t static void _ledc_fade_start(ledc_mode_t speed_mode, ledc_channel_t channel, ledc_fade_mode_t fade_mode) { - s_ledc_fade_rec[speed_mode][channel]->mode = fade_mode; + ledc_fade_t *fade = s_ledc_fade_rec[speed_mode][channel]; + fade->mode = fade_mode; // Clear interrupt status of channel ledc_hal_clear_fade_end_intr_status(&(p_ledc_obj[speed_mode]->ledc_hal), channel); // Enable interrupt for channel + portENTER_CRITICAL(&ledc_spinlock); ledc_enable_intr_type(speed_mode, channel, LEDC_INTR_FADE_END); + // Set fade state to HW_FADE state for starting the fade + assert(fade->fsm == LEDC_FSM_IDLE); + fade->fsm = LEDC_FSM_HW_FADE; + portEXIT_CRITICAL(&ledc_spinlock); + // Trigger the fade ledc_update_duty(speed_mode, channel); if (fade_mode == LEDC_FADE_WAIT_DONE) { // Waiting for fade done @@ -1047,6 +1110,57 @@ esp_err_t ledc_fade_start(ledc_mode_t speed_mode, ledc_channel_t channel, ledc_f return ESP_OK; } +// ESP32 does not support this functionality, fade cannot be overwritten with new duty config +#if SOC_LEDC_SUPPORT_FADE_STOP +esp_err_t ledc_fade_stop(ledc_mode_t speed_mode, ledc_channel_t channel) +{ + LEDC_ARG_CHECK(speed_mode < LEDC_SPEED_MODE_MAX, "speed_mode"); + LEDC_ARG_CHECK(channel < LEDC_CHANNEL_MAX, "channel"); + LEDC_CHECK(p_ledc_obj[speed_mode] != NULL, LEDC_NOT_INIT, ESP_ERR_INVALID_STATE); + LEDC_CHECK(ledc_fade_channel_init_check(speed_mode, channel) == ESP_OK , LEDC_FADE_INIT_ERROR_STR, ESP_FAIL); + ledc_fade_t *fade = s_ledc_fade_rec[speed_mode][channel]; + ledc_fade_fsm_t state = fade->fsm; + bool wait_for_idle = false; + assert(state != LEDC_FSM_KILLED_PENDING); + if (state == LEDC_FSM_IDLE) { + // if there is no fade going on, do nothing + return ESP_OK; + } + // Fade state is either HW_FADE or ISR_CAL (there is a fade in process) + portENTER_CRITICAL(&ledc_spinlock); + // Disable ledc channel interrupt first + ledc_enable_intr_type(speed_mode, channel, LEDC_INTR_DISABLE); + // Config duty to the duty cycle at this moment + uint32_t duty_cur = ledc_get_duty(speed_mode, channel); + ledc_duty_config(speed_mode, + channel, //uint32_t chan_num, + LEDC_VAL_NO_CHANGE, + duty_cur, //uint32_t duty_val, + 1, //uint32_t increase, + 0, //uint32_t duty_num, + 0, //uint32_t duty_cycle, + 0 //uint32_t duty_scale + ); + _ledc_update_duty(speed_mode, channel); + state = fade->fsm; + assert(state != LEDC_FSM_IDLE && state != LEDC_FSM_KILLED_PENDING); + if (state == LEDC_FSM_HW_FADE) { + fade->fsm = LEDC_FSM_IDLE; + } else if (state == LEDC_FSM_ISR_CAL) { + fade->fsm = LEDC_FSM_KILLED_PENDING; + wait_for_idle = true; + } + portEXIT_CRITICAL(&ledc_spinlock); + if (wait_for_idle) { + // Wait for ISR return, which gives the semaphore and switchs state to IDLE + _ledc_fade_hw_acquire(speed_mode, channel); + assert(fade->fsm == LEDC_FSM_IDLE); + } + _ledc_fade_hw_release(speed_mode, channel); + return ESP_OK; +} +#endif + esp_err_t ledc_fade_func_install(int intr_alloc_flags) { //OR intr_alloc_flags with ESP_INTR_FLAG_IRAM because the fade isr is in IRAM @@ -1092,8 +1206,10 @@ esp_err_t ledc_set_duty_and_update(ledc_mode_t speed_mode, ledc_channel_t channe LEDC_CHECK(p_ledc_obj[speed_mode] != NULL, LEDC_NOT_INIT, ESP_ERR_INVALID_STATE); LEDC_CHECK(ledc_fade_channel_init_check(speed_mode, channel) == ESP_OK, LEDC_FADE_INIT_ERROR_STR, ESP_FAIL); _ledc_fade_hw_acquire(speed_mode, channel); + portENTER_CRITICAL(&ledc_spinlock); ledc_duty_config(speed_mode, channel, hpoint, duty, 1, 0, 0, 0); - ledc_update_duty(speed_mode, channel); + _ledc_update_duty(speed_mode, channel); + portEXIT_CRITICAL(&ledc_spinlock); _ledc_fade_hw_release(speed_mode, channel); return ESP_OK; } diff --git a/components/driver/test/test_ledc.c b/components/driver/test/test_ledc.c index f0a4ab21ce..2134d27d6c 100644 --- a/components/driver/test/test_ledc.c +++ b/components/driver/test/test_ledc.c @@ -184,7 +184,7 @@ static void timer_frequency_test(ledc_channel_t channel, ledc_timer_bit_t timer_ TEST_ESP_OK(ledc_timer_config(&ledc_time_config)); frequency_set_get(ledc_ch_config.speed_mode, ledc_ch_config.timer_sel, 100, 100, 2); frequency_set_get(ledc_ch_config.speed_mode, ledc_ch_config.timer_sel, 5000, 5000, 5); - frequency_set_get(ledc_ch_config.speed_mode, ledc_ch_config.timer_sel, 9000, 9025, 5); + frequency_set_get(ledc_ch_config.speed_mode, ledc_ch_config.timer_sel, 9000, 8993, 5); } #endif // !TEMPORARY_DISABLED_FOR_TARGETS(ESP32S2, ESP32S3) @@ -439,6 +439,34 @@ TEST_CASE("LEDC fast switching duty with fade_no_wait", "[ledc]") ledc_fade_func_uninstall(); } +#if SOC_LEDC_SUPPORT_FADE_STOP +TEST_CASE("LEDC fade stop test", "[ledc]") +{ + const ledc_mode_t test_speed_mode = TEST_SPEED_MODE; + fade_setup(); + + // Overwrite the last fade with new fade + int64_t fade_start, fade_stop; + int time_ms = 0; + fade_start = esp_timer_get_time(); + TEST_ESP_OK(ledc_set_fade_with_time(test_speed_mode, LEDC_CHANNEL_0, 4000, 500)); + TEST_ESP_OK(ledc_fade_start(test_speed_mode, LEDC_CHANNEL_0, LEDC_FADE_NO_WAIT)); + // Add some delay before stopping the fade + vTaskDelay(127 / portTICK_RATE_MS); + uint32_t duty_at_stop = ledc_get_duty(test_speed_mode, LEDC_CHANNEL_0); + TEST_ESP_OK(ledc_fade_stop(test_speed_mode, LEDC_CHANNEL_0)); + fade_stop = esp_timer_get_time(); + time_ms = (fade_stop - fade_start) / 1000; + TEST_ASSERT_TRUE(fabs(time_ms - 127) < 20); + vTaskDelay(300 / portTICK_RATE_MS); + TEST_ASSERT_EQUAL_INT32(duty_at_stop, ledc_get_duty(test_speed_mode, LEDC_CHANNEL_0)); + TEST_ASSERT_NOT_EQUAL(4000, duty_at_stop); + + //deinitialize fade service + ledc_fade_func_uninstall(); +} +#endif // SOC_LEDC_SUPPORT_FADE_STOP + #if SOC_PCNT_SUPPORTED #if !TEMPORARY_DISABLED_FOR_TARGETS(ESP32S2, ESP32S3) diff --git a/components/soc/esp32c2/include/soc/Kconfig.soc_caps.in b/components/soc/esp32c2/include/soc/Kconfig.soc_caps.in index 8969ec7393..0640536ab6 100644 --- a/components/soc/esp32c2/include/soc/Kconfig.soc_caps.in +++ b/components/soc/esp32c2/include/soc/Kconfig.soc_caps.in @@ -251,6 +251,10 @@ config SOC_LEDC_TIMER_BIT_WIDE_NUM int default 14 +config SOC_LEDC_SUPPORT_FADE_STOP + bool + default y + config SOC_MPU_CONFIGURABLE_REGIONS_SUPPORTED bool default n diff --git a/components/soc/esp32c2/include/soc/soc_caps.h b/components/soc/esp32c2/include/soc/soc_caps.h index 37e94fe626..40ba64be7c 100644 --- a/components/soc/esp32c2/include/soc/soc_caps.h +++ b/components/soc/esp32c2/include/soc/soc_caps.h @@ -141,6 +141,7 @@ #define SOC_LEDC_SUPPORT_XTAL_CLOCK (1) #define SOC_LEDC_CHANNEL_NUM (6) #define SOC_LEDC_TIMER_BIT_WIDE_NUM (14) +#define SOC_LEDC_SUPPORT_FADE_STOP (1) /*-------------------------- MPU CAPS ----------------------------------------*/ #define SOC_MPU_CONFIGURABLE_REGIONS_SUPPORTED 0 diff --git a/components/soc/esp32c3/include/soc/Kconfig.soc_caps.in b/components/soc/esp32c3/include/soc/Kconfig.soc_caps.in index b9f473ddc3..b7d7dc0be9 100644 --- a/components/soc/esp32c3/include/soc/Kconfig.soc_caps.in +++ b/components/soc/esp32c3/include/soc/Kconfig.soc_caps.in @@ -319,6 +319,10 @@ config SOC_LEDC_TIMER_BIT_WIDE_NUM int default 14 +config SOC_LEDC_SUPPORT_FADE_STOP + bool + default y + config SOC_MPU_CONFIGURABLE_REGIONS_SUPPORTED bool default n diff --git a/components/soc/esp32c3/include/soc/soc_caps.h b/components/soc/esp32c3/include/soc/soc_caps.h index f3395bb06b..b22b2690c2 100644 --- a/components/soc/esp32c3/include/soc/soc_caps.h +++ b/components/soc/esp32c3/include/soc/soc_caps.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -162,6 +162,7 @@ #define SOC_LEDC_SUPPORT_XTAL_CLOCK (1) #define SOC_LEDC_CHANNEL_NUM (6) #define SOC_LEDC_TIMER_BIT_WIDE_NUM (14) +#define SOC_LEDC_SUPPORT_FADE_STOP (1) /*-------------------------- MPU CAPS ----------------------------------------*/ #define SOC_MPU_CONFIGURABLE_REGIONS_SUPPORTED 0 diff --git a/components/soc/esp32h2/include/soc/Kconfig.soc_caps.in b/components/soc/esp32h2/include/soc/Kconfig.soc_caps.in index e7454d936e..0c682d6df7 100644 --- a/components/soc/esp32h2/include/soc/Kconfig.soc_caps.in +++ b/components/soc/esp32h2/include/soc/Kconfig.soc_caps.in @@ -299,6 +299,10 @@ config SOC_LEDC_TIMER_BIT_WIDE_NUM int default 14 +config SOC_LEDC_SUPPORT_FADE_STOP + bool + default y + config SOC_MPU_CONFIGURABLE_REGIONS_SUPPORTED bool default n diff --git a/components/soc/esp32h2/include/soc/soc_caps.h b/components/soc/esp32h2/include/soc/soc_caps.h index c4d4de7b4c..a510b1f7ed 100644 --- a/components/soc/esp32h2/include/soc/soc_caps.h +++ b/components/soc/esp32h2/include/soc/soc_caps.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -156,6 +156,7 @@ #define SOC_LEDC_SUPPORT_XTAL_CLOCK (1) #define SOC_LEDC_CHANNEL_NUM (6) #define SOC_LEDC_TIMER_BIT_WIDE_NUM (14) +#define SOC_LEDC_SUPPORT_FADE_STOP (1) /*-------------------------- MPU CAPS ----------------------------------------*/ #define SOC_MPU_CONFIGURABLE_REGIONS_SUPPORTED 0 diff --git a/components/soc/esp32s2/include/soc/Kconfig.soc_caps.in b/components/soc/esp32s2/include/soc/Kconfig.soc_caps.in index e14c8500cb..2dcb23ef07 100644 --- a/components/soc/esp32s2/include/soc/Kconfig.soc_caps.in +++ b/components/soc/esp32s2/include/soc/Kconfig.soc_caps.in @@ -347,6 +347,10 @@ config SOC_LEDC_TIMER_BIT_WIDE_NUM int default 14 +config SOC_LEDC_SUPPORT_FADE_STOP + bool + default y + config SOC_MPU_CONFIGURABLE_REGIONS_SUPPORTED bool default n diff --git a/components/soc/esp32s2/include/soc/soc_caps.h b/components/soc/esp32s2/include/soc/soc_caps.h index 9374d84e37..f5ecbf5fbb 100644 --- a/components/soc/esp32s2/include/soc/soc_caps.h +++ b/components/soc/esp32s2/include/soc/soc_caps.h @@ -180,6 +180,7 @@ #define SOC_LEDC_SUPPORT_XTAL_CLOCK (1) #define SOC_LEDC_CHANNEL_NUM (8) #define SOC_LEDC_TIMER_BIT_WIDE_NUM (14) +#define SOC_LEDC_SUPPORT_FADE_STOP (1) /*-------------------------- MPU CAPS ----------------------------------------*/ //TODO: correct the caller and remove unsupported lines diff --git a/components/soc/esp32s3/include/soc/Kconfig.soc_caps.in b/components/soc/esp32s3/include/soc/Kconfig.soc_caps.in index 2eb054b0fc..ab3409d1f1 100644 --- a/components/soc/esp32s3/include/soc/Kconfig.soc_caps.in +++ b/components/soc/esp32s3/include/soc/Kconfig.soc_caps.in @@ -83,6 +83,10 @@ config SOC_LEDC_TIMER_BIT_WIDE_NUM int default 14 +config SOC_LEDC_SUPPORT_FADE_STOP + bool + default y + config SOC_MPU_CONFIGURABLE_REGIONS_SUPPORTED bool default n diff --git a/components/soc/esp32s3/include/soc/ledc_caps.h b/components/soc/esp32s3/include/soc/ledc_caps.h index 3f8150a431..2895d0e08f 100644 --- a/components/soc/esp32s3/include/soc/ledc_caps.h +++ b/components/soc/esp32s3/include/soc/ledc_caps.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2019-2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2019-2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -13,6 +13,7 @@ extern "C" { #define SOC_LEDC_SUPPORT_XTAL_CLOCK (1) #define SOC_LEDC_CHANNEL_NUM 8 #define SOC_LEDC_TIMER_BIT_WIDE_NUM (14) +#define SOC_LEDC_SUPPORT_FADE_STOP (1) #ifdef __cplusplus } diff --git a/examples/peripherals/ledc/ledc_fade/main/ledc_fade_example_main.c b/examples/peripherals/ledc/ledc_fade/main/ledc_fade_example_main.c index 394ebf61c9..107de2720b 100644 --- a/examples/peripherals/ledc/ledc_fade/main/ledc_fade_example_main.c +++ b/examples/peripherals/ledc/ledc_fade/main/ledc_fade_example_main.c @@ -9,10 +9,9 @@ #include #include "freertos/FreeRTOS.h" #include "freertos/task.h" +#include "freertos/semphr.h" #include "driver/ledc.h" #include "esp_err.h" -#include "freertos/FreeRTOS.h" -#include "freertos/semphr.h" /* * About this example