Objective
- The objective for this lab is to understand how use the Espressif
LEDC
and ADC
drivers. In this lab, create 2 task: adc and pwm task. The adc task
performs ADC readings every 100 millisecond (10 hertz). This task must store the adc readings and be sent through a queue. Lastly, pwm task
should receive the queue data from the adc task
and update ledc
duty cycle.
Task | Description | Queue |
adc_task | Read analog value @ 10 hz | send data |
pwm_task | Update duty cycle | receive data |
Bonus
- Undergrad Bonus
- Add
gpio
interrupt to start and stop PWM signal
- Grad Bonus
- Modifiy code to use 2 more PWM pins.
ESP32 Pinout
+-----------------------+
| O | USB | O |
| ------- |
3V3 | [ ] [ ] | VIN
GND | [ ] [ ] | GND
Touch3 / HSPI_CS0 / ADC2_3 / GPIO15 | [ ] [ ] | GPIO13 / ADC2_4 / HSPI_ID / Touch4
CS / Touch2 / HSPI_WP / ADC2_2 / GPIO2 | [ ] [ ] | GPIO12 / ADC2_5 / HSPI_Q / Touch5
Touch0 / HSPI_HD / ADC2_0 / GPIO4 | [ ] [ ] | GPIO14 / ADC2_6 / HSPI_CLK / Touch6
U2_RXD / GPIO16 | [ ] [ ] | GPIO27 / ADC2_7 / Touch7
U2_TXD / GPIO17 | [ ] [ ] | GPIO26 / ADC2_9 / DAC2
V_SPI_CS0 / GPIO5 | [ ] ___________ [ ] | GPIO25 / ADC2_8 / DAC1
SCK / V_SPI_CLK / GPIO18 | [ ] | | [ ] | GPIO33 / ADC1_5 / Touch8 / XTAL32
U0_CTS / MSIO / V_SPI_Q / GPIO19 | [ ] | | [ ] | GPIO32 / ADC1_4 / Touch9 / XTAL32
SDA / V_SPI_HD / GPIO21 | [ ] | | [ ] | GPIO35 / ADC1_7
CLK2 / U0_RXD / GPIO3 | [ ] | | [ ] | GPIO34 / ADC1_6
CLK3 / U0_TXD / GPIO1 | [ ] | | [ ] | GPIO39 / ADC1_3 / SensVN
SCL / U0_RTS / V_SPI_WP / GPIO22 | [ ] | | [ ] | GPIO36 / ADC1_0 / SensVP
MOSI / V_SPI_WP / GPIO23 | [ ] |___________| [ ] | EN
| |
| | | ____ ____ | |
| | | | | | | | |
| |__|__| |__| |__| |
| O O |
+-----------------------+
Example 1
Here is an example of a single ADC channel that will read and print value to the screen.
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include <driver/adc.h>
while(1){
int val = adc1_get_raw(ADC1_CHANNEL_6);
vTaskDelay(100/portTICK_PERIOD_MS);
printf("ADC val: %d\n", val);
}
}
adc1_config_width(ADC_WIDTH_BIT_12);
adc1_config_channel_atten(ADC1_CHANNEL_6, ADC_ATTEN_DB_11);
}
xTaskCreate(&
ADCtask,
"ADCtask", 2048,NULL, 5, NULL);
}
void app_main()
Definition main.c:261
void adc_setup(void)
Definition main.c:60
void ADCtask(void *pvParameters)
Definition main.c:37
Example 2
Here is an example of a PWM signal using the LEDC API to dim an LED.
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include <driver/ledc.h>
#define ONBOARD_LED 2
#define MAX_DUTY 4096
int i = 0;
while(1){
for (i = 0; i < MAX_DUTY; i += 10){
ledc_set_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_1,i);
ledc_update_duty(LEDC_LOW_SPEED_MODE,LEDC_CHANNEL_1);
vTaskDelay(10/portTICK_PERIOD_MS);
}
for (i = MAX_DUTY; i > 0; i -= 10){
ledc_set_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_1,i);
ledc_update_duty(LEDC_LOW_SPEED_MODE,LEDC_CHANNEL_1);
vTaskDelay(10/portTICK_PERIOD_MS);
}
}
}
ledc_timer_config_t timerConfig;
timerConfig.duty_resolution = LEDC_TIMER_13_BIT;
timerConfig.timer_num = LEDC_TIMER_1;
timerConfig.freq_hz = 5000;
timerConfig.speed_mode = LEDC_LOW_SPEED_MODE;
timerConfig.clk_cfg = LEDC_AUTO_CLK;
ledc_timer_config(&timerConfig);
ledc_channel_config_t channelConfig;
channelConfig.speed_mode = LEDC_LOW_SPEED_MODE;
channelConfig.channel = LEDC_CHANNEL_1;
channelConfig.intr_type = LEDC_INTR_DISABLE;
channelConfig.timer_sel = LEDC_TIMER_1;
channelConfig.duty = 0;
channelConfig.hpoint = MAX_DUTY;
ledc_channel_config(&channelConfig);
}
xTaskCreate(&
PWMtask,
"PWMtask", 2048, NULL, 5, NULL);
}
#define ONBOARD_LED
Definition main.c:31
void pwm_setup(void)
Definition main.c:66
void PWMtask(void *pvParameters)
Definition main.c:48
Example 3
This example can display the use of PWM on the ESP32 using a buzzer with a center frequency of 2.7k Hz. It will play the tune of mary had a little lamb changing the frequency to play the notes.
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include <driver/ledc.h>
#define BUZZ 21
#define MAX_DUTY 4096
#define DUTY50 2048
#define E0 2637
#define D0 2349
#define C0 2093
#define F0 2793
#define G0 3126
#define A0 3520
#define B0 3951
#define REST 0
int freq[] = {E0, D0, C0, D0, E0, E0, E0, REST, D0, D0, D0, REST, E0, E0, E0, REST, E0, D0, C0, D0, E0, E0, E0, REST, D0, D0, E0, D0, C0, REST};
int total_notes = sizeof(freq)/sizeof(int);
ledc_set_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_1,DUTY50);
ledc_update_duty(LEDC_LOW_SPEED_MODE,LEDC_CHANNEL_1);
while(1){
for(int note = 0; note < total_notes; note++){
if(freq[note] != REST){
ledc_timer_resume(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_1);
ledc_set_freq(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_1,freq[note]);
vTaskDelay(500/portTICK_PERIOD_MS);
ledc_timer_pause(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_1);
vTaskDelay(50/portTICK_PERIOD_MS);
}else{
ledc_timer_pause(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_1);
vTaskDelay(550/portTICK_PERIOD_MS);
}
}
}
}
ledc_timer_config_t timerConfig;
timerConfig.duty_resolution = LEDC_TIMER_13_BIT;
timerConfig.timer_num = LEDC_TIMER_1;
timerConfig.freq_hz = 262;
timerConfig.speed_mode = LEDC_LOW_SPEED_MODE;
timerConfig.clk_cfg = LEDC_AUTO_CLK;
ledc_timer_config(&timerConfig);
ledc_channel_config_t channelConfig;
channelConfig.gpio_num = BUZZ;
channelConfig.speed_mode = LEDC_LOW_SPEED_MODE;
channelConfig.channel = LEDC_CHANNEL_1;
channelConfig.intr_type = LEDC_INTR_DISABLE;
channelConfig.timer_sel = LEDC_TIMER_1;
channelConfig.duty = 0;
channelConfig.hpoint = MAX_DUTY;
ledc_channel_config(&channelConfig);
}
xTaskCreate(&
PWMtask,
"PWMtask", 2048, NULL, 5, NULL);
}
Written by Jose Granados
Lab Template
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include <driver/ledc.h>
#include <driver/adc.h>
{
while (1)
{
}
}
{
while (1)
{
}
}
{
}
{
}
{
xTaskCreate(&
ADCtask,
"ADCtask", 2048, NULL, 5, NULL);
xTaskCreate(&
PWMtask,
"PWMtask", 2048, NULL, 5, NULL);
}
QueueHandle_t duty_queue
Definition main.c:34
C helpful functions
Espressif ADC driver requires the following structures and enumerations in order to set the correct pin and operation. There are a few GPIOs that are avaiable to use analog converter. Depending on the ESP32, there are slight difference between the pinout so please verify the hardware.
typedef enum {
ADC1_CHANNEL_0 = 0,
ADC1_CHANNEL_1,
ADC1_CHANNEL_2,
ADC1_CHANNEL_3,
ADC1_CHANNEL_4,
ADC1_CHANNEL_5,
ADC1_CHANNEL_6,
ADC1_CHANNEL_7,
ADC1_CHANNEL_MAX,
} adc1_channel_t;
typedef enum {
ADC2_CHANNEL_0 = 0,
ADC2_CHANNEL_1,
ADC2_CHANNEL_2,
ADC2_CHANNEL_3,
ADC2_CHANNEL_4,
ADC2_CHANNEL_5,
ADC2_CHANNEL_6,
ADC2_CHANNEL_7,
ADC2_CHANNEL_8,
ADC2_CHANNEL_9,
ADC2_CHANNEL_MAX,
} adc2_channel_t;
The ESP has default 12 bit max width conversion but depending on the hardware, it can go up to 13 bit. To keep everything simple, please do not exceed 12 bit width. Espressif ADC width is selected by adc_bit_width_t
enumeration down below.
typedef enum {
#if CONFIG_IDF_TARGET_ESP32
ADC_WIDTH_BIT_9 = 0,
ADC_WIDTH_BIT_10 = 1,
ADC_WIDTH_BIT_11 = 2,
ADC_WIDTH_BIT_12 = 3,
#elif SOC_ADC_MAX_BITWIDTH == 12
ADC_WIDTH_BIT_12 = 3,
#elif SOC_ADC_MAX_BITWIDTH == 13
ADC_WIDTH_BIT_13 = 4,
#endif
ADC_WIDTH_MAX,
} adc_bits_width_t;
Espressif ADC driver has an attenuation feature to limit voltage reading to a fix range. The adc_atten_t
enumeration allows to select the range of the adc channel. Please go over the example program to get an idea of how the ADC driver works.
typedef enum {
ADC_ATTEN_DB_0 = 0,
ADC_ATTEN_DB_2_5 = 1,
ADC_ATTEN_DB_6 = 2,
ADC_ATTEN_DB_11 = 3,
} adc_atten_t;
Next, Espressif LEDC driver requires to use the following structures. ledc_timer_config_t
structure set the duty resolution, timer, frequency and clock source.
typedef struct {
ledc_mode_t speed_mode;
union {
ledc_timer_bit_t duty_resolution;
ledc_timer_bit_t bit_num __attribute__((deprecated));
};
ledc_timer_t timer_num;
uint32_t freq_hz;
ledc_clk_cfg_t clk_cfg;
} ledc_timer_config_t;
Furthermore, ledc_channel_config_t
structure allows to select the pin number, speed mode, channel, intr type, timer select, duty cycle and hpoint. For more details, please see Espressif documentation and go over the example programs to understand how to use it properly.
typedef struct {
int gpio_num;
ledc_mode_t speed_mode;
ledc_channel_t channel;
ledc_intr_type_t intr_type;
ledc_timer_t timer_sel;
uint32_t duty;
int hpoint;
struct {
unsigned int output_invert: 1;
} flags;
} ledc_channel_config_t;
Additionally, ledc_set_duty(ledc_mode_t speed_mode, ledc_channel_t channel, uint32_t duty)
is use to set the duty cycle and to actually update the duty you need to use ledc_update_duty(ledc_mode_t speed_mode, ledc_channel_t channel)
. Lastly, LEDC
driver allows users to control the operation of the timer: ledc_timer_pause(ledc_mode_t speed_mode, ledc_timer_t timer_sel)
and ledc_timer_resume(ledc_mode_t speed_mode, ledc_timer_t timer_sel)
.
Additional Links
Authors
GitHub
Read Next: Lab 8