Skip to content

Commit

Permalink
feat(ledc): Allow attaching multiple pins to 1 channel (#10032)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
P-R-O-C-H-Y and pre-commit-ci-lite[bot] committed Jul 16, 2024
1 parent e850afb commit 3c1e5a9
Show file tree
Hide file tree
Showing 4 changed files with 151 additions and 17 deletions.
92 changes: 75 additions & 17 deletions cores/esp32/esp32-hal-ledc.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -48,8 +49,25 @@ 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);
if (!channel_found) {
ledc_handle.used_channels &= ~(1UL << handle->channel);
}
free(handle);
if (ledc_handle.used_channels == 0) {
ledc_fade_func_uninstall();
Expand All @@ -59,8 +77,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) {
log_e("Channel %u is not available (maximum %u)!", channel, LEDC_CHANNELS);
return false;
}
if (freq == 0) {
Expand All @@ -85,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);
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;
}
} 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;
}

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);
log_i("Channel %u frequency: %u, resolution: %u", channel, ledc_get_freq(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);
Expand Down Expand Up @@ -150,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) {
Expand Down
10 changes: 10 additions & 0 deletions cores/esp32/esp32-hal-ledc.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down
16 changes: 16 additions & 0 deletions docs/en/api/ledc.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
********

Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}

0 comments on commit 3c1e5a9

Please sign in to comment.