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

本文涉及的产品
文本翻译,文本翻译 100万字符
日志服务 SLS,月写入数据量 50GB 1个月
图片翻译,图片翻译 100张
简介: 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内核驱动中,为了确保准确地报告每个触摸点的状态。我增加了防抖动逻辑,有效地避免了由硬件或其他外部因素引起的冗余触摸事件。


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

相关文章
|
5月前
|
Java Android开发 UED
🧠Android多线程与异步编程实战!告别卡顿,让应用响应如丝般顺滑!🧵
【7月更文挑战第28天】在Android开发中,确保UI流畅性至关重要。多线程与异步编程技术可将耗时操作移至后台,避免阻塞主线程。我们通常采用`Thread`类、`Handler`与`Looper`、`AsyncTask`及`ExecutorService`等进行多线程编程。
61 2
|
7月前
|
XML 开发工具 Android开发
|
4月前
|
Android开发
基于Amlogic 安卓9.0, 驱动简说(五):基于GPIO、LED子系统的LED驱动
这篇文章是关于如何在基于Amlogic T972的Android 9.0系统上,使用GPIO和LED子系统来实现LED驱动的教程,包括了DTS设备树配置、驱动源码编写以及如何在用户空间控制LED的亮度和开关。
113 0
基于Amlogic 安卓9.0, 驱动简说(五):基于GPIO、LED子系统的LED驱动
|
4月前
|
Android开发
基于Amlogic 安卓9.0, 驱动简说(四):Platform平台驱动,驱动与设备的分离
本文介绍了如何在基于Amlogic T972的Android 9.0系统上使用Platform平台驱动框架和设备树(DTS),实现设备与驱动的分离,并通过静态枚举在设备树中描述设备,自动触发驱动程序的加载和设备创建。
68 0
基于Amlogic 安卓9.0, 驱动简说(四):Platform平台驱动,驱动与设备的分离
|
4月前
|
Android开发
基于Amlogic 安卓9.0, 驱动简说(三):使用misc框架,让驱动更简单
如何使用Amlogic T972安卓9.0系统上的misc框架来简化驱动程序开发,通过misc框架自动分配设备号并创建设备文件,从而减少代码量并避免设备号冲突。
50 0
基于Amlogic 安卓9.0, 驱动简说(三):使用misc框架,让驱动更简单
|
4月前
|
Android开发 C语言
基于Amlogic 安卓9.0, 驱动简说(二):字符设备驱动,自动创建设备
这篇文章是关于如何在基于Amlogic T972的Android 9.0系统上,通过自动分配设备号和自动创建设备节点文件的方式,开发字符设备驱动程序的教程。
64 0
基于Amlogic 安卓9.0, 驱动简说(二):字符设备驱动,自动创建设备
|
4月前
|
自然语言处理 Shell Linux
基于Amlogic 安卓9.0, 驱动简说(一):字符设备驱动,手动创建设备
本文是关于在Amlogic安卓9.0平台上创建字符设备驱动的教程,详细介绍了驱动程序的编写、编译、部署和测试过程,并提供了完整的源码和应用层调用示例。
93 0
基于Amlogic 安卓9.0, 驱动简说(一):字符设备驱动,手动创建设备
|
4月前
|
传感器 Android开发 芯片
不写一行代码(三):实现安卓基于i2c bus的Slaver设备驱动
本文是系列文章的第三篇,展示了如何在Android系统中利用现有的i2c bus驱动,通过编写设备树节点和应用层的控制代码,实现对基于i2c bus的Slaver设备(如六轴陀螺仪模块QMI8658C)的控制,而无需编写设备驱动代码。
53 0
不写一行代码(三):实现安卓基于i2c bus的Slaver设备驱动
|
4月前
|
Android开发
不写一行代码(二):实现安卓基于PWM的LED设备驱动
本文介绍了在Android系统中不编写任何代码,通过设备树配置和内核支持的通用PWM LED驱动来实现基于PWM的LED设备驱动,并通过测试命令调整LED亮度级别。
49 0
不写一行代码(二):实现安卓基于PWM的LED设备驱动
|
4月前
|
Linux Android开发 C语言
不写一行代码(一):实现安卓基于GPIO的LED设备驱动
本文通过实践操作,展示了在Android系统中不编写任何代码,利用设备树(DTS)配置和内核支持的通用GPIO LED驱动来控制LED设备,并进一步通过C语言编写NDK测试APP来实现LED的闪烁效果。
167 0
不写一行代码(一):实现安卓基于GPIO的LED设备驱动