Преглед изворни кода

Merge branch 'feature/ledc_fade_callback' into 'master'

ledc: Add a callback for LEDC fade end

Closes IDF-2475 and IDF-3222

See merge request espressif/esp-idf!14302
Michael (XIAO Xufeng) пре 4 година
родитељ
комит
f26006a3bc

+ 67 - 20
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);

+ 29 - 8
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(&param, 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.

+ 37 - 4
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++) {