Updated example and descritpion of LED PWM Controller API

This commit is contained in:
krzychb 2017-09-18 07:08:01 +02:00
parent de750e9921
commit d60722c33d
4 changed files with 279 additions and 101 deletions

BIN
docs/_static/ledc-api-settings.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View file

@ -1,18 +1,172 @@
LED Control
===========
Overview
--------
Introduction
------------
The LED control (LEDC) module is primarily designed to control the intensity of LEDs, although it can be used to generate PWM signals for other purposes as well. It has 16 channels which can generate independent waveforms, that can be used to drive e.g. RGB LED devices.
Half of all LEDC's channels provide high speed mode of operation. This mode offers implemented in hardware, automatic and glitch free change of PWM duty cycle. The other half of channels operate in a low speed mode, where the moment of change depends on the application software. Each group of channels is also able to use different clock sources but this feature is not implemented in the API.
The PWM controller also has the ability to automatically increase or decrease the duty cycle gradually, allowing for fades without any processor interference.
Functionality Overview
----------------------
Getting LEDC to work on specific channel in either :ref:`high or low speed mode <ledc-api-high_low_speed_mode>` is done in three steps:
1. :ref:`ledc-api-configure-timer` to determine PWM signal's frequency and the a number (resolution of duty range).
2. :ref:`ledc-api-configure-channel` by associating it with the timer and GPIO to output the PWM signal.
3. :ref:`ledc-api-change-pwm-signal` that drives the output to change LED's intensity. This may be done under full control by software or with help of hardware fading functions.
In an optional step it is also possible to set up an interrupt on the fade end.
.. figure:: ../../_static/ledc-api-settings.jpg
:align: center
:alt: Key Settings of LED PWM Controller's API
:figclass: align-center
Key Settings of LED PWM Controller's API
.. _ledc-api-configure-timer:
Configure Timer
^^^^^^^^^^^^^^^
Setting of the timer is done by calling function :cpp:func:`ledc_timer_config`. This function should be provided with a data structure :cpp:type:`ledc_timer_config_t` that contains the following configuration settings:
* The timer number :cpp:type:`ledc_timer_t` and a speed mode :cpp:type:`ledc_mode_t`.
* The PWM signal's frequency and a "bit number". The later represents a resolution of PWM's duty value changes.
The frequency and the bit values are interdependent. The higher the PWM frequency, the lower bit number is available and vice versa. This relationship may became important, if you are planning to use this API for purposes other that changing intensity of LEDs. Check section :ref:`ledc-api-supported-range-frequency-bit-number` for more details.
.. _ledc-api-configure-channel:
Configure Channel
^^^^^^^^^^^^^^^^^
Having set up the timer, the next step is to configure selected channel (one out of :cpp:type:`ledc_channel_t`). This is done by calling function :cpp:func:`ledc_channel_config`.
In similar way, like with the timer configuration, the channel setup function should be provided with specific structure :cpp:type:`ledc_channel_config_t`, that contains channel's configuration parameters.
At this point channel should became operational and start generating PWM signal of frequency determined by the timer settings and the duty on selected GPIO, as configured in :cpp:type:`ledc_channel_config_t`. The channel operation / the signal generation may be suspended at any time by calling function :cpp:func:`ledc_stop`.
.. _ledc-api-change-pwm-signal:
Change PWM Signal
^^^^^^^^^^^^^^^^^
Once the channel is operational and generating the PWM signal of constant duty and frequency, there are couple of ways to change this signal. When driving LEDs we are changing primarily the duty to vary the light intensity. See the two section below how to change the duty by software or with hardware fading. If required, we can change signal's frequency as well and this is covered in section :ref:`ledc-api-change-pwm-frequency`.
Change PWM Duty by Software
"""""""""""""""""""""""""""
Setting of the duty is done by first calling dedicated function :cpp:func:`ledc_set_duty` and then calling :cpp:func:`ledc_update_duty` to make the change effective. To check the value currently set, there is a corresponding ``_get_`` function :cpp:func:`ledc_get_duty`.
Another way to set the duty, and some other channel parameters as well, is by calling :cpp:func:`ledc_channel_config` discussed in the previous section.
The range of the duty value entered into functions depends on selected bit number (`` bit_num``) and should be from 0 to (2 ** bit_num) - 1. For example, if selected bit number is 10, then the duty range is from 0 to 1023. This provides the duty resolution of ~0.1%.
Change PWM Duty with Hardware Fading
""""""""""""""""""""""""""""""""""""
The LEDC hardware provides the means to gradually fade from one duty value to another. To use this functionality first enable fading with :cpp:func:`ledc_fade_func_install`. Then configure it by calling one of available fading functions:
* :cpp:func:`ledc_set_fade_with_time`
* :cpp:func:`ledc_set_fade_with_step`
* :cpp:func:`ledc_set_fade`
Finally start fading with :cpp:func:`ledc_fade_start`.
If not required anymore, fading and associated interrupt may be disabled with :cpp:func:`ledc_fade_func_uninstall`.
.. _ledc-api-change-pwm-frequency:
Change PWM Frequency
""""""""""""""""""""
The LEDC API provides several means to change the PWM frequency "on the fly".
* One of options is to call :cpp:func:`ledc_set_freq`. There is a corresponding function :cpp:func:`ledc_get_freq` to check what frequency is currently set.
* Another option to change the frequency, and the bit number as well, is by calling :cpp:func:`ledc_bind_channel_timer` to bind other timer to the channel.
* Finally the channel's timer may be changed by calling :cpp:func:`ledc_channel_config`.
More Control Over PWM
"""""""""""""""""""""
There are couple of lower level timer specific functions, that may be used to provide additional means to change the PWM settings:
* :cpp:func:`ledc_timer_set`
* :cpp:func:`ledc_timer_rst`
* :cpp:func:`ledc_timer_pause`
* :cpp:func:`ledc_timer_resume`
The first two functions are called "behind the scenes" by :cpp:func:`ledc_channel_config` to provide "clean" start up of a timer after is it configured.
Use Interrupts
^^^^^^^^^^^^^^
When configuring a LEDC channel, one of parameters selected within :cpp:type:`ledc_channel_config_t` is :cpp:type:`ledc_intr_type_t` and allows to enable an interrupt on fade completion.
Registration of a handler to service this interrupt is done by calling :cpp:func:`ledc_isr_register`.
.. _ledc-api-high_low_speed_mode:
LEDC High and Low Speed Mode
----------------------------
Out of the total 8 timers and 16 channels available in the LED PWM Controller, half of them are dedicated to operate in the high speed mode and the other half in the low speed mode. Selection of the low or high speed "capable" timer or the channel is done with parameter :cpp:type:`ledc_mode_t` that is present in applicable function calls.
The advantage of the high speed mode is h/w supported, glitch-free changeover of the timer settings. This means that if the timer settings are modified, the changes will be applied automatically after the next overflow interrupt of the timer. In contrast, when updating the low-speed timer, the change of settings should be specifically triggered by software. The LEDC API is doing it "behind the scenes", e.g. when :cpp:func:`ledc_timer_config` or :cpp:func:`ledc_timer_set` is called.
For additional details regarding speed modes please refer to `ESP32 Technical Reference Manual <https://espressif.com/sites/default/files/documentation/esp32_technical_reference_manual_en.pdf>`_ (PDF). Note that support for ``SLOW_CLOCK`` mentioned in this manual is not implemented in the LEDC API.
.. _ledc-api-supported-range-frequency-bit-number:
Supported Range of Frequency and Bit Number
-------------------------------------------
The LED PWM Controller is designed primarily to drive LEDs and provides wide resolution of PWM duty settings. The resolution (or granularity) of the PWM duty is determined by the "bit number". For instance for the PWM frequency at 5 kHz, the maximum bit number is 13 bits. It means that the duty may be set anywhere from 0 to 100% with resolution of ~0.012% (13 ** 2 = 8192 discrete levels of the LED intensity).
The LEDC may be used for providing signals at much higher frequencies to clock other devices, e.g. a digital camera module. In such a case the maximum available frequency is 40 MHz with duty resolution of 1 bit. This means that duty is fixed at 50% and cannot be adjusted.
The API is designed to report an error when trying to set a frequency and a bit number that is out of the range of LEDC's hardware. For example, an attempt to set the frequency at 20 MHz and the bit number of 3 bits will result in the following error reported on a serial monitor:
.. highlight:: none
::
E (196) ledc: requested frequency and bit number can not be achieved, try reducing freq_hz or bit_num. div_param=128
In such a case either the bit number or the frequency should be reduced. For example setting the bit number at 2 will resolve this issue and provide possibility to set the duty with 25% steps, i.e. at 25%, 50% or 75%.
The LEDC API will also capture and report an attempt to configure frequency / bit number combination that is below the supported minimum, e.g.:
::
E (196) ledc: requested frequency and bit depth can not be achieved, try increasing freq_hz or bit_num. div_param=128000000
Setting of the bit number is normally done using :cpp:type:`ledc_timer_bit_t`. This enumeration covers the range from 10 to 15 bits. If a smaller bit number is required (below 10 down to 1), enter the equivalent numeric values directly.
The LED control module is primarily designed to control the intensity of LEDs, although it can be used to generate PWM signals for other purposes as well.
It has 16 channels which can generate independent waveforms that can be used to drive e.g. RGB LED devices. For maximum flexibility, the high-speed as well
as the low-speed channels can be driven from one of four high-speed/low-speed timers. The PWM controller also has the ability to automatically increase or
decrease the duty cycle gradually, allowing for fades without any processor interference.
Application Example
-------------------
LEDC change duty cycle and fading control example: :example:`peripherals/ledc`.
The LEDC change duty cycle and fading control example: :example:`peripherals/ledc`.
API Reference
-------------

View file

@ -1,17 +1,30 @@
# LEDC(LED control) Example
# LEDC (LED PWM Controller) Example
###This example shows:
This example shows how to control intensity of LEDs using ESP32's on-board hardware LED PWM Controller module.
* init LEDC module:
a. You need to set the timer of LEDC first, this decide the frequency and resolution of PWM.
b. You need to set the LEDC channel you want to use, and bind the channel with one of the timers.
* You can install a default fade function, then you can use fade APIs.
* You can also set a target duty directly without fading.
* This example use GPIO18/19/4/5 as LEDC ouput, and it will change the duty repeatedly.
## Functionality
Operations performed by example:
* Configuration of two timers (one high speed and the other low speed) that will be clocking four LEDC channels.
* Configuration of four channels of LEDC module, two channels will operate in high speed mode and two in low speed mode. Each channel will drive one GPIO / LED.
* Initialization of fade service to fade / gradually change intensity of LEDs.
* Operation of channels in a loop by cycling through four steps that will drive LEDs as follows:
1. Fade up / increase intensity
2. Fade down / decrease intensity (down to zero)
3. Set steady intensity
4. Set intensity to zero
## Hardware Setup
Connect four LEDs to the following LEDC channels / individual GPIOs:
* Channel 0 - GPIO18
* Channel 1 - GPIO19
* Channel 2 - GPIO4
* Channel 3 - GPIO5
* GPIO18/19 are from high speed channel group. GPIO4/5 are from low speed channel group.

View file

@ -1,4 +1,4 @@
/* Ledc fade example
/* LEDC (LED Controller) fade example
This example code is in the Public Domain (or CC0 licensed, at your option.)
@ -9,25 +9,28 @@
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/xtensa_api.h"
#include "freertos/queue.h"
#include "driver/ledc.h"
#include "esp_attr.h"
#include "esp_err.h"
/*
* About this example
* 1. init LEDC module:
* a. You need to set the timer of LEDC first, this decide the frequency and resolution of PWM.
* b. You need to set the LEDC channel you want to use, and bind with one of the timers.
*
* 2. You can install a default fade function, then you can use fade APIs.
* 1. Start with initializing LEDC module:
* a. Set the timer of LEDC first, this determines the frequency
* and resolution of PWM.
* b. Then set the LEDC channel you want to use,
* and bind with one of the timers.
*
* 2. You need first to install a default fade function,
* then you can use fade APIs.
*
* 3. You can also set a target duty directly without fading.
*
* 4. This example use GPIO18/19/4/5 as LEDC ouput, and it will change the duty repeatedly.
* 4. This example uses GPIO18/19/4/5 as LEDC output,
* and it will change the duty repeatedly.
*
* 5. GPIO18/19 are from high speed channel group. GPIO4/5 are from low speed channel group.
* 5. GPIO18/19 are from high speed channel group.
* GPIO4/5 are from low speed channel group.
*
*/
#define LEDC_HS_TIMER LEDC_TIMER_0
@ -45,105 +48,113 @@
#define LEDC_LS_CH3_CHANNEL LEDC_CHANNEL_3
#define LEDC_TEST_CH_NUM (4)
typedef struct {
int channel;
int io;
int mode;
int timer_idx;
} ledc_info_t;
#define LEDC_TEST_DUTY (4000)
#define LEDC_TEST_FADE_TIME (3000)
void app_main()
{
int ch;
ledc_info_t ledc_ch[LEDC_TEST_CH_NUM] = {
{
.channel = LEDC_HS_CH0_CHANNEL,
.io = LEDC_HS_CH0_GPIO,
.mode = LEDC_HS_MODE,
.timer_idx = LEDC_HS_TIMER
},
{
.channel = LEDC_HS_CH1_CHANNEL,
.io = LEDC_HS_CH1_GPIO,
.mode = LEDC_HS_MODE,
.timer_idx = LEDC_HS_TIMER
},
{
.channel = LEDC_LS_CH2_CHANNEL,
.io = LEDC_LS_CH2_GPIO,
.mode = LEDC_LS_MODE,
.timer_idx = LEDC_LS_TIMER
},
{
.channel = LEDC_LS_CH3_CHANNEL,
.io = LEDC_LS_CH3_GPIO,
.mode = LEDC_LS_MODE,
.timer_idx = LEDC_LS_TIMER
}
};
/*
* Prepare and set configuration of timers
* that will be used by LED Controller
*/
ledc_timer_config_t ledc_timer = {
.bit_num = LEDC_TIMER_13_BIT, //set timer counter bit number
.freq_hz = 5000, //set frequency of pwm
.speed_mode = LEDC_HS_MODE, //timer mode,
.timer_num = LEDC_HS_TIMER //timer index
.bit_num = LEDC_TIMER_13_BIT, // resolution of PWM duty
.freq_hz = 5000, // frequency of PWM signal
.speed_mode = LEDC_HS_MODE, // timer mode
.timer_num = LEDC_HS_TIMER // timer index
};
//configure timer0 for high speed channels
// Set configuration of timer0 for high speed channels
ledc_timer_config(&ledc_timer);
//configure timer1 for low speed channels
// Prepare and set configuration of timer1 for low speed channels
ledc_timer.speed_mode = LEDC_LS_MODE;
ledc_timer.timer_num = LEDC_LS_TIMER;
ledc_timer_config(&ledc_timer);
/*
* Prepare individual configuration
* for each channel of LED Controller
* by selecting:
* - controller's channel number
* - output duty cycle, set initially to 0
* - GPIO number where LED is connected to
* - speed mode, either high or low
* - timer servicing selected channel
* Note: if different channels use one timer,
* then frequency and bit_num of these channels
* will be the same
*/
ledc_channel_config_t ledc_channel[LEDC_TEST_CH_NUM] = {
{
.channel = LEDC_HS_CH0_CHANNEL,
.duty = 0,
.gpio_num = LEDC_HS_CH0_GPIO,
.speed_mode = LEDC_HS_MODE,
.timer_sel = LEDC_HS_TIMER
},
{
.channel = LEDC_HS_CH1_CHANNEL,
.duty = 0,
.gpio_num = LEDC_HS_CH1_GPIO,
.speed_mode = LEDC_HS_MODE,
.timer_sel = LEDC_HS_TIMER
},
{
.channel = LEDC_LS_CH2_CHANNEL,
.duty = 0,
.gpio_num = LEDC_LS_CH2_GPIO,
.speed_mode = LEDC_LS_MODE,
.timer_sel = LEDC_LS_TIMER
},
{
.channel = LEDC_LS_CH3_CHANNEL,
.duty = 0,
.gpio_num = LEDC_LS_CH3_GPIO,
.speed_mode = LEDC_LS_MODE,
.timer_sel = LEDC_LS_TIMER
},
};
// Set LED Controller with previously prepared configuration
for (ch = 0; ch < LEDC_TEST_CH_NUM; ch++) {
ledc_channel_config_t ledc_channel = {
//set LEDC channel 0
.channel = ledc_ch[ch].channel,
//set the duty for initialization.(duty range is 0 ~ ((2**bit_num)-1)
.duty = 0,
//GPIO number
.gpio_num = ledc_ch[ch].io,
//GPIO INTR TYPE, as an example, we enable fade_end interrupt here.
.intr_type = LEDC_INTR_FADE_END,
//set LEDC mode, from ledc_mode_t
.speed_mode = ledc_ch[ch].mode,
//set LEDC timer source, if different channel use one timer,
//the frequency and bit_num of these channels should be the same
.timer_sel = ledc_ch[ch].timer_idx,
};
//set the configuration
ledc_channel_config(&ledc_channel);
ledc_channel_config(&ledc_channel[ch]);
}
//initialize fade service.
// Initialize fade service.
ledc_fade_func_install(0);
while (1) {
printf("LEDC fade up\n");
printf("1. LEDC fade up to duty = %d\n", LEDC_TEST_DUTY);
for (ch = 0; ch < LEDC_TEST_CH_NUM; ch++) {
ledc_set_fade_with_time(ledc_ch[ch].mode, ledc_ch[ch].channel, 4000, 2000);
ledc_fade_start(ledc_ch[ch].mode, ledc_ch[ch].channel, LEDC_FADE_NO_WAIT);
ledc_set_fade_with_time(ledc_channel[ch].speed_mode,
ledc_channel[ch].channel, LEDC_TEST_DUTY, LEDC_TEST_FADE_TIME);
ledc_fade_start(ledc_channel[ch].speed_mode,
ledc_channel[ch].channel, LEDC_FADE_NO_WAIT);
}
vTaskDelay(3000 / portTICK_PERIOD_MS);
vTaskDelay(LEDC_TEST_FADE_TIME / portTICK_PERIOD_MS);
printf("LEDC fade down\n");
printf("2. LEDC fade down to duty = 0\n");
for (ch = 0; ch < LEDC_TEST_CH_NUM; ch++) {
ledc_set_fade_with_time(ledc_ch[ch].mode, ledc_ch[ch].channel, 0, 2000);
ledc_fade_start(ledc_ch[ch].mode, ledc_ch[ch].channel, LEDC_FADE_NO_WAIT);
ledc_set_fade_with_time(ledc_channel[ch].speed_mode,
ledc_channel[ch].channel, 0, LEDC_TEST_FADE_TIME);
ledc_fade_start(ledc_channel[ch].speed_mode,
ledc_channel[ch].channel, LEDC_FADE_NO_WAIT);
}
vTaskDelay(3000 / portTICK_PERIOD_MS);
vTaskDelay(LEDC_TEST_FADE_TIME / portTICK_PERIOD_MS);
printf("LEDC set duty without fade\n");
printf("3. LEDC set duty = %d without fade\n", LEDC_TEST_DUTY);
for (ch = 0; ch < LEDC_TEST_CH_NUM; ch++) {
ledc_set_duty(ledc_ch[ch].mode, ledc_ch[ch].channel, 2000);
ledc_update_duty(ledc_ch[ch].mode, ledc_ch[ch].channel);
ledc_set_duty(ledc_channel[ch].speed_mode, ledc_channel[ch].channel, LEDC_TEST_DUTY);
ledc_update_duty(ledc_channel[ch].speed_mode, ledc_channel[ch].channel);
}
vTaskDelay(1000 / portTICK_PERIOD_MS);
printf("LEDC set duty without fade\n");
printf("4. LEDC set duty = 0 without fade\n");
for (ch = 0; ch < LEDC_TEST_CH_NUM; ch++) {
ledc_set_duty(ledc_ch[ch].mode, ledc_ch[ch].channel, 0);
ledc_update_duty(ledc_ch[ch].mode, ledc_ch[ch].channel);
ledc_set_duty(ledc_channel[ch].speed_mode, ledc_channel[ch].channel, 0);
ledc_update_duty(ledc_channel[ch].speed_mode, ledc_channel[ch].channel);
}
vTaskDelay(1000 / portTICK_PERIOD_MS);
}