Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 7462b09

Browse files
P-R-O-C-H-Ypre-commit-ci-lite[bot]
andauthoredJun 16, 2025··
feat(LEDC): Add Gamma Fade support and enhance auto channel/timer selection for multi-group (espressif#11464)
* feat(ledc): Enhance LEDC auto channel/timer selection for multi-group support * feat(ledc): Add Gamma Fade support * fix(example): Update comments * ci(pre-commit): Apply automatic fixes --------- Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
1 parent ef995b6 commit 7462b09

File tree

4 files changed

+428
-31
lines changed

4 files changed

+428
-31
lines changed
 

‎cores/esp32/esp32-hal-ledc.c

Lines changed: 233 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222
#include "soc/gpio_sig_map.h"
2323
#include "esp_rom_gpio.h"
2424
#include "hal/ledc_ll.h"
25+
#if SOC_LEDC_GAMMA_CURVE_FADE_SUPPORTED
26+
#include <math.h>
27+
#endif
2528

2629
#ifdef SOC_LEDC_SUPPORT_HS_MODE
2730
#define LEDC_CHANNELS (SOC_LEDC_CHANNEL_NUM << 1)
@@ -56,7 +59,7 @@ static bool find_matching_timer(uint8_t speed_mode, uint32_t freq, uint8_t resol
5659
peripheral_bus_type_t type = perimanGetPinBusType(i);
5760
if (type == ESP32_BUS_TYPE_LEDC) {
5861
ledc_channel_handle_t *bus = (ledc_channel_handle_t *)perimanGetPinBus(i, ESP32_BUS_TYPE_LEDC);
59-
if (bus != NULL && (bus->channel / 8) == speed_mode && bus->freq_hz == freq && bus->channel_resolution == resolution) {
62+
if (bus != NULL && (bus->channel / SOC_LEDC_CHANNEL_NUM) == speed_mode && bus->freq_hz == freq && bus->channel_resolution == resolution) {
6063
log_d("Found matching timer %u for freq=%u, resolution=%u", bus->timer_num, freq, resolution);
6164
*timer_num = bus->timer_num;
6265
return true;
@@ -78,7 +81,7 @@ static bool find_free_timer(uint8_t speed_mode, uint8_t *timer_num) {
7881
peripheral_bus_type_t type = perimanGetPinBusType(i);
7982
if (type == ESP32_BUS_TYPE_LEDC) {
8083
ledc_channel_handle_t *bus = (ledc_channel_handle_t *)perimanGetPinBus(i, ESP32_BUS_TYPE_LEDC);
81-
if (bus != NULL && (bus->channel / 8) == speed_mode) {
84+
if (bus != NULL && (bus->channel / SOC_LEDC_CHANNEL_NUM) == speed_mode) {
8285
log_d("Timer %u is in use by channel %u", bus->timer_num, bus->channel);
8386
used_timers |= (1 << bus->timer_num);
8487
}
@@ -110,7 +113,7 @@ static void remove_channel_from_timer(uint8_t speed_mode, uint8_t timer_num, uin
110113
peripheral_bus_type_t type = perimanGetPinBusType(i);
111114
if (type == ESP32_BUS_TYPE_LEDC) {
112115
ledc_channel_handle_t *bus = (ledc_channel_handle_t *)perimanGetPinBus(i, ESP32_BUS_TYPE_LEDC);
113-
if (bus != NULL && (bus->channel / 8) == speed_mode && bus->timer_num == timer_num && bus->channel != channel) {
116+
if (bus != NULL && (bus->channel / SOC_LEDC_CHANNEL_NUM) == speed_mode && bus->timer_num == timer_num && bus->channel != channel) {
114117
log_d("Timer %u is still in use by channel %u", timer_num, bus->channel);
115118
timer_in_use = true;
116119
break;
@@ -168,8 +171,8 @@ static bool ledcDetachBus(void *bus) {
168171
}
169172
pinMatrixOutDetach(handle->pin, false, false);
170173
if (!channel_found) {
171-
uint8_t group = (handle->channel / 8);
172-
remove_channel_from_timer(group, handle->timer_num, handle->channel % 8);
174+
uint8_t group = (handle->channel / SOC_LEDC_CHANNEL_NUM);
175+
remove_channel_from_timer(group, handle->timer_num, handle->channel % SOC_LEDC_CHANNEL_NUM);
173176
ledc_handle.used_channels &= ~(1UL << handle->channel);
174177
}
175178
free(handle);
@@ -206,21 +209,21 @@ bool ledcAttachChannel(uint8_t pin, uint32_t freq, uint8_t resolution, uint8_t c
206209
return false;
207210
}
208211

209-
uint8_t group = (channel / 8);
212+
uint8_t group = (channel / SOC_LEDC_CHANNEL_NUM);
210213
uint8_t timer = 0;
211214
bool channel_used = ledc_handle.used_channels & (1UL << channel);
212215

213216
if (channel_used) {
214217
log_i("Channel %u is already set up, given frequency and resolution will be ignored", channel);
215-
if (ledc_set_pin(pin, group, channel % 8) != ESP_OK) {
218+
if (ledc_set_pin(pin, group, channel % SOC_LEDC_CHANNEL_NUM) != ESP_OK) {
216219
log_e("Attaching pin to already used channel failed!");
217220
return false;
218221
}
219222
} else {
220223
// Find a timer with matching frequency and resolution, or a free timer
221224
if (!find_matching_timer(group, freq, resolution, &timer)) {
222225
if (!find_free_timer(group, &timer)) {
223-
log_e("No free timers available for speed mode %u", group);
226+
log_w("No free timers available for speed mode %u", group);
224227
return false;
225228
}
226229

@@ -239,12 +242,12 @@ bool ledcAttachChannel(uint8_t pin, uint32_t freq, uint8_t resolution, uint8_t c
239242
}
240243
}
241244

242-
uint32_t duty = ledc_get_duty(group, (channel % 8));
245+
uint32_t duty = ledc_get_duty(group, (channel % SOC_LEDC_CHANNEL_NUM));
243246

244247
ledc_channel_config_t ledc_channel;
245248
memset((void *)&ledc_channel, 0, sizeof(ledc_channel_config_t));
246249
ledc_channel.speed_mode = group;
247-
ledc_channel.channel = (channel % 8);
250+
ledc_channel.channel = (channel % SOC_LEDC_CHANNEL_NUM);
248251
ledc_channel.timer_sel = timer;
249252
ledc_channel.intr_type = LEDC_INTR_DISABLE;
250253
ledc_channel.gpio_num = pin;
@@ -274,7 +277,7 @@ bool ledcAttachChannel(uint8_t pin, uint32_t freq, uint8_t resolution, uint8_t c
274277
ledc_handle.used_channels |= 1UL << channel;
275278
}
276279

277-
if (!perimanSetPinBus(pin, ESP32_BUS_TYPE_LEDC, (void *)handle, group, channel)) {
280+
if (!perimanSetPinBus(pin, ESP32_BUS_TYPE_LEDC, (void *)handle, channel, timer)) {
278281
ledcDetachBus((void *)handle);
279282
return false;
280283
}
@@ -291,14 +294,40 @@ bool ledcAttach(uint8_t pin, uint32_t freq, uint8_t resolution) {
291294
}
292295
uint8_t channel = __builtin_ctz(free_channel); // Convert the free_channel bit to channel number
293296

294-
return ledcAttachChannel(pin, freq, resolution, channel);
297+
// Try the first available channel
298+
if (ledcAttachChannel(pin, freq, resolution, channel)) {
299+
return true;
300+
}
301+
302+
#ifdef SOC_LEDC_SUPPORT_HS_MODE
303+
// If first attempt failed and HS mode is supported, try to find a free channel in group 1
304+
if ((channel / SOC_LEDC_CHANNEL_NUM) == 0) { // First attempt was in group 0
305+
log_d("LEDC: Group 0 channel %u failed, trying to find a free channel in group 1", channel);
306+
// Find free channels specifically in group 1
307+
uint32_t group1_mask = ((1UL << SOC_LEDC_CHANNEL_NUM) - 1) << SOC_LEDC_CHANNEL_NUM;
308+
int group1_free_channel = (~ledc_handle.used_channels) & group1_mask;
309+
if (group1_free_channel != 0) {
310+
uint8_t group1_channel = __builtin_ctz(group1_free_channel);
311+
if (ledcAttachChannel(pin, freq, resolution, group1_channel)) {
312+
return true;
313+
}
314+
}
315+
}
316+
#endif
317+
318+
log_e(
319+
"No free timers available for freq=%u, resolution=%u. To attach a new channel, use the same frequency and resolution as an already attached channel to "
320+
"share its timer.",
321+
freq, resolution
322+
);
323+
return false;
295324
}
296325

297326
bool ledcWrite(uint8_t pin, uint32_t duty) {
298327
ledc_channel_handle_t *bus = (ledc_channel_handle_t *)perimanGetPinBus(pin, ESP32_BUS_TYPE_LEDC);
299328
if (bus != NULL) {
300329

301-
uint8_t group = (bus->channel / 8), channel = (bus->channel % 8);
330+
uint8_t group = (bus->channel / SOC_LEDC_CHANNEL_NUM), channel = (bus->channel % SOC_LEDC_CHANNEL_NUM);
302331

303332
//Fixing if all bits in resolution is set = LEDC FULL ON
304333
uint32_t max_duty = (1 << bus->channel_resolution) - 1;
@@ -307,8 +336,14 @@ bool ledcWrite(uint8_t pin, uint32_t duty) {
307336
duty = max_duty + 1;
308337
}
309338

310-
ledc_set_duty(group, channel, duty);
311-
ledc_update_duty(group, channel);
339+
if (ledc_set_duty(group, channel, duty) != ESP_OK) {
340+
log_e("ledc_set_duty failed");
341+
return false;
342+
}
343+
if (ledc_update_duty(group, channel) != ESP_OK) {
344+
log_e("ledc_update_duty failed");
345+
return false;
346+
}
312347

313348
return true;
314349
}
@@ -321,7 +356,11 @@ bool ledcWriteChannel(uint8_t channel, uint32_t duty) {
321356
log_e("Channel %u is not available (maximum %u) or not used!", channel, LEDC_CHANNELS);
322357
return false;
323358
}
324-
uint8_t group = (channel / 8), timer = ((channel / 2) % 4);
359+
uint8_t group = (channel / SOC_LEDC_CHANNEL_NUM);
360+
ledc_timer_t timer;
361+
362+
// Get the actual timer being used by this channel
363+
ledc_ll_get_channel_timer(LEDC_LL_GET_HW(), group, (channel % SOC_LEDC_CHANNEL_NUM), &timer);
325364

326365
//Fixing if all bits in resolution is set = LEDC FULL ON
327366
uint32_t resolution = 0;
@@ -333,8 +372,14 @@ bool ledcWriteChannel(uint8_t channel, uint32_t duty) {
333372
duty = max_duty + 1;
334373
}
335374

336-
ledc_set_duty(group, channel, duty);
337-
ledc_update_duty(group, channel);
375+
if (ledc_set_duty(group, channel, duty) != ESP_OK) {
376+
log_e("ledc_set_duty failed");
377+
return false;
378+
}
379+
if (ledc_update_duty(group, channel) != ESP_OK) {
380+
log_e("ledc_update_duty failed");
381+
return false;
382+
}
338383

339384
return true;
340385
}
@@ -343,7 +388,7 @@ uint32_t ledcRead(uint8_t pin) {
343388
ledc_channel_handle_t *bus = (ledc_channel_handle_t *)perimanGetPinBus(pin, ESP32_BUS_TYPE_LEDC);
344389
if (bus != NULL) {
345390

346-
uint8_t group = (bus->channel / 8), channel = (bus->channel % 8);
391+
uint8_t group = (bus->channel / SOC_LEDC_CHANNEL_NUM), channel = (bus->channel % SOC_LEDC_CHANNEL_NUM);
347392
return ledc_get_duty(group, channel);
348393
}
349394
return 0;
@@ -355,8 +400,8 @@ uint32_t ledcReadFreq(uint8_t pin) {
355400
if (!ledcRead(pin)) {
356401
return 0;
357402
}
358-
uint8_t group = (bus->channel / 8), timer = ((bus->channel / 2) % 4);
359-
return ledc_get_freq(group, timer);
403+
uint8_t group = (bus->channel / SOC_LEDC_CHANNEL_NUM);
404+
return ledc_get_freq(group, bus->timer_num);
360405
}
361406
return 0;
362407
}
@@ -370,12 +415,12 @@ uint32_t ledcWriteTone(uint8_t pin, uint32_t freq) {
370415
return 0;
371416
}
372417

373-
uint8_t group = (bus->channel / 8), timer = ((bus->channel / 2) % 4);
418+
uint8_t group = (bus->channel / SOC_LEDC_CHANNEL_NUM);
374419

375420
ledc_timer_config_t ledc_timer;
376421
memset((void *)&ledc_timer, 0, sizeof(ledc_timer_config_t));
377422
ledc_timer.speed_mode = group;
378-
ledc_timer.timer_num = timer;
423+
ledc_timer.timer_num = bus->timer_num;
379424
ledc_timer.duty_resolution = 10;
380425
ledc_timer.freq_hz = freq;
381426
ledc_timer.clk_cfg = clock_source;
@@ -386,7 +431,7 @@ uint32_t ledcWriteTone(uint8_t pin, uint32_t freq) {
386431
}
387432
bus->channel_resolution = 10;
388433

389-
uint32_t res_freq = ledc_get_freq(group, timer);
434+
uint32_t res_freq = ledc_get_freq(group, bus->timer_num);
390435
ledcWrite(pin, 0x1FF);
391436
return res_freq;
392437
}
@@ -427,12 +472,12 @@ uint32_t ledcChangeFrequency(uint8_t pin, uint32_t freq, uint8_t resolution) {
427472
log_e("LEDC pin %u - resolution is zero or it is too big (maximum %u)", pin, LEDC_MAX_BIT_WIDTH);
428473
return 0;
429474
}
430-
uint8_t group = (bus->channel / 8), timer = ((bus->channel / 2) % 4);
475+
uint8_t group = (bus->channel / SOC_LEDC_CHANNEL_NUM);
431476

432477
ledc_timer_config_t ledc_timer;
433478
memset((void *)&ledc_timer, 0, sizeof(ledc_timer_config_t));
434479
ledc_timer.speed_mode = group;
435-
ledc_timer.timer_num = timer;
480+
ledc_timer.timer_num = bus->timer_num;
436481
ledc_timer.duty_resolution = resolution;
437482
ledc_timer.freq_hz = freq;
438483
ledc_timer.clk_cfg = clock_source;
@@ -442,7 +487,7 @@ uint32_t ledcChangeFrequency(uint8_t pin, uint32_t freq, uint8_t resolution) {
442487
return 0;
443488
}
444489
bus->channel_resolution = resolution;
445-
return ledc_get_freq(group, timer);
490+
return ledc_get_freq(group, bus->timer_num);
446491
}
447492
return 0;
448493
}
@@ -453,12 +498,14 @@ bool ledcOutputInvert(uint8_t pin, bool out_invert) {
453498
gpio_set_level(pin, out_invert);
454499

455500
#ifdef CONFIG_IDF_TARGET_ESP32P4
456-
esp_rom_gpio_connect_out_signal(pin, LEDC_LS_SIG_OUT_PAD_OUT0_IDX + ((bus->channel) % 8), out_invert, 0);
501+
esp_rom_gpio_connect_out_signal(pin, LEDC_LS_SIG_OUT_PAD_OUT0_IDX + ((bus->channel) % SOC_LEDC_CHANNEL_NUM), out_invert, 0);
457502
#else
458503
#ifdef SOC_LEDC_SUPPORT_HS_MODE
459-
esp_rom_gpio_connect_out_signal(pin, ((bus->channel / 8 == 0) ? LEDC_HS_SIG_OUT0_IDX : LEDC_LS_SIG_OUT0_IDX) + ((bus->channel) % 8), out_invert, 0);
504+
esp_rom_gpio_connect_out_signal(
505+
pin, ((bus->channel / SOC_LEDC_CHANNEL_NUM == 0) ? LEDC_HS_SIG_OUT0_IDX : LEDC_LS_SIG_OUT0_IDX) + ((bus->channel) % SOC_LEDC_CHANNEL_NUM), out_invert, 0
506+
);
460507
#else
461-
esp_rom_gpio_connect_out_signal(pin, LEDC_LS_SIG_OUT0_IDX + ((bus->channel) % 8), out_invert, 0);
508+
esp_rom_gpio_connect_out_signal(pin, LEDC_LS_SIG_OUT0_IDX + ((bus->channel) % SOC_LEDC_CHANNEL_NUM), out_invert, 0);
462509
#endif
463510
#endif // ifdef CONFIG_IDF_TARGET_ESP32P4
464511
return true;
@@ -505,7 +552,7 @@ static bool ledcFadeConfig(uint8_t pin, uint32_t start_duty, uint32_t target_dut
505552
}
506553
#endif
507554
#endif
508-
uint8_t group = (bus->channel / 8), channel = (bus->channel % 8);
555+
uint8_t group = (bus->channel / SOC_LEDC_CHANNEL_NUM), channel = (bus->channel % SOC_LEDC_CHANNEL_NUM);
509556

510557
// Initialize fade service.
511558
if (!fade_initialized) {
@@ -562,6 +609,161 @@ bool ledcFadeWithInterruptArg(uint8_t pin, uint32_t start_duty, uint32_t target_
562609
return ledcFadeConfig(pin, start_duty, target_duty, max_fade_time_ms, userFunc, arg);
563610
}
564611

612+
#ifdef SOC_LEDC_GAMMA_CURVE_FADE_SUPPORTED
613+
// Default gamma factor for gamma correction (common value for LEDs)
614+
static float ledcGammaFactor = 2.8;
615+
// Gamma correction LUT support
616+
static const float *ledcGammaLUT = NULL;
617+
static uint16_t ledcGammaLUTSize = 0;
618+
// Global variable to store current resolution for gamma callback
619+
static uint8_t ledcGammaResolution = 13;
620+
621+
bool ledcSetGammaTable(const float *gamma_table, uint16_t size) {
622+
if (gamma_table == NULL || size == 0) {
623+
log_e("Invalid gamma table or size");
624+
return false;
625+
}
626+
ledcGammaLUT = gamma_table;
627+
ledcGammaLUTSize = size;
628+
log_i("Custom gamma LUT set with %u entries", size);
629+
return true;
630+
}
631+
632+
void ledcClearGammaTable(void) {
633+
ledcGammaLUT = NULL;
634+
ledcGammaLUTSize = 0;
635+
log_i("Gamma LUT cleared, using mathematical calculation");
636+
}
637+
638+
void ledcSetGammaFactor(float factor) {
639+
ledcGammaFactor = factor;
640+
}
641+
642+
// Gamma correction calculator function
643+
static uint32_t ledcGammaCorrection(uint32_t duty) {
644+
if (duty == 0) {
645+
return 0;
646+
}
647+
648+
uint32_t max_duty = (1U << ledcGammaResolution) - 1;
649+
if (duty >= (1U << ledcGammaResolution)) {
650+
return max_duty;
651+
}
652+
653+
// Use LUT if provided, otherwise use mathematical calculation
654+
if (ledcGammaLUT != NULL && ledcGammaLUTSize > 0) {
655+
// LUT-based gamma correction
656+
uint32_t lut_index = (duty * (ledcGammaLUTSize - 1)) / max_duty;
657+
if (lut_index >= ledcGammaLUTSize) {
658+
lut_index = ledcGammaLUTSize - 1;
659+
}
660+
661+
float corrected_normalized = ledcGammaLUT[lut_index];
662+
return (uint32_t)(corrected_normalized * max_duty);
663+
} else {
664+
// Mathematical gamma correction
665+
double normalized = (double)duty / (1U << ledcGammaResolution);
666+
double corrected = pow(normalized, ledcGammaFactor);
667+
return (uint32_t)(corrected * (1U << ledcGammaResolution));
668+
}
669+
}
670+
671+
static bool ledcFadeGammaConfig(uint8_t pin, uint32_t start_duty, uint32_t target_duty, int max_fade_time_ms, void (*userFunc)(void *), void *arg) {
672+
ledc_channel_handle_t *bus = (ledc_channel_handle_t *)perimanGetPinBus(pin, ESP32_BUS_TYPE_LEDC);
673+
if (bus != NULL) {
674+
675+
#ifndef SOC_LEDC_SUPPORT_FADE_STOP
676+
#if !CONFIG_DISABLE_HAL_LOCKS
677+
if (bus->lock == NULL) {
678+
bus->lock = xSemaphoreCreateBinary();
679+
if (bus->lock == NULL) {
680+
log_e("xSemaphoreCreateBinary failed");
681+
return false;
682+
}
683+
xSemaphoreGive(bus->lock);
684+
}
685+
//acquire lock
686+
if (xSemaphoreTake(bus->lock, 0) != pdTRUE) {
687+
log_e("LEDC Fade is still running on pin %u! SoC does not support stopping fade.", pin);
688+
return false;
689+
}
690+
#endif
691+
#endif
692+
uint8_t group = (bus->channel / SOC_LEDC_CHANNEL_NUM), channel = (bus->channel % SOC_LEDC_CHANNEL_NUM);
693+
694+
// Initialize fade service.
695+
if (!fade_initialized) {
696+
ledc_fade_func_install(0);
697+
fade_initialized = true;
698+
}
699+
700+
bus->fn = (voidFuncPtr)userFunc;
701+
bus->arg = arg;
702+
703+
ledc_cbs_t callbacks = {.fade_cb = ledcFnWrapper};
704+
ledc_cb_register(group, channel, &callbacks, (void *)bus);
705+
706+
// Prepare gamma curve fade parameters
707+
ledc_fade_param_config_t fade_params[SOC_LEDC_GAMMA_CURVE_FADE_RANGE_MAX];
708+
uint32_t actual_fade_ranges = 0;
709+
710+
// Use a moderate number of linear segments for smooth gamma curve
711+
const uint32_t linear_fade_segments = 12;
712+
713+
// Set the global resolution for gamma correction
714+
ledcGammaResolution = bus->channel_resolution;
715+
716+
// Fill multi-fade parameter list using ESP-IDF API
717+
esp_err_t err = ledc_fill_multi_fade_param_list(
718+
group, channel, start_duty, target_duty, linear_fade_segments, max_fade_time_ms, ledcGammaCorrection, SOC_LEDC_GAMMA_CURVE_FADE_RANGE_MAX, fade_params,
719+
&actual_fade_ranges
720+
);
721+
722+
if (err != ESP_OK) {
723+
log_e("ledc_fill_multi_fade_param_list failed: %s", esp_err_to_name(err));
724+
return false;
725+
}
726+
727+
// Apply the gamma-corrected start duty
728+
uint32_t gamma_start_duty = ledcGammaCorrection(start_duty);
729+
730+
// Set multi-fade parameters
731+
err = ledc_set_multi_fade(group, channel, gamma_start_duty, fade_params, actual_fade_ranges);
732+
if (err != ESP_OK) {
733+
log_e("ledc_set_multi_fade failed: %s", esp_err_to_name(err));
734+
return false;
735+
}
736+
737+
// Start the gamma curve fade
738+
err = ledc_fade_start(group, channel, LEDC_FADE_NO_WAIT);
739+
if (err != ESP_OK) {
740+
log_e("ledc_fade_start failed: %s", esp_err_to_name(err));
741+
return false;
742+
}
743+
744+
log_d("Gamma curve fade started on pin %u: %u -> %u over %dms", pin, start_duty, target_duty, max_fade_time_ms);
745+
746+
} else {
747+
log_e("Pin %u is not attached to LEDC. Call ledcAttach first!", pin);
748+
return false;
749+
}
750+
return true;
751+
}
752+
753+
bool ledcFadeGamma(uint8_t pin, uint32_t start_duty, uint32_t target_duty, int max_fade_time_ms) {
754+
return ledcFadeGammaConfig(pin, start_duty, target_duty, max_fade_time_ms, NULL, NULL);
755+
}
756+
757+
bool ledcFadeGammaWithInterrupt(uint8_t pin, uint32_t start_duty, uint32_t target_duty, int max_fade_time_ms, voidFuncPtr userFunc) {
758+
return ledcFadeGammaConfig(pin, start_duty, target_duty, max_fade_time_ms, (voidFuncPtrArg)userFunc, NULL);
759+
}
760+
761+
bool ledcFadeGammaWithInterruptArg(uint8_t pin, uint32_t start_duty, uint32_t target_duty, int max_fade_time_ms, void (*userFunc)(void *), void *arg) {
762+
return ledcFadeGammaConfig(pin, start_duty, target_duty, max_fade_time_ms, userFunc, arg);
763+
}
764+
765+
#endif /* SOC_LEDC_GAMMA_CURVE_FADE_SUPPORTED */
766+
565767
static uint8_t analog_resolution = 8;
566768
static int analog_frequency = 1000;
567769
void analogWrite(uint8_t pin, int value) {

‎cores/esp32/esp32-hal-ledc.h

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,85 @@ bool ledcFadeWithInterrupt(uint8_t pin, uint32_t start_duty, uint32_t target_dut
232232
*/
233233
bool ledcFadeWithInterruptArg(uint8_t pin, uint32_t start_duty, uint32_t target_duty, int max_fade_time_ms, void (*userFunc)(void *), void *arg);
234234

235+
//Gamma Curve Fade functions - only available on supported chips
236+
#ifdef SOC_LEDC_GAMMA_CURVE_FADE_SUPPORTED
237+
238+
/**
239+
* @brief Set a custom gamma correction lookup table for gamma curve fading.
240+
* The LUT should contain normalized values (0.0 to 1.0) representing
241+
* the gamma-corrected brightness curve.
242+
*
243+
* @param gamma_table Pointer to array of float values (0.0 to 1.0)
244+
* @param size Number of entries in the lookup table
245+
*
246+
* @return true if gamma table was successfully set, false otherwise.
247+
*
248+
* @note The LUT array must remain valid for as long as gamma fading is used.
249+
* Larger tables provide smoother transitions but use more memory.
250+
*/
251+
bool ledcSetGammaTable(const float *gamma_table, uint16_t size);
252+
253+
/**
254+
* @brief Clear the current gamma correction lookup table.
255+
* After calling this, gamma correction will use mathematical
256+
* calculation with the default gamma factor (2.8).
257+
*/
258+
void ledcClearGammaTable(void);
259+
260+
/**
261+
* @brief Set the gamma factor for gamma correction.
262+
*
263+
* @param factor Gamma factor to use for gamma correction.
264+
*/
265+
void ledcSetGammaFactor(float factor);
266+
267+
/**
268+
* @brief Setup and start a gamma curve fade on a given LEDC pin.
269+
* Gamma correction makes LED brightness changes appear more gradual to human eyes.
270+
*
271+
* @param pin GPIO pin
272+
* @param start_duty initial duty cycle of the fade
273+
* @param target_duty target duty cycle of the fade
274+
* @param max_fade_time_ms maximum fade time in milliseconds
275+
*
276+
* @return true if gamma fade was successfully set and started, false otherwise.
277+
*
278+
* @note This function is only available on ESP32 variants that support gamma curve fading.
279+
*/
280+
bool ledcFadeGamma(uint8_t pin, uint32_t start_duty, uint32_t target_duty, int max_fade_time_ms);
281+
282+
/**
283+
* @brief Setup and start a gamma curve fade on a given LEDC pin with a callback function.
284+
*
285+
* @param pin GPIO pin
286+
* @param start_duty initial duty cycle of the fade
287+
* @param target_duty target duty cycle of the fade
288+
* @param max_fade_time_ms maximum fade time in milliseconds
289+
* @param userFunc callback function to be called after fade is finished
290+
*
291+
* @return true if gamma fade was successfully set and started, false otherwise.
292+
*
293+
* @note This function is only available on ESP32 variants that support gamma curve fading.
294+
*/
295+
bool ledcFadeGammaWithInterrupt(uint8_t pin, uint32_t start_duty, uint32_t target_duty, int max_fade_time_ms, void (*userFunc)(void));
296+
297+
/**
298+
* @brief Setup and start a gamma curve fade on a given LEDC pin with a callback function and argument.
299+
*
300+
* @param pin GPIO pin
301+
* @param start_duty initial duty cycle of the fade
302+
* @param target_duty target duty cycle of the fade
303+
* @param max_fade_time_ms maximum fade time in milliseconds
304+
* @param userFunc callback function to be called after fade is finished
305+
* @param arg argument to be passed to the callback function
306+
*
307+
* @return true if gamma fade was successfully set and started, false otherwise.
308+
*
309+
* @note This function is only available on ESP32 variants that support gamma curve fading.
310+
*/
311+
bool ledcFadeGammaWithInterruptArg(uint8_t pin, uint32_t start_duty, uint32_t target_duty, int max_fade_time_ms, void (*userFunc)(void *), void *arg);
312+
#endif // SOC_LEDC_GAMMA_CURVE_FADE_SUPPORTED
313+
235314
#ifdef __cplusplus
236315
}
237316
#endif
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
/* LEDC Gamma Curve Fade Arduino Example
2+
3+
This example demonstrates gamma curve fading on ESP32 variants that support it.
4+
Gamma correction makes LED brightness changes appear more gradual and natural
5+
to human eyes compared to linear fading.
6+
7+
Two methods are supported:
8+
1. Using a pre-computed Gamma Look-Up Table (LUT) for better performance
9+
2. Using mathematical gamma correction with a gamma factor
10+
11+
Supported chips: ESP32-C6, ESP32-C5, ESP32-H2, ESP32-P4 and future chips with Gamma Fade support
12+
13+
Created by Jan Procházka (https://github.com/P-R-O-C-H-Y/)
14+
*/
15+
16+
// use 12 bit precision for LEDC timer
17+
#define LEDC_TIMER_12_BIT 12
18+
19+
// use 5000 Hz as a LEDC base frequency
20+
#define LEDC_BASE_FREQ 5000
21+
22+
// define starting duty, target duty and maximum fade time
23+
#define LEDC_START_DUTY (0)
24+
#define LEDC_TARGET_DUTY (4095)
25+
#define LEDC_FADE_TIME (2000)
26+
27+
// gamma factor for mathematical calculation
28+
#define LEDC_GAMMA_FACTOR (2.6)
29+
30+
// use gamma LUT for better performance instead of mathematical calculation (gamma factor)
31+
#define USE_GAMMA_LUT 1
32+
33+
// fade LED pins
34+
const uint8_t ledPinR = 4;
35+
const uint8_t ledPinG = 5;
36+
const uint8_t ledPinB = 6;
37+
38+
uint8_t fade_ended = 0; // status of LED gamma fade
39+
bool fade_in = true;
40+
41+
#ifdef USE_GAMMA_LUT
42+
// Custom Gamma LUT demonstration with 101 steps (Brightness 0 - 100% gamma correction look up table (gamma = 2.6))
43+
// Y = B ^ 2.6 - Pre-computed LUT to save runtime computation
44+
static const float ledcGammaLUT[101] = {
45+
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,
46+
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,
47+
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,
48+
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,
49+
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,
50+
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,
51+
0.760380, 0.782542, 0.805097, 0.828048, 0.851398, 0.875148, 0.899301, 0.923861, 0.948829, 0.974208, 1.000000,
52+
};
53+
#endif
54+
55+
void ARDUINO_ISR_ATTR LED_FADE_ISR() {
56+
fade_ended += 1;
57+
}
58+
59+
void setup() {
60+
// Initialize serial communication at 115200 bits per second:
61+
Serial.begin(115200);
62+
63+
// Setup timer with given frequency, resolution and attach it to a led pin with auto-selected channel
64+
ledcAttach(ledPinR, LEDC_BASE_FREQ, LEDC_TIMER_12_BIT);
65+
ledcAttach(ledPinG, LEDC_BASE_FREQ, LEDC_TIMER_12_BIT);
66+
ledcAttach(ledPinB, LEDC_BASE_FREQ, LEDC_TIMER_12_BIT);
67+
68+
#if USE_GAMMA_LUT // Use default gamma LUT for better performance
69+
ledcSetGammaTable(ledcGammaLUT, 101);
70+
#else // Use mathematical gamma correction (default, more flexible)
71+
ledcSetGammaFactor(LEDC_GAMMA_FACTOR); // This is optional to set custom gamma factor (default is 2.8)
72+
#endif
73+
74+
// Setup and start gamma curve fade on led (duty from 0 to 4095)
75+
ledcFadeGamma(ledPinR, LEDC_START_DUTY, LEDC_TARGET_DUTY, LEDC_FADE_TIME);
76+
ledcFadeGamma(ledPinG, LEDC_START_DUTY, LEDC_TARGET_DUTY, LEDC_FADE_TIME);
77+
ledcFadeGamma(ledPinB, LEDC_START_DUTY, LEDC_TARGET_DUTY, LEDC_FADE_TIME);
78+
Serial.println("LED Gamma Fade on started.");
79+
80+
// Wait for fade to end
81+
delay(LEDC_FADE_TIME);
82+
83+
// Setup and start gamma curve fade off led and use ISR (duty from 4095 to 0)
84+
ledcFadeGammaWithInterrupt(ledPinR, LEDC_TARGET_DUTY, LEDC_START_DUTY, LEDC_FADE_TIME, LED_FADE_ISR);
85+
ledcFadeGammaWithInterrupt(ledPinG, LEDC_TARGET_DUTY, LEDC_START_DUTY, LEDC_FADE_TIME, LED_FADE_ISR);
86+
ledcFadeGammaWithInterrupt(ledPinB, LEDC_TARGET_DUTY, LEDC_START_DUTY, LEDC_FADE_TIME, LED_FADE_ISR);
87+
Serial.println("LED Gamma Fade off started.");
88+
}
89+
90+
void loop() {
91+
// Check if fade_ended flag was set to true in ISR
92+
if (fade_ended == 3) {
93+
Serial.println("LED gamma fade ended");
94+
fade_ended = 0;
95+
96+
// Check what gamma fade should be started next
97+
if (fade_in) {
98+
ledcFadeGammaWithInterrupt(ledPinR, LEDC_START_DUTY, LEDC_TARGET_DUTY, LEDC_FADE_TIME, LED_FADE_ISR);
99+
ledcFadeGammaWithInterrupt(ledPinG, LEDC_START_DUTY, LEDC_TARGET_DUTY, LEDC_FADE_TIME, LED_FADE_ISR);
100+
ledcFadeGammaWithInterrupt(ledPinB, LEDC_START_DUTY, LEDC_TARGET_DUTY, LEDC_FADE_TIME, LED_FADE_ISR);
101+
Serial.println("LED Gamma Fade in started.");
102+
fade_in = false;
103+
} else {
104+
ledcFadeGammaWithInterrupt(ledPinR, LEDC_TARGET_DUTY, LEDC_START_DUTY, LEDC_FADE_TIME, LED_FADE_ISR);
105+
ledcFadeGammaWithInterrupt(ledPinG, LEDC_TARGET_DUTY, LEDC_START_DUTY, LEDC_FADE_TIME, LED_FADE_ISR);
106+
ledcFadeGammaWithInterrupt(ledPinB, LEDC_TARGET_DUTY, LEDC_START_DUTY, LEDC_FADE_TIME, LED_FADE_ISR);
107+
Serial.println("LED Gamma Fade out started.");
108+
fade_in = true;
109+
}
110+
}
111+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"requires": [
3+
"CONFIG_SOC_LEDC_GAMMA_CURVE_FADE_SUPPORTED=y"
4+
]
5+
}

0 commit comments

Comments
 (0)
Please sign in to comment.