Skip to content

LEDC Fade implementation #8338

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jun 29, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
fade API + pointer fixes
  • Loading branch information
P-R-O-C-H-Y committed Jun 19, 2023
commit 850bcaf397ae6d874e5e2f46dd158d73e1a4953d
156 changes: 121 additions & 35 deletions cores/esp32/esp32-hal-ledc.c
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
// Copyright 2015-2023 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -37,33 +37,19 @@ typedef struct {
int used_channels : LEDC_CHANNELS; // Used channels as a bits
} ledc_periph_t;

/*
* LEDC Chan to Group/Channel/Timer Mapping
** ledc: 0 => Group: 0, Channel: 0, Timer: 0
** ledc: 1 => Group: 0, Channel: 1, Timer: 0
** ledc: 2 => Group: 0, Channel: 2, Timer: 1
** ledc: 3 => Group: 0, Channel: 3, Timer: 1
** ledc: 4 => Group: 0, Channel: 4, Timer: 2
** ledc: 5 => Group: 0, Channel: 5, Timer: 2
** ledc: 6 => Group: 0, Channel: 6, Timer: 3
** ledc: 7 => Group: 0, Channel: 7, Timer: 3
** ledc: 8 => Group: 1, Channel: 0, Timer: 0
** ledc: 9 => Group: 1, Channel: 1, Timer: 0
** ledc: 10 => Group: 1, Channel: 2, Timer: 1
** ledc: 11 => Group: 1, Channel: 3, Timer: 1
** ledc: 12 => Group: 1, Channel: 4, Timer: 2
** ledc: 13 => Group: 1, Channel: 5, Timer: 2
** ledc: 14 => Group: 1, Channel: 6, Timer: 3
** ledc: 15 => Group: 1, Channel: 7, Timer: 3
*/

ledc_periph_t ledc_handle;

static bool fade_initialized = false;

static bool ledcDetachBus(void * bus){
ledc_channel_handle_t handle = (ledc_channel_handle_t)bus;
ledc_channel_handle_t *handle = (ledc_channel_handle_t*)bus;
ledc_handle.used_channels &= ~(1UL << handle->channel);
pinMatrixOutDetach(handle->pin, false, false);
free(handle);
if(ledc_handle.used_channels == 0){
ledc_fade_func_uninstall();
fade_initialized = false;
}
return true;
}

Expand All @@ -77,7 +63,7 @@ bool ledcAttach(uint8_t pin, uint32_t freq, uint8_t resolution)
}

perimanSetBusDeinit(ESP32_BUS_TYPE_LEDC, ledcDetachBus);
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 && !perimanSetPinBus(pin, ESP32_BUS_TYPE_INIT, NULL)){
return false;
}
Expand Down Expand Up @@ -111,12 +97,14 @@ bool ledcAttach(uint8_t pin, uint32_t freq, uint8_t resolution)
};
ledc_channel_config(&ledc_channel);

ledc_channel_handle_t handle = malloc(sizeof(ledc_channel_handle_t));

handle->pin = pin,
handle->channel = channel,
handle->channel_resolution = resolution,
ledc_channel_handle_t *handle = (ledc_channel_handle_t *)malloc(sizeof(ledc_channel_handle_t));

handle->pin = pin;
handle->channel = channel;
handle->channel_resolution = resolution;
#ifndef SOC_LEDC_SUPPORT_FADE_STOP
handle->lock = NULL;
#endif
ledc_handle.used_channels |= 1UL << channel;

if(!perimanSetPinBus(pin, ESP32_BUS_TYPE_LEDC, (void *)handle)){
Expand All @@ -128,7 +116,7 @@ bool ledcAttach(uint8_t pin, uint32_t freq, uint8_t resolution)
}
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){

uint8_t group=(bus->channel/8), channel=(bus->channel%8);
Expand All @@ -150,7 +138,7 @@ bool ledcWrite(uint8_t pin, uint32_t duty)

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){

uint8_t group=(bus->channel/8), channel=(bus->channel%8);
Expand All @@ -161,7 +149,7 @@ uint32_t ledcRead(uint8_t pin)

uint32_t ledcReadFreq(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(!ledcRead(pin)){
return 0;
Expand All @@ -175,7 +163,7 @@ uint32_t ledcReadFreq(uint8_t pin)

uint32_t ledcWriteTone(uint8_t pin, uint32_t freq)
{
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(!freq){
Expand Down Expand Up @@ -222,7 +210,7 @@ uint32_t ledcWriteNote(uint8_t pin, note_t note, uint8_t octave){

bool ledcDetach(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){
// will call ledcDetachBus
return perimanSetPinBus(pin, ESP32_BUS_TYPE_INIT, NULL);
Expand All @@ -234,7 +222,7 @@ bool ledcDetach(uint8_t pin)

uint32_t ledcChangeFrequency(uint8_t pin, uint32_t freq, uint8_t resolution)
{
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(resolution > LEDC_MAX_BIT_WIDTH){
Expand Down Expand Up @@ -262,12 +250,110 @@ uint32_t ledcChangeFrequency(uint8_t pin, uint32_t freq, uint8_t resolution)
return 0;
}

static IRAM_ATTR bool ledcFnWrapper(const ledc_cb_param_t *param, void *user_arg)
{
if (param->event == LEDC_FADE_END_EVT) {
ledc_channel_handle_t *bus = (ledc_channel_handle_t*)user_arg;
#ifndef SOC_LEDC_SUPPORT_FADE_STOP
portBASE_TYPE xTaskWoken = 0;
xSemaphoreGiveFromISR(bus->lock, &xTaskWoken);
#endif
if(bus->fn) {
uint8_t group=(bus->channel/8), channel=(bus->channel%8);
if(bus->arg){
((voidFuncPtrArg)bus->fn)(bus->arg);
} else {
bus->fn();
}
}
}
return true;
}

static void ledcFadeConfig(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;
}
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;
}
#endif
#endif
uint8_t group=(bus->channel/8), channel=(bus->channel%8);

// 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);

//Fixing if all bits in resolution is set = LEDC FULL ON
uint32_t max_duty = (1 << bus->channel_resolution) - 1;

if((target_duty == max_duty) && (max_duty != 1)){
target_duty = max_duty + 1;
}
else if((start_duty == max_duty) && (max_duty != 1)){
start_duty = max_duty + 1;
}

#if SOC_LEDC_SUPPORT_FADE_STOP
ledc_fade_stop(group, channel);
#endif

if(ledc_set_duty_and_update(group, channel, start_duty, 0) != ESP_OK){
log_e("ledc_set_duty_and_update failed");
}
// Wait for LEDCs next PWM cycle to update duty (~ 1-2 ms)
while(ledc_get_duty(group,channel) != start_duty);

if(ledc_set_fade_time_and_start(group, channel, target_duty, max_fade_time_ms, LEDC_FADE_NO_WAIT) != ESP_OK){
log_e("ledc_set_fade_time_and_start failed");
}
}
else {
log_e("Pin %u is not attached to LEDC. Call ledcAttach first!", pin);
}
}

void ledcFade(uint8_t pin, uint32_t start_duty, uint32_t target_duty, int fade_time_ms){
ledcFadeConfig(pin, start_duty, target_duty, fade_time_ms, NULL, NULL);
}

void ledcFadeWithInterrupt(uint8_t pin, uint32_t start_duty, uint32_t target_duty, int fade_time_ms, voidFuncPtr userFunc){
ledcFadeConfig(pin, start_duty, target_duty, fade_time_ms, (voidFuncPtrArg)userFunc, NULL);
}

void ledcFadeWithInterruptArg(uint8_t pin, uint32_t start_duty, uint32_t target_duty, int fade_time_ms, void (*userFunc)(void*), void * arg){
ledcFadeConfig(pin, start_duty, target_duty, fade_time_ms, userFunc, arg);
}

static uint8_t analog_resolution = 8;
static int analog_frequency = 1000;
void analogWrite(uint8_t pin, int value) {
// Use ledc hardware for internal pins
if (pin < SOC_GPIO_PIN_COUNT) {
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 && perimanSetPinBus(pin, ESP32_BUS_TYPE_INIT, NULL)){
if(ledcAttach(pin, analog_frequency, analog_resolution) == 0){
log_e("analogWrite setup failed (freq = %u, resolution = %u). Try setting different resolution or frequency");
Expand Down
16 changes: 14 additions & 2 deletions cores/esp32/esp32-hal-ledc.h
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
// Copyright 2015-2023 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -26,11 +26,19 @@ typedef enum {
NOTE_C, NOTE_Cs, NOTE_D, NOTE_Eb, NOTE_E, NOTE_F, NOTE_Fs, NOTE_G, NOTE_Gs, NOTE_A, NOTE_Bb, NOTE_B, NOTE_MAX
} note_t;

typedef void (*voidFuncPtr)(void);
typedef void (*voidFuncPtrArg)(void*);

typedef struct {
uint8_t pin; // Pin assigned to channel
uint8_t channel; // Channel number
uint8_t channel_resolution; // Resolution of channel
} *ledc_channel_handle_t;
voidFuncPtr fn;
void* arg;
#ifndef SOC_LEDC_SUPPORT_FADE_STOP
SemaphoreHandle_t lock; //xSemaphoreCreateBinary
#endif
} ledc_channel_handle_t;

//channel 0-15 resolution 1-16bits freq limits depend on resolution
bool ledcAttach(uint8_t pin, uint32_t freq, uint8_t resolution);
Expand All @@ -42,6 +50,10 @@ uint32_t ledcReadFreq(uint8_t pin);
bool ledcDetach(uint8_t pin);
uint32_t ledcChangeFrequency(uint8_t pin, uint32_t freq, uint8_t resolution);

//Fade functions
void ledcFade(uint8_t pin, uint32_t start_duty, uint32_t target_duty, int fade_time_ms);
void ledcFadeWithInterrupt(uint8_t pin, uint32_t start_duty, uint32_t target_duty, int fade_time_ms, void (*userFunc)(void));
void ledcFadeWithInterruptArg(uint8_t pin, uint32_t start_duty, uint32_t target_duty, int fade_time_ms, void (*userFunc)(void*), void * arg);

#ifdef __cplusplus
}
Expand Down