From b68bfd890a04f97e7bd4cb6472c1ee894a898fdc Mon Sep 17 00:00:00 2001 From: Jan Prochazka <90197375+P-R-O-C-H-Y@users.noreply.github.com> Date: Mon, 15 Jul 2024 12:28:20 +0200 Subject: [PATCH 1/8] feat(ledc): Allow attaching multiple pins to 1 channel --- cores/esp32/esp32-hal-ledc.c | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/cores/esp32/esp32-hal-ledc.c b/cores/esp32/esp32-hal-ledc.c index 8c9454c996f..91d85b1b450 100644 --- a/cores/esp32/esp32-hal-ledc.c +++ b/cores/esp32/esp32-hal-ledc.c @@ -48,9 +48,26 @@ static bool fade_initialized = false; static bool ledcDetachBus(void *bus) { ledc_channel_handle_t *handle = (ledc_channel_handle_t *)bus; - ledc_handle.used_channels &= ~(1UL << handle->channel); + bool channel_found = false; + // Check if more pins are attached to the same ledc channel + for (uint8_t i = 0; i < SOC_GPIO_PIN_COUNT; i++) { + if (!perimanPinIsValid(i)) { + continue; //invalid pin, skip + } + peripheral_bus_type_t type = perimanGetPinBusType(i); + if (type == ESP32_BUS_TYPE_LEDC) { + ledc_channel_handle_t *bus_check = (ledc_channel_handle_t *)perimanGetPinBus(i, ESP32_BUS_TYPE_LEDC); + if (bus_check->channel == handle->channel) { + channel_found = true; + break; + } + } + } pinMatrixOutDetach(handle->pin, false, false); free(handle); + if (!channel_found) { + ledc_handle.used_channels &= ~(1UL << handle->channel); + } if (ledc_handle.used_channels == 0) { ledc_fade_func_uninstall(); fade_initialized = false; @@ -59,8 +76,8 @@ static bool ledcDetachBus(void *bus) { } bool ledcAttachChannel(uint8_t pin, uint32_t freq, uint8_t resolution, uint8_t channel) { - if (channel >= LEDC_CHANNELS || ledc_handle.used_channels & (1UL << channel)) { - log_e("Channel %u is not available (maximum %u) or already used!", channel, LEDC_CHANNELS); + if (channel >= LEDC_CHANNELS) { //|| ledc_handle.used_channels & (1UL << channel)) { + log_e("Channel %u is not available (maximum %u) TODO: delete (or already used)!", channel, LEDC_CHANNELS); return false; } if (freq == 0) { From 477527dffe3714e975030a47e3435b365e548f77 Mon Sep 17 00:00:00 2001 From: Jan Prochazka <90197375+P-R-O-C-H-Y@users.noreply.github.com> Date: Mon, 15 Jul 2024 14:14:22 +0200 Subject: [PATCH 2/8] feat(ledc): Add ledcWriteChannel function --- cores/esp32/esp32-hal-ledc.c | 69 ++++++++++++++++++++++++++++-------- cores/esp32/esp32-hal-ledc.h | 10 ++++++ 2 files changed, 65 insertions(+), 14 deletions(-) diff --git a/cores/esp32/esp32-hal-ledc.c b/cores/esp32/esp32-hal-ledc.c index 91d85b1b450..495303a8958 100644 --- a/cores/esp32/esp32-hal-ledc.c +++ b/cores/esp32/esp32-hal-ledc.c @@ -21,6 +21,7 @@ #include "esp32-hal-periman.h" #include "soc/gpio_sig_map.h" #include "esp_rom_gpio.h" +#include "hal/ledc_ll.h" #ifdef SOC_LEDC_SUPPORT_HS_MODE #define LEDC_CHANNELS (SOC_LEDC_CHANNEL_NUM << 1) @@ -77,7 +78,7 @@ static bool ledcDetachBus(void *bus) { bool ledcAttachChannel(uint8_t pin, uint32_t freq, uint8_t resolution, uint8_t channel) { if (channel >= LEDC_CHANNELS) { //|| ledc_handle.used_channels & (1UL << channel)) { - log_e("Channel %u is not available (maximum %u) TODO: delete (or already used)!", channel, LEDC_CHANNELS); + log_e("Channel %u is not available (maximum %u)!", channel, LEDC_CHANNELS); return false; } if (freq == 0) { @@ -102,29 +103,45 @@ bool ledcAttachChannel(uint8_t pin, uint32_t freq, uint8_t resolution, uint8_t c } uint8_t group = (channel / 8), timer = ((channel / 2) % 4); - - ledc_timer_config_t ledc_timer = {.speed_mode = group, .timer_num = timer, .duty_resolution = resolution, .freq_hz = freq, .clk_cfg = LEDC_DEFAULT_CLK}; - if (ledc_timer_config(&ledc_timer) != ESP_OK) { - log_e("ledc setup failed!"); - return false; + bool channel_used = ledc_handle.used_channels & (1UL << channel); + if (channel_used) { + if (ledc_set_pin(pin, group, channel % 8) != ESP_OK) { + log_e("Attaching pin to already used channel failed!"); + return false; + } } + else { + ledc_timer_config_t ledc_timer = {.speed_mode = group, .timer_num = timer, .duty_resolution = resolution, .freq_hz = freq, .clk_cfg = LEDC_DEFAULT_CLK}; + if (ledc_timer_config(&ledc_timer) != ESP_OK) { + log_e("ledc setup failed!"); + return false; + } - uint32_t duty = ledc_get_duty(group, (channel % 8)); + uint32_t duty = ledc_get_duty(group, (channel % 8)); - ledc_channel_config_t ledc_channel = { - .speed_mode = group, .channel = (channel % 8), .timer_sel = timer, .intr_type = LEDC_INTR_DISABLE, .gpio_num = pin, .duty = duty, .hpoint = 0 - }; - ledc_channel_config(&ledc_channel); + ledc_channel_config_t ledc_channel = { + .speed_mode = group, .channel = (channel % 8), .timer_sel = timer, .intr_type = LEDC_INTR_DISABLE, .gpio_num = pin, .duty = duty, .hpoint = 0 + }; + ledc_channel_config(&ledc_channel); + } 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; + + //get resolution of selected channel when used + if (channel_used) { + uint32_t channel_resolution = 0; + ledc_ll_get_duty_resolution(LEDC_LL_GET_HW(), group, timer, &channel_resolution); + handle->channel_resolution = (uint8_t)channel_resolution; + } + else { + handle->channel_resolution = resolution; + ledc_handle.used_channels |= 1UL << channel; + } if (!perimanSetPinBus(pin, ESP32_BUS_TYPE_LEDC, (void *)handle, group, channel)) { ledcDetachBus((void *)handle); @@ -167,6 +184,30 @@ bool ledcWrite(uint8_t pin, uint32_t duty) { return false; } +bool ledcWriteChannel(uint8_t channel, uint32_t duty){ + //check if channel is valid and used + if (channel >= LEDC_CHANNELS || !(ledc_handle.used_channels & (1UL << channel))) { + log_e("Channel %u is not available (maximum %u) or not used!", channel, LEDC_CHANNELS); + return false; + } + uint8_t group = (channel / 8), timer = ((channel / 2) % 4); + + //Fixing if all bits in resolution is set = LEDC FULL ON + uint32_t resolution = 0; + ledc_ll_get_duty_resolution(LEDC_LL_GET_HW(), group, timer, &resolution); + + uint32_t max_duty = (1 << resolution) - 1; + + if ((duty == max_duty) && (max_duty != 1)) { + duty = max_duty + 1; + } + + ledc_set_duty(group, channel, duty); + ledc_update_duty(group, channel); + + return true; +} + uint32_t ledcRead(uint8_t pin) { ledc_channel_handle_t *bus = (ledc_channel_handle_t *)perimanGetPinBus(pin, ESP32_BUS_TYPE_LEDC); if (bus != NULL) { diff --git a/cores/esp32/esp32-hal-ledc.h b/cores/esp32/esp32-hal-ledc.h index 2cff8dc0a8f..efa7bf1fe69 100644 --- a/cores/esp32/esp32-hal-ledc.h +++ b/cores/esp32/esp32-hal-ledc.h @@ -91,6 +91,16 @@ bool ledcAttachChannel(uint8_t pin, uint32_t freq, uint8_t resolution, uint8_t c */ bool ledcWrite(uint8_t pin, uint32_t duty); +/** + * @brief Set the duty cycle of a given channel. + * + * @param channel LEDC channel + * @param duty duty cycle to set + * + * @return true if duty cycle was successfully set, false otherwise. + */ +bool ledcWriteChannel(uint8_t channel, uint32_t duty); + /** * @brief Sets the duty to 50 % PWM tone on selected frequency. * From 19c650004e1047312d8bf4600686f7cafef47903 Mon Sep 17 00:00:00 2001 From: Jan Prochazka <90197375+P-R-O-C-H-Y@users.noreply.github.com> Date: Mon, 15 Jul 2024 14:20:48 +0200 Subject: [PATCH 3/8] feat(ledc): Print info about already set channel --- cores/esp32/esp32-hal-ledc.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cores/esp32/esp32-hal-ledc.c b/cores/esp32/esp32-hal-ledc.c index 495303a8958..74b6a8cccc1 100644 --- a/cores/esp32/esp32-hal-ledc.c +++ b/cores/esp32/esp32-hal-ledc.c @@ -105,6 +105,7 @@ bool ledcAttachChannel(uint8_t pin, uint32_t freq, uint8_t resolution, uint8_t c uint8_t group = (channel / 8), timer = ((channel / 2) % 4); bool channel_used = ledc_handle.used_channels & (1UL << channel); if (channel_used) { + 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) { log_e("Attaching pin to already used channel failed!"); return false; @@ -135,6 +136,7 @@ bool ledcAttachChannel(uint8_t pin, uint32_t freq, uint8_t resolution, uint8_t c //get resolution of selected channel when used if (channel_used) { uint32_t channel_resolution = 0; + log_i("Channel %u frequency: %u, resolution: %u", channel, ledc_get_freq(group, timer), channel_resolution); ledc_ll_get_duty_resolution(LEDC_LL_GET_HW(), group, timer, &channel_resolution); handle->channel_resolution = (uint8_t)channel_resolution; } From 3fc241dfc0d203f6faea41f3b9b2c06f4f57a4ad Mon Sep 17 00:00:00 2001 From: Jan Prochazka <90197375+P-R-O-C-H-Y@users.noreply.github.com> Date: Mon, 15 Jul 2024 15:08:56 +0200 Subject: [PATCH 4/8] docs(ledc): Add ledcWriteChannel function and attach update --- docs/en/api/ledc.rst | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/docs/en/api/ledc.rst b/docs/en/api/ledc.rst index 908165717f1..b7fd7b25537 100644 --- a/docs/en/api/ledc.rst +++ b/docs/en/api/ledc.rst @@ -45,6 +45,7 @@ ledcAttachChannel ***************** This function is used to setup LEDC pin with given frequency, resolution and channel. +Attaching multiple pins to the same channel will make them share the same duty cycle. Given frequency, resolution will be ignored if channel is already configured. .. code-block:: arduino @@ -76,6 +77,21 @@ This function is used to set duty for the LEDC pin. This function will return ``true`` if setting duty is successful. If ``false`` is returned, error occurs and duty was not set. +ledcWriteChannel +**************** + +This function is used to set duty for the LEDC channel. + +.. code-block:: arduino + + bool ledcWriteChannel(uint8_t channel, uint32_t duty); + +* ``channel`` select LEDC channel. +* ``duty`` select duty to be set for selected LEDC channel. + +This function will return ``true`` if setting duty is successful. +If ``false`` is returned, error occurs and duty was not set. + ledcRead ******** From 297f132d6fe6f3fe9fc6cd3cf39234dd2b1987a9 Mon Sep 17 00:00:00 2001 From: Jan Prochazka <90197375+P-R-O-C-H-Y@users.noreply.github.com> Date: Mon, 15 Jul 2024 15:27:16 +0200 Subject: [PATCH 5/8] feat(ledc): Add example and fixes --- cores/esp32/esp32-hal-ledc.c | 4 +- .../LEDCSingleChannel/LEDCSingleChannel.ino | 50 +++++++++++++++++++ 2 files changed, 52 insertions(+), 2 deletions(-) create mode 100644 libraries/ESP32/examples/AnalogOut/LEDCSingleChannel/LEDCSingleChannel.ino diff --git a/cores/esp32/esp32-hal-ledc.c b/cores/esp32/esp32-hal-ledc.c index 74b6a8cccc1..37ddfaee440 100644 --- a/cores/esp32/esp32-hal-ledc.c +++ b/cores/esp32/esp32-hal-ledc.c @@ -65,10 +65,10 @@ static bool ledcDetachBus(void *bus) { } } pinMatrixOutDetach(handle->pin, false, false); - free(handle); if (!channel_found) { ledc_handle.used_channels &= ~(1UL << handle->channel); } + free(handle); if (ledc_handle.used_channels == 0) { ledc_fade_func_uninstall(); fade_initialized = false; @@ -136,8 +136,8 @@ bool ledcAttachChannel(uint8_t pin, uint32_t freq, uint8_t resolution, uint8_t c //get resolution of selected channel when used if (channel_used) { uint32_t channel_resolution = 0; - log_i("Channel %u frequency: %u, resolution: %u", channel, ledc_get_freq(group, timer), channel_resolution); ledc_ll_get_duty_resolution(LEDC_LL_GET_HW(), group, timer, &channel_resolution); + log_i("Channel %u frequency: %u, resolution: %u", channel, ledc_get_freq(group, timer), channel_resolution); handle->channel_resolution = (uint8_t)channel_resolution; } else { diff --git a/libraries/ESP32/examples/AnalogOut/LEDCSingleChannel/LEDCSingleChannel.ino b/libraries/ESP32/examples/AnalogOut/LEDCSingleChannel/LEDCSingleChannel.ino new file mode 100644 index 00000000000..82e74c07895 --- /dev/null +++ b/libraries/ESP32/examples/AnalogOut/LEDCSingleChannel/LEDCSingleChannel.ino @@ -0,0 +1,50 @@ +/* + LEDC Software Fade on shared channel with multiple pins + + This example shows how to software fade LED + using the ledcWriteChannel function on multiple pins. + This example is useful if you need to control synchronously + multiple LEDs on different pins. + + Code adapted from original Arduino Fade example: + https://www.arduino.cc/en/Tutorial/Fade + + This example code is in the public domain. + */ + +// use 8 bit precision for LEDC timer +#define LEDC_TIMER_8_BIT 8 + +// use 5000 Hz as a LEDC base frequency +#define LEDC_BASE_FREQ 5000 + +// LED pins +#define LED_PIN_1 4 +#define LED_PIN_2 5 + +// LED channel that will be used instead of automatic selection. +#define LEDC_CHANNEL 0 + +int brightness = 0; // how bright the LED is +int fadeAmount = 5; // how many points to fade the LED by + +void setup() { + // Use single LEDC channel 0 for both pins + ledcAttachChannel(LED_PIN_1, LEDC_BASE_FREQ, LEDC_TIMER_8_BIT, LEDC_CHANNEL); + ledcAttachChannel(LED_PIN_2, LEDC_BASE_FREQ, LEDC_TIMER_8_BIT, LEDC_CHANNEL); +} + +void loop() { + // set the brightness on LEDC channel 0 + ledcWriteChannel(LEDC_CHANNEL, brightness); + + // change the brightness for next time through the loop: + brightness = brightness + fadeAmount; + + // reverse the direction of the fading at the ends of the fade: + if (brightness <= 0 || brightness >= 255) { + fadeAmount = -fadeAmount; + } + // wait for 30 milliseconds to see the dimming effect + delay(30); +} \ No newline at end of file From 7bbdb4e582997861ef4d219782c6efce6486d204 Mon Sep 17 00:00:00 2001 From: Jan Prochazka <90197375+P-R-O-C-H-Y@users.noreply.github.com> Date: Mon, 15 Jul 2024 15:59:18 +0200 Subject: [PATCH 6/8] feat(ledc): Remove commented code --- cores/esp32/esp32-hal-ledc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cores/esp32/esp32-hal-ledc.c b/cores/esp32/esp32-hal-ledc.c index 37ddfaee440..aee517f428b 100644 --- a/cores/esp32/esp32-hal-ledc.c +++ b/cores/esp32/esp32-hal-ledc.c @@ -77,7 +77,7 @@ static bool ledcDetachBus(void *bus) { } bool ledcAttachChannel(uint8_t pin, uint32_t freq, uint8_t resolution, uint8_t channel) { - if (channel >= LEDC_CHANNELS) { //|| ledc_handle.used_channels & (1UL << channel)) { + if (channel >= LEDC_CHANNELS) { log_e("Channel %u is not available (maximum %u)!", channel, LEDC_CHANNELS); return false; } From bc694ce3900c3e0808ea9f46ae5a73460470861b Mon Sep 17 00:00:00 2001 From: Jan Prochazka <90197375+P-R-O-C-H-Y@users.noreply.github.com> Date: Mon, 15 Jul 2024 16:00:54 +0200 Subject: [PATCH 7/8] feat(ledc): Fix missing new line at end of file --- .../examples/AnalogOut/LEDCSingleChannel/LEDCSingleChannel.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/ESP32/examples/AnalogOut/LEDCSingleChannel/LEDCSingleChannel.ino b/libraries/ESP32/examples/AnalogOut/LEDCSingleChannel/LEDCSingleChannel.ino index 82e74c07895..abe3a5d897d 100644 --- a/libraries/ESP32/examples/AnalogOut/LEDCSingleChannel/LEDCSingleChannel.ino +++ b/libraries/ESP32/examples/AnalogOut/LEDCSingleChannel/LEDCSingleChannel.ino @@ -47,4 +47,4 @@ void loop() { } // wait for 30 milliseconds to see the dimming effect delay(30); -} \ No newline at end of file +} From 4dbde964c13420d8b729141c9d9477076f59d6d8 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Tue, 16 Jul 2024 12:06:25 +0000 Subject: [PATCH 8/8] ci(pre-commit): Apply automatic fixes --- cores/esp32/esp32-hal-ledc.c | 10 ++++------ .../AnalogOut/LEDCSingleChannel/LEDCSingleChannel.ino | 2 +- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/cores/esp32/esp32-hal-ledc.c b/cores/esp32/esp32-hal-ledc.c index aee517f428b..eb2e51382c9 100644 --- a/cores/esp32/esp32-hal-ledc.c +++ b/cores/esp32/esp32-hal-ledc.c @@ -110,8 +110,7 @@ bool ledcAttachChannel(uint8_t pin, uint32_t freq, uint8_t resolution, uint8_t c log_e("Attaching pin to already used channel failed!"); return false; } - } - else { + } else { ledc_timer_config_t ledc_timer = {.speed_mode = group, .timer_num = timer, .duty_resolution = resolution, .freq_hz = freq, .clk_cfg = LEDC_DEFAULT_CLK}; if (ledc_timer_config(&ledc_timer) != ESP_OK) { log_e("ledc setup failed!"); @@ -139,8 +138,7 @@ bool ledcAttachChannel(uint8_t pin, uint32_t freq, uint8_t resolution, uint8_t c ledc_ll_get_duty_resolution(LEDC_LL_GET_HW(), group, timer, &channel_resolution); log_i("Channel %u frequency: %u, resolution: %u", channel, ledc_get_freq(group, timer), channel_resolution); handle->channel_resolution = (uint8_t)channel_resolution; - } - else { + } else { handle->channel_resolution = resolution; ledc_handle.used_channels |= 1UL << channel; } @@ -186,7 +184,7 @@ bool ledcWrite(uint8_t pin, uint32_t duty) { return false; } -bool ledcWriteChannel(uint8_t channel, uint32_t duty){ +bool ledcWriteChannel(uint8_t channel, uint32_t duty) { //check if channel is valid and used if (channel >= LEDC_CHANNELS || !(ledc_handle.used_channels & (1UL << channel))) { log_e("Channel %u is not available (maximum %u) or not used!", channel, LEDC_CHANNELS); @@ -197,7 +195,7 @@ bool ledcWriteChannel(uint8_t channel, uint32_t duty){ //Fixing if all bits in resolution is set = LEDC FULL ON uint32_t resolution = 0; ledc_ll_get_duty_resolution(LEDC_LL_GET_HW(), group, timer, &resolution); - + uint32_t max_duty = (1 << resolution) - 1; if ((duty == max_duty) && (max_duty != 1)) { diff --git a/libraries/ESP32/examples/AnalogOut/LEDCSingleChannel/LEDCSingleChannel.ino b/libraries/ESP32/examples/AnalogOut/LEDCSingleChannel/LEDCSingleChannel.ino index abe3a5d897d..2317e32a11a 100644 --- a/libraries/ESP32/examples/AnalogOut/LEDCSingleChannel/LEDCSingleChannel.ino +++ b/libraries/ESP32/examples/AnalogOut/LEDCSingleChannel/LEDCSingleChannel.ino @@ -3,7 +3,7 @@ This example shows how to software fade LED using the ledcWriteChannel function on multiple pins. - This example is useful if you need to control synchronously + This example is useful if you need to control synchronously multiple LEDs on different pins. Code adapted from original Arduino Fade example: