【奶茶Beta专项】【LVGL9.4源码分析】04-OS抽象层
- [1. 概述](#1. 概述)
-
- [1.1 文档目的](#1.1 文档目的)
- [1.2 代码版本与范围](#1.2 代码版本与范围)
- [2. 设计意图与总体定位](#2. 设计意图与总体定位)
-
- [2.1 问题背景](#2.1 问题背景)
- [2.2 OS 抽象层的角色](#2.2 OS 抽象层的角色)
- [2.3 设计目标](#2.3 设计目标)
- [3. 抽象接口分类与功能说明](#3. 抽象接口分类与功能说明)
-
- [3.1 全局锁与线程安全封装(lv_lock / lv_unlock)](#3.1 全局锁与线程安全封装(lv_lock / lv_unlock))
-
- [3.1.1 接口定义](#3.1.1 接口定义)
- [3.1.2 用法与语义](#3.1.2 用法与语义)
- [3.2 线程抽象:lv_thread_t 与 `lv_thread_*`](#3.2 线程抽象:lv_thread_t 与
lv_thread_*) -
- [3.2.1 抽象类型与优先级](#3.2.1 抽象类型与优先级)
- [3.2.2 抽象接口](#3.2.2 抽象接口)
- [3.3 互斥锁抽象:lv_mutex_t 与 `lv_mutex_*`](#3.3 互斥锁抽象:lv_mutex_t 与
lv_mutex_*) -
- [3.3.1 类型定义](#3.3.1 类型定义)
- [3.3.2 抽象接口](#3.3.2 抽象接口)
- [3.4 线程同步抽象:lv_thread_sync_t 与条件变量语义](#3.4 线程同步抽象:lv_thread_sync_t 与条件变量语义)
-
- [3.4.1 类型与目的](#3.4.1 类型与目的)
- [3.4.2 抽象接口](#3.4.2 抽象接口)
- [3.5 无 OS 场景优化:LV_OS_NONE](#3.5 无 OS 场景优化:LV_OS_NONE)
- [3.6 idle 统计与性能监控接口](#3.6 idle 统计与性能监控接口)
- [3.7 各 OS 后端实现概览](#3.7 各 OS 后端实现概览)
- [3.8 sleep 接口:lv_sleep_ms](#3.8 sleep 接口:lv_sleep_ms)
- [4. 使用方法(移植与应用层)](#4. 使用方法(移植与应用层))
-
- [4.1 配置 OS 类型](#4.1 配置 OS 类型)
- [4.2 在多线程应用中的推荐用法](#4.2 在多线程应用中的推荐用法)
- [4.3 自定义 OS 后端的实现步骤](#4.3 自定义 OS 后端的实现步骤)
- [5. 设计优势与可能的局限](#5. 设计优势与可能的局限)
-
- [5.1 优势](#5.1 优势)
- [5.2 局限与注意点](#5.2 局限与注意点)
- [5.3 与通用 OS 抽象层的差异](#5.3 与通用 OS 抽象层的差异)
- [5.4 OS 抽象层 API 速查表](#5.4 OS 抽象层 API 速查表)
- [6. 小结](#6. 小结)
-
- [7. 附录](#7. 附录)
-
- [A. 参考文档(外部)](#A. 参考文档(外部))
- [B. 相关资源(外部)](#B. 相关资源(外部))
- [C. 本文相关代码改动 Patch(OS 抽象层信号量扩展)](#C. 本文相关代码改动 Patch(OS 抽象层信号量扩展))
文档版本 : 1.0
更新日期 : 2025年11月
适用对象: LVGL9.4 在多 OS/多平台下移植与调优的工程师
1. 概述
1.1 文档目的
本篇面向需要在多种操作系统上移植和调优 LVGL 的工程师,帮助读者:
- 理解
library/lvgl/src/osal目录中 OS 抽象层在 LVGL 架构里的角色,以及它解决了什么问题; - 掌握在不同 OS(如 FreeRTOS、pthread、RT-Thread 等)下配置与使用 OS 抽象层的常见做法;
- 通过接口分类的方式快速了解常用 API 的含义和典型用法;
- 在项目选型和性能调优时,能够判断这种抽象方式的适用场景和可能带来的权衡。
1.2 代码版本与范围
- 仓库路径:
https://github.com/lvgl/lvgl.git - 版本:v9.4.0
- commit:
c016f72d4c125098287be5e83c0f1abed4706ee5 - 重点文件:
src/osal/lv_os.h/lv_os.c/lv_os_private.h- 各后端实现:
lv_freertos.*、lv_pthread.*、lv_rtthread.*、lv_cmsis_rtos2.*、lv_linux.*、lv_windows.*、lv_sdl2.*、lv_mqx.*、lv_os_none.*等
2. 设计意图与总体定位
2.1 问题背景
LVGL 需要在两端都工作良好:
- 一端是无 OS 的 MCU 场景 :单线程轮询
lv_timer_handler()即可,不希望引入复杂的线程/锁开销; - 另一端是多任务 OS 场景:Linux/pthread、FreeRTOS、RT-Thread、Windows 等,多线程/多核并发访问 LVGL 成为常态。
如果直接在内核里散布各 OS 的 API 调用,既难以维护,也会让无 OS 场景背负多余成本。
2.2 OS 抽象层的角色
OSAL 的定位可以概括为:
- 向上 :提供一套与具体 OS 解耦的抽象接口:
- 线程:
lv_thread_*; - 互斥锁:
lv_mutex_*; - 线程同步对象:
lv_thread_sync_*; - 全局锁与 sleep:
lv_lock/lv_unlock/lv_sleep_ms; - idle 统计接口:
lv_os_get_idle_percent()等。
- 线程:
- 向下 :针对不同 OS 提供一组实现文件:
- FreeRTOS →
lv_freertos.* - pthread/Linux →
lv_pthread.*/lv_linux.* - RT-Thread →
lv_rtthread.* - CMSIS-RTOS2 →
lv_cmsis_rtos2.* - Windows →
lv_windows.* - SDL2/桌面模拟 →
lv_sdl2.*等。
- FreeRTOS →
由 LV_USE_OS / LV_OS_* 配置在编译期选择具体后端。
2.3 设计目标
- 保证 LVGL 在多 OS 环境下有统一的并发模型(全局锁 + 线程抽象);
- 对于 LV_OS_NONE 场景,尽可能做到"零额外开销":
- 线程/互斥/同步函数全部 inline 空实现;
- 不额外引入调度和上下文切换。
3. 抽象接口分类与功能说明
本节按接口功能维度对 OSAL API 做分类说明,而不是逐文件罗列。
3.1 全局锁与线程安全封装(lv_lock / lv_unlock)
3.1.1 接口定义
在 lv_os.h / lv_os.c 中:
void lv_lock(void);- 加 LVGL 的"全局互斥锁"。
- 典型实现:
lv_mutex_lock(&lv_general_mutex)(在LV_USE_OS != LV_OS_NONE时)。
lv_result_t lv_lock_isr(void);- 中断上下文使用的加锁版本,对应
lv_mutex_lock_isr。
- 中断上下文使用的加锁版本,对应
void lv_unlock(void);- 解锁全局互斥。
3.1.2 用法与语义
- 在 非
lv_timer_handler线程 中调用 LVGL API 前:
c
lv_lock();
/* 调用任意 LVGL API */
lv_unlock();
- 在事件回调中(由
lv_timer_handler线程调用)则不需要显式加锁; - 这套机制保证了"同一时刻仅有一个线程在执行 LVGL 内核代码"。
3.2 线程抽象:lv_thread_t 与 lv_thread_*
3.2.1 抽象类型与优先级
lv_os_private.h中定义统一的线程优先级枚举:
c
typedef enum {
LV_THREAD_PRIO_LOWEST,
LV_THREAD_PRIO_LOW,
LV_THREAD_PRIO_MID,
LV_THREAD_PRIO_HIGH,
LV_THREAD_PRIO_HIGHEST,
} lv_thread_prio_t;
- 各后端头文件中定义
lv_thread_t的具体结构体:- FreeRTOS:包含任务句柄
TaskHandle_t xTaskHandle等; - pthread:一般会包含
pthread_t; - 其他 OS 类似。
- FreeRTOS:包含任务句柄
3.2.2 抽象接口
在 lv_os_private.h 中声明,由各后端实现:
lv_result_t lv_thread_init(lv_thread_t * thread, const char * name, lv_thread_prio_t prio, void (*callback)(void *), size_t stack_size, void * user_data);lv_result_t lv_thread_delete(lv_thread_t * thread);
这让 LVGL 内部及上层适配代码能启动辅助线程,而不需要直接依赖某个 OS 的 API。
3.3 互斥锁抽象:lv_mutex_t 与 lv_mutex_*
3.3.1 类型定义
- 各后端定义自己的
lv_mutex_t:- FreeRTOS 后端:
BaseType_t xIsInitialized标志位;SemaphoreHandle_t xMutex递归互斥;
- pthread 后端:通常会包装
pthread_mutex_t。
- FreeRTOS 后端:
3.3.2 抽象接口
- 初始化与销毁:
lv_result_t lv_mutex_init(lv_mutex_t * mutex);lv_result_t lv_mutex_delete(lv_mutex_t * mutex);
- 加锁与解锁:
lv_result_t lv_mutex_lock(lv_mutex_t * mutex);lv_result_t lv_mutex_lock_isr(lv_mutex_t * mutex);lv_result_t lv_mutex_unlock(lv_mutex_t * mutex);
全局锁 lv_lock / lv_unlock 就是对 lv_mutex_* 的一个具体使用。
3.4 线程同步抽象:lv_thread_sync_t 与条件变量语义
3.4.1 类型与目的
lv_thread_sync_t代表一个"线程同步对象",语义上类似于"条件变量 + 事件";- 设计目的:
- 在多线程环境中,让一个线程等待另一个线程发出"完成/唤醒"信号;
- 支持 ISR 触发唤醒。
3.4.2 抽象接口
- 初始化与销毁:
lv_thread_sync_init/lv_thread_sync_delete
- 等待与唤醒:
lv_thread_sync_wait:阻塞等待信号;lv_thread_sync_signal:从普通上下文发送信号;lv_thread_sync_signal_isr:从中断上下文发送信号。
以 FreeRTOS 实现为例:
- 可以使用 Task Notify 或自建
Semaphore + waitingThreads + syncMutex组合,实现"等待者计数 + 唤醒"的行为。
3.5 无 OS 场景优化:LV_OS_NONE
当 LV_USE_OS == LV_OS_NONE 时:
lv_thread_*/lv_mutex_*/lv_thread_sync_*在lv_os_private.h中全部实现为static inline空函数,返回LV_RESULT_INVALID或直接无操作;lv_lock/lv_unlock退化为空操作;lv_sleep_ms退化为lv_delay_ms(ms),仅依赖 LVGL 自己的 tick。
这样做的好处:
- 编译器可以完全内联并优化掉这些函数调用,对无 OS MCU 几乎"零开销";
- 即便应用代码误用线程/互斥 API,也不会导致崩溃,只是返回"不支持"。
3.6 idle 统计与性能监控接口
在 lv_os_private.h 中:
uint32_t lv_os_get_idle_percent(void);- 返回自上次调用以来 CPU 的 idle 百分比;
- 若启用
LV_SYSMON_PROC_IDLE_AVAILABLE:uint32_t lv_os_get_proc_idle_percent(void);
这些接口主要为 lv_sysmon 性能监控模块服务,用来计算:
CPU = 100 - idle%;- 以及必要时的进程级 CPU 占用。
各 OS 后端需要在适当位置(如 Idle Task、任务切换 hook)维护这些统计数据,LVGL 本身不直接依赖 OS 内核细节。
3.7 各 OS 后端实现概览
目录下的 .c/.h 文件可大致分组理解:
-
通用入口:
lv_os.h/lv_os.c:对上层公开的锁与 sleep 接口;lv_os_private.h:根据配置选择后端,并声明抽象接口。
-
无 OS 后端:
lv_os_none.c/lv_os_none.h:提供空实现和lv_sleep_ms的 delay 版本。
-
RTOS/平台后端:
lv_freertos.*:基于 FreeRTOS 任务、递归互斥、信号量、Task Notify 实现;lv_pthread.*:基于 POSIX pthread;lv_rtthread.*:RT-Thread;lv_cmsis_rtos2.*:CMSIS-RTOS2;lv_windows.*:Windows 下线程与同步抽象;lv_sdl2.*/lv_linux.*:桌面模拟/SDL2 环境下的 OS 封装;lv_mqx.*:特定 RTOS MQX 的适配。
所有后端共享同一组抽象函数签名,只在 .c 实现里使用各自 OS API。
3.8 sleep 接口:lv_sleep_ms
- 在有 OS 场景下(
LV_USE_OS != LV_OS_NONE),lv_sleep_ms通常由后端提供合适实现(如vTaskDelay/usleep等); - 在无 OS 场景下,
lv_os.c中定义:
c
#if LV_USE_OS == LV_OS_NONE
void lv_sleep_ms(uint32_t ms)
{
lv_delay_ms(ms);
}
#endif
使应用层可以用统一的 lv_sleep_ms 进行延时,而不关心底层是否存在线程/调度器。
4. 使用方法(移植与应用层)
4.1 配置 OS 类型
在 lv_conf.h 或 Kconfig 中设置:
LV_USE_OS:选择 OS 模式:LV_OS_NONE/LV_OS_FREERTOS/LV_OS_PTHREAD/LV_OS_RTTHREAD/LV_OS_CMSIS_RTOS2等;
- 若使用自定义 OS:
LV_USE_OS = LV_OS_CUSTOM;- 定义
LV_OS_CUSTOM_INCLUDE指向自定义后端头文件,例如"lv_myos.h"。
4.2 在多线程应用中的推荐用法
- 主线程/任务 :
- 定期调用
lv_timer_handler()驱动 LVGL 刷新;
- 定期调用
- 其他线程/任务 :
- 在调用 LVGL API 前后使用:
c
lv_lock();
/* UI 更新、对象创建、样式修改等 */
lv_unlock();
- 中断上下文 :
- 避免直接调用复杂 LVGL API,若确有需要(如发起少量无阻塞操作),用
lv_lock_isr/lv_thread_sync_signal_isr等 ISR 版本接口。
- 避免直接调用复杂 LVGL API,若确有需要(如发起少量无阻塞操作),用
4.3 自定义 OS 后端的实现步骤
- 创建
lv_myos.h/lv_myos.c:- 定义
lv_thread_t/lv_mutex_t/lv_thread_sync_t; - 实现
lv_thread_init/delete、lv_mutex_*、lv_thread_sync_*、lv_os_get_idle_percent等接口。
- 定义
- 在配置中设置:
c
#define LV_USE_OS LV_OS_CUSTOM
#define LV_OS_CUSTOM_INCLUDE "lv_myos.h"
- 在工程构建脚本中把
lv_myos.c编进 LVGL 库或项目。
5. 设计优势与可能的局限
5.1 优势
-
统一抽象 :
一套 API 支持多种 OS,应用和 LVGL 内部逻辑不再直接依赖具体 OS 的 API;
-
对无 OS 场景友好 :
无 OS 时所有 OS 接口 inline 空实现,几乎零成本;
-
扩展性好 :
自定义 OS 只需实现抽象接口,无需修改 LVGL 内核代码;
-
与性能监控良好集成 :
idle 统计、任务切换 hook 等通过 OSAL 预留接口,sysmon 模块可以跨平台展示 CPU/FPS 等指标。
5.2 局限与注意点
-
粒度较粗的线程安全模型 :
全局互斥锁简化了并发控制,但也意味着"LVGL 内部基本是串行"的,无法同时在多个线程中高并发执行复杂 UI 操作;
-
移植者需要理解 OS 细节 :
尤其是
lv_thread_sync_*在 FreeRTOS/其他 OS 中的实现,需要仔细处理竞态、唤醒顺序,否则容易埋下死锁或"永不唤醒"的坑; -
idle 统计精度依赖底层实现 :
若 OS/移植层未正确实现 idle 统计,sysmon 显示的 CPU/FPS 可能会偏差,需要联动调试。
-
没有统一的信号量抽象 :
当前 OS 抽象层只公开互斥锁和具备"条件变量语义"的同步对象(
lv_thread_sync_*),并没有单独的lv_semaphore_*一类计数信号量接口;在需要用到信号量语义(如资源计数、生产者-消费者限流)时,通常需要直接使用各自 OS 的原生信号量 API,或者在项目层基于现有 OSAL 设计一层lv_semaphore_t + lv_semaphore_*的扩展封装,由各后端仿照线程/互斥锁的方式去实现。
5.3 与通用 OS 抽象层的差异
在很多通用 OS 抽象层或平台抽象层(PAL)中,通常会把"系统能力"一股脑收进来,比如:
- 线程与同步(互斥、条件变量、事件组、信号量等);
- 软件定时器与时钟接口;
- 消息队列、mailbox;
- 文件系统、网络 socket、标准 IO;
- 看门狗、电源管理、中断控制等。
与这些"全功能 OSAL"相比,LVGL 的 OS 抽象层刻意做了非常明显的取舍:
- 只抽象与 GUI 内核强相关的部分:线程、互斥、简单同步、sleep、idle 统计;
- 不在
osal里抽象文件系统/网络/定时器/内存等 ,而是分别交给:lv_timer与lv_tick做统一软件定时;lv_fs抽象不同文件系统;lv_mem/lv_mem_buf管理 LVGL 内部的堆与临时缓冲;- 看门狗、电源管理、中断分发则完全留给 BSP / 应用层。
这种设计的直接结果是:
- 对 GUI 使用者来说,OS 抽象层更轻、更聚焦,不强行接管系统里所有 OS 能力;
- LVGL 可以在"完全无 OS"的芯片上保持极小体积,同时在复杂 OS 上又有统一的并发模型;
- 若项目本身已经有一套更大的 OSAL/PAL,可以把 LVGL 的 OS 抽象层视作其中一个子模块,二者并存,而不是相互替代。
5.4 OS 抽象层 API 速查表
下表按功能模块归纳了 OS 抽象层中对外可见的主要接口,方便在移植或查阅时快速定位。
| 功能模块 | 主要 API | 作用简述 |
|---|---|---|
| 全局锁与线程安全 | lv_lock() / lv_unlock() |
加/解 LVGL 全局互斥锁,保证同一时刻仅有一个线程在执行 LVGL 内核代码 |
lv_lock_isr() |
中断上下文可用的全局锁版本,内部调用 lv_mutex_lock_isr |
|
| 线程管理 | lv_thread_init() |
创建新线程/任务,接受线程名、优先级、栈大小、入口函数和用户数据等参数 |
lv_thread_delete() |
删除已创建的线程/任务,释放其 OS 资源 | |
| 互斥锁 | lv_mutex_init() / lv_mutex_delete() |
创建/销毁互斥锁对象 |
lv_mutex_lock() / lv_mutex_unlock() |
在普通上下文加锁/解锁互斥锁 | |
lv_mutex_lock_isr() |
在中断上下文尝试加锁互斥锁,返回 lv_result_t 表示结果 |
|
| 线程同步(条件变量语义) | lv_thread_sync_init() / lv_thread_sync_delete() |
创建/销毁线程同步对象,用于一对一或一对多的"等待-唤醒"场景 |
lv_thread_sync_wait() |
在同步对象上阻塞等待,直到被其他线程或中断唤醒 | |
lv_thread_sync_signal() / lv_thread_sync_signal_isr() |
从普通上下文或中断上下文向等待者发送唤醒信号 | |
| sleep 与时间相关 | lv_sleep_ms() |
以毫秒为单位休眠当前执行上下文;在无 OS 场景下退化为 lv_delay_ms |
| idle 统计与性能监控 | lv_os_get_idle_percent() |
返回自上次调用以来的 CPU idle 百分比,供 lv_sysmon 计算 CPU 占用 |
lv_os_get_proc_idle_percent()(可选) |
若平台支持,返回当前进程级别的 idle 百分比 |
在具体 OS 后端中,还会实现若干内部使用的辅助函数和宏(如临界区、Task Notify 封装等),但上述表格覆盖了移植与应用层最常直接接触的一组接口。
6. 小结
library/lvgl/src/osal 为 LVGL9.4 提供了一层关键的 OS 抽象:
- 用统一接口覆盖线程、互斥、同步、sleep、idle 统计等功能;
- 通过编译期选择后端实现多 OS 适配;
- 在无 OS 环境下保持最小开销。
在实际项目中,只要正确配置 LV_USE_OS,并在多线程场景中遵循 "所有 LVGL API 调用都包在全局锁内 " 的基本规则,就可以在大多数平台上安全、高效地运行 LVGL。
更高级的优化(如与 OS 定时器/事件系统更深度的融合)则可以在自定义 OS 后端中进一步探索。
7. 附录
A. 参考文档(外部)
B. 相关资源(外部)
- 【奶茶Beta专项】【LVGL9.4源码分析】01-目录结构
- 【奶茶Beta专项】【LVGL9.4源码分析】02-编译框架-Cmake详解
- 【奶茶Beta专项】【LVGL9.4源码分析】03-显示框架-display
- 【奶茶Beta专项】【LVGL9.4源码分析】03-显示框架-分辨率管理
- 【奶茶Beta专项】【LVGL9.4源码分析】03-显示框架-画布缓冲管理
- 【奶茶Beta专项】【LVGL9.4源码分析】03-显示框架-局部刷新和脏区计算规则
- 【奶茶Beta专项】【LVGL9.4源码分析】03-显示框架-图层管理
- 【奶茶Beta专项】【LVGL9.4源码分析】03-显示框架-旋转方案
- 【奶茶Beta专项】【LVGL9.4源码分析】03-显示框架-主题管理
- 【奶茶Beta专项】【LVGL9.4源码分析】03-显示框架-内存和性能调试
C. 本文相关代码改动 Patch(OS 抽象层信号量扩展)
diff
diff --git a/src/osal/lv_cmsis_rtos2.c b/src/osal/lv_cmsis_rtos2.c
index 7eb898315..7d2460ba9 100644
--- a/src/osal/lv_cmsis_rtos2.c
+++ b/src/osal/lv_cmsis_rtos2.c
@@ -191,6 +191,62 @@ lv_result_t lv_thread_sync_delete(lv_thread_sync_t * sync)
return LV_RESULT_OK;
}
+lv_result_t lv_semaphore_init(lv_semaphore_t * semaphore, uint32_t initial_count,
+ uint32_t max_count)
+{
+ const osSemaphoreAttr_t attr = {
+ .name = "LVGLSemaphore",
+ };
+
+ *semaphore = osSemaphoreNew(max_count, initial_count, &attr);
+ if(*semaphore == NULL) {
+ LV_LOG_WARN("Error: failed to create cmsis-rtos2 semaphore");
+ return LV_RESULT_INVALID;
+ }
+
+ return LV_RESULT_OK;
+}
+
+lv_result_t lv_semaphore_take(lv_semaphore_t * semaphore, uint32_t timeout_ms)
+{
+ uint32_t timeout = (timeout_ms == 0U) ? 0U : timeout_ms;
+ osStatus_t status = osSemaphoreAcquire(*semaphore, timeout);
+ if(status != osOK) {
+ LV_LOG_WARN("Error: failed to acquire cmsis-rtos2 semaphore %d", (int)status);
+ return LV_RESULT_INVALID;
+ }
+
+ return LV_RESULT_OK;
+}
+
+lv_result_t lv_semaphore_give(lv_semaphore_t * semaphore)
+{
+ osStatus_t status = osSemaphoreRelease(*semaphore);
+ if(status != osOK) {
+ LV_LOG_WARN("Error: failed to release cmsis-rtos2 semaphore %d", (int)status);
+ return LV_RESULT_INVALID;
+ }
+
+ return LV_RESULT_OK;
+}
+
+lv_result_t lv_semaphore_give_isr(lv_semaphore_t * semaphore)
+{
+ /* CMSIS-RTOS2 不区分普通/ISR 版本的 Release,这里直接复用 */
+ return lv_semaphore_give(semaphore);
+}
+
+lv_result_t lv_semaphore_delete(lv_semaphore_t * semaphore)
+{
+ osStatus_t status = osSemaphoreDelete(*semaphore);
+ if(status != osOK) {
+ LV_LOG_WARN("Error: failed to delete cmsis-rtos2 semaphore %d", (int)status);
+ return LV_RESULT_INVALID;
+ }
+
+ return LV_RESULT_OK;
+}
+
uint32_t lv_os_get_idle_percent(void)
{
return lv_timer_get_idle();
diff --git a/src/osal/lv_cmsis_rtos2.h b/src/osal/lv_cmsis_rtos2.h
index ae4f94f0a..c91130459 100644
--- a/src/osal/lv_cmsis_rtos2.h
+++ b/src/osal/lv_cmsis_rtos2.h
@@ -36,6 +36,8 @@ typedef osMutexId_t lv_mutex_t;
typedef osEventFlagsId_t lv_thread_sync_t;
+typedef osSemaphoreId_t lv_semaphore_t;
+
/**********************
* GLOBAL PROTOTYPES
**********************/
diff --git a/src/osal/lv_freertos.c b/src/osal/lv_freertos.c
index a8545c100..b3c69d9cd 100644
--- a/src/osal/lv_freertos.c
+++ b/src/osal/lv_freertos.c
@@ -387,6 +387,79 @@ lv_result_t lv_thread_sync_signal_isr(lv_thread_sync_t * pxCond)
return LV_RESULT_OK;
}
+lv_result_t lv_semaphore_init(lv_semaphore_t * pxSemaphore, uint32_t initial_count,
+ uint32_t max_count)
+{
+ if(pxSemaphore->xIsInitialized == pdTRUE) return LV_RESULT_OK;
+
+ pxSemaphore->xSemaphore = xSemaphoreCreateCounting(max_count, initial_count);
+ if(pxSemaphore->xSemaphore == NULL) {
+ LV_LOG_ERROR("xSemaphoreCreateCounting failed!");
+ return LV_RESULT_INVALID;
+ }
+
+ pxSemaphore->xIsInitialized = pdTRUE;
+ return LV_RESULT_OK;
+}
+
+lv_result_t lv_semaphore_take(lv_semaphore_t * pxSemaphore, uint32_t timeout_ms)
+{
+ if(pxSemaphore->xIsInitialized == pdFALSE) {
+ LV_LOG_ERROR("semaphore not initialized");
+ return LV_RESULT_INVALID;
+ }
+
+ TickType_t ticks = (timeout_ms == 0U) ? 0U : pdMS_TO_TICKS(timeout_ms);
+ BaseType_t ret = xSemaphoreTake(pxSemaphore->xSemaphore, ticks);
+ if(ret != pdTRUE) {
+ return LV_RESULT_INVALID;
+ }
+
+ return LV_RESULT_OK;
+}
+
+lv_result_t lv_semaphore_give(lv_semaphore_t * pxSemaphore)
+{
+ if(pxSemaphore->xIsInitialized == pdFALSE) {
+ LV_LOG_ERROR("semaphore not initialized");
+ return LV_RESULT_INVALID;
+ }
+
+ BaseType_t ret = xSemaphoreGive(pxSemaphore->xSemaphore);
+ if(ret != pdTRUE) {
+ LV_LOG_ERROR("xSemaphoreGive failed!");
+ return LV_RESULT_INVALID;
+ }
+
+ return LV_RESULT_OK;
+}
+
+lv_result_t lv_semaphore_give_isr(lv_semaphore_t * pxSemaphore)
+{
+ if(pxSemaphore->xIsInitialized == pdFALSE) {
+ return LV_RESULT_INVALID;
+ }
+
+ BaseType_t xHigherPriorityTaskWoken = pdFALSE;
+ BaseType_t ret = xSemaphoreGiveFromISR(pxSemaphore->xSemaphore, &xHigherPriorityTaskWoken);
+ if(ret != pdTRUE) {
+ return LV_RESULT_INVALID;
+ }
+
+ portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
+ return LV_RESULT_OK;
+}
+
+lv_result_t lv_semaphore_delete(lv_semaphore_t * pxSemaphore)
+{
+ if(pxSemaphore->xIsInitialized == pdFALSE) return LV_RESULT_INVALID;
+
+ vSemaphoreDelete(pxSemaphore->xSemaphore);
+ pxSemaphore->xIsInitialized = pdFALSE;
+
+ return LV_RESULT_OK;
+}
+
void lv_freertos_task_switch_in(const char * name)
{
diff --git a/src/osal/lv_freertos.h b/src/osal/lv_freertos.h
index 96196b743..cba469509 100644
--- a/src/osal/lv_freertos.h
+++ b/src/osal/lv_freertos.h
@@ -65,6 +65,11 @@ typedef struct {
#endif
} lv_thread_sync_t;
+typedef struct {
+ BaseType_t xIsInitialized; /**< Set to pdTRUE if this semaphore is initialized, pdFALSE otherwise. */
+ SemaphoreHandle_t xSemaphore; /**< FreeRTOS counting/binary semaphore. */
+} lv_semaphore_t;
+
/**********************
* GLOBAL PROTOTYPES
**********************/
diff --git a/src/osal/lv_mqx.c b/src/osal/lv_mqx.c
index 3b2442f4a..c82c9bf86 100644
--- a/src/osal/lv_mqx.c
+++ b/src/osal/lv_mqx.c
@@ -164,6 +164,68 @@ lv_result_t lv_thread_sync_signal_isr(lv_thread_sync_t * sync)
return LV_RESULT_INVALID;
}
+lv_result_t lv_semaphore_init(lv_semaphore_t * semaphore, uint32_t initial_count,
+ uint32_t max_count)
+{
+ LV_UNUSED(max_count);
+ if(MQX_OK != _lwsem_create(semaphore, (_mqx_int)initial_count)) {
+ LV_LOG_WARN("create semaphore failed");
+ return LV_RESULT_INVALID;
+ }
+
+ return LV_RESULT_OK;
+}
+
+lv_result_t lv_semaphore_take(lv_semaphore_t * semaphore, uint32_t timeout_ms)
+{
+ _mqx_uint ret;
+
+ if(timeout_ms == 0U) {
+ ret = _lwsem_poll(semaphore);
+ }
+ else {
+ /* MQX 只提供阻塞等待接口,这里使用阻塞等待。
+ * 如需严格超时控制,可在项目层封装定时等待逻辑。 */
+ LV_UNUSED(timeout_ms);
+ ret = _lwsem_wait(semaphore);
+ }
+
+ if(ret != MQX_OK) {
+ LV_LOG_WARN("Error: %x", ret);
+ return LV_RESULT_INVALID;
+ }
+
+ return LV_RESULT_OK;
+}
+
+lv_result_t lv_semaphore_give(lv_semaphore_t * semaphore)
+{
+ _mqx_uint ret = _lwsem_post(semaphore);
+ if(ret != MQX_OK) {
+ LV_LOG_WARN("Error: %x", ret);
+ return LV_RESULT_INVALID;
+ }
+
+ return LV_RESULT_OK;
+}
+
+lv_result_t lv_semaphore_give_isr(lv_semaphore_t * semaphore)
+{
+ /* MQX 支持在中断中调用 _lwsem_post,这里复用普通 give */
+ return lv_semaphore_give(semaphore);
+}
+
+lv_result_t lv_semaphore_delete(lv_semaphore_t * semaphore)
+{
+ _mqx_uint ret = _lwsem_destroy(semaphore);
+ if(ret != MQX_OK) {
+ LV_LOG_WARN("Error: %x", ret);
+ return LV_RESULT_INVALID;
+ }
+
+ return LV_RESULT_OK;
+}
+
uint32_t lv_os_get_idle_percent(void)
{
return lv_timer_get_idle();
diff --git a/src/osal/lv_mqx.h b/src/osal/lv_mqx.h
index 1ccd0091d..39c905475 100644
--- a/src/osal/lv_mqx.h
+++ b/src/osal/lv_mqx.h
@@ -34,6 +34,8 @@ typedef MUTEX_STRUCT lv_mutex_t;
typedef LWSEM_STRUCT lv_thread_sync_t;
+typedef LWSEM_STRUCT lv_semaphore_t;
+
/**********************
* GLOBAL PROTOTYPES
**********************/
diff --git a/src/osal/lv_os_none.h b/src/osal/lv_os_none.h
index e08a0dc42..67e15fc4d 100644
--- a/src/osal/lv_os_none.h
+++ b/src/osal/lv_os_none.h
@@ -25,6 +25,7 @@ extern "C" {
typedef int lv_mutex_t;
typedef int lv_thread_t;
typedef int lv_thread_sync_t;
+typedef int lv_semaphore_t;
/**********************
* GLOBAL PROTOTYPES
diff --git a/src/osal/lv_os_private.h b/src/osal/lv_os_private.h
index 029e90c25..b9814d218 100644
--- a/src/osal/lv_os_private.h
+++ b/src/osal/lv_os_private.h
@@ -81,7 +81,7 @@ uint32_t lv_os_get_idle_percent(void);
uint32_t lv_os_get_proc_idle_percent(void);
#endif
-
+
#if LV_USE_OS != LV_OS_NONE
/*----------------------------------------
@@ -180,6 +180,45 @@ lv_result_t lv_thread_sync_signal_isr(lv_thread_sync_t * sync);
*/
lv_result_t lv_thread_sync_delete(lv_thread_sync_t * sync);
+/**
+ * Create a semaphore
+ * @param semaphore a variable in which the semaphore will be stored
+ * @param initial_count initial count of the semaphore
+ * @param max_count maximum count of the semaphore
+ * @return LV_RESULT_OK: success; LV_RESULT_INVALID: failure
+ */
+lv_result_t lv_semaphore_init(lv_semaphore_t * semaphore, uint32_t initial_count,
+ uint32_t max_count);
+
+/**
+ * Take (decrement) a semaphore
+ * @param semaphore the semaphore to take
+ * @param timeout_ms timeout in milliseconds to wait; 0 means no wait
+ * @return LV_RESULT_OK: success; LV_RESULT_INVALID: failure or timeout
+ */
+lv_result_t lv_semaphore_take(lv_semaphore_t * semaphore, uint32_t timeout_ms);
+
+/**
+ * Give (increment) a semaphore
+ * @param semaphore the semaphore to give
+ * @return LV_RESULT_OK: success; LV_RESULT_INVALID: failure
+ */
+lv_result_t lv_semaphore_give(lv_semaphore_t * semaphore);
+
+/**
+ * Give (increment) a semaphore from interrupt context
+ * @param semaphore the semaphore to give
+ * @return LV_RESULT_OK: success; LV_RESULT_INVALID: failure
+ */
+lv_result_t lv_semaphore_give_isr(lv_semaphore_t * semaphore);
+
+/**
+ * Delete a semaphore
+ * @param semaphore the semaphore to delete
+ * @return LV_RESULT_OK: success; LV_RESULT_INVALID: failure
+ */
+lv_result_t lv_semaphore_delete(lv_semaphore_t * semaphore);
+
#else
/* Since compilation does not necessarily optimize cross-file empty functions well
@@ -266,6 +305,40 @@ static inline lv_result_t lv_thread_sync_delete(lv_thread_sync_t * sync)
return LV_RESULT_INVALID;
}
+static inline lv_result_t lv_semaphore_init(lv_semaphore_t * semaphore, uint32_t initial_count,
+ uint32_t max_count)
+{
+ LV_UNUSED(semaphore);
+ LV_UNUSED(initial_count);
+ LV_UNUSED(max_count);
+ return LV_RESULT_INVALID;
+}
+
+static inline lv_result_t lv_semaphore_take(lv_semaphore_t * semaphore, uint32_t timeout_ms)
+{
+ LV_UNUSED(semaphore);
+ LV_UNUSED(timeout_ms);
+ return LV_RESULT_INVALID;
+}
+
+static inline lv_result_t lv_semaphore_give(lv_semaphore_t * semaphore)
+{
+ LV_UNUSED(semaphore);
+ return LV_RESULT_INVALID;
+}
+
+static inline lv_result_t lv_semaphore_give_isr(lv_semaphore_t * semaphore)
+{
+ LV_UNUSED(semaphore);
+ return LV_RESULT_INVALID;
+}
+
+static inline lv_result_t lv_semaphore_delete(lv_semaphore_t * semaphore)
+{
+ LV_UNUSED(semaphore);
+ return LV_RESULT_INVALID;
+}
+
#endif /*LV_USE_OS != LV_OS_NONE*/
/**********************
diff --git a/src/osal/lv_pthread.c b/src/osal/lv_pthread.c
index 6bec2c77f..ba96f34d9 100644
--- a/src/osal/lv_pthread.c
+++ b/src/osal/lv_pthread.c
@@ -171,6 +171,69 @@ lv_result_t lv_thread_sync_signal_isr(lv_thread_sync_t * sync)
return LV_RESULT_INVALID;
}
+lv_result_t lv_semaphore_init(lv_semaphore_t * semaphore, uint32_t initial_count,
+ uint32_t max_count)
+{
+ LV_UNUSED(max_count);
+ int ret = sem_init(semaphore, 0, (unsigned int)initial_count);
+ if(ret != 0) {
+ LV_LOG_WARN("sem_init error: %d", ret);
+ return LV_RESULT_INVALID;
+ }
+
+ return LV_RESULT_OK;
+}
+
+lv_result_t lv_semaphore_take(lv_semaphore_t * semaphore, uint32_t timeout_ms)
+{
+ int ret;
+
+ if(timeout_ms == 0U) {
+ ret = sem_trywait(semaphore);
+ }
+ else {
+ /* For simplicity, block indefinitely when timeout_ms > 0.
+ * If strict timeout is required, sem_timedwait can be used here. */
+ LV_UNUSED(timeout_ms);
+ ret = sem_wait(semaphore);
+ }
+
+ if(ret != 0) {
+ LV_LOG_WARN("sem_wait error: %d", ret);
+ return LV_RESULT_INVALID;
+ }
+
+ return LV_RESULT_OK;
+}
+
+lv_result_t lv_semaphore_give(lv_semaphore_t * semaphore)
+{
+ int ret = sem_post(semaphore);
+ if(ret != 0) {
+ LV_LOG_WARN("sem_post error: %d", ret);
+ return LV_RESULT_INVALID;
+ }
+
+ return LV_RESULT_OK;
+}
+
+lv_result_t lv_semaphore_give_isr(lv_semaphore_t * semaphore)
+{
+ /* There is no dedicated ISR context in pthread; reuse normal give. */
+ return lv_semaphore_give(semaphore);
+}
+
+lv_result_t lv_semaphore_delete(lv_semaphore_t * semaphore)
+{
+ int ret = sem_destroy(semaphore);
+ if(ret != 0) {
+ LV_LOG_WARN("sem_destroy error: %d", ret);
+ return LV_RESULT_INVALID;
+ }
+
+ return LV_RESULT_OK;
+}
+
#ifndef __linux__
uint32_t lv_os_get_idle_percent(void)
{
diff --git a/src/osal/lv_pthread.h b/src/osal/lv_pthread.h
index b0c921587..d6118bd69 100644
--- a/src/osal/lv_pthread.h
+++ b/src/osal/lv_pthread.h
@@ -41,6 +41,8 @@ typedef struct {
bool v;
} lv_thread_sync_t;
+typedef sem_t lv_semaphore_t;
+
/**********************
* GLOBAL PROTOTYPES
**********************/
diff --git a/src/osal/lv_rtthread.c b/src/osal/lv_rtthread.c
index 9608ee24e..29e1dc5bd 100644
--- a/src/osal/lv_rtthread.c
+++ b/src/osal/lv_rtthread.c
@@ -185,6 +185,59 @@ lv_result_t lv_thread_sync_signal_isr(lv_thread_sync_t * sync)
return LV_RESULT_INVALID;
}
+lv_result_t lv_semaphore_init(lv_semaphore_t * semaphore, uint32_t initial_count,
+ uint32_t max_count)
+{
+ LV_UNUSED(max_count);
+ semaphore->sem = rt_sem_create("lvsem", initial_count, RT_IPC_FLAG_PRIO);
+ if(semaphore->sem == RT_NULL) {
+ LV_LOG_WARN("create semaphore failed");
+ return LV_RESULT_INVALID;
+ }
+
+ return LV_RESULT_OK;
+}
+
+lv_result_t lv_semaphore_take(lv_semaphore_t * semaphore, uint32_t timeout_ms)
+{
+ rt_int32_t timeout = (timeout_ms == 0U) ? 0 : rt_tick_from_millisecond(timeout_ms);
+ rt_err_t ret = rt_sem_take(semaphore->sem, timeout);
+ if(ret != RT_EOK) {
+ LV_LOG_WARN("Error: %d", ret);
+ return LV_RESULT_INVALID;
+ }
+
+ return LV_RESULT_OK;
+}
+
+lv_result_t lv_semaphore_give(lv_semaphore_t * semaphore)
+{
+ rt_err_t ret = rt_sem_release(semaphore->sem);
+ if(ret != RT_EOK) {
+ LV_LOG_WARN("Error: %d", ret);
+ return LV_RESULT_INVALID;
+ }
+
+ return LV_RESULT_OK;
+}
+
+lv_result_t lv_semaphore_give_isr(lv_semaphore_t * semaphore)
+{
+ /* RT-Thread semaphore API 可以在中断中使用,与普通 release 相同 */
+ return lv_semaphore_give(semaphore);
+}
+
+lv_result_t lv_semaphore_delete(lv_semaphore_t * semaphore)
+{
+ rt_err_t ret = rt_sem_delete(semaphore->sem);
+ if(ret != RT_EOK) {
+ LV_LOG_WARN("Error: %d", ret);
+ return LV_RESULT_INVALID;
+ }
+
+ return LV_RESULT_OK;
+}
+
uint32_t lv_os_get_idle_percent(void)
{
return lv_timer_get_idle();
diff --git a/src/osal/lv_rtthread.h b/src/osal/lv_rtthread.h
index 255a47afe..9205ed981 100644
--- a/src/osal/lv_rtthread.h
+++ b/src/osal/lv_rtthread.h
@@ -37,6 +37,10 @@ typedef struct {
rt_sem_t sem;
} lv_thread_sync_t;
+typedef struct {
+ rt_sem_t sem;
+} lv_semaphore_t;
+
/**********************
* GLOBAL PROTOTYPES
**********************/
diff --git a/src/osal/lv_sdl2.c b/src/osal/lv_sdl2.c
index a5ba0bedb..d95b9f0cd 100644
--- a/src/osal/lv_sdl2.c
+++ b/src/osal/lv_sdl2.c
@@ -173,6 +173,55 @@ lv_result_t lv_thread_sync_signal_isr(lv_thread_sync_t * sync)
return LV_RESULT_INVALID;
}
+lv_result_t lv_semaphore_init(lv_semaphore_t * semaphore, uint32_t initial_count,
+ uint32_t max_count)
+{
+ LV_UNUSED(max_count);
+ *semaphore = SDL_CreateSemaphore(initial_count);
+ if(*semaphore == NULL) {
+ LV_LOG_ERROR("Error: %s", SDL_GetError());
+ return LV_RESULT_INVALID;
+ }
+
+ return LV_RESULT_OK;
+}
+
+lv_result_t lv_semaphore_take(lv_semaphore_t * semaphore, uint32_t timeout_ms)
+{
+ Uint32 timeout = (timeout_ms == 0U) ? 0U : timeout_ms;
+ int ret = SDL_SemWaitTimeout(*semaphore, timeout);
+ if(ret != 0) {
+ /* SDL returns SDL_MUTEX_TIMEDOUT or error; 都视为失败 */
+ LV_LOG_ERROR("Error: %d", ret);
+ return LV_RESULT_INVALID;
+ }
+
+ return LV_RESULT_OK;
+}
+
+lv_result_t lv_semaphore_give(lv_semaphore_t * semaphore)
+{
+ int ret = SDL_SemPost(*semaphore);
+ if(ret != 0) {
+ LV_LOG_ERROR("Error: %d", ret);
+ return LV_RESULT_INVALID;
+ }
+
+ return LV_RESULT_OK;
+}
+
+lv_result_t lv_semaphore_give_isr(lv_semaphore_t * semaphore)
+{
+ /* SDL 没有中断上下文概念,直接复用普通 give */
+ return lv_semaphore_give(semaphore);
+}
+
+lv_result_t lv_semaphore_delete(lv_semaphore_t * semaphore)
+{
+ SDL_DestroySemaphore(*semaphore);
+ return LV_RESULT_OK;
+}
+
#ifndef __linux__
uint32_t lv_os_get_idle_percent(void)
{
diff --git a/src/osal/lv_sdl2.h b/src/osal/lv_sdl2.h
index 69eff5851..15c5cc22a 100644
--- a/src/osal/lv_sdl2.h
+++ b/src/osal/lv_sdl2.h
@@ -38,6 +38,8 @@ typedef struct {
bool v;
} lv_thread_sync_t;
+typedef SDL_sem * lv_semaphore_t;
+
/**********************
* GLOBAL PROTOTYPES
**********************/
diff --git a/src/osal/lv_windows.c b/src/osal/lv_windows.c
index c28bc0797..694f68393 100644
--- a/src/osal/lv_windows.c
+++ b/src/osal/lv_windows.c
@@ -207,6 +207,53 @@ lv_result_t lv_thread_sync_signal_isr(lv_thread_sync_t * sync)
return LV_RESULT_INVALID;
}
+lv_result_t lv_semaphore_init(lv_semaphore_t * semaphore, uint32_t initial_count,
+ uint32_t max_count)
+{
+ *semaphore = CreateSemaphore(NULL, (LONG)initial_count, (LONG)max_count, NULL);
+ if(!*semaphore) {
+ return LV_RESULT_INVALID;
+ }
+
+ return LV_RESULT_OK;
+}
+
+lv_result_t lv_semaphore_take(lv_semaphore_t * semaphore, uint32_t timeout_ms)
+{
+ DWORD timeout = (timeout_ms == 0U) ? 0U : timeout_ms;
+ DWORD res = WaitForSingleObject(*semaphore, timeout);
+
+ if(res == WAIT_OBJECT_0) {
+ return LV_RESULT_OK;
+ }
+
+ return LV_RESULT_INVALID;
+}
+
+lv_result_t lv_semaphore_give(lv_semaphore_t * semaphore)
+{
+ if(!ReleaseSemaphore(*semaphore, 1, NULL)) {
+ return LV_RESULT_INVALID;
+ }
+
+ return LV_RESULT_OK;
+}
+
+lv_result_t lv_semaphore_give_isr(lv_semaphore_t * semaphore)
+{
+ /* Windows 没有严格意义上的 ISR,这里复用普通 give */
+ return lv_semaphore_give(semaphore);
+}
+
+lv_result_t lv_semaphore_delete(lv_semaphore_t * semaphore)
+{
+ if(!CloseHandle(*semaphore)) {
+ return LV_RESULT_INVALID;
+ }
+
+ return LV_RESULT_OK;
+}
+
uint32_t lv_os_get_idle_percent(void)
{
return lv_timer_get_idle();
diff --git a/src/osal/lv_windows.h b/src/osal/lv_windows.h
index 9d864535d..edb071899 100644
--- a/src/osal/lv_windows.h
+++ b/src/osal/lv_windows.h
@@ -37,6 +37,8 @@ typedef struct {
bool v;
} lv_thread_sync_t;
+typedef HANDLE lv_semaphore_t;
+
/**********************
* GLOBAL PROTOTYPES
**********************/