03_瑞萨GUI(LVGL)移植实战教程之触摸屏驱动(I2C)与多点触控数据解析
1. 瑞萨RA6M5平台与GT911触摸屏基础认知第一次接触瑞萨RA6M5开发板和GT911触摸屏时我对着原理图发呆了半小时。这块集成电容触摸控制器的芯片看起来简单但实际调试时发现水很深。GT911作为工业级触控IC支持最高5点触控和800Hz报告率通过I2C接口与主控通信。在RA6M5的生态中我们需要重点关注三个核心要素硬件连接上开发板的P409(SDA2)和P410(SCL2)引脚对应GT911的I2C接口P403用作复位引脚P408则是中断引脚。实际布线时要注意线缆长度最好不超过30cm我在项目初期因为排线过长导致信号衰减出现了坐标漂移现象。建议使用双绞线并做好屏蔽这个经验是用两天调试时间换来的。开发环境配置有个坑要注意e² studio默认的I2C时钟配置是100kHz但GT911在快速模式下支持400kHz。刚开始我没注意这个参数导致读取坐标时经常超时。后来在FSP配置里把时钟频率调到380kHz留点余量稳定性立刻提升。具体操作是在FSP Configuration的r_iic_master模块属性中将Clock Frequency参数设为380000。触摸屏的坐标系统需要特别注意原点位置。GT911的原始坐标原点在液晶屏的右下角与常规认知相反。第一次测试时我手指明明点在左上角串口却输出(320,480)的坐标值差点以为硬件接反了。后来在驱动层做了坐标转换g_tp_drv.points_info[i].x g_tp_drv.width - g_tp_drv.points_info[i].x; g_tp_drv.points_info[i].y g_tp_drv.height - g_tp_drv.points_info[i].y;2. I2C通信协议深度解析GT911的I2C协议有几点特殊之处容易踩坑。首先是地址选择芯片支持0x14和0x5D两个7位地址通过复位时序中的INT引脚电平来设定。我推荐使用0x14地址因为多数示例代码都基于此地址开发。实测中发现如果INT引脚上拉电阻过大10kΩ可能导致地址识别失败这个细节手册里可没明确说明。寄存器访问需要特别注意字节序。GT911采用大端模式读取XY坐标时要先组合高字节和低字节uint16_t x (uint16_t)(buf[1] 8) | buf[0];有次调试时我把字节序弄反了结果触摸点在屏幕上跳起了机械舞坐标忽大忽小地乱跳。通信稳定性是另一个重灾区。建议在每次读写前后加入延时特别是复位后的初始化阶段g_ioport.p_api-pinWrite(g_ioport.p_ctrl, GT911_RESET_PIN, BSP_IO_LEVEL_LOW); R_BSP_SoftwareDelay(10, BSP_DELAY_UNITS_MILLISECONDS); // 保持10ms低电平 g_ioport.p_api-pinWrite(g_ioport.p_ctrl, GT911_RESET_PIN, BSP_IO_LEVEL_HIGH); R_BSP_SoftwareDelay(100, BSP_DELAY_UNITS_MILLISECONDS); // 等待100ms稳定中断处理机制很关键。GT911通过INT引脚通知主机有触摸事件发生但实际项目中我发现单纯依赖中断可能丢失快速触摸。我的解决方案是中断轮询双保险中断触发后启动一个50ms的定时轮询确保不会漏检连续触摸动作。3. 多点触控数据解析实战解析多点触控数据就像拆解俄罗斯套娃。GT911的状态寄存器(0x814E)低4位表示当前触摸点数量最高位表示缓存状态。当检测到bit71时表示有新数据到来此时需要读取五个触控点的寄存器块uint16_t pointers_regs[5] { GT_TP1_REG, // 0x814F GT_TP2_REG, // 0x8157 GT_TP3_REG, // 0x815F GT_TP4_REG, // 0x8167 GT_TP5_REG // 0x816F };每个触控点数据包包含7个字节Byte0: 触点ID0~4Byte1-2: X坐标低字节在前Byte3-4: Y坐标低字节在前Byte5-6: 触点尺寸压力值实际处理时要特别注意ID的跟踪。GT911会为每个物理触点分配唯一ID从按下到释放期间保持不变。这对手势识别很重要我曾在实现双指缩放时因为没有跟踪ID导致手势错乱。后来增加了触点轨迹预测算法才解决。坐标滤波是提升体验的关键。原始数据难免有噪声我采用加权滑动平均滤波#define FILTER_DEPTH 3 static uint16_t x_history[FILTER_DEPTH], y_history[FILTER_DEPTH]; void filter_touch_point(uint16_t *x, uint16_t *y) { // 移出最旧数据 for(int i0; iFILTER_DEPTH-1; i) { x_history[i] x_history[i1]; y_history[i] y_history[i1]; } // 存入新数据 x_history[FILTER_DEPTH-1] *x; y_history[FILTER_DEPTH-1] *y; // 计算加权平均值最新数据权重最大 *x (x_history[0] x_history[1]*2 x_history[2]*3)/6; *y (y_history[0] y_history[1]*2 y_history[2]*3)/6; }4. LVGL输入设备集成技巧将触摸驱动集成到LVGL需要实现input_device_read回调函数。这里有个性能优化点不要每次调用都读取I2C数据而是通过全局变量缓存触摸状态static lv_indev_state_t last_state LV_INDEV_STATE_REL; static lv_point_t last_point {0}; bool touchpad_read(lv_indev_drv_t *drv, lv_indev_data_t *data) { if(touchpad_is_touched() FSP_SUCCESS) { last_state LV_INDEV_STATE_PR; touchpad_get_pos(last_point.x, last_point.y, 0); } else { last_state LV_INDEV_STATE_REL; } >void touchpad_set_rotation(tp_rotation_t rotation) { g_tp_drv.rotation rotation; } // 在坐标读取处处理旋转 switch(g_tp_drv.rotation) { case TP_ROT_90: { uint16_t tmp x; x y; y g_tp_drv.height - tmp; break; } // 其他旋转角度处理... }事件响应延迟是常见痛点。通过实测发现将LVGL的输入读取周期设置为20ms时既能保证流畅度又不会给I2C总线太大压力。在lv_conf.h中修改#define LV_INDEV_DEF_READ_PERIOD 20最后提醒一个隐蔽的坑LVGL的坐标系原点在左上角而GT911原始输出是右下角。除了之前提到的坐标翻转还要注意在初始化时正确设置屏幕尺寸lv_disp_drv_t disp_drv; lv_disp_drv_init(disp_drv); disp_drv.hor_res 320; // 横向分辨率 disp_drv.ver_res 480; // 纵向分辨率