5 英寸 MIPI 屏驱动与触摸适配指南
1. 概述
本文以 HiDiTing V100 社区开发板和 5 英寸 800×480 MIPI 方屏为例,说明显示面板和带 TP 屏的触摸控制器如何完成硬件连接、驱动配置、代码适配与故障定位。
一块带 TP 的屏实际包含两条彼此独立的链路:
显示链路:GUI/Framebuffer -> LCD 公共层 -> MIPI LCD 总线层 -> MIPI TX -> 显示面板
触摸链路:触摸面板 -> TW31xx -> I2C1/INT/RESET -> 触摸驱动 -> Input Framework -> GUI
因此,“屏幕能显示”和“触摸能上报”应分开调试。无 TP 屏只需要显示链路;带 TP 屏还需要 I2C、触摸中断和复位信号。
插拔 MIPI 或 TP 排线前必须断电。排线不支持热插拔,反接或带电插拔可能损坏屏幕、转接板或开发板。
2. 相关术语
| 术语 | 含义 |
|---|---|
| MIPI DSI | 显示串行接口,负责传输面板初始化命令和像素数据 |
| DCS | Display Command Set,面板通用命令集,例如退出休眠和开启显示 |
| Generic Write | 不按 DCS 语义解释的通用数据包,常用于厂商私有寄存器 |
| HSA/HBP/HFP | 水平同步、后肩和前肩时序 |
| VSA/VBP/VFP | 垂直同步、后肩和前肩时序 |
| PHY data rate | 单条 MIPI 数据 lane 的传输速率 |
| TE | Tearing Effect,同步刷新信号 |
| TP | Touch Panel,触摸面板 |
3. 硬件连接
3.1 MIPI 接口位置
开发板顶部的 FPC 座用于连接 MIPI 显示屏。先确认连接器位置和排线触点方向,再压下锁扣。

显示链路使用一条数据 lane 和一对时钟差分信号。带 TP 型号还会通过屏幕组件和转接板引出 I2C1、触摸中断与触摸复位。适配时需要核对以下信号:
| 类别 | 需要核对的信号 |
|---|---|
| 显示数据 | MIPI 数据 lane、MIPI 时钟 lane |
| 面板控制 | LCD 供电、LCD 复位、背光、TE |
| 触摸控制 | I2C1 SCL、I2C1 SDA、TP INT、TP RESET |
接口定义和电气参数以社区开发板使用指南及屏幕规格书为准。
3.2 5 英寸无 TP 屏
无 TP 屏只连接屏幕显示排线。下图中的排线直接接入开发板 MIPI 接口,没有触摸转接板和触摸排线。

上电前检查:
- 排线完全插到底且两侧深度一致。
- 连接器锁扣已经压紧。
- 排线没有折伤,金手指方向与接口一致。
- 屏幕和开发板放在绝缘、平整的表面。
3.3 5 英寸带 TP 屏
带 TP 屏除了显示排线,还要连接触摸转接板与 TP 排线。下图可作为最终接线形态参考。

带 TP 屏上电后应分两步验收:先确认画面稳定,再确认 I2C 能读取触摸芯片 ID 和中断能持续上报坐标。
4. 软件结构与文件职责
4.1 显示链路
| 文件 | 作用 |
|---|---|
lcd_config.c |
面板时序、初始化命令和 lcd_drv_cfg 注册表 |
lcd_mipi.c |
LCD 公共层到 MIPI TX 的总线适配,负责命令封包、读写、刷新和休眠恢复 |
mipi_tx.c |
MIPI TX 控制器与 PHY 配置、参数校验和收发接口 |
lcd_porting.c |
背光 PWM、复位、供电和板级时序 |
product_evb_standard.h |
LCD、TE 和触摸相关板级 GPIO 定义 |
4.2 触摸链路
| 文件 | 作用 |
|---|---|
touch_ctrl.c |
I2C1 引脚复用、上拉、400 kHz 总线初始化 |
touch_ctrl.h |
触摸地址和寄存器地址长度等公共配置 |
touch_tw31xx.c |
TW31xx 复位、寄存器读写、报文解析、ESD 检测和电源管理 |
touch_tw31xx_drv.c |
把 TW31xx 能力注册到触摸外设接口 |
touch_tw31xx.h |
寄存器、事件类型、报文长度和工作状态定义 |
CMakeLists.txt |
根据触摸类型配置选择对应驱动源文件 |
5. MIPI 显示核心代码走读
5.1 面板时序配置
lcd_config.c 中的 g_panel_st7701_800x480_config 描述 MIPI 主机如何输出像素流:
static combo_dev_cfg_t g_panel_st7701_800x480_config = {
.devno = 0,
.lane_id = {0, -1},
.out_mode = OUT_MODE_DSI_VIDEO,
.out_format = OUT_FORMAT_RGB_16BIT,
.sync_info = {
.hsa_pixels = 20,
.hbp_pixels = 80,
.hact_pixels = 800,
.hfp_pixels = 90,
.vsa_lines = 8,
.vbp_lines = 20,
.vact_lines = 480,
.vfp_lines = 20,
},
.phy_data_rate = 512,
.pixel_clk = 31000,
};
关键点如下:
{0, -1}表示使用一条数据 lane,不能直接套用双 lane 屏的配置。OUT_MODE_DSI_VIDEO表示连续输出视频时序。OUT_FORMAT_RGB_16BIT对应 RGB565。面板、GUI framebuffer 和 MIPI 输出格式必须一致。hact_pixels与vact_lines是有效分辨率;其余参数共同决定一帧的总行列周期。pixel_clk的单位是 kHz,phy_data_rate的单位是 Mbps。
MIPI TX 会校验有效 lane、输出模式、像素格式和时钟关系,其中最容易触发失败的是:
更换面板时,应先从规格书取得 lane 数、像素格式、porch、sync 和推荐时钟,再计算 PHY 速率余量,不能只修改分辨率。
5.2 面板初始化命令表
g_st7701_800x480_init_on[] 保存面板厂商提供的寄存器序列。lcd_cmd_sequ 每项依次表示命令类型、字节数、数据和命令后延时。例如:
const static lcd_cmd_sequ g_st7701_800x480_init_on[] = {
{0, 2, {0xee, 0x01}, 0},
{0, 2, {0xea, 0x07}, 0},
/* 中间为电压、Gamma、GIP 和时序寄存器,必须使用屏厂参数 */
{0, 2, {0xee, 0x00}, 0},
{0, 1, {0x11}, 120},
{0, 1, {0x29}, 100},
};
最后两条分别是 Sleep Out 和 Display On。120 ms 与 100 ms 是面板完成内部上电和显示开启所需的等待时间,不能为了缩短启动时间随意删除。移植新屏时应整体替换厂商寄存器序列,并逐项确认:
- 是 DCS 写还是 Generic 写。
- 命令长度是否包含命令字本身。
- 页面切换、Gamma、电压和 GIP 参数是否属于目标屏批次。
- 复位释放到第一条命令、
Sleep Out到Display On的延时是否满足规格书。
5.3 将面板注册到 LCD 公共层
面板参数和命令表最终通过 g_lcd_driver_ops[] 暴露给 LCD 公共层:
{
.bus_type = BUS_DISPLAY_MIPI,
.bus_cfg = &g_panel_st7701_800x480_config,
.x_start = 0,
.y_start = 0,
.fps = 60,
.lcd_id = LCD_800x480_SCREEN_ID,
.display_on_cmds = &g_st7701_800x480_screen_init_on,
.display_off_cmds = &g_display_off_cmds,
.set_brightnes_cmds = &g_brightness_cmds,
.sleep_in_cmds = &g_sleep_in_cmds,
.sleep_out_cmds = &g_sleep_out_cmds,
.lcd_id_info.reg_addr = 0x04,
.lcd_id_info.read_len = 3,
},
lcd_id 用来匹配面板驱动项;reg_addr 和 read_len 定义面板 ID 的读取方法。若新屏不能可靠读 ID,应在产品选择逻辑中显式指定目标项,不要复用其他屏的 ID 造成误匹配。
5.4 MIPI 总线初始化
mipi_bus_init() 的调用顺序是:
uapi_mipi_tx_init()
-> 可选 bypass 配置
-> uapi_mipi_tx_set_dev_cfg(bus_cfg)
-> 保存分辨率、porch、pixel clock 和刷新区域
之后 mipi_bus_enable() 调用 uapi_mipi_tx_enable() 开始发送。休眠时 mipi_bus_suspend() 先停用并反初始化控制器;恢复时 mipi_bus_resume() 使用保存的面板配置重新初始化并使能,所以 bus_cfg 在整个运行期必须保持有效。
5.5 DCS 与 Generic 命令如何发送
mipi_bus_write() 根据 dcs_flag 选择 DCS 或 Generic 通道。以 DCS 为例,mipi_dcs_cmd_send() 先进入低功耗命令模式,再按负载长度决定数据包类型:
| 命令长度 | 数据包类型 |
|---|---|
| 1 字节 | DCS_SHORT_WRITE_0P,无参数短包 |
| 2 字节 | DCS_SHORT_WRITE_1P,单参数短包 |
| 大于 2 字节 | DCS_LONG_WRITE,长包 |
封包完成后调用 uapi_mipi_tx_set_cmd(),最后执行命令项携带的毫秒延时。读取寄存器时,mipi_bus_read() 先设置最大返回包长度,再调用 uapi_mipi_tx_get_cmd()。
这意味着初始化失败时不能只看命令字,还要检查 dcs_flag、长度和延时;相同字节序列用错误的数据包类型发送,面板可能完全不响应。
5.6 总线能力注册
lcd_register_mipi_bus() 将 MIPI 实现挂到 LCD 总线抽象:
mipi_bus_api.bus_init = mipi_bus_init;
mipi_bus_api.bus_deinit = mipi_bus_deinit;
mipi_bus_api.bus_enable = mipi_bus_enable;
mipi_bus_api.bus_disable = mipi_bus_disable;
mipi_bus_api.bus_write_cmd = mipi_bus_write;
mipi_bus_api.bus_read_cmd = mipi_bus_read;
mipi_bus_api.bus_suspend = mipi_bus_suspend;
mipi_bus_api.bus_resume = mipi_bus_resume;
lcd_bus_register_api(&mipi_bus_api, BUS_DISPLAY_MIPI);
公共层只面向 lcd_bus_api,不会直接访问 MIPI TX。新增面板通常只修改 lcd_config.c;只有新增总线能力或控制器特性时才需要改动 lcd_mipi.c 或 mipi_tx.c。
5.7 板级供电、复位和背光
社区开发板的板级定义包括:
| 功能 | 当前实现 |
|---|---|
| LCD 供电控制 | S_AGPIO20 |
| LCD 复位 | 根据产品版本使用 S_AGPIO13 或 S_AGPIO27 |
| TE | S_AGPIO19 |
| 背光 PWM | S_AGPIO15,AON PWM 通道 0 |
背光 PWM 的时钟为 32 MHz、目标频率为 50 kHz。调屏时建议先确认复位和初始化命令执行成功,再逐步增加背光占空比。只有背光亮并不代表 MIPI 数据链路正常。
6. TW31xx 触摸核心代码走读
6.1 构建选择
5 英寸带 TP 屏对应配置为:
peripheral/touch/CMakeLists.txt 根据该配置只加入:
同一个产品配置中只能选择一种触摸控制器。社区开发板 5 英寸带 TP 目标可参考 diting_community_bike.config。
6.2 I2C1 引脚与上拉
当前板级配置为:
| 项目 | 配置 |
|---|---|
| I2C 控制器 | I2C_BUS_1 |
| SCL/SDA | S_MGPIO6 / S_MGPIO7 |
| 速率 | 400 kHz |
| 7 位从地址 | 0x0C |
| 中断 | S_AGPIO0 |
| 复位 | S_AGPIO6 |
touch_host_peripheral_init() 先把 S_MGPIO6/7 复用为 I2C1,再对两根线启用内部上拉,最后初始化 400 kHz 主机:
uapi_pin_set_mode(S_MGPIO6, HAL_PIO_FUNC_I2C1_M1);
uapi_pin_set_mode(S_MGPIO7, HAL_PIO_FUNC_I2C1_M1);
uapi_pin_set_pull(S_MGPIO6, PIN_PULL_UP);
uapi_pin_set_pull(S_MGPIO7, PIN_PULL_UP);
uapi_i2c_master_init(TOUCH_I2C_BUS, TOUCH_I2C_SPEED_BAUDRATE, 0);
这里的上拉不可省略:屏幕 FPC 没有提供额外 I2C 上拉时,SCL/SDA 会悬空,表现为芯片 ID 读取失败或触摸偶发掉线。
6.3 寄存器地址与数据字节序
TW31xx 使用 16 位大端寄存器地址,但寄存器值按小端解释。驱动没有复用旧触摸芯片的一字节/小端辅助函数,而是专门构造两字节地址:
static ext_errno tw31xx_read_reg(uint16_t reg_addr,
uint8_t *data_buf, uint32_t data_len)
{
uint8_t address[2] = {
(uint8_t)(reg_addr >> 8),
(uint8_t)reg_addr
};
i2c_data_t data = {
.send_buf = address,
.send_len = sizeof(address),
.receive_buf = data_buf,
.receive_len = data_len,
};
return uapi_i2c_master_writeread(
TOUCH_I2C_BUS, TOUCH_I2C_ADDR, &data);
}
实际实现包含 3 次重试。写寄存器时同样先放入大端地址,再追加数据;单次写数据不能超过 14 字节。若把地址顺序写反,I2C 可能仍有 ACK,但读取的是错误寄存器,这是最隐蔽的适配错误之一。
6.4 复位、握手和芯片探测
初始化链路为:
tw31xx_detect()
-> 关闭触摸中断
-> tw31xx_reinitialize()
-> 硬件复位:高 1 ms -> 低 10 ms -> 高 10 ms
-> 轮询 0x0000,等待 RESET 事件
-> 向 0x10FF 写 I2C done
-> 向命令区写 14 字节 normal-mode 命令
-> 等待 COMMAND 回复并清除报告
-> 读取 0x10FC 芯片 ID
-> 开启触摸中断
14 字节 normal-mode 命令分两次写入:前 8 字节写到 0x1042,后 6 字节写到 0x104C。该间隔来自控制器命令区布局,不应改成连续地址写入。
初始化期间关闭中断,是为了同步消费复位和命令回复,避免输入框架尚未进入正常状态时丢失中断边沿。
6.5 中断报文读取
触摸中断进入 tw31xx_irq_callback() 后,驱动先读取固定 16 字节报告头:
只有事件类型为 TW31XX_EVENT_TOUCH 且长度在合法范围内时,驱动才从 data_addr 读取触摸数据。其他事件和非法长度都会被清除,最后向 0x10FF 写完成标记,让控制器释放当前报告。
这一完成握手很关键:忘记写 I2C done 会导致后续事件无法继续上报。
6.6 坐标和事件解析
tw31xx_parse_touch_data() 使用小端方式解析报告:
screen_info = tw31xx_le32(data);
point_num = (screen_info >> 25) & 0x1F;
point_info = tw31xx_le32(data + 12);
finger_id = (point_info >> 26) & 0x1F;
y = point_info & 0x1FFF;
x = (point_info >> 13) & 0x1FFF;
pressed = (point_info >> 31) & 0x01;
首次按下映射为 MC_TP_PRESS,保持接触时映射为 MC_TP_MOVE。没有有效按压点时,再根据手势 ID 映射短按、双击、移动或释放。若画面与触摸方向不一致,应在坐标变换层处理旋转、镜像和缩放,不要修改原始位域定义。
6.7 ESD 恢复与电源管理
驱动每处理 200 个事件读取一次 ESD 计数器。如果计数连续 3 次不变化,则关闭中断、重新执行完整初始化,成功后恢复中断。
进入睡眠或待机时,驱动关闭中断并拉低触摸复位;恢复正常模式时重新探测芯片。若唤醒后只有显示、没有触摸,应重点确认恢复路径是否重新完成 I2C 初始化、TW31xx 探测和中断使能。
7. 适配另一款 MIPI 面板
建议按以下顺序修改并逐项验证:
- 从规格书记录分辨率、像素格式、lane 数、video/command mode、porch、sync、像素时钟和 PHY 速率。
- 在
lcd_config.c新建独立的combo_dev_cfg_t,不要直接覆盖其他在用面板。 - 加入目标面板厂商提供的完整初始化命令,保留每条命令所需延时。
- 在
g_lcd_driver_ops[]新增注册项,配置显示开关、休眠、亮度、TE 和 ID 读取。 - 若电源、复位、TE 或背光引脚不同,在板级 porting 层修改,不要把 GPIO 操作塞进命令表。
- 先验证复位、供电和背光,再验证 MIPI 时钟和数据 lane,最后检查颜色、刷新率与休眠恢复。
首次点亮建议使用纯红、绿、蓝、黑、白测试图:颜色错误通常对应 RGB 格式或字节顺序,周期性错位通常对应时序,随机花屏更可能与 lane、PHY 速率或信号完整性有关。
8. 适配另一款触摸控制器
新增触摸控制器时需要同时完成:
- 在输入模块配置中增加互斥选项。
- 在
peripheral/touch/CMakeLists.txt中按配置选择新驱动及头文件目录。 - 实现触摸外设 API:初始化、反初始化、取点、挂起、恢复、睡眠、总线初始化和中断注册。
- 核对 I2C 地址、寄存器地址宽度、字节序、最大触点数、INT 有效电平和复位时序。
- 将芯片原始状态映射为
MC_TP_PRESS、MC_TP_MOVE、MC_TP_RELEASE等统一事件。 - 验证长按、滑动、快速连点、睡眠唤醒和异常断开恢复。
不要只改 I2C 地址。不同触摸控制器的报告头、数据地址、坐标位域和完成握手通常完全不同。
9. 编译、烧录和验收
在 SDK 工程根目录执行:
烧录生成的 output/3322/fwpkg/diting-community-bike.fwpkg,使用 750000 波特率查看串口日志。五英寸 MIPI 屏使用带 bike 的目标;不带 bike 的 pack_diting_community 目标对应 QSPI 屏镜像。显示和触摸驱动修改后应进行干净构建,避免缓存的配置或静态库掩盖问题。
建议验收项:
| 项目 | 通过标准 |
|---|---|
| 冷启动 | 连续多次上电均能点亮,无白屏、花屏 |
| 颜色 | 红绿蓝、黑白测试图颜色正确,无固定色偏 |
| 刷新 | 动画无明显撕裂,长时间运行无随机闪屏 |
| 休眠恢复 | 熄屏、亮屏后显示和触摸均恢复 |
| 单击/滑动 | 坐标方向正确,边缘区域可触达 |
| 稳定性 | 连续触摸和运行时没有 I2C 错误、事件卡死或异常重启 |
10. 常见问题
| 现象 | 优先检查 |
|---|---|
| 完全不亮 | 供电、排线方向、复位时序、背光 PWM |
| 背光亮但无画面 | 初始化命令、MIPI lane、PHY 是否使能、面板 ID 选择 |
| 固定颜色或颜色错乱 | RGB565/RGB888、framebuffer 格式、初始化寄存器 |
| 周期性错行或画面滚动 | HSA/HBP/HFP、VSA/VBP/VFP、pixel clock |
| 随机花屏 | PHY data rate、lane 极性、排线接触和信号完整性 |
| 显示正常但无触摸 | I2C1 复用和上拉、地址 0x0C、INT/RESET、触摸类型配置 |
| 能读 ID 但无坐标 | 报告地址/长度、事件类型、I2C done 握手、中断极性 |
| 坐标镜像或旋转 | GUI 坐标变换与屏幕安装方向 |
| 唤醒后无触摸 | I2C 恢复、重新探测和中断重新使能 |