1. RTOS基础概念:实时操作系统定义、任务调度、内核对象、无人机为何需要RTOS

1.1 到底什么是实时操作系统?

先说说我自己的理解。实时操作系统,英文叫RTOS,说白了就是「在规定时间内必须干完活」的操作系统。

你想想看,我们平时用的Windows、Linux,它们是什么系统?是分时系统。什么叫分时?就是大家轮流用CPU,你等一会儿,我等一会儿。你点个鼠标,系统可能过几十毫秒才响应,你感觉不到,因为人反应没那么快。

但无人机不一样。飞控每1毫秒就要读一次陀螺仪数据,每2毫秒就要算一次姿态,每5毫秒就要输出一次PWM给电机。这些时间节点,一个都不能晚。晚1毫秒,飞机可能就翻了。

所以,实时操作系统的核心就一句话:任务的执行时间是可预测的,最坏情况下的响应时间是确定的

实时 ≠ 快速

很多新手会搞混。实时不是说你跑得有多快,而是说你能不能保证在截止时间前完成。哪怕你跑得慢一点,但只要每次都能在截止时间前完成,那就是实时系统。

1.2 任务调度——RTOS的「大脑」

任务调度是RTOS最核心的机制。我刚开始学RTOS时,觉得调度不就是「谁先来谁先走」吗?后来发现完全不是这么回事。

RTOS里常用的调度策略有这么几种:

调度算法 特点 无人机场景
优先级抢占式调度 高优先级任务随时打断低优先级任务 最常用,比如姿态控制优先级最高
时间片轮转调度 同等优先级任务轮流执行固定时间 用于非关键任务,如日志记录
单调速率调度(RMS) 周期越短的任务优先级越高 适合传感器采集这类周期性任务

我个人习惯用优先级抢占式调度。为什么?因为无人机里有些任务就是「天大的事」,比如电机控制。一旦电机控制任务就绪,其他任务都得让路。

避坑指南

我曾经在一个项目中,把所有任务都设成了同一个优先级。结果呢?高频率的传感器中断把低优先级的通信任务饿死了,GPS数据一直发不出去,飞机在空中乱飘。后来我学乖了:关键任务给高优先级,非关键任务给低优先级,中间留一两个优先级做缓冲

1.3 内核对象——RTOS的「工具箱」

RTOS内核提供了几个常用的对象,用来协调任务之间的工作。我挑几个最重要的说说。

1.3.1 任务(Task)

任务就是一段独立的代码,有自己的栈空间和优先级。在FreeRTOS里创建一个任务很简单:

// 创建一个姿态控制任务
xTaskCreate(
    attitude_control_task,  // 任务函数
    "AttitudeCtrl",         // 任务名称
    512,                    // 栈大小(单位:字)
    NULL,                   // 参数
    5,                      // 优先级(5为高优先级)
    NULL                    // 任务句柄
);

嗯,这里要注意:栈大小一定要算好。我见过有人给任务分配了128字节的栈,结果函数调用一深,栈溢出了,飞控直接死机。

1.3.2 信号量(Semaphore)

信号量用来做同步。比如,传感器数据采集完了,通知姿态控制任务开始算。这就是典型的「生产者-消费者」模式。

// 传感器中断中释放信号量
void IMU_IRQHandler(void) {
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
    xSemaphoreGiveFromISR(xIMUSemaphore, &xHigherPriorityTaskWoken);
    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}

// 姿态控制任务等待信号量
void attitude_control_task(void *pvParameters) {
    while(1) {
        xSemaphoreTake(xIMUSemaphore, portMAX_DELAY);
        // 读取传感器数据,计算姿态
    }
}

1.3.3 消息队列(Message Queue)

消息队列用来传递数据。比如GPS模块把位置数据打包成消息,发给导航任务。

我建议:能用消息队列就别用全局变量。全局变量容易被多个任务乱改,出了bug极难排查。消息队列自带互斥保护,省心很多。

1.3.4 互斥量(Mutex)

互斥量用来保护共享资源。比如两个任务都要写同一个串口,不加互斥量的话,数据会混在一起。

特别注意:优先级反转

互斥量有个经典问题叫「优先级反转」。低优先级任务拿着锁,高优先级任务等锁,中间优先级的任务反而先执行了。解决办法是用「优先级继承」协议。FreeRTOS的互斥量默认支持这个特性,记得用 xSemaphoreCreateMutex() 而不是 xSemaphoreCreateBinary()

1.4 无人机为什么需要RTOS?

这个问题,我直接说结论:没有RTOS,你很难写出稳定可靠的飞控程序

为什么?我给你拆开来看:

  • 多任务并发:飞控要同时处理传感器采集、姿态解算、控制输出、通信、日志记录……如果用裸机写,你得在一个大循环里手动调度这些任务。任务一多,代码就成了一团乱麻。
  • 实时性要求:姿态控制环通常跑在1kHz,也就是1毫秒一个周期。裸机里一个延时函数就能把整个系统卡死。RTOS里,高优先级任务可以精确地按时执行。
  • 模块化开发:RTOS让每个功能模块独立成一个任务。传感器驱动一个任务,控制算法一个任务,通信一个任务。出了问题,定位也快。我曾经接手过一个裸机飞控项目,一个main函数写了3000行,改一个bug要花三天。
  • 资源管理:RTOS帮你管理CPU时间、内存、外设。你只需要关心业务逻辑,不用操心「这个任务跑了多久」「那个中断会不会冲突」。

我的真实经历

2018年我做一款四轴飞行器,刚开始用裸机写。飞起来倒是能飞,但一遇到强风,飞控就卡顿,因为传感器中断和PWM输出抢CPU时间。后来移植了FreeRTOS,把姿态控制任务优先级提到最高,电机控制用定时器硬件触发,问题就解决了。那次之后,我再也没用裸机写过飞控。

1.5 小结

这一章我们聊了RTOS的基本概念。记住三个关键词:任务调度内核对象实时性。下一章,我会带你实际移植一个RTOS到STM32上,手把手教你配置时钟、中断和任务栈。

嗯,先消化一下这些概念。有问题随时问我。