diff --git a/components/driver/include/driver/ledc.h b/components/driver/include/driver/ledc.h index c1dc6d4d8c..bc8328cdd2 100644 --- a/components/driver/include/driver/ledc.h +++ b/components/driver/include/driver/ledc.h @@ -24,6 +24,38 @@ extern "C" { typedef intr_handle_t ledc_isr_handle_t; +/** + * @brief LEDC callback event type + */ +typedef enum { + LEDC_FADE_END_EVT /**< LEDC fade end event */ +} ledc_cb_event_t; + +/** + * @brief LEDC callback parameter + */ +typedef struct { + ledc_cb_event_t event; /**< Event name */ + uint32_t speed_mode; /**< Speed mode of the LEDC channel group */ + uint32_t channel; /**< LEDC channel (0 - LEDC_CHANNEL_MAX-1) */ + uint32_t duty; /**< LEDC current duty of the channel, the range of duty is [0, (2**duty_resolution) - 1] */ +} ledc_cb_param_t; + +/** + * @brief Type of LEDC event callback + * @param param LEDC callback parameter + * @param user_arg User registered data + */ +typedef bool (* ledc_cb_t)(const ledc_cb_param_t *param, void *user_arg); + +/** + * @brief Group of supported LEDC callbacks + * @note The callbacks are all running under ISR environment + */ +typedef struct { + ledc_cb_t fade_cb; /**< LEDC fade_end callback function */ +} ledc_cbs_t; + /** * @brief LEDC channel configuration * Configure LEDC channel with the given channel/output gpio_num/interrupt/source timer/frequency(Hz)/LEDC duty resolution @@ -57,7 +89,7 @@ esp_err_t ledc_timer_config(const ledc_timer_config_t* timer_conf); * control one LEDC channel in different tasks at the same time. * A thread-safe version of API is ledc_set_duty_and_update * @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-7), select from ledc_channel_t + * @param channel LEDC channel (0 - LEDC_CHANNEL_MAX-1), select from ledc_channel_t * * @return * - ESP_OK Success @@ -72,7 +104,7 @@ esp_err_t ledc_update_duty(ledc_mode_t speed_mode, ledc_channel_t channel); * * @param gpio_num The LEDC output gpio * @param speed_mode Select the LEDC channel group with specified speed mode. Note that not all targets support high speed mode. - * @param ledc_channel LEDC channel (0-7), select from ledc_channel_t + * @param ledc_channel LEDC channel (0 - LEDC_CHANNEL_MAX-1), select from ledc_channel_t * * @return * - ESP_OK Success @@ -85,7 +117,7 @@ esp_err_t ledc_set_pin(int gpio_num, ledc_mode_t speed_mode, ledc_channel_t ledc * Disable LEDC output, and set idle level * * @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-7), select from ledc_channel_t + * @param channel LEDC channel (0 - LEDC_CHANNEL_MAX-1), select from ledc_channel_t * @param idle_level Set output idle level after LEDC stops. * * @return @@ -129,8 +161,8 @@ uint32_t ledc_get_freq(ledc_mode_t speed_mode, ledc_timer_t timer_num); * @note If a fade operation is running in progress on that channel, the driver would not allow it to be stopped. * 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-7), select from ledc_channel_t - * @param duty Set the LEDC duty, the range of duty setting is [0, (2**duty_resolution)] + * @param channel LEDC channel (0 - LEDC_CHANNEL_MAX-1), select from ledc_channel_t + * @param duty Set the LEDC duty, the range of duty setting is [0, (2**duty_resolution) - 1] * @param hpoint Set the LEDC hpoint value(max: 0xfffff) * * @return @@ -143,7 +175,7 @@ esp_err_t ledc_set_duty_with_hpoint(ledc_mode_t speed_mode, ledc_channel_t chann * @brief LEDC get hpoint value, the counter value when the output is set high level. * * @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-7), select from ledc_channel_t + * @param channel LEDC channel (0 - LEDC_CHANNEL_MAX-1), select from ledc_channel_t * @return * - LEDC_ERR_VAL if parameter error * - Others Current hpoint value of LEDC channel @@ -160,8 +192,8 @@ int ledc_get_hpoint(ledc_mode_t speed_mode, ledc_channel_t channel); * @note If a fade operation is running in progress on that channel, the driver would not allow it to be stopped. * 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-7), select from ledc_channel_t - * @param duty Set the LEDC duty, the range of duty setting is [0, (2**duty_resolution)] + * @param channel LEDC channel (0 - LEDC_CHANNEL_MAX-1), select from ledc_channel_t + * @param duty Set the LEDC duty, the range of duty setting is [0, (2**duty_resolution) - 1] * * @return * - ESP_OK Success @@ -173,7 +205,7 @@ esp_err_t ledc_set_duty(ledc_mode_t speed_mode, ledc_channel_t channel, uint32_t * @brief LEDC get 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 (0-7), select from ledc_channel_t + * @param channel LEDC channel (0 - LEDC_CHANNEL_MAX-1), select from ledc_channel_t * * @return * - LEDC_ERR_DUTY if parameter error @@ -187,8 +219,8 @@ uint32_t ledc_get_duty(ledc_mode_t speed_mode, ledc_channel_t channel); * @note If a fade operation is running in progress on that channel, the driver would not allow it to be stopped. * 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-7), select from ledc_channel_t - * @param duty Set the start of the gradient duty, the range of duty setting is [0, (2**duty_resolution)] + * @param channel LEDC channel (0 - LEDC_CHANNEL_MAX-1), select from ledc_channel_t + * @param duty Set the start of the gradient duty, the range of duty setting is [0, (2**duty_resolution) - 1] * @param fade_direction Set the direction of the gradient * @param step_num Set the number of the gradient * @param duty_cycle_num Set how many LEDC tick each time the gradient lasts @@ -274,7 +306,7 @@ esp_err_t ledc_timer_resume(ledc_mode_t speed_mode, ledc_timer_t timer_sel); * @brief Bind LEDC channel with the selected timer * * @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-7), select from ledc_channel_t + * @param channel LEDC channel index (0 - LEDC_CHANNEL_MAX-1), select from ledc_channel_t * @param timer_sel LEDC timer index (0-3), select from ledc_timer_t * * @return @@ -293,7 +325,7 @@ esp_err_t ledc_bind_channel_timer(ledc_mode_t speed_mode, ledc_channel_t channel * @note If a fade operation is running in progress on that channel, the driver would not allow it to be stopped. * 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-7), select from ledc_channel_t + * @param channel LEDC channel index (0 - LEDC_CHANNEL_MAX-1), select from ledc_channel_t * @param target_duty Target duty of fading [0, (2**duty_resolution) - 1] * @param scale Controls the increase or decrease step scale. * @param cycle_num increase or decrease the duty every cycle_num cycles @@ -316,8 +348,8 @@ esp_err_t ledc_set_fade_with_step(ledc_mode_t speed_mode, ledc_channel_t channel * @note If a fade operation is running in progress on that channel, the driver would not allow it to be stopped. * 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-7), select from ledc_channel_t - * @param target_duty Target duty of fading.( 0 - (2 ** duty_resolution - 1))) + * @param channel LEDC channel index (0 - LEDC_CHANNEL_MAX-1), select from ledc_channel_t + * @param target_duty Target duty of fading [0, (2**duty_resolution) - 1] * @param max_fade_time_ms The maximum time of the fading ( ms ). * * @return @@ -368,8 +400,8 @@ esp_err_t ledc_fade_start(ledc_mode_t speed_mode, ledc_channel_t channel, ledc_f * 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-7), select from ledc_channel_t - * @param duty Set the LEDC duty, the range of duty setting is [0, (2**duty_resolution)] + * @param channel LEDC channel (0 - LEDC_CHANNEL_MAX-1), select from ledc_channel_t + * @param duty Set the LEDC duty, the range of duty setting is [0, (2**duty_resolution) - 1] * @param hpoint Set the LEDC hpoint value(max: 0xfffff) * */ @@ -381,8 +413,8 @@ esp_err_t ledc_set_duty_and_update(ledc_mode_t speed_mode, ledc_channel_t channe * @note If a fade operation is running in progress on that channel, the driver would not allow it to be stopped. * 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-7), select from ledc_channel_t - * @param target_duty Target duty of fading.( 0 - (2 ** duty_resolution - 1))) + * @param channel LEDC channel index (0 - LEDC_CHANNEL_MAX-1), select from ledc_channel_t + * @param target_duty Target duty of fading [0, (2**duty_resolution) - 1] * @param max_fade_time_ms The maximum time of the fading ( ms ). * @param fade_mode choose blocking or non-blocking mode * @return @@ -399,7 +431,7 @@ esp_err_t ledc_set_fade_time_and_start(ledc_mode_t speed_mode, ledc_channel_t ch * @note If a fade operation is running in progress on that channel, the driver would not allow it to be stopped. * 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-7), select from ledc_channel_t + * @param channel LEDC channel index (0 - LEDC_CHANNEL_MAX-1), select from ledc_channel_t * @param target_duty Target duty of fading [0, (2**duty_resolution) - 1] * @param scale Controls the increase or decrease step scale. * @param cycle_num increase or decrease the duty every cycle_num cycles @@ -414,3 +446,18 @@ esp_err_t ledc_set_fade_step_and_start(ledc_mode_t speed_mode, ledc_channel_t ch #ifdef __cplusplus } #endif + +/** + * @brief LEDC callback registration function + * @note The callback is called from an ISR, it must never attempt to block, and any FreeRTOS API called must be ISR capable. + * @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 + * @param cbs Group of LEDC callback functions + * @param user_arg user registered data for the callback function + * @return + * - ESP_ERR_INVALID_ARG Parameter error + * - ESP_OK Success + * - ESP_ERR_INVALID_STATE Fade function not installed. + * - ESP_FAIL Fade function init error + */ +esp_err_t ledc_cb_register(ledc_mode_t speed_mode, ledc_channel_t channel, ledc_cbs_t *cbs, void *user_arg); diff --git a/components/driver/ledc.c b/components/driver/ledc.c index 960f3b72c5..ef0535b89d 100644 --- a/components/driver/ledc.c +++ b/components/driver/ledc.c @@ -37,6 +37,8 @@ typedef struct { #if CONFIG_SPIRAM_USE_MALLOC StaticQueue_t ledc_fade_sem_storage; #endif + ledc_cb_t ledc_fade_callback; + void *cb_user_arg; } ledc_fade_t; typedef struct { @@ -551,6 +553,7 @@ static inline void ledc_calc_fade_end_channel(uint32_t *fade_end_status, uint32_ void IRAM_ATTR ledc_fade_isr(void* arg) { + bool cb_yield = false; portBASE_TYPE HPTaskAwoken = pdFALSE; uint32_t speed_mode = 0; uint32_t channel = 0; @@ -576,17 +579,21 @@ void IRAM_ATTR ledc_fade_isr(void* arg) uint32_t duty_cur = 0; ledc_hal_get_duty(&(p_ledc_obj[speed_mode]->ledc_hal), channel, &duty_cur); - if (duty_cur == s_ledc_fade_rec[speed_mode][channel]->target_duty) { - xSemaphoreGiveFromISR(s_ledc_fade_rec[speed_mode][channel]->ledc_fade_sem, &HPTaskAwoken); - if (HPTaskAwoken == pdTRUE) { - portYIELD_FROM_ISR(); - } - continue; - } uint32_t duty_tar = s_ledc_fade_rec[speed_mode][channel]->target_duty; int scale = s_ledc_fade_rec[speed_mode][channel]->scale; - if (scale == 0) { + if (duty_cur == duty_tar || scale == 0) { xSemaphoreGiveFromISR(s_ledc_fade_rec[speed_mode][channel]->ledc_fade_sem, &HPTaskAwoken); + + ledc_cb_param_t param = { + .event = LEDC_FADE_END_EVT, + .speed_mode = speed_mode, + .channel = channel, + .duty = duty_cur + }; + ledc_cb_t fade_cb = s_ledc_fade_rec[speed_mode][channel]->ledc_fade_callback; + if (fade_cb) { + 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; @@ -618,6 +625,9 @@ void IRAM_ATTR ledc_fade_isr(void* arg) portEXIT_CRITICAL(&ledc_spinlock); } } + if (HPTaskAwoken == pdTRUE || cb_yield) { + portYIELD_FROM_ISR(); + } } static esp_err_t ledc_fade_channel_deinit(ledc_mode_t speed_mode, ledc_channel_t channel) @@ -825,6 +835,17 @@ void ledc_fade_func_uninstall(void) return; } +esp_err_t ledc_cb_register(ledc_mode_t speed_mode, ledc_channel_t channel, ledc_cbs_t *cbs, void *user_arg) +{ + 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); + s_ledc_fade_rec[speed_mode][channel]->ledc_fade_callback = cbs->fade_cb; + s_ledc_fade_rec[speed_mode][channel]->cb_user_arg = user_arg; + return ESP_OK; +} + /* * The functions below are thread-safe version of APIs for duty and fade control. * These APIs can be called from different tasks. 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 c65329e501..394ebf61c9 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 @@ -11,6 +11,8 @@ #include "freertos/task.h" #include "driver/ledc.h" #include "esp_err.h" +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" /* * About this example @@ -43,7 +45,7 @@ #endif #define LEDC_LS_TIMER LEDC_TIMER_1 #define LEDC_LS_MODE LEDC_LOW_SPEED_MODE -#if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32C3 +#if !CONFIG_IDF_TARGET_ESP32 #define LEDC_LS_CH0_GPIO (18) #define LEDC_LS_CH0_CHANNEL LEDC_CHANNEL_0 #define LEDC_LS_CH1_GPIO (19) @@ -58,6 +60,23 @@ #define LEDC_TEST_DUTY (4000) #define LEDC_TEST_FADE_TIME (3000) +/* + * This callback function will be called when fade operation has ended + * Use callback only if you are aware it is being called inside an ISR + * Otherwise, you can use a semaphore to unblock tasks + */ +static bool cb_ledc_fade_end_event(const ledc_cb_param_t *param, void *user_arg) +{ + portBASE_TYPE taskAwoken = pdFALSE; + + if (param->event == LEDC_FADE_END_EVT) { + SemaphoreHandle_t counting_sem = (SemaphoreHandle_t) user_arg; + xSemaphoreGiveFromISR(counting_sem, &taskAwoken); + } + + return (taskAwoken == pdTRUE); +} + void app_main(void) { int ch; @@ -114,7 +133,7 @@ void app_main(void) .timer_sel = LEDC_HS_TIMER, .flags.output_invert = 0 }, -#elif CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32C3 +#else { .channel = LEDC_LS_CH0_CHANNEL, .duty = 0, @@ -161,6 +180,14 @@ void app_main(void) // Initialize fade service. ledc_fade_func_install(0); + ledc_cbs_t callbacks = { + .fade_cb = cb_ledc_fade_end_event + }; + SemaphoreHandle_t counting_sem = xSemaphoreCreateCounting(LEDC_TEST_CH_NUM, 0); + + for (ch = 0; ch < LEDC_TEST_CH_NUM; ch++) { + ledc_cb_register(ledc_channel[ch].speed_mode, ledc_channel[ch].channel, &callbacks, (void *) counting_sem); + } while (1) { printf("1. LEDC fade up to duty = %d\n", LEDC_TEST_DUTY); @@ -170,7 +197,10 @@ void app_main(void) ledc_fade_start(ledc_channel[ch].speed_mode, ledc_channel[ch].channel, LEDC_FADE_NO_WAIT); } - vTaskDelay(LEDC_TEST_FADE_TIME / portTICK_PERIOD_MS); + + for (int i = 0; i < LEDC_TEST_CH_NUM; i++) { + xSemaphoreTake(counting_sem, portMAX_DELAY); + } printf("2. LEDC fade down to duty = 0\n"); for (ch = 0; ch < LEDC_TEST_CH_NUM; ch++) { @@ -179,7 +209,10 @@ void app_main(void) ledc_fade_start(ledc_channel[ch].speed_mode, ledc_channel[ch].channel, LEDC_FADE_NO_WAIT); } - vTaskDelay(LEDC_TEST_FADE_TIME / portTICK_PERIOD_MS); + + for (int i = 0; i < LEDC_TEST_CH_NUM; i++) { + xSemaphoreTake(counting_sem, portMAX_DELAY); + } printf("3. LEDC set duty = %d without fade\n", LEDC_TEST_DUTY); for (ch = 0; ch < LEDC_TEST_CH_NUM; ch++) {