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.
Antes de começar, vamos fazer um breve resumo dos tópicos abordados no post anterior:
Por fim, em nosso último projeto vimos como incluir bibliotecas de terceiros e fizemos uso de alguns drivers ainda não abordados. Nesse post não só vamos explicar o uso desses drivers, como ao decorrer do texto vamos falar de forma mais aprofundada sobre o que foi abordado anteriormente.
Outro ponto importante para citar é que essa sequência de artigos é baseada na versão 5.x da IDF, a maioria dos drives não teve grandes mudanças entre as versões passadas, mas a configuração do projeto pode ser incompatível, então sempre verifique a documentação oficial para ficar por dentro das novidades.
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.
O Component Manager é a fermenta da ESP-IDF que vai gerenciar os componentes, ele automaticamente vai baixar e incluir as dependências no nosso projeto, é bastante semelhante a “Library Manager” presente na Arduino IDE.
com o nosso projeto aberto, vamos abrir o “Command palette” do vscode, isso pode ser feito com pressionando as teclas: Ctrl + Shift + P ou indo em View->Command palette, isso vai abrir a aba de comandos do editor, nessa aba basta digitar: “ESP-IDF: Show ESP Component Registry” e isso vai abrir a pagina de componentes da 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!
caso não esteja usando o vscode, a instalação de um componente ainda é bem simples, primeiro nos temos que acessar a pagina “ESP Component Registry“, essa pagina é exatamente a mesma que a do Vscode, a única diferença aqui é que em vez de clicar instalar você vai rodar o comando especificado na linha “To add this component to your project, run:” na pasta do projeto.
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.
a ESP-IDF tem 3 tipos de timers diferentes, FreeRTOS timer, esp-timer e GPTimer timer, todos eles podem ser usados para controle de tempo e geração de eventos, mas cada um deles tem uma peculiaridade que o torna especial.
Esse é um software timer provido pelo FreeRTOS, aqui no blog eletrogate temos um artigo inteiro sobre a configuração e uso desse timer: FreeRTOS – Temporizadores de Software – Blog Eletrogate.
Assim como o FreeRTOS, esse também é um software timer, mas diferente do timer do FreeRTOS que usa ticks do RTOS, o esp-timer usa o clock interno do ESP32, então ele tem uma resolução muito maior e consequentemente uma precisão maior.
A configuração do esp-timer é bem simples e requer apenas 1 função e uma struct de configuração, para configurar o esp-timer temos:
<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.O esp timer pode ser usado de duas formas:
esp_timer_start_once(esp_timer_handle_t, uint64_t)
.esp_timer_start_periodic(esp_timer_handle_t, uint64_t)
.Ambas as funções recebem como primeiro argumento o handler do timer e como segundo argumento o tempo em microssegundos para o evento ocorrer.
uma lista de coisas que você deve ficar atento ao usar esp-timer:
esp_timer_stop(esp_timer_handle_t)
.(Referencia: ESP Timer (High Resolution Timer) – ESP32 – — ESP-IDF Programming Guide latest documentation (espressif.com))
Diferente dos timers anteriores, esse é uma representação dos hardware timers do ESP32, com eles nos temos acesso a funcionalidades mais complexas, com uma resolução muito maior do que a dos timers anteriores, o que tornar os GPTimer muito importante em aplicações em que o controle de tempo é extremamente importante, o uso dos GPTimers é mais difícil do que os software timers, garantir seu funcionamento requer conhecimento mais avançado sobre o hardware do ESP32.
Para configurar o GPTimer é necessário:
<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_DOWN
GPTIMER_COUNT_UP
resolution_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 GPTimerPronto GPTimer configurado!
O GPTimer pode ser usado para gerar eventos “one-shot” e periódicos ou como um “Wall Clock” (mecanismo para contagem de tempo, sem eventos), para gerar eventos devemos seguir os passos:
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).para iniciar o timer basta chamar a função: 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)
uma lista de coisas que você deve ficar atento ao usar GPTimer:
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; }
Veja que além do timers nos fizemos uso das GPIOs no modo “INPUT_OUTPUT” que permitiu a leitura e escrita da porta sem a necessidade de guardar o estado atual em uma variável.
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.Após a configuração, basta chamar a função: 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:
Exemplo: obtendo valor máximo de resolução:
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ãoCom a struct configurada, basta chamar a função: “ledc_channel_config(const ledc_channel_config_t*)
” passando um ponteiro para nossa struct de configuração.
Após a configuração, o sinal PWM vai imediatamente iniciar, podemos alterar o Duty-cycle usando as fuinções:
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.
Para pausar e resumir o sinal PWM usamos as funções:
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; }
Com esse assunto finalizado podemos ir a o foco do projeto desse Post, o uso de displays LCD gráficos usando LVGL.
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.
Essa configuração vai dizer para nossa placa como ela deve se comunicar com o display, para isso temos que executar os seguintes passos:
<esp_lcd_panel_io.h>
esp_lcd_panel_io_handle_t
esp_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)isso é tudo que temos que fazer por agora, a próxima etapa é configurar a interface do LCD.
Essa é a configuração que vai dizer para nossa placa o formato da informação que ela deve enviar para o display, para isso temos que executar os seguintes passos:
<esp_lcd_panel_io.h>
esp_lcd_panel_handle_t
esp_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_BGR
bits_per_pixel
: quantidade de Bits de cores (nosso display possui 16Bits de cores)Com o I/O configurado nosso próximo passo é iniciar o bus SPI.
(No último post nos vimos apenas os protocolos UART e I2C, mas não o SPI, isso porque a configuração e uso do SPI é mais complexa que a dos outros protocolos e inclui tópicos ainda não abordados como interrupções e DMA, nesse post vamos fazer apenas a inicialização do Bus e deixar SPI para um próximo post onde falaremos de forma mais aprofundadas das features do ESP32.)
Com o display já configurado o ultimo passo é configurar o periférico usado na comunicação, no nosso caso temos que configurar o SPI, para isso basta seguir os passos:
<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.Com todas as structs configuradas podemos iniciar o driver LCD!
Para iniciar o driver temos que chamar as funções de configuração na seguinte ordem:
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:Infelizmente esp-idf não tem uma implementação para o controlador do nosso display, mas podemos instalar um compatível pelo gerenciador de componentes!
Seguindo os passos do primeiro tópico desse post:
com a biblioteca instalada:
<esp_lcd_ili9341.h>
esp_lcd_new_panel_ili9341
, seguindo o ultimo passo da inicialização do driver.Com o display configurado, a ultima etapa é inicializar, para isso: chame as funções: 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 160×128, mas o funcionamento é igual para o display 128×128)
/* 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.Nos conseguimos enviar dados para o display, mas como monta uma interface a partir disso? manipular diretamente um array de cores para exibir informações é uma tarefa muito complexa para o dia a dia, por sorte, existem soluções prontas para isso, se você Arduino a algum tempo, deve conhecer coisas como Adafruit GFX, uma biblioteca gráfica muito popular na comunidade maker, mas apesar de sua facilidade de uso, a Adafruit GFX não tem tantas funcionalidades quando se fala da criação de interfaces gráficas profissionais, para esses casos existe a:
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!
Podemos instalar a LVGL direto pelo gerenciador de componentes:
A LVGL já possui APIs prontas para seu uso com ESP-IDF e FreeRTOS, mas nesse post vamos ver como criar um porte do zero para usar LVGL.
(não se preocupe em entender tudo agora, assim como no post passado, vamos fazer um breve introdução e posteriormente abordar com mais detalhes)
para iniciar o uso da LVGL temos que chamar a função: 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 issocom essas 4 funções implementadas já podemos fazer usa da LGVL, vamos ver um exemplo dessa implementação da LVGL e fazer um Hello world.
Seguindo o mesmo esquema usado no exemplo de LCD:
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; }
Com isso finalizamos a segunda parte da ESP-IDF, dúvidas, dicas e sugestões são sempre bem vindas nos comentários, obrigado por ler até aqui e até a próxima!
|
A Eletrogate é uma loja virtual de componentes eletrônicos do Brasil e possui diversos produtos relacionados à Arduino, Automação, Robótica e Eletrônica em geral.
Tenha a Metodologia Eletrogate dentro da sua Escola! Conheça nosso Programa de Robótica nas Escolas!