跳转至

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 显示屏。先确认连接器位置和排线触点方向,再压下锁扣。

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 接口,没有触摸转接板和触摸排线。

5 英寸无 TP MIPI 屏接线

上电前检查:

  1. 排线完全插到底且两侧深度一致。
  2. 连接器锁扣已经压紧。
  3. 排线没有折伤,金手指方向与接口一致。
  4. 屏幕和开发板放在绝缘、平整的表面。

3.3 5 英寸带 TP 屏

带 TP 屏除了显示排线,还要连接触摸转接板与 TP 排线。下图可作为最终接线形态参考。

5 英寸带 TP MIPI 屏接线

带 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、输出模式、像素格式和时钟关系,其中最容易触发失败的是:

bits_per_pixel × pixel_clk <= phy_data_rate × 1000

更换面板时,应先从规格书取得 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 是面板完成内部上电和显示开启所需的等待时间,不能为了缩短启动时间随意删除。移植新屏时应整体替换厂商寄存器序列,并逐项确认:

  1. 是 DCS 写还是 Generic 写。
  2. 命令长度是否包含命令字本身。
  3. 页面切换、Gamma、电压和 GIP 参数是否属于目标屏批次。
  4. 复位释放到第一条命令、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 屏对应配置为:

CONFIG_INPUT_USING_TPTYPE_TW31XX=y

peripheral/touch/CMakeLists.txt 根据该配置只加入:

tw31xx_touch/touch_tw31xx.c
tw31xx_touch/touch_tw31xx_drv.c

同一个产品配置中只能选择一种触摸控制器。社区开发板 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 字节报告头:

0x0000 报告头
  -> event_type:事件类型
  -> data_addr:本次数据地址
  -> data_len:本次数据长度

只有事件类型为 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 面板

建议按以下顺序修改并逐项验证:

  1. 从规格书记录分辨率、像素格式、lane 数、video/command mode、porch、sync、像素时钟和 PHY 速率。
  2. 在 lcd_config.c 新建独立的 combo_dev_cfg_t,不要直接覆盖其他在用面板。
  3. 加入目标面板厂商提供的完整初始化命令,保留每条命令所需延时。
  4. 在 g_lcd_driver_ops[] 新增注册项,配置显示开关、休眠、亮度、TE 和 ID 读取。
  5. 若电源、复位、TE 或背光引脚不同,在板级 porting 层修改,不要把 GPIO 操作塞进命令表。
  6. 先验证复位、供电和背光,再验证 MIPI 时钟和数据 lane,最后检查颜色、刷新率与休眠恢复。

首次点亮建议使用纯红、绿、蓝、黑、白测试图:颜色错误通常对应 RGB 格式或字节顺序,周期性错位通常对应时序,随机花屏更可能与 lane、PHY 速率或信号完整性有关。

8. 适配另一款触摸控制器

新增触摸控制器时需要同时完成:

  1. 在输入模块配置中增加互斥选项。
  2. 在 peripheral/touch/CMakeLists.txt 中按配置选择新驱动及头文件目录。
  3. 实现触摸外设 API:初始化、反初始化、取点、挂起、恢复、睡眠、总线初始化和中断注册。
  4. 核对 I2C 地址、寄存器地址宽度、字节序、最大触点数、INT 有效电平和复位时序。
  5. 将芯片原始状态映射为 MC_TP_PRESS、MC_TP_MOVE、MC_TP_RELEASE 等统一事件。
  6. 验证长按、滑动、快速连点、睡眠唤醒和异常断开恢复。

不要只改 I2C 地址。不同触摸控制器的报告头、数据地址、坐标位域和完成握手通常完全不同。

9. 编译、烧录和验收

在 SDK 工程根目录执行:

fbb set-target pack_diting_community_bike
fbb build --clean diting-community-bike

烧录生成的 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 恢复、重新探测和中断重新使能

11. 相关资料