4. 触摸驱动移植:I2C/SPI触摸芯片适配、触摸坐标校准、多点触控支持、中断与轮询模式选择

触摸驱动移植,说白了你得让TouchGFX知道「手指点在了哪儿」。这事儿看着简单,但坑是真不少。我最早做第一个项目时,触摸屏死活没反应,查了两天才发现是I2C地址写错了——嗯,低级错误,但特别容易犯。

这一章咱们就把触摸移植的四个核心问题拆开讲:通信协议适配、坐标校准、多点触控、以及中断和轮询怎么选。

核心要点:触摸驱动移植的成功率,90%取决于底层通信的稳定性和坐标映射的准确性。剩下10%是中断和轮询的取舍。

4.1 I2C/SPI触摸芯片适配:先让数据能读出来

不管你是用FT5x06、GT911还是CST816,第一步都是把原始触摸数据读出来。我习惯先把裸机驱动调通,再往TouchGFX里集成。

这里有个通用流程:

  1. 确认硬件连接:I2C地址对不对?SPI的CS引脚有没有拉高?
  2. 读芯片ID:几乎所有触摸芯片都有ID寄存器,读出来能确认通信正常。
  3. 读触摸点数:一般第一个寄存器就是触摸状态和点数。
  4. 读坐标数据:根据芯片手册解析X/Y坐标。

举个例子,FT5x06的I2C读取代码大概长这样:

// FT5x06 读取触摸坐标
bool BSP_TS_GetPoint(TS_PointTypeDef *point) {
    uint8_t buf[8];
    uint8_t addr = 0x02;  // 触摸数据起始寄存器
    
    // 读取8字节数据
    HAL_I2C_Mem_Read(&hi2c1, FT5x06_ADDR, addr, I2C_MEMADD_SIZE_8BIT, buf, 8, 100);
    
    // 解析触摸点数
    uint8_t touch_cnt = buf[0] & 0x0F;
    if (touch_cnt == 0) return false;
    
    // 解析第一个触摸点的坐标
    point->x = ((buf[1] & 0x0F) << 8) | buf[2];
    point->y = ((buf[3] & 0x0F) << 8) | buf[4];
    point->id = buf[5] >> 4;
    
    return true;
}

我的习惯:在TouchGFX的touchgfx_init()之前,先单独跑一个裸机测试函数,把原始坐标打印到串口。确认数据对了,再往下走。

4.2 触摸坐标校准:别让手指点歪了

你想想看,触摸屏的ADC坐标和LCD的像素坐标,大概率不是一一对应的。方向可能反了,比例可能不对,甚至还有偏移。

校准的核心公式就一个:

// 线性校准公式
lcd_x = (touch_x - x_offset) * x_scale;
lcd_y = (touch_y - y_offset) * y_scale;

但实际项目中,我遇到过更头疼的情况——触摸屏和LCD的安装角度差了90度。那时候你光调偏移没用,得交换X/Y轴:

// 旋转90度后的校准
lcd_x = (touch_y - y_offset) * x_scale;
lcd_y = (LCD_HEIGHT - 1) - ((touch_x - x_offset) * y_scale);

我曾经踩过的坑:有一块屏,触摸和LCD的X轴方向正好相反。我调了一下午偏移量,怎么点都不对。后来用示波器量了一下,发现触摸芯片的X输出是反的。直接加个lcd_x = LCD_WIDTH - 1 - touch_x就解决了。

TouchGFX里,校准通常在TouchController的sampleTouch()函数中完成:

bool TouchController::sampleTouch(int32_t& x, int32_t& y) {
    TS_PointTypeDef point;
    if (!BSP_TS_GetPoint(&point)) {
        return false;
    }
    
    // 校准映射
    x = (point.x - X_OFFSET) * X_SCALE;
    y = (point.y - Y_OFFSET) * Y_SCALE;
    
    // 边界裁剪
    if (x < 0) x = 0;
    if (x >= LCD_WIDTH) x = LCD_WIDTH - 1;
    if (y < 0) y = 0;
    if (y >= LCD_HEIGHT) y = LCD_HEIGHT - 1;
    
    return true;
}

4.3 多点触控支持:不只是读两个坐标那么简单

TouchGFX原生支持多点触控,但前提是你的触摸芯片能报告多个触摸点,而且你得正确解析。

多点触控的数据结构一般是这样的:

寄存器偏移 内容 说明
0x00 触摸状态 高4位保留,低4位为触摸点数
0x01-0x04 触摸点1坐标 X高字节、X低字节、Y高字节、Y低字节
0x05-0x08 触摸点2坐标 同上
0x09-0x0C 触摸点3坐标 同上

在TouchGFX里启用多点触控,需要做两件事:

  1. 在TouchController中实现sampleMultiTouch()函数。
  2. 在touchgfx_init()中设置MultiTouch为true。
// 多点触控示例
uint8_t TouchController::sampleMultiTouch(MultiTouchEvent& event) {
    uint8_t touch_cnt = 0;
    TS_PointTypeDef points[MAX_TOUCH];
    
    // 读取所有触摸点
    touch_cnt = BSP_TS_GetMultiPoint(points, MAX_TOUCH);
    
    for (int i = 0; i < touch_cnt; i++) {
        int32_t x, y;
        // 校准每个点
        x = (points[i].x - X_OFFSET) * X_SCALE;
        y = (points[i].y - Y_OFFSET) * Y_SCALE;
        event.addPoint(x, y, points[i].id);
    }
    
    return touch_cnt;
}

注意:不是所有触摸芯片都支持多点触控。GT911支持5点,FT5x06支持2点,CST816只支持单点。选型时一定要确认。

4.4 中断与轮询模式选择:性能与功耗的博弈

这是个老生常谈的问题,但每次项目都得重新权衡。我个人的经验是:

  • 中断模式:适合低功耗场景,手指没触摸时CPU可以睡觉。
  • 轮询模式:适合实时性要求高的场景,但CPU占用率高。

TouchGFX默认是轮询模式,在touchgfx_main()的循环里不断调用sampleTouch()。如果你要用中断,得自己改。

中断模式的典型实现:

// 中断服务函数
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {
    if (GPIO_Pin == TOUCH_INT_PIN) {
        touch_interrupt_flag = 1;  // 设置标志位
    }
}

// TouchGFX的sampleTouch中检查标志位
bool TouchController::sampleTouch(int32_t& x, int32_t& y) {
    if (!touch_interrupt_flag) {
        return false;  // 没有触摸事件
    }
    
    // 读取坐标
    TS_PointTypeDef point;
    BSP_TS_GetPoint(&point);
    
    // 校准
    x = (point.x - X_OFFSET) * X_SCALE;
    y = (point.y - Y_OFFSET) * Y_SCALE;
    
    touch_interrupt_flag = 0;  // 清除标志位
    return true;
}

我曾经踩过的坑:中断模式下,触摸芯片的INT引脚是开漏输出,需要外部上拉。我有一块板子忘了加上拉电阻,中断信号一直拉低,CPU一直在处理触摸中断,功耗反而比轮询还高。

两种模式的对比:

对比项 中断模式 轮询模式
CPU占用率 低(仅触摸时占用) 高(持续占用)
响应延迟 快(微秒级响应) 取决于轮询周期
功耗 低(可进入休眠) 高(CPU持续运行)
实现复杂度 较高(需处理中断嵌套) 简单(直接循环读取)
适用场景 电池供电设备 USB供电、实时性要求高

4.5 知识体系总览

下面这张图把触摸驱动移植的完整流程串起来了,你可以对照着检查自己的项目:

触摸驱动移植知识体系 触摸芯片 (FT5x06/GT911) 通信协议适配 (I2C / SPI) 坐标校准 多点触控支持 中断/轮询模式 TouchGFX TouchController 集成 验证:串口打印 / 触摸测试Demo 关键:先裸机调通通信 → 再校准坐标 → 最后集成到TouchGFX 中断模式适合低功耗,轮询模式适合高实时性,根据项目需求选择

4.6 避坑指南:我踩过的那些雷

最后分享几个我亲身经历过的坑,希望能帮你少走弯路:

  • I2C地址搞错:有些芯片的I2C地址是7位,有些是8位。HAL库用的是7位地址左移一位后的值,别搞混了。
  • 触摸中断引脚没配置:中断模式下,INT引脚要配置为外部中断输入,且要加上拉电阻。
  • 坐标校准参数不对:我有一块屏,触摸和LCD的分辨率不一样,忘了做缩放,结果手指点左上角,光标跑到右下角去了。
  • 多点触控没使能:TouchGFX默认只支持单点触控,要在touchgfx_init()里显式设置MultiTouch为true。
  • 轮询周期太慢:轮询模式下,如果主循环跑得太慢,触摸响应会有明显延迟。建议把触摸读取放在高优先级任务里。

我的建议:不管用哪种模式,先在串口打印原始坐标和校准后的坐标,对比一下。数据对了,触摸就成功了一半。