问题描述:

最近在做的项目是基于LVGL的一个UI设计,用的是SWM341的板子,LVGL版本是8.3。其他功能都OK,但是,当我用按键进行界面切换时,就会出问题,会导致界面卡死(有时),界面刷新不完全,会残存一些色块在屏幕上,只有再次切换界面时,才会消失,还有在第一个界面跳转到第二个界面时,会有一些label的图像或者文本会显示出来,但很快就消失了,而同样的切换,由屏幕触摸进行切换时就不会卡住,就很奇怪。

相关代码:

void KeyTimer() // 处理按键事件
{
    // 此处读取按键键值,并进行处理
    key = KeyCodeScan();
    KeyValueProc(key);
    key = 0; // 处理完置为0
}

// 在KeyValueProc函数中,会对按键键值进行判断,从而触发LVGL的信号发送函数:
lv_event_send(CurrentWindow, LV_EVENT_CLICKED, NULL); // 给当前界面发送按下的信号

// 在界面接收到信号后,会对信号进行处理,进行界面的切换
static void CurrentPageEventCbk(lv_event_t *event)
{
    lv_event_code_t code = lv_event_get_code(event);
    lv_obj_t *obj = event->current_target;

    if (code == LV_EVENT_CLICKED) // 接收到信号
    {
        lv_obj_del(obj); // 删除当前界面
        SetCurrentWindow(parentPage); // 设置当前界面为父界面,父界面,是界面创建时传入的一个指针,存储上一个界面。
    }
}
// 同样的,obj这个控件被点击时,也会发送LV_EVENT_CLICKED这个信号,也是进入上面这个函数
// 所以触摸点击切换和按键点击切换实际上走的是同一个函数。

// SetCurrentWindow函数的作用是将当前界面的指针存储在全局变量中:
void SetCurrentWindow(void *window)
{
    CurrentWindow = window;
}

实际上的界面切换是通过删除当前界面,以显示上一个界面来实现的。

问题分析:

既然按键跟屏幕实际都是走的同一个函数,而屏幕切换没有问题,那么说明函数本身是没有问题的,问题就出现在其他地方。经过不断的调试,排查,发现这两个触发点不同,按键触发是在FreeRTOS的第二个任务,也就是第二个线程里面的一个定时器实现的,而屏幕触发是在另一个任务(线程)中,两个属于不同的线程,但都是对同一个函数进行操作,这就可能就导致线程安全的问题了,对临界资源的抢占使用可能导致界面的卡顿,甚至死机。

问题解决:

解决方法有两个:

第一个:上锁,保证一个线程在调用时,另一个线程无法进入。

第二个:既然分为两个线程会有问题,那么就在同一个线程里面处理按键。

由于FreeRTOS的锁没怎么使用过,不太熟悉,所以我就采取了第二种方法。

由于在LVGL v8里面取消了原本的lv_task_create,只有lv_timer_create,只能创建定时器了,所以将KeyTimer()这个函数放置到LVGL的定时器任务里面去,如下:

lv_timer_create(KeyTimer, 50, NULL); // 开启定时器,处理按键事件

修改完成后,再来验证,没有问题了,界面切换灰常丝滑,没有残存的色块了。

总结一下:线程安全十分重要,采用多线程时要时刻关注各个线程的资源使用!!!