一、函数介绍:

首先我们要知道lv_label_set_text_fmt函数的作用,他是给LVGL中的标签设置文本用的:

/**
 * Set a new formatted text for a label. Memory will be allocated to store the text by the label.
 * @param obj           pointer to a label object
 * @param fmt           `printf`-like format
 * @example lv_label_set_text_fmt(label1, "%d user", user_num);
 */
void lv_label_set_text_fmt(lv_obj_t * obj, const char * fmt, ...) LV_FORMAT_ATTRIBUTE(2, 3);

//函数实现
void lv_label_set_text_fmt(lv_obj_t * obj, const char * fmt, ...)
{
    LV_ASSERT_OBJ(obj, MY_CLASS);
    LV_ASSERT_NULL(fmt);

    lv_obj_invalidate(obj);
    lv_label_t * label = (lv_label_t *)obj;

    /*If text is NULL then refresh*/
    if(fmt == NULL) {
        lv_label_refr_text(obj);
        return;
    }

    if(label->text != NULL && label->static_txt == 0) {
        lv_mem_free(label->text);
        label->text = NULL;
    }

    va_list args;
    va_start(args, fmt);
    label->text = _lv_txt_set_text_vfmt(fmt, args);
    va_end(args);
    label->static_txt = 0; /*Now the text is dynamically allocated*/

    lv_label_refr_text(obj);
}

由函数实现可知,在使用该函数时,会分配一个空间,所以要确保设备有足够的内存以供使用。

二、问题描述:

在我最近的一个项目中,使用到了这个函数,用来更新标签上的文本,大概流程就是首先接收信号,接收到了数据更新的信号后,会调用这个函数,进行数据的更新:

// 1.发送信号:
lv_event_send(obj, LV_EVENT_USER_REFRESH_RESULT, dataTypedef); // 自定义信号,用于更新数据,参数为一个结构体数组

// 2.在控件的回调函数中接收该信号,并进行处理
static void RefreDataCbk(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_USER_REFRESH_RESULT) // 接收到信号
    {
    	DataTypedef *data = (DataTypedef *)event->param
        for(u8 i = 0; i < 5; i++)
        {
            if(data[i]->param1[0] == '\0')
                break;
            lv_label_set_text_fmt(Label1[i], "%s", data[i]->param1);
            lv_obj_clear_flag(Label1[i], LV_OBJ_FLAG_HIDDEN);

            lv_label_set_text_fmt(Label2[i], "%d", data[i]->param2);
            lv_obj_clear_flag(Label2[i], LV_OBJ_FLAG_HIDDEN);

            lv_label_set_text_fmt(Label3[i], "%llu", data[i]->param3);
            lv_obj_clear_flag(Label3[i], LV_OBJ_FLAG_HIDDEN);
        }
    }
}

基本流程就是这样,但在实际使用过程中会出现卡死的情况,而且是有时会卡死,有时候又不会,让人摸不着头脑。尝试多次后无果,感觉我的流程没有任何问题,于是上官网的论坛去找,还真有:

关于LVGL中lv_label_set_text_fmt函数的问题_数据更新

看了一下,情况跟我的有点区别,发问人是因为分别在两个线程中调用这个函数,导致卡死了,官方解释到是因为没有上锁,导致的资源竞态问题,而我是在同一个线程中使用的,应该不存在这个问题。但是也给我了启发,会不会也是这种问题?就是我在处理这个当前信号的同时,又接收到一个信号,导致同时有两个对标签进行设置文本,导致卡死了?

三、问题解决:

既然找到了大概的方向,那么开始解决吧:

有两个方法:

其一:取消使用信号接收刷新标志的方式,改为用一个定时器定时去刷新:

lv_timer_create(DataRefreshTimer, 100, NULL); // 开启定时器,更新数据

// 定时器函数,注意:此处的变量升级为全局变量
void DataRefreshTimer(lv_timer_t *tm)
{
    for(u8 i = 0; i < 5; i++)
    {
        if(data[i]->param1[0] == '\0')
            break;
        lv_label_set_text_fmt(Label1[i], "%s", data[i]->param1);
        lv_obj_clear_flag(Label1[i], LV_OBJ_FLAG_HIDDEN);

        lv_label_set_text_fmt(Label2[i], "%d", data[i]->param2);
        lv_obj_clear_flag(Label2[i], LV_OBJ_FLAG_HIDDEN);

        lv_label_set_text_fmt(Label3[i], "%llu", data[i]->param3);
        lv_obj_clear_flag(Label3[i], LV_OBJ_FLAG_HIDDEN);
    }        
}

其二:人工上锁,用一个全局变量,进入前赋值,处理完赋值,当接收到信号后,但是还在处理当中时,就不处理/不发送这个信号:

// 1.发送信号:
if(FlagDone)
	lv_event_send(obj, LV_EVENT_USER_REFRESH_RESULT, dataTypedef); // 为1时才可以进来

// 2.在控件的回调函数中接收该信号,并进行处理
static void RefreDataCbk(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_USER_REFRESH_RESULT) // 接收到信号
    {
    	FlagDone = 0; // 进来,置0
    	DataTypedef *data = (DataTypedef *)event->param
        for(u8 i = 0; i < 5; i++)
        {
            if(data[i]->param1[0] == '\0')
                break;
            lv_label_set_text_fmt(Label1[i], "%s", data[i]->param1);
            lv_obj_clear_flag(Label1[i], LV_OBJ_FLAG_HIDDEN);

            lv_label_set_text_fmt(Label2[i], "%d", data[i]->param2);
            lv_obj_clear_flag(Label2[i], LV_OBJ_FLAG_HIDDEN);

            lv_label_set_text_fmt(Label3[i], "%llu", data[i]->param3);
            lv_obj_clear_flag(Label3[i], LV_OBJ_FLAG_HIDDEN);
        }
        FlagDone = 1; // 出去,置1
    }
}

这个方法我还没试过,但应该有用吧。。。

需要注意的是,可能存在数据更新不完全的情况,就是在最后一次接收数据时,前面没有处理完,导致忽略了最后一个信号,数据更新不完整,可以考虑再发送一次来解决(没试过,我猜的)。