Merge branch 'bugfix/ledc_consecutive_fade' into 'master'

ledc: Bugfixes for issues related to fade protection on ESP32

Closes IDFGH-4918 and IDFGH-5565

See merge request espressif/esp-idf!14568
This commit is contained in:
Song Ruo Jing
2022-01-26 06:00:59 +00:00
15 changed files with 375 additions and 123 deletions

View File

@@ -634,7 +634,7 @@ UT_006:
UT_007: UT_007:
extends: .unit_test_esp32_template extends: .unit_test_esp32_template
parallel: 4 parallel: 5
tags: tags:
- ESP32_IDF - ESP32_IDF
- UT_T1_1 - UT_T1_1
@@ -785,7 +785,7 @@ UT_046:
UT_047: UT_047:
extends: .unit_test_esp32s2_template extends: .unit_test_esp32s2_template
parallel: 5 parallel: 6
tags: tags:
- ESP32S2_IDF - ESP32S2_IDF
- UT_T1_1 - UT_T1_1
@@ -804,7 +804,7 @@ UT_S2_SDSPI:
UT_C3: UT_C3:
extends: .unit_test_esp32c3_template extends: .unit_test_esp32c3_template
parallel: 33 parallel: 34
tags: tags:
- ESP32C3_IDF - ESP32C3_IDF
- UT_T1_1 - UT_T1_1
@@ -848,7 +848,7 @@ UT_C3_SDSPI:
UT_S3: UT_S3:
extends: .unit_test_esp32s3_template extends: .unit_test_esp32s3_template
parallel: 31 parallel: 32
tags: tags:
- ESP32S3_IDF - ESP32S3_IDF
- UT_T1_1 - UT_T1_1

View File

@@ -116,6 +116,7 @@ esp_err_t ledc_timer_config(const ledc_timer_config_t* timer_conf);
* @brief LEDC update channel parameters * @brief LEDC update channel parameters
* @note Call this function to activate the LEDC updated 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. * 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 * @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. * control one LEDC channel in different tasks at the same time.
* A thread-safe version of API is ledc_set_duty_and_update * A thread-safe version of API is ledc_set_duty_and_update
@@ -189,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 * @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. * control one LEDC channel in different tasks at the same time.
* A thread-safe version of API is ledc_set_duty_and_update * 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. * 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 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 * @param channel LEDC channel (0 - LEDC_CHANNEL_MAX-1), select from ledc_channel_t
@@ -220,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 * @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. * control one LEDC channel in different tasks at the same time.
* A thread-safe version of API is ledc_set_duty_and_update. * 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. * 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 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 * @param channel LEDC channel (0 - LEDC_CHANNEL_MAX-1), select from ledc_channel_t
@@ -234,6 +235,9 @@ esp_err_t ledc_set_duty(ledc_mode_t speed_mode, ledc_channel_t channel, uint32_t
/** /**
* @brief LEDC get duty * @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 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 * @param channel LEDC channel (0 - LEDC_CHANNEL_MAX-1), select from ledc_channel_t
@@ -247,7 +251,7 @@ uint32_t ledc_get_duty(ledc_mode_t speed_mode, ledc_channel_t channel);
/** /**
* @brief LEDC set gradient * @brief LEDC set gradient
* Set LEDC gradient, After the function calls the ledc_update_duty function, the function can take effect. * 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. * 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 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 * @param channel LEDC channel (0 - LEDC_CHANNEL_MAX-1), select from ledc_channel_t
@@ -353,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 * @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. * control one LEDC channel in different tasks at the same time.
* A thread-safe version of API is ledc_set_fade_step_and_start * 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. * 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 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 channel LEDC channel index (0 - LEDC_CHANNEL_MAX-1), select from ledc_channel_t
@@ -376,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 * @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. * control one LEDC channel in different tasks at the same time.
* A thread-safe version of API is ledc_set_fade_step_and_start * 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. * 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 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 channel LEDC channel index (0 - LEDC_CHANNEL_MAX-1), select from ledc_channel_t
@@ -412,11 +416,13 @@ void ledc_fade_func_uninstall(void);
* @brief Start LEDC fading. * @brief Start LEDC fading.
* @note Call ledc_fade_func_install() once before calling this function. * @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. * 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. * 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 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 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 * @return
* - ESP_OK Success * - ESP_OK Success
@@ -425,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); 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. * @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. * 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 speed_mode Select the LEDC channel group with specified speed mode. Note that not all targets support high speed mode.
@@ -441,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. * @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 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. * 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 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 channel LEDC channel index (0 - LEDC_CHANNEL_MAX-1), select from ledc_channel_t
@@ -459,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. * @brief A thread-safe API to set and start LEDC fade function.
* @note Call ledc_fade_func_install() once before calling this 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. * 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 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 channel LEDC channel index (0 - LEDC_CHANNEL_MAX-1), select from ledc_channel_t

View File

@@ -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_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") #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 { typedef struct {
ledc_mode_t speed_mode; ledc_mode_t speed_mode;
ledc_duty_direction_t direction; ledc_duty_direction_t direction;
@@ -40,6 +47,7 @@ typedef struct {
#endif #endif
ledc_cb_t ledc_fade_callback; ledc_cb_t ledc_fade_callback;
void *cb_user_arg; void *cb_user_arg;
volatile ledc_fade_fsm_t fsm;
} ledc_fade_t; } ledc_fade_t;
typedef struct { 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) 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) { if (type == LEDC_INTR_FADE_END) {
ledc_hal_set_fade_end_intr(&(p_ledc_obj[speed_mode]->ledc_hal), channel, true); ledc_hal_set_fade_end_intr(&(p_ledc_obj[speed_mode]->ledc_hal), channel, true);
} else { } else {
ledc_hal_set_fade_end_intr(&(p_ledc_obj[speed_mode]->ledc_hal), channel, false); ledc_hal_set_fade_end_intr(&(p_ledc_obj[speed_mode]->ledc_hal), channel, false);
} }
portEXIT_CRITICAL(&ledc_spinlock);
return ESP_OK; 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]; ledc_fade_t *fade = s_ledc_fade_rec[mode][channel];
if (fade) { if (fade) {
xSemaphoreTake(fade->ledc_fade_sem, portMAX_DELAY); xSemaphoreTake(fade->ledc_fade_sem, portMAX_DELAY);
portENTER_CRITICAL(&ledc_spinlock);
ledc_enable_intr_type(mode, channel, LEDC_INTR_DISABLE); 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; 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, static IRAM_ATTR esp_err_t ledc_duty_config(ledc_mode_t speed_mode, ledc_channel_t channel, int hpoint_val,
ledc_duty_direction_t duty_direction, uint32_t duty_num, uint32_t duty_cycle, uint32_t duty_scale) 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);
if (hpoint_val >= 0) { if (hpoint_val >= 0) {
ledc_hal_set_hpoint(&(p_ledc_obj[speed_mode]->ledc_hal), channel, hpoint_val); 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_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_hal_set_duty_scale(&(p_ledc_obj[speed_mode]->ledc_hal), channel, duty_scale);
ledc_ls_channel_update(speed_mode, channel); ledc_ls_channel_update(speed_mode, channel);
portEXIT_CRITICAL(&ledc_spinlock);
return ESP_OK; 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*/ /*bind the channel with the timer*/
ledc_bind_channel_timer(speed_mode, ledc_channel, timer_select); ledc_bind_channel_timer(speed_mode, ledc_channel, timer_select);
/*set interrupt type*/ /*set interrupt type*/
portENTER_CRITICAL(&ledc_spinlock);
ledc_enable_intr_type(speed_mode, ledc_channel, intr_type); 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", ESP_LOGD(LEDC_TAG, "LEDC_PWM CHANNEL %1u|GPIO %02u|Duty %04u|Time %01u",
ledc_channel, gpio_num, duty, timer_select 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; 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) 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(speed_mode < LEDC_SPEED_MODE_MAX, "speed_mode");
LEDC_ARG_CHECK(channel < LEDC_CHANNEL_MAX, "channel"); 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(p_ledc_obj[speed_mode] != NULL, LEDC_NOT_INIT, ESP_ERR_INVALID_STATE);
portENTER_CRITICAL(&ledc_spinlock); portENTER_CRITICAL(&ledc_spinlock);
ledc_hal_set_sig_out_en(&(p_ledc_obj[speed_mode]->ledc_hal), channel, true); _ledc_update_duty(speed_mode, channel);
ledc_hal_set_duty_start(&(p_ledc_obj[speed_mode]->ledc_hal), channel, true);
ledc_ls_channel_update(speed_mode, channel);
portEXIT_CRITICAL(&ledc_spinlock); portEXIT_CRITICAL(&ledc_spinlock);
return ESP_OK; 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_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_CHECK(p_ledc_obj[speed_mode] != NULL, LEDC_NOT_INIT, ESP_ERR_INVALID_STATE);
_ledc_fade_hw_acquire(speed_mode, channel); _ledc_fade_hw_acquire(speed_mode, channel);
portENTER_CRITICAL(&ledc_spinlock);
ledc_duty_config(speed_mode, ledc_duty_config(speed_mode,
channel, //uint32_t chan_num, channel, //uint32_t chan_num,
LEDC_VAL_NO_CHANGE, 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_cyle_num, //uint32_t duty_cycle,
duty_scale //uint32_t duty_scale duty_scale //uint32_t duty_scale
); );
portEXIT_CRITICAL(&ledc_spinlock);
_ledc_fade_hw_release(speed_mode, channel); _ledc_fade_hw_release(speed_mode, channel);
return ESP_OK; return ESP_OK;
} }
@@ -695,15 +710,17 @@ 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); 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. */ /* The channel configuration should not be changed before the fade operation is done. */
_ledc_fade_hw_acquire(speed_mode, channel); _ledc_fade_hw_acquire(speed_mode, channel);
portENTER_CRITICAL(&ledc_spinlock);
ledc_duty_config(speed_mode, ledc_duty_config(speed_mode,
channel, //uint32_t chan_num, channel, //uint32_t chan_num,
hpoint, //uint32_t hpoint_val, hpoint, //uint32_t hpoint_val,
duty, //uint32_t duty_val, duty, //uint32_t duty_val,
1, //uint32_t increase, 1, //uint32_t increase,
1, //uint32_t duty_num, 0, //uint32_t duty_num,
1, //uint32_t duty_cycle, 0, //uint32_t duty_cycle,
0 //uint32_t duty_scale 0 //uint32_t duty_scale
); );
portEXIT_CRITICAL(&ledc_spinlock);
_ledc_fade_hw_release(speed_mode, channel); _ledc_fade_hw_release(speed_mode, channel);
return ESP_OK; return ESP_OK;
} }
@@ -715,15 +732,17 @@ 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); 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. */ /* The channel configuration should not be changed before the fade operation is done. */
_ledc_fade_hw_acquire(speed_mode, channel); _ledc_fade_hw_acquire(speed_mode, channel);
portENTER_CRITICAL(&ledc_spinlock);
ledc_duty_config(speed_mode, ledc_duty_config(speed_mode,
channel, //uint32_t chan_num, channel, //uint32_t chan_num,
LEDC_VAL_NO_CHANGE, LEDC_VAL_NO_CHANGE,
duty, //uint32_t duty_val, duty, //uint32_t duty_val,
1, //uint32_t increase, 1, //uint32_t increase,
1, //uint32_t duty_num, 0, //uint32_t duty_num,
1, //uint32_t duty_cycle, 0, //uint32_t duty_cycle,
0 //uint32_t duty_scale 0 //uint32_t duty_scale
); );
portEXIT_CRITICAL(&ledc_spinlock);
_ledc_fade_hw_release(speed_mode, channel); _ledc_fade_hw_release(speed_mode, channel);
return ESP_OK; return ESP_OK;
} }
@@ -792,6 +811,7 @@ void IRAM_ATTR ledc_fade_isr(void *arg)
uint32_t speed_mode = 0; uint32_t speed_mode = 0;
uint32_t channel = 0; uint32_t channel = 0;
uint32_t intr_status = 0; uint32_t intr_status = 0;
ledc_fade_fsm_t state;
for (speed_mode = 0; speed_mode < LEDC_SPEED_MODE_MAX; speed_mode++) { for (speed_mode = 0; speed_mode < LEDC_SPEED_MODE_MAX; speed_mode++) {
if (p_ledc_obj[speed_mode] == NULL) { 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); ledc_calc_fade_end_channel(&intr_status, &channel);
// clear interrupt // clear interrupt
portENTER_CRITICAL(&ledc_spinlock);
ledc_hal_clear_fade_end_intr_status(&(p_ledc_obj[speed_mode]->ledc_hal), channel); ledc_hal_clear_fade_end_intr_status(&(p_ledc_obj[speed_mode]->ledc_hal), channel);
portEXIT_CRITICAL(&ledc_spinlock);
if (s_ledc_fade_rec[speed_mode][channel] == NULL) { if (s_ledc_fade_rec[speed_mode][channel] == NULL) {
//fade object not initialized yet. //fade object not initialized yet.
continue; 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; uint32_t duty_cur = 0;
ledc_hal_get_duty(&(p_ledc_obj[speed_mode]->ledc_hal), channel, &duty_cur); 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; uint32_t duty_tar = s_ledc_fade_rec[speed_mode][channel]->target_duty;
int scale = s_ledc_fade_rec[speed_mode][channel]->scale; int scale = s_ledc_fade_rec[speed_mode][channel]->scale;
if (duty_cur == duty_tar || scale == 0) { 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 = { bool finished = false;
.event = LEDC_FADE_END_EVT, portENTER_CRITICAL_ISR(&ledc_spinlock);
.speed_mode = speed_mode, state = s_ledc_fade_rec[speed_mode][channel]->fsm;
.channel = channel, assert(state != LEDC_FSM_IDLE && state != LEDC_FSM_HW_FADE);
.duty = duty_cur 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; ledc_cb_t fade_cb = s_ledc_fade_rec[speed_mode][channel]->ledc_fade_callback;
if (fade_cb) { 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(&param, s_ledc_fade_rec[speed_mode][channel]->cb_user_arg); 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;
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(&ledc_spinlock);
ledc_hal_set_duty_start(&(p_ledc_obj[speed_mode]->ledc_hal), channel, true);
portEXIT_CRITICAL(&ledc_spinlock);
} }
} }
if (HPTaskAwoken == pdTRUE || cb_yield) { 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 #endif
s_ledc_fade_rec[speed_mode][channel]->ledc_fade_mux = xSemaphoreCreateMutex(); s_ledc_fade_rec[speed_mode][channel]->ledc_fade_mux = xSemaphoreCreateMutex();
xSemaphoreGive(s_ledc_fade_rec[speed_mode][channel]->ledc_fade_sem); 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] if (s_ledc_fade_rec[speed_mode][channel]
&& s_ledc_fade_rec[speed_mode][channel]->ledc_fade_mux && 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); portEXIT_CRITICAL(&ledc_spinlock);
if (scale > 0 && step_num > 0) { 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); 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", 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); duty_cur, target_duty, step_num, cycle_num, scale, dir);
} else { } else {
portENTER_CRITICAL(&ledc_spinlock);
ledc_duty_config(speed_mode, channel, LEDC_VAL_NO_CHANGE, target_duty, dir, 0, 1, 0); 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); ESP_LOGD(LEDC_TAG, "Set to target duty: %d", target_duty);
} }
return ESP_OK; return ESP_OK;
@@ -992,14 +1048,24 @@ 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) 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 // Clear interrupt status of channel
ledc_hal_clear_fade_end_intr_status(&(p_ledc_obj[speed_mode]->ledc_hal), channel); ledc_hal_clear_fade_end_intr_status(&(p_ledc_obj[speed_mode]->ledc_hal), channel);
// Enable interrupt for channel // Enable interrupt for channel
portENTER_CRITICAL(&ledc_spinlock);
ledc_enable_intr_type(speed_mode, channel, LEDC_INTR_FADE_END); 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); ledc_update_duty(speed_mode, channel);
if (fade_mode == LEDC_FADE_WAIT_DONE) { 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);
} }
} }
@@ -1041,9 +1107,59 @@ 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_CHECK(p_ledc_obj[speed_mode] != NULL, LEDC_NOT_INIT, ESP_ERR_INVALID_STATE);
_ledc_fade_hw_acquire(speed_mode, channel); _ledc_fade_hw_acquire(speed_mode, channel);
_ledc_fade_start(speed_mode, channel, fade_mode); _ledc_fade_start(speed_mode, channel, fade_mode);
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); _ledc_fade_hw_release(speed_mode, channel);
return ESP_OK; return ESP_OK;
} }
#endif
esp_err_t ledc_fade_func_install(int intr_alloc_flags) esp_err_t ledc_fade_func_install(int intr_alloc_flags)
{ {
@@ -1086,14 +1202,15 @@ 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(speed_mode < LEDC_SPEED_MODE_MAX, "speed_mode");
LEDC_ARG_CHECK(channel < LEDC_CHANNEL_MAX, "channel"); LEDC_ARG_CHECK(channel < LEDC_CHANNEL_MAX, "channel");
LEDC_ARG_CHECK(duty <= ledc_get_max_duty(speed_mode, channel), "target_duty"); 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(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_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_fade_hw_acquire(speed_mode, channel); _ledc_fade_hw_acquire(speed_mode, channel);
_ledc_set_fade_with_step(speed_mode, channel, duty, 0, 1); portENTER_CRITICAL(&ledc_spinlock);
_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);
portEXIT_CRITICAL(&ledc_spinlock);
_ledc_fade_hw_release(speed_mode, channel); _ledc_fade_hw_release(speed_mode, channel);
_ledc_op_lock_release(speed_mode, channel);
return ESP_OK; return ESP_OK;
} }
@@ -1109,9 +1226,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_fade_hw_acquire(speed_mode, channel);
_ledc_set_fade_with_time(speed_mode, channel, target_duty, max_fade_time_ms); _ledc_set_fade_with_time(speed_mode, channel, target_duty, max_fade_time_ms);
_ledc_fade_start(speed_mode, channel, fade_mode); _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); _ledc_op_lock_release(speed_mode, channel);
return ESP_OK; return ESP_OK;
} }
@@ -1130,9 +1244,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_fade_hw_acquire(speed_mode, channel);
_ledc_set_fade_with_step(speed_mode, channel, target_duty, scale, cycle_num); _ledc_set_fade_with_step(speed_mode, channel, target_duty, scale, cycle_num);
_ledc_fade_start(speed_mode, channel, fade_mode); _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); _ledc_op_lock_release(speed_mode, channel);
return ESP_OK; return ESP_OK;
} }

View File

@@ -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 * SPDX-License-Identifier: Apache-2.0
*/ */
@@ -34,6 +34,8 @@
#define HIGHEST_LIMIT 10000 #define HIGHEST_LIMIT 10000
#define LOWEST_LIMIT -10000 #define LOWEST_LIMIT -10000
#define TEST_PWM_FREQ 2000
#if SOC_LEDC_SUPPORT_HS_MODE #if SOC_LEDC_SUPPORT_HS_MODE
#define TEST_SPEED_MODE LEDC_HIGH_SPEED_MODE #define TEST_SPEED_MODE LEDC_HIGH_SPEED_MODE
#define SPEED_MODE_LIST {LEDC_HIGH_SPEED_MODE, LEDC_LOW_SPEED_MODE} #define SPEED_MODE_LIST {LEDC_HIGH_SPEED_MODE, LEDC_LOW_SPEED_MODE}
@@ -63,17 +65,31 @@ static ledc_timer_config_t create_default_timer_config(void)
ledc_time_config.speed_mode = TEST_SPEED_MODE; ledc_time_config.speed_mode = TEST_SPEED_MODE;
ledc_time_config.duty_resolution = LEDC_TIMER_13_BIT; ledc_time_config.duty_resolution = LEDC_TIMER_13_BIT;
ledc_time_config.timer_num = LEDC_TIMER_0; ledc_time_config.timer_num = LEDC_TIMER_0;
ledc_time_config.freq_hz = 2000; ledc_time_config.freq_hz = TEST_PWM_FREQ;
ledc_time_config.clk_cfg = LEDC_USE_APB_CLK; ledc_time_config.clk_cfg = LEDC_USE_APB_CLK;
return ledc_time_config; 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_RATE_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) 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_set_duty(speed_mode, channel, duty));
TEST_ESP_OK(ledc_update_duty(speed_mode, channel)); TEST_ESP_OK(ledc_update_duty(speed_mode, channel));
vTaskDelay(1000 / portTICK_RATE_MS); vTaskDelay(100 / portTICK_RATE_MS);
TEST_ASSERT_EQUAL_INT32(ledc_get_duty(speed_mode, channel), duty); TEST_ASSERT_EQUAL_INT32(duty, ledc_get_duty(speed_mode, channel));
} }
// use logic analyzer to view // use logic analyzer to view
@@ -168,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)); 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, 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, 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) #endif // !TEMPORARY_DISABLED_FOR_TARGETS(ESP32S2, ESP32S3)
@@ -302,9 +318,9 @@ TEST_CASE("LEDC memory leak test", "[ledc]")
TEST_ESP_OK(ledc_stop(ledc_time_config.speed_mode, LEDC_CHANNEL_0, 0)); 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 // duty should be manually checked from the waveform using a logic analyzer
// can't get it directly, so set it "ignore" // this test is enabled only for testting the settings
TEST_CASE("LEDC set and get dut(with logic analyzer)", "[ledc][ignore]") 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_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; ledc_mode_t speed_mode_list[LEDC_SPEED_MODE_MAX] = SPEED_MODE_LIST;
@@ -315,64 +331,142 @@ 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; const ledc_mode_t test_speed_mode = TEST_SPEED_MODE;
ledc_channel_config_t ledc_ch_config = initialize_channel_config(); fade_setup();
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_set_fade_with_time(test_speed_mode, LEDC_CHANNEL_0, 4000, 200));
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_time(test_speed_mode, LEDC_CHANNEL_0, 4000, 1000));
TEST_ESP_OK(ledc_fade_start(test_speed_mode, LEDC_CHANNEL_0, LEDC_FADE_WAIT_DONE)); 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) TEST_ASSERT_EQUAL_INT32(4000, ledc_get_duty(test_speed_mode, LEDC_CHANNEL_0));
vTaskDelay(1050 / portTICK_RATE_MS);
TEST_ASSERT_EQUAL_INT32(ledc_get_duty(test_speed_mode, LEDC_CHANNEL_0), 4000);
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)); TEST_ESP_OK(ledc_fade_start(test_speed_mode, LEDC_CHANNEL_0, LEDC_FADE_NO_WAIT));
vTaskDelay(1050 / portTICK_RATE_MS); // duty should not be too far from initial value
TEST_ASSERT_EQUAL_INT32(ledc_get_duty(test_speed_mode, LEDC_CHANNEL_0), 0); TEST_ASSERT_INT32_WITHIN(20, 4000, ledc_get_duty(test_speed_mode, LEDC_CHANNEL_0));
vTaskDelay(210 / portTICK_RATE_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(); 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; const ledc_mode_t test_speed_mode = TEST_SPEED_MODE;
ledc_channel_config_t ledc_ch_config = initialize_channel_config(); fade_setup();
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_set_fade_with_step(test_speed_mode, LEDC_CHANNEL_0, 4000, 4, 1));
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_fade_start(test_speed_mode, LEDC_CHANNEL_0, LEDC_FADE_WAIT_DONE)); 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(4000, ledc_get_duty(test_speed_mode, LEDC_CHANNEL_0));
TEST_ASSERT_EQUAL_INT32(ledc_get_duty(test_speed_mode, LEDC_CHANNEL_0), 4000);
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)); TEST_ESP_OK(ledc_fade_start(test_speed_mode, LEDC_CHANNEL_0, LEDC_FADE_NO_WAIT));
vTaskDelay(1050 / portTICK_RATE_MS); // duty should not be too far from initial value
TEST_ASSERT_EQUAL_INT32(ledc_get_duty(test_speed_mode, LEDC_CHANNEL_0), 0); TEST_ASSERT_INT32_WITHIN(20, 4000, ledc_get_duty(test_speed_mode, LEDC_CHANNEL_0));
vTaskDelay(525 / portTICK_RATE_MS);
TEST_ASSERT_EQUAL_INT32(0, ledc_get_duty(test_speed_mode, LEDC_CHANNEL_0));
//scaler=0 check //scaler=0 check
TEST_ASSERT(ledc_set_fade_with_step(test_speed_mode, LEDC_CHANNEL_0, 4000, 0, 1) == ESP_ERR_INVALID_ARG); 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(); 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_RATE_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_RATE_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_RATE_MS);
TEST_ASSERT_EQUAL_INT32(500, ledc_get_duty(test_speed_mode, LEDC_CHANNEL_0));
//deinitialize fade service
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 SOC_PCNT_SUPPORTED
#if !TEMPORARY_DISABLED_FOR_TARGETS(ESP32S2, ESP32S3) #if !TEMPORARY_DISABLED_FOR_TARGETS(ESP32S2, ESP32S3)
@@ -424,7 +518,7 @@ TEST_CASE("LEDC timer set", "[ledc][test_env=UT_T1_LEDC]")
count = wave_count(1000); count = wave_count(1000);
TEST_ASSERT_UINT32_WITHIN(10, count, freq_get); TEST_ASSERT_UINT32_WITHIN(10, count, freq_get);
//set timer 3 as 500Hz, use APB_CLK //set timer 0 as 500Hz, use APB_CLK
TEST_ESP_OK(ledc_timer_set(test_speed_mode, LEDC_TIMER_0, 5000, 13, LEDC_APB_CLK)); TEST_ESP_OK(ledc_timer_set(test_speed_mode, LEDC_TIMER_0, 5000, 13, LEDC_APB_CLK));
TEST_ESP_OK(ledc_timer_rst(test_speed_mode, LEDC_TIMER_0)); TEST_ESP_OK(ledc_timer_rst(test_speed_mode, LEDC_TIMER_0));
TEST_ASSERT_EQUAL_INT32(ledc_get_freq(test_speed_mode, LEDC_TIMER_0), 500); TEST_ASSERT_EQUAL_INT32(ledc_get_freq(test_speed_mode, LEDC_TIMER_0), 500);

View File

@@ -251,6 +251,10 @@ config SOC_LEDC_TIMER_BIT_WIDE_NUM
int int
default 14 default 14
config SOC_LEDC_SUPPORT_FADE_STOP
bool
default y
config SOC_MPU_CONFIGURABLE_REGIONS_SUPPORTED config SOC_MPU_CONFIGURABLE_REGIONS_SUPPORTED
bool bool
default n default n

View File

@@ -141,6 +141,7 @@
#define SOC_LEDC_SUPPORT_XTAL_CLOCK (1) #define SOC_LEDC_SUPPORT_XTAL_CLOCK (1)
#define SOC_LEDC_CHANNEL_NUM (6) #define SOC_LEDC_CHANNEL_NUM (6)
#define SOC_LEDC_TIMER_BIT_WIDE_NUM (14) #define SOC_LEDC_TIMER_BIT_WIDE_NUM (14)
#define SOC_LEDC_SUPPORT_FADE_STOP (1)
/*-------------------------- MPU CAPS ----------------------------------------*/ /*-------------------------- MPU CAPS ----------------------------------------*/
#define SOC_MPU_CONFIGURABLE_REGIONS_SUPPORTED 0 #define SOC_MPU_CONFIGURABLE_REGIONS_SUPPORTED 0

View File

@@ -319,6 +319,10 @@ config SOC_LEDC_TIMER_BIT_WIDE_NUM
int int
default 14 default 14
config SOC_LEDC_SUPPORT_FADE_STOP
bool
default y
config SOC_MPU_CONFIGURABLE_REGIONS_SUPPORTED config SOC_MPU_CONFIGURABLE_REGIONS_SUPPORTED
bool bool
default n default n

View File

@@ -162,6 +162,7 @@
#define SOC_LEDC_SUPPORT_XTAL_CLOCK (1) #define SOC_LEDC_SUPPORT_XTAL_CLOCK (1)
#define SOC_LEDC_CHANNEL_NUM (6) #define SOC_LEDC_CHANNEL_NUM (6)
#define SOC_LEDC_TIMER_BIT_WIDE_NUM (14) #define SOC_LEDC_TIMER_BIT_WIDE_NUM (14)
#define SOC_LEDC_SUPPORT_FADE_STOP (1)
/*-------------------------- MPU CAPS ----------------------------------------*/ /*-------------------------- MPU CAPS ----------------------------------------*/
#define SOC_MPU_CONFIGURABLE_REGIONS_SUPPORTED 0 #define SOC_MPU_CONFIGURABLE_REGIONS_SUPPORTED 0

View File

@@ -299,6 +299,10 @@ config SOC_LEDC_TIMER_BIT_WIDE_NUM
int int
default 14 default 14
config SOC_LEDC_SUPPORT_FADE_STOP
bool
default y
config SOC_MPU_CONFIGURABLE_REGIONS_SUPPORTED config SOC_MPU_CONFIGURABLE_REGIONS_SUPPORTED
bool bool
default n default n

View File

@@ -156,6 +156,7 @@
#define SOC_LEDC_SUPPORT_XTAL_CLOCK (1) #define SOC_LEDC_SUPPORT_XTAL_CLOCK (1)
#define SOC_LEDC_CHANNEL_NUM (6) #define SOC_LEDC_CHANNEL_NUM (6)
#define SOC_LEDC_TIMER_BIT_WIDE_NUM (14) #define SOC_LEDC_TIMER_BIT_WIDE_NUM (14)
#define SOC_LEDC_SUPPORT_FADE_STOP (1)
/*-------------------------- MPU CAPS ----------------------------------------*/ /*-------------------------- MPU CAPS ----------------------------------------*/
#define SOC_MPU_CONFIGURABLE_REGIONS_SUPPORTED 0 #define SOC_MPU_CONFIGURABLE_REGIONS_SUPPORTED 0

View File

@@ -347,6 +347,10 @@ config SOC_LEDC_TIMER_BIT_WIDE_NUM
int int
default 14 default 14
config SOC_LEDC_SUPPORT_FADE_STOP
bool
default y
config SOC_MPU_CONFIGURABLE_REGIONS_SUPPORTED config SOC_MPU_CONFIGURABLE_REGIONS_SUPPORTED
bool bool
default n default n

View File

@@ -180,6 +180,7 @@
#define SOC_LEDC_SUPPORT_XTAL_CLOCK (1) #define SOC_LEDC_SUPPORT_XTAL_CLOCK (1)
#define SOC_LEDC_CHANNEL_NUM (8) #define SOC_LEDC_CHANNEL_NUM (8)
#define SOC_LEDC_TIMER_BIT_WIDE_NUM (14) #define SOC_LEDC_TIMER_BIT_WIDE_NUM (14)
#define SOC_LEDC_SUPPORT_FADE_STOP (1)
/*-------------------------- MPU CAPS ----------------------------------------*/ /*-------------------------- MPU CAPS ----------------------------------------*/
//TODO: correct the caller and remove unsupported lines //TODO: correct the caller and remove unsupported lines

View File

@@ -59,6 +59,10 @@ config SOC_LEDC_TIMER_BIT_WIDE_NUM
int int
default 14 default 14
config SOC_LEDC_SUPPORT_FADE_STOP
bool
default y
config SOC_MPU_CONFIGURABLE_REGIONS_SUPPORTED config SOC_MPU_CONFIGURABLE_REGIONS_SUPPORTED
bool bool
default n default n

View File

@@ -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 * SPDX-License-Identifier: Apache-2.0
*/ */
@@ -13,6 +13,7 @@ extern "C" {
#define SOC_LEDC_SUPPORT_XTAL_CLOCK (1) #define SOC_LEDC_SUPPORT_XTAL_CLOCK (1)
#define SOC_LEDC_CHANNEL_NUM 8 #define SOC_LEDC_CHANNEL_NUM 8
#define SOC_LEDC_TIMER_BIT_WIDE_NUM (14) #define SOC_LEDC_TIMER_BIT_WIDE_NUM (14)
#define SOC_LEDC_SUPPORT_FADE_STOP (1)
#ifdef __cplusplus #ifdef __cplusplus
} }

View File

@@ -9,10 +9,9 @@
#include <stdio.h> #include <stdio.h>
#include "freertos/FreeRTOS.h" #include "freertos/FreeRTOS.h"
#include "freertos/task.h" #include "freertos/task.h"
#include "freertos/semphr.h"
#include "driver/ledc.h" #include "driver/ledc.h"
#include "esp_err.h" #include "esp_err.h"
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
/* /*
* About this example * About this example