I.MX6U 裸机开发 24. 电容触摸屏与 I2C 驱动

I.MX6U 裸机开发 24. 电容触摸屏与 I2C 驱动

    • 先看结论:本课实测到的硬件真相
    • 一、实验环境与硬件连接
      • [1. 环境](#1. 环境)
      • [2. 引脚分配(**实测值**)](#2. 引脚分配(实测值))
      • [3. 怎么确定自己的板子挂在哪条总线](#3. 怎么确定自己的板子挂在哪条总线)
    • 二、触摸屏原理速览
      • [1. 电阻屏 vs 电容屏](#1. 电阻屏 vs 电容屏)
      • [2. 电容屏怎么"看见"手指](#2. 电容屏怎么"看见"手指)
      • [3. INT / RST 两个引脚的作用](#3. INT / RST 两个引脚的作用)
      • [4. 为什么最终改成「中断置标志 + 主循环读」](#4. 为什么最终改成「中断置标志 + 主循环读」)
    • [三、I2C 协议与 i.MX6ULL 的 I2C 外设](#三、I2C 协议与 i.MX6ULL 的 I2C 外设)
      • [1. 主机读一次寄存器的完整时序](#1. 主机读一次寄存器的完整时序)
      • [2. 关键寄存器](#2. 关键寄存器)
      • [3. 波特率](#3. 波特率)
      • [4. 本文遇到的问题](#4. 本文遇到的问题)
        • [1:START 之后不要等 IBB](#1:START 之后不要等 IBB)
        • [2:读最后一字节要提前 STOP](#2:读最后一字节要提前 STOP)
        • [3:最后一字节的 RXAK=1 不是错误](#3:最后一字节的 RXAK=1 不是错误)
        • 4:仲裁丢失必须软复位控制器
    • [四、GT1158 驱动要点](#四、GT1158 驱动要点)
      • [1. 寄存器表(16 位地址)](#1. 寄存器表(16 位地址))
        • [修复记录:IRQ_CFG 地址曾被 `unsigned char` 截断(**代表性问题,务必一看**)](#修复记录:IRQ_CFG 地址曾被 unsigned char 截断(代表性问题,务必一看))
      • [2. 复位与 I2C 地址时序](#2. 复位与 I2C 地址时序)
      • [3. 读点流程](#3. 读点流程)
      • [4. 三种触控 IC 对照](#4. 三种触控 IC 对照)
    • 五、代码结构
      • [1. 本课新增文件](#1. 本课新增文件)
      • [2. 调用链](#2. 调用链)
      • [3. 中断服务程序(只做最少的事)](#3. 中断服务程序(只做最少的事))
      • [4. `led.c` 的隐蔽陷阱](#4. led.c 的隐蔽陷阱)
      • [5. 坐标方向校正](#5. 坐标方向校正)
    • 六、编译与运行
      • [1. 编译(WSL)](#1. 编译(WSL))
      • [2. USB-OTG 下载(Windows PowerShell)](#2. USB-OTG 下载(Windows PowerShell))
      • [3. 实测串口输出](#3. 实测串口输出)
      • [4. 屏上现象](#4. 屏上现象)
      • [5. 调试顺序建议](#5. 调试顺序建议)
    • 七、本次遇到问题汇总
    • 八、常见问题排查
    • 九、参考资料

调试方式同前几课 :WSL(Ubuntu)交叉编译 + Windows tools/dl.py USB-OTG 下载到 DDR 运行,串口 @ 115200。


先看结论:本课实测到的硬件真相

项目 例程 / 常见说法 本板实测
触控 IC 型号 FT5426 或 GT9147 GT1158 (GT9xx 家族,产物 ID 首字符 '1')
挂载总线 I2C1 I2C2
I2C 引脚 UART4_TX/RX_DATA(GPIO1_IO28/29) UART5_TX/RX_DATA(GPIO1_IO30/31)
7 位地址 0x38 / 0x5D 0x14
寄存器地址宽度 8 位(FT5426) 16 位
取点方式 主循环轮询 INT 引脚 GPIO 中断置标志,主循环再读 I2C
中断极性 固定下降沿 读 IC 的配置寄存器动态决定:0x8056 读回 1 → FallingEdge

一句话总结本课最值钱的经验:先让 printf 打出产物 ID,再决定走哪个驱动分支,别照抄例程。

附带一条教训:凡是这类"读失败就兜底"的代码,一定要想办法验证兜底路径有没有被走到。

本项目的中断极性就是因为寄存器地址被 8 位变量截断,读失败后兜底成了 RisingEdge,

而本板真实极性是 FallingEdge ------ 详见 4.1 节。


一、实验环境与硬件连接

1. 环境

项目 值
板子 正点原子 I.MX6ULL ALPHA / Mini
LCD 电容触摸屏,屏 ID 0x4384(ATK4384,800×480)
触控 IC GT1158 (I2C2,地址 0x14),代码同时兼容 FT5426 / GT9147 / 电阻屏
拨码 0100(USB 下载模式)
串口 115200 / 8 / 1 / 无(本机实测 COM16 = CH340)
代码链接地址 0x87800000
显存地址 0x89000000

2. 引脚分配(实测值)

信号 PAD 复用 电气属性 说明
I2C2_SCL UART5_TX_DATA ALT2 + SION=1 0x70B0 GPIO1_IO30
I2C2_SDA UART5_RX_DATA ALT2 + SION=1 0x70B0 GPIO1_IO31
CT_INT GPIO1_IO09 ALT0 GPIO 初始化 0x10B0,最终 0x0080 最终为带上拉输入
CT_RST SNVS_TAMPER9 ALT5 GPIO5_IO09 0x10B0 推挽输出,低有效

为什么 I2C 引脚要开 SION?

SION(Software Input On)把 pad 的输出回环到输入。I2C 半双工、支持时钟延展与仲裁,控制器必须读回自己发出的 SDA/SCL 电平才能判断 ACK 与总线忙闲。不开 SION 的典型表现是「写地址永远收不到 ACK(RXAK=1)」。
为什么不用 I2C1 的另外两个 pad(GPIO1_IO02/03)?

因为 IO03 正好是 LED0 ,点灯和总线会打架。所以 I2C1 只剩下 UART4 这一组可选。

3. 怎么确定自己的板子挂在哪条总线

裸机最快的手段就是扫总线 。本课在找不到触控芯片时会自动执行 i2c_bus_diag(),依次报告:

  1. I2C1@UART4 配好,扫 0x08~0x77
  2. I2C2@UART5 配好,再扫一遍
  3. 把 pad 切成 GPIO 输入,打印 SCL/SDA 静态电平(判断有没有上拉)
  4. 打印 INT / RST 电平(判断是不是一直卡在复位)

#mermaid-svg-Gfc3GC7L95D1S03N{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Gfc3GC7L95D1S03N .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Gfc3GC7L95D1S03N .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Gfc3GC7L95D1S03N .error-icon{fill:#552222;}#mermaid-svg-Gfc3GC7L95D1S03N .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Gfc3GC7L95D1S03N .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Gfc3GC7L95D1S03N .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Gfc3GC7L95D1S03N .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Gfc3GC7L95D1S03N .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Gfc3GC7L95D1S03N .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Gfc3GC7L95D1S03N .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Gfc3GC7L95D1S03N .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Gfc3GC7L95D1S03N .marker.cross{stroke:#333333;}#mermaid-svg-Gfc3GC7L95D1S03N svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Gfc3GC7L95D1S03N p{margin:0;}#mermaid-svg-Gfc3GC7L95D1S03N .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Gfc3GC7L95D1S03N .cluster-label text{fill:#333;}#mermaid-svg-Gfc3GC7L95D1S03N .cluster-label span{color:#333;}#mermaid-svg-Gfc3GC7L95D1S03N .cluster-label span p{background-color:transparent;}#mermaid-svg-Gfc3GC7L95D1S03N .label text,#mermaid-svg-Gfc3GC7L95D1S03N span{fill:#333;color:#333;}#mermaid-svg-Gfc3GC7L95D1S03N .node rect,#mermaid-svg-Gfc3GC7L95D1S03N .node circle,#mermaid-svg-Gfc3GC7L95D1S03N .node ellipse,#mermaid-svg-Gfc3GC7L95D1S03N .node polygon,#mermaid-svg-Gfc3GC7L95D1S03N .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Gfc3GC7L95D1S03N .rough-node .label text,#mermaid-svg-Gfc3GC7L95D1S03N .node .label text,#mermaid-svg-Gfc3GC7L95D1S03N .image-shape .label,#mermaid-svg-Gfc3GC7L95D1S03N .icon-shape .label{text-anchor:middle;}#mermaid-svg-Gfc3GC7L95D1S03N .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Gfc3GC7L95D1S03N .rough-node .label,#mermaid-svg-Gfc3GC7L95D1S03N .node .label,#mermaid-svg-Gfc3GC7L95D1S03N .image-shape .label,#mermaid-svg-Gfc3GC7L95D1S03N .icon-shape .label{text-align:center;}#mermaid-svg-Gfc3GC7L95D1S03N .node.clickable{cursor:pointer;}#mermaid-svg-Gfc3GC7L95D1S03N .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Gfc3GC7L95D1S03N .arrowheadPath{fill:#333333;}#mermaid-svg-Gfc3GC7L95D1S03N .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Gfc3GC7L95D1S03N .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Gfc3GC7L95D1S03N .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Gfc3GC7L95D1S03N .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Gfc3GC7L95D1S03N .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Gfc3GC7L95D1S03N .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Gfc3GC7L95D1S03N .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Gfc3GC7L95D1S03N .cluster text{fill:#333;}#mermaid-svg-Gfc3GC7L95D1S03N .cluster span{color:#333;}#mermaid-svg-Gfc3GC7L95D1S03N div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Gfc3GC7L95D1S03N .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Gfc3GC7L95D1S03N rect.text{fill:none;stroke-width:0;}#mermaid-svg-Gfc3GC7L95D1S03N .icon-shape,#mermaid-svg-Gfc3GC7L95D1S03N .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Gfc3GC7L95D1S03N .icon-shape p,#mermaid-svg-Gfc3GC7L95D1S03N .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Gfc3GC7L95D1S03N .icon-shape .label rect,#mermaid-svg-Gfc3GC7L95D1S03N .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Gfc3GC7L95D1S03N .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Gfc3GC7L95D1S03N .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Gfc3GC7L95D1S03N :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 否
是
touch_init
I2C2 @ UART5 初始化
读 PRODUCT_ID 0x8140
前两字节是数字?
失败: 自动跑 i2c_bus_diag
打印 CTP ID
扫 I2C1@UART4
扫 I2C2@UART5
打印 pad 电平 / INT / RST
按 ID 首字符选中断配置寄存器
读中断极性, 配置 GPIO1_IO09 中断
使能 GPIO1_Combined_0_15_IRQn
成功

源代码:

c 复制代码
/**
 * @brief 依次尝试所有可能的 I2C 引脚组合,定位触控 IC 到底挂在哪一组
 *
 * I2C1 可选 pad:UART4_TX/RX_DATA(GPIO1_IO28/29)
 * I2C2 可选 pad:UART5_TX/RX_DATA(GPIO1_IO30/31)
 * (GPIO1_IO02/03 这组在正点原子板上与 LED0 冲突,不参与扫描)
 */
static void i2c_bus_diag(void)
{
    printf("I2C bus diagnostic:\r\n");

    /* 1) I2C1 @ UART4 (IO28/IO29) */
    IOMUXC_SetPinMux(IOMUXC_UART4_TX_DATA_I2C1_SCL, 1U);
    IOMUXC_SetPinMux(IOMUXC_UART4_RX_DATA_I2C1_SDA, 1U);
    IOMUXC_SetPinConfig(IOMUXC_UART4_TX_DATA_I2C1_SCL, 0x1B8B0U);
    IOMUXC_SetPinConfig(IOMUXC_UART4_RX_DATA_I2C1_SDA, 0x1B8B0U);
    i2c_init(I2C1);
    i2c_scan_bus("I2C1@UART4 IO28/29", I2C1);

    /* 2) I2C2 @ UART5 (IO30/IO31) */
    IOMUXC_SetPinMux(IOMUXC_UART5_TX_DATA_I2C2_SCL, 1U);
    IOMUXC_SetPinMux(IOMUXC_UART5_RX_DATA_I2C2_SDA, 1U);
    IOMUXC_SetPinConfig(IOMUXC_UART5_TX_DATA_I2C2_SCL, 0x1B8B0U);
    IOMUXC_SetPinConfig(IOMUXC_UART5_RX_DATA_I2C2_SDA, 0x1B8B0U);
    i2c_init(I2C2);
    i2c_scan_bus("I2C2@UART5 IO30/31", I2C2);

    /* 3) 把 I2C1 的两个 pad 切成 GPIO 输入,看线上有没有上拉
     *    两个都是 1 说明上拉/连线基本正常;一直是 0 说明线被拉死或没接上 */
    IOMUXC_SetPinMux(IOMUXC_UART4_TX_DATA_GPIO1_IO28, 0U);
    IOMUXC_SetPinMux(IOMUXC_UART4_RX_DATA_GPIO1_IO29, 0U);
    IOMUXC_SetPinConfig(IOMUXC_UART4_TX_DATA_GPIO1_IO28, 0xF080U);  /* 带上拉输入 */
    IOMUXC_SetPinConfig(IOMUXC_UART4_RX_DATA_GPIO1_IO29, 0xF080U);
    GPIO1->GDIR &= ~((1U << 28) | (1U << 29));
    delay_ms(2);
    printf("pad level: SCL(IO28)=%d SDA(IO29)=%d\r\n",
           (int)((GPIO1->DR >> 28) & 0x1U),
           (int)((GPIO1->DR >> 29) & 0x1U));

    /* 4) 顺便看 INT / RST 引脚的静态电平(INT 正常应为高,RST 应为高) */
    printf("INT(IO09)=%d RST(GPIO5_IO09)=%d\r\n",
           gpio_pinread(GPIO1, 9), gpio_pinread(GPIO5, 9));
}

二、触摸屏原理速览

1. 电阻屏 vs 电容屏

对比项 电阻屏 电容屏(本课)
结构 两层 ITO 薄膜,按压使上下层导通 ITO 蚀刻成行列电极阵列
感知原理 分压 → 模拟电压 手指改变电极耦合电容
触摸点数 1 点 多点(GT1158 支持 5 点)
输出 模拟量,需 ADC 采样 数字坐标,走 I2C
MCU 接口 4 个 GPIO + ADC 2 根 I2C + INT + RST
校准 必须标定 出厂已标定,一般直接可用

本课主程序走电容屏 路径。仓库里另外保留了 bsp_rtouch.c(四线电阻屏实现,用 GPIO1_IO01~04 + ADC1 分两次采样 X/Y),作为对照学习,当前 main.c 未启用。

2. 电容屏怎么"看见"手指

屏玻璃上蚀刻了两组互相绝缘的透明电极(TX 发射行 / RX 接收列),交叉点处形成耦合电容。手指是导体,压上去会"偷走"一部分电场,导致该交叉点的互电容下降。触控 IC 扫描整个行列矩阵,算出电容下降最显著的位置,就是触点坐标。

坐标在 IC 内部算好后放进寄存器,MCU 只要用 I2C 读寄存器 就能拿到现成坐标------所以 MCU 端的工作量主要在 I2C 通信 和坐标换算 / 限幅上。

3. INT / RST 两个引脚的作用

  • INT :触控 IC 准备好一帧坐标后按配置好的极性翻转引脚通知 MCU。好处是 MCU 不必一直去 poll I2C,有数据再读,既省总线又不丢帧。
  • RST :低电平复位。除了复位本身,它还和 INT 配合决定 I2C 器件地址(见 4.2 节)。

4. 为什么最终改成「中断置标志 + 主循环读」

最初的实现是主循环 delay_ms(20) 后直接 touch_read() 轮询 I2C,现象是读着读着就 ARBLOST(仲裁丢失),总线卡死。

原因分析:

  • printf 本身耗时较长,I2C 传输期间若被 GPIO 中断服务程序打断(哪怕是无关中断),SCL 时序可能超出从机的时钟延展容忍窗口;
  • 更糟的是早期版本在 ISR 里直接跑 I2C 读点,ISR 长耗时 + 嵌套,总线基本必挂。

最终方案(其实就是 Linux 的上半部 / 下半部思想):
I2C2 主循环 中断服务程序 GPIO1_IO09 GT1158 I2C2 主循环 中断服务程序 GPIO1_IO09 GT1158 #mermaid-svg-PTDZMDmMOjOfOLFD{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-PTDZMDmMOjOfOLFD .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-PTDZMDmMOjOfOLFD .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-PTDZMDmMOjOfOLFD .error-icon{fill:#552222;}#mermaid-svg-PTDZMDmMOjOfOLFD .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-PTDZMDmMOjOfOLFD .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-PTDZMDmMOjOfOLFD .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-PTDZMDmMOjOfOLFD .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-PTDZMDmMOjOfOLFD .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-PTDZMDmMOjOfOLFD .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-PTDZMDmMOjOfOLFD .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-PTDZMDmMOjOfOLFD .marker{fill:#333333;stroke:#333333;}#mermaid-svg-PTDZMDmMOjOfOLFD .marker.cross{stroke:#333333;}#mermaid-svg-PTDZMDmMOjOfOLFD svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-PTDZMDmMOjOfOLFD p{margin:0;}#mermaid-svg-PTDZMDmMOjOfOLFD .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-PTDZMDmMOjOfOLFD text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-PTDZMDmMOjOfOLFD .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-PTDZMDmMOjOfOLFD .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-PTDZMDmMOjOfOLFD .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-PTDZMDmMOjOfOLFD .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-PTDZMDmMOjOfOLFD #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-PTDZMDmMOjOfOLFD .sequenceNumber{fill:white;}#mermaid-svg-PTDZMDmMOjOfOLFD #sequencenumber{fill:#333;}#mermaid-svg-PTDZMDmMOjOfOLFD #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-PTDZMDmMOjOfOLFD .messageText{fill:#333;stroke:none;}#mermaid-svg-PTDZMDmMOjOfOLFD .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-PTDZMDmMOjOfOLFD .labelText,#mermaid-svg-PTDZMDmMOjOfOLFD .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-PTDZMDmMOjOfOLFD .loopText,#mermaid-svg-PTDZMDmMOjOfOLFD .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-PTDZMDmMOjOfOLFD .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-PTDZMDmMOjOfOLFD .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-PTDZMDmMOjOfOLFD .noteText,#mermaid-svg-PTDZMDmMOjOfOLFD .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-PTDZMDmMOjOfOLFD .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-PTDZMDmMOjOfOLFD .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-PTDZMDmMOjOfOLFD .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-PTDZMDmMOjOfOLFD .actorPopupMenu{position:absolute;}#mermaid-svg-PTDZMDmMOjOfOLFD .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-PTDZMDmMOjOfOLFD .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-PTDZMDmMOjOfOLFD .actor-man circle,#mermaid-svg-PTDZMDmMOjOfOLFD line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-PTDZMDmMOjOfOLFD :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 按极性翻转引脚 GPIO1_Combined_0_15_IRQn irq_cnt++ / 置 intflag=1 gpio_disableint 关本脚中断(防重复进入) gpio_clearintflags 发现 intflag==1, 清 0 读 0x814E → 清 0 → 读 0x8150 坐标 touch_map_coord 换算 / 限幅 重新 gpio_enableint

ISR 里绝不碰 I2C,只置标志。 这是本课能稳定跑起来最关键的一条改动。


三、I2C 协议与 i.MX6ULL 的 I2C 外设

1. 主机读一次寄存器的完整时序

text 复制代码
写: S | addr<<1 + W | ACK | reg[15:8] | ACK | reg[7:0] | ACK | data... | ACK | P

读: S | addr<<1 + W | ACK | reg[15:8] | ACK | reg[7:0] | ACK |
    Sr | addr<<1 + R | ACK | data... | ACK | last byte NAK | P

关键是中间那个 Sr(Repeated Start):写寄存器地址和读数据之间必须再发一次起始条件并切到读方向,不能直接 STOP。

2. 关键寄存器

寄存器 位 作用
IADR - 自身机地址,主机模式不用
IFDR 5:0 分频系数,必须在 IEN=0 时写
I2CR 7 IEN 使能 / 6 IIEN 中断使能 / 5 MSTA 主机模式 / 4 MTX 方向 / 3 TXAK 不应答 / 2 RSTA 重复起始 控制寄存器
I2SR 7 ICF 传输完成 / 5 IBB 总线忙 / 4 IAL 仲裁丢失 / 1 IIF 中断挂起(写 0 清)/ 0 RXAK 未应答 状态寄存器
I2DR 7:0 数据寄存器

3. 波特率

IPG_CLK_ROOT = 66MHz,IFDR = 0x15 对应 640 分频:

text 复制代码
66MHz / 640 ≈ 103 KHz    (标准模式 100KHz)

4. 本文遇到的问题

1:START 之后不要等 IBB

直觉写法是先拉 START、等 IBB=1 再写地址。实际这样做必挂:

c 复制代码
/* 错误:等 IBB 之后再写 I2DR,地址字节会丢 */
base->I2CR |= I2CR_MSTA | I2CR_MTX;
while ((base->I2SR & I2SR_IBB) == 0) ;
base->I2DR = addr << 1;

/* 正确:置 MSTA+MTX 后立刻写地址 */
base->I2CR |= I2CR_MSTA | I2CR_MTX;
base->I2DR = ((unsigned int)address << 1) |
             ((direction == kI2C_Read) ? 1U : 0U);

表现为:扫遍整条总线一个 ACK 都收不到;或 IIF 超时后 IBB 一直为 1。

2:读最后一字节要提前 STOP

i.MX6ULL 是先收数据、后回 ACK/NAK 的写寄存器式控制器,流程必须是:

text 复制代码
数据还剩 2 字节 → 什么都不做
数据还剩 1 字节 → 置 TXAK(下一个字节回 NAK)
数据还剩 0 字节 → 先发 STOP,再读 I2DR

顺序倒过来就会少收一个字节,或者多触发一次传输。

3:最后一字节的 RXAK=1 不是错误

主机读最后一个字节时按协议要回 NAK ,此时 RXAK=1 完全正常,不能当失败处理 。

所以 i2c_wait_iif(base, check_nak) 在读阶段要传 0,跳过 NAK 检查,只判 IAL。

4:仲裁丢失必须软复位控制器

IAL 置位后光清标志没用,总线会一直表现为 busy。正确恢复:

c 复制代码
base->I2SR &= ~I2SR_IAL;
base->I2CR &= ~I2CR_IEN;      /* 关 I2C */
base->I2CR |=  I2CR_IEN;      /* 再开 */

驱动里 i2c_hw_reset() 更进一步,把整条 I2SR 清零并重开使能。每到 ARBLOST / BUSY / TIMEOUT,读点函数都会主动调一次 i2c_init(I2C2) 自愈。


四、GT1158 驱动要点

1. 寄存器表(16 位地址)

地址 名称 说明
0x8040 CTRL bit1 软复位 ------ GT1158 上不要发,会跑飞
0x804D IRQ_CFG(GT9xx) bit1:0 中断极性
0x8056 IRQ_CFG(GT115x) bit1:0 中断极性 ------ 本板走这个,读回值 1
0x8140 PRODUCT_ID 4 字节 ASCII ID(本板为 1158),后跟固件信息
0x814E STATUS bit7 数据就绪 / bit3:0 有效点数 ------ 读完必须写 0 清除
0x8150 TP1 第一点起始,每点 8 字节,X/Y 为小端

判断用哪个 IRQ_CFG 就看产物 ID 首字符:'1'(0x31)开头走 GT115x 的 0x8056,否则走 GT9xx 的 0x804D。

修复记录:IRQ_CFG 地址曾被 unsigned char 截断(代表性问题,务必一看)

修复前 ,bsp_gt9147.c 里 irq_reg 被声明为 unsigned char ,装不下 16 位的 0x8056,

赋值时被静默截断成 0x0056,I2C 于是跑去读一个并不存在的寄存器:

c 复制代码
unsigned char irq_reg;                       /* 8 位,装不下 16 位地址 */
irq_reg = (temp[0] == 0x31U) ? GT115X_IRQ_CFG_REG /* 0x8056 */ : GT9XX_IRQ_CFG_REG;
/* 结果 irq_reg == 0x56,实际读的是 0x0056 */

当时的日志:

text 复制代码
GT irq_cfg@0x0056 = 0        ← 地址已被截断
InterruptMode: RisingEdge    ← 0 是读失败后的兜底值,并非 IC 的真实配置

修复后 (改成 unsigned int),同一块板子打出:

text 复制代码
GT irq_cfg@0x8056 = 1        ← 地址正常
InterruptMode: FallingEdge   ← 本板真实极性!

结论与教训:

  1. 之前那个 RisingEdge 完全是兜底来的 ------ 本板 GT1158 的真实极性是 FallingEdge 。
    也就是说在截断 bug 存在期间,中断极性一直是配错的。
  2. 之所以"看起来能用",是因为 GT1158 在两种边沿下都会产生脉冲,叠加主循环的轮询窗口,
    错配的极性仍能捞到一部分事件。这种"能跑但其实是错的"配置最危险,换屏或提速必翻车。
  3. 凡是写着「读失败就兜底」的代码,务必把真实返回值打日志出来 ,
    否则永远分不清自己走的是正路还是兜底路径 ------ 这两者的差别在日志里是不显形的。

2. 复位与 I2C 地址时序

GT9xx 的 7 位地址由上电瞬间 INT 的电平 决定。本驱动固定使用 0x14,做法是让 INT 在复位释放后保持低电平足够久:

c 复制代码
gpio_pinwrite(GPIO5, 9, 0);   /* RST 拉低 */
delay_ms(10);
gpio_pinwrite(GPIO5, 9, 1);   /* RST 释放 */
delay_ms(10);
gpio_pinwrite(GPIO1, 9, 0);   /* INT 拉低并维持 */
delay_ms(100);                /* 保证锁到 0x14 */

这段时间里 INT 被当输出用,等极性配置好之后再切回输入。

3. 读点流程

c 复制代码
/* 1) 读状态 */
i2c_read_byte(I2C2, 0x14, 0x814E, 2, &status);
num = status & 0x0F;
if (num == 0 || num > TOUCH_MAX_POINTS) {
    if (status & 0x80) i2c_write_byte(I2C2, 0x14, 0x814E, 2, 0x00);  /* 空也要清 */
    return 0;
}

/* 2) 清状态寄存器,允许 IC 准备下一帧 */
i2c_write_byte(I2C2, 0x14, 0x814E, 2, 0x00);

/* 3) 读坐标:小端 */
i2c_read(I2C2, 0x14, 0x8150, 2, xy, 4);
x = xy[0] | (xy[1] << 8);
y = xy[2] | (xy[3] << 8);
if (x > 2000 || y > 2000) return 0;      /* 过滤未贴合时的脏数据 */

touch_map_coord(&x, &y);                 /* 映射 + 限幅 */

0x814E 读完必须写 0。不清的话 IC 认为 MCU 还没取走数据,不再产生下一次中断,表现为「只有第一个点有反应」。

4. 三种触控 IC 对照

项目 FT5426 GT9147 GT1158(本板)
地址宽度 8 位 16 位 16 位
7 位地址 0x38 0x5D / 0x14 0x14
坐标字节序 大端(高 4 位在 XH3:0) 小端 小端
每点字节数 6 8 8
状态寄存器 0x02 0x814E 0x814E
中断配置寄存器 0xA4 0x804D 0x8056 (读回 1,见 4.1 修复记录)
软复位 可以 可以 禁止

驱动层保留了 bsp_ft5426.c / bsp_gt9147.c 两套实现,便于换屏对比,统一由 bsp_touch.c 分发。


五、代码结构

1. 本课新增文件

文件 作用
board/imx6u/bsp_i2c.{c,h} I2C 主机轮询驱动,含 ARBLOST 自愈
board/imx6u/bsp_touch.{c,h} 触控统一层:总线选择、坐标映射 / 限幅、类型分发
board/imx6u/bsp_gt9147.{c,h} GT9xx/GT1158 驱动(本板实际使用,中断方式)
board/imx6u/bsp_ft5426.{c,h} FT5426 驱动(对照用)
board/imx6u/bsp_rtouch.{c,h} 四线电阻屏驱动(对照用,未启用)
board/imx6u/bsp_adc.{c,h} ADC1 采样,供电阻屏分支使用
apps/main.c 演示程序:画 UI、触摸中断、屏上画点、串口打印坐标

另有两处对既有文件的改动:

文件 改动
apps/led.c GPIO1->GDIR = 0x8; → `GPIO1->GDIR
apps/main.c 改写为触摸演示程序

Makefile 不用改 :SRCDIRS 是通配的,board/imx6u 下新增的 .c 会自动进编译。

2. 调用链

#mermaid-svg-Ai5lwLUP9nprqrGy{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Ai5lwLUP9nprqrGy .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Ai5lwLUP9nprqrGy .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Ai5lwLUP9nprqrGy .error-icon{fill:#552222;}#mermaid-svg-Ai5lwLUP9nprqrGy .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Ai5lwLUP9nprqrGy .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Ai5lwLUP9nprqrGy .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Ai5lwLUP9nprqrGy .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Ai5lwLUP9nprqrGy .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Ai5lwLUP9nprqrGy .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Ai5lwLUP9nprqrGy .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Ai5lwLUP9nprqrGy .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Ai5lwLUP9nprqrGy .marker.cross{stroke:#333333;}#mermaid-svg-Ai5lwLUP9nprqrGy svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Ai5lwLUP9nprqrGy p{margin:0;}#mermaid-svg-Ai5lwLUP9nprqrGy .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Ai5lwLUP9nprqrGy .cluster-label text{fill:#333;}#mermaid-svg-Ai5lwLUP9nprqrGy .cluster-label span{color:#333;}#mermaid-svg-Ai5lwLUP9nprqrGy .cluster-label span p{background-color:transparent;}#mermaid-svg-Ai5lwLUP9nprqrGy .label text,#mermaid-svg-Ai5lwLUP9nprqrGy span{fill:#333;color:#333;}#mermaid-svg-Ai5lwLUP9nprqrGy .node rect,#mermaid-svg-Ai5lwLUP9nprqrGy .node circle,#mermaid-svg-Ai5lwLUP9nprqrGy .node ellipse,#mermaid-svg-Ai5lwLUP9nprqrGy .node polygon,#mermaid-svg-Ai5lwLUP9nprqrGy .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Ai5lwLUP9nprqrGy .rough-node .label text,#mermaid-svg-Ai5lwLUP9nprqrGy .node .label text,#mermaid-svg-Ai5lwLUP9nprqrGy .image-shape .label,#mermaid-svg-Ai5lwLUP9nprqrGy .icon-shape .label{text-anchor:middle;}#mermaid-svg-Ai5lwLUP9nprqrGy .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Ai5lwLUP9nprqrGy .rough-node .label,#mermaid-svg-Ai5lwLUP9nprqrGy .node .label,#mermaid-svg-Ai5lwLUP9nprqrGy .image-shape .label,#mermaid-svg-Ai5lwLUP9nprqrGy .icon-shape .label{text-align:center;}#mermaid-svg-Ai5lwLUP9nprqrGy .node.clickable{cursor:pointer;}#mermaid-svg-Ai5lwLUP9nprqrGy .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Ai5lwLUP9nprqrGy .arrowheadPath{fill:#333333;}#mermaid-svg-Ai5lwLUP9nprqrGy .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Ai5lwLUP9nprqrGy .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Ai5lwLUP9nprqrGy .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Ai5lwLUP9nprqrGy .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Ai5lwLUP9nprqrGy .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Ai5lwLUP9nprqrGy .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Ai5lwLUP9nprqrGy .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Ai5lwLUP9nprqrGy .cluster text{fill:#333;}#mermaid-svg-Ai5lwLUP9nprqrGy .cluster span{color:#333;}#mermaid-svg-Ai5lwLUP9nprqrGy div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Ai5lwLUP9nprqrGy .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Ai5lwLUP9nprqrGy rect.text{fill:none;stroke-width:0;}#mermaid-svg-Ai5lwLUP9nprqrGy .icon-shape,#mermaid-svg-Ai5lwLUP9nprqrGy .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Ai5lwLUP9nprqrGy .icon-shape p,#mermaid-svg-Ai5lwLUP9nprqrGy .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Ai5lwLUP9nprqrGy .icon-shape .label rect,#mermaid-svg-Ai5lwLUP9nprqrGy .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Ai5lwLUP9nprqrGy .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Ai5lwLUP9nprqrGy .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Ai5lwLUP9nprqrGy :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 总线层
芯片驱动
触控统一层 bsp_touch
应用层
intflag
main.c
touch_init
touch_read
touch_map_coord
gt9147_read / gt9147_irqhandler
ft5426_read
rtouch_read_xy
bsp_i2c.c
bsp_adc.c

3. 中断服务程序(只做最少的事)

c 复制代码
static void gt9147_irqhandler(unsigned int gicc_iar, void *param)
{
    (void)gicc_iar;
    (void)param;
    if (gt_init_done != 0U) {
        s_irq_cnt++;
        s_intflag = 1U;
        gpio_disableint(GPIO1, 9);   /* 电平触发时防止反复进入 */
    }
    gpio_clearintflags(GPIO1, 9);
}

主循环侧读完点之后再开回去:

c 复制代码
gpio_clearintflags(GPIO1, 9);
gpio_enableint(GPIO1, 9);

「关 → 读 → 开」这个握手保证单次中断只处理一帧,不会被同一事件反复触发。

4. led.c 的隐蔽陷阱

本次调试里最不起眼、却足以让整块触摸完全失效的一行:

c 复制代码
/* 原来的写法 */
GPIO1->GDIR = 0x8;            /* 把整个 GPIO1 方向寄存器覆盖成"只有 IO03 输出" */

/* 修好的写法 */
GPIO1->GDIR |= (1U << 3);     /* 只改 LED 这一个脚 */

GDIR 是整个 GPIO1 bank 的方向寄存器 ,用 = 赋值会把 IO09(CT_INT)在内的其它脚一并改掉,导致触摸中断永远进不来。

凡是操作共享寄存器,一律用 |= / &=~,不要用 =。

5. 坐标方向校正

不同批次 LCD 的贴合方向可能不同,改这三个宏即可,不需要动驱动:

c 复制代码
#define TOUCH_SWAP_XY   (0U)   /* 1:X/Y 互换(点行列整体反了) */
#define TOUCH_FLIP_X    (0U)   /* 1:X 镜像(左右反)          */
#define TOUCH_FLIP_Y    (0U)   /* 1:Y 镜像(上下反)          */

touch_map_coord() 最后还会按屏幕宽高做限幅,防止异常坐标把点画到显存外面。


六、编译与运行

1. 编译(WSL)

bash 复制代码
cd /mnt/d/documents/learn_i.mx6u/24_touch
make clean && make && make imx

make imx 生成 USB 下载用的 load.imx(内含 IVT + DCD)。

2. USB-OTG 下载(Windows PowerShell)

powershell 复制代码
# 1) 拨码拨到 0100(USB 下载),OTG Type-C 与串口 Type-C 都接电脑
# 2) 按一下 RESET,设备管理器出现 SE Blank 6ULL(VID_15A2 / PID_0080)
# 3) 下载:
python D:\documents\learn_i.mx6u\tools\dl.py D:\documents\learn_i.mx6u\24_touch\load.imx --com COM16

# 串口已被串口助手占用时,只下载不读串口:
python D:\documents\learn_i.mx6u\tools\dl.py D:\documents\learn_i.mx6u\24_touch\load.imx --no-serial

dl.py 的默认镜像是 22_lcdid\load.imx,本课必须显式传路径 。

若在 [1] DCD_WRITE 阶段 write timeout,多半是板子已经跳出 SDP 状态,按一次 RESET 再跑即可。

3. 实测串口输出

初始化阶段(lcd_init → touch_init)------以下为真机抓取原文:

text 复制代码
LCD ID = 0x4384
panel...
panel ok
lcdgpio...
gpio ok
clk...
clk ok
lcd reset...
reset ok
elcdif...
enable+clear...
LCD 800x480 fb=0x89000000 ready
LCD 800x480, touch ui ready.
CTP ID:1158 00 00                    ← 产物 ID,据此确认是 GT1158
GT irq_cfg@0x8056 = 1                ← 地址正确读出,值 1
InterruptMode: FallingEdge           ← 本板真实中断极性
Touch IC = GT9xx/GT115x on I2C2
GT addr=0x14
first read: no point yet, touch the panel
Touch the screen, coords will print.
日志行 含义
LCD ID = 0x4384 屏型号 ID 读取成功
LCD 800x480 fb=0x89000000 ready eLCDIF + framebuffer 就绪
CTP ID:1158 00 00 产物 ID = 1158 → GT1158,后两字节为固件区内容
GT irq_cfg@0x8056 = 1 中断极性:地址正常读出,值 1 = FallingEdge
InterruptMode: FallingEdge 实际配置的 GPIO 触发边沿(由 IC 决定,不是写死的)
GT addr=0x14 7 位 I2C 地址锁定成功

运行阶段(实际抓取,手指在屏上滑动):

text 复制代码
P0(738,158)
alive st=0x81 sret=0 irq=1659
P0(729,192)
P0(721,224)
P0(714,251)
P0(709,274)
alive st=0x81 sret=0 irq=1672
P0(708,293)
alive st=0x80 sret=0 irq=1680        ← 手指离开,就绪位保留、点数归 0

字段含义:

字段 含义
P0(x,y) 第 0 号触点的屏幕像素坐标
st=0x8N 最近一次读到的 0x814E:bit7 就绪 + N 个有效点
sret=0 最近一次 I2C 返回码,0 = 成功(非 0 见第八节)
irq=NNNN 累计 GPIO 中断次数,持续增长说明中断链路正常

4. 屏上现象

  • 白底 + 黑色双层外框(标出触摸有效区)
  • 中心十字准星 + 灰色圆,用于验证逻辑坐标对不对得上
  • 四角各有 20×20 色块:左上红 / 右上绿 / 左下蓝 / 右下黄,一眼看出方向有没有镜像
  • 每按一下,触点处出现 7×7 色块;多点时依次取 5 种颜色
  • LED0 每约 2s 翻转一次(心跳)
  • 触摸时串口坐标最多 2Hz 打印一次,避免 printf 抢占总线;屏幕上画点是跟手的,不受此限制

5. 调试顺序建议

#mermaid-svg-M4FCbpBoHoLvD0O9{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-M4FCbpBoHoLvD0O9 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-M4FCbpBoHoLvD0O9 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-M4FCbpBoHoLvD0O9 .error-icon{fill:#552222;}#mermaid-svg-M4FCbpBoHoLvD0O9 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-M4FCbpBoHoLvD0O9 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-M4FCbpBoHoLvD0O9 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-M4FCbpBoHoLvD0O9 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-M4FCbpBoHoLvD0O9 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-M4FCbpBoHoLvD0O9 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-M4FCbpBoHoLvD0O9 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-M4FCbpBoHoLvD0O9 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-M4FCbpBoHoLvD0O9 .marker.cross{stroke:#333333;}#mermaid-svg-M4FCbpBoHoLvD0O9 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-M4FCbpBoHoLvD0O9 p{margin:0;}#mermaid-svg-M4FCbpBoHoLvD0O9 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-M4FCbpBoHoLvD0O9 .cluster-label text{fill:#333;}#mermaid-svg-M4FCbpBoHoLvD0O9 .cluster-label span{color:#333;}#mermaid-svg-M4FCbpBoHoLvD0O9 .cluster-label span p{background-color:transparent;}#mermaid-svg-M4FCbpBoHoLvD0O9 .label text,#mermaid-svg-M4FCbpBoHoLvD0O9 span{fill:#333;color:#333;}#mermaid-svg-M4FCbpBoHoLvD0O9 .node rect,#mermaid-svg-M4FCbpBoHoLvD0O9 .node circle,#mermaid-svg-M4FCbpBoHoLvD0O9 .node ellipse,#mermaid-svg-M4FCbpBoHoLvD0O9 .node polygon,#mermaid-svg-M4FCbpBoHoLvD0O9 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-M4FCbpBoHoLvD0O9 .rough-node .label text,#mermaid-svg-M4FCbpBoHoLvD0O9 .node .label text,#mermaid-svg-M4FCbpBoHoLvD0O9 .image-shape .label,#mermaid-svg-M4FCbpBoHoLvD0O9 .icon-shape .label{text-anchor:middle;}#mermaid-svg-M4FCbpBoHoLvD0O9 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-M4FCbpBoHoLvD0O9 .rough-node .label,#mermaid-svg-M4FCbpBoHoLvD0O9 .node .label,#mermaid-svg-M4FCbpBoHoLvD0O9 .image-shape .label,#mermaid-svg-M4FCbpBoHoLvD0O9 .icon-shape .label{text-align:center;}#mermaid-svg-M4FCbpBoHoLvD0O9 .node.clickable{cursor:pointer;}#mermaid-svg-M4FCbpBoHoLvD0O9 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-M4FCbpBoHoLvD0O9 .arrowheadPath{fill:#333333;}#mermaid-svg-M4FCbpBoHoLvD0O9 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-M4FCbpBoHoLvD0O9 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-M4FCbpBoHoLvD0O9 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-M4FCbpBoHoLvD0O9 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-M4FCbpBoHoLvD0O9 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-M4FCbpBoHoLvD0O9 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-M4FCbpBoHoLvD0O9 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-M4FCbpBoHoLvD0O9 .cluster text{fill:#333;}#mermaid-svg-M4FCbpBoHoLvD0O9 .cluster span{color:#333;}#mermaid-svg-M4FCbpBoHoLvD0O9 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-M4FCbpBoHoLvD0O9 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-M4FCbpBoHoLvD0O9 rect.text{fill:none;stroke-width:0;}#mermaid-svg-M4FCbpBoHoLvD0O9 .icon-shape,#mermaid-svg-M4FCbpBoHoLvD0O9 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-M4FCbpBoHoLvD0O9 .icon-shape p,#mermaid-svg-M4FCbpBoHoLvD0O9 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-M4FCbpBoHoLvD0O9 .icon-shape .label rect,#mermaid-svg-M4FCbpBoHoLvD0O9 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-M4FCbpBoHoLvD0O9 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-M4FCbpBoHoLvD0O9 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-M4FCbpBoHoLvD0O9 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 否
是
否
是
否
是
否
是
反了
正常
1 LCD 白底+框+十字出来了吗?
查 LCD 排线 / lcd_init
2 串口有 CTP ID 吗?
查 I2C 引脚组: I2C1@UART4 还是 I2C2@UART5
3 InterruptMode 打印了什么?
4 触碰时 irq 计数涨不涨?
查 INT 复用 / GDIR 被覆盖 / GIC 中断使能
5 P0 坐标出来了吗?
看 sret: 非 0 表示 I2C 报错, 转第八节
6 坐标方向对不对?
改 TOUCH_SWAP_XY / FLIP_X / FLIP_Y
完成


七、本次遇到问题汇总

# 现象 根因 修法
1 整条 I2C 总线一个设备都扫不到 引脚组用错,实际在 I2C2@UART5 改用 UART5_TX/RX_DATA + SION=1
2 撸一遍总线全是 NAK START 后等了 IBB 才写地址 置 `MSTA
3 坐标时准时乱,最后总线死 在 ISR 内跑 I2C,与轮询/printf 抢总线 → ARBLOST ISR 只置标志,主循环读;出错自动 i2c_init 自愈
4 触摸中断完全不进来 led.c 里 GPIO1->GDIR = 0x8 覆盖了整组方向 改成 `GPIO1->GDIR
5 只有第一个点有反应 0x814E 读完没写 0 读点后一定 i2c_write_byte(..., 0x814E, 2, 0x00)
6 给 GT1158 发 0x8040 软复位后 IC 不响应 GT1158 不支持该软复位 注释掉,只做硬件复位
7 未触摸时坐标跳到 2000+ 的脏值 IC 在没有贴合时会上报无效值 `if (x > 2000
8 探测前从未释放 RST,芯片一直在复位 初始化顺序问题 先 RST 拉高释放 + 延时,再 probe
9 日志里 GT irq_cfg@0x0056 而不是 0x8056,且极性莫名是 RisingEdge irq_reg 声明为 unsigned char,16 位地址被截断 → 读失败 → 兜底成 RisingEdge 改成 unsigned int,真实极性 FallingEdge 才浮出水面(详见 4.1)

八、常见问题排查

现象 可能原因 处理
Touch IC not found! 自动跑诊断 引脚组不对 / 排线松 / 芯片在复位 看 i2c_bus_diag 输出:哪组扫到地址就用哪组;SCL/SDA 都应为 1
两组总线都 (none),且 SCL=1 SDA=1 上拉正常但没有器件应答 检查 LCD 触摸排线是否插紧、模组是否带电
SDA=0 或 IBB=1 持续 总线被拉死 查 RST 是否一直为低;先 i2c_hw_reset() 再试
INT=0 且 RST=0 RST 被拉住 查 GPIO5_IO09 复用与输出电平
sret != 0 1=参数错 / 2=忙 / 3=超时 / 4=NAK / 5=ARBLOST 5 最常见,驱动已自动软复位恢复;若频繁就把 ISR 里的活再往主循环挪
irq 计数一直为 0 中断没进来 确认 GIC_EnableIRQ(GPIO1_Combined_0_15_IRQn)、gpio_enableint,以及 INT pad 方向没被别的代码覆盖
irq 涨但没有 P0 0x814E bit3:0 为 0 确认读完状态已写 0 清除,且确实按住了屏
点画得出但方向反 屏幕贴合方向差异 改 bsp_touch.h 的 TOUCH_SWAP_XY / FLIP_X / FLIP_Y
下载报 发送命令 0x0A0A 失败 / write timeout 板子已跳出 SDP 状态 按一次 RESET 再执行 dl.py

九、参考资料

  • 《I.MX6ULL 参考手册》Chapter 31:I2C(IFDR 分频表、I2CR / I2SR 位定义)
  • 《I.MX6ULL 参考手册》Chapter 28:GPIO(重点看 GDIR / ISR / ICR 这类共享寄存器)
  • 《I.MX6ULL 参考手册》Chapter 5:IOMUX Controller(SION 位的作用)
  • GT9147 / GT1158 Programming Guide
  • FT5x06 Datasheet
  • 正点原子《I.MX6U 嵌入式 Linux 驱动开发指南》触摸屏实验章节
相关推荐
Zwawa1 小时前
传感器模块都在交出什么:从模拟电压到数字协议
c语言·单片机·嵌入式
FakeOccupational2 小时前
【电路笔记 STM32】Cortex-M7 UART 中断模式(串口接收中断+串口空闲中断)
笔记·stm32·单片机
小李不想当小白3 小时前
USART串口协议(STM32标准库学习笔记)
经验分享·笔记·stm32·单片机·嵌入式硬件·学习
无垠的广袤3 小时前
拆解电子价签
单片机·蓝牙·diy·调试器·jlink·墨水屏
小何好运暴富开心幸福4 小时前
嵌入式历史与生态
stm32·单片机·mcu
甜甜的大香瓜4 小时前
【MT32F006】MT32F006之Stimer延时(轮询)
单片机
辰哥单片机设计5 小时前
MP3播放器模块(STM32)
stm32·单片机·嵌入式硬件
嵌入式-老费5 小时前
esp32开发与应用(题外话之SoC核心板Layout外包)
嵌入式硬件
神一样的老师5 小时前
Windows 中文用户名导致 ccache 编译失败
嵌入式硬件