Android 解决USB TP驱动中触摸卡顿和防抖动问题

简介: Android 解决USB TP驱动中触摸卡顿和防抖动问题
背景

在一个较为成熟的支持类项目中,客户更换了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
//不正常

从这两段日志中,可以提炼出以下关键差异:

  1. 跟踪ID的差异:在正常日志中,触摸屏只报告了一个触摸点的跟踪ID(0000002f)。而在异常日志中,触摸屏报告了两个触摸点的跟踪ID(00000030和ffffffff)。这可能暗示在异常日志中,触摸屏可能检测到了两个或更多的触摸点。
  2. 按键事件的响应:在正常日志中,触摸屏在报告触摸位置后,立即报告了按键事件(BTN_TOUCH DOWN),并在触摸结束后立即报告了按键事件(BTN_TOUCH UP)。而在异常日志中,触摸屏在报告触摸位置后,并没有立即报告按键事件,而是有一定的延迟。

简单的说 对于相同的操作,异常日志显示触摸屏在按下后并没有及时检测到松开动作。

此外,我还对比测试了XXX、XXX和XXX项目,发现它们的现象均相同。询问其他同行后得知,他们也遇到了类似的问题,是通过触摸屏厂家提供的固件更新来解决(然而我们这个TP已不支持了,所以只能通过系统软件解决)。

解决方案
  1. 立即报告触摸点的离开状态
    我修改了驱动代码,使其在处理完一个触摸点后立即报告其离开状态。
  2. 添加防抖动逻辑
    为了解决多次触摸事件的问题,我在驱动中添加了防抖动逻辑。使用了一个时间戳来记录上次触摸事件的时间,并与当前事件的时间进行比较。如果两个事件之间的时间差小于预定义的阈值(例如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;
        }
    }
}

最终解决方案总结:

问题描述

为了解决之前遗留的问题,不能长按拖动滑动,我希望在触摸屏驱动中实现以下功能:

  1. 当用户单击屏幕而不进行持续滑动时,应立即发送触摸释放(UP)事件。
  2. 当用户在屏幕上进行持续滑动时,应在滑动结束后发送触摸释放(UP)事件。
  3. 屏蔽多点触控,只处理第一个触摸点。

解决方案

  1. 防抖动处理:为了避免因为手指微小的震动导致的误判,引入了一个防抖动时间阈值DEBOUNCE_TIME_MS。如果两次触摸事件之间的时间小于此值,会忽略这个事件。
  2. 移动检测:使用MOVE_THRESHOLD来确定触摸点是否移动了。只有当触摸点移动的距离超过此值时,才会被认为是有效的移动。
  3. 滑动检测:使用SLIDE_TIME_THRESHOLD_MS来确定是否发生了滑动。触摸点必须持续移动超过此时间才会被认为是滑动。
  4. 处理单点触摸:为了屏蔽多点触控,检查触摸点的编号(slot number)。如果不是第一个触摸点(即slot number不为0),会忽略这个触摸点。
  5. 发送UP事件
  • 对于单击非持续滑动的情况,在检测到触摸点后,立即发送UP事件。
  • 对于持续滑动的情况,在滑动结束后发送UP事件。

代码实现

mt_complete_slot函数中实现了上述逻辑。检查触摸点的数量和有效性。然后使用ktime_get函数获取当前时间,并与上一次的触摸事件时间进行比较,以确定是否需要进行防抖动处理。

接着检查触摸点的移动距离和持续时间,以确定是否发生了滑动。如果发生了滑动,会设置was_moved标志。

根据was_moved标志和触摸状态来决定是否发送UP事件。如果没有发生滑动(普通触摸点击),会立即发送UP事件。如果发生了滑动,会在滑动结束后发送UP事件。

通过上述修改,成功地实现了需求(测试和正常的无差别,不会影响操作),即正确处理单击和滑动事件,并屏蔽了多点触控。

结论

根据我的解决方案,客户的USB TP现在可以正常点击,满足了他们的需求。我怀疑USB TP本身存在问题,但由于供应商不再支持这款TP,无法进一步确认。在Android内核驱动中,为了确保准确地报告每个触摸点的状态。我增加了防抖动逻辑,有效地避免了由硬件或其他外部因素引起的冗余触摸事件。


我希望这篇博客对你有所帮助,如果有任何问题或建议,请在评论区留言。谢谢

相关文章
|
1月前
|
XML 开发工具 Android开发
|
15小时前
|
安全 API Android开发
Android打开USB调试命令
【6月更文挑战第20天】
|
5天前
|
XML 监控 安全
Android App性能优化之卡顿监控和卡顿优化
本文探讨了Android应用的卡顿优化,重点在于布局优化。建议包括将耗时操作移到后台、使用ViewPager2实现懒加载、减少布局嵌套并利用merge标签、使用ViewStub减少资源消耗,以及通过Layout Inspector和GPU过度绘制检测来优化。推荐使用AsyncLayoutInflater异步加载布局,但需注意线程安全和不支持特性。卡顿监控方面,提到了通过Looper、ChoreographerHelper、adb命令及第三方工具如systrace和BlockCanary。总结了Choreographer基于掉帧计算和BlockCanary基于Looper监控的原理。
15 3
|
11天前
|
Java API Android开发
安卓开发app 调用usb 摄像头 需要用到哪个库
在安卓开发中,调用USB摄像头常常使用libuvc库,这是一个跨平台处理USB视频设备的库。有多个基于libuvc的开源项目简化了在安卓上的使用,如UVCCamera和Android EasyCap UVC。例如,UVCCamera提供了一个更简单的接口来访问USB摄像头,并且可以在Jetpack Compose中显示预览。开发者可以参考官方文档、开源项目以及相关教程和资源来学习和实现这一功能。
|
1月前
|
Android开发
Android监听USB设备插拔
Android监听USB设备插拔
70 7
|
1月前
|
Java Android开发
Android 触摸音的播放
Android 触摸音的播放
19 5
|
1月前
|
Java Android开发
修改Android 触摸提示音及音量大小
修改Android 触摸提示音及音量大小
46 4
|
1月前
|
Android开发
Android 获取 USB设备列表
Android 获取 USB设备列表 【5月更文挑战第6天】
46 4
|
1月前
|
编解码 调度 Android开发
Android音频框架之一 详解audioPolicy流程及HAL驱动加载与配置
Android音频框架之一 详解audioPolicy流程及HAL驱动加载与配置
61 0
|
1月前
|
Android开发
Android 12修改usb tp触摸唤醒
Android 12修改usb tp触摸唤醒
27 0