ESP-IDF MIPI DSI 接口 LCD 驱动指南:从 DSI 总线到 DBI/DPI 双通道显示
【免费下载链接】esp-idfEspressif IoT Development Framework. Official development framework for Espressif SoCs.项目地址: https://gitcode.com/GitHub_Trending/es/esp-idf
本文基于 ESP-IDF(Espressif IoT Development Framework)的 esp_lcd 组件,系统讲解如何驱动 MIPI DSI 接口的 LCD 屏。文章以 dsi_lcd.rst 为核心脉络,结合 esp_lcd_mipi_dsi.h 源码与 mipi_dsi 示例,完整覆盖从创建 DSI 总线、派生 DBI 控制通道、安装 LCD 控制器驱动,到通过 DPI 数据通道刷帧的全流程。读完本文,你将掌握 MIPI DSI LCD 在 ESP-IDF 下的完整初始化链路、关键配置参数含义,以及 DMA2D 加速与自定义 draw bitmap 钩子的进阶用法。
MIPI DSI 与 esp_lcd 的分层模型
MIPI DSI(Display Serial Interface)是一种高速串行显示接口,常用于高分辨率 LCD。与传统的 SPI/MCU 接口 LCD 不同,DSI LCD 的控制器内通常没有 GRAM(显存),因此主机侧必须维护帧缓冲(frame buffer),并通过持续刷新把像素数据流式送入屏幕。
在 esp_lcd 组件中,MIPI DSI 被抽象为三层结构:
- DSI 总线(Bus):对应物理层的 D-PHY 与 DSI 主机控制器,负责时钟与数据通道(data lanes)的物理传输;
- DBI 接口(控制通道):基于 DSI 的 command mode,用于读写 LCD 控制器芯片内部的配置寄存器(发命令、传参数);
- DPI 接口(数据通道):基于 DSI 的 video mode,用于持续传输像素数据(即刷帧)。
这套分层的实现位于 components/esp_lcd/dsi 目录下,核心文件包括 esp_lcd_mipi_dsi_bus.c、esp_lcd_panel_io_dbi.c 和 esp_lcd_panel_dpi.c。接下来按官方推荐的初始化顺序逐步展开。
第一步:创建 DSI 总线并初始化 D-PHY
创建 DSI 总线是整个过程的第一步,调用esp_lcd_new_dsi_bus()时驱动会一并完成 D-PHY 的初始化。需要填充esp_lcd_dsi_bus_config_t结构体(定义见 esp_lcd_mipi_dsi.h):
esp_lcd_dsi_bus_handle_t mipi_dsi_bus = NULL; esp_lcd_dsi_bus_config_t bus_config = { .bus_id = 0, // index from 0, specify the DSI host to use .num_data_lanes = 2, // Number of data lanes to use, can't set a value that exceeds the chip's capability .lane_bit_rate_mbps = EXAMPLE_MIPI_DSI_LANE_BITRATE_MBPS, // Bit rate of the data lanes, in Mbps }; ESP_ERROR_CHECK(esp_lcd_new_dsi_bus(&bus_config, &mipi_dsi_bus));各字段说明:
bus_id:选择使用哪个 DSI 控制器,从 0 开始编号;num_data_lanes:使用的数据通道数量。从源码注释可知,若设为 0,驱动会自动回退到使用芯片支持的最大通道数;lane_bit_rate_mbps:每条数据通道的比特率,单位 Mbps,直接影响最大带宽;- 此外结构体还包含
phy_clk_src(PHY PLL 的时钟源)与可选标志位flags.clock_lane_force_hs(强制时钟通道进入高速模式),这些在基础应用中可保持默认。
调用失败时可能返回的错误码包括:ESP_ERR_INVALID_ARG(参数非法)、ESP_ERR_NO_MEM(内存不足)、ESP_ERR_NOT_FOUND(无空闲的 DSI 硬件实例)、ESP_FAIL(其他错误)。对应销毁接口为esp_lcd_del_dsi_bus()。
第二步:从 DSI 总线派生 DBI 控制接口
DSI 总线创建好后,需要从中派生一个 DBI 接口作为控制 IO 层,用于读写 LCD 设备内部的配置寄存器(例如初始化序列、背光、伽马等命令)。
esp_lcd_panel_io_handle_t mipi_dbi_io = NULL; esp_lcd_dbi_io_config_t dbi_config = { .virtual_channel = 0, .lcd_cmd_bits = 8, // according to the LCD spec .lcd_param_bits = 8, // according to the LCD spec }; ESP_ERROR_CHECK(esp_lcd_new_panel_io_dbi(mipi_dsi_bus, &dbi_config, &mipi_dbi_io));esp_lcd_dbi_io_config_t(见 esp_lcd_mipi_dsi.h)中两个关键字段:
virtual_channel:虚拟通道号。虚拟通道是 DSI 中用于多路复用不同数据源的逻辑通道。如果只接一块 LCD,直接设为0;lcd_cmd_bits/lcd_param_bits:LCD 控制器芯片能识别的命令与参数的位宽,这是芯片相关的,需要事先查阅你的 LCD 规格书。绝大多数 MIPI DSI LCD 控制器使用 8 位命令、8 位参数。
需要留意的一点(源码注释中有明确说明):虽然这里命名为 "DBI",但驱动内部实际使用的是 DSI 的 "generic" 接口来收发 LCD 命令与参数。
第三步:安装 LCD 控制器驱动
有了 DBI IO 句柄后,即可安装 LCD 控制器驱动。控制器驱动负责把命令与参数真正发送给 LCD 控制芯片。这里以常见芯片 ILI9881C 为例(对应实现文件 esp_lcd_ili9881c.h 位于 esp_lcd 组件):
esp_lcd_panel_handle_t ili9881c_ctrl_panel = NULL; esp_lcd_panel_dev_config_t lcd_dev_config = { .bits_per_pixel = 24, // MIPI LCD usually uses 24 bit (i.e., RGB888) per pixel .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB, .reset_gpio_num = EXAMPLE_PIN_NUM_LCD_RST, }; ESP_ERROR_CHECK(esp_lcd_new_panel_ili9881c(mipi_dbi_io, &lcd_dev_config, &ili9881c_ctrl_panel));esp_lcd_panel_dev_config_t中三个对本场景最重要的字段:
bits_per_pixel:每个像素的位宽。LCD 驱动会用它计算需要发送给控制芯片的字节数。MIPI LCD 通常使用 24 bit,即 RGB888;reset_gpio_num:复位引脚对应的 GPIO 编号。若 LCD 控制器芯片没有复位引脚,可设为-1(示例中EXAMPLE_PIN_NUM_LCD_RST即定义为-1);rgb_ele_order:像素数据的 RGB 元素顺序,可选LCD_RGB_ELEMENT_ORDER_RGB或LCD_RGB_ELEMENT_ORDER_BGR(枚举定义见 esp_lcd_types.h)。
拿到控制面板句柄后,就可以复位 LCD、执行基础初始化并点亮显示:
ESP_ERROR_CHECK(esp_lcd_panel_reset(ili9881c_ctrl_panel)); ESP_ERROR_CHECK(esp_lcd_panel_init(ili9881c_ctrl_panel)); ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(ili9881c_ctrl_panel, true));注意:这里esp_lcd_panel_disp_on_off(..., true)控制的是 LCD 控制器的显示开关,与背光 GPIO 的亮灭(由 BSP 层自行管理)是两回事。
第四步:分配 DPI 数据面板用于刷帧
此时还不能用控制面板发送像素数据——因为 MIPI LCD 分辨率高且控制器内没有 GRAM。必须由主机侧维护帧缓冲,并通过 MIPI DSI 的 DPI 接口把帧缓冲持续刷到 LCD 上。创建 DPI 数据面板需要提供包括 DPI 时钟频率、像素格式、视频时序在内的大量参数:
esp_lcd_panel_handle_t mipi_dpi_panel = NULL; esp_lcd_dpi_panel_config_t dpi_config = { .virtual_channel = 0, .dpi_clk_src = MIPI_DSI_DPI_CLK_SRC_DEFAULT, .dpi_clock_freq_mhz = 1 * 1000, .in_color_format = LCD_COLOR_FMT_RGB888, .video_timing = { .h_size = EXAMPLE_MIPI_DSI_LCD_H_RES, .v_size = EXAMPLE_MIPI_DSI_LCD_V_RES, .hsync_back_porch = EXAMPLE_MIPI_DSI_LCD_HBP, .hsync_pulse_width = EXAMPLE_MIPI_DSI_LCD_HSYNC, .hsync_front_porch = EXAMPLE_MIPI_DSI_LCD_HFP, .vsync_back_porch = EXAMPLE_MIPI_DSI_LCD_VBP, .vsync_pulse_width = EXAMPLE_MIPI_DSI_LCD_VSYNC, .vsync_front_porch = EXAMPLE_MIPI_DSI_LCD_VFP, }, }; ESP_ERROR_CHECK(esp_lcd_new_panel_dpi(mipi_dsi_bus, &dpi_config, &mipi_dpi_panel)); ESP_ERROR_CHECK(esp_lcd_panel_init(mipi_dpi_panel));esp_lcd_dpi_panel_config_t(见 esp_lcd_mipi_dsi.h)关键字段逐一说明:
virtual_channel:与 DBI 接口同理,仅接一块屏时设为0;dpi_clk_src:DPI 接口的时钟源,可选值见mipi_dsi_dpi_clock_source_t枚举,常用MIPI_DSI_DPI_CLK_SRC_DEFAULT;dpi_clock_freq_mhz:DPI 像素时钟频率(MHz)。像素时钟越高刷新率越高,但如果 DMA 带宽不足或 LCD 控制器芯片不支持高像素时钟,可能引起闪烁,需要按 LCD 规格书与硬件带宽综合权衡;in_color_format:输入像素数据格式,可选值见lcd_color_format_t。MIPI LCD 通常用RGB888以获得最佳色彩深度;video_timing:LCD 屏特有的时序参数,类型为esp_lcd_video_timing_t,必须按 LCD 数据手册填写。
esp_lcd_video_timing_t(定义见 esp_lcd_types.h)各成员含义:
| 成员 | 含义 |
|---|---|
h_size | 水平分辨率,即每行像素数 |
v_size | 垂直分辨率,即每帧行数 |
hsync_pulse_width | 水平同步脉冲宽度,以像素时钟计 |
hsync_back_porch | 水平后廊,hsync 结束到行有效数据开始之间的像素时钟数 |
hsync_front_porch | 水平前廊,行有效数据结束到下一个 hsync 之间的像素时钟数 |
vsync_pulse_width | 垂直同步脉冲宽度,以行数计 |
vsync_back_porch | 垂直后廊,vsync 结束到帧起始之间的无效行数 |
vsync_front_porch | 垂直前廊,帧结束到下一个 vsync 之间的无效行数 |
时序参数与刷新率的换算关系:刷新率 = dpi_clock_freq_mhz × 1e6 / (H_total × V_total),其中H_total = h_size + hsync_pulse_width + hsync_back_porch + hsync_front_porch,V_total = v_size + vsync_pulse_width + vsync_back_porch + vsync_front_porch。示例 mipi_dsi_lcd_example_main.c 中给出了两组可直接验证的配置:
- ILI9881C(800×1280):
刷新率 = 80000000/(40+140+40+800)/(4+16+16+1280) = 60Hz,即dpi_clock_freq_mhz = 80; - EK79007(1024×600):
刷新率 = 48000000/(10+120+120+1024)/(1+20+10+600) = 60Hz,即dpi_clock_freq_mhz = 48。
另外,esp_lcd_dpi_panel_config_t还包含两个进阶字段:out_color_format(输出像素格式,即面板可接受的格式,用于输入输出格式不一致时的转换场景)与num_fbs(驱动分配的整屏帧缓冲数量,默认 0 或 1 表示只创建一个帧缓冲)。DPO 面板创建成功后,可用esp_lcd_dpi_panel_get_frame_buffer()拿到驱动分配的帧缓冲地址,也可用esp_lcd_dpi_panel_set_pattern()输出预定义测试图案用于调试。
第五步:配置 draw bitmap 钩子(可选)
DPI 面板驱动支持通过"钩子"机制接管esp_lcd_panel_draw_bitmap()的像素搬运过程,适用于 DMA2D 加速、旋转、缩放等场景。
内置 DMA2D 加速
如果希望用DMA2D来实现 draw bitmap,驱动已经内置了对应的钩子函数,只需调用esp_lcd_dpi_panel_enable_dma2d()即可启用:
ESP_ERROR_CHECK(esp_lcd_dpi_panel_enable_dma2d(mipi_dpi_panel));硬件限制提醒:由于硬件限制,若使能了外部内存加密(flash encryption),DMA2D 只能访问地址与长度均按SOC_GDMA_EXT_MEM_ENC_ALIGNMENT字节对齐的内存。你必须保证 draw buffer 的地址与长度按该值对齐。官方示例peripherals/lcd/mipi_dsi演示了如何用 LVGL 的LV_EVENT_INVALIDATE_AREA事件回调来约束重绘区域、强制把刷新区域对齐到 16 字节,从而规避 MSPI 错误。在示例 mipi_dsi_lcd_example_main.c 中可以看到这段对齐逻辑:
void example_rounder_flush_area_cb(lv_event_t * event) { lv_area_t * area = lv_event_get_invalidated_area(event); area->x1 = ESP_ALIGN_DOWN(area->x1, 16); area->x2 = ESP_ALIGN_UP(area->x2, 16) - 1; }同时,示例在使能 flash encryption 时把 LVGL draw buffer 的分配对齐到SOC_GDMA_EXT_MEM_ENC_ALIGNMENT(见 mipi_dsi_lcd_example_main.c)。与之对称的关闭接口为esp_lcd_dpi_panel_disable_dma2d()。
自定义 draw bitmap 钩子
如需更高级的应用(例如用PPA硬件加速器实现旋转、缩放、色彩空间转换等),可以注册自定义钩子:
esp_lcd_panel_hooks_t hooks = { .draw_bitmap_hook = custom_draw_bitmap_hook, }; ESP_ERROR_CHECK(esp_lcd_dpi_panel_register_hooks(mipi_dpi_panel, &hooks, &user_ctx));钩子回调的原型为esp_lcd_panel_draw_bitmap_hook_t,其参数esp_lcd_draw_bitmap_hook_data_t(见 esp_lcd_mipi_dsi.h)携带了完整的目标/源缓冲信息:目标帧缓冲指针与目标区域坐标、源位图数据指针与源区域坐标、每像素位宽,以及钩子完成时回调on_hook_end。源码注释特别强调:钩子需要自行保证绘制操作的同步。此外,多次调用esp_lcd_dpi_panel_register_hooks()会覆盖之前注册的钩子。
除 draw bitmap 钩子外,DPI 面板还支持事件回调注册(esp_lcd_dpi_panel_register_event_callbacks()),包括on_color_trans_done(用户 draw buffer 拷贝到帧缓冲完成,可安全回收 draw buffer)、on_frame_buf_complete(帧缓冲可安全复用的信号,替代已废弃的on_refresh_done)以及on_vsync(VSYNC 事件)。示例中正是利用on_color_trans_done回调去通知 LVGL 刷新完成:
static bool example_notify_lvgl_flush_ready(esp_lcd_panel_handle_t panel, esp_lcd_dpi_panel_event_data_t *edata, void *user_ctx) { lv_display_t *disp = (lv_display_t *)user_ctx; lv_display_flush_ready(disp); return false; }MIPI DPHY 的电源供应
MIPI DSI 驱动能否正常工作,硬件上还有一个前提条件:芯片上的 MIPI DPHY 需要一路专用的 2.5V 电源。请对照原理图确认电源引脚(丝印通常为VDD_MIPI_DPHY)已正确连接到 2.5V 电源,再使用 MIPI DSI 驱动。
在支持内部可调 LDO(对应 Kconfig 能力SOC_GP_LDO_SUPPORTED)的芯片上,MIPI DPHY 可以由内部 LDO 供电:把 LDO 通道的输出引脚接到 MIPI DPHY 电源引脚,并在初始化 DSI 驱动之前,使用 LDO 调节器 API(参见文档 ldo_regulator)把 LDO 输出电压配置为 2.5V。
示例 mipi_dsi_lcd_example_main.c 中的 BSP 层演示了完整做法:定义EXAMPLE_MIPI_DSI_PHY_PWR_LDO_CHAN 3(LDO_VO3 连接到 VDD_MIPI_DPHY)与EXAMPLE_MIPI_DSI_PHY_PWR_LDO_VOLTAGE_MV 2500,然后在创建 DSI 总线之前调用esp_ldo_acquire_channel()使能该通道,使 DPHY 从 "No Power" 状态进入 "Shutdown" 状态,随后才执行esp_lcd_new_dsi_bus()。
从示例工程看完整落地流程
官方示例 examples/peripherals/lcd/mipi_dsi 将上述步骤串成了一个可运行的 LVGL 应用,其主流程(见 mipi_dsi_lcd_example_main.c)可归纳为:
- 初始化刷新监测 GPIO(可选)→ 使能 DSI PHY 电源 → 初始化并熄灭背光;
- 创建 DSI 总线(
esp_lcd_new_dsi_bus); - 安装 DBI 控制 IO(
esp_lcd_new_panel_io_dbi); - 按 Kconfig 选择 ILI9881C 或 EK79007,填充
vendor_config(内含 DSI 总线句柄与 DPI 配置)后创建数据面板(esp_lcd_new_panel_ili9881c/esp_lcd_new_panel_ek79007); - 按需使能 DMA2D(
esp_lcd_dpi_panel_enable_dma2d); - 复位并初始化面板,点亮背光;
- 初始化 LVGL:创建 display、设置 RGB888 色彩格式、从 PSRAM 分配双 draw buffer、设置 flush 回调;
- 注册 DPI 面板事件回调(
on_color_trans_done通知 LVGL 刷屏完成,on_vsync用于刷新率监测); - 用
esp_timer提供 LVGL tick,创建 LVGL 任务并加载示例 UI。
其中第 4 步值得注意:ILI9881C/EK79007 这类控制器驱动的vendor_config中携带了dsi_bus与dpi_config指针,这意味着控制面板与数据面板共用同一条 DSI 总线——DBI 负责初始化命令,DPI 负责持续刷帧,二者通过虚拟通道在同一物理链路上协同工作。
小结
MIPI DSI LCD 的驱动链路可以概括为"一条总线、两条通道":esp_lcd_new_dsi_bus()建立物理层,esp_lcd_new_panel_io_dbi()建立寄存器控制通道,esp_lcd_new_panel_dpi()建立像素数据通道。相比 SPI LCD,DSI LCD 的关键差异在于主机侧必须自建帧缓冲并持续刷帧,因此video_timing与dpi_clock_freq_mhz的取值直接决定了画面能否稳定刷新。在此基础上,DMA2D 内置钩子与自定义 draw bitmap 钩子(如 PPA 旋转/缩放)为高性能 UI 渲染提供了扩展空间。动手实践时,建议从examples/peripherals/lcd/mipi_dsi示例出发,对照本文各字段说明修改为自家 LCD 的规格参数,并务必先确认VDD_MIPI_DPHY的 2.5V 供电已就绪。
【免费下载链接】esp-idfEspressif IoT Development Framework. Official development framework for Espressif SoCs.项目地址: https://gitcode.com/GitHub_Trending/es/esp-idf
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考