好久没写东西了,今天开始咱们继续。
背景
在一个较为成熟的支持类项目中,客户更换了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内核驱动中,为了确保准确地报告每个触摸点的状态。我增加了防抖动逻辑,有效地避免了由硬件或其他外部因素引起的冗余触摸事件。
我希望这篇博客对你有所帮助,如果有任何问题或建议,请在评论区留言。谢谢!