1. 为什么使用RTOS
在单片机项目中,功能较少时,使用 while (1) 循环轮询就能满足需求。随着串口通信、按键扫描和状态上报等功能逐渐增加,各项工作的响应要求也开始不同。串口数据到达后需要及时处理,状态上报则按固定周期执行。如果这些逻辑都放在同一个循环中,一次耗时的操作就可能推迟后续处理。引入RTOS后可将不同功能拆分为独立任务,由调度器根据任务优先级和就绪状态安排执行。以串口接收为例,接收中断负责读取数据并通知处理任务,组帧和校验交由任务完成。处理任务在没有数据时可以阻塞等待,让出 CPU 给其他任务。这样的分工使程序更容易兼顾及时响应和周期执行,也便于后续增加功能和定位问题。
轮询系统即是在裸机编程的时候,先初始化好相关的硬件,然后让主程序在一个死循环里面不断循环,顺序地做各种事情,参考代码结构如下:
int main(void)
{
/* 硬件相关初始化 */
HardWareInit();
/* 无限循环 */
for (;;) {
/* 处理事情 1 */
DoSomething1();
/* 处理事情 2 */
DoSomething2();
/* 处理事情 3 */
DoSomething3();
}
}
前后台系统既是轮询系统加中断组成,其中大循环称为后台系统,中断称为前台。示例代码结构如下:
int flag1 = 0;
int flag2 = 0;
int flag3 = 0;
int main(void)
{
/* 硬件相关初始化 */
HardWareInit();
/* 无限循环 */
for (;;) {
if (flag1) {
/* 处理事情 1 */
DoSomething1();
}
if (flag2) {
/* 处理事情 2 */
DoSomething2();
}
if (flag3) {
/* 处理事情 3 */
DoSomething3();
}
}
}
void ISR1(void)
{
/* 置位标志位 */
flag1 = 1;
/* 如果事件处理时间很短,则在中断里面处理
如果事件处理时间比较长,在回到前台处理 */
DoSomething1();
}
void ISR2(void)
{
/* 置位标志位 */
flag2 = 1;
/* 如果事件处理时间很短,则在中断里面处理
如果事件处理时间比较长,在回到前台处理 */
DoSomething2();
}
void ISR3(void)
{
/* 置位标志位 */
flag3 = 1;
/* 如果事件处理时间很短,则在中断里面处理
如果事件处理时间比较长,在回到前台处理 */
DoSomething3();
}
相比前后台系统,多任务系统的事件响应也是在中断中完成的,但是事件的处理是在任务中完成的。在多任务系统中,任务跟中断一样,也具有优先级,优先级高的任务会被优先执行。当一个紧急的事件在中断被标记之后,如果事件对应的任务的优先级足够高,就会立马得到响应。相比前后台系统,多任务系统的实时性又被提高了。多任务系统大概的伪代码具体见代码结构如下:
int flag1 = 0;
int flag2 = 0;
int flag3 = 0;
int main(void)
{
/* 硬件相关初始化 */
HardWareInit();
/* OS 初始化 */
RTOSInit();
/* OS 启动,开始多任务调度,不再返回 */
RTOSStart();
}
void ISR1(void)
{
/* 置位标志位 */
flag1 = 1;
}
void ISR2(void)
{
/* 置位标志位 */
flag2 = 2;
}
void ISR3(void)
{
/* 置位标志位 */
flag3 = 1;
}
void DoSomething1(void)
{
/* 无限循环,不能返回 */
for (;;) {
/* 任务实体 */
if (flag1) {
}
}
}
void DoSomething2(void)
{
/* 无限循环,不能返回 */
for (;;) {
/* 任务实体 */
if (flag2) {
}
}
}
void DoSomething3(void)
{
/* 无限循环,不能返回 */
for (;;) {
/* 任务实体 */
if (flag3) {
}
}
}
相比前后台系统中后台顺序执行的程序主体,在多任务系统中,根据程序的功能,我们把这个程序主体分割成一个个独立的,无限循环且不能返回的小程序,这个小程序我们称之为任务。每个任务都是独立的,互不干扰的,且具备自身的优先级,它由操作系统调度管理。加入操作系统后,我们在编程的时候不需要精心地去设计程序的执行流,不用担心每个功能模块之间是否存在干扰。加入了操作系统,我们的编程反而变得简单了。整个系统随之带来的额外开销就是操作系统占据的那一丁点的 FLASH 和 RAM。现如今,单片机的 FLASH 和 RAM 是越来越大,完全足以抵挡 RTOS 那点开销。
2. RTOS简介
2.1 什么是RTOS
RTOS(Real-Time Operating System,实时操作系统)是一类面向时间约束设计的操作系统。它通过任务调度、定时和同步等机制,帮助应用在规定的时间内响应事件、执行周期性工作或完成数据处理。在嵌入式系统中,常见的做法是将通信、数据采集、状态监测等功能划分为不同任务,再根据各项工作的时序要求安排执行。
2.2 如何理解实时性
实时性关注的是系统能否在规定的时间内完成工作。例如,某通信设备要求从收到完整请求到发出应答最多间隔 10 ms,那么这 10 ms 就是这次处理的时间约束。即使平时只需要 2 ms,只要在某些负载下需要 15 ms,就会发生超时。因此,评估实时性时,需要关注高负载下的响应时间、执行时间的波动以及能否满足截止时间。
按错过截止时间所产生的后果,实时系统通常分为两类。
- 硬实时。错过截止时间就意味着系统未满足设计要求,例如要求在规定时间窗口内更新输出的闭环控制。
- 软实时。偶尔超时会降低服务质量,但系统仍能继续提供服务,例如界面刷新偶尔掉帧。
RTOS 提供了管理时间约束的机制。应用能否满足截止时间,还取决于任务执行时间、中断占用、共享资源等待和系统负载。
2.3 任务调度
任务是 RTOS 调度的基本执行单元,通常拥有独立的栈和运行上下文。以单核 MCU 为例,同一时刻只有一个任务占用 CPU。调度器根据任务的状态和调度策略,决定接下来运行哪个任务。
| 常见状态 | 含义 |
|---|---|
| 就绪 | 已具备运行条件,正在等待 CPU |
| 运行 | 正在使用 CPU 执行 |
| 阻塞 | 正在等待数据、事件、资源或延时结束 |
以常见的基于优先级的抢占式调度为例,当更高优先级的任务从阻塞转为就绪时,调度器可以让它优先运行。任务切换时,内核保存当前任务的运行上下文,并恢复下一个任务的上下文,使任务能够从上次暂停的位置继续执行。
2.4 任务间通信与同步
任务拆分后,需要解决数据如何传递、事件如何通知,以及共享资源如何访问的问题。常见机制如下:
| 机制 | 典型用途 |
|---|---|
| 消息队列 | 在任务之间传递采样数据、命令或处理结果 |
| 信号量 | 通知事件发生,或记录可用资源的数量 |
| 互斥锁 | 保护共享外设或数据结构,使同一时刻只有一个任务访问 |
| 事件标志 | 让任务等待一个或多个条件满足 |
| 延时与定时器 | 实现周期执行、延时触发或超时处理 |
以串口接收为例,中断服务程序可以把收到的数据写入缓冲区,再通知串口处理任务。处理任务被唤醒后读取数据,完成组帧、校验和解析。没有数据时,任务可以阻塞等待,让 CPU 执行其他就绪任务。这种分工可以缩短中断服务时间,也让数据处理流程集中在一个任务中。
异常处理:
- Xtal无法修改:https://blog.csdn.net/qq_38850943/article/details/142398195
- 新工程编译报错L6218E:魔术棒中选择“Linker”,去勾选“Use Memory Layout from Target Dialog”,"Scatter File"中选择对应芯片的sct文件。