Skip to content

Commit 3c1e5a9

Browse files
feat(ledc): Allow attaching multiple pins to 1 channel (espressif#10032)
* feat(ledc): Allow attaching multiple pins to 1 channel * feat(ledc): Add ledcWriteChannel function * feat(ledc): Print info about already set channel * docs(ledc): Add ledcWriteChannel function and attach update * feat(ledc): Add example and fixes * feat(ledc): Remove commented code * feat(ledc): Fix missing new line at end of file * ci(pre-commit): Apply automatic fixes --------- Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
1 parent e850afb commit 3c1e5a9

File tree

4 files changed

+151
-17
lines changed

4 files changed

+151
-17
lines changed

cores/esp32/esp32-hal-ledc.c

+75-17
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include "esp32-hal-periman.h"
2222
#include "soc/gpio_sig_map.h"
2323
#include "esp_rom_gpio.h"
24+
#include "hal/ledc_ll.h"
2425

2526
#ifdef SOC_LEDC_SUPPORT_HS_MODE
2627
#define LEDC_CHANNELS (SOC_LEDC_CHANNEL_NUM << 1)
@@ -48,8 +49,25 @@ static bool fade_initialized = false;
4849

4950
static bool ledcDetachBus(void *bus) {
5051
ledc_channel_handle_t *handle = (ledc_channel_handle_t *)bus;
51-
ledc_handle.used_channels &= ~(1UL << handle->channel);
52+
bool channel_found = false;
53+
// Check if more pins are attached to the same ledc channel
54+
for (uint8_t i = 0; i < SOC_GPIO_PIN_COUNT; i++) {
55+
if (!perimanPinIsValid(i)) {
56+
continue; //invalid pin, skip
57+
}
58+
peripheral_bus_type_t type = perimanGetPinBusType(i);
59+
if (type == ESP32_BUS_TYPE_LEDC) {
60+
ledc_channel_handle_t *bus_check = (ledc_channel_handle_t *)perimanGetPinBus(i, ESP32_BUS_TYPE_LEDC);
61+
if (bus_check->channel == handle->channel) {
62+
channel_found = true;
63+
break;
64+
}
65+
}
66+
}
5267
pinMatrixOutDetach(handle->pin, false, false);
68+
if (!channel_found) {
69+
ledc_handle.used_channels &= ~(1UL << handle->channel);
70+
}
5371
free(handle);
5472
if (ledc_handle.used_channels == 0) {
5573
ledc_fade_func_uninstall();
@@ -59,8 +77,8 @@ static bool ledcDetachBus(void *bus) {
5977
}
6078

6179
bool ledcAttachChannel(uint8_t pin, uint32_t freq, uint8_t resolution, uint8_t channel) {
62-
if (channel >= LEDC_CHANNELS || ledc_handle.used_channels & (1UL << channel)) {
63-
log_e("Channel %u is not available (maximum %u) or already used!", channel, LEDC_CHANNELS);
80+
if (channel >= LEDC_CHANNELS) {
81+
log_e("Channel %u is not available (maximum %u)!", channel, LEDC_CHANNELS);
6482
return false;
6583
}
6684
if (freq == 0) {
@@ -85,29 +103,45 @@ bool ledcAttachChannel(uint8_t pin, uint32_t freq, uint8_t resolution, uint8_t c
85103
}
86104

87105
uint8_t group = (channel / 8), timer = ((channel / 2) % 4);
106+
bool channel_used = ledc_handle.used_channels & (1UL << channel);
107+
if (channel_used) {
108+
log_i("Channel %u is already set up, given frequency and resolution will be ignored", channel);
109+
if (ledc_set_pin(pin, group, channel % 8) != ESP_OK) {
110+
log_e("Attaching pin to already used channel failed!");
111+
return false;
112+
}
113+
} else {
114+
ledc_timer_config_t ledc_timer = {.speed_mode = group, .timer_num = timer, .duty_resolution = resolution, .freq_hz = freq, .clk_cfg = LEDC_DEFAULT_CLK};
115+
if (ledc_timer_config(&ledc_timer) != ESP_OK) {
116+
log_e("ledc setup failed!");
117+
return false;
118+
}
88119

89-
ledc_timer_config_t ledc_timer = {.speed_mode = group, .timer_num = timer, .duty_resolution = resolution, .freq_hz = freq, .clk_cfg = LEDC_DEFAULT_CLK};
90-
if (ledc_timer_config(&ledc_timer) != ESP_OK) {
91-
log_e("ledc setup failed!");
92-
return false;
93-
}
94-
95-
uint32_t duty = ledc_get_duty(group, (channel % 8));
120+
uint32_t duty = ledc_get_duty(group, (channel % 8));
96121

97-
ledc_channel_config_t ledc_channel = {
98-
.speed_mode = group, .channel = (channel % 8), .timer_sel = timer, .intr_type = LEDC_INTR_DISABLE, .gpio_num = pin, .duty = duty, .hpoint = 0
99-
};
100-
ledc_channel_config(&ledc_channel);
122+
ledc_channel_config_t ledc_channel = {
123+
.speed_mode = group, .channel = (channel % 8), .timer_sel = timer, .intr_type = LEDC_INTR_DISABLE, .gpio_num = pin, .duty = duty, .hpoint = 0
124+
};
125+
ledc_channel_config(&ledc_channel);
126+
}
101127

102128
ledc_channel_handle_t *handle = (ledc_channel_handle_t *)malloc(sizeof(ledc_channel_handle_t));
103-
104129
handle->pin = pin;
105130
handle->channel = channel;
106-
handle->channel_resolution = resolution;
107131
#ifndef SOC_LEDC_SUPPORT_FADE_STOP
108132
handle->lock = NULL;
109133
#endif
110-
ledc_handle.used_channels |= 1UL << channel;
134+
135+
//get resolution of selected channel when used
136+
if (channel_used) {
137+
uint32_t channel_resolution = 0;
138+
ledc_ll_get_duty_resolution(LEDC_LL_GET_HW(), group, timer, &channel_resolution);
139+
log_i("Channel %u frequency: %u, resolution: %u", channel, ledc_get_freq(group, timer), channel_resolution);
140+
handle->channel_resolution = (uint8_t)channel_resolution;
141+
} else {
142+
handle->channel_resolution = resolution;
143+
ledc_handle.used_channels |= 1UL << channel;
144+
}
111145

112146
if (!perimanSetPinBus(pin, ESP32_BUS_TYPE_LEDC, (void *)handle, group, channel)) {
113147
ledcDetachBus((void *)handle);
@@ -150,6 +184,30 @@ bool ledcWrite(uint8_t pin, uint32_t duty) {
150184
return false;
151185
}
152186

187+
bool ledcWriteChannel(uint8_t channel, uint32_t duty) {
188+
//check if channel is valid and used
189+
if (channel >= LEDC_CHANNELS || !(ledc_handle.used_channels & (1UL << channel))) {
190+
log_e("Channel %u is not available (maximum %u) or not used!", channel, LEDC_CHANNELS);
191+
return false;
192+
}
193+
uint8_t group = (channel / 8), timer = ((channel / 2) % 4);
194+
195+
//Fixing if all bits in resolution is set = LEDC FULL ON
196+
uint32_t resolution = 0;
197+
ledc_ll_get_duty_resolution(LEDC_LL_GET_HW(), group, timer, &resolution);
198+
199+
uint32_t max_duty = (1 << resolution) - 1;
200+
201+
if ((duty == max_duty) && (max_duty != 1)) {
202+
duty = max_duty + 1;
203+
}
204+
205+
ledc_set_duty(group, channel, duty);
206+
ledc_update_duty(group, channel);
207+
208+
return true;
209+
}
210+
153211
uint32_t ledcRead(uint8_t pin) {
154212
ledc_channel_handle_t *bus = (ledc_channel_handle_t *)perimanGetPinBus(pin, ESP32_BUS_TYPE_LEDC);
155213
if (bus != NULL) {

cores/esp32/esp32-hal-ledc.h

+10
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,16 @@ bool ledcAttachChannel(uint8_t pin, uint32_t freq, uint8_t resolution, uint8_t c
9191
*/
9292
bool ledcWrite(uint8_t pin, uint32_t duty);
9393

94+
/**
95+
* @brief Set the duty cycle of a given channel.
96+
*
97+
* @param channel LEDC channel
98+
* @param duty duty cycle to set
99+
*
100+
* @return true if duty cycle was successfully set, false otherwise.
101+
*/
102+
bool ledcWriteChannel(uint8_t channel, uint32_t duty);
103+
94104
/**
95105
* @brief Sets the duty to 50 % PWM tone on selected frequency.
96106
*

docs/en/api/ledc.rst

+16
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ ledcAttachChannel
4545
*****************
4646

4747
This function is used to setup LEDC pin with given frequency, resolution and channel.
48+
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.
4849

4950
.. code-block:: arduino
5051
@@ -76,6 +77,21 @@ This function is used to set duty for the LEDC pin.
7677
This function will return ``true`` if setting duty is successful.
7778
If ``false`` is returned, error occurs and duty was not set.
7879

80+
ledcWriteChannel
81+
****************
82+
83+
This function is used to set duty for the LEDC channel.
84+
85+
.. code-block:: arduino
86+
87+
bool ledcWriteChannel(uint8_t channel, uint32_t duty);
88+
89+
* ``channel`` select LEDC channel.
90+
* ``duty`` select duty to be set for selected LEDC channel.
91+
92+
This function will return ``true`` if setting duty is successful.
93+
If ``false`` is returned, error occurs and duty was not set.
94+
7995
ledcRead
8096
********
8197

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
LEDC Software Fade on shared channel with multiple pins
3+
4+
This example shows how to software fade LED
5+
using the ledcWriteChannel function on multiple pins.
6+
This example is useful if you need to control synchronously
7+
multiple LEDs on different pins.
8+
9+
Code adapted from original Arduino Fade example:
10+
https://www.arduino.cc/en/Tutorial/Fade
11+
12+
This example code is in the public domain.
13+
*/
14+
15+
// use 8 bit precision for LEDC timer
16+
#define LEDC_TIMER_8_BIT 8
17+
18+
// use 5000 Hz as a LEDC base frequency
19+
#define LEDC_BASE_FREQ 5000
20+
21+
// LED pins
22+
#define LED_PIN_1 4
23+
#define LED_PIN_2 5
24+
25+
// LED channel that will be used instead of automatic selection.
26+
#define LEDC_CHANNEL 0
27+
28+
int brightness = 0; // how bright the LED is
29+
int fadeAmount = 5; // how many points to fade the LED by
30+
31+
void setup() {
32+
// Use single LEDC channel 0 for both pins
33+
ledcAttachChannel(LED_PIN_1, LEDC_BASE_FREQ, LEDC_TIMER_8_BIT, LEDC_CHANNEL);
34+
ledcAttachChannel(LED_PIN_2, LEDC_BASE_FREQ, LEDC_TIMER_8_BIT, LEDC_CHANNEL);
35+
}
36+
37+
void loop() {
38+
// set the brightness on LEDC channel 0
39+
ledcWriteChannel(LEDC_CHANNEL, brightness);
40+
41+
// change the brightness for next time through the loop:
42+
brightness = brightness + fadeAmount;
43+
44+
// reverse the direction of the fading at the ends of the fade:
45+
if (brightness <= 0 || brightness >= 255) {
46+
fadeAmount = -fadeAmount;
47+
}
48+
// wait for 30 milliseconds to see the dimming effect
49+
delay(30);
50+
}

0 commit comments

Comments
 (0)