第9章 消息队列、事件与软件定时器

这一章的主线是:LiteOS 中任务之间如何传递数据、如何等待多个事件,以及如何实现定时触发任务。


一、本章总体框架

第九章节可以分成三部分:

  1. 消息队列
    • 解决“任务之间怎样传递数据”的问题;
    • 重点是 FIFO、LIFO、阻塞读写、队列创建与读写函数。
  2. 事件
    • 解决“任务怎样等待一个或多个事件发生”的问题;
    • 重点是事件标志位、逻辑与、逻辑或、自动清除事件。
  3. 软件定时器
    • 解决“怎样在指定时间后执行某个函数”的问题;
    • 重点是单次模式、周期模式、回调函数、Tick 精度。

可以先这样记:

机制 主要作用 是否传数据 典型用途
消息队列 任务间传递消息 能传数据 按键任务发送消息给处理任务
事件 多任务同步 不传数据 等待 KEY1、KEY2 等事件
软件定时器 定时触发回调 不直接传数据 周期翻转 LED、定时采样

一句话概括:

消息队列传“数据”,事件传“状态”,软件定时器管“时间”。


二、消息队列是什么?

消息队列又称队列,是 LiteOS 中的一种任务间通信机制,可以在任务与任务之间中断与任务之间传递消息。任务可以从队列中读取消息,也可以向队列中写入消息。队列中的消息为空时,读队列的任务可以进入阻塞态等待消息;队列满时,写队列的任务也可以根据超时时间进入阻塞态等待空闲空间。

可以把消息队列理解成一个“邮箱”:

  • 发送任务把消息放进邮箱;
  • 接收任务从邮箱里取消息;
  • 邮箱空了,接收任务可以等待;
  • 邮箱满了,发送任务可以等待。

三、消息队列的特点

LiteOS 消息队列有几个典型特点:

  1. 支持先进先出 FIFO
    • 先写入的消息先被读取;
    • 类似排队买票。
  2. 支持后进先出 LIFO
    • 后写入的消息可以先被读取;
    • 适合紧急消息优先处理。
  3. 支持异步读写
    • 写消息的任务和读消息的任务不需要同时运行。
  4. 读写都支持超时机制
    • 队列空时,读任务可以等待;
    • 队列满时,写任务可以等待。
  5. 支持不同长度消息
    • 只要不超过创建队列时指定的消息节点最大值即可。
  6. 多个任务可以读写同一个队列
    • 一个任务可以从任意队列读写;
    • 多个任务也可以操作同一个队列。

四、消息队列的传输机制

消息队列的基本传输方式是:

写消息 → 消息进入队列 → 读消息 → 消息出队

通常情况下是 FIFO:

消息1 → 消息2 → 消息3

读取顺序就是:

消息1 → 消息2 → 消息3

但 LiteOS 也支持 LIFO,用于紧急消息优先处理。例如某个报警消息后到,但比普通消息更重要,就可以让它优先被读取。


五、复制传递与引用传递

两种数据传输方式:

1. 复制传递

复制传递就是把数据内容复制一份放到队列中。

优点:

  • 原数据被修改后,不影响队列中的数据;
  • 数据更安全。

缺点:

  • 占用更多内存;
  • 大数据复制效率较低。

适合:

  • 小数据;
  • 对数据稳定性要求较高的场景。

2. 引用传递

引用传递就是把数据地址,也就是指针,传递给队列。

优点:

  • 速度快;
  • 占用内存少。

缺点:

  • 如果源数据被修改,接收方看到的数据也会变化;
  • 如果源数据生命周期结束,指针可能失效。

适合:

  • 大数据;
  • 缓冲区管理;
  • 对效率要求较高的场景。

可以这样记:

复制传递更安全,引用传递更高效。


六、消息队列的阻塞机制

1. 出队阻塞

出队就是读队列。

如果任务读取队列时发现队列为空,有三种选择:

  1. 不等待
    • 立即返回;
    • 不进入阻塞态。
  2. 等待一段时间
    • 例如等待 1000 个 Tick;
    • 如果期间有消息到来,就读取;
    • 如果超时还没有消息,就恢复就绪态。
  3. 永久等待
    • 一直阻塞;
    • 直到队列中有消息。

这对应实际开发中的三种策略:

0                // 不等待
1000             // 等待 1000 Tick
LOS_WAIT_FOREVER // 永久等待

2. 入队阻塞

入队就是写队列。

如果任务写队列时发现队列已经满了,也可以选择:

  1. 不等待,立即返回失败;
  2. 等待一段时间;
  3. 永久等待,直到队列有空闲节点。

注意:

在中断中写队列不能使用阻塞等待,等待时间必须设为 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. 事件相互独立
    • 每一位表示一种事件类型。
  2. 不传递数据
    • 只表示事件是否发生。
  3. 无计数性
    • 多次设置同一事件,效果相当于设置一次。
  4. 允许多个任务读写同一事件
    • 多任务可以共享事件标志组。
  5. 支持超时等待
    • 等不到事件时可以超时返回。
  6. 支持逻辑或和逻辑与
    • 可以等待任意事件;
    • 也可以等待多个事件全部发生。

十三、事件的逻辑或与逻辑与

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. 软件定时器。

1. 硬件定时器

硬件定时器由芯片外设提供,通常基于外部晶振或内部时钟,计数到设定值后触发中断。

特点:

  • 精度高;
  • 可达到较高时间分辨率;
  • 通常通过中断处理;
  • 数量受硬件资源限制。

2. 软件定时器

软件定时器由操作系统提供,建立在硬件定时器基础上。

特点:

  • 不直接占用多个硬件定时器;
  • 可以创建多个定时业务;
  • 使用回调函数处理超时事件;
  • 精度不如硬件定时器;
  • 适合对时间精度要求不高的任务。

可以这样理解:

硬件定时器是真正的“硬件闹钟”,软件定时器是操作系统利用系统 Tick 模拟出来的“软件闹钟”。


十九、软件定时器的两种模式

LiteOS 软件定时器支持两种常用模式:

1. 单次模式

单次模式是:

定时时间到达后,只执行一次回调函数。

例如创建一个 5000 Tick 的单次定时器,启动后经过 5000 Tick 执行一次回调函数,然后结束。

课件中还提到单次模式执行完回调函数后,系统会自动删除该软件定时器并回收资源。


2. 周期模式

周期模式是:

每隔指定时间重复执行回调函数。

例如每 1000 Tick 翻转一次 LED。

周期模式会一直运行,直到用户停止或删除定时器。


二十、软件定时器的运行机制

LiteOS 软件定时器以 Tick 为基本计时单位。

运行过程如下:

  1. 用户创建软件定时器;
  2. 用户指定超时时间、工作模式和回调函数;
  3. 用户启动定时器;
  4. LiteOS 根据当前 Tick 和超时时间计算定时器到期时间;
  5. 将软件定时器插入定时器列表;
  6. Tick 到期后,软件定时器任务被唤醒;
  7. 软件定时器任务调用用户指定的回调函数。

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 频率越高,定时器精度越高,但系统开销也越大。

软件定时器可能被中断或调度影响,因此不适合纳秒级、微秒级高精度场景;如果需要高精度,应使用硬件定时器。


二十四、软件定时器使用注意事项

强调几个注意点:

  1. 回调函数执行时间要短
    • 类似中断服务程序;
    • 不要做耗时操作。
  2. 回调函数中不能调用导致阻塞的函数
    • 例如不要调用 LOS_TaskDelay()
  3. 软件定时器占用一个队列和一个任务资源
    • 软件定时器任务优先级为 0;
    • 不允许修改该优先级。
  4. 单次模式执行完会自动删除
    • 执行回调后系统回收资源。
  5. 适合精度要求不高的定时任务
    • 不适合严格高精度场景。

二十五、软件定时器控制块

每个软件定时器都有对应控制块。

其中包含:

  • 指向下一个定时器控制块的指针;
  • 定时器状态;
  • 定时器模式;
  • 定时器 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_ONCELOS_SWTMR_MODE_PERIODLOS_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 的定时回调,支持单次和周期模式,适合精度要求不高的定时任务。