SOC-ESP32S3部分:31-ESP-LCD控制器库

飞书文档https://x509p6c8to.feishu.cn/wiki/Syy3wsqHLiIiQJkC6PucEJ7Snib

ESP 系列芯片可以支持市场上常见的 LCD(如 SPI LCD、I2C LCD、并行 LCD (Intel 8080)、RGB/SRGB LCD、MIPI DSI LCD 等)所需的各种时序。esp_lcd 控制器为上述各类 LCD 提供了一个统一的抽象驱动框架。

更多支持的接口例程可以查看:esp-idf/examples/peripherals/lcd

在开发LCD类应用时,我们可以优先选择IDF自带了部分驱动,例如NT35510 SSD1306 ST7789,这部分驱动位于esp-idf/components/esp_lcd中,或者,我们也可以在组件库中查找,例如gc9a01

复制代码
https://components.espressif.com/components?q=esp_lcd_gc9a01

这些驱动都是适配了esp_lcd控制器的,使用起来非常方便。

如果以上两个方法都找不到对应驱动呢?这时候就需要我们自己写了,有两种办法,

第一种是比较传统的,是把厂家提供的驱动文件,修改为ESP32的接口,例如SPI、IO相关的函数,这部分可以参考我们SPI课程中,使用SPI适配的ST7789屏幕驱动。

第二种是推荐大家用的,把驱动按esp_lcd框架的方式封装,找接近的芯片,更改部分参数即可,例如找不到ST7789的,我们可以找ST77916的,一般同一个厂家一系列的芯片,差异点只在初始化参数的不同。

复制代码
https://components.espressif.com/components?q=esp_lcd_st

最终参考esp-idf/examples/peripherals/lcd/spi_lcd_touch实现的驱动:

步骤如下

  1. 初始化背光IO
  2. 初始化LCD的SPI配置
  3. 初始化其它显示IO
  4. 初始化ST7789驱动
  5. 设置屏幕显示方向颜色,打开背光
  6. 使用绘制函数绘制图像esp_lcd_panel_draw_bitmap

初始化背光IO

复制代码
   gpio_config_t bk_gpio_config = {
       .mode = GPIO_MODE_OUTPUT, // 设置GPIO模式为输出
       .pin_bit_mask = 1ULL << EXAMPLE_PIN_NUM_BK_LIGHT // 设置背光控制引脚
   };
   ESP_ERROR_CHECK(gpio_config(&bk_gpio_config)); // 配置GPIO

初始化LCD的SPI配置

复制代码
   spi_bus_config_t buscfg = {
       .sclk_io_num = EXAMPLE_PIN_NUM_SCLK, // SCLK引脚编号
       .mosi_io_num = EXAMPLE_PIN_NUM_MOSI, // MOSI引脚编号
       .miso_io_num = GPIO_NUM_NC,          // MISO引脚编号
       .quadwp_io_num = GPIO_NUM_NC,        // QUADWP引脚编号(未使用)
       .quadhd_io_num = GPIO_NUM_NC,        // QUADHD引脚编号(未使用)
       .max_transfer_sz = EXAMPLE_LCD_H_RES * EXAMPLE_LCD_V_RES * sizeof(uint16_t), // 最大传输大小
   };
   ESP_ERROR_CHECK(spi_bus_initialize(LCD_HOST, &buscfg, SPI_DMA_CH_AUTO)); // 初始化SPI总线

初始化其它显示IO

复制代码
   ESP_LOGI(TAG, "Install panel IO");
   esp_lcd_panel_io_handle_t io_handle = NULL;
   esp_lcd_panel_io_spi_config_t io_config = {
       .dc_gpio_num = EXAMPLE_PIN_NUM_LCD_DC, // 数据/命令控制引脚编号
       .cs_gpio_num = EXAMPLE_PIN_NUM_LCD_CS, // 片选引脚编号
       .pclk_hz = EXAMPLE_LCD_PIXEL_CLOCK_HZ, // 像素时钟频率
       .lcd_cmd_bits = EXAMPLE_LCD_CMD_BITS, // 命令位数
       .lcd_param_bits = EXAMPLE_LCD_PARAM_BITS, // 参数位数
       .spi_mode = 3, // SPI模式
       .trans_queue_depth = 10, // 传输队列深度
   };
   // 将LCD连接到SPI总线
   ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)LCD_HOST, &io_config, &io_handle));

初始化ST7789驱动

复制代码
   // 安装st7789面板驱动
   esp_lcd_panel_handle_t panel_handle = NULL;
   esp_lcd_panel_dev_config_t panel_config = {
       .reset_gpio_num = EXAMPLE_PIN_NUM_LCD_RST,  // 复位引脚编号
       .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB, // RGB元素顺序
       .bits_per_pixel = 16,                       // 每像素位数
       .data_endian = LCD_RGB_DATA_ENDIAN_BIG,     // MSB
   };
   ESP_LOGI(TAG, "Install st7789 panel driver");
   ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(io_handle, &panel_config, &panel_handle));

设置屏幕显示方向颜色,打开背光

复制代码
   // 复位和初始化面板
   ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle)); // 复位面板
   ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle)); // 初始化面板
   ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel_handle, true)); // 反转颜色
   ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel_handle, true, false)); // 镜像显示(水平镜像)

   // 用户可以在点亮屏幕或背光之前刷新预定义的图案到屏幕上
   ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, true)); // 打开屏幕显示
  
   // 打开LCD背光
   ESP_LOGI(TAG, "Turn on LCD backlight");
   gpio_set_level(EXAMPLE_PIN_NUM_BK_LIGHT, EXAMPLE_LCD_BK_LIGHT_ON_LEVEL); // 设置背光引脚电平

使用绘制函数绘制图像esp_lcd_panel_draw_bitmap

复制代码
// 设置液晶屏颜色
void lcd_set_color(uint16_t color)
{
   // 分配内存 这里分配了液晶屏一行数据需要的大小
    uint16_t *buffer = (uint16_t *)heap_caps_malloc(EXAMPLE_LCD_H_RES * EXAMPLE_LCD_V_RES * sizeof(uint16_t), MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL);

    if (NULL == buffer)
    {
        ESP_LOGE(TAG, "Memory for bitmap is not enough");
    }
    else
    {
        for (size_t i = 0; i < EXAMPLE_LCD_H_RES * EXAMPLE_LCD_V_RES; i++) // 给缓存中放入颜色数据
        {
            buffer[i] = color;
        }
        esp_lcd_panel_draw_bitmap(panel_handle, 0 + X_OFFSET, 0, EXAMPLE_LCD_H_RES + X_OFFSET, EXAMPLE_LCD_V_RES, buffer);
        free(buffer); // 释放内存
    }
}

SPI屏幕

SPI屏幕使用的驱动芯片是ST7789,这个驱动芯片支持的分辨率是240*320,据厂家手册说明可知,由于是异形屏幕,屏幕的分辨率是172*320,所以驱动芯片左右两边分别有34列((240-172)/2) = 34是没有接到屏幕的,所以我们设置显示地址是,要偏移34列,屏幕接线部分如下:

|----------------------------------------------------------------------------|----------------------------------------------------------------------------|
| | |

从原理图可知:

复制代码
SPI SCLK为GPIO_NUM_16
SPI MOSI为GPIO_NUM_17
SPI MISO不需要
LCD_DC为GPIO_NUM_21
LCD_RST为GPIO_NUM_18
LCD_CS为GPIO_NUM_15
背光IO为GPIO_NUM_2,背光有效电平为低电平

具体代码如下

复制代码
#include <stdio.h>
#include <unistd.h>
#include <sys/lock.h>
#include <sys/param.h>
#include <string.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 = "example";

// Using SPI2 in the example
#define LCD_HOST  SPI2_HOST


 Please update the following configuration according to your LCD spec //

#define EXAMPLE_LCD_PIXEL_CLOCK_HZ     (20 * 1000 * 1000)
#define EXAMPLE_LCD_BK_LIGHT_ON_LEVEL  0
#define EXAMPLE_LCD_BK_LIGHT_OFF_LEVEL !EXAMPLE_LCD_BK_LIGHT_ON_LEVEL

#define EXAMPLE_PIN_NUM_SCLK           GPIO_NUM_16
#define EXAMPLE_PIN_NUM_MOSI           GPIO_NUM_17
#define EXAMPLE_PIN_NUM_MISO           GPIO_NUM_NC
#define EXAMPLE_PIN_NUM_LCD_DC         GPIO_NUM_21
#define EXAMPLE_PIN_NUM_LCD_RST        GPIO_NUM_18
#define EXAMPLE_PIN_NUM_LCD_CS         GPIO_NUM_15
#define EXAMPLE_PIN_NUM_BK_LIGHT       GPIO_NUM_2

// The pixel number in horizontal and vertical
#define EXAMPLE_LCD_H_RES              172
// Bit number used to represent command and parameter
#define EXAMPLE_LCD_V_RES              320
#define EXAMPLE_LCD_CMD_BITS           8
#define EXAMPLE_LCD_PARAM_BITS         8

// 列地址偏移示例(假设X起始偏移为0,Y起始偏移为40)
#define X_OFFSET 34
#define Y_OFFSET 0
#define Y_OFFSET_NUM 0

esp_lcd_panel_handle_t panel_handle = NULL;

typedef struct {
    int cmd;                /*<! The specific LCD command */
    const void *data;       /*<! Buffer that holds the command specific data */
    size_t data_bytes;      /*<! Size of `data` in memory, in bytes */
    unsigned int delay_ms;  /*<! Delay in milliseconds after this command */
} st7789_lcd_init_cmd_t;

typedef struct {
    const st7789_lcd_init_cmd_t *init_cmds;   
    uint16_t init_cmds_size;    /*<! Number of commands in above array */
    struct {
        unsigned int use_qspi_interface: 1;     /*<! Set to 1 if use QSPI interface, default is SPI interface */
    } flags;
} st7789_vendor_config_t;

static const st7789_lcd_init_cmd_t lcd_init_cmds [] ={
    /* {cmd, { data }, data_size, delay_ms} "*/
    // {0x2A, (uint8_t []){0x00, X_OFFSET, 0x00, 0xEF - X_OFFSET}, 4, 0}, // 列地址 0~171 (0xAB = 171)
    // {0x2B, (uint8_t []){0x00, Y_OFFSET, 0x01, 0x3F - Y_OFFSET}, 4, 0}, // 行地址 0~319
    {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 lcd_set_color(uint16_t color)
{
   // 分配内存 这里分配了液晶屏一行数据需要的大小
    uint16_t *buffer = (uint16_t *)heap_caps_malloc(EXAMPLE_LCD_H_RES * EXAMPLE_LCD_V_RES * sizeof(uint16_t), MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL);

    if (NULL == buffer)
    {
        ESP_LOGE(TAG, "Memory for bitmap is not enough");
    }
    else
    {
        for (size_t i = 0; i < EXAMPLE_LCD_H_RES * EXAMPLE_LCD_V_RES; i++) // 给缓存中放入颜色数据
        {
            buffer[i] = color;
        }
        esp_lcd_panel_draw_bitmap(panel_handle, 0 + X_OFFSET, 0, EXAMPLE_LCD_H_RES + X_OFFSET, EXAMPLE_LCD_V_RES, buffer);
        free(buffer); // 释放内存
    }
}

void app_main(void)
{

   // 关闭LCD背光
   ESP_LOGI(TAG, "Turn off LCD backlight");
   gpio_config_t bk_gpio_config = {
       .mode = GPIO_MODE_OUTPUT, // 设置GPIO模式为输出
       .pin_bit_mask = 1ULL << EXAMPLE_PIN_NUM_BK_LIGHT // 设置背光控制引脚
   };
   ESP_ERROR_CHECK(gpio_config(&bk_gpio_config)); // 配置GPIO

   // 初始化SPI总线
   ESP_LOGI(TAG, "Initialize SPI bus");
   spi_bus_config_t buscfg = {
       .sclk_io_num = EXAMPLE_PIN_NUM_SCLK, // SCLK引脚编号
       .mosi_io_num = EXAMPLE_PIN_NUM_MOSI, // MOSI引脚编号
       .miso_io_num = GPIO_NUM_NC,          // MISO引脚编号
       .quadwp_io_num = GPIO_NUM_NC,        // QUADWP引脚编号(未使用)
       .quadhd_io_num = GPIO_NUM_NC,        // QUADHD引脚编号(未使用)
       .max_transfer_sz = EXAMPLE_LCD_H_RES * EXAMPLE_LCD_V_RES * sizeof(uint16_t), // 最大传输大小
   };
   ESP_ERROR_CHECK(spi_bus_initialize(LCD_HOST, &buscfg, SPI_DMA_CH_AUTO)); // 初始化SPI总线

   // 安装面板IO
   ESP_LOGI(TAG, "Install panel IO");
   esp_lcd_panel_io_handle_t io_handle = NULL;
   esp_lcd_panel_io_spi_config_t io_config = {
       .dc_gpio_num = EXAMPLE_PIN_NUM_LCD_DC, // 数据/命令控制引脚编号
       .cs_gpio_num = EXAMPLE_PIN_NUM_LCD_CS, // 片选引脚编号
       .pclk_hz = EXAMPLE_LCD_PIXEL_CLOCK_HZ, // 像素时钟频率
       .lcd_cmd_bits = EXAMPLE_LCD_CMD_BITS, // 命令位数
       .lcd_param_bits = EXAMPLE_LCD_PARAM_BITS, // 参数位数
       .spi_mode = 3, // SPI模式
       .trans_queue_depth = 10, // 传输队列深度
   };
   // 将LCD连接到SPI总线
   ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)LCD_HOST, &io_config, &io_handle));

   st7789_vendor_config_t vendor_config = {  // 用于替换驱动组件中的初始化命令及参数
    .init_cmds = lcd_init_cmds,
    .init_cmds_size = sizeof(lcd_init_cmds) / sizeof(st7789_lcd_init_cmd_t),
    };

   esp_lcd_panel_dev_config_t panel_config = {
       .reset_gpio_num = EXAMPLE_PIN_NUM_LCD_RST,  // 复位引脚编号
       .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB, // RGB元素顺序
       .bits_per_pixel = 16,                       // 每像素位数
       .data_endian = LCD_RGB_DATA_ENDIAN_BIG,     // MSB
       .vendor_config = &vendor_config,           // 用于替换驱动组件中的初始化命令及参数
   };
   ESP_LOGI(TAG, "Install ST7789 panel driver");
   ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(io_handle, &panel_config, &panel_handle));
   // 复位和初始化面板
   ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle)); // 复位面板
   ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle)); // 初始化面板
   ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel_handle, true)); // 反转颜色
   ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel_handle, false, false)); // 镜像显示(水平镜像)

   // 用户可以在点亮屏幕或背光之前刷新预定义的图案到屏幕上
   ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, true)); // 打开屏幕显示

   // 打开LCD背光
   ESP_LOGI(TAG, "Turn on LCD backlight");
   gpio_set_level(EXAMPLE_PIN_NUM_BK_LIGHT, EXAMPLE_LCD_BK_LIGHT_ON_LEVEL); // 设置背光引脚电平

   while(1){
    lcd_set_color(0xFFFF);
    vTaskDelay(1000 / portTICK_PERIOD_MS);
    ESP_LOGI(TAG, "lcd_set_color switch");
    lcd_set_color(0x001F);
    vTaskDelay(1000 / portTICK_PERIOD_MS);
    ESP_LOGI(TAG, "lcd_set_color switch");
   }
}

如果编译报错fatal error: esp_lcd_panel_io.h: No such file or directory

需要在main/CMakeLists.txt中添加esp_lcd组件

复制代码
idf_component_register(SRCS "main.c"
                    PRIV_REQUIRES esp_lcd
                    INCLUDE_DIRS "")
相关推荐
逐步前行16 小时前
STM32_USART_寄存器操作
stm32·单片机·嵌入式硬件
沐欣工作室_lvyiyi16 小时前
基于单片机的多参数监护仪系统(论文+源码)
stm32·单片机·嵌入式硬件·多参数监护仪
zhaoshuzhaoshu16 小时前
BLE(蓝牙低功耗)连接过程详解
物联网·蓝牙·无线
搜佛说16 小时前
下一代跨语言原生操作系统商业计划书
物联网·软件工程
BY组态16 小时前
Ricon组态系统在实际项目中的应用案例分享
物联网·web组态·组态
熬夜有啥好17 小时前
51单片机(1)
单片机·嵌入式硬件·51单片机
DLGXY17 小时前
STM32(二十六)——WDG看门狗
stm32·单片机·嵌入式硬件
集芯微电科技有限公司17 小时前
AD536A高性能真有效值直流转换电路替代PC2909
人工智能·单片机·嵌入式硬件·神经网络·生成对抗网络
可乐鸡翅好好吃17 小时前
关于频率(HZ)与周期(ms)的转换
单片机·嵌入式硬件
进击的横打18 小时前
【车载开发系列】RH850常用的定时器
单片机·嵌入式硬件·rh850