基于小智派-LZ3863星闪开发版:OpenHarmony 内核消息队列编程
1、实验简介
参考网址:https://gitee.com/Lockzhiner-Electronics/lz3863/tree/master/apps/a5_kernel_queue
1.1、实验目的
本实验旨在帮助学习者掌握 OpenHarmony 轻量系统中 消息队列(Message Queue) 的基本概念与编程方法。通过本实验,你将学会:
- 理解消息队列在任务间通信中的作用与 FIFO 特性;
- 使用 CMSIS-RTOS v2 标准接口创建、写入和读取消息队列;
- 通过消息队列实现生产者-消费者模式的多线程协作;
- 完成案例代码的编译、烧录与串口调试。
1.2、实验内容
本案例在 LZ3863-星闪开发板上创建 一个消息队列 和 两个协作线程:
| 线程名称 | 优先级 | 主要行为 |
|---|---|---|
msg_write_thread |
较高(Normal+1) | 每隔约 1 秒向队列写入一条递增的无符号整型消息 |
msg_read_thread |
普通(Normal) | 阻塞等待队列消息,获取成功后打印日志 |
msg_write_thread 作为 生产者 持续向队列投递消息;msg_read_thread 作为 消费者 从队列中取出消息。当队列为空时,读线程将阻塞等待,直到写线程写入新消息后才会被唤醒,从而直观展示消息队列在任务间传递数据与同步阻塞的效果。
1.3、实验环境
| 项目 | 说明 |
|---|---|
| 硬件 | LZ3863-星闪开发板、USB 数据线 |
| 软件 | OpenHarmony v5.1.0 源码、hb 编译工具 |
| 调试工具 | 串口助手(波特率 115200) |
| 案例路径 | applications/sample/wifi-iot/app/a5_kernel_queue/ |
2、基础知识
2.1、什么是消息队列
消息队列(Message Queue) 是一种内核提供的任务间通信机制,常用于:
- 任务间数据传递:一个任务将消息放入队列,另一个任务从队列中取出消息;
- 任务与中断间通信:中断服务程序可向队列投递消息,任务在合适时机读取处理;
- 异步解耦:发送方无需等待接收方立即处理,消息暂存在队列中。
消息队列可以存储 有限数量、固定大小 的数据项。创建队列时需要指定 单条消息的大小 和 队列长度(最多可存放的消息条数)。
2.2、FIFO 先进先出原则
当多个消息依次写入队列时,读取时按照 先进先出(FIFO) 的顺序取出:最先写入的消息最先被读出。这一特性保证了消息传递的顺序性,适用于事件通知、数据流传递等场景。
2.3、消息队列的工作原理
可以把消息队列想象成 带编号的信箱:
- 发送方将消息 放入(Put) 信箱,若信箱已满则阻塞或超时返回;
- 接收方从信箱 取出(Get) 消息,若信箱为空则阻塞或超时返回;
- 队列内部维护当前消息数量,发送与接收操作会相应增减计数;
- 阻塞时间可由用户通过
timeout参数自行设定,osWaitForever表示永久等待。
在本实验中,msg_write_thread 扮演 生产者 角色(写入消息),msg_read_thread 扮演 消费者 角色(读取消息),通过消息队列实现线程间的数据传递与同步。
2.4、消息队列与信号量的区别
| 对比项 | 消息队列 | 信号量 |
|---|---|---|
| 传递内容 | 实际数据(固定大小的消息体) | 仅传递同步信号(计数值/token) |
| 数据存储 | 队列内部缓存多条消息 | 不存储用户数据,仅维护计数值 |
| 典型用途 | 任务间传递结构化数据、事件通知 | 资源互斥、任务同步、流量控制 |
| 读取行为 | 取出消息同时拷贝数据到用户缓冲区 | 获取令牌,不传递具体数据内容 |
2.5、核心 API 介绍
2.5.1、头文件
#include <stdio.h>
#include "ohos_init.h"
#include "cmsis_os2.h"
2.5.2、常用消息队列 API
| API 名称 | 功能说明 |
|---|---|
osMessageQueueNew |
创建并初始化消息队列 |
osMessageQueuePut |
向队列写入 1 条消息,队列满时阻塞或超时返回 |
osMessageQueueGet |
从队列读取 1 条消息,队列空时阻塞或超时返回 |
osMessageQueueGetCount |
获取队列中当前消息数量 |
osMessageQueueGetSpace |
获取队列中剩余可存放的消息数量 |
osMessageQueueGetCapacity |
获取队列最大消息容量 |
osMessageQueueGetMsgSize |
获取单条消息的最大字节数 |
osMessageQueueGetName |
获取消息队列名称 |
osMessageQueueReset |
将队列重置为初始空状态 |
osMessageQueueDelete |
删除消息队列 |
2.5.3、osMessageQueueNew — 创建消息队列
osMessageQueueId_t osMessageQueueNew(uint32_t msg_count, uint32_t msg_size,
const osMessageQueueAttr_t *attr);
| 参数 | 说明 |
|---|---|
msg_count |
队列最多可存放的消息条数(队列长度) |
msg_size |
单条消息的字节大小 |
attr |
队列属性(名称等,可为 NULL) |
返回值:成功返回消息队列 ID;失败返回 NULL。
注意:不能在中断服务程序(ISR)中调用
osMessageQueueNew。
2.5.4、osMessageQueuePut — 写入消息
osStatus_t osMessageQueuePut(osMessageQueueId_t mq_id, const void *msg_ptr,
uint8_t msg_prio, uint32_t timeout);
| 参数 | 说明 |
|---|---|
mq_id |
消息队列 ID |
msg_ptr |
指向待写入消息数据的指针 |
msg_prio |
消息优先级(本实验设为 0,按 FIFO 顺序处理) |
timeout |
超时时间(tick 数),osWaitForever 表示永久等待 |
返回值:osOK 表示写入成功;队列已满且超时则返回 osErrorTimeout。
2.5.5、osMessageQueueGet — 读取消息
osStatus_t osMessageQueueGet(osMessageQueueId_t mq_id, void *msg_ptr,
uint8_t *msg_prio, uint32_t timeout);
| 参数 | 说明 |
|---|---|
mq_id |
消息队列 ID |
msg_ptr |
指向接收缓冲区的指针,用于存放读出的消息数据 |
msg_prio |
输出参数,存放读出消息的优先级(可为 NULL) |
timeout |
超时时间(tick 数),osWaitForever 表示永久等待 |
返回值:osOK 表示读取成功;队列为空且超时则返回 osErrorTimeout。
2.5.6、APP_FEATURE_INIT — 应用入口注册
APP_FEATURE_INIT(queue_example);
该宏将 queue_example 注册为应用特性初始化函数,系统启动完成后自动调用,无需手动修改 main() 函数。
3、程序设计
3.1、程序架构
本案例文件目录结构:
a5_kernel_queue/
├── kernel_queue_example.c # 主程序源码
├── BUILD.gn # GN 编译配置
├── README_zh.md # 案例简要说明
└── 实验手册.md # 本实验手册
本案例采用 生产者-消费者 协作结构:
系统启动
│
▼
queue_example() ← APP_FEATURE_INIT 注册,系统启动时自动执行
│
├── osMessageQueueNew() ← 创建消息队列(16 条 unsigned int 消息)
├── msg_write_thread() ← 生产者:每隔约 1 秒写入一条递增消息
└── msg_read_thread() ← 消费者:阻塞等待并读取队列消息
3.2、源文件说明
| 文件 | 说明 |
|---|---|
kernel_queue_example.c |
主程序源码,包含消息队列创建与读写线程的实现 |
BUILD.gn |
GN 编译配置文件,定义静态库 queue_example |
3.3、关键代码分析
(1)全局定义与队列句柄
#define MSG_QUEUE_LENGTH 16
static osMessageQueueId_t m_msg_queue;
MSG_QUEUE_LENGTH 定义队列最多存放 16 条消息,全局句柄 m_msg_queue 供读写两个线程共享使用。单条消息类型为 unsigned int(4 字节)。
(2)应用启动入口 — queue_example
系统启动后,通过 APP_FEATURE_INIT 宏自动调用此函数,创建消息队列和两个协作线程:
void queue_example(void)
{
osThreadAttr_t attr_write = {
.name = "msg_write_thread",
.stack_size = 2048,
.priority = osPriorityNormal + 1,
};
osThreadAttr_t attr_read = {
.name = "msg_read_thread",
.stack_size = 2048,
.priority = osPriorityNormal,
};
/* 创建消息队列:最多16个 unsigned int 消息 */
m_msg_queue = osMessageQueueNew(MSG_QUEUE_LENGTH, sizeof(unsigned int), NULL);
if (m_msg_queue == NULL) {
osal_printk("Failed to create Message Queue\n");
return;
}
/* 创建写线程 */
if (osThreadNew(msg_write_thread, NULL, &attr_write) == NULL) {
osal_printk("Failed to create msg_write_thread\n");
return;
}
/* 创建读线程 */
if (osThreadNew(msg_read_thread, NULL, &attr_read) == NULL) {
osal_printk("Failed to create msg_read_thread\n");
return;
}
}
写线程优先级略高于读线程(osPriorityNormal + 1),确保在有消息待处理时写操作能优先得到调度。
(3)生产者线程 — msg_write_thread
每隔约 1 秒向队列写入一条递增的无符号整型消息:
void msg_write_thread(void *arg)
{
(void)arg;
unsigned int data = 0;
while (1) {
data++;
if (osMessageQueuePut(m_msg_queue, &data, 0, osWaitForever) != osOK) {
osal_printk("%s write Message Queue msg fail\n", __func__);
} else {
osal_printk("%s write Message Queue msg:%u\n", __func__, data);
}
/* delay 1s */
osDelay(100);
}
}
data从 1 开始递增,每条消息携带当前计数值;osMessageQueuePut的timeout设为osWaitForever,队列满时永久阻塞等待;osDelay(100)延时约 1 秒(本系统 1 tick = 10 ms)。
(4)消费者线程 — msg_read_thread
持续阻塞等待队列消息,读取成功后打印日志:
void msg_read_thread(void *arg)
{
(void)arg;
unsigned int data;
while (1) {
/* wait for message */
if (osMessageQueueGet(m_msg_queue, &data, NULL, osWaitForever) == osOK) {
osal_printk("%s read Message Queue msg:%u\n", __func__, data);
} else {
osal_printk("%s read Message Queue fail\n", __func__);
}
}
}
osMessageQueueGet的timeout设为osWaitForever,队列为空时永久阻塞等待;- 读出的消息值存入局部变量
data,与写线程发送的值一一对应。
3.4、程序执行流程
3.5、协作行为说明
| 场景 | 写线程行为 | 读线程行为 |
|---|---|---|
| 队列为空 | 写入消息成功,打印 write ... msg:N |
此前阻塞等待,被唤醒后读出消息,打印 read ... msg:N |
| 写线程快于读线程 | 消息在队列中暂存,按 FIFO 顺序等待被读取 | 连续读取队列中积压的消息 |
| 读写速率一致(本实验) | 每 1 秒写入 1 条 | 几乎同时读出对应消息,编号一一匹配 |
| 队列已满(16 条消息) | osMessageQueuePut 阻塞等待 |
持续读取腾出空间后,写线程方可继续写入 |
本实验中写线程每 1 秒才写入 1 条消息,读线程无额外延时,因此队列中通常最多只有 0~1 条消息,输出呈现 先写后读、编号递增 的规律。
4、编译步骤
4.1、确认案例目录
确认案例已位于 OpenHarmony 源码目录下:
applications/sample/wifi-iot/app/a5_kernel_queue/
├── kernel_queue_example.c
├── BUILD.gn
└── 实验手册.md
若从外部复制,请将 a5_kernel_queue 目录放到上述 app/ 路径下。
4.2、修改 BUILD.gn(注册编译组件)
编辑 applications/sample/wifi-iot/app/BUILD.gn,在 features 列表中添加本案例:
lite_component("app") {
features = [
"startup",
"a5_kernel_queue:queue_example", // 添加此行
]
}
4.3、修改 SDK 配置文件
步骤 1:编辑 device/soc/hisilicon/ws63v100/sdk/build/config/target_config/ws63/config.py
找到 'ws63-liteos-app' 配置段,在其 'ram_component' 列表中添加:
"queue_example"
步骤 2:编辑 device/soc/hisilicon/ws63v100/sdk/libs_url/ws63/cmake/ohos.cmake
找到 "ws63-liteos-app" 对应的 set(COMPONENT_LIST 部分,添加:
"queue_example"
4.4、执行编译
在 OpenHarmony 源码根目录下执行:
rm -rf out
hb set -root .
# 通过上下方向键选择 ws63 对应的编译分支(如 ws63-liteos-app)
hb build -f
编译成功后,固件输出路径通常在 out/ws63/ 目录下。
4.5、烧录固件
使用开发板配套的烧录工具,将编译生成的固件烧写到 LZ3863-星闪开发板。具体烧录步骤请参考开发板用户手册。
5、运行结果
5.1、串口配置
| 参数 | 值 |
|---|---|
| 波特率 | 115200 |
| 数据位 | 8 |
| 停止位 | 1 |
| 校验位 | 无 |
5.2、预期输出
烧录并复位开发板后,串口助手应显示类似以下日志:
msg_write_thread write Message Queue msg:1
msg_read_thread read Message Queue msg:1
msg_write_thread write Message Queue msg:2
msg_read_thread read Message Queue msg:2
msg_write_thread write Message Queue msg:3
msg_read_thread read Message Queue msg:3
msg_write_thread write Message Queue msg:4
msg_read_thread read Message Queue msg:4
...
5.3、结果分析
| 现象 | 说明 |
|---|---|
write Message Queue msg:N |
写线程成功向队列投递第 N 条消息,N 从 1 开始递增 |
read Message Queue msg:N |
读线程成功从队列取出消息,N 与对应写入值一致 |
| 每条 write 后紧跟对应 read | 消息队列 FIFO 传递生效,读写线程通过队列正确协作 |
| 消息编号持续递增 | 写线程 data++ 逻辑正常,队列未丢失消息 |
| 约每 1 秒出现一对 write/read 日志 | osDelay(100) 延时生效,写线程按预期周期运行 |
5.4、常见问题排查
| 问题 | 可能原因 | 解决方法 |
|---|---|---|
| 无任何串口输出 | 串口参数错误或线序接错 | 确认波特率 115200,检查 TX/RX 接线 |
编译报错找不到 queue_example |
BUILD.gn 或 config.py 未正确修改 | 逐步核对 4.2、4.3 节的三处配置 |
打印 Failed to create Message Queue |
系统资源不足或队列参数错误 | 检查 MSG_QUEUE_LENGTH 和消息大小是否合理 |
打印 Failed to create msg_write_thread |
线程资源耗尽或栈配置不合理 | 检查是否创建了过多线程,适当增大栈大小 |
| 只有 write 日志,无 read 日志 | 读线程未成功创建 | 检查启动日志,确认两个线程均已创建 |
出现 write Message Queue msg fail |
队列已满且写入超时(本实验不应出现) | 检查读线程是否正常运行,或增大 MSG_QUEUE_LENGTH |
出现 read Message Queue fail |
队列读取超时(本实验不应出现) | 检查写线程是否正常运行,确认队列创建成功 |
| read 的消息编号与 write 不对应 | 多线程调度异常或队列被其他代码干扰 | 确认仅本案例在使用该队列,重新烧录固件后复测 |
6、实验扩展
完成基本实验后,可尝试以下扩展练习:
- 修改队列长度:将
MSG_QUEUE_LENGTH改为 2,加快写线程频率(减小osDelay),观察队列满时写线程的阻塞行为; - 增加超时机制:将
osMessageQueueGet的timeout改为有限超时(如50),在超时分支打印"等待超时"提示; - 传递结构体消息:定义包含多个字段的结构体(如
{uint32_t id; uint32_t value;}),修改msg_size并观察读写效果; - 增加第二个消费者线程:创建
msg_read_thread2,观察同一队列多读者场景下的消息分配(每条消息仅被一个读者取走); - 查询队列状态:在写线程中调用
osMessageQueueGetCount和osMessageQueueGetSpace,打印当前队列占用情况; - 结合中断场景:在中断回调中调用
osMessageQueuePut向队列投递事件,任务侧读取并处理,体会队列在 ISR 与任务间通信的用法。
更多推荐



所有评论(0)