去年做了一个基于 esp32p4的游戏机兼开发板 ,驱动了一个st7701 的mipi屏,这是当时记录了的驱动笔记。
记录一下 mipi dsi 相关理解, 一个lcd ic 可能支持多种通信方式 如spi3/4 线 、rgb、mipi 等,屏厂可能会引出一个ic 的多种接口让用户通过相应的输入引脚配置成相应对应方式,也可能只引出某一种通信方式,在选屏时对应 屏幕ic 就可以知道支持哪种通信方式,mipi 不只有dsi 还有 csi ufs 等,相对屏幕也就特指dsi 。dsi 只是一种通信方式,dsi 需要物理支持无法通过软件模拟高速差分信号,基于这种通信方式有两种不同的逻辑接口 dbi 、dpi ,dbi 工作于命令模式用于发送命令配置寄存器,通过低速方式传输数据,主要在初始阶段工作;dpi 用于传输显示数据工作在视频模式,数据量大通过高速方式传输,工作于视频模式下的屏幕需要主机不断的以相应的刷新率通过dpi 向其发送显示数据来维持显示。
基于对工作原理的了解,再结合esp-idf 中 mipi dsi 的程序来看主要分为几个步骤:
1、开启mipi dsi PHY 的ldo电源
这里需要通过结构体配置 ldo 通道与电压,通过 esp32p4 技术规格书查阅
2、创建mipi dsi bus ,后面的 dbi 和 dpi 都基于 dsi 通信
这里需要配置 lane 数、 时针源与速率raw_data_rate
3、创建 mipi dbi ,用于命令控制
配置命令与参数的位数,创建需要传入 dsi bus
4、创建mipi dpi ,用于发送显示数据
配置 缓冲数,驱动内部会维护相应数量的缓冲区,dpi 的时针源 ,像素时钟频率 pixel_clock, 色深格式及分辨率与时序
5、创建屏幕面板 esp_lcd_panel_handle_t,这是 esp-idf 中对屏幕设备抽象后的实现,大量屏幕操作接口都基于面板操作
关于面板的创建,不同的屏幕会有不同的创建实现,大概就是会有一个对应ic 及通信方式命名的new方法比如 esp_lcd_new_panel_st7701_mipi ,和对这个屏幕操作需要的一些数据定义一个结构体如 st7701_panel_t,在 new 方法中先通过传入的数据将结构体设置好, 在最后创建 esp_lcd_panel_handle_t 实例,并将回调函数设置好,最后将 st7701_panel_t 的实例设置到 esp_lcd_panel_handle_t 实例的 user_data 中,这样通过 esp_lcd_panel_handle_t 实例调用回调时就可以通过 user_data 取到相关的数据对屏幕进行更具体的操作。
6、复位并初始化
7、开始调用 esp_lcd_panel_draw_bitmap 向屏幕发送显示数据
需要注意第2步的速率是lane 线的传输速率是要计算到bit的单位是mbps,第4步的频率是相对像素的单位是mhz,关于 dsi 速率与 dpi 的像素时钟频率的计算:
pixel_clock = 水平总像素 × 垂直总像素 × 刷新率
raw_data_rate = pixel_clock × bits_per_pixel
下面是程序中的初始化代码,可以参考理解:
使用了组件 :https://components.espressif.com/components/espressif/esp_lcd_st7701/versions/1.1.5/readme
cpp
// MIPI DSI 配置 - 根据你的硬件调整这些参数
#ifndef RG_MIPI_DSI_LANE_NUM
#define RG_MIPI_DSI_LANE_NUM 1 // 数据通道数
#endif
#ifndef RG_MIPI_DSI_LANE_BITRATE_MBPS
#define RG_MIPI_DSI_LANE_BITRATE_MBPS 1000 // 1Gbps
#endif
#ifndef RG_MIPI_DSI_DPI_CLK_MHZ
#define RG_MIPI_DSI_DPI_CLK_MHZ 32 // DPI 时钟频率
#endif
// 时序参数 - 根据你的屏幕规格调整
#ifndef RG_MIPI_DSI_LCD_HSYNC
#define RG_MIPI_DSI_LCD_HSYNC 10
#endif
#ifndef RG_MIPI_DSI_LCD_HBP
#define RG_MIPI_DSI_LCD_HBP 10
#endif
#ifndef RG_MIPI_DSI_LCD_HFP
#define RG_MIPI_DSI_LCD_HFP 20
#endif
#ifndef RG_MIPI_DSI_LCD_VSYNC
#define RG_MIPI_DSI_LCD_VSYNC 10
#endif
#ifndef RG_MIPI_DSI_LCD_VBP
#define RG_MIPI_DSI_LCD_VBP 10
#endif
#ifndef RG_MIPI_DSI_LCD_VFP
#define RG_MIPI_DSI_LCD_VFP 10
#endif
// LDO 配置(用于 MIPI DSI PHY 供电)
#ifndef RG_MIPI_DSI_PHY_PWR_LDO_CHAN
#define RG_MIPI_DSI_PHY_PWR_LDO_CHAN 3 // LDO 通道
#endif
#ifndef RG_MIPI_DSI_PHY_PWR_LDO_VOLTAGE_MV
#define RG_MIPI_DSI_PHY_PWR_LDO_VOLTAGE_MV 2500 // 2.5V
#endif
// ST7701 初始化命令序列 - 由屏厂提供
static const st7701_lcd_init_cmd_t st7701_init_cmds[] = {
{0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x13}, 5, 0},
{0xEF, (uint8_t []){0x08}, 1, 0},
{0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x10}, 5, 0},
{0xC0, (uint8_t []){0x4F, 0x00}, 2, 0},
{0xC1, (uint8_t []){0x0C, 0x02}, 2, 0},
{0xC2, (uint8_t []){0x37, 0x06}, 2, 0},
{0xB0, (uint8_t []){0x40, 0xC3, 0x96, 0x0C, 0x12, 0x08, 0x07, 0x06, 0x06, 0x20, 0x03, 0x92, 0x10, 0x66, 0xAF, 0x9D}, 16, 0},
{0xB1, (uint8_t []){0x40, 0xC3, 0x95, 0x0E, 0x11, 0x07, 0x0A, 0x09, 0x09, 0x22, 0x05, 0x11, 0x0F, 0x28, 0xEE, 0xD9}, 16, 0},
{0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x11}, 5, 0},
{0xB0, (uint8_t []){0x3D}, 1, 0},
{0xB1, (uint8_t []){0x7F}, 1, 0},
{0xB2, (uint8_t []){0x89}, 1, 0},
{0xB3, (uint8_t []){0x80}, 1, 0},
{0xB5, (uint8_t []){0x49}, 1, 0},
{0xB7, (uint8_t []){0x85}, 1, 0},
{0xB8, (uint8_t []){0x23}, 1, 0},
{0xC1, (uint8_t []){0x78}, 1, 0},
{0xC2, (uint8_t []){0x78}, 1, 0},
{0xD0, (uint8_t []){0x88}, 1, 0},
{0xE0, (uint8_t []){0x00, 0x00, 0x02}, 3, 0},
{0xE1, (uint8_t []){0x07, 0xC0, 0x07, 0xC0, 0x06, 0xC0, 0x06, 0xC0, 0x00, 0x24, 0x24}, 11, 0},
{0xE2, (uint8_t []){0x20, 0x20, 0x22, 0x22, 0x8D, 0xC0, 0x00, 0x00, 0x8C, 0xC0, 0x00, 0x00, 0x00}, 13, 0},
{0xE3, (uint8_t []){0x00, 0x00, 0x22, 0x22}, 4, 0},
{0xE4, (uint8_t []){0x44, 0x44}, 2, 0},
{0xE5, (uint8_t []){0x09, 0x91, 0x10, 0xFA, 0x0B, 0x93, 0x10, 0xFA, 0x0D, 0x8D, 0x10, 0xFA, 0x0F, 0x8F, 0x10, 0xFA}, 16, 0},
{0xE6, (uint8_t []){0x00, 0x00, 0x22, 0x22}, 4, 0},
{0xE7, (uint8_t []){0x44, 0x44}, 2, 0},
{0xE8, (uint8_t []){0x08, 0x90, 0x10, 0xFA, 0x0A, 0x92, 0x10, 0xFA, 0x0C, 0x8C, 0x10, 0xFA, 0x0E, 0x8E, 0x10, 0xFA}, 16, 0},
{0xEB, (uint8_t []){0x02, 0x01, 0xE4, 0xE4, 0x88, 0x00, 0x00}, 7, 0},
{0xED, (uint8_t []){0xFF, 0x04, 0x56, 0x7F, 0xBA, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF2, 0xAB, 0xF7, 0x65, 0x40, 0xFF}, 16, 0},
{0xEF, (uint8_t []){0x08, 0x08, 0x08, 0x45, 0x3F, 0x54}, 6, 0},
{0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x00}, 5, 0},
{0x11, (uint8_t []){0x00}, 0, 120},
{0x29, (uint8_t []){0x00}, 0, 0},
};
// 全局变量
static esp_lcd_panel_handle_t mipi_dpi_panel = NULL;
static esp_lcd_dsi_bus_handle_t mipi_dsi_bus = NULL;
static esp_lcd_panel_io_handle_t mipi_dbi_io = NULL;
static esp_ldo_channel_handle_t ldo_mipi_phy = NULL;
static void lcd_init(void)
{
esp_err_t ret;
RG_LOGI("Initializing ST7701 MIPI DSI LCD driver\n");
// 1. 启用 MIPI DSI PHY 电源
#ifdef RG_MIPI_DSI_PHY_PWR_LDO_CHAN
esp_ldo_channel_config_t ldo_mipi_phy_config = {
.chan_id = RG_MIPI_DSI_PHY_PWR_LDO_CHAN,
.voltage_mv = RG_MIPI_DSI_PHY_PWR_LDO_VOLTAGE_MV,
};
ret = esp_ldo_acquire_channel(&ldo_mipi_phy_config, &ldo_mipi_phy);
RG_ASSERT(ret == ESP_OK, "Failed to acquire LDO channel for MIPI DSI PHY");
RG_LOGI("MIPI DSI PHY powered on\n");
#endif
// 2. 创建 MIPI DSI 总线
esp_lcd_dsi_bus_config_t bus_config = {
.bus_id = 0,
.num_data_lanes = RG_MIPI_DSI_LANE_NUM,
.phy_clk_src = MIPI_DSI_PHY_CLK_SRC_DEFAULT,
.lane_bit_rate_mbps = RG_MIPI_DSI_LANE_BITRATE_MBPS,
};
ret = esp_lcd_new_dsi_bus(&bus_config, &mipi_dsi_bus);
RG_ASSERT(ret == ESP_OK, "Failed to create MIPI DSI bus");
RG_LOGI("MIPI DSI bus created\n");
// 3. 创建 MIPI DBI IO (用于发送命令)
esp_lcd_dbi_io_config_t dbi_config = {
.virtual_channel = 0,
.lcd_cmd_bits = 8,
.lcd_param_bits = 8,
};
ret = esp_lcd_new_panel_io_dbi(mipi_dsi_bus, &dbi_config, &mipi_dbi_io);
RG_ASSERT(ret == ESP_OK, "Failed to create MIPI DBI IO");
RG_LOGI("MIPI DBI IO created\n");
// 4. 配置 MIPI DPI 面板 (用于发送像素数据)
esp_lcd_dpi_panel_config_t dpi_config = {
.num_fbs = FB_NUM,
.virtual_channel = 0,
.dpi_clk_src = MIPI_DSI_DPI_CLK_SRC_DEFAULT,
.dpi_clock_freq_mhz = RG_MIPI_DSI_DPI_CLK_MHZ,
.in_color_format = LCD_COLOR_FMT_RGB565,
.video_timing = {
.h_size = 480, // 物理屏幕宽度(固定)
.v_size = 640, // 物理屏幕高度(固定)
.hsync_back_porch = RG_MIPI_DSI_LCD_HBP,
.hsync_pulse_width = RG_MIPI_DSI_LCD_HSYNC,
.hsync_front_porch = RG_MIPI_DSI_LCD_HFP,
.vsync_back_porch = RG_MIPI_DSI_LCD_VBP,
.vsync_pulse_width = RG_MIPI_DSI_LCD_VSYNC,
.vsync_front_porch = RG_MIPI_DSI_LCD_VFP,
},
.flags.use_dma2d = true,
};
// 5. 创建 ST7701 面板
st7701_vendor_config_t vendor_config = {
.init_cmds = st7701_init_cmds,
.init_cmds_size = sizeof(st7701_init_cmds) / sizeof(st7701_lcd_init_cmd_t),
.flags.use_mipi_interface = 1,
.mipi_config = {
.dsi_bus = mipi_dsi_bus,
.dpi_config = &dpi_config,
},
};
esp_lcd_panel_dev_config_t lcd_dev_config = {
#ifdef RG_GPIO_LCD_RST
.reset_gpio_num = RG_GPIO_LCD_RST,
#else
.reset_gpio_num = -1,
#endif
.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB,
.bits_per_pixel = 16, // RGB565
.vendor_config = &vendor_config,
};
ret = esp_lcd_new_panel_st7701(mipi_dbi_io, &lcd_dev_config, &mipi_dpi_panel);
RG_ASSERT(ret == ESP_OK, "Failed to create ST7701 panel");
RG_LOGI("ST7701 panel created\n");
// 6. 复位并初始化面板
ret = esp_lcd_panel_reset(mipi_dpi_panel);
RG_ASSERT(ret == ESP_OK, "Failed to reset panel");
ret = esp_lcd_panel_init(mipi_dpi_panel);
RG_ASSERT(ret == ESP_OK, "Failed to init panel");
RG_LOGI("ST7701 MIPI DSI LCD initialized successfully\n");
}