第9章 消息队列、事件与软件定时器
这一章的主线是:LiteOS 中任务之间如何传递数据、如何等待多个事件,以及如何实现定时触发任务。
一、本章总体框架
第九章节可以分成三部分:
- 消息队列
- 解决“任务之间怎样传递数据”的问题;
- 重点是 FIFO、LIFO、阻塞读写、队列创建与读写函数。
- 事件
- 解决“任务怎样等待一个或多个事件发生”的问题;
- 重点是事件标志位、逻辑与、逻辑或、自动清除事件。
- 软件定时器
- 解决“怎样在指定时间后执行某个函数”的问题;
- 重点是单次模式、周期模式、回调函数、Tick 精度。
可以先这样记:
| 机制 | 主要作用 | 是否传数据 | 典型用途 |
|---|---|---|---|
| 消息队列 | 任务间传递消息 | 能传数据 | 按键任务发送消息给处理任务 |
| 事件 | 多任务同步 | 不传数据 | 等待 KEY1、KEY2 等事件 |
| 软件定时器 | 定时触发回调 | 不直接传数据 | 周期翻转 LED、定时采样 |
一句话概括:
消息队列传“数据”,事件传“状态”,软件定时器管“时间”。
二、消息队列是什么?
消息队列又称队列,是 LiteOS 中的一种任务间通信机制,可以在任务与任务之间、中断与任务之间传递消息。任务可以从队列中读取消息,也可以向队列中写入消息。队列中的消息为空时,读队列的任务可以进入阻塞态等待消息;队列满时,写队列的任务也可以根据超时时间进入阻塞态等待空闲空间。
可以把消息队列理解成一个“邮箱”:
- 发送任务把消息放进邮箱;
- 接收任务从邮箱里取消息;
- 邮箱空了,接收任务可以等待;
- 邮箱满了,发送任务可以等待。
三、消息队列的特点
LiteOS 消息队列有几个典型特点:
- 支持先进先出 FIFO
- 先写入的消息先被读取;
- 类似排队买票。
- 支持后进先出 LIFO
- 后写入的消息可以先被读取;
- 适合紧急消息优先处理。
- 支持异步读写
- 写消息的任务和读消息的任务不需要同时运行。
- 读写都支持超时机制
- 队列空时,读任务可以等待;
- 队列满时,写任务可以等待。
- 支持不同长度消息
- 只要不超过创建队列时指定的消息节点最大值即可。
- 多个任务可以读写同一个队列
- 一个任务可以从任意队列读写;
- 多个任务也可以操作同一个队列。
四、消息队列的传输机制
消息队列的基本传输方式是:
写消息 → 消息进入队列 → 读消息 → 消息出队
通常情况下是 FIFO:
消息1 → 消息2 → 消息3
读取顺序就是:
消息1 → 消息2 → 消息3
但 LiteOS 也支持 LIFO,用于紧急消息优先处理。例如某个报警消息后到,但比普通消息更重要,就可以让它优先被读取。
五、复制传递与引用传递
两种数据传输方式:
1. 复制传递
复制传递就是把数据内容复制一份放到队列中。
优点:
- 原数据被修改后,不影响队列中的数据;
- 数据更安全。
缺点:
- 占用更多内存;
- 大数据复制效率较低。
适合:
- 小数据;
- 对数据稳定性要求较高的场景。
2. 引用传递
引用传递就是把数据地址,也就是指针,传递给队列。
优点:
- 速度快;
- 占用内存少。
缺点:
- 如果源数据被修改,接收方看到的数据也会变化;
- 如果源数据生命周期结束,指针可能失效。
适合:
- 大数据;
- 缓冲区管理;
- 对效率要求较高的场景。
可以这样记:
复制传递更安全,引用传递更高效。
六、消息队列的阻塞机制
1. 出队阻塞
出队就是读队列。
如果任务读取队列时发现队列为空,有三种选择:
- 不等待
- 立即返回;
- 不进入阻塞态。
- 等待一段时间
- 例如等待 1000 个 Tick;
- 如果期间有消息到来,就读取;
- 如果超时还没有消息,就恢复就绪态。
- 永久等待
- 一直阻塞;
- 直到队列中有消息。
这对应实际开发中的三种策略:
0 // 不等待
1000 // 等待 1000 Tick
LOS_WAIT_FOREVER // 永久等待2. 入队阻塞
入队就是写队列。
如果任务写队列时发现队列已经满了,也可以选择:
- 不等待,立即返回失败;
- 等待一段时间;
- 永久等待,直到队列有空闲节点。
注意:
在中断中写队列不能使用阻塞等待,等待时间必须设为 0。
因为中断服务程序不能阻塞。
七、消息队列控制块
每个队列都有一个队列控制块,用来保存队列信息。
队列控制块包含:
- 队列指针;
- 队列状态;
- 队列长度;
- 消息节点大小;
- 队列 ID;
- 队列头指针;
- 队列尾指针;
- 可读/可写资源计数;
- 读写阻塞链表;
- 内存链表。
可以这样理解:
队列控制块就是消息队列的“管理表”,LiteOS 通过它知道队列在哪里、还有多少消息、还能不能写、有没有任务在等。
八、消息队列常用函数
1. 创建消息队列:LOS_QueueCreate
函数原型:
extern UINT32 LOS_QueueCreate(
CHAR *pcQueueName,
UINT16 usLen,
UINT32 *puwQueueID,
UINT32 uwFlags,
UINT16 usMaxMsgSize
);参数含义:
| 参数 | 含义 |
|---|---|
pcQueueName |
队列名称,保留参数,暂未使用 |
usLen |
队列长度,也就是最多能存几个消息 |
puwQueueID |
队列 ID 指针,创建成功后返回队列 ID |
uwFlags |
队列模式,保留参数 |
usMaxMsgSize |
单个消息节点最大大小,单位字节 |
例子:
UINT32 Test_Queue_Handle;
LOS_QueueCreate(
"Test_Queue",
128,
&Test_Queue_Handle,
0,
16
);含义是:
创建一个队列,最多存 128 条消息,每条消息最大 16 字节。
创建队列时,队列长度和消息节点大小已经确定,后续不能再改。队列创建成功后会占用相应内存,只有删除队列后才释放。
2. 写消息队列:LOS_QueueWrite
常用形式:
LOS_QueueWrite(
Test_Queue_Handle,
&send_data,
sizeof(send_data),
0
);含义:
- 向
Test_Queue_Handle对应的队列写入消息; - 消息内容是
send_data; - 消息长度是
sizeof(send_data); - 等待时间为 0,表示不阻塞。
例如,按下 KEY1 或 KEY2 后,会向队列写入不同消息,接收任务再从队列中读取并处理。
3. 读消息队列:LOS_QueueRead
常用形式:
LOS_QueueRead(
Test_Queue_Handle,
&read_buffer,
buffer_size,
LOS_WAIT_FOREVER
);含义:
- 从队列中读取消息;
- 如果队列为空,则一直等待;
- 有消息后读取到
read_buffer中。
4. 删除消息队列:LOS_QueueDelete
函数:
LOS_QueueDelete(Test_Queue_Handle);注意:
如果队列中还有任务阻塞等待读或写,或者队列正在使用,不能删除。
如果有任务等待队列消息、等待写入队列,或者队列读写不同步,队列无法删除;删除成功后,队列控制块会回到空闲列表,队列内存会释放。
九、消息队列常见错误
常见错误包括:
| 错误代码 | 含义 |
|---|---|
LOS_ERRNO_QUEUE_CREATE_NO_MEMORY |
创建队列内存不足 |
LOS_ERRNO_QUEUE_SIZE_TOO_BIG |
消息节点大小超过上限 |
LOS_ERRNO_QUEUE_NOT_FOUND |
队列 ID 无效 |
LOS_ERRNO_QUEUE_TIMEOUT |
等待队列超时 |
LOS_ERRNO_QUEUE_ISFULL |
队列满,无法写入 |
LOS_ERRNO_QUEUE_ISEMPTY |
队列空,无法读取 |
LOS_ERRNO_QUEUE_READ_IN_INTERRUPT |
中断中不能阻塞读队列 |
LOS_ERRNO_QUEUE_WRITE_IN_INTERRUPT |
中断中不能阻塞写队列 |
LOS_ERRNO_QUEUE_READ_PTR_NULL |
读队列传入空指针 |
LOS_ERRNO_QUEUE_WRITE_PTR_NULL |
写队列传入空指针 |
最重要的是:
中断中读写队列时不能阻塞,等待时间要设为 0。
十、事件是什么?
事件是一种任务间通信机制,主要用于任务同步。
但事件和消息队列不同:
事件只能表示“某件事发生了”,不能传递具体数据。
事件可以实现一对多、多对多同步。一个任务可以等待多个事件发生,可以是任意一个事件发生就唤醒任务,也可以是多个事件都发生后再唤醒任务。
例如:
- KEY1 被按下;
- KEY2 被按下;
- 串口接收完成;
- 网络连接成功;
- 传感器数据准备好。
这些都可以作为事件。
十一、事件标志组
LiteOS 使用一个 32 位变量 uwEventID 来保存事件集合。
每一位代表一种事件:
bit0 → 事件0
bit1 → 事件1
bit2 → 事件2
...
如果某一位是 0,表示该事件没有发生; 如果某一位是 1,表示该事件已经发生。
例如:
#define KEY1_EVENT (0x01 << 0)
#define KEY2_EVENT (0x01 << 1)表示:
KEY1_EVENT对应 bit0;KEY2_EVENT对应 bit1。
注:第 25 位是 LiteOS 保留位,不能设置为 1。事件没有计数性,也就是说,如果同一个事件连续写入多次,但任务还没读取,效果等同于只写入一次。
十二、事件的特点
事件有以下特点:
- 事件相互独立
- 每一位表示一种事件类型。
- 不传递数据
- 只表示事件是否发生。
- 无计数性
- 多次设置同一事件,效果相当于设置一次。
- 允许多个任务读写同一事件
- 多任务可以共享事件标志组。
- 支持超时等待
- 等不到事件时可以超时返回。
- 支持逻辑或和逻辑与
- 可以等待任意事件;
- 也可以等待多个事件全部发生。
十三、事件的逻辑或与逻辑与
1. 逻辑或:LOS_WAITMODE_OR
逻辑或的意思是:
等待的多个事件中,只要任意一个发生,任务就被唤醒。
例如任务等待:
KEY1_EVENT | KEY2_EVENT模式为:
LOS_WAITMODE_OR那么只要 KEY1 或 KEY2 任意一个按下,任务就会被唤醒。
这叫独立型同步。
2. 逻辑与:LOS_WAITMODE_AND
逻辑与的意思是:
等待的多个事件必须全部发生,任务才被唤醒。
例如任务等待:
KEY1_EVENT | KEY2_EVENT模式为:
LOS_WAITMODE_AND那么必须 KEY1 和 KEY2 都发生,任务才会被唤醒。
这叫关联型同步。
一个任务等待事件 3 或事件 5 时使用 OR;另一个任务等待事件 3 和事件 5 都发生时使用 AND。
3. 自动清除:LOS_WAITMODE_CLR
如果读取模式中加上:
LOS_WAITMODE_CLR表示读取成功后,自动清除对应事件位。
例如:
LOS_WAITMODE_AND | LOS_WAITMODE_CLR表示:
等待所有事件都发生,读取成功后自动清除这些事件位。
如果没有设置 LOS_WAITMODE_CLR,就需要用 LOS_EventClear() 手动清除。
十四、事件控制块
事件控制块结构如下:
typedef struct tagEvent {
UINT32 uwEventID;
LOS_DL_LIST stEventList;
} EVENT_CB_S;含义:
| 成员 | 含义 |
|---|---|
uwEventID |
32 位事件集合,每一位表示一个事件 |
stEventList |
事件阻塞列表,记录等待事件的任务 |
可以理解为:
事件控制块就是事件标志组的“管理表”。
十五、事件常用函数
1. 初始化事件:LOS_EventInit
使用前要先定义事件控制块:
static EVENT_CB_S EventGroup_CB;然后初始化:
LOS_EventInit(&EventGroup_CB);初始化时,uwEventID 被置为 0,表示所有事件都没有发生,并初始化事件阻塞链表。
2. 写事件:LOS_EventWrite
写事件就是把某个事件位设置为 1。
例如:
LOS_EventWrite(&EventGroup_CB, KEY1_EVENT);表示 KEY1 事件发生。
例如:按键任务
if (KEY1 被按下)
{
LOS_EventWrite(&EventGroup_CB, KEY1_EVENT);
}
if (KEY2 被按下)
{
LOS_EventWrite(&EventGroup_CB, KEY2_EVENT);
}写事件后,LiteOS 会遍历阻塞在事件列表中的任务,如果写入的事件满足任务的唤醒条件,就把任务从阻塞态恢复为就绪态,并可能触发调度。
3. 读事件:LOS_EventRead
函数形式:
LOS_EventRead(
&EventGroup_CB,
KEY1_EVENT | KEY2_EVENT,
LOS_WAITMODE_AND | LOS_WAITMODE_CLR,
LOS_WAIT_FOREVER
);含义:
- 等待 KEY1 和 KEY2 两个事件;
- 必须两个都发生;
- 读取成功后自动清除;
- 一直等待,直到事件满足。
例如:LED 任务等待 KEY1 和 KEY2 都按下,满足后翻转 LED。
4. 清除事件:LOS_EventClear
如果读取时没有使用 LOS_WAITMODE_CLR,就需要手动清除:
LOS_EventClear(&EventGroup_CB, ~KEY1_EVENT);注意这里通常要对事件掩码取反,因为源码中使用的是按位与方式清除指定事件位。
5. 销毁事件:LOS_EventDestory
如果事件不再使用,可以销毁事件控制块:
LOS_EventDestory(&EventGroup_CB);销毁后事件链表指针被置空。
十六、事件常见错误
| 错误代码 | 含义 |
|---|---|
LOS_ERRNO_EVENT_PTR_NULL |
事件控制块指针为空 |
LOS_ERRNO_EVENT_SETBIT_INVALID |
设置了保留位,第 25 位不能置 1 |
LOS_ERRNO_EVENT_EVENTMASK_INVALID |
事件掩码无效 |
LOS_ERRNO_EVENT_READ_IN_INTERRUPT |
中断中读取事件 |
LOS_ERRNO_EVENT_FLAGS_INVALID |
读取模式错误 |
LOS_ERRNO_EVENT_READ_IN_LOCK |
任务锁定时不能读取事件 |
LOS_ERRNO_EVENT_READ_TIMEOUT |
读取事件超时 |
重点记:
事件可以由任务或中断写入,但读取事件可能导致阻塞,所以不能在中断中读取事件。
十七、事件和信号量、消息队列的区别
| 机制 | 是否传数据 | 是否计数 | 是否能等多个条件 | 典型用途 |
|---|---|---|---|---|
| 信号量 | 不传具体数据 | 有计数 | 通常等单一信号 | 同步、资源计数 |
| 消息队列 | 传数据 | 队列中有多个消息 | 一般按队列读消息 | 任务间传递消息 |
| 事件 | 不传数据 | 无计数 | 能等多个事件 | 多条件同步 |
最关键的区别:
消息队列能传数据;事件不能传数据。 信号量有计数性;事件无计数性。 事件能同时等待多个条件;信号量通常对应一个同步信号。
十八、软件定时器是什么?
定时器是指从某个时刻开始,经过指定时间后触发一个超时事件。
定时器分为:
- 硬件定时器;
- 软件定时器。
1. 硬件定时器
硬件定时器由芯片外设提供,通常基于外部晶振或内部时钟,计数到设定值后触发中断。
特点:
- 精度高;
- 可达到较高时间分辨率;
- 通常通过中断处理;
- 数量受硬件资源限制。
2. 软件定时器
软件定时器由操作系统提供,建立在硬件定时器基础上。
特点:
- 不直接占用多个硬件定时器;
- 可以创建多个定时业务;
- 使用回调函数处理超时事件;
- 精度不如硬件定时器;
- 适合对时间精度要求不高的任务。
可以这样理解:
硬件定时器是真正的“硬件闹钟”,软件定时器是操作系统利用系统 Tick 模拟出来的“软件闹钟”。
十九、软件定时器的两种模式
LiteOS 软件定时器支持两种常用模式:
1. 单次模式
单次模式是:
定时时间到达后,只执行一次回调函数。
例如创建一个 5000 Tick 的单次定时器,启动后经过 5000 Tick 执行一次回调函数,然后结束。
课件中还提到单次模式执行完回调函数后,系统会自动删除该软件定时器并回收资源。
2. 周期模式
周期模式是:
每隔指定时间重复执行回调函数。
例如每 1000 Tick 翻转一次 LED。
周期模式会一直运行,直到用户停止或删除定时器。
二十、软件定时器的运行机制
LiteOS 软件定时器以 Tick 为基本计时单位。
运行过程如下:
- 用户创建软件定时器;
- 用户指定超时时间、工作模式和回调函数;
- 用户启动定时器;
- LiteOS 根据当前 Tick 和超时时间计算定时器到期时间;
- 将软件定时器插入定时器列表;
- Tick 到期后,软件定时器任务被唤醒;
- 软件定时器任务调用用户指定的回调函数。
LiteOS 通过软件定时器任务 osSwTmrTask 管理软件定时器,该任务优先级为 0,也就是最高优先级,并且是在内核初始化时自动创建的。软件定时器使用系统中的一个队列和一个任务资源,通过软件定时器命令队列处理软件定时器。
二十一、软件定时器和消息队列的关系
这是容易考的细节。
前面 LiteOS 初始化内容提到:
使用软件定时器时,必须启用消息队列功能。
也就是说,在 target_config.h 中需要确认:
LOSCFG_BASE_CORE_SWTMR = YES
LOSCFG_BASE_IPC_QUEUE = YES因为 LiteOS 软件定时器依赖一个软件定时器命令队列来管理启动、停止、删除等操作。
二十二、软件定时器状态
软件定时器有三种状态:
| 状态 | 含义 |
|---|---|
OS_SWTMR_STATUS_UNUSED |
未使用状态 |
OS_SWTMR_STATUS_CREATED |
已创建但未启动,或已停止 |
OS_SWTMR_STATUS_TICKING |
运行状态 |
状态变化可以这样理解:
未使用 → 创建 → 启动运行 → 停止 → 创建未启动状态
如果删除,则回到未使用状态。
二十三、软件定时器精度
软件定时器以系统 Tick 为单位。
例如系统配置:
LOSCFG_BASE_CORE_TICK_PER_SECOND = 1000表示 1 秒有 1000 个 Tick,那么 1 Tick 约等于 1ms。
如果把这个值调高:
- 定时精度更高;
- 但每秒进入 Tick 中断的次数更多;
- 系统开销更大。
所以:
Tick 频率越高,定时器精度越高,但系统开销也越大。
软件定时器可能被中断或调度影响,因此不适合纳秒级、微秒级高精度场景;如果需要高精度,应使用硬件定时器。
二十四、软件定时器使用注意事项
强调几个注意点:
- 回调函数执行时间要短
- 类似中断服务程序;
- 不要做耗时操作。
- 回调函数中不能调用导致阻塞的函数
- 例如不要调用
LOS_TaskDelay()。
- 例如不要调用
- 软件定时器占用一个队列和一个任务资源
- 软件定时器任务优先级为 0;
- 不允许修改该优先级。
- 单次模式执行完会自动删除
- 执行回调后系统回收资源。
- 适合精度要求不高的定时任务
- 不适合严格高精度场景。
二十五、软件定时器控制块
每个软件定时器都有对应控制块。
其中包含:
- 指向下一个定时器控制块的指针;
- 定时器状态;
- 定时器模式;
- 定时器 ID;
- 剩余计数值;
- 定时时间间隔;
- 回调函数参数;
- 回调函数。
可以这样理解:
软件定时器控制块就是软件定时器的“档案”,系统通过它知道定时器是否运行、多久超时、超时后执行哪个函数。
二十六、软件定时器常用函数
1. 创建软件定时器:LOS_SwtmrCreate
函数主要参数包括:
| 参数 | 含义 |
|---|---|
uwInterval |
定时时间,单位 Tick |
ucMode |
工作模式 |
pfnHandler |
回调函数 |
pusSwTmrID |
软件定时器 ID 指针 |
uwArg |
传给回调函数的参数 |
例子:
UINT16 Timer1_Handle;
LOS_SwtmrCreate(
5000,
LOS_SWTMR_MODE_ONCE,
(SWTMR_PROC_FUNC)Timer1_Callback,
&Timer1_Handle,
0
);含义:
创建一个 5000 Tick 后触发的单次软件定时器,超时后执行
Timer1_Callback。
软件定时器模式包括 LOS_SWTMR_MODE_ONCE、LOS_SWTMR_MODE_PERIOD、LOS_SWTMR_MODE_NO_SELFDELETE 等,其中常用的是单次模式和周期模式。
2. 编写回调函数
回调函数由用户实现,例如:
static void Timer1_Callback(UINT32 arg)
{
TmrCb_Count++;
LED1_TOGGLE;
printf("Timer callback\r\n");
}回调函数中不要写长时间阻塞代码,也不要调用 LOS_TaskDelay()。
3. 启动软件定时器:LOS_SwtmrStart
创建完成的软件定时器默认只是“已创建”,还没有运行。
需要调用:
LOS_SwtmrStart(Timer1_Handle);例如:创建了两个软件定时器,然后分别调用 LOS_SwtmrStart() 启动。
4. 停止软件定时器:LOS_SwtmrStop
如果不想让定时器继续运行,可以调用:
LOS_SwtmrStop(Timer1_Handle);停止后,定时器进入创建未启动/停止状态。
5. 删除软件定时器:LOS_SwtmrDelete
删除函数:
LOS_SwtmrDelete(Timer1_Handle);删除后,定时器资源被系统回收,不能继续执行回调函数。建议删除前应先停止软件定时器,再删除。
二十七、软件定时器常见错误
| 错误代码 | 含义 |
|---|---|
LOS_ERRNO_SWTMR_PTR_NULL |
回调函数为空 |
LOS_ERRNO_SWTMR_INTERVAL_NOT_SUITED |
定时间隔为 0 |
LOS_ERRNO_SWTMR_MODE_INVALID |
定时器模式错误 |
LOS_ERRNO_SWTMR_RET_PTR_NULL |
定时器 ID 指针为空 |
LOS_ERRNO_SWTMR_MAXSIZE |
软件定时器数量超过上限 |
LOS_ERRNO_SWTMR_ID_INVALID |
定时器 ID 无效 |
LOS_ERRNO_SWTMR_NOT_CREATED |
定时器未创建 |
LOS_ERRNO_SWTMR_NOT_STARTED |
定时器未启动 |
LOS_ERRNO_SWTMR_STATUS_INVALID |
定时器状态错误 |
LOS_ERRNO_SWTMR_TICK_PTR_NULL |
获取超时 Tick 数时指针为空 |
重点记:
创建软件定时器时,回调函数不能为空,定时间隔不能为 0,定时器 ID 指针不能为空。
二十八、消息队列、事件、软件定时器对比
这是第九章最适合考试的综合表。
| 对比项 | 消息队列 | 事件 | 软件定时器 |
|---|---|---|---|
| 核心作用 | 传递消息数据 | 多事件同步 | 定时触发 |
| 是否传数据 | 是 | 否 | 否,主要触发回调 |
| 是否有计数 | 有队列长度 | 无计数性 | 按 Tick 计时 |
| 是否可阻塞等待 | 读写都可阻塞 | 读事件可阻塞 | 不用于等待,超时回调 |
| 是否支持多个条件 | 主要按消息处理 | 支持 AND/OR | 不涉及 |
| 典型函数 | LOS_QueueCreate/Read/Write/Delete |
LOS_EventInit/Read/Write/Clear |
LOS_SwtmrCreate/Start/Stop/Delete |
| 典型应用 | 按键消息、传感器数据传递 | 多按键组合触发 | 周期采样、定时 LED 翻转 |
一句话记:
消息队列用于“传消息”,事件用于“等条件”,软件定时器用于“到时间执行”。
二十九、本章重点背诵版
1. 消息队列定义
消息队列是一种任务间通信机制,可以在任务与任务之间、中断与任务之间传送信息。任务可以向队列写入消息,也可以从队列读取消息。队列通常遵循 FIFO 原则,也支持 LIFO,读写队列均支持超时阻塞机制。
2. 消息队列特点
消息队列支持异步读写、FIFO 和 LIFO 排队方式、读写超时机制,允许多个任务从同一个队列中读取和写入消息。消息类型由通信双方约定,消息大小不能超过创建队列时指定的节点最大值。
3. 消息队列常用函数
LOS_QueueCreate(); // 创建消息队列
LOS_QueueWrite(); // 写消息队列
LOS_QueueRead(); // 读消息队列
LOS_QueueDelete(); // 删除消息队列4. 事件定义
事件是一种任务间通信机制,主要用于任务同步。事件不传输具体数据,而是通过事件集合中的位来表示不同事件是否发生。一个 32 位变量中的每一位可表示一个事件类型。
5. 事件特点
事件不提供数据传输功能,没有计数性,多次设置同一事件等效于设置一次。事件支持超时等待,允许多个任务读写同一事件,并支持逻辑或和逻辑与两种等待方式。
6. 事件常用函数
LOS_EventInit(); // 初始化事件
LOS_EventWrite(); // 写事件
LOS_EventRead(); // 读事件
LOS_EventClear(); // 清除事件
LOS_EventDestory(); // 销毁事件7. 事件读取模式
LOS_WAITMODE_OR // 任意一个事件发生即可唤醒
LOS_WAITMODE_AND // 所有等待事件都发生才唤醒
LOS_WAITMODE_CLR // 读取成功后自动清除事件位8. 软件定时器定义
软件定时器是操作系统提供的定时服务,建立在硬件定时器基础上,以系统 Tick 为计时单位。用户创建软件定时器时指定定时时间、工作模式和回调函数,定时时间到达后由系统调用回调函数。
9. 软件定时器模式
LiteOS 软件定时器常用模式包括单次模式和周期模式。单次模式在超时后只执行一次回调函数;周期模式按照指定时间间隔反复执行回调函数,直到用户停止或删除定时器。
10. 软件定时器常用函数
LOS_SwtmrCreate(); // 创建软件定时器
LOS_SwtmrStart(); // 启动软件定时器
LOS_SwtmrStop(); // 停止软件定时器
LOS_SwtmrDelete(); // 删除软件定时器三十、考试可能这样问
题目1:什么是消息队列?它有什么特点?
答:
消息队列是 LiteOS 中用于任务间通信的数据结构,可以在任务与任务之间、中断与任务之间传递消息。消息队列支持先进先出 FIFO,也支持后进先出 LIFO;支持异步读写和超时等待;多个任务可以从同一个队列中读写消息;写入消息的类型由通信双方约定,消息大小不能超过创建队列时指定的节点最大值。
题目2:消息队列的阻塞机制是什么?
答:
当任务读取队列时,如果队列为空,任务可以选择不等待、等待指定时间或永久等待;如果在等待时间内队列中有消息到来,任务被唤醒并读取消息,若超时仍没有消息,则任务恢复就绪态。当任务写队列时,如果队列已满,也可以根据超时时间等待空闲节点。中断中读写队列不能使用阻塞等待。
题目3:事件与消息队列有什么区别?
答:
消息队列可以传递具体数据,适合任务间数据通信;事件不传输数据,只通过事件标志位表示某个事件是否发生,适合任务间同步。消息队列中的消息具有排队和存储特性,而事件没有计数性,多次设置同一事件等效于设置一次。
题目4:事件的逻辑或和逻辑与是什么意思?
答:
事件逻辑或是指任务等待的多个事件中,只要任意一个事件发生,任务就被唤醒,对应 LOS_WAITMODE_OR。事件逻辑与是指任务等待的多个事件必须全部发生,任务才会被唤醒,对应 LOS_WAITMODE_AND。如果同时设置 LOS_WAITMODE_CLR,则读取成功后会自动清除对应事件位。
题目5:软件定时器和硬件定时器有什么区别?
答:
硬件定时器由芯片硬件提供,精度高,通常通过中断触发,但数量受硬件资源限制。软件定时器由操作系统提供,建立在硬件定时器和系统 Tick 基础上,可以创建多个定时业务,但精度不如硬件定时器,适合对时间精度要求不高的定时任务。
题目6:LiteOS 软件定时器有哪些工作模式?
答:
LiteOS 软件定时器主要有单次模式和周期模式。单次模式在定时时间到达后只执行一次回调函数;周期模式会按照指定时间间隔周期性执行回调函数,直到用户停止或删除该定时器。
题目7:使用软件定时器时应注意什么?
答:
软件定时器回调函数执行时间应尽量短,不允许调用会导致任务阻塞或挂起的函数,如 LOS_TaskDelay()。软件定时器依赖系统 Tick,精度低于硬件定时器。使用软件定时器需要开启软件定时器功能和消息队列功能,因为 LiteOS 软件定时器依赖软件定时器任务和命令队列实现。
题目8:消息队列、事件和软件定时器分别适合什么场景?
答:
消息队列适合任务之间传递具体数据,例如按键任务向处理任务发送按键值;事件适合任务之间或中断与任务之间进行状态同步,尤其适合等待多个事件组合触发;软件定时器适合周期性或延时性任务,例如周期翻转 LED、定时采样、定时上报状态等。
三十一、本章一句话总结
第九章节可以这样总结:
消息队列用于任务间传递具体数据,支持 FIFO/LIFO 和阻塞读写;事件用于多任务同步,通过 32 位事件标志组实现逻辑与、逻辑或等待,但不传数据且无计数性;软件定时器用于基于 Tick 的定时回调,支持单次和周期模式,适合精度要求不高的定时任务。