4. 触摸驱动移植:I2C/SPI触摸芯片适配、触摸坐标校准、多点触控支持、中断与轮询模式选择
触摸驱动移植,说白了你得让TouchGFX知道「手指点在了哪儿」。这事儿看着简单,但坑是真不少。我最早做第一个项目时,触摸屏死活没反应,查了两天才发现是I2C地址写错了——嗯,低级错误,但特别容易犯。
这一章咱们就把触摸移植的四个核心问题拆开讲:通信协议适配、坐标校准、多点触控、以及中断和轮询怎么选。
核心要点:触摸驱动移植的成功率,90%取决于底层通信的稳定性和坐标映射的准确性。剩下10%是中断和轮询的取舍。
4.1 I2C/SPI触摸芯片适配:先让数据能读出来
不管你是用FT5x06、GT911还是CST816,第一步都是把原始触摸数据读出来。我习惯先把裸机驱动调通,再往TouchGFX里集成。
这里有个通用流程:
- 确认硬件连接:I2C地址对不对?SPI的CS引脚有没有拉高?
- 读芯片ID:几乎所有触摸芯片都有ID寄存器,读出来能确认通信正常。
- 读触摸点数:一般第一个寄存器就是触摸状态和点数。
- 读坐标数据:根据芯片手册解析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里启用多点触控,需要做两件事:
- 在
TouchController中实现sampleMultiTouch()函数。 - 在
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 知识体系总览
下面这张图把触摸驱动移植的完整流程串起来了,你可以对照着检查自己的项目:
4.6 避坑指南:我踩过的那些雷
最后分享几个我亲身经历过的坑,希望能帮你少走弯路:
- I2C地址搞错:有些芯片的I2C地址是7位,有些是8位。HAL库用的是7位地址左移一位后的值,别搞混了。
- 触摸中断引脚没配置:中断模式下,INT引脚要配置为外部中断输入,且要加上拉电阻。
- 坐标校准参数不对:我有一块屏,触摸和LCD的分辨率不一样,忘了做缩放,结果手指点左上角,光标跑到右下角去了。
- 多点触控没使能:TouchGFX默认只支持单点触控,要在
touchgfx_init()里显式设置MultiTouch为true。 - 轮询周期太慢:轮询模式下,如果主循环跑得太慢,触摸响应会有明显延迟。建议把触摸读取放在高优先级任务里。
我的建议:不管用哪种模式,先在串口打印原始坐标和校准后的坐标,对比一下。数据对了,触摸就成功了一半。