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内核驱动中,为了确保准确地报告每个触摸点的状态。我增加了防抖动逻辑,有效地避免了由硬件或其他外部因素引起的冗余触摸事件。


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

猜你喜欢

转载自blog.csdn.net/SHH_1064994894/article/details/133938998