标签#LiteOS-M#OpenHarmony#STM32MP157#RTOS#嵌入式阅读对象刚接触 RTOS 的单片机开发者准备把 LiteOS-M 移植到 STM32MP157 M4 内核的同学前言很多初学单片机的同学会有个疑问单片机裸机while(1)大循环已经能跑程序了我们为什么还要费劲去移植、学习 RTOS 实时操作系统很多教程一上来就讲 API、讲移植步骤却很少讲清楚「RTOS 到底解决了裸机的什么痛点」。本文从最简单的 LED 闪烁案例入手对比裸机与 RTOS 的差异再结合我们正在做的STM32MP157 Cortex-M4 内核移植 OpenHarmony LiteOS-M把 RTOS 的核心价值讲明白。一、裸机开发的美好与痛点多 LED 闪烁案例我们手上的这块正点原子 STM32MP157 开发板M4 内核引出了 2 路用户 LEDDS0 红 PI0DS1 绿 PF3。场景1只控制 1 颗 LED 闪烁只让 1 颗 LED 周期性闪烁裸机写起来非常简单翻转 GPIO 输出加上延时即可。while(1){GPIO_ResetBits(GPIOI,GPIO_PIN_0);// LED 亮低电平点亮HAL_Delay(600);GPIO_SetBits(GPIOI,GPIO_PIN_0);// LED 灭HAL_Delay(600);}逻辑清晰、实现简单这种单一周期的需求裸机完全胜任。 说明正点原子 STM32MP157 开发板在 M4 侧实际引出的用户 LED 就是2 路——DS0红PI0和DS1绿PF3。下面「3 个 LED 不同周期」只是为把「多周期冲突」这个痛点讲清楚而设的假设场景我们最终的 LiteOS-M 移植演示就是用这 2 路物理 LED 做成 2 个独立任务详见第四节。场景2需求升级 —— 3 个 LED不同周期闪烁假设场景用于讲解多周期冲突LED10.6s 闪烁一次LED20.8s 闪烁一次LED31.0s 闪烁一次问题来了STM32MP157 的 M4 是单核 CPU同一时刻只能执行一段代码指令是顺序执行的。如果直接写三段带阻塞延时的while(1)代码会变成这样示意用来暴露问题// 示意三段逻辑无法真正并行CPU 只会执行第一段while(1){led1_toggle();HAL_Delay(600);}while(1)// 以下两段永远进不去{led2_toggle();HAL_Delay(800);}while(1){led3_toggle();HAL_Delay(1000);}现实很残酷CPU 进入第一个while死循环之后就卡死在这里后面两段代码永远得不到执行。裸机怎么解决多周期闪烁裸机当然也能实现有两条路手写状态机 SysTick 毫秒计时不用阻塞延时把每一路 LED 的时间戳存下来在主循环里判断时间是否到达再翻转 IO。用定时器中断拆分业务逻辑。但是业务越多状态变量、判断分支就会爆炸代码逻辑越来越像「意大利面条」可读性很差新增、修改功能很容易引入 bug维护成本急剧上升。当产品同时要处理LED 闪烁、按键扫描、串口收发、传感器采集、电机控制……裸机状态机写起来会非常折磨人。二、RTOS 如何解决单核单片机「同时做多件事」RTOS 的核心思想把业务拆成独立任务Task内核调度器自动在多个任务之间切换 CPU 使用权宏观上模拟出「多线程并发」的效果。注意M4 依旧是单核不是真正的并行而是快速分时抢占 —— 宏观看起来多个任务在同时跑。我们课程用的是 OpenHarmony 的轻量内核LiteOS-M思想和大家熟悉的 FreeRTOS 高度一致。RTOS 版本三个 LED 各自一个独立任务每一路 LED 闪烁写成一个独立任务函数每个任务内部直接写自己的死循环用LOS_TaskDelay()做延时。// 任务10.6s 闪烁voidLedTask1(void){while(1){LED1_TOGGLE();LOS_TaskDelay(600);}}// 任务20.8s 闪烁voidLedTask2(void){while(1){LED2_TOGGLE();LOS_TaskDelay(800);}}// 任务31.0s 闪烁voidLedTask3(void){while(1){LED3_TOGGLE();LOS_TaskDelay(1000);}}然后在初始化时调用LOS_TaskCreate()创建 3 个任务指定任务入口函数、栈大小、任务优先级、任务名字。LOS_TaskCreate(taskId1,taskParam1);// 创建任务交给 LiteOS-M 内核调度LOS_TaskCreate(taskId2,taskParam2);LOS_TaskCreate(taskId3,taskParam3);LOS_Start();// 启动调度器✅ 优势非常直观业务解耦每个 LED 的闪烁逻辑完全独立一个任务的代码几乎不用关心其他任务新增业务直接新增一个任务即可。代码可读性高逻辑就是我们人脑思考的业务流程不需要手写复杂的状态机。内核自动处理任务切换开发者专注业务本身不用手动维护时间戳、状态标志。三、关键知识点LOS_TaskDelay()和HAL_Delay()根本不是一回事很多新手会混淆这两个延时而这正是理解 RTOS 的关键HAL_Delay()裸机忙等延时CPU 空循环计数全程占用 CPU原地空转别的代码无法运行。LiteOS-MLOS_TaskDelay(tick)调用之后当前任务直接进入【阻塞态】主动让出 CPU 使用权。重点任务阻塞期间不再占用 CPU调度器把 CPU 分配给其他就绪任务延时时间到之后任务回到就绪队列等待调度器再次分配 CPU。一句话区分忙等延时占着 CPU 原地睡觉RTOS 任务 Delay放下 CPU 去睡觉别人先用闹钟响了再回来跑。除了延时等待消息队列、信号量、互斥锁时任务同样会进入阻塞、释放 CPU。任务之间还能通过队列、信号量做任务间通信实现数据交互。裸机忙等 vs RTOS 阻塞 —— 速查表对比项裸机HAL_Delay()RTOSLOS_TaskDelay()CPU 占用全程占用、原地空转主动让出CPU 去跑别的任务多任务友好否会卡死后续逻辑是阻塞期间其他任务照常跑代码写法状态机 / 时间戳手动管理每个任务独立while(1) Delay适用场景极简单单任务多业务并发、需通信/抢占RTOS 调度器怎么工作物理 CPU 只有一个RTOS 内核给我们虚拟出多个「虚拟 CPU」每个虚拟 CPU 跑一个任务。SysTick 系统节拍定时器产生中断触发 PendSV 异常完成任务上下文切换保存寄存器、栈再切到另一个任务。调度规则高优先级优先运行同优先级时间片轮转任务阻塞主动让出 CPU。高优先级任务就绪会抢占低优先级任务任务调用 Delay / 等待资源时主动让出 CPUCPU 去跑其他就绪任务没有任何就绪任务时就跑 Idle 空闲任务。四、回到我们的 STM32MP157 M4 LiteOS-M 移植课程我们实操的工程零物理裁剪移植 LiteOS-MCortex-M4 内核完全运行在 256KB 片内 SRAM。内核源码一行不改适配全部收敛到targets目录Makefile 控制逻辑裁剪不删除内核源文件最终实现两个 LED 任务交替闪烁和本文案例原理一模一样。裸机开发适合简单小项目当你的项目有多个并发业务、需要任务间通信、对实时抢占有要求时RTOS 的价值就体现出来了。RTOS 不是魔法底层依旧是汇编、中断、寄存器。但它把任务切换、内存管理、IPC 通信这些复杂底层机制封装好了开发者只需要调用对应 API 写业务逻辑。当你搞懂任务状态、阻塞、调度、任务间通信这些基础概念之后再去写 RTOS 业务代码会发现本质就是调用 API思路理清了代码并不难写。五、小结单片机是单核 CPU裸机多业务会陷入「状态机复杂度爆炸」RTOS 把业务拆成独立 Task 任务内核调度器自动做任务切换宏观实现「多任务并发」RTOS 的 Delay不是忙等延时任务进入阻塞、释放 CPU这是 RTOS 高效的关键我们 STM32MP157 M4 移植 LiteOS-M 的课程就是手把手把这套 RTOS 内核跑起来从底层看懂 RTOS 如何工作。思考小问题留给读者如果一个 RTOS 任务里写了while (1);没有任何 Delay 或阻塞 API会发生什么欢迎评论区留言。