由于要给开发板做一个触摸屏的demo演示,于是就找到了LVGL这款开源的GUI,打算利用内核自带的FT5X06驱动和LVGL一起完成触摸屏的demo程序但是在联调的时候发现触摸屏在运行一段时间后界面就卡死不动了。下面就简单讲述一下问题的解决方法。
- 一、驱动是否卡死
先检查一下内核的FT5X06驱动是否卡死,在命令行输入evtest命令可以读取系统中所有input设备的状态。通过这个命令读取到FT5X06的驱动可以向应用层上报事件,所以排除了FT5X06驱动卡死的原因。
- 二、LVGL是否卡死
LVGL在初始化完成后,是通过一系列的线程去不停地调用一些函数来实时更新界面,所以有没有可能是LVGL的某些线程卡死导致界面不刷新。LVGL读取触摸事件的函数是evdev_read,这个函数会循环读取驱动发过来的事件并进行相应的处理,并得到触摸屏的x和y坐标。如果这个函数卡死了那么LVGL就不能处理驱动发来的事件,触摸屏也就卡死了。但是经过检查,这个函数也没有卡死。
void evdev_read(lv_indev_drv_t * drv, lv_indev_data_t * data)
{
struct input_event in;
while(read(evdev_fd, &in, sizeof(struct input_event)) > 0) {
// printf("in.type = %d\r\n",in.type);
if(in.type == EV_REL) {
if(in.code == REL_X)
#if EVDEV_SWAP_AXES
evdev_root_y += in.value;
#else
evdev_root_x += in.value;
#endif
else if(in.code == REL_Y)
#if EVDEV_SWAP_AXES
evdev_root_x += in.value;
#else
evdev_root_y += in.value;
#endif
} else if(in.type == EV_ABS) {
// printf("EV_ABS event\r\n");
if(in.code == ABS_X)
#if EVDEV_SWAP_AXES
evdev_root_y = in.value;
#else
evdev_root_x = in.value;
#endif
else if(in.code == ABS_Y)
#if EVDEV_SWAP_AXES
evdev_root_x = in.value;
#else
evdev_root_y = in.value;
#endif
else if(in.code == ABS_MT_POSITION_X)
#if EVDEV_SWAP_AXES
evdev_root_y = in.value;
#else
evdev_root_x = in.value;
#endif
else if(in.code == ABS_MT_POSITION_Y)
#if EVDEV_SWAP_AXES
evdev_root_x = in.value;
#else
evdev_root_y = in.value;
#endif
else if(in.code == ABS_MT_TRACKING_ID) {
if(in.value == -1)
evdev_button = LV_INDEV_STATE_REL;
else if(in.value == 0)
evdev_button = LV_INDEV_STATE_PR;
}
} else if(in.type == EV_KEY) {
// printf("EV_KEY event\r\n");
if(in.code == BTN_MOUSE || in.code == BTN_TOUCH) {
if(in.value == 0)
evdev_button = LV_INDEV_STATE_REL;
else if(in.value == 1)
evdev_button = LV_INDEV_STATE_PR;
} else if(drv->type == LV_INDEV_TYPE_KEYPAD) {
#if USE_XKB
data->key = xkb_process_key(in.code, in.value != 0);
#else
switch(in.code) {
case KEY_BACKSPACE:
data->key = LV_KEY_BACKSPACE;
break;
case KEY_ENTER:
data->key = LV_KEY_ENTER;
break;
case KEY_PREVIOUS:
data->key = LV_KEY_PREV;
break;
case KEY_NEXT:
data->key = LV_KEY_NEXT;
break;
case KEY_UP:
data->key = LV_KEY_UP;
break;
case KEY_LEFT:
data->key = LV_KEY_LEFT;
break;
case KEY_RIGHT:
data->key = LV_KEY_RIGHT;
break;
case KEY_DOWN:
data->key = LV_KEY_DOWN;
break;
case KEY_TAB:
data->key = LV_KEY_NEXT;
break;
default:
data->key = 0;
break;
}
#endif /* USE_XKB */
if (data->key != 0) {
/* Only record button state when actual output is produced to prevent widgets from refreshing */
data->state = (in.value) ? LV_INDEV_STATE_PR : LV_INDEV_STATE_REL;
}
evdev_key_val = data->key;
evdev_button = data->state;
return;
}
}
}
if(drv->type == LV_INDEV_TYPE_KEYPAD) {
/* No data retrieved */
data->key = evdev_key_val;
data->state = evdev_button;
return;
}
if(drv->type != LV_INDEV_TYPE_POINTER)
return ;
/*Store the collected data*/
#if EVDEV_CALIBRATE
data->point.x = map(evdev_root_x, EVDEV_HOR_MIN, EVDEV_HOR_MAX, 0, drv->disp->driver->hor_res);
data->point.y = map(evdev_root_y, EVDEV_VER_MIN, EVDEV_VER_MAX, 0, drv->disp->driver->ver_res);
#else
data->point.x = evdev_root_x;
data->point.y = evdev_root_y;
#endif
data->state = evdev_button;
if(data->point.x < 0)
data->point.x = 0;
if(data->point.y < 0)
data->point.y = 0;
if(data->point.x >= drv->disp->driver->hor_res)
data->point.x = drv->disp->driver->hor_res - 1;
if(data->point.y >= drv->disp->driver->ver_res)
data->point.y = drv->disp->driver->ver_res - 1;
return ;
}
- 三、驱动的多点触摸问题
后来又经常一段时间的问题排查后,终于发现了触摸卡死的原因。其实原因就出现在FT5X06的驱动上。在LVGL的evdev_read函数中,会判断一个事件BTN_TOUCH是否有上报,这个BTN_TOUCH是内核定义的一个输入事件,用于向应用层上报触摸屏是否有按下。LVGL根据这个事件来判断触摸屏是否有按下,从而更新界面。
在FT5X06中的驱动中,默认是使用了最大的触摸点,也就是5个触摸点。在FT5X06的中断函数中会依次处理这些触摸点。问题就出现在处理触摸点上。在input_mt_report_pointer_emulation这个函数中有一个**input_event(dev, EV_KEY, BTN_TOUCH, count > 0)**的函数。这个函数就是往应用层上报BTN_TOUCH事件。后来发现当触摸点个数超过两个的时候,FT5X06的驱动就不会往应用层发送BTN_TOUCH事件,这就是导致LVGL卡死的原因。
void input_mt_report_pointer_emulation(struct input_dev *dev, bool use_count)
{
struct input_mt *mt = dev->mt;
struct input_mt_slot *oldest;
int oldid, count, i;
if (!mt)
return;
oldest = NULL;
oldid = mt->trkid;
count = 0;
for (i = 0; i < mt->num_slots; ++i) {
struct input_mt_slot *ps = &mt->slots[i];
int id = input_mt_get_value(ps, ABS_MT_TRACKING_ID);
if (id < 0)
continue;
if ((id - oldid) & TRKID_SGN) {
oldest = ps;
oldid = id;
}
count++;
}
input_event(dev, EV_KEY, BTN_TOUCH, count > 0);
if (use_count)
input_mt_report_finger_count(dev, count);
if (oldest) {
int x = input_mt_get_value(oldest, ABS_MT_POSITION_X);
int y = input_mt_get_value(oldest, ABS_MT_POSITION_Y);
input_event(dev, EV_ABS, ABS_X, x);
input_event(dev, EV_ABS, ABS_Y, y);
if (test_bit(ABS_MT_PRESSURE, dev->absbit)) {
int p = input_mt_get_value(oldest, ABS_MT_PRESSURE);
input_event(dev, EV_ABS, ABS_PRESSURE, p);
}
} else {
if (test_bit(ABS_MT_PRESSURE, dev->absbit))
input_event(dev, EV_ABS, ABS_PRESSURE, 0);
}
}
解决方法也很简单,在驱动中把最大的触摸点个数从5个改成1个,只支持单点触摸就可以解决这个问题。