Skip to content

实时操作系统

当裸机的超级循环无法满足多任务并发、优先级调度、精确定时的需求时,就需要 RTOS(Real-Time Operating System)。RTOS 不是 Linux 那种通用 OS,它极度轻量(内核几 KB),专为确定性响应设计。

一、为什么需要 RTOS

裸机超级循环的问题:

  • 某个任务耗时长会阻塞其他任务
  • 无法保证高优先级任务的响应时间
  • 复杂状态机难以维护
  • 多人协作时代码耦合严重

RTOS 解决这些问题的方式:把程序拆成多个独立任务(线程),由调度器按优先级分配 CPU 时间。

二、核心概念

任务(Task / Thread)

每个任务是一个独立的执行流,有自己的栈空间。

c
// FreeRTOS 创建任务
void led_task(void *param) {
    while (1) {
        HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);
        vTaskDelay(pdMS_TO_TICKS(500));  // 让出 CPU 500ms
    }
}

xTaskCreate(led_task, "LED", 128, NULL, 1, NULL);
//          函数      名字   栈大小 参数  优先级 句柄

调度器

决定哪个任务获得 CPU。常见策略:

  • 抢占式优先级调度:高优先级任务就绪时立即抢占低优先级任务(FreeRTOS 默认)
  • 时间片轮转:同优先级任务轮流执行,每个时间片(通常 1ms)切换一次
  • 协作式调度:任务主动让出 CPU(少用,确定性差)

临界区与中断管理

c
// 进入临界区(关中断)
taskENTER_CRITICAL();
shared_variable++;
taskEXIT_CRITICAL();

// 在 ISR 中使用 FromISR 版本的 API
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xSemaphoreGiveFromISR(sem, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);

三、同步与通信原语

信号量(Semaphore)

c
// 二值信号量:中断通知任务
SemaphoreHandle_t uart_sem = xSemaphoreCreateBinary();

void USART1_IRQHandler(void) {
    // ... 处理接收
    xSemaphoreGiveFromISR(uart_sem, NULL);
}

void uart_task(void *param) {
    while (1) {
        xSemaphoreTake(uart_sem, portMAX_DELAY);  // 阻塞等待
        process_uart_data();
    }
}

互斥锁(Mutex)

保护共享资源,带优先级继承防止优先级反转。

c
SemaphoreHandle_t spi_mutex = xSemaphoreCreateMutex();

void task_a(void *param) {
    xSemaphoreTake(spi_mutex, portMAX_DELAY);
    spi_transfer(data_a, len_a);
    xSemaphoreGive(spi_mutex);
}

消息队列(Queue)

任务间传递数据的安全通道。

c
QueueHandle_t sensor_queue = xQueueCreate(10, sizeof(SensorData));

// 生产者
void sensor_task(void *param) {
    SensorData d;
    while (1) {
        d.temperature = read_temp();
        d.humidity = read_humi();
        xQueueSend(sensor_queue, &d, portMAX_DELAY);
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

// 消费者
void display_task(void *param) {
    SensorData d;
    while (1) {
        xQueueReceive(sensor_queue, &d, portMAX_DELAY);
        lcd_show_temp(d.temperature);
    }
}

事件组(Event Group)

多个事件的组合等待。

c
EventGroupHandle_t events = xEventGroupCreate();
#define EVT_WIFI_CONNECTED  (1 << 0)
#define EVT_SENSOR_READY    (1 << 1)

// 等待 Wi-Fi 连接且传感器就绪
xEventGroupWaitBits(events, EVT_WIFI_CONNECTED | EVT_SENSOR_READY,
                    pdTRUE, pdTRUE, portMAX_DELAY);

软件定时器

c
TimerHandle_t heartbeat = xTimerCreate("HB", pdMS_TO_TICKS(5000),
                                        pdTRUE, NULL, heartbeat_callback);
xTimerStart(heartbeat, 0);

void heartbeat_callback(TimerHandle_t timer) {
    send_heartbeat_packet();
}

四、内存管理

FreeRTOS 提供 5 种堆实现:

方案特点适用场景
heap_1只分配不释放任务/队列创建后不删除
heap_2支持释放但不合并碎片固定大小块分配
heap_3封装标准 malloc/free有 C 库的平台
heap_4支持释放且合并相邻空闲块通用推荐
heap_5heap_4 + 支持不连续内存区域多块 RAM 的 MCU

栈溢出检测:

c
// FreeRTOSConfig.h
#define configCHECK_FOR_STACK_OVERFLOW 2

void vApplicationStackOverflowHook(TaskHandle_t task, char *name) {
    printf("Stack overflow in task: %s\n", name);
    while (1);  // 停在这里方便调试
}

五、主流 RTOS 对比

维度FreeRTOSRT-ThreadZephyr
许可证MITApache 2.0Apache 2.0
内核大小6~10 KB3~10 KB8~20 KB
组件生态内核精简,AWS IoT 集成丰富(文件系统/网络/GUI)极丰富(蓝牙/网络/USB)
中文文档一般优秀一般
支持芯片几乎所有 MCU主流 ARM/RISC-V广泛(Nordic/ST/NXP)
学习曲线
适合场景入门学习、简单产品国产生态、中等复杂度产品BLE/Thread/复杂 IoT

选型建议

  • 入门学习:FreeRTOS(资料最多、API 最简单)
  • 国产芯片 + 中文生态:RT-Thread(软件包丰富、社区活跃)
  • BLE/Thread/Matter 产品:Zephyr(Nordic 官方支持)
  • 安全认证产品:SafeRTOS(FreeRTOS 的认证版本)或 Zephyr(有安全子系统)

六、FreeRTOS 实战配置

FreeRTOSConfig.h 关键配置

c
#define configUSE_PREEMPTION            1       // 抢占式调度
#define configCPU_CLOCK_HZ              168000000
#define configTICK_RATE_HZ              1000    // 1ms tick
#define configMAX_PRIORITIES            5
#define configMINIMAL_STACK_SIZE        128     // 最小栈(单位:字)
#define configTOTAL_HEAP_SIZE           (32 * 1024)
#define configUSE_MUTEXES               1
#define configUSE_COUNTING_SEMAPHORES   1
#define configUSE_QUEUE_SETS            1
#define configUSE_TIMERS                1
#define configTIMER_TASK_STACK_DEPTH    256

典型任务架构

优先级 4(最高):硬件中断处理任务(接收 ISR 信号量后处理)
优先级 3:通信任务(UART/CAN/BLE 收发)
优先级 2:业务逻辑任务(传感器采集、控制算法)
优先级 1:显示/日志任务
优先级 0(最低):空闲任务(IDLE,可在此进入低功耗)

七、调试技巧

  • 任务状态查看vTaskList() 打印所有任务的状态、优先级、剩余栈
  • 运行时统计vTaskGetRunTimeStats() 查看各任务 CPU 占用率
  • Tracealyzer:SEGGER 出品的 RTOS 可视化分析工具,能看到任务切换时序
  • 栈水位线uxTaskGetStackHighWaterMark() 查看任务栈最大使用量,据此调整栈大小

八、常见陷阱

问题原因解决
优先级反转低优先级任务持有锁,高优先级任务等待用 Mutex(带优先级继承)而不是二值信号量
死锁两个任务互相等待对方持有的锁统一加锁顺序、设置超时
栈溢出任务栈分配太小开启溢出检测、用水位线函数调整
ISR 中调用阻塞 APIISR 不能阻塞只用 FromISR 版本的 API
任务饿死高优先级任务不让出 CPU确保高优先级任务有阻塞点(delay/等待信号量)

返回 总览与学习路线

别急,先让缓存热一下。