基于小智派-LZ3863星闪开发板:OpenHarmony 内核任务(线程)编程
1、实验简介
参考网址:https://gitee.com/Lockzhiner-Electronics/lz3863/tree/master/apps/a1_kernel_task
1.1、实验目的
本实验旨在帮助学习者掌握 OpenHarmony 轻量系统中 任务(线程) 的基本概念与编程方法。通过本实验,你将学会:
- 理解 OpenHarmony 多任务调度机制;
- 使用 CMSIS-RTOS v2 标准接口创建和管理线程;
- 通过
osDelay()实现任务的周期性执行; - 完成案例代码的编译、烧录与串口调试。
1.2、实验内容
本案例在 LZ3863-星闪开发板上创建 两个独立任务:
| 任务名称 | 优先级 | 执行周期 | 输出内容 |
|---|---|---|---|
task_helloworld |
普通(Normal) | 约 1 秒 | Hello World |
task_openharmony |
低于普通(BelowNormal) | 约 2 秒 | Hello OpenHarmony |
两个任务并发运行,由内核调度器按优先级和时间片进行调度,通过串口周期性打印日志,直观展示多任务并发效果。
1.3、实验环境
| 项目 | 说明 |
|---|---|
| 硬件 | LZ3863-星闪开发板、USB 数据线 |
| 软件 | OpenHarmony v5.1.0 源码、hb 编译工具 |
| 调试工具 | 串口助手(波特率 115200) |
| 案例路径 | applications/sample/wifi-iot/app/a1_kernel_task/ |
2、基础知识
2.1、什么是任务(线程)
从操作系统的角度看,任务是竞争系统资源的最小运行单元。每个任务可以:
- 独立使用或等待 CPU;
- 拥有独立的栈空间;
- 与其他任务并发执行,互不阻塞(除非主动等待)。
在 OpenHarmony 轻量系统中,一个任务对应一个线程(Thread)。本实验使用的 CMSIS-RTOS v2 是 ARM 定义的标准 RTOS 接口,OpenHarmony LiteOS-M 对其提供了完整支持。
2.2、任务调度机制
OpenHarmony 采用 抢占式调度,同时支持 时间片轮转:
- 抢占式调度:高优先级任务可以打断正在运行的低优先级任务;
- 时间片轮转:同优先级任务按时间片轮流获得 CPU;
- 阻塞等待:任务调用
osDelay()等阻塞接口时主动让出 CPU,其他任务得以运行。
本实验中,task_helloworld 优先级高于 task_openharmony,因此在两者同时就绪时,前者优先获得调度。
2.3、核心 API 介绍
2.3.1、头文件
#include "ohos_init.h"
#include "cmsis_os2.h"
#include <stdio.h>
2.3.2、常用线程 API
| API 名称 | 功能说明 |
|---|---|
osThreadNew |
创建线程并加入就绪队列 |
osThreadGetName |
获取线程名称 |
osThreadGetId |
获取当前线程 ID |
osThreadSetPriority |
设置线程优先级 |
osThreadGetPriority |
获取线程优先级 |
osThreadSuspend |
挂起指定线程 |
osThreadResume |
恢复被挂起的线程 |
osThreadTerminate |
终止指定线程 |
osDelay |
当前线程延时(让出 CPU) |
2.3.3、osThreadNew — 创建线程
osThreadId_t osThreadNew(osThreadFunc_t func, void *argument, const osThreadAttr_t *attr);
| 参数 | 说明 |
|---|---|
func |
线程入口函数 |
argument |
传递给线程函数的参数指针 |
attr |
线程属性(名称、栈大小、优先级等) |
注意:不能在中断服务程序(ISR)中调用
osThreadNew。
2.3.4、osThreadAttr_t — 线程属性结构体
osThreadAttr_t attr = {0};
attr.name = "task_name"; // 线程名称
attr.stack_size = 2048; // 栈大小(字节)
attr.priority = osPriorityNormal; // 线程优先级
2.3.5、osDelay — 线程延时
osStatus_t osDelay(uint32_t ticks);
参数 ticks 为内核时钟节拍数。本系统默认 1 tick = 10 ms,因此:
osDelay(100)≈ 延时 1 秒;osDelay(200)≈ 延时 2 秒。
2.3.6、APP_FEATURE_INIT — 应用入口注册
APP_FEATURE_INIT(HelloWorldLauncher);
该宏将 HelloWorldLauncher 注册为应用特性初始化函数,系统启动完成后自动调用,无需手动修改 main() 函数。
3、程序设计
3.1、程序架构
本案例文件目录结构:
a1_kernel_task/
├── kernel_task_example.c # 主程序源码
├── BUILD.gn # GN 编译配置
├── README_zh.md # 案例简要说明
└── 实验手册.md # 本实验手册
本案例采用 三层任务结构:
系统启动
│
▼
HelloWorldLauncher() ← APP_FEATURE_INIT 注册,系统启动时自动执行
│
▼
hello_main() ← 主任务,负责创建子任务
├── task_helloworld() ← 子任务 1,每 1 秒打印 "Hello World"
└── task_openharmony() ← 子任务 2,每 2 秒打印 "Hello OpenHarmony"
3.2、源文件说明
| 文件 | 说明 |
|---|---|
kernel_task_example.c |
主程序源码 |
BUILD.gn |
GN 编译配置文件,定义静态库 task_example |
3.3、关键代码分析
(1)应用启动入口 — HelloWorldLauncher
系统启动后,通过 APP_FEATURE_INIT 宏自动调用此函数,创建主任务 hello_main:
static void HelloWorldLauncher(void)
{
osThreadAttr_t attr = {0};
attr.name = "hello_main";
attr.stack_size = 2048;
attr.priority = osPriorityNormal;
if (osThreadNew(hello_main, NULL, &attr) == NULL) {
printf("[HelloWorldLauncher] Failed to create hello_main!\n");
return;
}
printf("[HelloWorldLauncher] create hello_main successful!\n");
}
APP_FEATURE_INIT(HelloWorldLauncher);
(2)主任务 — hello_main
hello_main 负责创建两个子任务,并设置各自的名称、栈大小和优先级:
void hello_main(void *arg)
{
(void)arg;
// 创建 task_helloworld,优先级:Normal
osThreadAttr_t attr1 = {0};
attr1.name = "task_helloworld";
attr1.stack_size = 2048;
attr1.priority = osPriorityNormal;
if (osThreadNew(task_helloworld, NULL, &attr1) == NULL) {
printf("[HelloMain] Failed to create task_helloworld!\n");
return;
}
// 创建 task_openharmony,优先级:BelowNormal
osThreadAttr_t attr2 = {0};
attr2.name = "task_openharmony";
attr2.stack_size = 2048;
attr2.priority = osPriorityBelowNormal;
if (osThreadNew(task_openharmony, NULL, &attr2) == NULL) {
printf("[HelloMain] Failed to create task_openharmony!\n");
return;
}
printf("[HelloMain] Both tasks created successfully.\n");
}
(3)子任务 — task_helloworld
每 1 秒打印一次 "Hello World",然后调用 osDelay(100) 主动让出 CPU:
void task_helloworld(void *arg)
{
(void)arg;
for (;;) {
printf("Hello World\n");
osDelay(100); // 延时约 1 秒
}
}
(4)子任务 — task_openharmony
每 2 秒打印一次 "Hello OpenHarmony":
void task_openharmony(void *arg)
{
(void)arg;
for (;;) {
printf("Hello OpenHarmony\n");
osDelay(200); // 延时约 2 秒
}
}
3.4、程序执行流程
4、编译步骤
4.1、确认案例目录
确认案例已位于 OpenHarmony 源码目录下:
applications/sample/wifi-iot/app/a1_kernel_task/
├── kernel_task_example.c
├── BUILD.gn
└── 实验手册.md
若从外部复制,请将 a1_kernel_task 目录放到上述 app/ 路径下。
4.2、修改 BUILD.gn(注册编译组件)
编辑 applications/sample/wifi-iot/app/BUILD.gn,在 features 列表中添加本案例:
lite_component("app") {
features = [
"startup",
"a1_kernel_task:task_example", // 添加此行
]
}
4.3、修改 SDK 配置文件
步骤 1:编辑 device/soc/hisilicon/ws63v100/sdk/build/config/target_config/ws63/config.py
找到 'ws63-liteos-app' 配置段,在其 'ram_component' 列表中添加:
"task_example"
步骤 2:编辑 device/soc/hisilicon/ws63v100/sdk/libs_url/ws63/cmake/ohos.cmake
找到 "ws63-liteos-app" 对应的 set(COMPONENT_LIST 部分,添加:
"task_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、预期输出
烧录并复位开发板后,串口助手应显示类似以下日志:
[HelloWorldLauncher] create hello_main successful!
[HelloMain] Both tasks created successfully.
Hello World
Hello World
Hello OpenHarmony
Hello World
Hello World
Hello OpenHarmony
Hello World
Hello World
...
5.3、结果分析
| 现象 | 说明 |
|---|---|
| 启动日志 | HelloWorldLauncher 和 hello_main 依次打印,表明任务创建成功 |
Hello World 约每 1 秒出现 1 次 |
task_helloworld 中 osDelay(100) 生效 |
Hello OpenHarmony 约每 2 秒出现 1 次 |
task_openharmony 中 osDelay(200) 生效 |
| 两任务输出交替出现 | 多任务并发调度正常工作 |
5.4、常见问题排查
| 问题 | 可能原因 | 解决方法 |
|---|---|---|
| 无任何串口输出 | 串口参数错误或线序接错 | 确认波特率 115200,检查 TX/RX 接线 |
编译报错找不到 task_example |
BUILD.gn 或 config.py 未正确修改 | 逐步核对 4.2、4.3 节的三处配置 |
| 只打印启动日志,无循环输出 | 任务创建失败 | 检查串口是否有 Failed to create 报错信息 |
| 输出频率不对 | 内核 tick 配置不同 | 确认 tick 为 10 ms,或相应调整 osDelay 参数 |
6、实验扩展
完成基本实验后,可尝试以下扩展练习:
- 修改优先级:将
task_openharmony的优先级改为osPriorityAboveNormal,观察输出顺序变化; - 增加第三个任务:新建
task_custom,每 500 ms 打印自定义字符串; - 动态挂起/恢复:在
hello_main中延时一段时间后调用osThreadSuspend/osThreadResume控制任务运行; - 调整栈大小:将
stack_size改为 512,观察是否出现栈溢出异常。
更多推荐



所有评论(0)