feat(LEDC): Add Gamma Fade support and enhance auto channel/timer selection for multi-group (#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>
This commit is contained in:
parent
ef995b6564
commit
7462b09bb4
4 changed files with 428 additions and 31 deletions
|
|
@ -22,6 +22,9 @@
|
||||||
#include "soc/gpio_sig_map.h"
|
#include "soc/gpio_sig_map.h"
|
||||||
#include "esp_rom_gpio.h"
|
#include "esp_rom_gpio.h"
|
||||||
#include "hal/ledc_ll.h"
|
#include "hal/ledc_ll.h"
|
||||||
|
#if SOC_LEDC_GAMMA_CURVE_FADE_SUPPORTED
|
||||||
|
#include <math.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef SOC_LEDC_SUPPORT_HS_MODE
|
#ifdef SOC_LEDC_SUPPORT_HS_MODE
|
||||||
#define LEDC_CHANNELS (SOC_LEDC_CHANNEL_NUM << 1)
|
#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
|
||||||
peripheral_bus_type_t type = perimanGetPinBusType(i);
|
peripheral_bus_type_t type = perimanGetPinBusType(i);
|
||||||
if (type == ESP32_BUS_TYPE_LEDC) {
|
if (type == ESP32_BUS_TYPE_LEDC) {
|
||||||
ledc_channel_handle_t *bus = (ledc_channel_handle_t *)perimanGetPinBus(i, ESP32_BUS_TYPE_LEDC);
|
ledc_channel_handle_t *bus = (ledc_channel_handle_t *)perimanGetPinBus(i, ESP32_BUS_TYPE_LEDC);
|
||||||
if (bus != NULL && (bus->channel / 8) == speed_mode && bus->freq_hz == freq && bus->channel_resolution == resolution) {
|
if (bus != NULL && (bus->channel / SOC_LEDC_CHANNEL_NUM) == speed_mode && bus->freq_hz == freq && bus->channel_resolution == resolution) {
|
||||||
log_d("Found matching timer %u for freq=%u, resolution=%u", bus->timer_num, freq, resolution);
|
log_d("Found matching timer %u for freq=%u, resolution=%u", bus->timer_num, freq, resolution);
|
||||||
*timer_num = bus->timer_num;
|
*timer_num = bus->timer_num;
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -78,7 +81,7 @@ static bool find_free_timer(uint8_t speed_mode, uint8_t *timer_num) {
|
||||||
peripheral_bus_type_t type = perimanGetPinBusType(i);
|
peripheral_bus_type_t type = perimanGetPinBusType(i);
|
||||||
if (type == ESP32_BUS_TYPE_LEDC) {
|
if (type == ESP32_BUS_TYPE_LEDC) {
|
||||||
ledc_channel_handle_t *bus = (ledc_channel_handle_t *)perimanGetPinBus(i, ESP32_BUS_TYPE_LEDC);
|
ledc_channel_handle_t *bus = (ledc_channel_handle_t *)perimanGetPinBus(i, ESP32_BUS_TYPE_LEDC);
|
||||||
if (bus != NULL && (bus->channel / 8) == speed_mode) {
|
if (bus != NULL && (bus->channel / SOC_LEDC_CHANNEL_NUM) == speed_mode) {
|
||||||
log_d("Timer %u is in use by channel %u", bus->timer_num, bus->channel);
|
log_d("Timer %u is in use by channel %u", bus->timer_num, bus->channel);
|
||||||
used_timers |= (1 << bus->timer_num);
|
used_timers |= (1 << bus->timer_num);
|
||||||
}
|
}
|
||||||
|
|
@ -110,7 +113,7 @@ static void remove_channel_from_timer(uint8_t speed_mode, uint8_t timer_num, uin
|
||||||
peripheral_bus_type_t type = perimanGetPinBusType(i);
|
peripheral_bus_type_t type = perimanGetPinBusType(i);
|
||||||
if (type == ESP32_BUS_TYPE_LEDC) {
|
if (type == ESP32_BUS_TYPE_LEDC) {
|
||||||
ledc_channel_handle_t *bus = (ledc_channel_handle_t *)perimanGetPinBus(i, ESP32_BUS_TYPE_LEDC);
|
ledc_channel_handle_t *bus = (ledc_channel_handle_t *)perimanGetPinBus(i, ESP32_BUS_TYPE_LEDC);
|
||||||
if (bus != NULL && (bus->channel / 8) == speed_mode && bus->timer_num == timer_num && bus->channel != channel) {
|
if (bus != NULL && (bus->channel / SOC_LEDC_CHANNEL_NUM) == speed_mode && bus->timer_num == timer_num && bus->channel != channel) {
|
||||||
log_d("Timer %u is still in use by channel %u", timer_num, bus->channel);
|
log_d("Timer %u is still in use by channel %u", timer_num, bus->channel);
|
||||||
timer_in_use = true;
|
timer_in_use = true;
|
||||||
break;
|
break;
|
||||||
|
|
@ -168,8 +171,8 @@ static bool ledcDetachBus(void *bus) {
|
||||||
}
|
}
|
||||||
pinMatrixOutDetach(handle->pin, false, false);
|
pinMatrixOutDetach(handle->pin, false, false);
|
||||||
if (!channel_found) {
|
if (!channel_found) {
|
||||||
uint8_t group = (handle->channel / 8);
|
uint8_t group = (handle->channel / SOC_LEDC_CHANNEL_NUM);
|
||||||
remove_channel_from_timer(group, handle->timer_num, handle->channel % 8);
|
remove_channel_from_timer(group, handle->timer_num, handle->channel % SOC_LEDC_CHANNEL_NUM);
|
||||||
ledc_handle.used_channels &= ~(1UL << handle->channel);
|
ledc_handle.used_channels &= ~(1UL << handle->channel);
|
||||||
}
|
}
|
||||||
free(handle);
|
free(handle);
|
||||||
|
|
@ -206,13 +209,13 @@ bool ledcAttachChannel(uint8_t pin, uint32_t freq, uint8_t resolution, uint8_t c
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t group = (channel / 8);
|
uint8_t group = (channel / SOC_LEDC_CHANNEL_NUM);
|
||||||
uint8_t timer = 0;
|
uint8_t timer = 0;
|
||||||
bool channel_used = ledc_handle.used_channels & (1UL << channel);
|
bool channel_used = ledc_handle.used_channels & (1UL << channel);
|
||||||
|
|
||||||
if (channel_used) {
|
if (channel_used) {
|
||||||
log_i("Channel %u is already set up, given frequency and resolution will be ignored", channel);
|
log_i("Channel %u is already set up, given frequency and resolution will be ignored", channel);
|
||||||
if (ledc_set_pin(pin, group, channel % 8) != ESP_OK) {
|
if (ledc_set_pin(pin, group, channel % SOC_LEDC_CHANNEL_NUM) != ESP_OK) {
|
||||||
log_e("Attaching pin to already used channel failed!");
|
log_e("Attaching pin to already used channel failed!");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -220,7 +223,7 @@ bool ledcAttachChannel(uint8_t pin, uint32_t freq, uint8_t resolution, uint8_t c
|
||||||
// Find a timer with matching frequency and resolution, or a free timer
|
// Find a timer with matching frequency and resolution, or a free timer
|
||||||
if (!find_matching_timer(group, freq, resolution, &timer)) {
|
if (!find_matching_timer(group, freq, resolution, &timer)) {
|
||||||
if (!find_free_timer(group, &timer)) {
|
if (!find_free_timer(group, &timer)) {
|
||||||
log_e("No free timers available for speed mode %u", group);
|
log_w("No free timers available for speed mode %u", group);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -239,12 +242,12 @@ bool ledcAttachChannel(uint8_t pin, uint32_t freq, uint8_t resolution, uint8_t c
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t duty = ledc_get_duty(group, (channel % 8));
|
uint32_t duty = ledc_get_duty(group, (channel % SOC_LEDC_CHANNEL_NUM));
|
||||||
|
|
||||||
ledc_channel_config_t ledc_channel;
|
ledc_channel_config_t ledc_channel;
|
||||||
memset((void *)&ledc_channel, 0, sizeof(ledc_channel_config_t));
|
memset((void *)&ledc_channel, 0, sizeof(ledc_channel_config_t));
|
||||||
ledc_channel.speed_mode = group;
|
ledc_channel.speed_mode = group;
|
||||||
ledc_channel.channel = (channel % 8);
|
ledc_channel.channel = (channel % SOC_LEDC_CHANNEL_NUM);
|
||||||
ledc_channel.timer_sel = timer;
|
ledc_channel.timer_sel = timer;
|
||||||
ledc_channel.intr_type = LEDC_INTR_DISABLE;
|
ledc_channel.intr_type = LEDC_INTR_DISABLE;
|
||||||
ledc_channel.gpio_num = pin;
|
ledc_channel.gpio_num = pin;
|
||||||
|
|
@ -274,7 +277,7 @@ bool ledcAttachChannel(uint8_t pin, uint32_t freq, uint8_t resolution, uint8_t c
|
||||||
ledc_handle.used_channels |= 1UL << channel;
|
ledc_handle.used_channels |= 1UL << channel;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!perimanSetPinBus(pin, ESP32_BUS_TYPE_LEDC, (void *)handle, group, channel)) {
|
if (!perimanSetPinBus(pin, ESP32_BUS_TYPE_LEDC, (void *)handle, channel, timer)) {
|
||||||
ledcDetachBus((void *)handle);
|
ledcDetachBus((void *)handle);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -291,14 +294,40 @@ bool ledcAttach(uint8_t pin, uint32_t freq, uint8_t resolution) {
|
||||||
}
|
}
|
||||||
uint8_t channel = __builtin_ctz(free_channel); // Convert the free_channel bit to channel number
|
uint8_t channel = __builtin_ctz(free_channel); // Convert the free_channel bit to channel number
|
||||||
|
|
||||||
return ledcAttachChannel(pin, freq, resolution, channel);
|
// Try the first available channel
|
||||||
|
if (ledcAttachChannel(pin, freq, resolution, channel)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef SOC_LEDC_SUPPORT_HS_MODE
|
||||||
|
// If first attempt failed and HS mode is supported, try to find a free channel in group 1
|
||||||
|
if ((channel / SOC_LEDC_CHANNEL_NUM) == 0) { // First attempt was in group 0
|
||||||
|
log_d("LEDC: Group 0 channel %u failed, trying to find a free channel in group 1", channel);
|
||||||
|
// Find free channels specifically in group 1
|
||||||
|
uint32_t group1_mask = ((1UL << SOC_LEDC_CHANNEL_NUM) - 1) << SOC_LEDC_CHANNEL_NUM;
|
||||||
|
int group1_free_channel = (~ledc_handle.used_channels) & group1_mask;
|
||||||
|
if (group1_free_channel != 0) {
|
||||||
|
uint8_t group1_channel = __builtin_ctz(group1_free_channel);
|
||||||
|
if (ledcAttachChannel(pin, freq, resolution, group1_channel)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
log_e(
|
||||||
|
"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 "
|
||||||
|
"share its timer.",
|
||||||
|
freq, resolution
|
||||||
|
);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ledcWrite(uint8_t pin, uint32_t duty) {
|
bool ledcWrite(uint8_t pin, uint32_t duty) {
|
||||||
ledc_channel_handle_t *bus = (ledc_channel_handle_t *)perimanGetPinBus(pin, ESP32_BUS_TYPE_LEDC);
|
ledc_channel_handle_t *bus = (ledc_channel_handle_t *)perimanGetPinBus(pin, ESP32_BUS_TYPE_LEDC);
|
||||||
if (bus != NULL) {
|
if (bus != NULL) {
|
||||||
|
|
||||||
uint8_t group = (bus->channel / 8), channel = (bus->channel % 8);
|
uint8_t group = (bus->channel / SOC_LEDC_CHANNEL_NUM), channel = (bus->channel % SOC_LEDC_CHANNEL_NUM);
|
||||||
|
|
||||||
//Fixing if all bits in resolution is set = LEDC FULL ON
|
//Fixing if all bits in resolution is set = LEDC FULL ON
|
||||||
uint32_t max_duty = (1 << bus->channel_resolution) - 1;
|
uint32_t max_duty = (1 << bus->channel_resolution) - 1;
|
||||||
|
|
@ -307,8 +336,14 @@ bool ledcWrite(uint8_t pin, uint32_t duty) {
|
||||||
duty = max_duty + 1;
|
duty = max_duty + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
ledc_set_duty(group, channel, duty);
|
if (ledc_set_duty(group, channel, duty) != ESP_OK) {
|
||||||
ledc_update_duty(group, channel);
|
log_e("ledc_set_duty failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (ledc_update_duty(group, channel) != ESP_OK) {
|
||||||
|
log_e("ledc_update_duty failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -321,7 +356,11 @@ bool ledcWriteChannel(uint8_t channel, uint32_t duty) {
|
||||||
log_e("Channel %u is not available (maximum %u) or not used!", channel, LEDC_CHANNELS);
|
log_e("Channel %u is not available (maximum %u) or not used!", channel, LEDC_CHANNELS);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
uint8_t group = (channel / 8), timer = ((channel / 2) % 4);
|
uint8_t group = (channel / SOC_LEDC_CHANNEL_NUM);
|
||||||
|
ledc_timer_t timer;
|
||||||
|
|
||||||
|
// Get the actual timer being used by this channel
|
||||||
|
ledc_ll_get_channel_timer(LEDC_LL_GET_HW(), group, (channel % SOC_LEDC_CHANNEL_NUM), &timer);
|
||||||
|
|
||||||
//Fixing if all bits in resolution is set = LEDC FULL ON
|
//Fixing if all bits in resolution is set = LEDC FULL ON
|
||||||
uint32_t resolution = 0;
|
uint32_t resolution = 0;
|
||||||
|
|
@ -333,8 +372,14 @@ bool ledcWriteChannel(uint8_t channel, uint32_t duty) {
|
||||||
duty = max_duty + 1;
|
duty = max_duty + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
ledc_set_duty(group, channel, duty);
|
if (ledc_set_duty(group, channel, duty) != ESP_OK) {
|
||||||
ledc_update_duty(group, channel);
|
log_e("ledc_set_duty failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (ledc_update_duty(group, channel) != ESP_OK) {
|
||||||
|
log_e("ledc_update_duty failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -343,7 +388,7 @@ uint32_t ledcRead(uint8_t pin) {
|
||||||
ledc_channel_handle_t *bus = (ledc_channel_handle_t *)perimanGetPinBus(pin, ESP32_BUS_TYPE_LEDC);
|
ledc_channel_handle_t *bus = (ledc_channel_handle_t *)perimanGetPinBus(pin, ESP32_BUS_TYPE_LEDC);
|
||||||
if (bus != NULL) {
|
if (bus != NULL) {
|
||||||
|
|
||||||
uint8_t group = (bus->channel / 8), channel = (bus->channel % 8);
|
uint8_t group = (bus->channel / SOC_LEDC_CHANNEL_NUM), channel = (bus->channel % SOC_LEDC_CHANNEL_NUM);
|
||||||
return ledc_get_duty(group, channel);
|
return ledc_get_duty(group, channel);
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
|
|
@ -355,8 +400,8 @@ uint32_t ledcReadFreq(uint8_t pin) {
|
||||||
if (!ledcRead(pin)) {
|
if (!ledcRead(pin)) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
uint8_t group = (bus->channel / 8), timer = ((bus->channel / 2) % 4);
|
uint8_t group = (bus->channel / SOC_LEDC_CHANNEL_NUM);
|
||||||
return ledc_get_freq(group, timer);
|
return ledc_get_freq(group, bus->timer_num);
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
@ -370,12 +415,12 @@ uint32_t ledcWriteTone(uint8_t pin, uint32_t freq) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t group = (bus->channel / 8), timer = ((bus->channel / 2) % 4);
|
uint8_t group = (bus->channel / SOC_LEDC_CHANNEL_NUM);
|
||||||
|
|
||||||
ledc_timer_config_t ledc_timer;
|
ledc_timer_config_t ledc_timer;
|
||||||
memset((void *)&ledc_timer, 0, sizeof(ledc_timer_config_t));
|
memset((void *)&ledc_timer, 0, sizeof(ledc_timer_config_t));
|
||||||
ledc_timer.speed_mode = group;
|
ledc_timer.speed_mode = group;
|
||||||
ledc_timer.timer_num = timer;
|
ledc_timer.timer_num = bus->timer_num;
|
||||||
ledc_timer.duty_resolution = 10;
|
ledc_timer.duty_resolution = 10;
|
||||||
ledc_timer.freq_hz = freq;
|
ledc_timer.freq_hz = freq;
|
||||||
ledc_timer.clk_cfg = clock_source;
|
ledc_timer.clk_cfg = clock_source;
|
||||||
|
|
@ -386,7 +431,7 @@ uint32_t ledcWriteTone(uint8_t pin, uint32_t freq) {
|
||||||
}
|
}
|
||||||
bus->channel_resolution = 10;
|
bus->channel_resolution = 10;
|
||||||
|
|
||||||
uint32_t res_freq = ledc_get_freq(group, timer);
|
uint32_t res_freq = ledc_get_freq(group, bus->timer_num);
|
||||||
ledcWrite(pin, 0x1FF);
|
ledcWrite(pin, 0x1FF);
|
||||||
return res_freq;
|
return res_freq;
|
||||||
}
|
}
|
||||||
|
|
@ -427,12 +472,12 @@ uint32_t ledcChangeFrequency(uint8_t pin, uint32_t freq, uint8_t resolution) {
|
||||||
log_e("LEDC pin %u - resolution is zero or it is too big (maximum %u)", pin, LEDC_MAX_BIT_WIDTH);
|
log_e("LEDC pin %u - resolution is zero or it is too big (maximum %u)", pin, LEDC_MAX_BIT_WIDTH);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
uint8_t group = (bus->channel / 8), timer = ((bus->channel / 2) % 4);
|
uint8_t group = (bus->channel / SOC_LEDC_CHANNEL_NUM);
|
||||||
|
|
||||||
ledc_timer_config_t ledc_timer;
|
ledc_timer_config_t ledc_timer;
|
||||||
memset((void *)&ledc_timer, 0, sizeof(ledc_timer_config_t));
|
memset((void *)&ledc_timer, 0, sizeof(ledc_timer_config_t));
|
||||||
ledc_timer.speed_mode = group;
|
ledc_timer.speed_mode = group;
|
||||||
ledc_timer.timer_num = timer;
|
ledc_timer.timer_num = bus->timer_num;
|
||||||
ledc_timer.duty_resolution = resolution;
|
ledc_timer.duty_resolution = resolution;
|
||||||
ledc_timer.freq_hz = freq;
|
ledc_timer.freq_hz = freq;
|
||||||
ledc_timer.clk_cfg = clock_source;
|
ledc_timer.clk_cfg = clock_source;
|
||||||
|
|
@ -442,7 +487,7 @@ uint32_t ledcChangeFrequency(uint8_t pin, uint32_t freq, uint8_t resolution) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
bus->channel_resolution = resolution;
|
bus->channel_resolution = resolution;
|
||||||
return ledc_get_freq(group, timer);
|
return ledc_get_freq(group, bus->timer_num);
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
@ -453,12 +498,14 @@ bool ledcOutputInvert(uint8_t pin, bool out_invert) {
|
||||||
gpio_set_level(pin, out_invert);
|
gpio_set_level(pin, out_invert);
|
||||||
|
|
||||||
#ifdef CONFIG_IDF_TARGET_ESP32P4
|
#ifdef CONFIG_IDF_TARGET_ESP32P4
|
||||||
esp_rom_gpio_connect_out_signal(pin, LEDC_LS_SIG_OUT_PAD_OUT0_IDX + ((bus->channel) % 8), out_invert, 0);
|
esp_rom_gpio_connect_out_signal(pin, LEDC_LS_SIG_OUT_PAD_OUT0_IDX + ((bus->channel) % SOC_LEDC_CHANNEL_NUM), out_invert, 0);
|
||||||
#else
|
#else
|
||||||
#ifdef SOC_LEDC_SUPPORT_HS_MODE
|
#ifdef SOC_LEDC_SUPPORT_HS_MODE
|
||||||
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);
|
esp_rom_gpio_connect_out_signal(
|
||||||
|
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
|
||||||
|
);
|
||||||
#else
|
#else
|
||||||
esp_rom_gpio_connect_out_signal(pin, LEDC_LS_SIG_OUT0_IDX + ((bus->channel) % 8), out_invert, 0);
|
esp_rom_gpio_connect_out_signal(pin, LEDC_LS_SIG_OUT0_IDX + ((bus->channel) % SOC_LEDC_CHANNEL_NUM), out_invert, 0);
|
||||||
#endif
|
#endif
|
||||||
#endif // ifdef CONFIG_IDF_TARGET_ESP32P4
|
#endif // ifdef CONFIG_IDF_TARGET_ESP32P4
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -505,7 +552,7 @@ static bool ledcFadeConfig(uint8_t pin, uint32_t start_duty, uint32_t target_dut
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
uint8_t group = (bus->channel / 8), channel = (bus->channel % 8);
|
uint8_t group = (bus->channel / SOC_LEDC_CHANNEL_NUM), channel = (bus->channel % SOC_LEDC_CHANNEL_NUM);
|
||||||
|
|
||||||
// Initialize fade service.
|
// Initialize fade service.
|
||||||
if (!fade_initialized) {
|
if (!fade_initialized) {
|
||||||
|
|
@ -562,6 +609,161 @@ bool ledcFadeWithInterruptArg(uint8_t pin, uint32_t start_duty, uint32_t target_
|
||||||
return ledcFadeConfig(pin, start_duty, target_duty, max_fade_time_ms, userFunc, arg);
|
return ledcFadeConfig(pin, start_duty, target_duty, max_fade_time_ms, userFunc, arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef SOC_LEDC_GAMMA_CURVE_FADE_SUPPORTED
|
||||||
|
// Default gamma factor for gamma correction (common value for LEDs)
|
||||||
|
static float ledcGammaFactor = 2.8;
|
||||||
|
// Gamma correction LUT support
|
||||||
|
static const float *ledcGammaLUT = NULL;
|
||||||
|
static uint16_t ledcGammaLUTSize = 0;
|
||||||
|
// Global variable to store current resolution for gamma callback
|
||||||
|
static uint8_t ledcGammaResolution = 13;
|
||||||
|
|
||||||
|
bool ledcSetGammaTable(const float *gamma_table, uint16_t size) {
|
||||||
|
if (gamma_table == NULL || size == 0) {
|
||||||
|
log_e("Invalid gamma table or size");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ledcGammaLUT = gamma_table;
|
||||||
|
ledcGammaLUTSize = size;
|
||||||
|
log_i("Custom gamma LUT set with %u entries", size);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ledcClearGammaTable(void) {
|
||||||
|
ledcGammaLUT = NULL;
|
||||||
|
ledcGammaLUTSize = 0;
|
||||||
|
log_i("Gamma LUT cleared, using mathematical calculation");
|
||||||
|
}
|
||||||
|
|
||||||
|
void ledcSetGammaFactor(float factor) {
|
||||||
|
ledcGammaFactor = factor;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gamma correction calculator function
|
||||||
|
static uint32_t ledcGammaCorrection(uint32_t duty) {
|
||||||
|
if (duty == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t max_duty = (1U << ledcGammaResolution) - 1;
|
||||||
|
if (duty >= (1U << ledcGammaResolution)) {
|
||||||
|
return max_duty;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use LUT if provided, otherwise use mathematical calculation
|
||||||
|
if (ledcGammaLUT != NULL && ledcGammaLUTSize > 0) {
|
||||||
|
// LUT-based gamma correction
|
||||||
|
uint32_t lut_index = (duty * (ledcGammaLUTSize - 1)) / max_duty;
|
||||||
|
if (lut_index >= ledcGammaLUTSize) {
|
||||||
|
lut_index = ledcGammaLUTSize - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
float corrected_normalized = ledcGammaLUT[lut_index];
|
||||||
|
return (uint32_t)(corrected_normalized * max_duty);
|
||||||
|
} else {
|
||||||
|
// Mathematical gamma correction
|
||||||
|
double normalized = (double)duty / (1U << ledcGammaResolution);
|
||||||
|
double corrected = pow(normalized, ledcGammaFactor);
|
||||||
|
return (uint32_t)(corrected * (1U << ledcGammaResolution));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool ledcFadeGammaConfig(uint8_t pin, uint32_t start_duty, uint32_t target_duty, int max_fade_time_ms, void (*userFunc)(void *), void *arg) {
|
||||||
|
ledc_channel_handle_t *bus = (ledc_channel_handle_t *)perimanGetPinBus(pin, ESP32_BUS_TYPE_LEDC);
|
||||||
|
if (bus != NULL) {
|
||||||
|
|
||||||
|
#ifndef SOC_LEDC_SUPPORT_FADE_STOP
|
||||||
|
#if !CONFIG_DISABLE_HAL_LOCKS
|
||||||
|
if (bus->lock == NULL) {
|
||||||
|
bus->lock = xSemaphoreCreateBinary();
|
||||||
|
if (bus->lock == NULL) {
|
||||||
|
log_e("xSemaphoreCreateBinary failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
xSemaphoreGive(bus->lock);
|
||||||
|
}
|
||||||
|
//acquire lock
|
||||||
|
if (xSemaphoreTake(bus->lock, 0) != pdTRUE) {
|
||||||
|
log_e("LEDC Fade is still running on pin %u! SoC does not support stopping fade.", pin);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
uint8_t group = (bus->channel / SOC_LEDC_CHANNEL_NUM), channel = (bus->channel % SOC_LEDC_CHANNEL_NUM);
|
||||||
|
|
||||||
|
// Initialize fade service.
|
||||||
|
if (!fade_initialized) {
|
||||||
|
ledc_fade_func_install(0);
|
||||||
|
fade_initialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bus->fn = (voidFuncPtr)userFunc;
|
||||||
|
bus->arg = arg;
|
||||||
|
|
||||||
|
ledc_cbs_t callbacks = {.fade_cb = ledcFnWrapper};
|
||||||
|
ledc_cb_register(group, channel, &callbacks, (void *)bus);
|
||||||
|
|
||||||
|
// Prepare gamma curve fade parameters
|
||||||
|
ledc_fade_param_config_t fade_params[SOC_LEDC_GAMMA_CURVE_FADE_RANGE_MAX];
|
||||||
|
uint32_t actual_fade_ranges = 0;
|
||||||
|
|
||||||
|
// Use a moderate number of linear segments for smooth gamma curve
|
||||||
|
const uint32_t linear_fade_segments = 12;
|
||||||
|
|
||||||
|
// Set the global resolution for gamma correction
|
||||||
|
ledcGammaResolution = bus->channel_resolution;
|
||||||
|
|
||||||
|
// Fill multi-fade parameter list using ESP-IDF API
|
||||||
|
esp_err_t err = ledc_fill_multi_fade_param_list(
|
||||||
|
group, channel, start_duty, target_duty, linear_fade_segments, max_fade_time_ms, ledcGammaCorrection, SOC_LEDC_GAMMA_CURVE_FADE_RANGE_MAX, fade_params,
|
||||||
|
&actual_fade_ranges
|
||||||
|
);
|
||||||
|
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
log_e("ledc_fill_multi_fade_param_list failed: %s", esp_err_to_name(err));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply the gamma-corrected start duty
|
||||||
|
uint32_t gamma_start_duty = ledcGammaCorrection(start_duty);
|
||||||
|
|
||||||
|
// Set multi-fade parameters
|
||||||
|
err = ledc_set_multi_fade(group, channel, gamma_start_duty, fade_params, actual_fade_ranges);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
log_e("ledc_set_multi_fade failed: %s", esp_err_to_name(err));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start the gamma curve fade
|
||||||
|
err = ledc_fade_start(group, channel, LEDC_FADE_NO_WAIT);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
log_e("ledc_fade_start failed: %s", esp_err_to_name(err));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
log_d("Gamma curve fade started on pin %u: %u -> %u over %dms", pin, start_duty, target_duty, max_fade_time_ms);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
log_e("Pin %u is not attached to LEDC. Call ledcAttach first!", pin);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ledcFadeGamma(uint8_t pin, uint32_t start_duty, uint32_t target_duty, int max_fade_time_ms) {
|
||||||
|
return ledcFadeGammaConfig(pin, start_duty, target_duty, max_fade_time_ms, NULL, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ledcFadeGammaWithInterrupt(uint8_t pin, uint32_t start_duty, uint32_t target_duty, int max_fade_time_ms, voidFuncPtr userFunc) {
|
||||||
|
return ledcFadeGammaConfig(pin, start_duty, target_duty, max_fade_time_ms, (voidFuncPtrArg)userFunc, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ledcFadeGammaWithInterruptArg(uint8_t pin, uint32_t start_duty, uint32_t target_duty, int max_fade_time_ms, void (*userFunc)(void *), void *arg) {
|
||||||
|
return ledcFadeGammaConfig(pin, start_duty, target_duty, max_fade_time_ms, userFunc, arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* SOC_LEDC_GAMMA_CURVE_FADE_SUPPORTED */
|
||||||
|
|
||||||
static uint8_t analog_resolution = 8;
|
static uint8_t analog_resolution = 8;
|
||||||
static int analog_frequency = 1000;
|
static int analog_frequency = 1000;
|
||||||
void analogWrite(uint8_t pin, int value) {
|
void analogWrite(uint8_t pin, int value) {
|
||||||
|
|
|
||||||
|
|
@ -232,6 +232,85 @@ bool ledcFadeWithInterrupt(uint8_t pin, uint32_t start_duty, uint32_t target_dut
|
||||||
*/
|
*/
|
||||||
bool ledcFadeWithInterruptArg(uint8_t pin, uint32_t start_duty, uint32_t target_duty, int max_fade_time_ms, void (*userFunc)(void *), void *arg);
|
bool ledcFadeWithInterruptArg(uint8_t pin, uint32_t start_duty, uint32_t target_duty, int max_fade_time_ms, void (*userFunc)(void *), void *arg);
|
||||||
|
|
||||||
|
//Gamma Curve Fade functions - only available on supported chips
|
||||||
|
#ifdef SOC_LEDC_GAMMA_CURVE_FADE_SUPPORTED
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set a custom gamma correction lookup table for gamma curve fading.
|
||||||
|
* The LUT should contain normalized values (0.0 to 1.0) representing
|
||||||
|
* the gamma-corrected brightness curve.
|
||||||
|
*
|
||||||
|
* @param gamma_table Pointer to array of float values (0.0 to 1.0)
|
||||||
|
* @param size Number of entries in the lookup table
|
||||||
|
*
|
||||||
|
* @return true if gamma table was successfully set, false otherwise.
|
||||||
|
*
|
||||||
|
* @note The LUT array must remain valid for as long as gamma fading is used.
|
||||||
|
* Larger tables provide smoother transitions but use more memory.
|
||||||
|
*/
|
||||||
|
bool ledcSetGammaTable(const float *gamma_table, uint16_t size);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Clear the current gamma correction lookup table.
|
||||||
|
* After calling this, gamma correction will use mathematical
|
||||||
|
* calculation with the default gamma factor (2.8).
|
||||||
|
*/
|
||||||
|
void ledcClearGammaTable(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set the gamma factor for gamma correction.
|
||||||
|
*
|
||||||
|
* @param factor Gamma factor to use for gamma correction.
|
||||||
|
*/
|
||||||
|
void ledcSetGammaFactor(float factor);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Setup and start a gamma curve fade on a given LEDC pin.
|
||||||
|
* Gamma correction makes LED brightness changes appear more gradual to human eyes.
|
||||||
|
*
|
||||||
|
* @param pin GPIO pin
|
||||||
|
* @param start_duty initial duty cycle of the fade
|
||||||
|
* @param target_duty target duty cycle of the fade
|
||||||
|
* @param max_fade_time_ms maximum fade time in milliseconds
|
||||||
|
*
|
||||||
|
* @return true if gamma fade was successfully set and started, false otherwise.
|
||||||
|
*
|
||||||
|
* @note This function is only available on ESP32 variants that support gamma curve fading.
|
||||||
|
*/
|
||||||
|
bool ledcFadeGamma(uint8_t pin, uint32_t start_duty, uint32_t target_duty, int max_fade_time_ms);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Setup and start a gamma curve fade on a given LEDC pin with a callback function.
|
||||||
|
*
|
||||||
|
* @param pin GPIO pin
|
||||||
|
* @param start_duty initial duty cycle of the fade
|
||||||
|
* @param target_duty target duty cycle of the fade
|
||||||
|
* @param max_fade_time_ms maximum fade time in milliseconds
|
||||||
|
* @param userFunc callback function to be called after fade is finished
|
||||||
|
*
|
||||||
|
* @return true if gamma fade was successfully set and started, false otherwise.
|
||||||
|
*
|
||||||
|
* @note This function is only available on ESP32 variants that support gamma curve fading.
|
||||||
|
*/
|
||||||
|
bool ledcFadeGammaWithInterrupt(uint8_t pin, uint32_t start_duty, uint32_t target_duty, int max_fade_time_ms, void (*userFunc)(void));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Setup and start a gamma curve fade on a given LEDC pin with a callback function and argument.
|
||||||
|
*
|
||||||
|
* @param pin GPIO pin
|
||||||
|
* @param start_duty initial duty cycle of the fade
|
||||||
|
* @param target_duty target duty cycle of the fade
|
||||||
|
* @param max_fade_time_ms maximum fade time in milliseconds
|
||||||
|
* @param userFunc callback function to be called after fade is finished
|
||||||
|
* @param arg argument to be passed to the callback function
|
||||||
|
*
|
||||||
|
* @return true if gamma fade was successfully set and started, false otherwise.
|
||||||
|
*
|
||||||
|
* @note This function is only available on ESP32 variants that support gamma curve fading.
|
||||||
|
*/
|
||||||
|
bool ledcFadeGammaWithInterruptArg(uint8_t pin, uint32_t start_duty, uint32_t target_duty, int max_fade_time_ms, void (*userFunc)(void *), void *arg);
|
||||||
|
#endif // SOC_LEDC_GAMMA_CURVE_FADE_SUPPORTED
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,111 @@
|
||||||
|
/* LEDC Gamma Curve Fade Arduino Example
|
||||||
|
|
||||||
|
This example demonstrates gamma curve fading on ESP32 variants that support it.
|
||||||
|
Gamma correction makes LED brightness changes appear more gradual and natural
|
||||||
|
to human eyes compared to linear fading.
|
||||||
|
|
||||||
|
Two methods are supported:
|
||||||
|
1. Using a pre-computed Gamma Look-Up Table (LUT) for better performance
|
||||||
|
2. Using mathematical gamma correction with a gamma factor
|
||||||
|
|
||||||
|
Supported chips: ESP32-C6, ESP32-C5, ESP32-H2, ESP32-P4 and future chips with Gamma Fade support
|
||||||
|
|
||||||
|
Created by Jan Procházka (https://github.com/P-R-O-C-H-Y/)
|
||||||
|
*/
|
||||||
|
|
||||||
|
// use 12 bit precision for LEDC timer
|
||||||
|
#define LEDC_TIMER_12_BIT 12
|
||||||
|
|
||||||
|
// use 5000 Hz as a LEDC base frequency
|
||||||
|
#define LEDC_BASE_FREQ 5000
|
||||||
|
|
||||||
|
// define starting duty, target duty and maximum fade time
|
||||||
|
#define LEDC_START_DUTY (0)
|
||||||
|
#define LEDC_TARGET_DUTY (4095)
|
||||||
|
#define LEDC_FADE_TIME (2000)
|
||||||
|
|
||||||
|
// gamma factor for mathematical calculation
|
||||||
|
#define LEDC_GAMMA_FACTOR (2.6)
|
||||||
|
|
||||||
|
// use gamma LUT for better performance instead of mathematical calculation (gamma factor)
|
||||||
|
#define USE_GAMMA_LUT 1
|
||||||
|
|
||||||
|
// fade LED pins
|
||||||
|
const uint8_t ledPinR = 4;
|
||||||
|
const uint8_t ledPinG = 5;
|
||||||
|
const uint8_t ledPinB = 6;
|
||||||
|
|
||||||
|
uint8_t fade_ended = 0; // status of LED gamma fade
|
||||||
|
bool fade_in = true;
|
||||||
|
|
||||||
|
#ifdef USE_GAMMA_LUT
|
||||||
|
// Custom Gamma LUT demonstration with 101 steps (Brightness 0 - 100% gamma correction look up table (gamma = 2.6))
|
||||||
|
// Y = B ^ 2.6 - Pre-computed LUT to save runtime computation
|
||||||
|
static const float ledcGammaLUT[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,
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void ARDUINO_ISR_ATTR LED_FADE_ISR() {
|
||||||
|
fade_ended += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
// Initialize serial communication at 115200 bits per second:
|
||||||
|
Serial.begin(115200);
|
||||||
|
|
||||||
|
// Setup timer with given frequency, resolution and attach it to a led pin with auto-selected channel
|
||||||
|
ledcAttach(ledPinR, LEDC_BASE_FREQ, LEDC_TIMER_12_BIT);
|
||||||
|
ledcAttach(ledPinG, LEDC_BASE_FREQ, LEDC_TIMER_12_BIT);
|
||||||
|
ledcAttach(ledPinB, LEDC_BASE_FREQ, LEDC_TIMER_12_BIT);
|
||||||
|
|
||||||
|
#if USE_GAMMA_LUT // Use default gamma LUT for better performance
|
||||||
|
ledcSetGammaTable(ledcGammaLUT, 101);
|
||||||
|
#else // Use mathematical gamma correction (default, more flexible)
|
||||||
|
ledcSetGammaFactor(LEDC_GAMMA_FACTOR); // This is optional to set custom gamma factor (default is 2.8)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Setup and start gamma curve fade on led (duty from 0 to 4095)
|
||||||
|
ledcFadeGamma(ledPinR, LEDC_START_DUTY, LEDC_TARGET_DUTY, LEDC_FADE_TIME);
|
||||||
|
ledcFadeGamma(ledPinG, LEDC_START_DUTY, LEDC_TARGET_DUTY, LEDC_FADE_TIME);
|
||||||
|
ledcFadeGamma(ledPinB, LEDC_START_DUTY, LEDC_TARGET_DUTY, LEDC_FADE_TIME);
|
||||||
|
Serial.println("LED Gamma Fade on started.");
|
||||||
|
|
||||||
|
// Wait for fade to end
|
||||||
|
delay(LEDC_FADE_TIME);
|
||||||
|
|
||||||
|
// Setup and start gamma curve fade off led and use ISR (duty from 4095 to 0)
|
||||||
|
ledcFadeGammaWithInterrupt(ledPinR, LEDC_TARGET_DUTY, LEDC_START_DUTY, LEDC_FADE_TIME, LED_FADE_ISR);
|
||||||
|
ledcFadeGammaWithInterrupt(ledPinG, LEDC_TARGET_DUTY, LEDC_START_DUTY, LEDC_FADE_TIME, LED_FADE_ISR);
|
||||||
|
ledcFadeGammaWithInterrupt(ledPinB, LEDC_TARGET_DUTY, LEDC_START_DUTY, LEDC_FADE_TIME, LED_FADE_ISR);
|
||||||
|
Serial.println("LED Gamma Fade off started.");
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
// Check if fade_ended flag was set to true in ISR
|
||||||
|
if (fade_ended == 3) {
|
||||||
|
Serial.println("LED gamma fade ended");
|
||||||
|
fade_ended = 0;
|
||||||
|
|
||||||
|
// Check what gamma fade should be started next
|
||||||
|
if (fade_in) {
|
||||||
|
ledcFadeGammaWithInterrupt(ledPinR, LEDC_START_DUTY, LEDC_TARGET_DUTY, LEDC_FADE_TIME, LED_FADE_ISR);
|
||||||
|
ledcFadeGammaWithInterrupt(ledPinG, LEDC_START_DUTY, LEDC_TARGET_DUTY, LEDC_FADE_TIME, LED_FADE_ISR);
|
||||||
|
ledcFadeGammaWithInterrupt(ledPinB, LEDC_START_DUTY, LEDC_TARGET_DUTY, LEDC_FADE_TIME, LED_FADE_ISR);
|
||||||
|
Serial.println("LED Gamma Fade in started.");
|
||||||
|
fade_in = false;
|
||||||
|
} else {
|
||||||
|
ledcFadeGammaWithInterrupt(ledPinR, LEDC_TARGET_DUTY, LEDC_START_DUTY, LEDC_FADE_TIME, LED_FADE_ISR);
|
||||||
|
ledcFadeGammaWithInterrupt(ledPinG, LEDC_TARGET_DUTY, LEDC_START_DUTY, LEDC_FADE_TIME, LED_FADE_ISR);
|
||||||
|
ledcFadeGammaWithInterrupt(ledPinB, LEDC_TARGET_DUTY, LEDC_START_DUTY, LEDC_FADE_TIME, LED_FADE_ISR);
|
||||||
|
Serial.println("LED Gamma Fade out started.");
|
||||||
|
fade_in = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
5
libraries/ESP32/examples/AnalogOut/LEDCGammaFade/ci.json
Normal file
5
libraries/ESP32/examples/AnalogOut/LEDCGammaFade/ci.json
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"requires": [
|
||||||
|
"CONFIG_SOC_LEDC_GAMMA_CURVE_FADE_SUPPORTED=y"
|
||||||
|
]
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue