Skip to content

Commit

Permalink
LEDC Fade implementation (#8338)
Browse files Browse the repository at this point in the history
* fade API + pointer fixes

* Add fade api

* Add fade example

* update ledc docs

* remove unused variables

* fix path to example
  • Loading branch information
P-R-O-C-H-Y authored Jun 29, 2023
1 parent 4f94154 commit 31b07e0
Show file tree
Hide file tree
Showing 4 changed files with 271 additions and 38 deletions.
159 changes: 124 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,113 @@ 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) {
if(bus->arg){
((voidFuncPtrArg)bus->fn)(bus->arg);
} else {
bus->fn();
}
}
}
return true;
}

static bool 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 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/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");
return false;
}
// 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");
return false;
}
}
else {
log_e("Pin %u is not attached to LEDC. Call ledcAttach first!", pin);
return false;
}
return true;
}

bool ledcFade(uint8_t pin, uint32_t start_duty, uint32_t target_duty, int max_fade_time_ms){
return ledcFadeConfig(pin, start_duty, target_duty, max_fade_time_ms, NULL, NULL);
}

bool ledcFadeWithInterrupt(uint8_t pin, uint32_t start_duty, uint32_t target_duty, int max_fade_time_ms, voidFuncPtr userFunc){
return ledcFadeConfig(pin, start_duty, target_duty, max_fade_time_ms, (voidFuncPtrArg)userFunc, NULL);
}

bool ledcFadeWithInterruptArg(uint8_t pin, uint32_t start_duty, uint32_t target_duty, int max_fade_time_ms, void (*userFunc)(void*), void * arg){
return ledcFadeConfig(pin, start_duty, target_duty, max_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
bool ledcFade(uint8_t pin, uint32_t start_duty, uint32_t target_duty, int max_fade_time_ms);
bool ledcFadeWithInterrupt(uint8_t pin, uint32_t start_duty, uint32_t target_duty, int max_fade_time_ms, void (*userFunc)(void));
bool ledcFadeWithInterruptArg(uint8_t pin, uint32_t start_duty, uint32_t target_duty, int max_fade_time_ms, void (*userFunc)(void*), void * arg);

#ifdef __cplusplus
}
Expand Down
65 changes: 64 additions & 1 deletion docs/source/api/ledc.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ ESP32 SoC Number of LEDC channels
========= =======================
ESP32 16
ESP32-S2 8
ESP32-C3 6
ESP32-S3 8
ESP32-C3 6
ESP32-C6 6
========= =======================

Arduino-ESP32 LEDC API
Expand Down Expand Up @@ -51,6 +52,9 @@ This function is used to set duty for the LEDC pin.
* ``pin`` select LEDC pin.
* ``duty`` select duty to be set for selected LEDC pin.

This function will return ``true`` if setting duty is successful.
If ``false`` is returned, error occurs and duty was not set.

ledcRead
********

Expand Down Expand Up @@ -143,6 +147,60 @@ This function is used to set frequency for the LEDC pin.
This function will return ``frequency`` configured for the LEDC channel.
If ``0`` is returned, error occurs and the LEDC channel frequency was not set.

ledcFade
********

This function is used to setup and start fade for the LEDC pin.

.. code-block:: arduino
bool ledcFade(uint8_t pin, uint32_t start_duty, uint32_t target_duty, int max_fade_time_ms);
* ``pin`` select LEDC pin.
* ``start_duty`` select starting duty of fade.
* ``target_duty`` select target duty of fade.
* ``max_fade_time_ms`` select maximum time for fade.

This function will return ``true`` if configuration is successful.
If ``false`` is returned, error occurs and LEDC fade was not configured / started.

ledcFadeWithInterrupt
*********************

This function is used to setup and start fade for the LEDC pin with interrupt.

.. code-block:: arduino
bool ledcFadeWithInterrupt(uint8_t pin, uint32_t start_duty, uint32_t target_duty, int max_fade_time_ms, void (*userFunc)(void));
* ``pin`` select LEDC pin.
* ``start_duty`` select starting duty of fade.
* ``target_duty`` select target duty of fade.
* ``max_fade_time_ms`` select maximum time for fade.
* ``userFunc`` funtion to be called when interrupt is triggered.

This function will return ``true`` if configuration is successful and fade start.
If ``false`` is returned, error occurs and LEDC fade was not configured / started.

ledcFadeWithInterruptArg
************************

This function is used to setup and start fade for the LEDC pin with interrupt using arguments.

.. code-block:: arduino
bool ledcFadeWithInterruptArg(uint8_t pin, uint32_t start_duty, uint32_t target_duty, int max_fade_time_ms, void (*userFunc)(void*), void * arg);
* ``pin`` select LEDC pin.
* ``start_duty`` select starting duty of fade.
* ``target_duty`` select target duty of fade.
* ``max_fade_time_ms`` select maximum time for fade.
* ``userFunc`` funtion to be called when interrupt is triggered.
* ``arg`` pointer to the interrupt arguments.

This function will return ``true`` if configuration is successful and fade start.
If ``false`` is returned, error occurs and LEDC fade was not configured / started.

analogWrite
***********

Expand Down Expand Up @@ -184,6 +242,11 @@ This function is used to set frequency for selected analogWrite pin.
Example Applications
********************

LEDC fade example:

.. literalinclude:: ../../../libraries/ESP32/examples/AnalogOut/LEDCFade/LEDCFade.ino
:language: arduino

LEDC software fade example:

.. literalinclude:: ../../../libraries/ESP32/examples/AnalogOut/LEDCSoftwareFade/LEDCSoftwareFade.ino
Expand Down
Loading

0 comments on commit 31b07e0

Please sign in to comment.