



Nesse post vamos dar continuidade aos assuntos comentados no post anterior: "Introdução a ESP-IDF no VS Code", abordando o gerenciamento de componentes, drivers de Timer e LCD e uma pequena introdução a LVGL.
Nesse post, vamos usar os seguintes materiais:
No post passado nos vimos uma forma básica de incluir bibliotecas, onde manualmente baixamos e configuramos, apesar de simples, esse processo pode ser uma dor de cabeça em projetos maiores, imagine lidar com as versões de todas as bibliotecas instaladas onde a cada atualização você teria que manualmente ir alterando uma por uma..., por sorte a ESP-IDF tem um sistema de gerenciamento de dependências baseado em "componentes", os componentes nada mais são de que uma forma padronizada de criar bibliotecas para ESP-IDF.
Nessa pagina basta pesquisar o nome do componente que você deseja instalar e clicar no componente:
isso vai abrir a pagina do componente, onde normalmente vai ter todos os detalhes sobre ele, para instalar, selecione a versão desejada e clique em install, pronto!, componente instalado, simples assim!
Nos vamos fazer uso de alguns componentes no projeto desse post, mas antes disso temos mais alguns tópicos para abordar. Outra coisa que usamos no ultimo exemplo do post passado foi o uso de timers para gerar o delay para nosso LCD, no caso o Driver usado foi o: GPTimer (General Purpose Timer), nesse post também vamos fazer uso deles, mas antes precisamos entender como eles funcionam.
<esp_timer.h>esp_timer_handle_t", esse é o handler onde as configurações do timer vão ser salvas, não é necessário pré-configurar.esp_timer_create_args_t", essa é a struct onde vamos configurar o timer, ela contem os seguintes campos:
callback: a função que vai ser chamada quando ocorrer um evento de timer. (a assinatura dessa função deve ser (void)(void *args), uma função sem retorno que recebe um ponteiro de tipo indefinido)arg: um ponteiro de tipo indefinido para a informação passada para a função anterior. (pode ser NULL)dispatch_method: define se a função vai ser chamada como uma task ou como uma ISR, recebe um dos seguintes valores:ESP_TIMER_TASK.ESP_TIMER_ISR. (é desabilitado por padrão, pode ser habilitado pelas configurações do projeto)name: uma string com o nome do timer (útil para debug, não é obrigatório).skip_unhandled_events: define se o timer deve pular eventos ainda não executados, recebe true ou false. esp_timer_create(esp_timer_create_args_t*, esp_timer_handle_t*) o primeiro argumento é um ponteiro para struct de configuração, o segundo é um pónterio para o handler do timer.esp_timer_start_once(esp_timer_handle_t, uint64_t).esp_timer_start_periodic(esp_timer_handle_t, uint64_t).esp_timer_stop(esp_timer_handle_t) .<driver/gptimer.h>gptimer_handle_t, esse é o handler onde as configurações serão salvas, não é necessário pre-configurar. gptimer_config_t, essa struct contem os seguintes campos:
clk_src: a fonte de clock do timer, no ESP32 pode ser dos seguintes valores:
GPTIMER_CLK_SRC_APB ou GPTIMER_CLK_SRC_DEFAULT (no esp32 os dois são iguais)GPTIMER_COUNT_DOWNGPTIMER_COUNT_UPresolution_hz: resolução em Hz do timerintr_priority: prioridade do interrupção do timerflags.intr_shared: se o número da interrupção pode ser compartilhado com outros periféricosgptimer_new_timer(const gptimer_config_t*, gptimer_handle_t*), o primeiro argumento é um ponteiro para nossa struct de configuração o segundo é um ponteiro para o handler do GPTimer gptimer_alarm_config_t, essa struct contem os seguintes campos:
alarm_count: o tempo em ticks* em que o evento vai acorrerreload_count: o valor em ticks* que o timer vai carregar após o resetflags.auto_reload_on_alarm: se o timer deve resetar após o evento (1=true/0=false) gptimer_event_callbacks_t, essa struct só tem um campo: .on_alarm, que recebe um ponteiro de função com a assinatura: bool()(gptimer_handle_t, const gptimer_alarm_event_data_t*, void*)gptimer_set_alarm_action(gptimer_handle_t, const gptimer_alarm_config_t*), o primeiro argumento é o nosso handler o segundo é um ponteiro para as configurações do evento.gptimer_register_event_callbacks(gptimer_handle_t, const gptimer_event_callbacks_t *, void*), o primeiro argumento é o nosso handler, o segundo é um ponterio para struct de callback o ultimo é um ponteiro com argumentos para nosso callback (pode ser NULL caso nosso evento não receba argumentos).gptimer_enable(gptimer_handle_t) seguida da função: gptimer_start(gptimer_handler_t),isso vai inciar a contagem do nosso timer, e iniciar novo evento como periódico, para tornar "one-shot" temos que manualmente chamar gptimer_stop (gptimer_handler_t) após o evento; Para configurar como "Wall clock" basta pular a etapa de configuração e iniciar o timer.
a leitura e escrita do valor da contagem do timer pode ser feito pelas seguintes funções: gptimer_get_raw_count(gptimer_handle_t, uint64_t*) e gptimer_set_raw_count(gptimer_handle_t, uint64_t)
Nesse primeiro Exemplo vamos ver na pratica o funcionamento do timers e ver uma característica interessante sobre os GPIOs do ESP32.
/*
===================================
ESP timers exemplo (Introdução ESP-IDF pt2 - Eletrogate blog)
blink basico com GPTimer e ESPTimer
Author: Guilherme SilVA Schultz (RecursiveError)
data: 2024-08-09
===================================
*/
//C includes
#include <stdio.h>
//FreeRTOS includes
#include <freertos/FreeRTOS.h>
//esp-idf includes
#include <driver/gptimer.h>
#include <driver/gpio.h>
#include <esp_log.h>
#include <esp_err.h>
#include <esp_timer.h>
//Led difines
#define GPTIMER_LED GPIO_NUM_23
#define ESP_TIMER_LED GPIO_NUM_22
//timer defines
#define ESP_TIMER_DELAY_US 500*1000 //~0.5s
#define GPTIMER_DELAY_US 800*1000 //~0.8s
//init tasks
void GPIO_init();
void GPTimer_init();
void ESPTimer_init();
//timer callbacks
void esptimer_callback(void *args);
bool gptimer_callback(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_ctx);
//timers handlers
esp_timer_handle_t esp_timer_h;
gptimer_handle_t gptimer_h;
//timer consts
const char esp_timer_name[] = "esp_timer_example";
void app_main(void){
const char TAG[] = "main";
ESP_LOGI(TAG, "start GPIO config");
GPIO_init();
ESP_LOGI(TAG, "start ESPTimer config");
ESPTimer_init();
ESP_LOGI(TAG, "start GPtimer config");
GPTimer_init();
ESP_LOGI(TAG, "main loop start");
while(1){
vTaskDelay(portMAX_DELAY);
}
}
void GPIO_init(){
const char TAG[] = "GPIO_init";
const gpio_config_t conf = {
.pin_bit_mask = (1<<GPTIMER_LED) | (1<<ESP_TIMER_LED),
.mode = GPIO_MODE_INPUT_OUTPUT, //esse modo nos permite usar as funções de output e input nos mesmo pino!
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.pull_up_en = GPIO_PULLUP_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
};
ESP_ERROR_CHECK(gpio_config(&conf));
ESP_LOGI(TAG, "GPIO config OK");
}
void ESPTimer_init(){
const char TAG[] = "ESPTimer_init";
const esp_timer_create_args_t timer_conf = {
.callback = esptimer_callback,
.arg = NULL, //nosso evento não revebe argumentos
.dispatch_method = ESP_TIMER_TASK, //i evento vai ser executado como task do FreeRTOS
.name = esp_timer_name,
.skip_unhandled_events = true,
};
ESP_ERROR_CHECK(esp_timer_create(&timer_conf, &esp_timer_h));
ESP_ERROR_CHECK(esp_timer_start_periodic(esp_timer_h, ESP_TIMER_DELAY_US)); // inivia o evento como periodico
ESP_LOGI(TAG, "ESPtimer config OK");
}
void GPTimer_init(){
const char TAG[] = "GPTimer_init";
const gptimer_config_t gpt_config = {
.clk_src = GPTIMER_CLK_SRC_DEFAULT, //fonte de clock padrão
.direction = GPTIMER_COUNT_UP,
.resolution_hz = 1000*1000, //clock = 1Mhz, cada tick = 1uS
.intr_priority = 0, //prioridade automatica
.flags.intr_shared = 0,
};
const gptimer_alarm_config_t gpt_event_cb_conf = {
.alarm_count = GPTIMER_DELAY_US,
.reload_count = 0,
.flags.auto_reload_on_alarm = 1,
};
const gptimer_event_callbacks_t gpt_event_cb = {
.on_alarm = gptimer_callback,
};
//inicia o timer e o evento periodico
ESP_ERROR_CHECK(gptimer_new_timer(&gpt_config, &gptimer_h));
ESP_ERROR_CHECK(gptimer_set_alarm_action(gptimer_h, &gpt_event_cb_conf));
ESP_ERROR_CHECK(gptimer_register_event_callbacks(gptimer_h, &gpt_event_cb, NULL));
ESP_ERROR_CHECK(gptimer_enable(gptimer_h));
ESP_ERROR_CHECK(gptimer_start(gptimer_h));
ESP_LOGI(TAG, "GPtimer config OK");
}
void esptimer_callback(void *args){
int gpio_level = 1 & ~(gpio_get_level(ESP_TIMER_LED)); //le o valor no pino de saida e inverte o sinal
gpio_set_level(ESP_TIMER_LED, (uint32_t) gpio_level);
}
bool gptimer_callback(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_ctx){
int gpio_level = 1 & ~(gpio_get_level(GPTIMER_LED)); //le o valor no pino de saida e inverte o sinal
gpio_set_level(GPTIMER_LED, (uint32_t) gpio_level);
return true;
}
Para finalizar o tema dos timers temos que falar de um dos usos mais comuns de timers: PWM; A esp-idf provem uma API para PWM por meio do driver: LedC (Led Contol), inicialmente esse driver era apenas para controle de brilho de leds, por isso o nome, mas atualmente pode ser usada sem problemas como saída PWM comum. O esp32 tem dois grupos de PWM, um de alta velocidade e outro de baixa velocidade, com oito canais cada, ou seja no máximo você pode ter 16 saídas PWM. A primeira parte para configuração do PWM é a configuração do timer que será usado como fonte do sinal PWM, e isso pode ser feito da seguinte forma:
<driver/ledc.h> ledc_timer_config_t, essa struct contem os seguintes campos:
speed_mode: o grupo PWM, pode receber um dos seguintes valores:LEDC_LOW_SPEED_MODE.LEDC_HIGH_SPEED_MODE. (nem todos tem grupos de PWM de alta velocidade)duty_resolution: tamanho de bits de resolução, Max 20Bits.timer_num: número do timer que estamos configurando.freq_hz: frequência do timer.clk_cfg: fonte de clock do timer.deconfigure: false para configurar o timer, true para desconfigurar o timer.ledc_timer_config(const ledc_timer_config_t*)passando um ponteiro para nossa struct de configuração.
O esp32 permite a escolha do tamanho da resolução entre: 1 e 20 bits, esse valor depende da frequência do timer, quando maior a frequência do timer, menor é o limite máximo de resolução, Exemplo: um timer de 5Khz é capaz de obter uma resolução máxima de 13bits.
O valor máximo de resolução de um timer pode ser obtido por meio de um calculo envolvendo a fonte de clock e a frequência do timer, por sorte a ESP-IDF fornece meios para realizar esse calculo de forma automática:
esp_clk_tree_src_get_freq_hz(soc_module_clk_t, esp_clk_tree_src_freq_precision_t, uint32_t*),para obter a velocidade atual de uma fonte de clock, os argumentos dessa função são respectivamente:
uint32_t apb_clock_speed = 0; ESP_ERROR_CHECK(esp_clk_tree_src_get_freq_hz(SOC_MOD_CLK_APB, ESP_CLK_TREE_SRC_FREQ_PRECISION_APPROX, &apb_clock_speed)); //obtem a velocidade aproximada da fonte de clock APB ESP_LOGI(TAG, "APB CLOCK SPEED: %lu", apb_clock_speed); uint32_t duty = ledc_find_suitable_duty_resolution(apb_clock_speed, 10*1000); //APB CLOCK 80Mhz|timer freq 10Khz = max 12bits ESP_LOGI(TAG,"MAX DUTY RESOLUTION: %lu", duty)Nesse post não vamos entrar em detalhes sobre as fontes de clock do ESP32, vamos fazer uso apenas do APB. Pronto, timer configurado, agora vamos configurar a saída PWM, para isso temos que seguir esses passos:
ledc_channel_config_t, essa struct contem os seguintes campos:
gpio_num: número da GPIO que vamos ter a saida PWMspeed_mode: o grupo PWM (deve ser a mesma do timer)channel: canal PWM que estamos configurando (0-7, 8 canais totais)intr_type: tipo de interrupção do PWM (nesse post vamos manter em: LEDC_INTR_DISABLE)timer_sel: timer usado para gerar o sinal PWM (deve ser o mesmo timer configurado na struct anterior)duty: resolução do sinal PWM (pode ir de 0 até 2**resolução do timer)hpoint: valor do Hpoint (hpoint um o valor de comparação do timer no ESP32, quando o valor do contador chegar no valor hpoint o sinal vai para HIGH), pode ir de 0 até (2**resolução do timer)-1flags.output_invert: se o sinal deve ser invertido, 1-sim, 0-nãoledc_channel_config(const ledc_channel_config_t*)" passando um ponteiro para nossa struct de configuração.
ledc_set_duty(ledc_mode_t, ledc_channel_t, uint32_t),para carregar o valor para o PWM, recebe: o grupo PWM, canal PWM (os mesmos definidos na configuração) e valor do duty,
ledc_update_duty(ledc_mode_t, ledc_channel_t), para efetivar o PWM selecionado na função anterior, recebe: o grupo PWM, canal PWM.
ledc_timer_pause(ledc_mode_t, ledc_timer_t) e ledc_timer_resume(ledc_mode_t, ledc_timer_t) ambas recebem o grupo PWM e o canal PWM.Nesse exemplo vamos mostrar o funcionamento básico da saida PWM, controlando o brilho de um LED.
/*
===================================
ESP PWM exemplo (Introdução ESP-IDF pt2 - Eletrogate blog)
uso basico do PWM
Author: Guilherme Silva Schultz (RecursiveError)
data: 2024-08-13
===================================
*/
// C includes
#include <stdio.h>
#include <math.h>
//ESP-IDF includes
#include <driver/ledc.h>
#include <driver/gpio.h>
#include <esp_err.h>
#include <esp_log.h>
#include <esp_clk_tree.h>
#include <soc/clk_tree_defs.h>
//FreeRTOS includes
#include <freertos/FreeRTOS.h>
#define PWM_CLOCK_SPEED 10*1000 //10Khz
#define PWM_OUT_GPIO GPIO_NUM_2 //saida do PWM
#define PWM_CH LEDC_CHANNEL_0 //canal PWM
#define PWM_TIMER LEDC_TIMER_0 //timer do PWM
uint32_t PWM_init();
void app_main(void){
const char TAG[] = "main";
ESP_LOGI(TAG, "INIT PWM");
uint32_t duty = PWM_init();
uint32_t max_duty_pwm = pow(2,duty);//obtem o valor maximo do duty-cycle
ESP_LOGI(TAG, "MAX duty: %lu",max_duty_pwm);
while(1){
//aumento em 50 o valor do duty a cada 100ms
for(uint32_t duty_ac = 0; duty_ac<max_duty_pwm; duty_ac = duty_ac + 50){
ledc_set_duty(LEDC_LOW_SPEED_MODE, PWM_CH, duty_ac);
ledc_update_duty(LEDC_LOW_SPEED_MODE, PWM_CH);
vTaskDelay(pdMS_TO_TICKS(100));
}
}
}
uint32_t PWM_init(){
const char TAG[] = "PWM CONFIG";
//obtem o a velocidade do clock APB
uint32_t apb_clock_speed = 0;
ESP_ERROR_CHECK(esp_clk_tree_src_get_freq_hz(SOC_MOD_CLK_APB, ESP_CLK_TREE_SRC_FREQ_PRECISION_APPROX, &apb_clock_speed));
ESP_LOGI(TAG, "APB CLOCK SPEED: %lu", apb_clock_speed);
//obtem o maximo de bits da resolução
uint32_t duty = ledc_find_suitable_duty_resolution(apb_clock_speed, PWM_CLOCK_SPEED); //APB CLOCK 80Mhz/ timer freq 10Khz
ESP_LOGI(TAG,"MAX DUTY RESOLUTION: %lu", duty);
ledc_timer_config_t pwm_timer_conf = {
.speed_mode = LEDC_LOW_SPEED_MODE, //pwm de baixa velocidade
.duty_resolution = duty, //qtd de bits da resolução
.timer_num = PWM_TIMER, //timer usado
.freq_hz = PWM_CLOCK_SPEED, //frequencia de 10Khz
.clk_cfg = LEDC_USE_APB_CLK,//fonte de clock APB
.deconfigure = false,
};
ledc_channel_config_t pwm_ch = {
.gpio_num = PWM_OUT_GPIO, //pino de saida do sinal PWM
.speed_mode = LEDC_LOW_SPEED_MODE,//PWM de baixa velocidade
.channel = PWM_CH,
.intr_type = LEDC_INTR_DISABLE,//interrupçoes desabilitadas
.timer_sel = PWM_TIMER,
.duty = pow(2, duty), //valor maximo possivel de duty com a resolução de bits do timer
.hpoint = 0,
.flags.output_invert = 0//outout normal
};
//inicia o sinal PWM
ESP_ERROR_CHECK(ledc_timer_config(&pwm_timer_conf));
ESP_ERROR_CHECK(ledc_channel_config(&pwm_ch));
return duty;
}
A esp-idf provem uma interface genérica para trabalhar com displays gráficos, essa interface nos permite usar vários tipos de LCD gráficos de forma fácil, seja ele: SPI, I2C, PARALELO/ com ou sem touch, e muito mais, nesse post vamos abordar apenas a configuração do display TFT 1.44", que possui interface SPI, Essa configuração é dividida em 3 etapas: configuração do I/O, configuração da interface do display e configuração do bus de comunicação.
<esp_lcd_panel_io.h>esp_lcd_panel_io_handle_tesp_lcd_panel_io_spi_config_t, essa é a struct que vai guardar as informações básicas do nosso display, nessa struct só é necessário configurar os seguintes itens:
cs_gpio_num: GPIO do pino CS do displaydc_gpio_num: GPIO do pino DC do display (esse pino também pode ser chamado de A0)spi_mode: modo de SPI do display (no nosso caso é modo 0)pclk_hz: velocidade em Hz, do clock de pixel do display (o recomendado para 1.44" é 15 Mhz)lcd_cmd_bits: quantidade de Bits em comandos do display (no nosso caso é 8)lcd_param_bits: quantidade de Bits em parâmetros do display (no nosso caso é 8)trans_queue_depth: tamanho da fila de transferência (não é uma configuração do display, isso é apenas o tamanho do buffer onde a placa pode salvar as transações)<esp_lcd_panel_io.h>esp_lcd_panel_handle_tesp_lcd_panel_dev_config_t, essa struct contem os seguintes campos:
reset_gpio_num: GPIO do pino de reset do displayrgb_ele_order: padão de cores do display, pode ser LCD_RGB_ELEMENT_ORDER_RGB ou LCD_RGB_ELEMENT_ORDER_BGRbits_per_pixel: quantidade de Bits de cores (nosso display possui 16Bits de cores)<driver/spi_master.h> spi_bus_config_t, nessa struct temos que configurar os seguintes campos:
sclk_io_num: GPIO do pino SCK do display.mosi_io_num: GPIO do pino SDI do display (esse pino também pode estar marcado como "SDA").miso_io_num: GPIO do pino SDO do display (nosso display não envia informações então esse pino é marcado como -1)max_transfer_sz: tamanho máximo de transferência, use zero para valor padrão de 4096.spi_bus_initialize(spi_host_device_t, const spi_bus_config_t*, spi_dma_chan_t), cada argumento dessa função significa:
SPIx_HOST, onde X é um valor de 1 a 3)SPI_DMA_CH_AUTO para configuração automática)esp_lcd_new_panel_io_spi(esp_lcd_spi_bus_handle_t, const esp_lcd_panel_io_spi_config_t*, esp_lcd_panel_io_handle_t*) cada argumento dessa função significa:esp_lcd_new_panel_x(const esp_lcd_panel_io_handle_t, const esp_lcd_panel_dev_config_t*, esp_lcd_panel_handle_t*) onde X é o nome do controlador do nosso display, cada argumento dessa função significa:
com a biblioteca instalada:
<esp_lcd_ili9341.h>esp_lcd_new_panel_ili9341, seguindo o ultimo passo da inicialização do driver.esp_lcd_panel_reset (para resetar as variáveis internas e garantir uma inicialização limpa) e esp_lcd_panel_init (para inicializar os display), ambas recebem apenas o handler do LCD como argumento, feito isso basta ligar o output do display com a função: esp_lcd_panel_disp_on_off(esp_lcd_panel_handle_t, bool) passando o handler do nosso LCD como primeiro argumento e true com segundo.
Display inicializado com sucesso, agora vamos ver o seu uso com um exemplo pratico! Nesse Exemplo vamos configurar e usar um display LCD SPI para exibir cores de 16Bits.
(a imagem usa um display 160x128, mas o funcionamento é igual para o display 128x128)
/*
ESP LCD exemplo (Introdução ESP-IDF pt2 - Eletrogate blog)
uso basico do Driver LCD
Author: Guilherme Silva Schultz (RecursiveError)
data: 2024-08-18
*/
//C includes
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//FreeRTOS includes
#include <freertos/FreeRTOS.h>
//ESP-IDF includes
#include <esp_log.h>
#include <esp_err.h>
#include <driver/gpio.h>
#include <driver/spi_master.h>
#include <esp_lcd_panel_io.h>
#include <esp_lcd_panel_vendor.h>
#include <esp_lcd_panel_ops.h>
//extra includes
#include <esp_lcd_ili9341.h>
//LCD IO DEFINES
#define LCD_LED_PIN GPIO_NUM_23
#define LCD_SCK_PIN GPIO_NUM_22
#define LCD_SDA_PIN GPIO_NUM_21
#define LCD_A0_PIN GPIO_NUM_19
#define LCD_RESET_PIN GPIO_NUM_18
#define LCD_CS_PIN GPIO_NUM_5
#define LCD_SPI_BUS SPI2_HOST
#define LCD_H_RES 128 //resolução horizontal do display
#define LCD_V_RES 128 //resolução vertical do display
//lcd global vars
esp_lcd_panel_handle_t lcd_handler;
esp_lcd_panel_io_handle_t lcd_io_handler;
uint16_t color_buf[LCD_H_RES][LCD_V_RES]; //buffer de pixels
void GPIO_init();
void LCD_init();
void app_main(void){
ESP_LOGI("main", "init GPIOs start");
GPIO_init();
ESP_LOGI("main", "init GPIOs done, init LCD");
LCD_init();
ESP_LOGI("main", "init LCD done");
ESP_LOGI("main", "START MAIN LOOP");
uint16_t color = 0;
while(1){
//passa por todas as cores de 16Bits
for(uint16_t red = 0; red < 31; red++){
for(uint16_t green = 0; green < 63; green++){
for(uint16_t blue = 0; blue < 31; blue++){
color = (red<<11) | (green<<5) | blue;
memset(color_buf, color, sizeof(color_buf)); //salva a cor no buffer
esp_lcd_panel_draw_bitmap(lcd_handler, 0, 0, LCD_H_RES, LCD_V_RES, color_buf); //envia um buffer de pixels para o display
vTaskDelay(pdMS_TO_TICKS(155));
}
}
}
}
}
void GPIO_init(){
gpio_config_t bk_conf = {
.pin_bit_mask = 1<<LCD_LED_PIN,
.mode = GPIO_MODE_OUTPUT,
.intr_type = GPIO_INTR_DISABLE,
.pull_down_en = 0,
.pull_up_en = 0,
};
ESP_ERROR_CHECK(gpio_config(&bk_conf));
}
void LCD_init() {
spi_bus_config_t spi_bus_conf ={
.sclk_io_num = LCD_SCK_PIN,
.mosi_io_num = LCD_SDA_PIN,
.miso_io_num = -1,
.max_transfer_sz = 0,
};
esp_lcd_panel_io_spi_config_t lcd_io_spi_conf = {
.cs_gpio_num = LCD_CS_PIN,
.dc_gpio_num = LCD_A0_PIN,
.spi_mode = 0,
.pclk_hz = 15*1000*1000, //15Mhz
.lcd_cmd_bits = 8,
.lcd_param_bits = 8,
.trans_queue_depth = 10
};
esp_lcd_panel_dev_config_t panel_config = {
.reset_gpio_num = LCD_RESET_PIN,
.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB,
.bits_per_pixel = 16
};
ESP_ERROR_CHECK(spi_bus_initialize(LCD_SPI_BUS, &spi_bus_conf, SPI_DMA_CH_AUTO));
ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi( (esp_lcd_spi_bus_handle_t) LCD_SPI_BUS,&lcd_io_spi_conf, &lcd_io_handler));
ESP_ERROR_CHECK(esp_lcd_new_panel_ili9341(lcd_io_handler, &panel_config, &lcd_handler));
//reseta as variáveis internas do display para uma inicialização limpa
ESP_ERROR_CHECK(esp_lcd_panel_reset(lcd_handler));
//inicia o display
ESP_ERROR_CHECK(esp_lcd_panel_init(lcd_handler));
//liga o LED do display
ESP_ERROR_CHECK(gpio_set_level(LCD_LED_PIN, 1));
//liga o output do display
ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(lcd_handler, true));
}
alguns tópicos importantes desse código:
<esp_lcd_panel_ops.h>esp_lcd_panel_draw_bitmap responsável pelo envio de imagens para o display, pode enviar vários pixels de uma só vez.
Light and Versatile Graphics Library ou LVGL é uma das bibliotecas gráficas mais famosas entre profissionais de sistemas embarcados, ela permite a criação de UIs leves, altamente personalizáveis, interativas e portáveis em dispositivos com recursos limitados, é usado por Grandes empresas de tecnologia como exemplo: ST, NXP, Xiaomi, Samsung até a Espressif fabricante dos ESP32 recomenda o uso dessa biblioteca, e a cereja do bolo: é totalmente grátis e open-source!
lv_init() e implementar 4 funções básicas:
lv_tick_inc(x) (onde x é o tempo um Ms decorrido), nos já vimos sobre como timers podem gerar eventos periódicos, podemos usar esp_timer para gerar um evento que chame: lv_tick_inc lv_timer_handler novamente, podemos usar uma task para isso chamando a função: lv_timer_handler em um loop com vTaskDelay(pdMS_TO_TICKS(x)) (onde X é o valor retornado por lv_timer_handler)lv_display_flush_ready(display_lvgl), podemos usar os campos: on_color_trans_done e user_ctx da struct de IO do lcd:
esp_lcd_panel_io_spi_config_t para chamar o evento de notificação do LVGLesp_lcd_panel_draw_bitmap vista no exemplo anterior para isso
vamos escrever o seguinte código:
/* LVGL hello world exemplo (Introdução ESP-IDF pt2 - Eletrogate blog)
setup basico da LVGL + Hello world
Author: Guilherme Silva Schultz (RecursiveError)
data: 2024-08-21
*/
//C includes
#include <stdio.h>
#include <stdlib.h>
//FreeRTOS includes
#include <freertos/FreeRTOS.h>
//ESP_IDF includes
#include "esp_timer.h"
#include <driver/spi_master.h>
#include <driver/gpio.h>
#include <esp_lcd_panel_ops.h>
#include <esp_lcd_panel_io.h>
#include <esp_lcd_panel_vendor.h>
#include <esp_err.h>
#include <esp_log.h>
//extra includes
#include <lvgl.h>
#include <esp_lcd_ili9341.h>
//SPI CONFIG
#define LCD_LED_PIN GPIO_NUM_23
#define LCD_SCK_PIN GPIO_NUM_22
#define LCD_SDA_PIN GPIO_NUM_21
#define LCD_A0_PIN GPIO_NUM_19
#define LCD_RESET_PIN GPIO_NUM_18
#define LCD_CS_PIN GPIO_NUM_5
#define LCD_SPI_BUS SPI2_HOST
#define LCD_H_RES 128 //resolução horizontal do display
#define LCD_V_RES 128 //resolução vertical do display
//=============================== init functions ===============================
void GPIO_init();
void LCD_init(spi_host_device_t host, esp_lcd_panel_io_handle_t *io_handler, esp_lcd_panel_handle_t *p_handler, lv_display_t *disp);
void timer_init();
void LVGL_init(lv_display_t **display, lv_draw_buf_t *display_bufs[2], esp_lcd_panel_handle_t *lcd_handler);
//=============================== LVGL functions ===============================
bool lvgl_flush_ready(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx); //avisa o LVGL que o display está pronto para receber novos dados
void lvgl_flush_cb(lv_display_t *disp, const lv_area_t *area, uint8_t *px_map); //envia os novos dados para o display
void lvgl_tick_inc(void *arg); //incrementa o timer interno do LVGL
void lv_example_get_started_1();
void app_main(void){
//LCD handlers
esp_lcd_panel_io_handle_t io_handler = NULL;
esp_lcd_panel_handle_t lcd_handler = NULL;
//LVGL handlers
lv_display_t *display = NULL;
lv_draw_buf_t *bufs[2] = {NULL, NULL};
GPIO_init();
LVGL_init(&display, bufs, &lcd_handler);
LCD_init(LCD_SPI_BUS,&io_handler,&lcd_handler, display);
timer_init();
lv_example_get_started_1();
uint32_t task_delay_ms = 0;
while (1) {
//espera o tempo indicado por lv_timer_handler
task_delay_ms = lv_timer_handler();
vTaskDelay(pdMS_TO_TICKS(task_delay_ms));
}
}
//=============================== init functions ===============================
void GPIO_init(){
//configura o led do display
gpio_config_t conf_output = {
.pin_bit_mask = 1 << LCD_LED_PIN,
.mode = GPIO_MODE_OUTPUT
};
gpio_config(&conf_output);
}
void LCD_init(spi_host_device_t host, esp_lcd_panel_io_handle_t *io_handler, esp_lcd_panel_handle_t *p_handler, lv_display_t *disp){
spi_bus_config_t spi_bus_conf ={
.sclk_io_num = LCD_SCK_PIN,
.mosi_io_num = LCD_SDA_PIN,
.miso_io_num = -1,
.max_transfer_sz = 0,
};
esp_lcd_panel_io_spi_config_t lcd_io_spi_conf = {
.cs_gpio_num = LCD_CS_PIN,
.dc_gpio_num = LCD_A0_PIN,
.spi_mode = 0,
.pclk_hz = 15*1000*1000, //15Mhz
.lcd_cmd_bits = 8,
.lcd_param_bits = 8,
.trans_queue_depth = 10,
.on_color_trans_done = lvgl_flush_ready, //chama o evento que indica o estado do display para a LVGL
.user_ctx = disp
};
esp_lcd_panel_dev_config_t panel_config = {
.reset_gpio_num = LCD_RESET_PIN,
.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_BGR,
.bits_per_pixel = 16
};
ESP_ERROR_CHECK(spi_bus_initialize(LCD_SPI_BUS, &spi_bus_conf, SPI_DMA_CH_AUTO));
ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi( (esp_lcd_spi_bus_handle_t) LCD_SPI_BUS, &lcd_io_spi_conf, io_handler));
ESP_ERROR_CHECK(esp_lcd_new_panel_ili9341(*io_handler, &panel_config, p_handler));
//reseta as variaveis internas do display para uma inicialização limpa
ESP_ERROR_CHECK(esp_lcd_panel_reset(*p_handler));
//inicia o display
ESP_ERROR_CHECK(esp_lcd_panel_init(*p_handler));
//liga o LED do display
ESP_ERROR_CHECK(gpio_set_level(LCD_LED_PIN, 1));
//liga o output do display
ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(*p_handler, true));
ESP_ERROR_CHECK(esp_lcd_panel_invert_color(*p_handler, false));
}
void LVGL_init(lv_display_t **display, lv_draw_buf_t *display_bufs[2], esp_lcd_panel_handle_t *lcd_handler){
lv_init();
//cria um display LVGL
*display = lv_display_create(LCD_H_RES, LCD_V_RES);
//cria buffers de escrita do display LVGL
display_bufs[0] = lv_draw_buf_create(LCD_H_RES,LCD_V_RES, LV_COLOR_FORMAT_RGB565, 0);
display_bufs[1] = lv_draw_buf_create(LCD_H_RES,LCD_V_RES, LV_COLOR_FORMAT_RGB565, 0);
//envia nosso LCD handler como argumento para os eventos do LVGL
lv_display_set_user_data(*display, lcd_handler);
lv_display_set_flush_cb(*display, lvgl_flush_cb);
lv_display_set_draw_buffers(*display, display_bufs[0], display_bufs[1]);
lv_display_set_color_format(*display, LV_COLOR_FORMAT_RGB565);
}
void timer_init(){
const esp_timer_create_args_t lvgl_tick_timer_args = {
.callback = &lvgl_tick_inc, //evento que vai indicar a passagem de tempo para a LVGL
.name = "lvgl_tick"
};
esp_timer_handle_t lvgl_tick_timer = NULL;
ESP_ERROR_CHECK(esp_timer_create(&lvgl_tick_timer_args, &lvgl_tick_timer));
ESP_ERROR_CHECK(esp_timer_start_periodic(lvgl_tick_timer, 1 * 1000)); //chama o evento a cada 1Ms
}
//=============================== LVGL functions ===============================
bool lvgl_flush_ready(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx){
lv_display_t *disp_driver = (lv_display_t *)user_ctx;
lv_display_flush_ready(disp_driver); //avisa a LVGL que o display está pronto para receber dados
return false;
}
void lvgl_flush_cb(lv_display_t *disp, const lv_area_t *area, uint8_t *px_map){
esp_lcd_panel_handle_t *lcd_handler = (esp_lcd_panel_handle_t *)lv_display_get_user_data(disp);
if(lcd_handler != NULL){
esp_lcd_panel_draw_bitmap(*lcd_handler,area->x1, area->y1, area->x2+1, area->y2+1, px_map); //envia os dados da LVGL para o display
}
}
void lvgl_tick_inc(void *arg){
lv_tick_inc(1); //indica para LVGL que passou 1Ms
}
//Esse exemplo foi tirado da documentação do LVGL:
//links: https://github.com/lvgl/lvgl/blob/b78a4de8984e7e9b76ec4fc0e437fc952435f433/examples/get_started/lv_example_get_started_1.c
void lv_example_get_started_1(){
/*Change the active screen's background color*/
lv_obj_set_style_bg_color(lv_screen_active(), lv_color_hex(0x003a57), LV_PART_MAIN);
/*Create a white label, set its text and align it to the center*/
lv_obj_t * label = lv_label_create(lv_screen_active());
lv_label_set_text(label, "Hello world");
lv_obj_set_style_text_color(lv_screen_active(), lv_color_hex(0xffffff), LV_PART_MAIN);
lv_obj_align(label, LV_ALIGN_CENTER, 0, 0);
}
lv_draw_buf_create devem ter no mínimo 1/10 da resolução da placa, use 0 para configuração automática.lv_display_set_draw_buffers, o segundo é opcional, podendo ser NULL caso não utilizado.Vamos juntar tudo que nos vimos até agora e criar um projeto que contem a quantidade de vezes que um botão foi pressionado usando LVGL
//C includes
#include <stdio.h>
#include <stdlib.h>
//FreeRTOS includes
#include <freertos/FreeRTOS.h>
//ESP_IDF includes
#include "esp_timer.h"
#include <driver/spi_master.h>
#include <driver/gpio.h>
#include <esp_lcd_panel_ops.h>
#include <esp_lcd_panel_io.h>
#include <esp_lcd_panel_vendor.h>
#include <esp_err.h>
#include <esp_log.h>
//extra includes
#include <lvgl.h>
#include <esp_lcd_ili9341.h>
//SPI CONFIG
#define LCD_LED_PIN GPIO_NUM_23
#define LCD_SCK_PIN GPIO_NUM_22
#define LCD_SDA_PIN GPIO_NUM_21
#define LCD_A0_PIN GPIO_NUM_19
#define LCD_RESET_PIN GPIO_NUM_18
#define LCD_CS_PIN GPIO_NUM_5
#define LCD_SPI_BUS SPI2_HOST
#define LCD_H_RES 128 //resolução horizontal do display
#define LCD_V_RES 128 //resolução vertical do display
//LVGL inputs
#define LVGL_INC_BTN GPIO_NUM_17
//=============================== init functions ===============================
void GPIO_init();
void LCD_init(spi_host_device_t host, esp_lcd_panel_io_handle_t *io_handler, esp_lcd_panel_handle_t *p_handler, lv_display_t *disp);
void timer_init();
void LVGL_init(lv_display_t **display, lv_draw_buf_t *display_bufs[2], esp_lcd_panel_handle_t *lcd_handler, lv_indev_t **input_device);
//=============================== LVGL functions ===============================
bool lvgl_flush_ready(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx); //avisa o LVGL que o display está pronto para receber novos dados
void lvgl_flush_cb(lv_display_t *disp, const lv_area_t *area, uint8_t *px_map); //envia os novos dados para o display
void lvgl_tick_inc(void *arg); //incrementa o timer interno do LVGL
void lvgl_example();
void lvgl_btn_read(lv_indev_t * indev, lv_indev_data_t*data);
//LVGL global vars
const lv_point_t btn_coords[] = {{10,10}, {1,1}};
void app_main(void){
//LCD handlers
esp_lcd_panel_io_handle_t io_handler = NULL;
esp_lcd_panel_handle_t lcd_handler = NULL;
//LVGL handlers
lv_display_t *display = NULL;
lv_draw_buf_t *bufs[2] = {NULL, NULL};
lv_indev_t *input_device = NULL;
GPIO_init();
LVGL_init(&display, bufs, &lcd_handler, &input_device);
LCD_init(LCD_SPI_BUS,&io_handler,&lcd_handler, display);
timer_init();
lvgl_example();
uint32_t task_delay_ms = 0;
while (1) {
task_delay_ms = lv_timer_handler();
vTaskDelay(pdMS_TO_TICKS(task_delay_ms));
}
}
//=============================== init functions ===============================
void GPIO_init(){
gpio_config_t conf_output = {
.pin_bit_mask = 1 << LCD_LED_PIN,
.mode = GPIO_MODE_OUTPUT
};
gpio_config(&conf_output);
gpio_config_t conf_input = {
.pin_bit_mask = (1 << LVGL_INC_BTN),
.mode = GPIO_MODE_INPUT,
.pull_up_en = GPIO_PULLUP_ENABLE
};
gpio_config(&conf_input);
}
void LCD_init(spi_host_device_t host, esp_lcd_panel_io_handle_t *io_handler, esp_lcd_panel_handle_t *p_handler, lv_display_t *disp){
spi_bus_config_t spi_bus_conf ={
.sclk_io_num = LCD_SCK_PIN,
.mosi_io_num = LCD_SDA_PIN,
.miso_io_num = -1,
.max_transfer_sz = 0,
};
esp_lcd_panel_io_spi_config_t lcd_io_spi_conf = {
.cs_gpio_num = LCD_CS_PIN,
.dc_gpio_num = LCD_A0_PIN,
.spi_mode = 0,
.pclk_hz = 15*1000*1000, //15Mhz
.lcd_cmd_bits = 8,
.lcd_param_bits = 8,
.trans_queue_depth = 10,
.on_color_trans_done = lvgl_flush_ready,
.user_ctx = disp
};
esp_lcd_panel_dev_config_t panel_config = {
.reset_gpio_num = LCD_RESET_PIN,
.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_BGR,
.bits_per_pixel = 16
};
ESP_ERROR_CHECK(spi_bus_initialize(LCD_SPI_BUS, &spi_bus_conf, SPI_DMA_CH_AUTO));
ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi( (esp_lcd_spi_bus_handle_t) LCD_SPI_BUS, &lcd_io_spi_conf, io_handler));
ESP_ERROR_CHECK(esp_lcd_new_panel_ili9341(*io_handler, &panel_config, p_handler));
//reseta as variaveis internas do display para uma inicialização limpa
ESP_ERROR_CHECK(esp_lcd_panel_reset(*p_handler));
//inicia o display
ESP_ERROR_CHECK(esp_lcd_panel_init(*p_handler));
//liga o LED do display
ESP_ERROR_CHECK(gpio_set_level(LCD_LED_PIN, 1));
//liga o output do display
ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(*p_handler, true));
ESP_ERROR_CHECK(esp_lcd_panel_invert_color(*p_handler, false));
}
void LVGL_init(lv_display_t **display, lv_draw_buf_t *display_bufs[2], esp_lcd_panel_handle_t *lcd_handler, lv_indev_t **input_device){
lv_init();
//cria um display LVGL
*display = lv_display_create(LCD_H_RES, LCD_V_RES);
//cria buffers de escrita do display LVGL
display_bufs[0] = lv_draw_buf_create(LCD_H_RES,LCD_V_RES, LV_COLOR_FORMAT_RGB565, 0);
display_bufs[1] = lv_draw_buf_create(LCD_H_RES,LCD_V_RES, LV_COLOR_FORMAT_RGB565, 0);
//envia nosso LCD handler como argumento para os eventos do LVGL
lv_display_set_user_data(*display, lcd_handler);
lv_display_set_flush_cb(*display, lvgl_flush_cb);
lv_display_set_draw_buffers(*display, display_bufs[0], display_bufs[1]);
lv_display_set_color_format(*display, LV_COLOR_FORMAT_RGB565);
*input_device = lv_indev_create();
lv_indev_set_type(*input_device, LV_INDEV_TYPE_BUTTON);
lv_indev_set_read_cb(*input_device, lvgl_btn_read);
lv_indev_set_button_points(*input_device, btn_coords);
}
void timer_init(){
const esp_timer_create_args_t lvgl_tick_timer_args = {
.callback = &lvgl_tick_inc,
.name = "lvgl_tick"
};
esp_timer_handle_t lvgl_tick_timer = NULL;
ESP_ERROR_CHECK(esp_timer_create(&lvgl_tick_timer_args, &lvgl_tick_timer));
ESP_ERROR_CHECK(esp_timer_start_periodic(lvgl_tick_timer, 2 * 1000));
}
//=============================== LVGL functions ===============================
bool lvgl_flush_ready(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx){
lv_display_t *disp_driver = (lv_display_t *)user_ctx;
lv_display_flush_ready(disp_driver);
return false;
}
void lvgl_flush_cb(lv_display_t *disp, const lv_area_t *area, uint8_t *px_map){
esp_lcd_panel_handle_t *lcd_handler = (esp_lcd_panel_handle_t *)lv_display_get_user_data(disp);
if(lcd_handler != NULL){
esp_lcd_panel_draw_bitmap(*lcd_handler,area->x1, area->y1, area->x2+1, area->y2+1, px_map);
}
}
void lvgl_tick_inc(void *arg){
lv_tick_inc(2);
}
//Esse exemplo foi tirado da documentação do LVGL:
//links:
//btn counter: https://github.com/lvgl/lvgl/blob/b78a4de8984e7e9b76ec4fc0e437fc952435f433/examples/get_started/lv_example_get_started_2.c
//btn indev: https://docs.lvgl.io/master/porting/indev.html#button
static void btn_event_cb(lv_event_t * e)
{
lv_event_code_t code = lv_event_get_code(e);
lv_obj_t * btn = lv_event_get_target(e);
if(code == LV_EVENT_PRESSED) {
static uint8_t cnt = 0;
cnt++;
/*Get the first child of the button which is the label and change its text*/
lv_obj_t * label = lv_obj_get_child(btn, 0);
lv_label_set_text_fmt(label, "Button: %d", cnt);
}
}
void lvgl_example(){
lv_obj_t * btn = lv_button_create(lv_screen_active()); /*Add a button the current screen*/
lv_obj_set_pos(btn, 5, 10); /*Set its position*/
lv_obj_set_size(btn, 120, 50); /*Set its size*/
lv_obj_add_event_cb(btn, btn_event_cb, LV_EVENT_ALL, NULL); /*Assign a callback to the button*/
lv_obj_t * label = lv_label_create(btn); /*Add a label to the button*/
lv_label_set_text(label, "Button: 0"); /*Set the labels text*/
lv_obj_center(label);
}
void lvgl_btn_read(lv_indev_t * indev, lv_indev_data_t*data){
int btn_state = gpio_get_level(LVGL_INC_BTN);
data->state = LV_INDEV_STATE_RELEASED;
if(btn_state == 0){
data->state = LV_INDEV_STATE_PRESSED;
}
data->btn_id = 0;
}
|
Nesse post vamos dar continuidade a nosso aprendizado com a ESP-IDF, explorando o uso de componentes, timers, eventos, displays de LCD e mais.
Encontre tudo na Loja Eletrogate com frete grátis para compras acima de R$ 200