diff --git a/components/driver/ledc/include/driver/ledc.h b/components/driver/ledc/include/driver/ledc.h index fd51b563e8d..509b81634d3 100644 --- a/components/driver/ledc/include/driver/ledc.h +++ b/components/driver/ledc/include/driver/ledc.h @@ -530,6 +530,157 @@ esp_err_t ledc_set_fade_step_and_start(ledc_mode_t speed_mode, ledc_channel_t ch */ esp_err_t ledc_cb_register(ledc_mode_t speed_mode, ledc_channel_t channel, ledc_cbs_t *cbs, void *user_arg); +#if SOC_LEDC_GAMMA_CURVE_FADE_SUPPORTED +/** + * @brief Structure for the fade parameters for one hardware fade to be written to gamma wr register + * + * @verbatim + * duty ^ ONE HW LINEAR FADE + * | + * | + * | + * | + * start_duty + scale * n = end_duty |. . . . . . . . . . . . . . . . . . . . . . . . . .+- + * | | + * | | + * | +--------+ + * | | . + * | | . + * | -------+ . + * | . . + * | . . + * | . . + * | . . + * ^ --- |. . . . . . . . . .+-------- . + * scale| | | . + * | | | . + * v --- |. . . . .+---------+ . + * | | . . + * | | . . + * start_duty +---------+ . . + * | . . . + * | . . . + * +-----------------------------------------------------------> + * PWM cycle + * | | | | + * | 1 step | 1 step | | + * |<------->|<------->| | + * | m cycles m cycles | + * | | + * <---------------------------------------------------> + * n total steps + * cycles = m * n + * @endverbatim + * + * @note Be aware of the maximum value available on each element + */ +typedef struct { + uint32_t dir : 1; /*!< Duty change direction. Set 1 as increase, 0 as decrease */ + uint32_t cycle_num : SOC_LEDC_FADE_PARAMS_BIT_WIDTH; /*!< Number of PWM cycles of each step [0, 2**SOC_LEDC_FADE_PARAMS_BIT_WIDTH-1] */ + uint32_t scale : SOC_LEDC_FADE_PARAMS_BIT_WIDTH; /*!< Duty change of each step [0, 2**SOC_LEDC_FADE_PARAMS_BIT_WIDTH-1] */ + uint32_t step_num : SOC_LEDC_FADE_PARAMS_BIT_WIDTH; /*!< Total number of steps in one hardware fade [0, 2**SOC_LEDC_FADE_PARAMS_BIT_WIDTH-1] */ +} ledc_fade_param_config_t; + +/** + * @brief Set a LEDC multi-fade + * + * @note Call `ledc_fade_func_install()` once before calling this function. + * Call `ledc_fade_start()` after this to start fading. + * @note This function is not thread-safe, do not call it to control one LEDC channel in different tasks at the same time. + * A thread-safe version of API is ledc_set_multi_fade_and_start + * @note This function does not prohibit from duty overflow. User should take care of this by themselves. If duty + * overflow happens, the PWM signal will suddenly change from 100% duty cycle to 0%, or the other way around. + * + * @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 start_duty Set the start of the gradient duty, the range of duty setting is [0, (2**duty_resolution)] + * @param fade_params_list Pointer to the array of fade parameters for a multi-fade + * @param list_len Length of the fade_params_list, i.e. number of fade ranges for a multi-fade (1 - SOC_LEDC_GAMMA_CURVE_FADE_RANGE_MAX) + * @return + * - ESP_OK Success + * - ESP_ERR_INVALID_ARG Parameter error + * - ESP_ERR_INVALID_STATE Fade function not installed + * - ESP_FAIL Fade function init error + */ +esp_err_t ledc_set_multi_fade(ledc_mode_t speed_mode, ledc_channel_t channel, uint32_t start_duty, const ledc_fade_param_config_t *fade_params_list, uint32_t list_len); + +/** + * @brief A thread-safe API to set and start LEDC multi-fade function + * + * @note Call `ledc_fade_func_install()` once before calling this function. + * @note Fade will always begin from the current duty cycle. Make sure it is stable and synchronized to the desired + * initial value before calling this function. Otherwise, you may see unexpected duty change. + * @note This function does not prohibit from duty overflow. User should take care of this by themselves. If duty + * overflow happens, the PWM signal will suddenly change from 100% duty cycle to 0%, or the other way around. + * + * @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 start_duty Set the start of the gradient duty, the range of duty setting is [0, (2**duty_resolution)] + * @param fade_params_list Pointer to the array of fade parameters for a multi-fade + * @param list_len Length of the fade_params_list, i.e. number of fade ranges for a multi-fade (1 - SOC_LEDC_GAMMA_CURVE_FADE_RANGE_MAX) + * @param fade_mode Choose blocking or non-blocking mode + * @return + * - ESP_OK Success + * - ESP_ERR_INVALID_ARG Parameter error + * - ESP_ERR_INVALID_STATE Fade function not installed + * - ESP_FAIL Fade function init error + */ +esp_err_t ledc_set_multi_fade_and_start(ledc_mode_t speed_mode, ledc_channel_t channel, uint32_t start_duty, const ledc_fade_param_config_t *fade_params_list, uint32_t list_len, ledc_fade_mode_t fade_mode); + +/** + * @brief Helper function to fill the fade params for a multi-fade. Useful if desires a gamma curve fading. + * + * @note The fade params are calculated based on the given start_duty and end_duty. If the duty is not at + * the start duty (gamma-corrected) when the fade begins, you may see undesired brightness change. + * Therefore, please always remember thet when passing the fade_params to either `ledc_set_multi_fade` or + * `ledc_set_multi_fade_and start`, the start_duty argument has to be the gamma-corrected start_duty. + * + * @param[in] speed_mode Select the LEDC channel group with specified speed mode. Note that not all targets support high speed mode. + * @param[in] channel LEDC channel index (0 - LEDC_CHANNEL_MAX-1), select from ledc_channel_t + * @param[in] start_duty Duty cycle [0, (2**duty_resolution)] where the multi-fade begins with. This value should be a non-gamma-corrected duty cycle. + * @param[in] end_duty Duty cycle [0, (2**duty_resolution)] where the multi-fade ends with. This value should be a non-gamma-corrected duty cycle. + * @param[in] linear_phase_num Number of linear fades to simulate a gamma curved fade (1 - SOC_LEDC_GAMMA_CURVE_FADE_RANGE_MAX) + * @param[in] max_fade_time_ms The maximum time of the fading ( ms ). + * @param[in] gamma_correction_operator User provided gamma correction function. The function argument should be able to + * take any value within [0, (2**duty_resolution)]. And returns the gamma-corrected duty cycle. + * @param[in] fade_params_list_size The size of the fade_params_list user allocated (1 - SOC_LEDC_GAMMA_CURVE_FADE_RANGE_MAX) + * @param[out] fade_params_list Pointer to the array of ledc_fade_param_config_t structure + * @param[out] hw_fade_range_num Number of fade ranges for this multi-fade + * @return + * - ESP_OK Success + * - ESP_ERR_INVALID_ARG Parameter error + * - ESP_ERR_INVALID_STATE LEDC not initialized + * - ESP_ERR_NO_MEM Out of memory + * - ESP_FAIL Required number of hardware ranges exceeds the size of the ledc_fade_param_config_t array user allocated + */ +esp_err_t ledc_fill_multi_fade_param_list(ledc_mode_t speed_mode, ledc_channel_t channel, + uint32_t start_duty, uint32_t end_duty, + uint32_t linear_phase_num, uint32_t max_fade_time_ms, + uint32_t (* gamma_correction_operator)(uint32_t), + uint32_t fade_params_list_size, + ledc_fade_param_config_t *fade_params_list, uint32_t *hw_fade_range_num); + +/** + * @brief Get the fade parameters that are stored in gamma ram for a certain fade range + * + * Gamma ram is where saves the fade parameters for each fade range. The fade parameters are written in during fade + * configuration. When fade begins, the duty will change according to the parameters in gamma ram. + * + * @param[in] speed_mode Select the LEDC channel group with specified speed mode. Note that not all targets support high speed mode. + * @param[in] channel LEDC channel index (0 - LEDC_CHANNEL_MAX-1), select from ledc_channel_t + * @param[in] range Range index (0 - (SOC_LEDC_GAMMA_CURVE_FADE_RANGE_MAX-1)), it specifies to which range in gamma ram to read + * @param[out] dir Pointer to accept fade direction value + * @param[out] cycle Pointer to accept fade cycle value + * @param[out] scale Pointer to accept fade scale value + * @param[out] step Pointer to accept fade step value + * @return + * - ESP_OK Success + * - ESP_ERR_INVALID_ARG Parameter error + * - ESP_ERR_INVALID_STATE LEDC not initialized + */ +esp_err_t ledc_read_fade_param(ledc_mode_t speed_mode, ledc_channel_t channel, uint32_t range, uint32_t *dir, uint32_t *cycle, uint32_t *scale, uint32_t *step); +#endif // SOC_LEDC_GAMMA_CURVE_FADE_SUPPORTED + #ifdef __cplusplus } #endif diff --git a/components/driver/ledc/ledc.c b/components/driver/ledc/ledc.c index fe6f156e739..3e129913ba9 100644 --- a/components/driver/ledc/ledc.c +++ b/components/driver/ledc/ledc.c @@ -209,8 +209,8 @@ int duty_val, ledc_duty_direction_t duty_direction, uint32_t duty_num, uint32_t ledc_hal_set_duty_num(&(p_ledc_obj[speed_mode]->ledc_hal), channel, duty_num); 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); -#if SOC_LEDC_GAMMA_FADE_RANGE_MAX > 1 - ledc_hal_set_duty_range(&(p_ledc_obj[speed_mode]->ledc_hal), channel, 0); +#if SOC_LEDC_GAMMA_CURVE_FADE_SUPPORTED + ledc_hal_set_duty_range_wr_addr(&(p_ledc_obj[speed_mode]->ledc_hal), channel, 0); ledc_hal_set_range_number(&(p_ledc_obj[speed_mode]->ledc_hal), channel, 1); #endif return ESP_OK; @@ -840,6 +840,14 @@ 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); uint32_t duty_tar = s_ledc_fade_rec[speed_mode][channel]->target_duty; +#if SOC_LEDC_GAMMA_CURVE_FADE_SUPPORTED + // If a multi-fade is done, check that target duty computed in sw is equal to the duty at the end of the fade + uint32_t range_num; + ledc_hal_get_range_number(&(p_ledc_obj[speed_mode]->ledc_hal), channel, &range_num); + if (range_num > 1) { + assert(duty_cur == duty_tar); + } +#endif int scale = s_ledc_fade_rec[speed_mode][channel]->scale; if (duty_cur == duty_tar || scale == 0) { // Target duty has reached @@ -1246,3 +1254,222 @@ esp_err_t ledc_set_fade_step_and_start(ledc_mode_t speed_mode, ledc_channel_t ch _ledc_op_lock_release(speed_mode, channel); return ESP_OK; } + +#if SOC_LEDC_GAMMA_CURVE_FADE_SUPPORTED +static esp_err_t _ledc_set_multi_fade(ledc_mode_t speed_mode, ledc_channel_t channel, uint32_t start_duty, const ledc_fade_param_config_t *fade_params_list, uint32_t list_len) +{ + uint32_t max_duty = ledc_get_max_duty(speed_mode, channel); + LEDC_ARG_CHECK(start_duty <= max_duty, "start_duty"); + portENTER_CRITICAL(&ledc_spinlock); + ledc_hal_set_duty_int_part(&(p_ledc_obj[speed_mode]->ledc_hal), channel, start_duty); + for (int i = 0; i < list_len; i++) { + ledc_fade_param_config_t fade_param = fade_params_list[i]; + ledc_hal_set_duty_direction(&(p_ledc_obj[speed_mode]->ledc_hal), channel, fade_param.dir); + ledc_hal_set_duty_cycle(&(p_ledc_obj[speed_mode]->ledc_hal), channel, fade_param.cycle_num); + ledc_hal_set_duty_scale(&(p_ledc_obj[speed_mode]->ledc_hal), channel, fade_param.scale); + ledc_hal_set_duty_num(&(p_ledc_obj[speed_mode]->ledc_hal), channel, fade_param.step_num); + ledc_hal_set_duty_range_wr_addr(&(p_ledc_obj[speed_mode]->ledc_hal), channel, i); + } + ledc_hal_set_range_number(&(p_ledc_obj[speed_mode]->ledc_hal), channel, list_len); + portEXIT_CRITICAL(&ledc_spinlock); + // Calculate target duty, and take account for overflow + uint32_t target_duty = start_duty; + for (int i = 0; i < list_len; i++) { + uint32_t delta_duty = (fade_params_list[i].step_num * fade_params_list[i].scale) % (max_duty + 1); + if (fade_params_list[i].dir == LEDC_DUTY_DIR_INCREASE) { + target_duty += delta_duty; + if (target_duty > max_duty) { + target_duty -= max_duty + 1; + } + } else { + if (delta_duty > target_duty) { + target_duty += max_duty + 1; + } + target_duty -= delta_duty; + } + } + // Set interrupt exit criteria + s_ledc_fade_rec[speed_mode][channel]->target_duty = target_duty; + s_ledc_fade_rec[speed_mode][channel]->scale = fade_params_list[list_len - 1].scale; + return ESP_OK; +} + +esp_err_t ledc_set_multi_fade(ledc_mode_t speed_mode, ledc_channel_t channel, uint32_t start_duty, const ledc_fade_param_config_t *fade_params_list, uint32_t list_len) +{ + LEDC_ARG_CHECK(speed_mode < LEDC_SPEED_MODE_MAX, "speed_mode"); + LEDC_ARG_CHECK(channel < LEDC_CHANNEL_MAX, "channel"); + LEDC_ARG_CHECK(list_len <= SOC_LEDC_GAMMA_CURVE_FADE_RANGE_MAX, "list_len"); + LEDC_ARG_CHECK(fade_params_list, "fade_params_list"); + LEDC_CHECK(p_ledc_obj[speed_mode] != NULL, LEDC_NOT_INIT, ESP_ERR_INVALID_STATE); + LEDC_CHECK(ledc_fade_channel_init_check(speed_mode, channel) == ESP_OK, LEDC_FADE_INIT_ERROR_STR, ESP_FAIL); + + _ledc_fade_hw_acquire(speed_mode, channel); + esp_err_t ret = _ledc_set_multi_fade(speed_mode, channel, start_duty, fade_params_list, list_len); + _ledc_fade_hw_release(speed_mode, channel); + return ret; +} + +esp_err_t ledc_set_multi_fade_and_start(ledc_mode_t speed_mode, ledc_channel_t channel, uint32_t start_duty, const ledc_fade_param_config_t *fade_params_list, uint32_t list_len, ledc_fade_mode_t fade_mode) +{ + LEDC_ARG_CHECK(speed_mode < LEDC_SPEED_MODE_MAX, "speed_mode"); + LEDC_ARG_CHECK(channel < LEDC_CHANNEL_MAX, "channel"); + LEDC_ARG_CHECK(list_len <= SOC_LEDC_GAMMA_CURVE_FADE_RANGE_MAX, "list_len"); + LEDC_ARG_CHECK(fade_params_list, "fade_params_list"); + LEDC_ARG_CHECK(fade_mode < LEDC_FADE_MAX, "fade_mode"); + 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_fade_hw_acquire(speed_mode, channel); + esp_err_t ret = _ledc_set_multi_fade(speed_mode, channel, start_duty, fade_params_list, list_len); + if (ret != ESP_OK) { + _ledc_fade_hw_release(speed_mode, channel); + } else { + _ledc_fade_start(speed_mode, channel, fade_mode); + } + _ledc_op_lock_release(speed_mode, channel); + return ret; +} + +esp_err_t ledc_fill_multi_fade_param_list(ledc_mode_t speed_mode, ledc_channel_t channel, + uint32_t start_duty, uint32_t end_duty, + uint32_t linear_phase_num, uint32_t max_fade_time_ms, + uint32_t (* gamma_correction_operator)(uint32_t), + uint32_t fade_params_list_size, + ledc_fade_param_config_t *fade_params_list, uint32_t *hw_fade_range_num) +{ + LEDC_ARG_CHECK(speed_mode < LEDC_SPEED_MODE_MAX, "speed_mode"); + LEDC_ARG_CHECK(channel < LEDC_CHANNEL_MAX, "channel"); + LEDC_ARG_CHECK(linear_phase_num > 0 && linear_phase_num <= SOC_LEDC_GAMMA_CURVE_FADE_RANGE_MAX, "linear_phase_num"); + LEDC_ARG_CHECK(gamma_correction_operator, "gamma_correction_operator"); + LEDC_ARG_CHECK(fade_params_list_size <= SOC_LEDC_GAMMA_CURVE_FADE_RANGE_MAX, "fade_params_list_size"); + LEDC_ARG_CHECK(fade_params_list, "fade_params_list"); + LEDC_ARG_CHECK(hw_fade_range_num, "hw_fade_range_num"); + LEDC_CHECK(p_ledc_obj[speed_mode] != NULL, LEDC_NOT_INIT, ESP_ERR_INVALID_STATE); + + uint32_t max_duty = ledc_get_max_duty(speed_mode, channel); + LEDC_ARG_CHECK(start_duty <= max_duty && end_duty <= max_duty, "duty"); + + esp_err_t ret = ESP_OK; + + ledc_timer_t timer_sel; + ledc_hal_get_channel_timer(&(p_ledc_obj[speed_mode]->ledc_hal), channel, &timer_sel); + uint32_t freq = ledc_get_freq(speed_mode, timer_sel); + + uint32_t dir = (end_duty > start_duty) ? LEDC_DUTY_DIR_INCREASE : LEDC_DUTY_DIR_DECREASE; + uint32_t total_cycles = max_fade_time_ms * freq / 1000; + // If no duty change is need, then simplify the case + if (start_duty == end_duty) { + total_cycles = 1; + linear_phase_num = 1; + } + uint32_t avg_cycles_per_phase = total_cycles / linear_phase_num; + if (avg_cycles_per_phase == 0) { + ESP_LOGW(LEDC_TAG, LEDC_FADE_TOO_FAST_STR); + avg_cycles_per_phase = 1; + } + int sgn = (dir == LEDC_DUTY_DIR_INCREASE) ? 1 : (-1); + int32_t delta_brightness_per_phase = sgn * ((sgn * (end_duty - start_duty)) / linear_phase_num); + + // First phase start and end values + uint32_t gamma_corrected_phase_head = gamma_correction_operator(start_duty); + uint32_t gamma_corrected_phase_tail = 0; + int32_t phase_tail = start_duty + delta_brightness_per_phase; + + // Compute raw fade parameters for each linear phase + uint32_t total_fade_range = 0; // To record the required hw fade ranges + uint32_t surplus_cycles_last_phase = 0; + for (int i = 0; i < linear_phase_num; i++) { + uint32_t cycle, scale, step; + gamma_corrected_phase_tail = gamma_correction_operator(phase_tail); + uint32_t duty_delta = (dir == LEDC_DUTY_DIR_INCREASE) ? (gamma_corrected_phase_tail - gamma_corrected_phase_head) : + (gamma_corrected_phase_head - gamma_corrected_phase_tail); + uint32_t cycles_per_phase = avg_cycles_per_phase + surplus_cycles_last_phase; + if (duty_delta == 0) { + scale = 0; + cycle = (cycles_per_phase > LEDC_LL_DUTY_CYCLE_MAX) ? LEDC_LL_DUTY_CYCLE_MAX : cycles_per_phase; + step = 1; + } else if (cycles_per_phase > duty_delta) { + scale = 1; + step = duty_delta; + cycle = cycles_per_phase / duty_delta; + if (cycle > LEDC_LL_DUTY_CYCLE_MAX) { + ESP_LOGW(LEDC_TAG, LEDC_FADE_TOO_SLOW_STR); + cycle = LEDC_LL_DUTY_CYCLE_MAX; + } + } else { + cycle = 1; + scale = duty_delta / cycles_per_phase; + if (scale > LEDC_LL_DUTY_SCALE_MAX) { + ESP_LOGW(LEDC_TAG, LEDC_FADE_TOO_FAST_STR); + scale = LEDC_LL_DUTY_SCALE_MAX; + } + step = duty_delta / scale; + } + + // Prepare for next phase calculation + phase_tail = phase_tail + delta_brightness_per_phase; + if (dir == LEDC_DUTY_DIR_INCREASE) { + gamma_corrected_phase_head += step * scale; + } else { + gamma_corrected_phase_head -= step * scale; + } + surplus_cycles_last_phase = cycles_per_phase - step * cycle; + // If next phase is the last one, then account for all remaining duty and cycles + if (i == linear_phase_num - 2) { + phase_tail = end_duty; + surplus_cycles_last_phase += total_cycles - avg_cycles_per_phase * linear_phase_num; + } + + // Fill into the fade parameter list + // One linear phase might need multiple hardware fade ranges + do { + if (total_fade_range >= fade_params_list_size) { + ret = ESP_FAIL; + break; + } + fade_params_list[total_fade_range].dir = dir; + fade_params_list[total_fade_range].cycle_num = cycle; + fade_params_list[total_fade_range].scale = scale; + fade_params_list[total_fade_range].step_num = (step > LEDC_LL_DUTY_NUM_MAX) ? LEDC_LL_DUTY_NUM_MAX : step; + step -= fade_params_list[total_fade_range].step_num; + total_fade_range += 1; + } while (step > 0); + + if (ret != ESP_OK) { + break; + } + } + + uint32_t remaining_duty_delta = (dir == LEDC_DUTY_DIR_INCREASE) ? (gamma_corrected_phase_tail - gamma_corrected_phase_head) : + (gamma_corrected_phase_head - gamma_corrected_phase_tail); + if (remaining_duty_delta) { + total_fade_range += 1; + } + + ESP_RETURN_ON_FALSE(total_fade_range <= fade_params_list_size, ESP_FAIL, LEDC_TAG, + "hw fade ranges required exceeds the space offered to fill the fade params." + " Please allocate more space, or split into smaller multi-fades, or reduce linear_phase_num"); + + if (remaining_duty_delta) { + fade_params_list[total_fade_range].dir = dir; + fade_params_list[total_fade_range].step_num = 1; + fade_params_list[total_fade_range].cycle_num = 1; + fade_params_list[total_fade_range].scale = remaining_duty_delta; + } + + *hw_fade_range_num = total_fade_range; + return ret; +} + +esp_err_t ledc_read_fade_param(ledc_mode_t speed_mode, ledc_channel_t channel, uint32_t range, uint32_t *dir, uint32_t *cycle, uint32_t *scale, uint32_t *step) +{ + LEDC_ARG_CHECK(speed_mode < LEDC_SPEED_MODE_MAX, "speed_mode"); + LEDC_ARG_CHECK(channel < LEDC_CHANNEL_MAX, "channel"); + LEDC_ARG_CHECK(range < SOC_LEDC_GAMMA_CURVE_FADE_RANGE_MAX, "range"); + LEDC_CHECK(p_ledc_obj[speed_mode] != NULL, LEDC_NOT_INIT, ESP_ERR_INVALID_STATE); + + ledc_hal_get_fade_param(&(p_ledc_obj[speed_mode]->ledc_hal), channel, range, dir, cycle, scale, step); + return ESP_OK; +} +#endif // SOC_LEDC_GAMMA_CURVE_FADE_SUPPORTED diff --git a/components/driver/test_apps/ledc/main/test_ledc.c b/components/driver/test_apps/ledc/main/test_ledc.c index 7c473df6011..c37a6cd8db2 100644 --- a/components/driver/test_apps/ledc/main/test_ledc.c +++ b/components/driver/test_apps/ledc/main/test_ledc.c @@ -84,7 +84,7 @@ static void timer_duty_set_get(ledc_mode_t speed_mode, ledc_channel_t channel, u { TEST_ESP_OK(ledc_set_duty(speed_mode, channel, duty)); TEST_ESP_OK(ledc_update_duty(speed_mode, channel)); - vTaskDelay(100 / portTICK_PERIOD_MS); + vTaskDelay(5 / portTICK_PERIOD_MS); TEST_ASSERT_EQUAL_INT32(duty, ledc_get_duty(speed_mode, channel)); } @@ -103,6 +103,7 @@ static void timer_duty_test(ledc_channel_t channel, ledc_timer_bit_t timer_bit, TEST_ESP_OK(ledc_channel_config(&ledc_ch_config)); TEST_ESP_OK(ledc_timer_config(&ledc_time_config)); + vTaskDelay(5 / portTICK_PERIOD_MS); // duty ratio: (2^duty)/(2^timer_bit) timer_duty_set_get(ledc_ch_config.speed_mode, ledc_ch_config.channel, 0); @@ -390,6 +391,80 @@ TEST_CASE("LEDC fade stop test", "[ledc]") } #endif // SOC_LEDC_SUPPORT_FADE_STOP +#if SOC_LEDC_GAMMA_CURVE_FADE_SUPPORTED +TEST_CASE("LEDC gamma ram write and read test", "[ledc]") +{ + const ledc_mode_t test_speed_mode = TEST_SPEED_MODE; + fade_setup(); + + // Construct fade parameters + ledc_fade_param_config_t *fade_params = (ledc_fade_param_config_t *) heap_caps_calloc(SOC_LEDC_GAMMA_CURVE_FADE_RANGE_MAX, sizeof(ledc_fade_param_config_t), MALLOC_CAP_DEFAULT); + for (int i = 0; i < SOC_LEDC_GAMMA_CURVE_FADE_RANGE_MAX; i++) { + fade_params[i].dir = (i + 1) % 2; + fade_params[i].step_num = i + 1; + fade_params[i].cycle_num = i + 2; + fade_params[i].scale = i + 3; + } + + // Write into gamma ram + TEST_ESP_OK(ledc_set_multi_fade(test_speed_mode, LEDC_CHANNEL_0, 0, fade_params, SOC_LEDC_GAMMA_CURVE_FADE_RANGE_MAX)); + + // Read out from gamma ram and check correctness + for (int i = 0; i < SOC_LEDC_GAMMA_CURVE_FADE_RANGE_MAX; i++) { + uint32_t dir, step, cycle, scale; + ledc_read_fade_param(test_speed_mode, LEDC_CHANNEL_0, i, &dir, &cycle, &scale, &step); + TEST_ASSERT_EQUAL_INT32((i + 1) % 2, dir); + TEST_ASSERT_EQUAL_INT32(i + 1, step); + TEST_ASSERT_EQUAL_INT32(i + 2, cycle); + TEST_ASSERT_EQUAL_INT32(i + 3, scale); + } + + // Deinitialize fade service + ledc_fade_func_uninstall(); +} + +TEST_CASE("LEDC multi fade test", "[ledc]") +{ + const ledc_mode_t test_speed_mode = TEST_SPEED_MODE; + fade_setup(); + + // Construct fade parameters + const ledc_fade_param_config_t fade_params[] = { + {.dir = 1, .step_num = 100, .cycle_num = 1, .scale = 1}, + {.dir = 1, .step_num = 50, .cycle_num = 2, .scale = 2}, + {.dir = 1, .step_num = 200, .cycle_num = 10, .scale = 5}, + {.dir = 0, .step_num = 100, .cycle_num = 5, .scale = 5}, + {.dir = 1, .step_num = 1000, .cycle_num = 1, .scale = 1}, + {.dir = 0, .step_num = 200, .cycle_num = 1, .scale = 1}, + {.dir = 1, .step_num = 1, .cycle_num = 1000, .scale = 1000}, + }; + uint32_t fade_range = 7; + int32_t start_duty = 2000; + int32_t end_duty = start_duty; + uint32_t total_cycles = 0; + for (int i = 0; i < fade_range; i++) { + end_duty += ((fade_params[i].dir == 1) ? (1) : (-1)) * fade_params[i].step_num * fade_params[i].scale; + total_cycles += fade_params[i].step_num * fade_params[i].cycle_num; + } + + TEST_ESP_OK(ledc_set_multi_fade(test_speed_mode, LEDC_CHANNEL_0, start_duty, fade_params, fade_range)); + + int64_t fade_start, fade_end; + fade_start = esp_timer_get_time(); + TEST_ESP_OK(ledc_fade_start(test_speed_mode, LEDC_CHANNEL_0, LEDC_FADE_WAIT_DONE)); + fade_end = esp_timer_get_time(); + int64_t time_ms = (fade_end - fade_start) / 1000; + // Check time escaped is expected + // The time it takes to fade should exactly match with the given parameters, therefore, acceptable error range is small + TEST_ASSERT_TRUE(llabs(time_ms - total_cycles * 1000 / TEST_PWM_FREQ) < 2); + // Check the duty at the end of the fade + TEST_ASSERT_EQUAL_INT32((uint32_t)end_duty, ledc_get_duty(test_speed_mode, LEDC_CHANNEL_0)); + + // Deinitialize fade service + ledc_fade_func_uninstall(); +} +#endif // SOC_LEDC_GAMMA_CURVE_FADE_SUPPORTED + #if SOC_PCNT_SUPPORTED // Note. C3, C2, H4 do not have PCNT peripheral, the following test cases cannot be tested #include "driver/pulse_cnt.h" diff --git a/components/hal/esp32c6/include/hal/ledc_ll.h b/components/hal/esp32c6/include/hal/ledc_ll.h index 9c8abda01ca..1dcd353c389 100644 --- a/components/hal/esp32c6/include/hal/ledc_ll.h +++ b/components/hal/esp32c6/include/hal/ledc_ll.h @@ -395,16 +395,16 @@ static inline void ledc_ll_set_duty_scale(ledc_dev_t *hw, ledc_mode_t speed_mode } /** - * @brief Set the range number of the specified duty configurations written to gamma_wr register + * @brief Set the range number of the specified duty configurations to be written from gamma_wr register to gamma ram * * @param hw Beginning address of the peripheral registers * @param speed_mode LEDC speed_mode, low-speed mode only * @param channel_num LEDC channel index (0-5), select from ledc_channel_t - * @param duty_range Range index (0 - (SOC_LEDC_GAMMA_FADE_RANGE_MAX-1)), it specifies to which range the configurations in gamma_wr register apply + * @param duty_range Range index (0 - (SOC_LEDC_GAMMA_CURVE_FADE_RANGE_MAX-1)), it specifies to which range in gamma ram to write * * @return None */ -static inline void ledc_ll_set_duty_range(ledc_dev_t *hw, ledc_mode_t speed_mode, ledc_channel_t channel_num, uint32_t duty_range) +static inline void ledc_ll_set_duty_range_wr_addr(ledc_dev_t *hw, ledc_mode_t speed_mode, ledc_channel_t channel_num, uint32_t duty_range) { hw->channel_gamma_group[speed_mode].channel[channel_num].wr_addr.gamma_wr_addr = duty_range; } @@ -415,7 +415,7 @@ static inline void ledc_ll_set_duty_range(ledc_dev_t *hw, ledc_mode_t speed_mode * @param hw Beginning address of the peripheral registers * @param speed_mode LEDC speed_mode, low-speed mode only * @param channel_num LEDC channel index (0-5), select from ledc_channel_t - * @param range_num Total number of ranges (1 - SOC_LEDC_GAMMA_FADE_RANGE_MAX) of the fading configured + * @param range_num Total number of ranges (1 - SOC_LEDC_GAMMA_CURVE_FADE_RANGE_MAX) of the fading configured * * @return None */ @@ -424,6 +424,58 @@ static inline void ledc_ll_set_range_number(ledc_dev_t *hw, ledc_mode_t speed_mo hw->channel_gamma_conf_group[speed_mode].gamma_conf[channel_num].gamma_entry_num = range_num; } +/** + * @brief Get the total number of ranges in one fading + * + * @param hw Beginning address of the peripheral registers + * @param speed_mode LEDC speed_mode, low-speed mode only + * @param channel_num LEDC channel index (0-5), select from ledc_channel_t + * @param range_num Pointer to accept fade range number + * + * @return None + */ +static inline void ledc_ll_get_range_number(ledc_dev_t *hw, ledc_mode_t speed_mode, ledc_channel_t channel_num, uint32_t *range_num) +{ + *range_num = hw->channel_gamma_conf_group[speed_mode].gamma_conf[channel_num].gamma_entry_num; +} + +/** + * @brief Set the range number of the specified duty configurations to be read from gamma ram to gamma_rd register + * + * @param hw Beginning address of the peripheral registers + * @param speed_mode LEDC speed_mode, low-speed mode only + * @param channel_num LEDC channel index (0-5), select from ledc_channel_t + * @param duty_range Range index (0 - (SOC_LEDC_GAMMA_CURVE_FADE_RANGE_MAX-1)), it specifies to which range in gamma ram to read + * + * @return None + */ +static inline void ledc_ll_set_duty_range_rd_addr(ledc_dev_t *hw, ledc_mode_t speed_mode, ledc_channel_t channel_num, uint32_t duty_range) +{ + hw->channel_gamma_group[speed_mode].channel[channel_num].rd_addr.gamma_rd_addr = duty_range; +} + +/** + * @brief Get fade configurations in gamma_rd register + * + * @param hw Beginning address of the peripheral registers + * @param speed_mode LEDC speed_mode, low-speed mode only + * @param channel_num LEDC channel index (0-5), select from ledc_channel_t + * @param dir Pointer to accept fade direction value + * @param cycle Pointer to accept fade cycle value + * @param scale Pointer to accept fade scale value + * @param step Pointer to accept fade step value + * + * @return None + */ +static inline void ledc_ll_get_duty_param(ledc_dev_t *hw, ledc_mode_t speed_mode, ledc_channel_t channel_num, uint32_t *dir, uint32_t *cycle, uint32_t *scale, uint32_t *step) +{ + uint32_t val = hw->channel_gamma_group[speed_mode].channel[channel_num].rd_data.gamma_rd_data; + *dir = (val & LEDC_CH0_GAMMA_DUTY_INC_M) >> LEDC_CH0_GAMMA_DUTY_INC_S; + *cycle = (val & LEDC_CH0_GAMMA_DUTY_CYCLE_M) >> LEDC_CH0_GAMMA_DUTY_CYCLE_S; + *scale = (val & LEDC_CH0_GAMMA_SCALE_M) >> LEDC_CH0_GAMMA_SCALE_S; + *step = (val & LEDC_CH0_GAMMA_DUTY_NUM_M) >> LEDC_CH0_GAMMA_DUTY_NUM_S; +} + /** * @brief Set the output enable * diff --git a/components/hal/esp32h2/include/hal/ledc_ll.h b/components/hal/esp32h2/include/hal/ledc_ll.h index 17c7c1c7d13..7c319e374ae 100644 --- a/components/hal/esp32h2/include/hal/ledc_ll.h +++ b/components/hal/esp32h2/include/hal/ledc_ll.h @@ -395,16 +395,16 @@ static inline void ledc_ll_set_duty_scale(ledc_dev_t *hw, ledc_mode_t speed_mode } /** - * @brief Set the range number of the specified duty configurations written to gamma_wr register + * @brief Set the range number of the specified duty configurations to be written from gamma_wr register to gamma ram * * @param hw Beginning address of the peripheral registers * @param speed_mode LEDC speed_mode, low-speed mode only * @param channel_num LEDC channel index (0-5), select from ledc_channel_t - * @param duty_range Range index (0 - (SOC_LEDC_GAMMA_FADE_RANGE_MAX-1)), it specifies to which range the configurations in gamma_wr register apply + * @param duty_range Range index (0 - (SOC_LEDC_GAMMA_CURVE_FADE_RANGE_MAX-1)), it specifies to which range in gamma ram to write * * @return None */ -static inline void ledc_ll_set_duty_range(ledc_dev_t *hw, ledc_mode_t speed_mode, ledc_channel_t channel_num, uint32_t duty_range) +static inline void ledc_ll_set_duty_range_wr_addr(ledc_dev_t *hw, ledc_mode_t speed_mode, ledc_channel_t channel_num, uint32_t duty_range) { hw->channel_gamma_group[speed_mode].channel[channel_num].wr_addr.gamma_wr_addr = duty_range; } @@ -415,7 +415,7 @@ static inline void ledc_ll_set_duty_range(ledc_dev_t *hw, ledc_mode_t speed_mode * @param hw Beginning address of the peripheral registers * @param speed_mode LEDC speed_mode, low-speed mode only * @param channel_num LEDC channel index (0-5), select from ledc_channel_t - * @param range_num Total number of ranges (1 - SOC_LEDC_GAMMA_FADE_RANGE_MAX) of the fading configured + * @param range_num Total number of ranges (1 - SOC_LEDC_GAMMA_CURVE_FADE_RANGE_MAX) of the fading configured * * @return None */ @@ -424,6 +424,58 @@ static inline void ledc_ll_set_range_number(ledc_dev_t *hw, ledc_mode_t speed_mo hw->channel_gamma_conf_group[speed_mode].gamma_conf[channel_num].gamma_entry_num = range_num; } +/** + * @brief Get the total number of ranges in one fading + * + * @param hw Beginning address of the peripheral registers + * @param speed_mode LEDC speed_mode, low-speed mode only + * @param channel_num LEDC channel index (0-5), select from ledc_channel_t + * @param range_num Pointer to accept fade range number + * + * @return None + */ +static inline void ledc_ll_get_range_number(ledc_dev_t *hw, ledc_mode_t speed_mode, ledc_channel_t channel_num, uint32_t *range_num) +{ + *range_num = hw->channel_gamma_conf_group[speed_mode].gamma_conf[channel_num].gamma_entry_num; +} + +/** + * @brief Set the range number of the specified duty configurations to be read from gamma ram to gamma_rd register + * + * @param hw Beginning address of the peripheral registers + * @param speed_mode LEDC speed_mode, low-speed mode only + * @param channel_num LEDC channel index (0-5), select from ledc_channel_t + * @param duty_range Range index (0 - (SOC_LEDC_GAMMA_CURVE_FADE_RANGE_MAX-1)), it specifies to which range in gamma ram to read + * + * @return None + */ +static inline void ledc_ll_set_duty_range_rd_addr(ledc_dev_t *hw, ledc_mode_t speed_mode, ledc_channel_t channel_num, uint32_t duty_range) +{ + hw->channel_gamma_group[speed_mode].channel[channel_num].rd_addr.gamma_rd_addr = duty_range; +} + +/** + * @brief Get fade configurations in gamma_rd register + * + * @param hw Beginning address of the peripheral registers + * @param speed_mode LEDC speed_mode, low-speed mode only + * @param channel_num LEDC channel index (0-5), select from ledc_channel_t + * @param dir Pointer to accept fade direction value + * @param cycle Pointer to accept fade cycle value + * @param scale Pointer to accept fade scale value + * @param step Pointer to accept fade step value + * + * @return None + */ +static inline void ledc_ll_get_duty_param(ledc_dev_t *hw, ledc_mode_t speed_mode, ledc_channel_t channel_num, uint32_t *dir, uint32_t *cycle, uint32_t *scale, uint32_t *step) +{ + uint32_t val = hw->channel_gamma_group[speed_mode].channel[channel_num].rd_data.gamma_rd_data; + *dir = (val & LEDC_CH0_GAMMA_DUTY_INC_M) >> LEDC_CH0_GAMMA_DUTY_INC_S; + *cycle = (val & LEDC_CH0_GAMMA_DUTY_CYCLE_M) >> LEDC_CH0_GAMMA_DUTY_CYCLE_S; + *scale = (val & LEDC_CH0_GAMMA_SCALE_M) >> LEDC_CH0_GAMMA_SCALE_S; + *step = (val & LEDC_CH0_GAMMA_DUTY_NUM_M) >> LEDC_CH0_GAMMA_DUTY_NUM_S; +} + /** * @brief Set the output enable * diff --git a/components/hal/include/hal/ledc_hal.h b/components/hal/include/hal/ledc_hal.h index 0a82f431395..4f8731b1adc 100644 --- a/components/hal/include/hal/ledc_hal.h +++ b/components/hal/include/hal/ledc_hal.h @@ -338,17 +338,17 @@ void ledc_hal_set_duty_cycle(ledc_hal_context_t *hal, ledc_channel_t channel_num */ void ledc_hal_set_duty_scale(ledc_hal_context_t *hal, ledc_channel_t channel_num, uint32_t duty_scale); -#if SOC_LEDC_GAMMA_FADE_RANGE_MAX > 1 +#if SOC_LEDC_GAMMA_CURVE_FADE_SUPPORTED /** - * @brief Set the range number of the specified duty configurations written to gamma_wr register + * @brief Set the range number of the specified duty configurations to be written from gamma_wr register to gamma ram * * @param hal Context of the HAL layer * @param channel_num LEDC channel index, select from ledc_channel_t - * @param duty_range Range index (0-15), it specifies to which range the configurations in gamma_wr register apply + * @param duty_range Range index (0 - (SOC_LEDC_GAMMA_CURVE_FADE_RANGE_MAX-1)), it specifies to which range in gamma ram to write * * @return None */ -void ledc_hal_set_duty_range(ledc_hal_context_t *hal, ledc_channel_t channel_num, uint32_t duty_range); +void ledc_hal_set_duty_range_wr_addr(ledc_hal_context_t *hal, ledc_channel_t channel_num, uint32_t duty_range); /** * @brief Set the total number of ranges in one fading @@ -360,7 +360,33 @@ void ledc_hal_set_duty_range(ledc_hal_context_t *hal, ledc_channel_t channel_num * @return None */ void ledc_hal_set_range_number(ledc_hal_context_t *hal, ledc_channel_t channel_num, uint32_t range_num); -#endif //SOC_LEDC_GAMMA_FADE_RANGE_MAX > 1 + +/** + * @brief Get the total number of ranges in one fading + * + * @param hal Context of the HAL layer + * @param channel_num LEDC channel index, select from ledc_channel_t + * @param range_num Pointer to accept fade range number + * + * @return None + */ +void ledc_hal_get_range_number(ledc_hal_context_t *hal, ledc_channel_t channel_num, uint32_t *range_num); + +/** + * @brief Read the fade parameters that are stored in gamma ram for a certain fade range + * + * @param hal Context of the HAL layer + * @param channel_num LEDC channel index, select from ledc_channel_t + * @param range Range index (0 - (SOC_LEDC_GAMMA_CURVE_FADE_RANGE_MAX-1)), it specifies to which range in gamma ram to read + * @param dir Pointer to accept fade direction value + * @param cycle Pointer to accept fade cycle value + * @param scale Pointer to accept fade scale value + * @param step Pointer to accept fade step value + * + * @return None + */ +void ledc_hal_get_fade_param(ledc_hal_context_t *hal, ledc_channel_t channel_num, uint32_t range, uint32_t *dir, uint32_t *cycle, uint32_t *scale, uint32_t *step); +#endif //SOC_LEDC_GAMMA_CURVE_FADE_SUPPORTED /** * @brief Get interrupt status of the specified channel diff --git a/components/hal/ledc_hal.c b/components/hal/ledc_hal.c index 4c312ffac1c..10c90df1c33 100644 --- a/components/hal/ledc_hal.c +++ b/components/hal/ledc_hal.c @@ -11,6 +11,7 @@ #include "soc/soc_caps.h" #include "sdkconfig.h" #include "hal/assert.h" +#include "esp_rom_sys.h" void ledc_hal_init(ledc_hal_context_t *hal, ledc_mode_t speed_mode) { @@ -58,3 +59,14 @@ void ledc_hal_get_clk_cfg(ledc_hal_context_t *hal, ledc_timer_t timer_sel, ledc_ *clk_cfg = driver_clk; } + +#if SOC_LEDC_GAMMA_CURVE_FADE_SUPPORTED +void ledc_hal_get_fade_param(ledc_hal_context_t *hal, ledc_channel_t channel_num, uint32_t range, uint32_t *dir, uint32_t *cycle, uint32_t *scale, uint32_t *step) +{ + ledc_ll_set_duty_range_rd_addr(hal->dev, hal->speed_mode, channel_num, range); + // On ESP32C6/H2, gamma ram read/write has the APB and LEDC clock domain sync issue + // To make sure the parameter read is from the correct gamma ram addr, add a delay in between to ensure syncronization + esp_rom_delay_us(5); + ledc_ll_get_duty_param(hal->dev, hal->speed_mode, channel_num, dir, cycle, scale, step); +} +#endif diff --git a/components/hal/ledc_hal_iram.c b/components/hal/ledc_hal_iram.c index 19e81aa79a1..ede00c26a59 100644 --- a/components/hal/ledc_hal_iram.c +++ b/components/hal/ledc_hal_iram.c @@ -55,17 +55,22 @@ void ledc_hal_set_duty_scale(ledc_hal_context_t *hal, ledc_channel_t channel_num ledc_ll_set_duty_scale(hal->dev, hal->speed_mode, channel_num, duty_scale); } -#if SOC_LEDC_GAMMA_FADE_RANGE_MAX > 1 -void ledc_hal_set_duty_range(ledc_hal_context_t *hal, ledc_channel_t channel_num, uint32_t duty_range) +#if SOC_LEDC_GAMMA_CURVE_FADE_SUPPORTED +void ledc_hal_set_duty_range_wr_addr(ledc_hal_context_t *hal, ledc_channel_t channel_num, uint32_t duty_range) { - ledc_ll_set_duty_range(hal->dev, hal->speed_mode, channel_num, duty_range); + ledc_ll_set_duty_range_wr_addr(hal->dev, hal->speed_mode, channel_num, duty_range); } void ledc_hal_set_range_number(ledc_hal_context_t *hal, ledc_channel_t channel_num, uint32_t range_num) { ledc_ll_set_range_number(hal->dev, hal->speed_mode, channel_num, range_num); } -#endif //SOC_LEDC_GAMMA_FADE_RANGE_MAX > 1 + +void ledc_hal_get_range_number(ledc_hal_context_t *hal, ledc_channel_t channel_num, uint32_t *range_num) +{ + ledc_ll_get_range_number(hal->dev, hal->speed_mode, channel_num, range_num); +} +#endif //SOC_LEDC_GAMMA_CURVE_FADE_SUPPORTED void ledc_hal_get_fade_end_intr_status(ledc_hal_context_t *hal, uint32_t *intr_status) { diff --git a/components/soc/esp32/include/soc/Kconfig.soc_caps.in b/components/soc/esp32/include/soc/Kconfig.soc_caps.in index 51971ce8a1c..f10ec049c83 100644 --- a/components/soc/esp32/include/soc/Kconfig.soc_caps.in +++ b/components/soc/esp32/include/soc/Kconfig.soc_caps.in @@ -395,10 +395,6 @@ config SOC_LEDC_TIMER_BIT_WIDTH int default 20 -config SOC_LEDC_GAMMA_FADE_RANGE_MAX - int - default 1 - config SOC_MCPWM_GROUPS int default 2 diff --git a/components/soc/esp32/include/soc/soc_caps.h b/components/soc/esp32/include/soc/soc_caps.h index b5b129a40ab..ed2013b00c7 100644 --- a/components/soc/esp32/include/soc/soc_caps.h +++ b/components/soc/esp32/include/soc/soc_caps.h @@ -212,7 +212,6 @@ #define SOC_LEDC_SUPPORT_HS_MODE (1) #define SOC_LEDC_CHANNEL_NUM (8) #define SOC_LEDC_TIMER_BIT_WIDTH (20) -#define SOC_LEDC_GAMMA_FADE_RANGE_MAX (1U) // The target does not support gamma curve fading /*-------------------------- MCPWM CAPS --------------------------------------*/ #define SOC_MCPWM_GROUPS (2) ///< 2 MCPWM groups on the chip (i.e., the number of independent MCPWM peripherals) diff --git a/components/soc/esp32c2/include/soc/Kconfig.soc_caps.in b/components/soc/esp32c2/include/soc/Kconfig.soc_caps.in index 8f376d3e78a..c4aa86ca585 100644 --- a/components/soc/esp32c2/include/soc/Kconfig.soc_caps.in +++ b/components/soc/esp32c2/include/soc/Kconfig.soc_caps.in @@ -299,10 +299,6 @@ config SOC_LEDC_SUPPORT_FADE_STOP bool default y -config SOC_LEDC_GAMMA_FADE_RANGE_MAX - int - default 1 - config SOC_MMU_PAGE_SIZE_CONFIGURABLE bool default y diff --git a/components/soc/esp32c2/include/soc/soc_caps.h b/components/soc/esp32c2/include/soc/soc_caps.h index 823331ea692..8cd49175aa3 100644 --- a/components/soc/esp32c2/include/soc/soc_caps.h +++ b/components/soc/esp32c2/include/soc/soc_caps.h @@ -147,7 +147,6 @@ #define SOC_LEDC_CHANNEL_NUM (6) #define SOC_LEDC_TIMER_BIT_WIDTH (14) #define SOC_LEDC_SUPPORT_FADE_STOP (1) -#define SOC_LEDC_GAMMA_FADE_RANGE_MAX (1U) // The target does not support gamma curve fading /*-------------------------- MMU CAPS ----------------------------------------*/ #define SOC_MMU_PAGE_SIZE_CONFIGURABLE (1) diff --git a/components/soc/esp32c3/include/soc/Kconfig.soc_caps.in b/components/soc/esp32c3/include/soc/Kconfig.soc_caps.in index bc703eae566..697d080b69e 100644 --- a/components/soc/esp32c3/include/soc/Kconfig.soc_caps.in +++ b/components/soc/esp32c3/include/soc/Kconfig.soc_caps.in @@ -435,10 +435,6 @@ config SOC_LEDC_SUPPORT_FADE_STOP bool default y -config SOC_LEDC_GAMMA_FADE_RANGE_MAX - int - default 1 - config SOC_MMU_LINEAR_ADDRESS_REGION_NUM int default 1 diff --git a/components/soc/esp32c3/include/soc/soc_caps.h b/components/soc/esp32c3/include/soc/soc_caps.h index c4ad02d05e3..c7abe37fad5 100644 --- a/components/soc/esp32c3/include/soc/soc_caps.h +++ b/components/soc/esp32c3/include/soc/soc_caps.h @@ -200,7 +200,6 @@ #define SOC_LEDC_CHANNEL_NUM (6) #define SOC_LEDC_TIMER_BIT_WIDTH (14) #define SOC_LEDC_SUPPORT_FADE_STOP (1) -#define SOC_LEDC_GAMMA_FADE_RANGE_MAX (1U) // The target does not support gamma curve fading /*-------------------------- MMU CAPS ----------------------------------------*/ #define SOC_MMU_LINEAR_ADDRESS_REGION_NUM (1U) diff --git a/components/soc/esp32c6/include/soc/Kconfig.soc_caps.in b/components/soc/esp32c6/include/soc/Kconfig.soc_caps.in index e5f15a886b0..2373c9452d9 100644 --- a/components/soc/esp32c6/include/soc/Kconfig.soc_caps.in +++ b/components/soc/esp32c6/include/soc/Kconfig.soc_caps.in @@ -527,10 +527,18 @@ config SOC_LEDC_SUPPORT_FADE_STOP bool default y -config SOC_LEDC_GAMMA_FADE_RANGE_MAX +config SOC_LEDC_GAMMA_CURVE_FADE_SUPPORTED + bool + default y + +config SOC_LEDC_GAMMA_CURVE_FADE_RANGE_MAX int default 16 +config SOC_LEDC_FADE_PARAMS_BIT_WIDTH + int + default 10 + config SOC_MMU_PAGE_SIZE_CONFIGURABLE bool default y diff --git a/components/soc/esp32c6/include/soc/soc_caps.h b/components/soc/esp32c6/include/soc/soc_caps.h index 7e0b35ddcd5..ef0d583a8c6 100644 --- a/components/soc/esp32c6/include/soc/soc_caps.h +++ b/components/soc/esp32c6/include/soc/soc_caps.h @@ -233,7 +233,9 @@ #define SOC_LEDC_CHANNEL_NUM (6) #define SOC_LEDC_TIMER_BIT_WIDTH (20) #define SOC_LEDC_SUPPORT_FADE_STOP (1) -#define SOC_LEDC_GAMMA_FADE_RANGE_MAX (16) +#define SOC_LEDC_GAMMA_CURVE_FADE_SUPPORTED (1) +#define SOC_LEDC_GAMMA_CURVE_FADE_RANGE_MAX (16) +#define SOC_LEDC_FADE_PARAMS_BIT_WIDTH (10) /*-------------------------- MMU CAPS ----------------------------------------*/ #define SOC_MMU_PAGE_SIZE_CONFIGURABLE (1) diff --git a/components/soc/esp32h2/include/soc/Kconfig.soc_caps.in b/components/soc/esp32h2/include/soc/Kconfig.soc_caps.in index 02e10d5a27d..47b4111580a 100644 --- a/components/soc/esp32h2/include/soc/Kconfig.soc_caps.in +++ b/components/soc/esp32h2/include/soc/Kconfig.soc_caps.in @@ -499,10 +499,18 @@ config SOC_LEDC_SUPPORT_FADE_STOP bool default y -config SOC_LEDC_GAMMA_FADE_RANGE_MAX +config SOC_LEDC_GAMMA_CURVE_FADE_SUPPORTED + bool + default y + +config SOC_LEDC_GAMMA_CURVE_FADE_RANGE_MAX int default 16 +config SOC_LEDC_FADE_PARAMS_BIT_WIDTH + int + default 10 + config SOC_MPU_CONFIGURABLE_REGIONS_SUPPORTED bool default n diff --git a/components/soc/esp32h2/include/soc/soc_caps.h b/components/soc/esp32h2/include/soc/soc_caps.h index 60114f51005..443caf1ff6a 100644 --- a/components/soc/esp32h2/include/soc/soc_caps.h +++ b/components/soc/esp32h2/include/soc/soc_caps.h @@ -225,7 +225,9 @@ #define SOC_LEDC_CHANNEL_NUM (6) #define SOC_LEDC_TIMER_BIT_WIDTH (20) #define SOC_LEDC_SUPPORT_FADE_STOP (1) -#define SOC_LEDC_GAMMA_FADE_RANGE_MAX (16) +#define SOC_LEDC_GAMMA_CURVE_FADE_SUPPORTED (1) +#define SOC_LEDC_GAMMA_CURVE_FADE_RANGE_MAX (16) +#define SOC_LEDC_FADE_PARAMS_BIT_WIDTH (10) // TODO: IDF-6332 (Copy from esp32c6, need check) /*-------------------------- MPU CAPS ----------------------------------------*/ diff --git a/components/soc/esp32h4/include/soc/Kconfig.soc_caps.in b/components/soc/esp32h4/include/soc/Kconfig.soc_caps.in index c63a589bc23..1efb5ef96dc 100644 --- a/components/soc/esp32h4/include/soc/Kconfig.soc_caps.in +++ b/components/soc/esp32h4/include/soc/Kconfig.soc_caps.in @@ -415,10 +415,6 @@ config SOC_LEDC_SUPPORT_FADE_STOP bool default y -config SOC_LEDC_GAMMA_FADE_RANGE_MAX - int - default 1 - config SOC_MMU_LINEAR_ADDRESS_REGION_NUM int default 1 diff --git a/components/soc/esp32h4/include/soc/soc_caps.h b/components/soc/esp32h4/include/soc/soc_caps.h index 3332825cd0a..759114e918d 100644 --- a/components/soc/esp32h4/include/soc/soc_caps.h +++ b/components/soc/esp32h4/include/soc/soc_caps.h @@ -213,7 +213,6 @@ #define SOC_LEDC_CHANNEL_NUM (6) #define SOC_LEDC_TIMER_BIT_WIDTH (14) #define SOC_LEDC_SUPPORT_FADE_STOP (1) -#define SOC_LEDC_GAMMA_FADE_RANGE_MAX (1U) // The target does not support gamma curve fading /*-------------------------- MMU CAPS ----------------------------------------*/ #define SOC_MMU_LINEAR_ADDRESS_REGION_NUM (1U) diff --git a/components/soc/esp32s2/include/soc/Kconfig.soc_caps.in b/components/soc/esp32s2/include/soc/Kconfig.soc_caps.in index aea363ac0b2..7de00e0726f 100644 --- a/components/soc/esp32s2/include/soc/Kconfig.soc_caps.in +++ b/components/soc/esp32s2/include/soc/Kconfig.soc_caps.in @@ -463,10 +463,6 @@ config SOC_LEDC_SUPPORT_FADE_STOP bool default y -config SOC_LEDC_GAMMA_FADE_RANGE_MAX - int - default 1 - config SOC_MMU_LINEAR_ADDRESS_REGION_NUM int default 5 diff --git a/components/soc/esp32s2/include/soc/soc_caps.h b/components/soc/esp32s2/include/soc/soc_caps.h index b12b7027949..d9fda75323f 100644 --- a/components/soc/esp32s2/include/soc/soc_caps.h +++ b/components/soc/esp32s2/include/soc/soc_caps.h @@ -210,7 +210,6 @@ #define SOC_LEDC_CHANNEL_NUM (8) #define SOC_LEDC_TIMER_BIT_WIDTH (14) #define SOC_LEDC_SUPPORT_FADE_STOP (1) -#define SOC_LEDC_GAMMA_FADE_RANGE_MAX (1U) // The target does not support gamma curve fading /*-------------------------- MMU CAPS ----------------------------------------*/ #define SOC_MMU_LINEAR_ADDRESS_REGION_NUM 5 diff --git a/components/soc/esp32s3/include/soc/Kconfig.soc_caps.in b/components/soc/esp32s3/include/soc/Kconfig.soc_caps.in index 9a4ec50d3b1..03cb01d6bb7 100644 --- a/components/soc/esp32s3/include/soc/Kconfig.soc_caps.in +++ b/components/soc/esp32s3/include/soc/Kconfig.soc_caps.in @@ -499,10 +499,6 @@ config SOC_LEDC_SUPPORT_FADE_STOP bool default y -config SOC_LEDC_GAMMA_FADE_RANGE_MAX - int - default 1 - config SOC_MCPWM_GROUPS int default 2 diff --git a/components/soc/esp32s3/include/soc/soc_caps.h b/components/soc/esp32s3/include/soc/soc_caps.h index 9063abff608..10cef9d9f7b 100644 --- a/components/soc/esp32s3/include/soc/soc_caps.h +++ b/components/soc/esp32s3/include/soc/soc_caps.h @@ -205,7 +205,6 @@ #define SOC_LEDC_CHANNEL_NUM (8) #define SOC_LEDC_TIMER_BIT_WIDTH (14) #define SOC_LEDC_SUPPORT_FADE_STOP (1) -#define SOC_LEDC_GAMMA_FADE_RANGE_MAX (1U) // The target does not support gamma curve fading /*-------------------------- MCPWM CAPS --------------------------------------*/ #define SOC_MCPWM_GROUPS (2) ///< 2 MCPWM groups on the chip (i.e., the number of independent MCPWM peripherals) diff --git a/docs/en/api-reference/peripherals/ledc.rst b/docs/en/api-reference/peripherals/ledc.rst index 3d285003782..9e41561d1f1 100644 --- a/docs/en/api-reference/peripherals/ledc.rst +++ b/docs/en/api-reference/peripherals/ledc.rst @@ -1,6 +1,8 @@ LED Control (LEDC) ================== -{IDF_TARGET_LEDC_CHAN_NUM:default="6", esp32="16", esp32s2="8", esp32s3="8"} +{IDF_TARGET_LEDC_CHAN_NUM: default="6", esp32="16", esp32s2="8", esp32s3="8"} + +{IDF_TARGET_LEDC_MAX_FADE_RANGE_NUM: default="1", esp32c6="16", esp32h2="16"} :link_to_translation:`zh_CN:[中文]` @@ -269,6 +271,12 @@ The LEDC hardware provides the means to gradually transition from one duty cycle * :cpp:func:`ledc_set_fade_with_step` * :cpp:func:`ledc_set_fade` +.. only:: SOC_LEDC_GAMMA_CURVE_FADE_SUPPORTED + + On {IDF_TARGET_NAME}, the hardware additionally allows to perform up to {IDF_TARGET_LEDC_MAX_FADE_RANGE_NUM} consecutive linear fades without CPU intervention. This feature can be useful if you want to do a fade with gamma correction. + + The luminance perceived by human eyes does not have a linear relationship with the PWM duty cycle. In order to make human feel the LED is dimming or lightening linearly, the change in duty cycle should be non-linear, which is the so-called gamma correction. The LED controller can simulate a gamma curve fading by piecewise linear approximation. :cpp:func:`ledc_fill_multi_fade_param_list` is a function that can help to construct the parameters for the piecewise linear fades. First, you need to allocate a memory block for saving the fade parameters, then by providing start/end PWM duty cycle values, gamma correction function, and the total number of desired linear segments to the helper function, it will fill the calculation results into the allocated space. You can also construct the array of :cpp:type:`ledc_fade_param_config_t` manually. Once the fade parameter structs are prepared, a consecutive fading can be configured by passing the pointer to the prepared :cpp:type:`ledc_fade_param_config_t` list and the total number of fade ranges to :cpp:func:`ledc_set_multi_fade`. + .. only:: esp32 Start fading with :cpp:func:`ledc_fade_start`. A fade can be operated in blocking or non-blocking mode, please check :cpp:enum:`ledc_fade_mode_t` for the difference between the two available fade modes. Note that with either fade mode, the next fade or fixed-duty update will not take effect until the last fade finishes. Due to hardware limitations, there is no way to stop a fade before it reaches its target duty. @@ -361,9 +369,13 @@ The duty resolution is normally set using :cpp:type:`ledc_timer_bit_t`. This enu Application Example ------------------- +The LEDC basic example: :example:`peripherals/ledc/ledc_basic`. + The LEDC change duty cycle and fading control example: :example:`peripherals/ledc/ledc_fade`. -The LEDC basic example: :example:`peripherals/ledc/ledc_basic`. +.. only:: SOC_LEDC_GAMMA_CURVE_FADE_SUPPORTED + + The LEDC color control with Gamma correction on RGB LED example: :example:`peripherals/ledc/ledc_gamma_curve_fade`. API Reference ------------- diff --git a/docs/zh_CN/api-reference/peripherals/ledc.rst b/docs/zh_CN/api-reference/peripherals/ledc.rst index 7264eb4f2a4..7f472902238 100644 --- a/docs/zh_CN/api-reference/peripherals/ledc.rst +++ b/docs/zh_CN/api-reference/peripherals/ledc.rst @@ -1,6 +1,8 @@ LED PWM 控制器 ============== -{IDF_TARGET_LEDC_CHAN_NUM:default="6", esp32="16", esp32s2="8", esp32s3="8"} +{IDF_TARGET_LEDC_CHAN_NUM: default="6", esp32="16", esp32s2="8", esp32s3="8"} + +{IDF_TARGET_LEDC_MAX_FADE_RANGE_NUM: default="1", esp32c6="16", esp32h2="16"} :link_to_translation:`en:[English]` @@ -269,6 +271,12 @@ LED PWM 控制器硬件可逐渐改变占空比的数值。要使用此功能, * :cpp:func:`ledc_set_fade_with_step` * :cpp:func:`ledc_set_fade` +.. only:: SOC_LEDC_GAMMA_CURVE_FADE_SUPPORTED + + {IDF_TARGET_NAME} 的硬件额外支持多达 {IDF_TARGET_LEDC_MAX_FADE_RANGE_NUM} 次,无需 CPU 介入的连续渐变。此功能可以更加有效便捷得实现一个带伽马校正的渐变。 + + 众所周知,人眼所感知的亮度与 PWM 占空比并非成线性关系。为了能使人感观上认为一盏灯明暗的变化是线性的,我们对其 PWM 信号的占空比控制必须为非线性的,俗称伽马校正。LED PWM 控制器可以通过多段线型拟合来模仿伽马曲线渐变。 你需要自己在应用程序中分配一段用以保存渐变参数的内存块,并提供开始和结束的占空比,伽马校正公式,以及期望的线性渐变段数信息,:cpp:func:`ledc_fill_multi_fade_param_list` 就能快速生成所有分段线性渐变的参数。或者你也可以自己直接构造一个 :cpp:type:`ledc_fade_param_config_t` 的数组。在获得所有渐变参数后,通过将 :cpp:type:`ledc_fade_param_config_t` 数组的指针和渐变区间数传入 :cpp:func:`ledc_set_multi_fade`,一次连续渐变的配置就完成了。 + .. only:: esp32 最后需要调用 :cpp:func:`ledc_fade_start` 开启渐变。渐变可以在阻塞或非阻塞模式下运行,具体区别请查看 :cpp:enum:`ledc_fade_mode_t`。需要特别注意的是,不管在哪种模式下,下一次渐变或单次占空比配置的指令生效都必须等到前一次渐变结束。由于 {IDF_TARGET_NAME} 的硬件限制,在渐变达到原先预期的占空比前想要中止本次渐变是不被支持的。 @@ -361,9 +369,13 @@ LED PWM 控制器 API 会在设定的频率和占空比分辨率超过 LED PWM 应用实例 ------------------- +使用 LEDC 基本实例请参照 :example:`peripherals/ledc/ledc_basic`。 + 使用 LEDC 改变占空比和渐变控制的实例请参照 :example:`peripherals/ledc/ledc_fade`。 -使用 LEDC 基本实例请参照 :example:`peripherals/ledc/ledc_basic`。 +.. only:: SOC_LEDC_GAMMA_CURVE_FADE_SUPPORTED + + 使用 LEDC 对 RGB LED 实现带伽马校正的颜色控制实例请参照 :example:`peripherals/ledc/ledc_gamma_curve_fade`。 API 参考 ------------- diff --git a/examples/peripherals/.build-test-rules.yml b/examples/peripherals/.build-test-rules.yml index 2bd8c93cb49..6d8c66f57dc 100644 --- a/examples/peripherals/.build-test-rules.yml +++ b/examples/peripherals/.build-test-rules.yml @@ -91,6 +91,10 @@ examples/peripherals/ledc: disable: - if: SOC_LEDC_SUPPORTED != 1 +examples/peripherals/ledc/ledc_gamma_curve_fade: + disable: + - if: SOC_LEDC_SUPPORTED != 1 or SOC_LEDC_GAMMA_CURVE_FADE_SUPPORTED != 1 + examples/peripherals/mcpwm: disable: - if: SOC_MCPWM_SUPPORTED != 1 diff --git a/examples/peripherals/ledc/ledc_gamma_curve_fade/CMakeLists.txt b/examples/peripherals/ledc/ledc_gamma_curve_fade/CMakeLists.txt new file mode 100644 index 00000000000..e615f5ed1f7 --- /dev/null +++ b/examples/peripherals/ledc/ledc_gamma_curve_fade/CMakeLists.txt @@ -0,0 +1,6 @@ +# The following lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.16) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(ledc_gamma_curve_fade) diff --git a/examples/peripherals/ledc/ledc_gamma_curve_fade/README.md b/examples/peripherals/ledc/ledc_gamma_curve_fade/README.md new file mode 100644 index 00000000000..3bd47b1aa4c --- /dev/null +++ b/examples/peripherals/ledc/ledc_gamma_curve_fade/README.md @@ -0,0 +1,73 @@ +| Supported Targets | ESP32-C6 | ESP32-H2 | +| ----------------- | -------- | -------- | + +# _LEDC Gamma Curve Fade Example_ + +(See the README.md file in the upper level 'examples' directory for more information about examples.) + +This example shows how to control intensity of LEDs with [gamma correction](https://en.wikipedia.org/wiki/Gamma_correction) involved using selected SoC's on-board hardware LED PWM Controller module. + +## How to use example + +### Hardware Required + +* A development board with an Espressif SoC that has Gamma Curve Fading feature (e.g., ESP32C6, ESP32H2) +* A USB cable for power supply and programming + +Connect two RGB LEDs to the ESP Board as illustrated in the following diagram: + +``` + ESP Board + +-----------------------+ RGB_LED_1 + | | +---------------+ + | LEDC_RED_IO +----------------+ R | + | | | | + | LEDC_GREEN_IO +----------------+ G | + | | | | + | LEDC_BLUE_IO +----------------+ B GND | + | | +-------+-------+ + | | | + | GND +------------------------| + | | | + | | +-------+-------+ + | LEDC_GAMMA_RED_IO +----------------+ R GND | + | | | | + | LEDC_GAMMA_GREEN_IO +----------------+ G | + | | | | + | LEDC_GAMMA_BLUE_IO +----------------+ B | + | | +---------------+ + +-----------------------+ RGB_LED_2 +``` + +The GPIO number used by this example can be changed in [ledc_gamma_curve_fade_example_main.c](main/ledc_gamma_curve_fade_example_main.c). + +### Configure the project + +The example provides two ways to calculate gamma corrected duty cycles. One is through math power operation, the other is through look up table (LUT). By default, the example uses the former method, where the gamma factor value can be adjusted in `GAMMA_FACTOR` macro in [ledc_gamma_curve_fade_example_main.c](main/ledc_gamma_curve_fade_example_main.c). If runtime computation wants to be reduced, LUT method can be enabled by running `idf.py menuconfig` and selecting the `Use look up table to do gamma correction` option at `Example Configuration`. Note that the LUT provided in the example is only for a Gamma factor of 2.6. Any LUT for a different Gamma factor needs to be provided by the users. + +### Build and Flash + +Build the project and flash it to the board, then run the monitor tool to view the serial output: + +Run `idf.py -p PORT flash monitor` to build, flash and monitor the project. + +(To exit the serial monitor, type ``Ctrl-]``.) + +See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html) for full steps to configure and use ESP-IDF to build projects. + +## Example Output + +Running this example, you will see color and brightness difference between two RGB LEDs during fading. This is because on RGB_LED_1, no gamma correction is applied; while on RGB_LED_2, gamma correction is applied, and it is fading fitting to a gamma curve. Overall, the color change on RGB_LED_2 should look more gradual and smoother to human eyes. + +The program first lights up and dims down some certain colors to show how Gamma corrects the RGB color on RGB LEDs by applying Gamma correction to the PWM duty cycle values and how Gamma curve fading helps to maintain color hue when lighting up and dimming down through the change of duty cycle. + +Then both the RGB LEDs start to cycle through the color spectrum. + +## Troubleshooting + +* Programming fail + + * Hardware connection is not correct: run `idf.py -p PORT monitor`, and reboot your board to see if there are any output logs. + * The baud rate for downloading is too high: lower your baud rate in the `menuconfig` menu, and try again. + +For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon. diff --git a/examples/peripherals/ledc/ledc_gamma_curve_fade/main/CMakeLists.txt b/examples/peripherals/ledc/ledc_gamma_curve_fade/main/CMakeLists.txt new file mode 100644 index 00000000000..0948cd72b27 --- /dev/null +++ b/examples/peripherals/ledc/ledc_gamma_curve_fade/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "ledc_gamma_curve_fade_example_main.c" + INCLUDE_DIRS ".") diff --git a/examples/peripherals/ledc/ledc_gamma_curve_fade/main/Kconfig.projbuild b/examples/peripherals/ledc/ledc_gamma_curve_fade/main/Kconfig.projbuild new file mode 100644 index 00000000000..39d3d89880d --- /dev/null +++ b/examples/peripherals/ledc/ledc_gamma_curve_fade/main/Kconfig.projbuild @@ -0,0 +1,11 @@ +menu "Example Configuration" + + config GAMMA_CORRECTION_WITH_LUT + bool "Use look up table to do gamma correction" + default n + help + By default, the gamma correction in the example is through math power operation. This requires computation + on embedded system. With this option enabled, gamma correction will be based on a look up table. Note that, + the look up table provided in the example is only for a Gamma factor of 2.6. + +endmenu diff --git a/examples/peripherals/ledc/ledc_gamma_curve_fade/main/ledc_gamma_curve_fade_example_main.c b/examples/peripherals/ledc/ledc_gamma_curve_fade/main/ledc_gamma_curve_fade_example_main.c new file mode 100644 index 00000000000..24cc72fb7d3 --- /dev/null +++ b/examples/peripherals/ledc/ledc_gamma_curve_fade/main/ledc_gamma_curve_fade_example_main.c @@ -0,0 +1,325 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "driver/ledc.h" +#include "esp_err.h" + +/** + * About this example + * + * This example sets up two RGB LEDs to show how gamma correction can affect towards human eyes. + * + * RGB LED 1: R (IO0), G (IO1), B (IO2) + * Gamma correction is not applied to this LED + * + * RGB LED 2: R (IO3), G (IO4), B (IO5) + * Gamma correction is applied to this LED + * + * You should feel the fade in general is more gradual and smoother on RGB LED 2 than RGB LED 1. + */ + +#define LEDC_TIMER LEDC_TIMER_0 +#define LEDC_MODE LEDC_LOW_SPEED_MODE + +#define LEDC_DUTY_RES LEDC_TIMER_13_BIT // Set duty resolution to 13 bits +#define LEDC_FREQUENCY (5000) // Frequency in Hertz. Set frequency at 5 kHz + +// Define two RGB LEDs IOs and channels +#define LEDC_RED_IO (0) +#define LEDC_GREEN_IO (1) +#define LEDC_BLUE_IO (2) +#define LEDC_GAMMA_RED_IO (3) +#define LEDC_GAMMA_GREEN_IO (4) +#define LEDC_GAMMA_BLUE_IO (5) + +#define LEDC_CHANNEL_RED (LEDC_CHANNEL_0) +#define LEDC_CHANNEL_GREEN (LEDC_CHANNEL_1) +#define LEDC_CHANNEL_BLUE (LEDC_CHANNEL_2) +#define LEDC_CHANNEL_GAMMA_RED (LEDC_CHANNEL_3) +#define LEDC_CHANNEL_GAMMA_GREEN (LEDC_CHANNEL_4) +#define LEDC_CHANNEL_GAMMA_BLUE (LEDC_CHANNEL_5) + +// Structure to store R, G, B channels +typedef struct { + uint32_t red_channel; + uint32_t green_channel; + uint32_t blue_channel; +} rgb_channel_config_t; + +static rgb_channel_config_t rgb_led_1_channels = { + .red_channel = LEDC_CHANNEL_RED, + .green_channel = LEDC_CHANNEL_GREEN, + .blue_channel = LEDC_CHANNEL_BLUE, +}; + +static rgb_channel_config_t rgb_led_2_channels = { + .red_channel = LEDC_CHANNEL_GAMMA_RED, + .green_channel = LEDC_CHANNEL_GAMMA_GREEN, + .blue_channel = LEDC_CHANNEL_GAMMA_BLUE, +}; + + +// Define some colors R, G, B channel PWM duty cycles +#define RGB_TO_DUTY(x) (x * (1 << LEDC_DUTY_RES) / 255) + +#define OFF_R 0 +#define OFF_G 0 +#define OFF_B 0 + +// RED - R: 255, G: 0 , B: 0 +// H: 0 , S: 100, V: 100 +#define RED_R RGB_TO_DUTY(255) +#define RED_G RGB_TO_DUTY(0) +#define RED_B RGB_TO_DUTY(0) + +// YELLOW - R: 255, G: 255, B: 0 +// H: 60 , S: 100, V: 100 +#define YELLOW_R RGB_TO_DUTY(255) +#define YELLOW_G RGB_TO_DUTY(255) +#define YELLOW_B RGB_TO_DUTY(0) + +// GREEN - R: 0 , G: 255, B: 0 +// H: 120, S: 100, V: 100 +#define GREEN_R RGB_TO_DUTY(0) +#define GREEN_G RGB_TO_DUTY(255) +#define GREEN_B RGB_TO_DUTY(0) + +// CYAN - R: 0 , G: 255, B: 255 +// H: 180, S: 100, V: 100 +#define CYAN_R RGB_TO_DUTY(0) +#define CYAN_G RGB_TO_DUTY(255) +#define CYAN_B RGB_TO_DUTY(255) + +// BLUE - R: 0 , G: 0 , B: 255 +// H: 240, S: 100, V: 100 +#define BLUE_R RGB_TO_DUTY(0) +#define BLUE_G RGB_TO_DUTY(0) +#define BLUE_B RGB_TO_DUTY(255) + +// MAGENTA - R: 255, G: 0 , B: 255 +// H: 300, S: 100, V: 100 +#define MAGENTA_R RGB_TO_DUTY(255) +#define MAGENTA_G RGB_TO_DUTY(0) +#define MAGENTA_B RGB_TO_DUTY(255) + +// ORANGE - R: 255, G: 128, B: 0 +// H: 30 , S: 100, V: 100 +#define ORANGE_R RGB_TO_DUTY(255) +#define ORANGE_G RGB_TO_DUTY(128) +#define ORANGE_B RGB_TO_DUTY(0) + +// ROSE PINK - R: 255, G: 0 , B: 128 +// H: 330, S: 100, V: 100 +#define ROSE_R RGB_TO_DUTY(255) +#define ROSE_G RGB_TO_DUTY(0) +#define ROSE_B RGB_TO_DUTY(128) + +// BLUEISH PURPLE - R: 178, G: 102, B: 255 +// H: 270, S: 60, V: 100 +#define BLUEISH_PURPLE_R RGB_TO_DUTY(178) +#define BLUEISH_PURPLE_G RGB_TO_DUTY(102) +#define BLUEISH_PURPLE_B RGB_TO_DUTY(255) + + +#if CONFIG_GAMMA_CORRECTION_WITH_LUT +// Brightness 0 - 100% gamma correction look up table (gamma = 2.6) +// Y = B ^ 2.6 +// Pre-computed LUT to save some runtime computation +static const float gamma_correction_lut[101] = { + 0.000000, 0.000006, 0.000038, 0.000110, 0.000232, 0.000414, 0.000666, 0.000994, 0.001406, 0.001910, + 0.002512, 0.003218, 0.004035, 0.004969, 0.006025, 0.007208, 0.008525, 0.009981, 0.011580, 0.013328, + 0.015229, 0.017289, 0.019512, 0.021902, 0.024465, 0.027205, 0.030125, 0.033231, 0.036527, 0.040016, + 0.043703, 0.047593, 0.051688, 0.055993, 0.060513, 0.065249, 0.070208, 0.075392, 0.080805, 0.086451, + 0.092333, 0.098455, 0.104821, 0.111434, 0.118298, 0.125416, 0.132792, 0.140428, 0.148329, 0.156498, + 0.164938, 0.173653, 0.182645, 0.191919, 0.201476, 0.211321, 0.221457, 0.231886, 0.242612, 0.253639, + 0.264968, 0.276603, 0.288548, 0.300805, 0.313378, 0.326268, 0.339480, 0.353016, 0.366879, 0.381073, + 0.395599, 0.410461, 0.425662, 0.441204, 0.457091, 0.473325, 0.489909, 0.506846, 0.524138, 0.541789, + 0.559801, 0.578177, 0.596920, 0.616032, 0.635515, 0.655374, 0.675610, 0.696226, 0.717224, 0.738608, + 0.760380, 0.782542, 0.805097, 0.828048, 0.851398, 0.875148, 0.899301, 0.923861, 0.948829, 0.974208, + 1.000000, +}; + +static uint32_t gamma_correction_calculator(uint32_t duty) +{ + return gamma_correction_lut[duty * 100 / (1 << LEDC_DUTY_RES)] * (1 << LEDC_DUTY_RES); +} +#else // !CONFIG_GAMMA_CORRECTION_WITH_LUT +#define GAMMA_FACTOR (2.8) + +static uint32_t gamma_correction_calculator(uint32_t duty) +{ + return pow((double)duty / (1 << LEDC_DUTY_RES), GAMMA_FACTOR) * (1 << LEDC_DUTY_RES); +} +#endif // CONFIG_GAMMA_CORRECTION_WITH_LUT + + +static void rgb_set_duty_and_update(rgb_channel_config_t rgb_channels, + uint32_t target_r_duty, uint32_t target_g_duty, uint32_t target_b_duty) +{ + ESP_ERROR_CHECK(ledc_set_duty(LEDC_MODE, rgb_channels.red_channel, target_r_duty)); + ESP_ERROR_CHECK(ledc_set_duty(LEDC_MODE, rgb_channels.green_channel, target_g_duty)); + ESP_ERROR_CHECK(ledc_set_duty(LEDC_MODE, rgb_channels.blue_channel, target_b_duty)); + + ESP_ERROR_CHECK(ledc_update_duty(LEDC_MODE, rgb_channels.red_channel)); + ESP_ERROR_CHECK(ledc_update_duty(LEDC_MODE, rgb_channels.green_channel)); + ESP_ERROR_CHECK(ledc_update_duty(LEDC_MODE, rgb_channels.blue_channel)); +} + +static void rgb_set_linear_fade(rgb_channel_config_t rgb_channels, + uint32_t target_r_duty, uint32_t target_g_duty, uint32_t target_b_duty, + uint32_t duration) +{ + ESP_ERROR_CHECK(ledc_set_fade_with_time(LEDC_MODE, rgb_channels.red_channel, target_r_duty, duration)); + ESP_ERROR_CHECK(ledc_set_fade_with_time(LEDC_MODE, rgb_channels.green_channel, target_g_duty, duration)); + ESP_ERROR_CHECK(ledc_set_fade_with_time(LEDC_MODE, rgb_channels.blue_channel, target_b_duty, duration)); +} + +static void rgb_set_gamma_curve_fade(rgb_channel_config_t rgb_channels, + uint32_t start_r_duty, uint32_t start_g_duty, uint32_t start_b_duty, + uint32_t target_r_duty, uint32_t target_g_duty, uint32_t target_b_duty, + uint32_t duration) +{ + const uint32_t linear_fade_segments = 12; + uint32_t actual_fade_ranges; + ledc_fade_param_config_t fade_params_list[SOC_LEDC_GAMMA_CURVE_FADE_RANGE_MAX] = {}; + + ESP_ERROR_CHECK(ledc_fill_multi_fade_param_list(LEDC_MODE, rgb_channels.red_channel, + start_r_duty, target_r_duty, + linear_fade_segments, duration, + gamma_correction_calculator, + SOC_LEDC_GAMMA_CURVE_FADE_RANGE_MAX, + fade_params_list, &actual_fade_ranges)); + ESP_ERROR_CHECK(ledc_set_multi_fade(LEDC_MODE, rgb_channels.red_channel, gamma_correction_calculator(start_r_duty), fade_params_list, actual_fade_ranges)); + + ESP_ERROR_CHECK(ledc_fill_multi_fade_param_list(LEDC_MODE, rgb_channels.green_channel, + start_g_duty, target_g_duty, + linear_fade_segments, duration, + gamma_correction_calculator, + SOC_LEDC_GAMMA_CURVE_FADE_RANGE_MAX, + fade_params_list, &actual_fade_ranges)); + ESP_ERROR_CHECK(ledc_set_multi_fade(LEDC_MODE, rgb_channels.green_channel, gamma_correction_calculator(start_g_duty), fade_params_list, actual_fade_ranges)); + + ESP_ERROR_CHECK(ledc_fill_multi_fade_param_list(LEDC_MODE, rgb_channels.blue_channel, + start_b_duty, target_b_duty, + linear_fade_segments, duration, + gamma_correction_calculator, + SOC_LEDC_GAMMA_CURVE_FADE_RANGE_MAX, + fade_params_list, &actual_fade_ranges)); + ESP_ERROR_CHECK(ledc_set_multi_fade(LEDC_MODE, rgb_channels.blue_channel, gamma_correction_calculator(start_b_duty), fade_params_list, actual_fade_ranges)); +} + +static void rgb_fade_start(rgb_channel_config_t rgb_channels) +{ + ESP_ERROR_CHECK(ledc_fade_start(LEDC_MODE, rgb_channels.red_channel, LEDC_FADE_NO_WAIT)); + ESP_ERROR_CHECK(ledc_fade_start(LEDC_MODE, rgb_channels.green_channel, LEDC_FADE_NO_WAIT)); + ESP_ERROR_CHECK(ledc_fade_start(LEDC_MODE, rgb_channels.blue_channel, LEDC_FADE_NO_WAIT)); +} + +static void example_rgb_ledc_init(void) +{ + // Prepare and then apply the LEDC PWM timer configuration + ledc_timer_config_t ledc_timer = { + .speed_mode = LEDC_MODE, + .timer_num = LEDC_TIMER, + .duty_resolution = LEDC_DUTY_RES, + .freq_hz = LEDC_FREQUENCY, // Set output frequency at 5 kHz + .clk_cfg = LEDC_AUTO_CLK + }; + ESP_ERROR_CHECK(ledc_timer_config(&ledc_timer)); + + // Prepare and then apply the LEDC PWM configuration to the six channels + ledc_channel_config_t ledc_channel = { + .speed_mode = LEDC_MODE, + .timer_sel = LEDC_TIMER, + .intr_type = LEDC_INTR_DISABLE, + .duty = 0, // Set initial duty to 0% + .hpoint = 0 + }; + ledc_channel.channel = LEDC_CHANNEL_RED; + ledc_channel.gpio_num = LEDC_RED_IO; + ESP_ERROR_CHECK(ledc_channel_config(&ledc_channel)); + ledc_channel.channel = LEDC_CHANNEL_GREEN; + ledc_channel.gpio_num = LEDC_GREEN_IO; + ESP_ERROR_CHECK(ledc_channel_config(&ledc_channel)); + ledc_channel.channel = LEDC_CHANNEL_BLUE; + ledc_channel.gpio_num = LEDC_BLUE_IO; + ESP_ERROR_CHECK(ledc_channel_config(&ledc_channel)); + ledc_channel.channel = LEDC_CHANNEL_GAMMA_RED; + ledc_channel.gpio_num = LEDC_GAMMA_RED_IO; + ESP_ERROR_CHECK(ledc_channel_config(&ledc_channel)); + ledc_channel.channel = LEDC_CHANNEL_GAMMA_GREEN; + ledc_channel.gpio_num = LEDC_GAMMA_GREEN_IO; + ESP_ERROR_CHECK(ledc_channel_config(&ledc_channel)); + ledc_channel.channel = LEDC_CHANNEL_GAMMA_BLUE; + ledc_channel.gpio_num = LEDC_GAMMA_BLUE_IO; + ESP_ERROR_CHECK(ledc_channel_config(&ledc_channel)); +} + +static void example_set_fade_and_start(uint32_t start_r_duty, uint32_t start_g_duty, uint32_t start_b_duty, + uint32_t target_r_duty, uint32_t target_g_duty, uint32_t target_b_duty, + uint32_t duration) +{ + rgb_set_linear_fade(rgb_led_1_channels, target_r_duty, target_g_duty, target_b_duty, duration); + rgb_set_gamma_curve_fade(rgb_led_2_channels, start_r_duty, start_g_duty, start_b_duty, target_r_duty, target_g_duty, target_b_duty, duration); + rgb_fade_start(rgb_led_1_channels); + rgb_fade_start(rgb_led_2_channels); + vTaskDelay(pdMS_TO_TICKS(duration)); +} + +void app_main(void) +{ + example_rgb_ledc_init(); + ledc_fade_func_install(0); + + // Light up RGB LEDs to GREEN in 500ms + example_set_fade_and_start(OFF_R, OFF_G, OFF_B, GREEN_R, GREEN_G, GREEN_B, 500); + vTaskDelay(pdMS_TO_TICKS(1000)); + + // Dim to OFF in 2000ms + example_set_fade_and_start(GREEN_R, GREEN_G, GREEN_B, OFF_R, OFF_G, OFF_B, 2000); + vTaskDelay(pdMS_TO_TICKS(1000)); + + // Set RGB LEDs to BLUEISH_PURPLE color + rgb_set_duty_and_update(rgb_led_1_channels, BLUEISH_PURPLE_R, BLUEISH_PURPLE_G, BLUEISH_PURPLE_B); + rgb_set_duty_and_update(rgb_led_2_channels, gamma_correction_calculator(BLUEISH_PURPLE_R), gamma_correction_calculator(BLUEISH_PURPLE_G), gamma_correction_calculator(BLUEISH_PURPLE_B)); + vTaskDelay(pdMS_TO_TICKS(1000)); + + // Dim to OFF in 5000ms + example_set_fade_and_start(BLUEISH_PURPLE_R, BLUEISH_PURPLE_G, BLUEISH_PURPLE_B, OFF_R, OFF_G, OFF_B, 5000); + vTaskDelay(pdMS_TO_TICKS(1000)); + + // Light up to ORANGE in 5000ms + example_set_fade_and_start(OFF_R, OFF_G, OFF_B, ORANGE_R, ORANGE_G, ORANGE_B, 5000); + vTaskDelay(pdMS_TO_TICKS(1000)); + + // Fade color to ROSE PINK in 5000ms + example_set_fade_and_start(ORANGE_R, ORANGE_G, ORANGE_B, ROSE_R, ROSE_G, ROSE_B, 5000); + vTaskDelay(pdMS_TO_TICKS(1000)); + + // Cycle through color spectrum + while (1) { + example_set_fade_and_start(RED_R, RED_G, RED_B, YELLOW_R, YELLOW_G, YELLOW_B, 3000); + vTaskDelay(pdMS_TO_TICKS(5)); + + example_set_fade_and_start(YELLOW_R, YELLOW_G, YELLOW_B, GREEN_R, GREEN_G, GREEN_B, 3000); + vTaskDelay(pdMS_TO_TICKS(5)); + + example_set_fade_and_start(GREEN_R, GREEN_G, GREEN_B, CYAN_R, CYAN_G, CYAN_B, 3000); + vTaskDelay(pdMS_TO_TICKS(5)); + + example_set_fade_and_start(CYAN_R, CYAN_G, CYAN_B, BLUE_R, BLUE_G, BLUE_B, 3000); + vTaskDelay(pdMS_TO_TICKS(5)); + + example_set_fade_and_start(BLUE_R, BLUE_G, BLUE_B, MAGENTA_R, MAGENTA_G, MAGENTA_B, 3000); + vTaskDelay(pdMS_TO_TICKS(5)); + + example_set_fade_and_start(MAGENTA_R, MAGENTA_G, MAGENTA_B, RED_R, RED_G, RED_B, 3000); + vTaskDelay(pdMS_TO_TICKS(5)); + } +}