Skip to content

Commit e479eee

Browse files
committed
feat(ledc): Add Gamma Fade support
1 parent 7150245 commit e479eee

File tree

4 files changed

+358
-1
lines changed

4 files changed

+358
-1
lines changed

cores/esp32/esp32-hal-ledc.c

Lines changed: 157 additions & 1 deletion
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)
@@ -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
}
@@ -600,6 +603,159 @@ bool ledcFadeWithInterruptArg(uint8_t pin, uint32_t start_duty, uint32_t target_
600603
return ledcFadeConfig(pin, start_duty, target_duty, max_fade_time_ms, userFunc, arg);
601604
}
602605

606+
#ifdef SOC_LEDC_GAMMA_CURVE_FADE_SUPPORTED
607+
// Default gamma factor for gamma correction (common value for LEDs)
608+
static float ledcGammaFactor = 2.8;
609+
// Gamma correction LUT support
610+
static const float *ledcGammaLUT = NULL;
611+
static uint16_t ledcGammaLUTSize = 0;
612+
// Global variable to store current resolution for gamma callback
613+
static uint8_t ledcGammaResolution = 13;
614+
615+
bool ledcSetGammaTable(const float* gamma_table, uint16_t size) {
616+
if (gamma_table == NULL || size == 0) {
617+
log_e("Invalid gamma table or size");
618+
return false;
619+
}
620+
ledcGammaLUT = gamma_table;
621+
ledcGammaLUTSize = size;
622+
log_i("Custom gamma LUT set with %u entries", size);
623+
return true;
624+
}
625+
626+
void ledcClearGammaTable(void) {
627+
ledcGammaLUT = NULL;
628+
ledcGammaLUTSize = 0;
629+
log_i("Gamma LUT cleared, using mathematical calculation");
630+
}
631+
632+
void ledcSetGammaFactor(float factor) {
633+
ledcGammaFactor = factor;
634+
}
635+
636+
// Gamma correction calculator function
637+
static uint32_t ledcGammaCorrection(uint32_t duty) {
638+
if (duty == 0) return 0;
639+
640+
uint32_t max_duty = (1U << ledcGammaResolution) - 1;
641+
if (duty >= (1U << ledcGammaResolution)) return max_duty;
642+
643+
// Use LUT if provided, otherwise use mathematical calculation
644+
if (ledcGammaLUT != NULL && ledcGammaLUTSize > 0) {
645+
// LUT-based gamma correction
646+
uint32_t lut_index = (duty * (ledcGammaLUTSize - 1)) / max_duty;
647+
if (lut_index >= ledcGammaLUTSize) lut_index = ledcGammaLUTSize - 1;
648+
649+
float corrected_normalized = ledcGammaLUT[lut_index];
650+
return (uint32_t)(corrected_normalized * max_duty);
651+
} else {
652+
// Mathematical gamma correction
653+
double normalized = (double)duty / (1U << ledcGammaResolution);
654+
double corrected = pow(normalized, ledcGammaFactor);
655+
return (uint32_t)(corrected * (1U << ledcGammaResolution));
656+
}
657+
}
658+
659+
static bool ledcFadeGammaConfig(uint8_t pin, uint32_t start_duty, uint32_t target_duty, int max_fade_time_ms, void (*userFunc)(void *), void *arg) {
660+
ledc_channel_handle_t *bus = (ledc_channel_handle_t *)perimanGetPinBus(pin, ESP32_BUS_TYPE_LEDC);
661+
if (bus != NULL) {
662+
663+
#ifndef SOC_LEDC_SUPPORT_FADE_STOP
664+
#if !CONFIG_DISABLE_HAL_LOCKS
665+
if (bus->lock == NULL) {
666+
bus->lock = xSemaphoreCreateBinary();
667+
if (bus->lock == NULL) {
668+
log_e("xSemaphoreCreateBinary failed");
669+
return false;
670+
}
671+
xSemaphoreGive(bus->lock);
672+
}
673+
//acquire lock
674+
if (xSemaphoreTake(bus->lock, 0) != pdTRUE) {
675+
log_e("LEDC Fade is still running on pin %u! SoC does not support stopping fade.", pin);
676+
return false;
677+
}
678+
#endif
679+
#endif
680+
uint8_t group = (bus->channel / SOC_LEDC_CHANNEL_NUM), channel = (bus->channel % SOC_LEDC_CHANNEL_NUM);
681+
682+
// Initialize fade service.
683+
if (!fade_initialized) {
684+
ledc_fade_func_install(0);
685+
fade_initialized = true;
686+
}
687+
688+
bus->fn = (voidFuncPtr)userFunc;
689+
bus->arg = arg;
690+
691+
ledc_cbs_t callbacks = {.fade_cb = ledcFnWrapper};
692+
ledc_cb_register(group, channel, &callbacks, (void *)bus);
693+
694+
// Prepare gamma curve fade parameters
695+
ledc_fade_param_config_t fade_params[SOC_LEDC_GAMMA_CURVE_FADE_RANGE_MAX];
696+
uint32_t actual_fade_ranges = 0;
697+
698+
// Use a moderate number of linear segments for smooth gamma curve
699+
const uint32_t linear_fade_segments = 12;
700+
701+
// Set the global resolution for gamma correction
702+
ledcGammaResolution = bus->channel_resolution;
703+
704+
// Fill multi-fade parameter list using ESP-IDF API
705+
esp_err_t err = ledc_fill_multi_fade_param_list(
706+
group, channel,
707+
start_duty, target_duty,
708+
linear_fade_segments, max_fade_time_ms,
709+
ledcGammaCorrection,
710+
SOC_LEDC_GAMMA_CURVE_FADE_RANGE_MAX,
711+
fade_params, &actual_fade_ranges
712+
);
713+
714+
if (err != ESP_OK) {
715+
log_e("ledc_fill_multi_fade_param_list failed: %s", esp_err_to_name(err));
716+
return false;
717+
}
718+
719+
// Apply the gamma-corrected start duty
720+
uint32_t gamma_start_duty = ledcGammaCorrection(start_duty);
721+
722+
// Set multi-fade parameters
723+
err = ledc_set_multi_fade(group, channel, gamma_start_duty, fade_params, actual_fade_ranges);
724+
if (err != ESP_OK) {
725+
log_e("ledc_set_multi_fade failed: %s", esp_err_to_name(err));
726+
return false;
727+
}
728+
729+
// Start the gamma curve fade
730+
err = ledc_fade_start(group, channel, LEDC_FADE_NO_WAIT);
731+
if (err != ESP_OK) {
732+
log_e("ledc_fade_start failed: %s", esp_err_to_name(err));
733+
return false;
734+
}
735+
736+
log_d("Gamma curve fade started on pin %u: %u -> %u over %dms", pin, start_duty, target_duty, max_fade_time_ms);
737+
738+
} else {
739+
log_e("Pin %u is not attached to LEDC. Call ledcAttach first!", pin);
740+
return false;
741+
}
742+
return true;
743+
}
744+
745+
bool ledcFadeGamma(uint8_t pin, uint32_t start_duty, uint32_t target_duty, int max_fade_time_ms) {
746+
return ledcFadeGammaConfig(pin, start_duty, target_duty, max_fade_time_ms, NULL, NULL);
747+
}
748+
749+
bool ledcFadeGammaWithInterrupt(uint8_t pin, uint32_t start_duty, uint32_t target_duty, int max_fade_time_ms, voidFuncPtr userFunc) {
750+
return ledcFadeGammaConfig(pin, start_duty, target_duty, max_fade_time_ms, (voidFuncPtrArg)userFunc, NULL);
751+
}
752+
753+
bool ledcFadeGammaWithInterruptArg(uint8_t pin, uint32_t start_duty, uint32_t target_duty, int max_fade_time_ms, void (*userFunc)(void *), void *arg) {
754+
return ledcFadeGammaConfig(pin, start_duty, target_duty, max_fade_time_ms, userFunc, arg);
755+
}
756+
757+
#endif /* SOC_LEDC_GAMMA_CURVE_FADE_SUPPORTED */
758+
603759
static uint8_t analog_resolution = 8;
604760
static int analog_frequency = 1000;
605761
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: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
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 only 10 steps (the default built-in Gamma lut is 101 steps)
43+
// Brightness 0 - 100% gamma correction look up table (gamma = 2.6)
44+
// Y = B ^ 2.6 - Pre-computed LUT to save runtime computation
45+
static const float ledcGammaLUT[101] = {
46+
0.000000, 0.000006, 0.000038, 0.000110, 0.000232, 0.000414, 0.000666, 0.000994, 0.001406, 0.001910,
47+
0.002512, 0.003218, 0.004035, 0.004969, 0.006025, 0.007208, 0.008525, 0.009981, 0.011580, 0.013328,
48+
0.015229, 0.017289, 0.019512, 0.021902, 0.024465, 0.027205, 0.030125, 0.033231, 0.036527, 0.040016,
49+
0.043703, 0.047593, 0.051688, 0.055993, 0.060513, 0.065249, 0.070208, 0.075392, 0.080805, 0.086451,
50+
0.092333, 0.098455, 0.104821, 0.111434, 0.118298, 0.125416, 0.132792, 0.140428, 0.148329, 0.156498,
51+
0.164938, 0.173653, 0.182645, 0.191919, 0.201476, 0.211321, 0.221457, 0.231886, 0.242612, 0.253639,
52+
0.264968, 0.276603, 0.288548, 0.300805, 0.313378, 0.326268, 0.339480, 0.353016, 0.366879, 0.381073,
53+
0.395599, 0.410461, 0.425662, 0.441204, 0.457091, 0.473325, 0.489909, 0.506846, 0.524138, 0.541789,
54+
0.559801, 0.578177, 0.596920, 0.616032, 0.635515, 0.655374, 0.675610, 0.696226, 0.717224, 0.738608,
55+
0.760380, 0.782542, 0.805097, 0.828048, 0.851398, 0.875148, 0.899301, 0.923861, 0.948829, 0.974208,
56+
1.000000,
57+
};
58+
#endif
59+
60+
void ARDUINO_ISR_ATTR LED_FADE_ISR() {
61+
fade_ended += 1;
62+
}
63+
64+
void setup() {
65+
// Initialize serial communication at 115200 bits per second:
66+
Serial.begin(115200);
67+
68+
// Setup timer with given frequency, resolution and attach it to a led pin with auto-selected channel
69+
ledcAttach(ledPinR, LEDC_BASE_FREQ, LEDC_TIMER_12_BIT);
70+
ledcAttach(ledPinG, LEDC_BASE_FREQ, LEDC_TIMER_12_BIT);
71+
ledcAttach(ledPinB, LEDC_BASE_FREQ, LEDC_TIMER_12_BIT);
72+
73+
74+
#if USE_GAMMA_LUT // Use default gamma LUT for better performance
75+
ledcSetGammaTable(ledcGammaLUT, 101);
76+
#else // Use mathematical gamma correction (default, more flexible)
77+
ledcSetGammaFactor(LEDC_GAMMA_FACTOR); // default gamma factor is 2.8
78+
#endif
79+
80+
// Setup and start gamma curve fade on led (duty from 0 to 4095)
81+
ledcFadeGamma(ledPinR, LEDC_START_DUTY, LEDC_TARGET_DUTY, LEDC_FADE_TIME);
82+
ledcFadeGamma(ledPinG, LEDC_START_DUTY, LEDC_TARGET_DUTY, LEDC_FADE_TIME);
83+
ledcFadeGamma(ledPinB, LEDC_START_DUTY, LEDC_TARGET_DUTY, LEDC_FADE_TIME);
84+
Serial.println("LED Gamma Fade on started.");
85+
86+
// Wait for fade to end
87+
delay(LEDC_FADE_TIME);
88+
89+
// Setup and start gamma curve fade off led and use ISR (duty from 4095 to 0)
90+
ledcFadeGammaWithInterrupt(ledPinR, LEDC_TARGET_DUTY, LEDC_START_DUTY, LEDC_FADE_TIME, LED_FADE_ISR);
91+
ledcFadeGammaWithInterrupt(ledPinG, LEDC_TARGET_DUTY, LEDC_START_DUTY, LEDC_FADE_TIME, LED_FADE_ISR);
92+
ledcFadeGammaWithInterrupt(ledPinB, LEDC_TARGET_DUTY, LEDC_START_DUTY, LEDC_FADE_TIME, LED_FADE_ISR);
93+
Serial.println("LED Gamma Fade off started.");
94+
}
95+
96+
void loop() {
97+
// Check if fade_ended flag was set to true in ISR
98+
if (fade_ended == 3) {
99+
Serial.println("LED gamma fade ended");
100+
fade_ended = 0;
101+
102+
// Check what gamma fade should be started next
103+
if (fade_in) {
104+
ledcFadeGammaWithInterrupt(ledPinR, LEDC_START_DUTY, LEDC_TARGET_DUTY, LEDC_FADE_TIME, LED_FADE_ISR);
105+
ledcFadeGammaWithInterrupt(ledPinG, LEDC_START_DUTY, LEDC_TARGET_DUTY, LEDC_FADE_TIME, LED_FADE_ISR);
106+
ledcFadeGammaWithInterrupt(ledPinB, LEDC_START_DUTY, LEDC_TARGET_DUTY, LEDC_FADE_TIME, LED_FADE_ISR);
107+
Serial.println("LED Gamma Fade in started.");
108+
fade_in = false;
109+
} else {
110+
ledcFadeGammaWithInterrupt(ledPinR, LEDC_TARGET_DUTY, LEDC_START_DUTY, LEDC_FADE_TIME, LED_FADE_ISR);
111+
ledcFadeGammaWithInterrupt(ledPinG, LEDC_TARGET_DUTY, LEDC_START_DUTY, LEDC_FADE_TIME, LED_FADE_ISR);
112+
ledcFadeGammaWithInterrupt(ledPinB, LEDC_TARGET_DUTY, LEDC_START_DUTY, LEDC_FADE_TIME, LED_FADE_ISR);
113+
Serial.println("LED Gamma Fade out started.");
114+
fade_in = true;
115+
}
116+
}
117+
}
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)