当前位置: 首页 > ops >正文

如何在LVGL之外的线程更新UI内容

前言

  作为一个刚开始学习LVGL和嵌入式开发的新手,学会绘制一个界面之后,遇到了一个问题:在LVGL线程之外的线程,更新UI内容时,会导致程序崩溃。

1、问题分析

首先,需要了解LVGL的基本工作原理。LVGL(Light and Versatile Graphics Library)是一个用于嵌入式系统的开源图形库,主要用于创建用户界面。它通常运行在单线程环境中,所有的UI操作(如创建控件、更新显示等)都在主线程中完成。这是因为LVGL本身并不是线程安全的,直接在其他线程中操作UI可能会导致竞态条件或数据不一致的问题。

接下来,需要理解为什么要在其他线程更新UI。在实际应用中,可能会有一些耗时操作,比如网络请求、数据处理或硬件交互,这些操作如果放在主线程中执行,会导致界面卡顿,影响用户体验。因此,将这些操作放在后台线程中执行,完成后再更新UI,是一种常见的做法。

现在的问题是,如何在非LVGL线程(即后台线程)中安全地更新UI。我的初步思路是,需要某种机制将后台线程中的数据或状态传递到主线程,由主线程负责实际的UI更新。这通常涉及到线程间的通信或同步机制,比如消息队列、信号量、互斥锁等。

需要了解LVGL的主循环机制。LVGL通常在一个主循环中处理任务,例如通过调用`lv_timer_handler()`来执行定时器任务和UI刷新。这个主循环通常运行在主线程中。因此,任何对UI的修改都需要在这个主线程中进行,以确保线程安全。

2、解决思路

那么,何将后台线程的更新请求传递到主线程呢?可能的解决方案包括:

2.1.使用消息队列

后台线程将更新UI的请求放入一个消息队列中,主线程在每次循环中检查队列并处理这些请求。

2.2 使用回调函数

在后台线程中完成处理后,通过某种方式触发主线程中的回调函数来更新UI。

2.3 使用互斥锁(Mutex)

在访问共享的UI资源时,使用互斥锁来确保同一时间只有一个线程操作UI。

3、步骤分析

接下来,需要考虑这些方法的可行性和具体实现细节。

3.1 使用消息队列的步骤

1. 创建一个线程安全的消息队列,用于存储UI更新请求。

2. 后台线程在需要更新UI时,将请求(例如,更新某个标签的文本)放入队列。

3. 主线程在每次执行`lv_timer_handler()`之前或之后,从队列中取出请求并执行相应的UI操作。

**注意事项**:

- 需要确保消息队列的线程安全性,避免数据竞争。

- 消息的结构需要包含足够的信息来描述如何更新UI(例如,控件指针、新值等)。

- 需要考虑内存管理,避免在传递指针时出现悬垂指针或内存泄漏。

3.2 使用互斥锁的步骤

1. 在访问LVGL的API时,使用互斥锁来保护关键区域。

2. 后台线程在更新UI前获取锁,执行操作后释放锁。

3. 主线程同样在操作UI时获取锁,确保互斥访问

**注意事项**:

- LVGL本身可能并不是线程安全的,即使使用互斥锁,某些内部状态可能仍然存在竞态条件。

- 频繁的锁操作可能会影响性能,尤其是在高频率更新时。

- 需要仔细设计锁的范围,避免死锁。

3.3 使用回调函数的步骤

1. 主线程注册一个回调函数,用于处理特定的UI更新。

2. 后台线程在需要更新UI时,触发这个回调函数。

3. 回调函数在主线程的上下文中执行,确保安全地操作UI。

**注意事项**:

- 需要确保回调函数在主线程中被正确调用,可能需要借助操作系统的事件或消息机制。

- 需要处理回调函数的参数传递和生命周期管理。

4、可行性分析

现在,我需要进一步验证这些方法的可行性,并考虑实际实现中的细节问题。

例如,使用消息队列的方法,在FreeRTOS中,可以使用`xQueueSend()`和`xQueueReceive()`函数来实现线程间的消息传递。消息队列中的每个消息可以包含控件指针、操作类型(如设置文本、改变颜色等)以及相应的参数。主线程在每次循环中处理队列中的所有消息,执行对应的UI操作。

另一个例子,使用互斥锁的方法,在FreeRTOS中,可以使用`xSemaphoreCreateMutex()`创建一个互斥锁。在后台线程中,操作UI前先获取锁,操作完成后释放锁。主线程在调用LVGL的API时也需要获取相同的锁。但需要注意,如果LVGL的主循环本身涉及到多个步骤,可能需要在整个处理过程中保持锁,这可能影响其他线程的响应性。

此外,我还需要考虑LVGL的官方建议最佳实践。根据LVGL的文档,官方推荐在主线程中处理所有的UI操作,避免在多线程中直接调用LVGL的API。因此,使用消息队列将UI更新请求传递到主线程,可能是更符合LVGL设计理念的做法

5、解决方案

5.1 创建线程安全的消息队列

  • 使用RTOS提供的消息队列(如FreeRTOS的QueueHandle_t

  • 定义消息结构体,包含UI更新所需的信息

  • typedef struct {lv_obj_t* target;    // 目标控件void* data;          // 数据指针void (*update_func)(lv_obj_t*, void*); // 更新函数
    } UI_Update_Message;QueueHandle_t ui_update_queue = xQueueCreate(10, sizeof(UI_Update_Message));

    5.2 实现消息处理函数

  • 在主线程的LVGL循环中处理消息
  • void process_ui_messages(void) {UI_Update_Message msg;while(xQueueReceive(ui_update_queue, &msg, 0) == pdTRUE) {if(msg.target && msg.update_func) {msg.update_func(msg.target, msg.data);}// 释放动态分配的数据(如果需要则不释放)if(msg.data) {free(msg.data);}}
    }// 在LVGL主循环中调用
    while(1) {lv_timer_handler();process_ui_messages();vTaskDelay(pdMS_TO_TICKS(5));
    }

    5.3 后台线程发送更新请求

  • // 通用更新函数模板
    void safe_ui_update(lv_obj_t* target, void (*func)(lv_obj_t*, void*),void* data, size_t data_size) {UI_Update_Message msg = {.target = target,.update_func = func,.data = NULL};if(data && data_size > 0) {msg.data = pvPortMalloc(data_size);if(msg.data) memcpy(msg.data, data, data_size);}xQueueSend(ui_update_queue, &msg, portMAX_DELAY);
    }// 具体实现示例:更新标签文本
    void update_label_text(lv_obj_t* label, const char* text) {safe_ui_update(label, (void (*)(lv_obj_t*, void*))lv_label_set_text, (void*)text, strlen(text)+1);
    }// 后台线程中调用
    void sensor_thread(void* arg) {while(1) {float temp = read_temperature();char buffer[20];snprintf(buffer, sizeof(buffer), "Temp: %.1f℃", temp);update_label_text(ui.temp_label, buffer);vTaskDelay(pdMS_TO_TICKS(1000));}
    }

    5.4 高级功能扩展 (待开发)

    • 优先级消息处理

    • 批量更新优化

    • 异步回调支持

    • 内存管理

    • 性能优化

      • 批量处理UI更新请求

      • 使用双缓冲技术

      • 限制更新频率(如最大60FPS)

    • 错误处理

      • 添加队列满时的处理策略

      • 实现超时机制

      • 添加内存分配失败的回退方案

        

6、总结

这种设计模式的优势:

  1. 完全解耦业务逻辑和UI渲染

  2. 确保LVGL在单线程环境下运行

实际项目中需要根据具体需求调整:

  • 对于高实时性系统:缩短队列处理周期

  • 对于内存受限系统:使用静态内存分配

  • 对于复杂UI:添加批量更新优化

  • 对于安全关键系统:添加校验和验证机制

总的来说,能够导致LVGL卡死现象,主要原因就是在LVGL的线程外更新了LVGL对象导致的,内容如有不对之处,欢迎各位指点修改!

ps:参考并复制粘贴了DEEPSEEK的回答。

http://www.xdnf.cn/news/7331.html

相关文章:

  • 《Linux服务与安全管理》| DNS服务器安装和配置
  • 进程退出 和 僵尸进程、孤儿进程
  • 电子电路原理第十六章(负反馈)
  • 打卡第二十二天
  • 鸿蒙PC操作系统:从Linux到自研微内核的蜕变
  • Vue 3 项目开发 MinIO 文件管理模块
  • 数字图像处理——图像压缩
  • ai agent(智能体)开发 python高级应用8:crawl4ai 对用 LLMExtractionStrategy 和 python的 re 如何选择
  • 【C++】19. set和map的使用
  • Vue.js---立即执行的watch与回调执行时机
  • 【PhysUnits】4.4 零类型(Z0)及其算术运算(zero.rs)
  • 支持python的单片机有哪些
  • 高防CDN:让攻击流量“一键清零“的智能防御之道
  • 动态页面采集技术
  • Spring Boot入门案例(Spring Initializr方式,IDEA版)
  • 新时代的可观测性:拥抱 GuanceDB 3.0
  • 前端的面试笔记——HTMLJavaScript篇(二)前端页面性能检测
  • 2025B卷 - 华为OD机试七日集训第2期 - 按算法分类,由易到难,循序渐进,玩转OD(Python/JS/C/C++)
  • 192环境记录 zlm2
  • 大小端模式和消息的加密解密
  • IPV4详解和ensp基础命令
  • C#学习9——接口、抽象类
  • Web3:Ubuntu系统 使用Docker-compose方式部署blockscout浏览器配置版本-v5.2.3-beta+charts图表
  • 2025ICPC邀请赛南昌游记
  • 架构演变 -单体到云原生
  • C++学习:六个月从基础到就业——C++20:范围(Ranges)进阶
  • 高速光耦在通信行业的应用(六) | 5Mbps通信光耦的应用
  • 5月19日day30打卡
  • JavaWeb:SpringBoot处理全局异常(RestControllerAdvice)
  • 5.19本日总结