esp-iot-solution SPI LCD 驱动详解:接口模式、驱动流程与组件移植实战 esp-iot-solution SPI LCD 驱动详解接口模式、驱动流程与组件移植实战【免费下载链接】esp-iot-solutionEspressif IoT Library. IoT Device Drivers, Documentations and Solutions.项目地址: https://gitcode.com/GitHub_Trending/es/esp-iot-solutionSPISerial Peripheral Interface是目前 ESP32 系列芯片驱动小尺寸 LCD 屏最常用的接口之一它接线简单、占用引脚少在物联网面板、可穿戴设备、温控器等场景中广泛使用。本文以 esp-iot-solution 开源仓库中 docs/zh_CN/display/lcd/spi_lcd.rst 为核心骨架以ST7789为例系统讲解 SPI LCD 的 4 种接口模式并基于 ESP-IDFesp_lcd框架拆解初始化接口设备 → 移植驱动组件 → 初始化 LCD 设备的完整驱动流程最后结合仓库内 esp_lcd_gc9b71 等驱动组件的真实源码进行印证。读完本文你将掌握 SPI LCD 的选型判断读哪几根引脚、配哪些参数、驱动移植的标准步骤以及刷新图像时关于 DMA、缓存与分包的底层注意事项。术语准备在进入接口模式讨论之前先熟悉几个 LCD 常用术语便于理解后续的命令与时序描述。完整术语表见仓库文档 LCD 术语表术语含义GRAMGraphic RAM 的缩写用于保存图形数据的存储区域TE用于指示屏幕刷新垂直同步或水平同步的时序信号Porch在显示一行或一帧图像数据之前的空白时间间隔VSYNC垂直同步信号HSYNC水平同步信号SPI LCD 的接口模式以 ST7789 为例不同的接口模式需要主控采用不同的接线和驱动方式。下面以ST7789为例介绍几种常见的接口模式。ST7789通过IM[3:0]引脚来选择Interface I/II和3/4-line的配置二者组合可以实现 4 种不同的接口模式下图是ST7789的SPI接口引脚描述注意 SPI 引脚名称约定CS、SCK(SCL)、SDA(MOSI)、SDO(MISO)、DC(RS)Interface I/II 模式Interface I和Interface II的主要区别在于是否仅用一根数据线如仅用 MOSI实现数据的读取和写入。模式是否仅用一根数据线实现数据的读取和写入ESP 是否支持Interface I是是Interface II否是在驱动层面对应的关键点是若 LCD 驱动 IC 配置为Interface I模式软件只需设置mosi_io_num为其数据线 IO而将miso_io_num设为-1详见下文初始化总线。3/4-line 模式3-line和4-line的主要区别在于是否使用 D/C 信号线D/C 用于区分当前传输的是命令还是数据。模式是否使用 D/C 信号线ESP 是否支持3-line否否4-line是是注意3-line模式有时也称为3-wire或9-bit模式命令/数据不靠 D/C 引脚区分而是通过 9 bit 数据帧的最高位来标记。虽然 ESP 的 SPI 外设硬件不支持 LCD 的3-line模式但可以通过软件模拟实现。仓库中的 esp_lcd_panel_io_additions 组件正是为此设计它提供esp_lcd_new_panel_io_3wire_spi()函数利用 GPIO 或 IO 扩展器进行 bit-banging位翻转模拟时序专门用于3-Wire SPI Parallel RGB 接口类屏幕通常用于完成 RGB LCD 的初始化。SPI LCD 驱动流程总览SPI LCD 驱动流程可大致分为三个部分初始化接口设备先初始化 SPI 总线再基于总线创建esp_lcd_panel_io接口设备移植驱动组件实现esp_lcd_panel_t中的各项回调为具体的 LCD 驱动 IC如 ST7789、GC9B71编写驱动初始化 LCD 设备通过驱动组件创建 LCD 设备句柄并使用 LCD 通用 API 完成复位、初始化、显示开关等操作。下面基于 ESP-IDF release/v6.0 中的spi_lcd_touch示例逐部分展开。第一步初始化接口设备初始化接口设备需要先初始化总线再创建接口设备。每个接口设备对应一个 SPI master 设备如果有多个设备同时使用同一 SPI 总线总线只需初始化一次。初始化总线示例代码#include driver/spi_master.h // 依赖的头文件 #include esp_check.h spi_bus_config_t buscfg { .sclk_io_num EXAMPLE_PIN_NUM_SCLK, // 连接 LCD SCKSCL 信号的 IO 编号 .mosi_io_num EXAMPLE_PIN_NUM_MOSI, // 连接 LCD MOSISDO、SDA 信号的 IO 编号 .miso_io_num EXAMPLE_PIN_NUM_MISO, // 连接 LCD MISOSDI 信号的 IO 编号如果不需要从 LCD 读取数据可以设为 -1 .quadwp_io_num -1, // 必须设置且为 -1 .quadhd_io_num -1, // 必须设置且为 -1 .max_transfer_sz EXAMPLE_LCD_H_RES * 80 * sizeof(uint16_t), // 表示 SPI 单次传输允许的最大字节数上限通常设为全屏大小即可 }; ESP_ERROR_CHECK(spi_bus_initialize(LCD_HOST, buscfg, SPI_DMA_CH_AUTO)); // 第 1 个参数表示使用的 SPI 主机 ID和后续创建接口设备时保持一致 // 第 3 个参数表示使用的 DMA 通道号默认设置为 SPI_DMA_CH_AUTO 即可配置参数说明若 LCD 驱动 IC 配置为Interface I接口模式软件仅需设置mosi_io_num为其数据线 IO而设置miso_io_num为 -1。关于max_transfer_sz的底层逻辑SPI 驱动在传输数据前会对输入数据量的大小进行判断若单次传输的字节数超过max_transfer_sz则会报错。但SPI 单次 DMA 传输允许的最大字节数不仅取决于max_transfer_sz还受限于 ESP-IDF 中的SPI_LL_DATA_MAX_BIT_LEN不同系列 ESP 的值不同即满足最大字节数 MIN(max_transfer_sz, (SPI_LL_DATA_MAX_BIT_LEN / 8))由于esp_lcd驱动会提前判断输入的数据量是否超过限制如果超过则进行分包处理后再控制 SPI 进行多次传输因此 max_transfer_sz 通常设为全屏大小即可。创建接口设备示例代码#include esp_lcd_panel_io.h // 依赖的头文件 static bool example_on_color_trans_dome(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx) { /* 色彩数据传输完成时的回调函数可以在此处进行一些操作 */ return false; } 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, // 连接 LCD DCRS 信号的 IO 编号可以设为 -1 表示不使用 .cs_gpio_num EXAMPLE_PIN_NUM_LCD_CS, // 连接 LCD CS 信号的 IO 编号可以设为 -1 表示不使用 .pclk_hz EXAMPLE_LCD_PIXEL_CLOCK_HZ, // SPI 的时钟频率HzESP 最高支持 80MSPI_MASTER_FREQ_80M // 需根据 LCD 驱动 IC 的数据手册确定其最大值 .lcd_cmd_bits EXAMPLE_LCD_CMD_BITS, // 单位 LCD 命令的比特数应为 8 的整数倍 .lcd_param_bits EXAMPLE_LCD_PARAM_BITS, // 单位 LCD 参数的比特数应为 8 的整数倍 .spi_mode 0, // SPI 模式0-3需根据 LCD 驱动 IC 的数据手册以及硬件的配置确定如 IM[3:0] .trans_queue_depth 10, // SPI 设备传输数据的队列深度一般设为 10 即可 .on_color_trans_done example_on_color_trans_dome, // 单次调用 esp_lcd_panel_draw_bitmap() 传输完成后的回调函数 .user_ctx example_user_ctx, // 传给回调函数的用户参数 .flags { // 以下为 SPI 时序的相关参数需根据 LCD 驱动 IC 的数据手册以及硬件的配置确定 .sio_mode 0, // 通过一根数据线MOSI读写数据0: Interface I 型1: Interface II 型 }, }; ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)LCD_HOST, io_config, io_handle)); /* 以下函数也可用于注册色彩数据传输完成事件的回调函数 */ // const esp_lcd_panel_io_callbacks_t cbs { // .on_color_trans_done example_on_color_trans_dome, // }; // esp_lcd_panel_io_register_event_callbacks(io_handle, cbs, example_user_ctx);注意关于 SPI 接口配置参数更详细的说明请参考 ESP-IDF 编程指南中 LCD 外设SPI LCD章节。通过创建接口设备可以获取数据类型为esp_lcd_panel_io_handle_t的句柄然后可以使用接口通用 API 给 LCD 的驱动 IC 发送命令和图像数据esp_lcd_panel_io_tx_param()用于发送单个 LCD 的命令及配套参数其内部通过函数spi_device_polling_transmit()实现数据传输使用该函数会等待数据传输完毕后才会返回。esp_lcd_panel_io_tx_color()用于发送单次 LCD 刷屏命令和图像数据。在函数内部它通过spi_device_polling_transmit()发送命令和一些少量的参数然后通过spi_device_queue_trans()分包发送大量的图像数据每个包的大小由 SPI 单次 DMA 传输允许的最大字节数限制。这个函数将图像缓存地址等相关数据压入队列队列深度由trans_queue_depth参数指定一旦数据成功压入队列函数就会立刻返回。因此如果计划在后续操作中修改相同的图像缓存则需要注册回调函数来判断上一次传输是否已经完成否则可能在未完成的传输上修改缓存导致数据混乱、显示出现错误。第二步移植驱动组件移植 SPI LCD 驱动组件的基本原理包含以下三点基于数据类型为esp_lcd_panel_io_handle_t的接口设备句柄发送指定格式的命令及参数实现并创建一个 LCD 设备然后通过注册回调函数的方式实现结构体esp_lcd_panel_t中的各项功能实现一个函数用于提供数据类型为esp_lcd_panel_handle_t的 LCD 设备句柄使得应用程序能够利用 LCD 通用 API 来操作 LCD 设备。下面给出esp_lcd_panel_handle_t各项功能的实现说明及其与 LCD 通用 API 的对应关系功能LCD 通用 APIs实现说明reset()esp_lcd_panel_reset()若设备连接了复位引脚则通过该引脚进行硬件复位否则通过命令LCD_CMD_SWRESET(01h)进行软件复位init()esp_lcd_panel_init()通过发送一系列的命令及参数来初始化 LCD 设备del()esp_lcd_panel_del()释放驱动占用的资源包括申请的存储空间和使用的 IOdraw_bitmap()esp_lcd_panel_draw_bitmap()首先通过命令LCD_CMD_CASET(2Ah)和LCD_CMD_RASET(2Bh)发送图像的起始和终止坐标然后通过命令LCD_CMD_RAMWR(2Ch)发送图像数据mirror()esp_lcd_panel_mirror()通过命令LCD_CMD_MADCTL(36h)设置是否镜像屏幕的 X 轴和 Y 轴swap_xy()esp_lcd_panel_swap_xy()通过命令LCD_CMD_MADCTL(36h)设置是否交换屏幕的 X 轴和 Y 轴set_gap()esp_lcd_panel_set_gap()通过软件修改画图时的起始和终止坐标从而实现画图的偏移invert_color()esp_lcd_panel_invert_color()通过命令LCD_CMD_INVON(21h)和LCD_CMD_INVOFF(20h)实现像素的颜色数据按位取反0xF0F0 - 0x0F0Fdisp_on_off()esp_lcd_panel_disp_on_off()通过命令LCD_CMD_DISON(29h)和LCD_CMD_DISOFF(28h)实现屏幕显示的开关源码级印证以仓库 esp_lcd_gc9b71 驱动组件为例仓库 components/display/lcd/esp_lcd_gc9b71/esp_lcd_gc9b71.c 中的实现与上表完全一一对应可作为阅读参考reset()panel_gc9b71_reset()在reset_gpio_num 0时通过 GPIO 拉低/拉高复位线gpio_set_level 10ms/120ms 延时进行硬件复位否则发送LCD_CMD_SWRESET软件复位并延时 20msinit()panel_gc9b71_init()先发送LCD_CMD_MADCTL、LCD_CMD_COLMOD配置色彩方向与格式再逐条发送厂商初始化命令序列每项含{cmd, data, data_bytes, delay_ms}对应头文件中的gc9b71_lcd_init_cmd_t结构见 esp_lcd_gc9b71.hdraw_bitmap()panel_gc9b71_draw_bitmap()先发送LCD_CMD_CASET/LCD_CMD_RASET各 4 字节即起始/结束坐标各占 2 字节再计算len (x_end - x_start) * (y_end - y_start) * fb_bits_per_pixel / 8最后通过LCD_CMD_RAMWR发送图像数据mirror()/swap_xy()都通过修改驱动内部保存的madctl_val对应LCD_CMD_MADCTL寄存器值的MX/MY/MV位再回写实现set_gap()仅将x_gap/y_gap存入结构体绘制时在draw_bitmap()中统一叠加偏移见esp_lcd_gc9b71.c中x_start gc9b71-x_gap;等行invert_color()/disp_on_off()分别发送INVON/INVOFF与DISPON/DISPOFF命令del()调用gpio_reset_pin()释放复位引脚并free()驱动结构体。同时esp_lcd_gc9b71.h 还暴露了gc9b71_vendor_config_t通过vendor_config字段传入esp_lcd_panel_dev_config_t可以覆盖默认初始化命令序列init_cmds/init_cmds_size并可切换 QSPI 接口use_qspi_interface。这正是不同制造商的同型号屏幕需要各自初始化命令这一移植要点的代码级体现。移植步骤对于大多数 SPI LCD其驱动 IC 的命令及参数与上述实现说明兼容因此可以通过以下步骤完成移植在LCD 驱动组件中选择一个型号相似的 SPI LCD 驱动组件。仓库 components/display/lcd/ 下提供了大量现成组件例如esp_lcd_gc9107、esp_lcd_gc9b71、esp_lcd_gc9d01、esp_lcd_st7701、esp_lcd_st77916、esp_lcd_st77926等可直接作为移植模板。通过查阅目标 LCD 驱动 IC 的数据手册确认其与所选组件中各功能使用到的命令及参数是否一致若不一致则需要修改相关代码。即使 LCD 驱动 IC 的型号相同不同制造商的屏幕也通常需要使用各自提供的初始化命令配置。因此需要修改初始化函数init()中发送的命令和参数。这些初始化命令通常以特定的格式存储在一个静态数组中如上面 GC9B71 源码中的vendor_specific_init_default[]。此外需要注意不要在初始化命令中包含一些特殊的命令例如LCD_CMD_COLMOD(3Ah)和LCD_CMD_MADCTL(36h)这些命令是由驱动组件进行管理和使用的——GC9B71 源码中对应地做了保护当外部init_cmds中出现 MADCTL/COLMOD 时会打印警告日志并覆写内部保存的madctl_val/colmod_val而不是重复发送。可使用编辑器的字符搜索和替换功能将组件中的 LCD 驱动 IC 名称替换为目标名称如将gc9a01替换为st77916。第三步初始化 LCD 设备下面以GC9A01ESP-IDF 官方组件 esp_lcd_gc9a01为例说明初始化流程仓库内同结构驱动如 esp_lcd_gc9b71用法完全一致只需替换头文件与创建函数名#include esp_lcd_panel_vendor.h // 依赖的头文件 #include esp_lcd_panel_ops.h #include esp_lcd_gc9a01.h // 目标驱动组件的头文件 /** * 用于存放 LCD 驱动 IC 的初始化命令及参数 */ // static const gc9a01_lcd_init_cmd_t lcd_init_cmds[] { // // {cmd, { data }, data_size, delay_ms} // {0xfe, (uint8_t []){0x00}, 0, 0}, // {0xef, (uint8_t []){0x00}, 0, 0}, // {0xeb, (uint8_t []){0x14}, 1, 0}, // ... // }; /* 创建 LCD 设备 */ esp_lcd_panel_handle_t panel_handle NULL; // const gc9a01_vendor_config_t vendor_config { // 用于替换驱动组件中的初始化命令及参数 // .init_cmds lcd_init_cmds, // .init_cmds_size sizeof(lcd_init_cmds) / sizeof(gc9a01_lcd_init_cmd_t), // }; esp_lcd_panel_dev_config_t panel_config { .reset_gpio_num EXAMPLE_PIN_NUM_LCD_RST, // 连接 LCD 复位信号的 IO 编号可以设为 -1 表示不使用 .rgb_ele_order LCD_RGB_ELEMENT_ORDER_RGB, // 像素色彩的元素顺序RGB/BGR // 一般通过命令 LCD_CMD_MADCTL36h 控制 .bits_per_pixel EXAMPLE_LCD_BIT_PER_PIXEL, // 色彩格式的位数RGB56516RGB66618 // 一般通过命令 LCD_CMD_COLMOD3Ah 控制 // .vendor_config vendor_config, // 用于替换驱动组件中的初始化命令及参数 }; ESP_ERROR_CHECK(esp_lcd_new_panel_gc9a01(io_handle, panel_config, panel_handle)); /* 初始化 LCD 设备 */ 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, true)); // ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel_handle, true)); // ESP_ERROR_CHECK(esp_lcd_panel_set_gap(panel_handle, 0, 0)); ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, true));首先通过移植好的驱动组件创建 LCD 设备并获取esp_lcd_panel_handle_t句柄然后使用 LCD 通用 API 初始化 LCD 设备。其中.bits_per_pixel与色彩格式的对应关系RGB56516、RGB66618在仓库 GC9B71 源码中同样有体现esp_lcd_gc9b71.c中分别映射colmod_val为 0x55RGB565/ 0x66RGB666并据此换算每像素字节数。使用 esp_lcd_panel_draw_bitmap() 刷新图像的注意事项传入该函数的图像缓存的字节数可以大于max_transfer_sz此时esp_lcd驱动内部会根据 SPI 单次 DMA 传输允许的最大字节数进行分包处理。由于该函数是采用 DMA 方式来传输图像数据函数调用完成后数据仍在通过 DMA 传输此时不能修改正在使用的缓存区域如进行 LVGL 的渲染。因此需要通过总线初始化时注册的回调或者调用esp_lcd_panel_io_register_event_callbacks()注册的回调来判断上一次传输是否完成。由于 SPI 驱动目前不支持直接通过 DMA 传输 PSRAM 上的数据其内部会判断数据是否存放在 PSRAM 上若是则会将其拷贝到 SRAM中再进行传输。因此推荐使用 SRAM 作为图像缓存如用于 LVGL 渲染的缓存否则直接传输 PSRAM 上较大的图像数据很可能出现 SRAM 不足的情况。相关文档与延伸阅读本文对应的英文版与文档索引见 LCD 显示屏章节索引其中还包含 LCD 开发指南、RGB LCD 详解、MIPI DSI LCD 详解 等姊妹篇可横向对比不同接口的异同。ST7789数据手册ST7789V_SPEC_V1.0.pdf。需要软件模拟 3-wire SPI 时可查阅仓库组件 esp_lcd_panel_io_additions其中esp_lcd_new_panel_io_3wire_spi()支持通过 GPIO 或 IO 扩展器实现位翻转时序并演示了spi_line_config_t与esp_lcd_panel_io_3wire_spi_config_t的完整配置方法。【免费下载链接】esp-iot-solutionEspressif IoT Library. IoT Device Drivers, Documentations and Solutions.项目地址: https://gitcode.com/GitHub_Trending/es/esp-iot-solution创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考