From 31b07e0ad65e5b196998b692ba5af62ed6adaa12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Proch=C3=A1zka?= <90197375+P-R-O-C-H-Y@users.noreply.github.com> Date: Thu, 29 Jun 2023 22:05:57 +0200 Subject: [PATCH] LEDC Fade implementation (#8338) * fade API + pointer fixes * Add fade api * Add fade example * update ledc docs * remove unused variables * fix path to example --- cores/esp32/esp32-hal-ledc.c | 159 ++++++++++++++---- cores/esp32/esp32-hal-ledc.h | 16 +- docs/source/api/ledc.rst | 65 ++++++- .../examples/AnalogOut/LEDCFade/LEDCFade.ino | 69 ++++++++ 4 files changed, 271 insertions(+), 38 deletions(-) create mode 100644 libraries/ESP32/examples/AnalogOut/LEDCFade/LEDCFade.ino diff --git a/cores/esp32/esp32-hal-ledc.c b/cores/esp32/esp32-hal-ledc.c index 74aac1c463d..fe98740856c 100644 --- a/cores/esp32/esp32-hal-ledc.c +++ b/cores/esp32/esp32-hal-ledc.c @@ -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. @@ -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; } @@ -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; } @@ -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)){ @@ -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); @@ -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); @@ -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; @@ -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){ @@ -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); @@ -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){ @@ -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"); diff --git a/cores/esp32/esp32-hal-ledc.h b/cores/esp32/esp32-hal-ledc.h index 18142659587..e379183b590 100644 --- a/cores/esp32/esp32-hal-ledc.h +++ b/cores/esp32/esp32-hal-ledc.h @@ -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. @@ -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); @@ -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 } diff --git a/docs/source/api/ledc.rst b/docs/source/api/ledc.rst index 1f81c03a5ef..330ad07cff7 100644 --- a/docs/source/api/ledc.rst +++ b/docs/source/api/ledc.rst @@ -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 @@ -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 ******** @@ -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 *********** @@ -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 diff --git a/libraries/ESP32/examples/AnalogOut/LEDCFade/LEDCFade.ino b/libraries/ESP32/examples/AnalogOut/LEDCFade/LEDCFade.ino new file mode 100644 index 00000000000..002df746014 --- /dev/null +++ b/libraries/ESP32/examples/AnalogOut/LEDCFade/LEDCFade.ino @@ -0,0 +1,69 @@ +/* LEDC Fade Arduino Example + + This example code is in the Public Domain (or CC0 licensed, at your option.) + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ + +// use 12 bit precission for LEDC timer +#define LEDC_TIMER_12_BIT 12 + +// use 5000 Hz as a LEDC base frequency +#define LEDC_BASE_FREQ 5000 + +// fade LED PIN (replace with LED_BUILTIN constant for built-in LED) +#define LED_PIN 4 + +// define starting duty, target duty and maximum fade time +#define LEDC_START_DUTY (0) +#define LEDC_TARGET_DUTY (4095) +#define LEDC_FADE_TIME (3000) + +bool fade_ended = false; // status of LED fade +bool fade_on = true; + +void ARDUINO_ISR_ATTR LED_FADE_ISR() +{ + fade_ended = true; +} + +void setup() { + // Initialize serial communication at 115200 bits per second: + Serial.begin(115200); + while(!Serial) delay(10); + + // Setup timer and attach timer to a led pins + ledcAttach(LED_PIN, LEDC_BASE_FREQ, LEDC_TIMER_12_BIT); + + // Setup and start fade on led (duty from 0 to 4095) + ledcFade(LED_PIN, LEDC_START_DUTY, LEDC_TARGET_DUTY, LEDC_FADE_TIME); + Serial.println("LED Fade on started."); + + // Wait for fade to end + delay(LEDC_FADE_TIME); + + // Setup and start fade off led and use ISR (duty from 4095 to 0) + ledcFadeWithInterrupt(LED_PIN, LEDC_TARGET_DUTY, LEDC_START_DUTY, LEDC_FADE_TIME, LED_FADE_ISR); + Serial.println("LED Fade off started."); +} + +void loop() { + // Check if fade_ended flag was set to true in ISR + if(fade_ended){ + Serial.println("LED fade ended"); + fade_ended = false; + + // Check if last fade was fade on + if(fade_on){ + ledcFadeWithInterrupt(LED_PIN, LEDC_START_DUTY, LEDC_TARGET_DUTY, LEDC_FADE_TIME, LED_FADE_ISR); + Serial.println("LED Fade off started."); + fade_on = false; + } + else { + ledcFadeWithInterrupt(LED_PIN, LEDC_TARGET_DUTY, LEDC_START_DUTY, LEDC_FADE_TIME, LED_FADE_ISR); + Serial.println("LED Fade on started."); + fade_on = true; + } + } +} \ No newline at end of file