A família de microcontroladores ESP oferece um suporte robusto para uma vasta gama de displays LCD, abrangendo diversas interfaces como SPI, I2C, Paralela (Intel 8080), RGB/SRGB e MIPI DSI. Para simplificar o desenvolvimento e proporcionar uma experiência de programação consistente, o ESP-IDF inclui a biblioteca controladora esp_lcd. Esta biblioteca atua como uma camada de abstração unificada, permitindo que os desenvolvedores interajam com diferentes tipos de displays LCD de maneira padronizada.
Para explorar exemplos de uso e encontrar drivers específicos para várias interfaces, consulte o diretório esp-idf/examples/peripherals/lcd. Ao desenvolver aplicações com LCD, o ESP-IDF já fornece drivers para chipsets populares como NT35510, SSD1306 e ST7789, localizados em esp-idf/components/esp_lcd. Além disso, a Biblioteca de Componentes da Espressif é uma excelente fonte para drivers de displays adicionais, como o GC9A01, que são projetados para serem compatíveis com o controlador esp_lcd, facilitando sua integração.
Caso não encontre um driver pré-existente para seu display específico, existem duas abordagens para desenvolver um driver personalizado:
- **Adaptação Tradicional:** Modificar o código-fonte de um driver fornecido pelo fabricante para se alinhar às APIs de periféricos do ESP32, como SPI e GPIO. Esta abordagem exige uma compreensão detalhada das funções de interface do ESP32 e do driver original.
- **Abordagem Recomendada com
esp_lcd:** Encapsular o driver do display de acordo com a estrutura do frameworkesp_lcd. Uma estratégia eficaz é identificar um driver existente para um chip similar (por exemplo, usar um driver ST77916 como base para um ST7789) e ajustar os parâmetros de inicialização conforme necessário. Fabricantes geralmente utilizam conjuntos de chips com pequenas variações, o que torna esta abordagem eficiente. A Biblioteca de Componentes da Espressif também pode ser útil para encontrar drivers de referência para chips relacionados.
A seguir, detalhamos a implementação de um driver utilizando o framework esp_lcd para um display SPI com controlador ST7789, similar ao exemplo spi_lcd_touch do ESP-IDF. Os passos envolvem a configuração inicial dos periféricos, a instalação do driver do painel e a funcionalidade de desenho.
Exemplo de Integração de Display SPI ST7789
O display utilizado neste exemplo é um ST7789 com resolução nativa de 240x320 pixels, mas o painel físico possui uma resolução efetiva de 172x320. Isso implica que 34 colunas em cada lado do controlador ((240 - 172) / 2 = 34) não estão conectadas ao display, exigindo um deslocamento de coluna (X\_OFFSET) de 34 pixels para o mapeamento correto da área visível.
O hardware específico para este display no ESP32S3 é configurado da seguinte forma:
- **SPI SCLK:** GPIO_NUM_16
- **SPI MOSI:** GPIO_NUM_17
- **SPI MISO:** Não utilizado (GPIO_NUM_NC)
- **LCD_DC (Data/Command):** GPIO_NUM_21
- **LCD_RST (Reset):** GPIO_NUM_18
- **LCD_CS (Chip Select):** GPIO_NUM_15
- **Backlight (Retroiluminação):** GPIO_NUM_2 (ativo em nível baixo)
Configuração do Backlight
A primeira etapa é inicializar o GPIO responsável pelo controle da retroiluminação do display.
gpio_config_t config_bk_luz = {
.mode = GPIO_MODE_OUTPUT,
.pin_bit_mask = 1ULL << GPIO_NUM_2 // Mapeia para GPIO_NUM_2 para controle da retroiluminação
};
ESP_ERROR_CHECK(gpio_config(&config_bk_luz));
// Inicialmente, desliga a retroiluminação
gpio_set_level(GPIO_NUM_2, 0); // Supondo nível baixo para 'ligar' e nível alto para 'desligar'
Inicialização do Barramento SPI
Em seguida, configura-se o barramento SPI que será utilizado para a comunicação com o display.
spi_bus_config_t config_barramento_spi = {
.sclk_io_num = GPIO_NUM_16, // Pino SCLK
.mosi_io_num = GPIO_NUM_17, // Pino MOSI
.miso_io_num = GPIO_NUM_NC, // MISO não usado para este display
.quadwp_io_num = GPIO_NUM_NC, // Não usado
.quadhd_io_num = GPIO_NUM_NC, // Não usado
.max_transfer_sz = 172 * 320 * sizeof(uint16_t), // Tamanho máximo da transferência (largura * altura * 2 bytes por pixel)
};
ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &config_barramento_spi, SPI_DMA_CH_AUTO)); // Usa SPI2
Configuração do Painel IO do LCD
Após a inicialização do barramento SPI, configura-se a interface de E/S do painel LCD, conectando-o ao barramento SPI.
esp_lcd_panel_io_handle_t manipulador_io_painel = NULL;
esp_lcd_panel_io_spi_config_t config_io_painel = {
.dc_gpio_num = GPIO_NUM_21, // Pino Data/Command
.cs_gpio_num = GPIO_NUM_15, // Pino Chip Select
.pclk_hz = 20 * 1000 * 1000, // Frequência do clock de pixel (20MHz)
.lcd_cmd_bits = 8, // 8 bits para comandos
.lcd_param_bits = 8, // 8 bits para parâmetros
.spi_mode = 3, // Modo SPI 3
.trans_queue_depth = 10, // Profundidade da fila de transmissão
};
ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)SPI2_HOST, &config_io_painel, &manipulador_io_painel));
Instalação do Driver do Painel ST7789
O driver do painel ST7789 é então instalado, incluindo a configuração de inicialização específica para o chip. Este exemplo utiliza uma estrutura de comandos de inicialização personalizada, definida como comandos_iniciais_st7789.
// Comandos de inicialização ST7789 personalizados
static const st7789_lcd_init_cmd_t comandos_iniciais_st7789 [] = {
{0x11, (uint8_t []){0x00}, 0, 0},
{0x36, (uint8_t []){0x00}, 1, 0},
{0x3A, (uint8_t []){0x05}, 1, 0},
{0xB2, (uint8_t []){0x0C, 0x0C, 0x00, 0x33, 0x33}, 5, 0},
{0xB7, (uint8_t []){0x35}, 1, 0},
{0xBB, (uint8_t []){0x35}, 1, 0},
{0xC0, (uint8_t []){0x2C}, 1, 0},
{0xC2, (uint8_t []){0x01}, 1, 0},
{0xC3, (uint8_t []){0x13}, 1, 0},
{0xC4, (uint8_t []){0x20}, 1, 0},
{0xC6, (uint8_t []){0x0F}, 1, 0},
{0xD0, (uint8_t []){0xA4, 0xA1}, 2, 0},
{0xD6, (uint8_t []){0xA1}, 1, 0},
{0xE0, (uint8_t []){0xF0, 0x00, 0x04, 0x04, 0x04, 0x05, 0x29, 0x33, 0x3e, 0x38, 0x12, 0x12, 0x28, 0x30}, 14, 0},
{0xE1, (uint8_t []){0xF0, 0x07, 0x0A, 0x0D, 0x0b, 0x07, 0x28, 0x33, 0x3e, 0x36, 0x14, 0x14, 0x29, 0x32}, 14, 0},
{0x21, (uint8_t []){0x00}, 0, 0},
{0x11, (uint8_t []){0x00}, 0, 120},
{0x29, (uint8_t []){0x00}, 0, 0},
};
st7789_vendor_config_t config_fornecedor = {
.init_cmds = comandos_iniciais_st7789,
.init_cmds_size = sizeof(comandos_iniciais_st7789) / sizeof(st7789_lcd_init_cmd_t),
};
esp_lcd_panel_handle_t manipulador_painel_lcd = NULL;
esp_lcd_panel_dev_config_t config_painel_dev = {
.reset_gpio_num = GPIO_NUM_18, // Pino de Reset
.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB, // Ordem dos elementos RGB
.bits_per_pixel = 16, // 16 bits por pixel
.data_endian = LCD_RGB_DATA_ENDIAN_BIG, // Endianness dos dados
.vendor_config = &config_fornecedor, // Configurações específicas do fornecedor
};
ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(manipulador_io_painel, &config_painel_dev, &manipulador_painel_lcd));
Configuração e Operação do Display
Após a instalação do driver, o display é reiniciado, inicializado e suas propriedades de exibição são definidas, como inversão de cor e espelhamento. A retroiluminação é ligada após a inicialização do display.
// Reset e inicialização do painel
ESP_ERROR_CHECK(esp_lcd_panel_reset(manipulador_painel_lcd));
ESP_ERROR_CHECK(esp_lcd_panel_init(manipulador_painel_lcd));
ESP_ERROR_CHECK(esp_lcd_panel_invert_color(manipulador_painel_lcd, true)); // Inverter cores
ESP_ERROR_CHECK(esp_lcd_panel_mirror(manipulador_painel_lcd, false, false)); // Sem espelhamento
// Ligar o display
ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(manipulador_painel_lcd, true));
// Ligar a retroiluminação (nível baixo, conforme especificado)
gpio_set_level(GPIO_NUM_2, 0); // Ajuste conforme seu hardware
Função de Desenho: Preencher a Tela
Uma função de exemplo para preencher o display com uma cor uniforme. Ela aloca um buffer, preenche-o com a cor especificada e utiliza esp_lcd_panel_draw_bitmap para renderizar o conteúdo. O X_OFFSET é aplicado para compensar a área não utilizada do controlador.
void preencher_tela_com_cor(uint16_t cor_fundo)
{
// Aloca memória para o framebuffer completo
uint16_t *buffer_pixels = (uint16_t *)heap_caps_malloc(172 * 320 * sizeof(uint16_t), MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL);
if (buffer_pixels == NULL) {
ESP_LOGE("APP_MAIN", "Memória insuficiente para o buffer de pixels");
return;
}
for (size_t i = 0; i < 172 * 320; i++) {
buffer_pixels[i] = cor_fundo;
}
// Desenha o bitmap na tela, aplicando o X_OFFSET
// Os parâmetros são: handle do painel, x_start, y_start, x_end, y_end, buffer de dados
esp_lcd_panel_draw_bitmap(manipulador_painel_lcd, 0 + 34, 0, 172 + 34, 320, buffer_pixels);
free(buffer_pixels);
}
Função Principal (app_main)
A função principal integra todos os passos de inicialização e demonstra a alternância de cores na tela.
#include <stdio.h>
#include <unistd.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_lcd_panel_io.h"
#include "esp_lcd_panel_vendor.h"
#include "esp_lcd_panel_ops.h"
#include "driver/gpio.h"
#include "driver/spi_master.h"
#include "esp_err.h"
#include "esp_log.h"
static const char *TAG = "APP_MAIN";
// Definições de Pinos e Configurações do Display
#define SPI_HOST_ID SPI2_HOST
#define DISPLAY_CLK_HZ (20 * 1000 * 1000) // 20 MHz
#define PIN_SCLK GPIO_NUM_16
#define PIN_MOSI GPIO_NUM_17
#define PIN_MISO GPIO_NUM_NC // Não utilizado
#define PIN_LCD_DC GPIO_NUM_21
#define PIN_LCD_RST GPIO_NUM_18
#define PIN_LCD_CS GPIO_NUM_15
#define PIN_BACKLIGHT GPIO_NUM_2
#define BACKLIGHT_ON_LEVEL 0 // Nível baixo para ligar
#define DISPLAY_WIDTH_PX 172
#define DISPLAY_HEIGHT_PX 320
#define CMD_BITS 8
#define PARAM_BITS 8
#define COL_OFFSET_PX 34 // Deslocamento devido ao layout do controlador
esp_lcd_panel_handle_t manipulador_painel_global = NULL; // Usado para acesso global
// Estruturas para inicialização do ST7789
typedef struct {
int cmd;
const void *data;
size_t data_bytes;
unsigned int delay_ms;
} st7789_init_cmd_t;
typedef struct {
const st7789_init_cmd_t *init_cmds;
uint16_t init_cmds_size;
} st7789_config_personalizada_t;
static const st7789_init_cmd_t comandos_iniciais_st7789[] = {
{0x11, (uint8_t []){0x00}, 0, 0},
{0x36, (uint8_t []){0x00}, 1, 0},
{0x3A, (uint8_t []){0x05}, 1, 0},
{0xB2, (uint8_t []){0x0C, 0x0C, 0x00, 0x33, 0x33}, 5, 0},
{0xB7, (uint8_t []){0x35}, 1, 0},
{0xBB, (uint8_t []){0x35}, 1, 0},
{0xC0, (uint8_t []){0x2C}, 1, 0},
{0xC2, (uint8_t []){0x01}, 1, 0},
{0xC3, (uint8_t []){0x13}, 1, 0},
{0xC4, (uint8_t []){0x20}, 1, 0},
{0xC6, (uint8_t []){0x0F}, 1, 0},
{0xD0, (uint8_t []){0xA4, 0xA1}, 2, 0},
{0xD6, (uint8_t []){0xA1}, 1, 0},
{0xE0, (uint8_t []){0xF0, 0x00, 0x04, 0x04, 0x04, 0x05, 0x29, 0x33, 0x3e, 0x38, 0x12, 0x12, 0x28, 0x30}, 14, 0},
{0xE1, (uint8_t []){0xF0, 0x07, 0x0A, 0x0D, 0x0b, 0x07, 0x28, 0x33, 0x3e, 0x36, 0x14, 0x14, 0x29, 0x32}, 14, 0},
{0x21, (uint8_t []){0x00}, 0, 0},
{0x11, (uint8_t []){0x00}, 0, 120},
{0x29, (uint8_t []){0x00}, 0, 0},
};
void preencher_tela_com_cor(uint16_t cor_fundo)
{
uint16_t *buffer_pixels = (uint16_t *)heap_caps_malloc(DISPLAY_WIDTH_PX * DISPLAY_HEIGHT_PX * sizeof(uint16_t), MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL);
if (buffer_pixels == NULL) {
ESP_LOGE(TAG, "Memória insuficiente para o buffer de pixels");
return;
}
for (size_t i = 0; i < DISPLAY_WIDTH_PX * DISPLAY_HEIGHT_PX; i++) {
buffer_pixels[i] = cor_fundo;
}
ESP_ERROR_CHECK(esp_lcd_panel_draw_bitmap(manipulador_painel_global, COL_OFFSET_PX, 0, DISPLAY_WIDTH_PX + COL_OFFSET_PX, DISPLAY_HEIGHT_PX, buffer_pixels));
free(buffer_pixels);
}
void app_main(void)
{
// 1. Configurar GPIO para retroiluminação
ESP_LOGI(TAG, "Configurando GPIO para retroiluminação");
gpio_config_t config_gpio_bk = {
.mode = GPIO_MODE_OUTPUT,
.pin_bit_mask = 1ULL << PIN_BACKLIGHT
};
ESP_ERROR_CHECK(gpio_config(&config_gpio_bk));
gpio_set_level(PIN_BACKLIGHT, !BACKLIGHT_ON_LEVEL); // Desliga a retroiluminação inicialmente
// 2. Inicializar barramento SPI
ESP_LOGI(TAG, "Inicializando barramento SPI");
spi_bus_config_t config_spi_bus = {
.sclk_io_num = PIN_SCLK,
.mosi_io_num = PIN_MOSI,
.miso_io_num = PIN_MISO,
.quadwp_io_num = GPIO_NUM_NC,
.quadhd_io_num = GPIO_NUM_NC,
.max_transfer_sz = DISPLAY_WIDTH_PX * DISPLAY_HEIGHT_PX * sizeof(uint16_t),
};
ESP_ERROR_CHECK(spi_bus_initialize(SPI_HOST_ID, &config_spi_bus, SPI_DMA_CH_AUTO));
// 3. Instalar Painel IO
ESP_LOGI(TAG, "Instalando Painel IO do LCD");
esp_lcd_panel_io_handle_t manipulador_io_painel = NULL;
esp_lcd_panel_io_spi_config_t config_io_spi = {
.dc_gpio_num = PIN_LCD_DC,
.cs_gpio_num = PIN_LCD_CS,
.pclk_hz = DISPLAY_CLK_HZ,
.lcd_cmd_bits = CMD_BITS,
.lcd_param_bits = PARAM_BITS,
.spi_mode = 3,
.trans_queue_depth = 10,
};
ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)SPI_HOST_ID, &config_io_spi, &manipulador_io_painel));
// 4. Instalar driver do painel ST7789
ESP_LOGI(TAG, "Instalando driver do painel ST7789");
st7789_config_personalizada_t config_forn = {
.init_cmds = comandos_iniciais_st7789,
.init_cmds_size = sizeof(comandos_iniciais_st7789) / sizeof(st7789_init_cmd_t),
};
esp_lcd_panel_dev_config_t config_painel = {
.reset_gpio_num = PIN_LCD_RST,
.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB,
.bits_per_pixel = 16,
.data_endian = LCD_RGB_DATA_ENDIAN_BIG,
.vendor_config = &config_forn,
};
ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(manipulador_io_painel, &config_painel, &manipulador_painel_global));
// 5. Reset e Inicialização do Painel, e configurações de exibição
ESP_LOGI(TAG, "Reiniciando e inicializando o painel LCD");
ESP_ERROR_CHECK(esp_lcd_panel_reset(manipulador_painel_global));
ESP_ERROR_CHECK(esp_lcd_panel_init(manipulador_painel_global));
ESP_ERROR_CHECK(esp_lcd_panel_invert_color(manipulador_painel_global, true));
ESP_ERROR_CHECK(esp_lcd_panel_mirror(manipulador_painel_global, false, false)); // Sem espelhamento
// 6. Ligar o display e a retroiluminação
ESP_LOGI(TAG, "Ligando o display e a retroiluminação LCD");
ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(manipulador_painel_global, true));
gpio_set_level(PIN_BACKLIGHT, BACKLIGHT_ON_LEVEL); // Ligar a retroiluminação
// Loop principal para alternar cores
while(1) {
preencher_tela_com_cor(0xFFFF); // Branco
vTaskDelay(pdMS_TO_TICKS(1000));
ESP_LOGI(TAG, "Cor da tela: Branco");
preencher_tela_com_cor(0x001F); // Azul
vTaskDelay(pdMS_TO_TICKS(1000));
ESP_LOGI(TAG, "Cor da tela: Azul");
}
}
Para garantir que o componente esp_lcd seja corretamente incluído na compilação, adicione a seguinte linha ao seu arquivo main/CMakeLists.txt:
idf_component_register(SRCS "main.c"
PRIV_REQUIRES esp_lcd
INCLUDE_DIRS "")