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 all commits
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
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