Controlador esp_lcd no ESP32S3: Unificando a Interação com Displays LCD

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:

  1. **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.
  2. **Abordagem Recomendada com esp_lcd:** Encapsular o driver do display de acordo com a estrutura do framework esp_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 "")

Tags: ESP32S3 esp_lcd SPI LCD ST7789 Controlador LCD

Publicado em 7-18 10:38