背景
在一个较为成熟的支持类项目中,客户更换了USB触摸屏。由于设备已经组装为整机,拆卸变得不太方便,因此我选择通过软件途径来进行问题的排查和调试。在这种情境下,触摸事件的防抖动问题成为了一个问题,这往往是由于USB TP硬件因素导致的冗余触摸事件。
问题描述
客户反映,在操作触摸屏时,偶尔会出现多次触摸事件的触发,而不是预期中的单一事件。这种情况可能导致用户界面的不稳定行为,如误触,视觉看起来的卡顿。具体表现为,当点击USB触摸屏时,有时会出现没有“抬起”事件或者多次连续点击的现象,这使得视觉误以为系统出现了卡顿。在本文中,我将探讨如何在Android 内核驱动中优化USB TP并解决这一触摸事件的防抖动问题。
初步分析
在使用getevent
命令进行调试并仔细检查驱动代码后,观察到驱动在处理触摸事件时为每个触摸点报告了一个状态。然而当触摸点离开屏幕时,驱动似乎没有立即报告其离开状态。
以下是两段通过getevent -lv
抓取的event日志,用于对比相同操作:
正常日志:
/dev/input/event5: EV_ABS ABS_MT_TRACKING_ID 0000002f /dev/input/event5: EV_ABS ABS_MT_POSITION_X 0000477b /dev/input/event5: EV_ABS ABS_MT_POSITION_Y 00004155 /dev/input/event5: EV_ABS ABS_MT_POSITION_X 0000477c /dev/input/event5: EV_ABS ABS_MT_POSITION_Y 00004152 /dev/input/event5: EV_KEY BTN_TOUCH DOWN /dev/input/event5: EV_ABS ABS_X 0000477c /dev/input/event5: EV_ABS ABS_Y 00004152 /dev/input/event5: EV_SYN SYN_REPORT 00000000 /dev/input/event5: EV_ABS ABS_MT_POSITION_X 00004768 /dev/input/event5: EV_ABS ABS_MT_POSITION_Y 00004192 /dev/input/event5: EV_ABS ABS_MT_TRACKING_ID ffffffff /dev/input/event5: EV_KEY BTN_TOUCH UP /dev/input/event5: EV_SYN SYN_REPORT 00000000 //正常
异常日志:
/dev/input/event5: EV_ABS ABS_MT_TRACKING_ID 00000030 /dev/input/event5: EV_ABS ABS_MT_POSITION_X 00003b69 /dev/input/event5: EV_ABS ABS_MT_POSITION_Y 00004038 /dev/input/event5: EV_KEY BTN_TOUCH DOWN /dev/input/event5: EV_ABS ABS_X 00003b69 /dev/input/event5: EV_ABS ABS_Y 00004038 /dev/input/event5: EV_SYN SYN_REPORT 00000000 /dev/input/event5: EV_ABS ABS_MT_POSITION_X 00003b82 /dev/input/event5: EV_ABS ABS_MT_POSITION_Y 00004023 /dev/input/event5: EV_ABS ABS_MT_POSITION_X 00003b97 /dev/input/event5: EV_ABS ABS_MT_POSITION_Y 00004012 /dev/input/event5: EV_ABS ABS_X 00003b97 /dev/input/event5: EV_ABS ABS_Y 00004012 /dev/input/event5: EV_SYN SYN_REPORT 00000000 //不正常
从这两段日志中,可以提炼出以下关键差异:
- 跟踪ID的差异:在正常日志中,触摸屏只报告了一个触摸点的跟踪ID(0000002f)。而在异常日志中,触摸屏报告了两个触摸点的跟踪ID(00000030和ffffffff)。这可能暗示在异常日志中,触摸屏可能检测到了两个或更多的触摸点。
- 按键事件的响应:在正常日志中,触摸屏在报告触摸位置后,立即报告了按键事件(BTN_TOUCH DOWN),并在触摸结束后立即报告了按键事件(BTN_TOUCH UP)。而在异常日志中,触摸屏在报告触摸位置后,并没有立即报告按键事件,而是有一定的延迟。
简单的说 对于相同的操作,异常日志显示触摸屏在按下后并没有及时检测到松开动作。
此外,我还对比测试了XXX、XXX和XXX项目,发现它们的现象均相同。询问其他同行后得知,他们也遇到了类似的问题,是通过触摸屏厂家提供的固件更新来解决(然而我们这个TP已不支持了,所以只能通过系统软件解决)。
解决方案
- 立即报告触摸点的离开状态
我修改了驱动代码,使其在处理完一个触摸点后立即报告其离开状态。 - 添加防抖动逻辑
为了解决多次触摸事件的问题,我在驱动中添加了防抖动逻辑。使用了一个时间戳来记录上次触摸事件的时间,并与当前事件的时间进行比较。如果两个事件之间的时间差小于预定义的阈值(例如100毫秒),则会忽略当前事件。
#define DEBOUNCE_TIME_MS 100 #define MOVE_THRESHOLD 100 #define SLIDE_TIME_THRESHOLD_MS 200 static int last_x = -1, last_y = -1; static bool was_moved = false; static ktime_t last_report_time; static ktime_t touch_start_time; static void mt_complete_slot(struct mt_device *td, struct input_dev *input) { ktime_t now = ktime_get(); ktime_t diff = ktime_sub(now, last_report_time); if ((td->mtclass.quirks & MT_QUIRK_CONTACT_CNT_ACCURATE) && td->num_received >= td->num_expected) return; if (td->curvalid || (td->mtclass.quirks & MT_QUIRK_ALWAYS_VALID)) { int slotnum = mt_compute_slot(td, input); // 屏蔽多点触控 if (slotnum != 0) { return; } struct mt_slot *s = &td->curdata; if (ktime_to_ms(diff) < DEBOUNCE_TIME_MS) { return; } if (s->touch_state || s->inrange_state) { int wide = (s->w > s->h); int major = max(s->w, s->h) >> 1; int minor = min(s->w, s->h) >> 1; int x_diff = abs(last_x - s->x); int y_diff = abs(last_y - s->y); if (x_diff > MOVE_THRESHOLD || y_diff > MOVE_THRESHOLD) { ktime_t touch_duration = ktime_sub(now, touch_start_time); if (ktime_to_ms(touch_duration) > SLIDE_TIME_THRESHOLD_MS) { was_moved = true; } } last_x = s->x; last_y = s->y; input_event(input, EV_ABS, ABS_MT_POSITION_X, s->x); input_event(input, EV_ABS, ABS_MT_POSITION_Y, s->y); input_event(input, EV_ABS, ABS_MT_TOOL_X, s->cx); input_event(input, EV_ABS, ABS_MT_TOOL_Y, s->cy); input_event(input, EV_ABS, ABS_MT_DISTANCE, !s->touch_state); input_event(input, EV_ABS, ABS_MT_ORIENTATION, wide); input_event(input, EV_ABS, ABS_MT_PRESSURE, s->p); input_event(input, EV_ABS, ABS_MT_TOUCH_MAJOR, major); input_event(input, EV_ABS, ABS_MT_TOUCH_MINOR, minor); input_report_key(input, BTN_TOUCH, 1); input_sync(input); if (!was_moved) { // 如果没有持续滑动,立即发送UP事件 input_mt_report_slot_state(input, MT_TOOL_FINGER, false); input_report_key(input, BTN_TOUCH, 0); input_sync(input); } } else { if (was_moved) { // 如果之前有滑动,现在发送UP事件 input_mt_report_slot_state(input, MT_TOOL_FINGER, false); input_report_key(input, BTN_TOUCH, 0); input_sync(input); was_moved = false; } last_x = -1; last_y = -1; } } }
在文件的开头定义了一个常量DEBOUNCE_TIME_MS
,并设置其值为100毫秒。这是用来处理触摸事件防抖的时间阈值。
定义了一个新的变量last_report_time
,用于存储上次触摸事件报告的时间。
在mt_complete_slot
函数中,首先获取当前的时间,并计算出从上次报告到现在的时间差。当检查到一个有效的触摸点时,首先检查从上次报告到现在是否超过了定义的防抖时间。如果时间差小于DEBOUNCE_TIME_MS
就不处理这个触摸事件,直接返回。
如果触摸事件是有效的,会报告触摸的状态,并立即同步这个事件。这确保了触摸事件被及时地报告给上层应用。
在处理完所有的触摸点后,会立即报告触摸点的离开状态,并同步这个事件。这确保了触摸的结束状态被及时地报告给上层应用。
最后更新last_report_time
为当前时间,以便下次检查。
测试和验证
修改后的驱动在实际硬件上进行了测试。测试结果显示,现在触摸事件的行为更加稳定,不再出现误触,卡顿现象的情况 但是!牺牲了长按功能 但是客户不需要 只是需要解决‘卡顿’问题即可。
20231020最终解决方案
这个问题查了快一天了,就是为了让效果不受任何影响。
#define DEBOUNCE_TIME_MS 100 #define MOVE_THRESHOLD 100 #define SLIDE_TIME_THRESHOLD_MS 200 static int last_x = -1, last_y = -1; static bool was_moved = false; static ktime_t last_report_time; static ktime_t touch_start_time; static void mt_complete_slot(struct mt_device *td, struct input_dev *input) { ktime_t now = ktime_get(); ktime_t diff = ktime_sub(now, last_report_time); if ((td->mtclass.quirks & MT_QUIRK_CONTACT_CNT_ACCURATE) && td->num_received >= td->num_expected) return; if (td->curvalid || (td->mtclass.quirks & MT_QUIRK_ALWAYS_VALID)) { int slotnum = mt_compute_slot(td, input); // 屏蔽多点触控 if (slotnum != 0) { return; } struct mt_slot *s = &td->curdata; if (ktime_to_ms(diff) < DEBOUNCE_TIME_MS) { return; } if (s->touch_state || s->inrange_state) { int wide = (s->w > s->h); int major = max(s->w, s->h) >> 1; int minor = min(s->w, s->h) >> 1; int x_diff = abs(last_x - s->x); int y_diff = abs(last_y - s->y); if (x_diff > MOVE_THRESHOLD || y_diff > MOVE_THRESHOLD) { ktime_t touch_duration = ktime_sub(now, touch_start_time); if (ktime_to_ms(touch_duration) > SLIDE_TIME_THRESHOLD_MS) { was_moved = true; } } last_x = s->x; last_y = s->y; input_event(input, EV_ABS, ABS_MT_POSITION_X, s->x); input_event(input, EV_ABS, ABS_MT_POSITION_Y, s->y); input_event(input, EV_ABS, ABS_MT_TOOL_X, s->cx); input_event(input, EV_ABS, ABS_MT_TOOL_Y, s->cy); input_event(input, EV_ABS, ABS_MT_DISTANCE, !s->touch_state); input_event(input, EV_ABS, ABS_MT_ORIENTATION, wide); input_event(input, EV_ABS, ABS_MT_PRESSURE, s->p); input_event(input, EV_ABS, ABS_MT_TOUCH_MAJOR, major); input_event(input, EV_ABS, ABS_MT_TOUCH_MINOR, minor); input_report_key(input, BTN_TOUCH, 1); input_sync(input); if (!was_moved) { // 如果没有持续滑动,立即发送UP事件 input_mt_report_slot_state(input, MT_TOOL_FINGER, false); input_report_key(input, BTN_TOUCH, 0); input_sync(input); } } else { if (was_moved) { // 如果之前有滑动,现在发送UP事件 input_mt_report_slot_state(input, MT_TOOL_FINGER, false); input_report_key(input, BTN_TOUCH, 0); input_sync(input); was_moved = false; } last_x = -1; last_y = -1; } } }
最终解决方案总结:
问题描述:
为了解决之前遗留的问题,不能长按拖动滑动,我希望在触摸屏驱动中实现以下功能:
- 当用户单击屏幕而不进行持续滑动时,应立即发送触摸释放(UP)事件。
- 当用户在屏幕上进行持续滑动时,应在滑动结束后发送触摸释放(UP)事件。
- 屏蔽多点触控,只处理第一个触摸点。
解决方案:
- 防抖动处理:为了避免因为手指微小的震动导致的误判,引入了一个防抖动时间阈值
DEBOUNCE_TIME_MS
。如果两次触摸事件之间的时间小于此值,会忽略这个事件。 - 移动检测:使用
MOVE_THRESHOLD
来确定触摸点是否移动了。只有当触摸点移动的距离超过此值时,才会被认为是有效的移动。 - 滑动检测:使用
SLIDE_TIME_THRESHOLD_MS
来确定是否发生了滑动。触摸点必须持续移动超过此时间才会被认为是滑动。 - 处理单点触摸:为了屏蔽多点触控,检查触摸点的编号(slot number)。如果不是第一个触摸点(即slot number不为0),会忽略这个触摸点。
- 发送UP事件:
- 对于单击非持续滑动的情况,在检测到触摸点后,立即发送UP事件。
- 对于持续滑动的情况,在滑动结束后发送UP事件。
代码实现:
在mt_complete_slot
函数中实现了上述逻辑。检查触摸点的数量和有效性。然后使用ktime_get
函数获取当前时间,并与上一次的触摸事件时间进行比较,以确定是否需要进行防抖动处理。
接着检查触摸点的移动距离和持续时间,以确定是否发生了滑动。如果发生了滑动,会设置was_moved
标志。
根据was_moved
标志和触摸状态来决定是否发送UP事件。如果没有发生滑动(普通触摸点击),会立即发送UP事件。如果发生了滑动,会在滑动结束后发送UP事件。
通过上述修改,成功地实现了需求(测试和正常的无差别,不会影响操作),即正确处理单击和滑动事件,并屏蔽了多点触控。
结论
根据我的解决方案,客户的USB TP现在可以正常点击,满足了他们的需求。我怀疑USB TP本身存在问题,但由于供应商不再支持这款TP,无法进一步确认。在Android内核驱动中,为了确保准确地报告每个触摸点的状态。我增加了防抖动逻辑,有效地避免了由硬件或其他外部因素引起的冗余触摸事件。
我希望这篇博客对你有所帮助,如果有任何问题或建议,请在评论区留言。谢谢