1. 课程导入

大家好,我是你们的老朋友。咱们这门课讲的是《STM32 FreeRTOS移植与任务调度实战》,第一节课,我想先聊聊最基础的问题——我们为什么需要RTOS?

很多刚入行的朋友会问:我裸机跑得挺好的,为什么要上个操作系统?这不是增加复杂度吗?

嗯,这个问题问得好。我刚开始做嵌入式那会儿,也觉得裸机够用。直到有一次,我接手一个项目——一个带触摸屏、Wi-Fi通信、多路传感器采集的智能家居网关。用前后台系统写,光是按键消抖和屏幕刷新就让我焦头烂额。后来我咬牙上了FreeRTOS,整个代码结构瞬间清晰了。

说白了,RTOS不是万能的,但在复杂系统中,它是救命的。

1.1 前后台系统 vs RTOS:一场效率革命

先看一张对比图,你就能明白差距在哪。

核心差异一句话总结:前后台系统是“一个人干所有活”,RTOS是“一群人分工协作”。

前后台系统 vs RTOS 执行模型对比 前后台系统(裸机) 主循环(大循环) 任务A:传感器采集 任务B:屏幕刷新 任务C:Wi-Fi通信 任务D:按键处理 ⚠ 一个任务卡死,全部瘫痪 ⚠ 实时性无法保证 RTOS系统 调度器(核心) 任务A 传感器采集 任务B 屏幕刷新 任务C Wi-Fi通信 任务D 按键处理 ✅ 任务独立运行,互不阻塞 ✅ 高优先级任务抢占CPU ✅ 实时性可预测 ✅ 代码模块化,易维护

你看,左边是前后台系统——一个大while循环里塞了所有功能。任务A跑完才能跑任务B,任务B卡住了,后面的全得等着。这就是典型的“串行执行”。

右边是RTOS——调度器统一管理多个任务。每个任务看起来像是“同时运行”的。高优先级的任务可以随时打断低优先级的任务。这就是“抢占式调度”。

对比维度 前后台系统(裸机) RTOS
任务执行方式 串行,一个接一个 并发,抢占式调度
实时性 差,依赖主循环周期 好,可配置优先级
代码耦合度 高,全局变量满天飞 低,任务间通过队列/信号量通信
扩展性 差,加功能容易崩 好,加任务不影响其他
资源开销 极低(几KB ROM/RAM) 较低(FreeRTOS最小4KB RAM)
调试难度 低,单线程思维 中等,需理解任务同步

我的经验:什么时候用RTOS?当你的项目有3个以上需要“同时”处理的任务时,果断上RTOS。比如:一边采集传感器,一边刷新屏幕,一边处理按键,一边通信。裸机写这种代码,你会想砸键盘的。

1.2 RTOS的核心要素:四个支柱

RTOS不是玄学,它的核心就四个东西:任务、调度、同步、中断管理。把这四个搞明白,你就掌握了RTOS的命脉。

1.2.1 任务(Task)—— 最小的执行单元

任务是什么?说白了,就是一个无限循环的函数。每个任务有自己的栈空间、优先级和状态。

// FreeRTOS中定义一个任务
void vTaskFunction(void *pvParameters)
{
    // 任务初始化
    while(1)
    {
        // 任务主体代码
        vTaskDelay(pdMS_TO_TICKS(100)); // 延时100ms
    }
}

任务有四种状态:

  • 运行态:正在使用CPU
  • 就绪态:可以运行,但CPU被别的任务占着
  • 阻塞态:等待某个事件(延时、信号量、队列)
  • 挂起态:被暂停,不参与调度

我记得刚学RTOS时,总搞不清阻塞和挂起的区别。后来我师傅一句话点醒我:阻塞是“等东西”,挂起是“被关禁闭”。阻塞的任务等到了资源会自动恢复,挂起的任务必须别人叫它才能恢复。

1.2.2 调度(Scheduler)—— 谁先跑谁后跑

调度器是RTOS的大脑。FreeRTOS用的是抢占式优先级调度,规则很简单:

  1. 谁优先级高,谁先跑
  2. 同优先级,轮流跑(时间片轮转)
  3. 高优先级任务就绪时,立即打断低优先级任务

避坑指南:我曾经在一个项目中,把两个关键任务的优先级设成了相同。结果它们互相抢CPU,导致一个任务始终跑不完。后来我把其中一个优先级调高了一级,问题立刻解决。记住:优先级不是越多越好,够用就行。FreeRTOS支持0~31级,我一般只用3~5个优先级。

1.2.3 同步(Synchronization)—— 任务间的沟通桥梁

任务之间怎么通信?靠全局变量?那太原始了。RTOS提供了专业的同步机制:

同步机制 用途 典型场景
队列(Queue) 任务间传递数据 传感器数据从采集任务发给显示任务
信号量(Semaphore) 资源管理、事件通知 串口发送完成,通知下一个任务
互斥量(Mutex) 保护共享资源 多个任务访问同一个LCD缓冲区
事件组(Event Group) 多事件同步 等待多个传感器都采集完成

举个实际例子。我在做四轴飞行器时,姿态解算任务需要IMU数据。采集任务通过队列把数据发过来,解算任务从队列里取。这样两边互不干扰,数据也不会丢失。

1.2.4 中断管理(Interrupt Management)—— 快进快出

中断是嵌入式系统的灵魂。RTOS对中断有个黄金法则:ISR里只做最紧急的事,把耗时操作交给任务。

为什么?因为中断服务函数(ISR)执行时,调度器是暂停的。你在ISR里待太久,其他任务就饿死了。

重要提醒:FreeRTOS的API函数,有一部分可以在ISR中调用,但必须使用带FromISR后缀的版本。比如:

  • xQueueSend() → xQueueSendFromISR()
  • xSemaphoreGive() → xSemaphoreGiveFromISR()

我见过有人直接在ISR里调用vTaskDelay(),结果系统直接死机。记住:ISR里不能阻塞!

典型的中断处理流程是这样的:

// 中断服务函数
void EXTI0_IRQHandler(void)
{
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
    
    // 1. 清除中断标志
    EXTI->PR |= EXTI_PR_PR0;
    
    // 2. 通过队列发送数据到任务
    xQueueSendFromISR(xDataQueue, &data, &xHigherPriorityTaskWoken);
    
    // 3. 如果有更高优先级的任务被唤醒,立即切换
    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}

你看,ISR里只做了三件事:清标志、发数据、触发切换。真正的数据处理在任务里完成。这就是“快进快出”的精髓。

1.3 为什么选择FreeRTOS?

市面上RTOS很多,uC/OS、RT-Thread、ThreadX……为什么我推荐FreeRTOS?

  • 免费开源:商业项目也能用,没有授权费
  • 轻量级:最小内核只需4KB RAM,STM32F103都能跑
  • 生态好:STM32CubeMX直接支持,点几下就移植好了
  • 文档全:官方手册、社区资源都很丰富
  • 稳定性高:全球数以亿计的设备在跑FreeRTOS

个人建议:如果你是第一次接触RTOS,从FreeRTOS入手是最佳选择。它简单、直观、坑少。等你把FreeRTOS玩熟了,再去看其他RTOS,会发现很多概念都是相通的。