Kaynağa Gözat

Merge branch 'bugfix/ledc_consecutive_fade_v4.4' into 'release/v4.4'

ledc: Bugfixes for issues related to fade protection (backport v4.4)

See merge request espressif/esp-idf!16953
Jiang Jiang Jian 3 yıl önce
ebeveyn
işleme
d326c5283d

+ 3 - 3
.gitlab/ci/target-test.yml

@@ -684,7 +684,7 @@ UT_046:
 
 UT_047:
   extends: .unit_test_esp32s2_template
-  parallel: 6
+  parallel: 7
   tags:
     - ESP32S2_IDF
     - UT_T1_1
@@ -703,7 +703,7 @@ UT_S2_SDSPI:
 
 UT_C3:
   extends: .unit_test_esp32c3_template
-  parallel: 34
+  parallel: 35
   tags:
     - ESP32C3_IDF
     - UT_T1_1
@@ -747,7 +747,7 @@ UT_C3_SDSPI:
 
 UT_S3:
   extends: .unit_test_esp32s3_template
-  parallel: 31
+  parallel: 32
   tags:
     - ESP32S3_IDF
     - UT_T1_1

+ 6 - 1
components/driver/include/driver/ledc.h

@@ -85,6 +85,7 @@ esp_err_t ledc_timer_config(const ledc_timer_config_t* timer_conf);
  * @brief LEDC update channel parameters
  * @note  Call this function to activate the LEDC updated parameters.
  *        After ledc_set_duty, we need to call this function to update the settings.
+ *        And the new LEDC parameters don't take effect until the next PWM cycle.
  * @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
@@ -203,6 +204,9 @@ esp_err_t ledc_set_duty(ledc_mode_t speed_mode, ledc_channel_t channel, uint32_t
 
 /**
  * @brief LEDC get duty
+ *        This function returns the duty at the present PWM cycle.
+ *        You shouldn't expect the function to return the new duty in the same cycle of calling ledc_update_duty,
+ *        because duty update doesn't take effect until the next cycle.
  *
  * @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
@@ -385,7 +389,8 @@ void ledc_fade_func_uninstall(void);
  *        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
- * @param fade_mode Whether to block until fading done.
+ * @param fade_mode Whether to block until fading done. See ledc_types.h ledc_fade_mode_t for more info.
+ *        Note that this function will not return until fading to the target duty if LEDC_FADE_WAIT_DONE mode is selected.
  *
  * @return
  *     - ESP_OK Success

+ 18 - 23
components/driver/ledc.c

@@ -184,7 +184,7 @@ esp_err_t ledc_timer_set(ledc_mode_t speed_mode, ledc_timer_t timer_sel, uint32_
 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(&ledc_spinlock);
+    portENTER_CRITICAL_SAFE(&ledc_spinlock);
     if (hpoint_val >= 0) {
         ledc_hal_set_hpoint(&(p_ledc_obj[speed_mode]->ledc_hal), channel, hpoint_val);
     }
@@ -196,7 +196,7 @@ 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(&ledc_spinlock);
+    portEXIT_CRITICAL_SAFE(&ledc_spinlock);
     return ESP_OK;
 }
 
@@ -466,8 +466,8 @@ esp_err_t ledc_set_duty_with_hpoint(ledc_mode_t speed_mode, ledc_channel_t chann
                      hpoint,          //uint32_t hpoint_val,
                      duty,           //uint32_t duty_val,
                      1,               //uint32_t increase,
-                     1,               //uint32_t duty_num,
-                     1,               //uint32_t duty_cycle,
+                     0,               //uint32_t duty_num,
+                     0,               //uint32_t duty_cycle,
                      0                //uint32_t duty_scale
                      );
     _ledc_fade_hw_release(speed_mode, channel);
@@ -486,8 +486,8 @@ esp_err_t ledc_set_duty(ledc_mode_t speed_mode, ledc_channel_t channel, uint32_t
                      LEDC_VAL_NO_CHANGE,
                      duty,           //uint32_t duty_val,
                      1,               //uint32_t increase,
-                     1,               //uint32_t duty_num,
-                     1,               //uint32_t duty_cycle,
+                     0,               //uint32_t duty_num,
+                     0,               //uint32_t duty_cycle,
                      0                //uint32_t duty_scale
                      );
     _ledc_fade_hw_release(speed_mode, channel);
@@ -568,9 +568,9 @@ void IRAM_ATTR ledc_fade_isr(void* arg)
             ledc_calc_fade_end_channel(&intr_status, &channel);
 
             // clear interrupt
-            portENTER_CRITICAL(&ledc_spinlock);
+            portENTER_CRITICAL_ISR(&ledc_spinlock);
             ledc_hal_clear_fade_end_intr_status(&(p_ledc_obj[speed_mode]->ledc_hal), channel);
-            portEXIT_CRITICAL(&ledc_spinlock);
+            portEXIT_CRITICAL_ISR(&ledc_spinlock);
 
             if (s_ledc_fade_rec[speed_mode][channel] == NULL) {
                 //fade object not initialized yet.
@@ -620,9 +620,9 @@ void IRAM_ATTR ledc_fade_isr(void* arg)
                     1,
                     0);
             }
-            portENTER_CRITICAL(&ledc_spinlock);
+            portENTER_CRITICAL_ISR(&ledc_spinlock);
             ledc_hal_set_duty_start(&(p_ledc_obj[speed_mode]->ledc_hal), channel, true);
-            portEXIT_CRITICAL(&ledc_spinlock);
+            portEXIT_CRITICAL_ISR(&ledc_spinlock);
         }
     }
     if (HPTaskAwoken == pdTRUE || cb_yield) {
@@ -765,7 +765,10 @@ static void _ledc_fade_start(ledc_mode_t speed_mode, ledc_channel_t channel, led
     ledc_enable_intr_type(speed_mode, channel, LEDC_INTR_FADE_END);
     ledc_update_duty(speed_mode, channel);
     if (fade_mode == LEDC_FADE_WAIT_DONE) {
-        xSemaphoreTake(s_ledc_fade_rec[speed_mode][channel]->ledc_fade_sem, portMAX_DELAY);
+        // Waiting for fade done
+        _ledc_fade_hw_acquire(speed_mode, channel);
+        // Release hardware to support next time fade configure
+        _ledc_fade_hw_release(speed_mode, channel);
     }
 }
 
@@ -807,7 +810,6 @@ esp_err_t ledc_fade_start(ledc_mode_t speed_mode, ledc_channel_t channel, ledc_f
     LEDC_CHECK(p_ledc_obj[speed_mode] != NULL, LEDC_NOT_INIT, ESP_ERR_INVALID_STATE);
     _ledc_fade_hw_acquire(speed_mode, channel);
     _ledc_fade_start(speed_mode, channel, fade_mode);
-    _ledc_fade_hw_release(speed_mode, channel);
     return ESP_OK;
 }
 
@@ -855,14 +857,13 @@ esp_err_t ledc_set_duty_and_update(ledc_mode_t speed_mode, ledc_channel_t channe
     LEDC_ARG_CHECK(speed_mode < LEDC_SPEED_MODE_MAX, "speed_mode");
     LEDC_ARG_CHECK(channel < LEDC_CHANNEL_MAX, "channel");
     LEDC_ARG_CHECK(duty <= ledc_get_max_duty(speed_mode, channel), "target_duty");
+    LEDC_ARG_CHECK(hpoint <= LEDC_HPOINT_VAL_MAX, "hpoint");
     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_op_lock_acquire(speed_mode, channel);
+    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);
-    _ledc_set_fade_with_step(speed_mode, channel, duty, 0, 1);
-    _ledc_fade_start(speed_mode, channel, LEDC_FADE_WAIT_DONE);
+    ledc_duty_config(speed_mode, channel, hpoint, duty, 1, 0, 0, 0);
+    ledc_update_duty(speed_mode, channel);
     _ledc_fade_hw_release(speed_mode, channel);
-    _ledc_op_lock_release(speed_mode, channel);
     return ESP_OK;
 }
 
@@ -878,9 +879,6 @@ esp_err_t ledc_set_fade_time_and_start(ledc_mode_t speed_mode, ledc_channel_t ch
     _ledc_fade_hw_acquire(speed_mode, channel);
     _ledc_set_fade_with_time(speed_mode, channel, target_duty, max_fade_time_ms);
     _ledc_fade_start(speed_mode, channel, fade_mode);
-    if (fade_mode == LEDC_FADE_WAIT_DONE) {
-        _ledc_fade_hw_release(speed_mode, channel);
-    }
     _ledc_op_lock_release(speed_mode, channel);
     return ESP_OK;
 }
@@ -899,9 +897,6 @@ esp_err_t ledc_set_fade_step_and_start(ledc_mode_t speed_mode, ledc_channel_t ch
     _ledc_fade_hw_acquire(speed_mode, channel);
     _ledc_set_fade_with_step(speed_mode, channel, target_duty, scale, cycle_num);
     _ledc_fade_start(speed_mode, channel, fade_mode);
-    if (fade_mode == LEDC_FADE_WAIT_DONE) {
-        _ledc_fade_hw_release(speed_mode, channel);
-    }
     _ledc_op_lock_release(speed_mode, channel);
     return ESP_OK;
 }

+ 104 - 40
components/driver/test/test_ledc.c

@@ -70,12 +70,26 @@ static ledc_timer_config_t create_default_timer_config(void)
     return ledc_time_config;
 }
 
+static void fade_setup(void)
+{
+    ledc_channel_config_t ledc_ch_config = initialize_channel_config();
+    ledc_ch_config.duty = 0;
+    ledc_timer_config_t ledc_time_config = create_default_timer_config();
+
+    TEST_ESP_OK(ledc_channel_config(&ledc_ch_config));
+    TEST_ESP_OK(ledc_timer_config(&ledc_time_config));
+    vTaskDelay(5 / portTICK_PERIOD_MS);
+
+    //initialize fade service
+    TEST_ESP_OK(ledc_fade_func_install(0));
+}
+
 static void timer_duty_set_get(ledc_mode_t speed_mode, ledc_channel_t channel, uint32_t duty)
 {
     TEST_ESP_OK(ledc_set_duty(speed_mode, channel, duty));
     TEST_ESP_OK(ledc_update_duty(speed_mode, channel));
-    vTaskDelay(1000 / portTICK_RATE_MS);
-    TEST_ASSERT_EQUAL_INT32(ledc_get_duty(speed_mode, channel), duty);
+    vTaskDelay(100 / portTICK_RATE_MS);
+    TEST_ASSERT_EQUAL_INT32(duty, ledc_get_duty(speed_mode, channel));
 }
 
 // use logic analyzer to view
@@ -304,9 +318,9 @@ TEST_CASE("LEDC memory leak test", "[ledc]")
     TEST_ESP_OK(ledc_stop(ledc_time_config.speed_mode, LEDC_CHANNEL_0, 0));
 }
 
-// the duty need to be detected by waveform given by the logic analyzer
-// can't get it directly, so set it "ignore"
-TEST_CASE("LEDC set and get dut(with logic analyzer)", "[ledc][ignore]")
+// duty should be manually checked from the waveform using a logic analyzer
+// this test is enabled only for testting the settings
+TEST_CASE("LEDC set and get duty", "[ledc]")
 {
     ledc_timer_t timer_list[4] = {LEDC_TIMER_0, LEDC_TIMER_1, LEDC_TIMER_2, LEDC_TIMER_3};
     ledc_mode_t speed_mode_list[LEDC_SPEED_MODE_MAX] = SPEED_MODE_LIST;
@@ -317,61 +331,111 @@ TEST_CASE("LEDC set and get dut(with logic analyzer)", "[ledc][ignore]")
     }
 }
 
-TEST_CASE("LEDC fade with time(logic analyzer)", "[ledc][ignore]")
+TEST_CASE("LEDC fade with time", "[ledc]")
 {
     const ledc_mode_t test_speed_mode = TEST_SPEED_MODE;
-    ledc_channel_config_t ledc_ch_config = initialize_channel_config();
-    ledc_ch_config.duty = 0;
-    ledc_timer_config_t ledc_time_config = create_default_timer_config();
-
-    TEST_ESP_OK(ledc_channel_config(&ledc_ch_config));
-    TEST_ESP_OK(ledc_timer_config(&ledc_time_config));
+    fade_setup();
 
-    //initialize fade service
-    TEST_ESP_OK(ledc_fade_func_install(0));
-
-    TEST_ESP_OK(ledc_set_fade_with_time(test_speed_mode, LEDC_CHANNEL_0, 4000, 1000));
+    TEST_ESP_OK(ledc_set_fade_with_time(test_speed_mode, LEDC_CHANNEL_0, 4000, 200));
     TEST_ESP_OK(ledc_fade_start(test_speed_mode, LEDC_CHANNEL_0, LEDC_FADE_WAIT_DONE));
-    // TODO: allows for 5% deviation here due to driver code (IDF-2099)
-    vTaskDelay(1050 / portTICK_RATE_MS);
-    TEST_ASSERT_EQUAL_INT32(ledc_get_duty(test_speed_mode, LEDC_CHANNEL_0), 4000);
+    TEST_ASSERT_EQUAL_INT32(4000, ledc_get_duty(test_speed_mode, LEDC_CHANNEL_0));
 
-    TEST_ESP_OK(ledc_set_fade_with_time(test_speed_mode, LEDC_CHANNEL_0, 0, 1000));
+    TEST_ESP_OK(ledc_set_fade_with_time(test_speed_mode, LEDC_CHANNEL_0, 0, 200));
     TEST_ESP_OK(ledc_fade_start(test_speed_mode, LEDC_CHANNEL_0, LEDC_FADE_NO_WAIT));
-    vTaskDelay(1050 / portTICK_RATE_MS);
-    TEST_ASSERT_EQUAL_INT32(ledc_get_duty(test_speed_mode, LEDC_CHANNEL_0), 0);
+    // duty should not be too far from initial value
+    TEST_ASSERT_INT32_WITHIN(20, 4000, ledc_get_duty(test_speed_mode, LEDC_CHANNEL_0));
+    vTaskDelay(210 / portTICK_PERIOD_MS);
+    TEST_ASSERT_EQUAL_INT32(0, ledc_get_duty(test_speed_mode, LEDC_CHANNEL_0));
 
-    //deinitial fade service
+    //deinitialize fade service
     ledc_fade_func_uninstall();
 }
 
-TEST_CASE("LEDC fade with step(logic analyzer)", "[ledc][ignore]")
+TEST_CASE("LEDC fade with step", "[ledc]")
 {
     const ledc_mode_t test_speed_mode = TEST_SPEED_MODE;
-    ledc_channel_config_t ledc_ch_config = initialize_channel_config();
-    ledc_ch_config.duty = 0;
-    ledc_timer_config_t ledc_time_config = create_default_timer_config();
+    fade_setup();
 
-    TEST_ESP_OK(ledc_channel_config(&ledc_ch_config));
-    TEST_ESP_OK(ledc_timer_config(&ledc_time_config));
-
-    //initialize fade service.
-    TEST_ESP_OK(ledc_fade_func_install(0));
-
-    TEST_ESP_OK(ledc_set_fade_with_step(test_speed_mode, LEDC_CHANNEL_0, 4000, 2, 1));
+    TEST_ESP_OK(ledc_set_fade_with_step(test_speed_mode, LEDC_CHANNEL_0, 4000, 4, 1));
     TEST_ESP_OK(ledc_fade_start(test_speed_mode, LEDC_CHANNEL_0, LEDC_FADE_WAIT_DONE));
-    vTaskDelay(1050 / portTICK_RATE_MS);
-    TEST_ASSERT_EQUAL_INT32(ledc_get_duty(test_speed_mode, LEDC_CHANNEL_0), 4000);
+    TEST_ASSERT_EQUAL_INT32(4000, ledc_get_duty(test_speed_mode, LEDC_CHANNEL_0));
 
-    TEST_ESP_OK(ledc_set_fade_with_step(test_speed_mode, LEDC_CHANNEL_0, 0, 4, 2));
+    TEST_ESP_OK(ledc_set_fade_with_step(test_speed_mode, LEDC_CHANNEL_0, 0, 4, 1));
     TEST_ESP_OK(ledc_fade_start(test_speed_mode, LEDC_CHANNEL_0, LEDC_FADE_NO_WAIT));
-    vTaskDelay(1050 / portTICK_RATE_MS);
-    TEST_ASSERT_EQUAL_INT32(ledc_get_duty(test_speed_mode, LEDC_CHANNEL_0), 0);
+    // duty should not be too far from initial value
+    TEST_ASSERT_INT32_WITHIN(20, 4000, ledc_get_duty(test_speed_mode, LEDC_CHANNEL_0));
+    vTaskDelay(525 / portTICK_PERIOD_MS);
+    TEST_ASSERT_EQUAL_INT32(0, ledc_get_duty(test_speed_mode, LEDC_CHANNEL_0));
 
     //scaler=0 check
     TEST_ASSERT(ledc_set_fade_with_step(test_speed_mode, LEDC_CHANNEL_0, 4000, 0, 1) == ESP_ERR_INVALID_ARG);
 
-    //deinitial fade service
+    //deinitialize fade service
+    ledc_fade_func_uninstall();
+}
+
+TEST_CASE("LEDC fast switching duty with fade_wait_done", "[ledc]")
+{
+    const ledc_mode_t test_speed_mode = TEST_SPEED_MODE;
+    fade_setup();
+
+    // fade function will block until fading to the target duty
+    int64_t fade_start, fade_stop;
+    fade_start = esp_timer_get_time();
+    TEST_ESP_OK(ledc_set_fade_with_time(test_speed_mode, LEDC_CHANNEL_0, 4000, 200));
+    TEST_ESP_OK(ledc_fade_start(test_speed_mode, LEDC_CHANNEL_0, LEDC_FADE_WAIT_DONE));
+    TEST_ASSERT_EQUAL_INT32(4000, ledc_get_duty(test_speed_mode, LEDC_CHANNEL_0));
+    TEST_ESP_OK(ledc_set_fade_with_time(test_speed_mode, LEDC_CHANNEL_0, 1000, 150));
+    TEST_ESP_OK(ledc_fade_start(test_speed_mode, LEDC_CHANNEL_0, LEDC_FADE_WAIT_DONE));
+    TEST_ASSERT_EQUAL_INT32(1000, ledc_get_duty(test_speed_mode, LEDC_CHANNEL_0));
+    fade_stop = esp_timer_get_time();
+    int time_ms = (fade_stop - fade_start) / 1000;
+    TEST_ASSERT_TRUE(fabs(time_ms - 350) < 20);
+
+    // next duty update will not take place until last fade reaches its target duty
+    TEST_ESP_OK(ledc_set_fade_with_time(test_speed_mode, LEDC_CHANNEL_0, 4000, 200));
+    TEST_ESP_OK(ledc_fade_start(test_speed_mode, LEDC_CHANNEL_0, LEDC_FADE_WAIT_DONE));
+    TEST_ASSERT_EQUAL_INT32(4000, ledc_get_duty(test_speed_mode, LEDC_CHANNEL_0));
+    TEST_ESP_OK(ledc_set_duty(test_speed_mode, LEDC_CHANNEL_0, 500));
+    TEST_ESP_OK(ledc_update_duty(test_speed_mode, LEDC_CHANNEL_0));
+    vTaskDelay(5 / portTICK_PERIOD_MS);
+    TEST_ASSERT_EQUAL_INT32(500, ledc_get_duty(test_speed_mode, LEDC_CHANNEL_0));
+
+    //deinitialize fade service
+    ledc_fade_func_uninstall();
+}
+
+TEST_CASE("LEDC fast switching duty with fade_no_wait", "[ledc]")
+{
+    const ledc_mode_t test_speed_mode = TEST_SPEED_MODE;
+    fade_setup();
+
+    // fade function returns immediately, but next fade still needs to wait for last fade ends
+    int64_t fade_start, first_fade_complete;
+    fade_start = esp_timer_get_time();
+    TEST_ESP_OK(ledc_set_fade_with_time(test_speed_mode, LEDC_CHANNEL_0, 4000, 200));
+    TEST_ESP_OK(ledc_fade_start(test_speed_mode, LEDC_CHANNEL_0, LEDC_FADE_NO_WAIT));
+    TEST_ASSERT_LESS_THAN(4000, ledc_get_duty(test_speed_mode, LEDC_CHANNEL_0));
+    TEST_ESP_OK(ledc_set_fade_with_time(test_speed_mode, LEDC_CHANNEL_0, 1000, 150));
+    TEST_ESP_OK(ledc_fade_start(test_speed_mode, LEDC_CHANNEL_0, LEDC_FADE_NO_WAIT));
+    first_fade_complete = esp_timer_get_time();
+    // duty should not be too far from first fade target duty
+    TEST_ASSERT_INT32_WITHIN(20, 4000, ledc_get_duty(test_speed_mode, LEDC_CHANNEL_0));
+    int time_ms = (first_fade_complete - fade_start) / 1000;
+    TEST_ASSERT_TRUE(fabs(time_ms - 200) < 20);
+    vTaskDelay(158 / portTICK_PERIOD_MS);
+    TEST_ASSERT_EQUAL_INT32(1000, ledc_get_duty(test_speed_mode, LEDC_CHANNEL_0));
+
+    // next duty update will not take place until last fade reaches its target duty
+    TEST_ESP_OK(ledc_set_fade_with_time(test_speed_mode, LEDC_CHANNEL_0, 4000, 200));
+    TEST_ESP_OK(ledc_fade_start(test_speed_mode, LEDC_CHANNEL_0, LEDC_FADE_NO_WAIT));
+    TEST_ASSERT_LESS_THAN(4000, ledc_get_duty(test_speed_mode, LEDC_CHANNEL_0));
+    TEST_ESP_OK(ledc_set_duty(test_speed_mode, LEDC_CHANNEL_0, 500));
+    TEST_ESP_OK(ledc_update_duty(test_speed_mode, LEDC_CHANNEL_0));
+    vTaskDelay(5 / portTICK_PERIOD_MS);
+    TEST_ASSERT_EQUAL_INT32(500, ledc_get_duty(test_speed_mode, LEDC_CHANNEL_0));
+
+    //deinitialize fade service
     ledc_fade_func_uninstall();
 }