1. 什么是 FreeRTOS

FreeRTOS 是一个开源、轻量级的实时操作系统(RTOS)内核,由 Richard Barry 主导开发,后被 Amazon 托管并更名为 Amazon FreeRTOS 的一部分。它采用 C 语言编写,支持从 8 位单片机到 32 位高端 MCU 的多种平台,凭借源码免费、文档齐全、生态成熟等优势,成为嵌入式领域使用最广泛的 RTOS 之一。

与裸机程序(前后台系统)不同,FreeRTOS 提供了一套完整的多任务调度机制,让开发者可以把复杂应用拆分成多个相对独立的任务,由内核统一管理 CPU 时间。理解 FreeRTOS 的基础知识,是进入实时嵌入式开发的重要一步。

2. 为什么需要 RTOS

在传统的裸机开发中,程序通常由一个超级循环和若干中断服务函数组成:

int main(void)
{
    /* 硬件初始化 */
    while (1)
    {
        task_led();
        task_key();
        task_uart();
        task_sensor();
    }
}

这种前后台结构简单直接,但随着业务复杂化会逐渐暴露出问题:

  • 各个功能模块的执行时间相互干扰,实时性难以保证;
  • 某个模块阻塞(如等待外设响应)会拖慢整个主循环;
  • 业务耦合严重,代码结构随功能增加而迅速恶化。

RTOS 的价值在于把「谁来执行、何时执行」交给调度器统一处理。每个任务只专注自己的逻辑,由内核基于优先级和时间片切换任务,既提高了实时响应能力,也让模块之间的边界更清晰。

3. 核心概念:任务与调度器

3.1 任务

任务是 FreeRTOS 中最基本的执行单元,本质是一个永不返回的 C 函数。每个任务拥有独立的栈空间,用于保存局部变量、函数调用现场等。

void vLedTask(void *pvParameters)
{
    for (;;)
    {
        LED_Toggle();
        vTaskDelay(pdMS_TO_TICKS(500));
    }
}

任务函数必须遵循「死循环 + 阻塞」的模式:要么在循环中主动让出 CPU,要么调用阻塞型 API(如 vTaskDelay),否则低优先级任务将永远得不到执行机会。

3.2 调度器

调度器是 FreeRTOS 的心脏,负责在任务就绪时决定下一个运行的任务。FreeRTOS 默认使用优先级抢占式调度:

  • 数值越大,任务优先级越高;
  • 高优先级任务就绪后会立即抢占低优先级任务;
  • 同优先级任务可以通过时间片轮流执行。

调度器由 vTaskStartScheduler() 启动,启动后通常不再返回:

int main(void)
{
    xTaskCreate(vLedTask, "led", 128, NULL, 1, NULL);
    xTaskCreate(vUartTask, "uart", 512, NULL, 2, NULL);
    vTaskStartScheduler();

    /* 正常情况不会执行到这里 */
    for (;;);
}

3.3 任务状态

FreeRTOS 中的任务在生命周期内会在以下几种状态间迁移:

S1["就绪态

Ready"]

S2["运行态

Running"]

S2

S1

S3["阻塞态

Blocked"]

S3

S4["挂起态

Suspended"]

S4

  • 运行态:当前正在占用 CPU 的任务,单核系统中同一时刻只有一个;
  • 就绪态:具备执行条件,等待调度器分配 CPU;
  • 阻塞态:等待某个事件而暂时不能执行,如延时、等信号量、等队列数据;
  • 挂起态:被 vTaskSuspend() 挂起,不参与调度,直到被 vTaskResume() 恢复。

4. 任务创建与常用 API

创建任务使用 xTaskCreate():

BaseType_t xTaskCreate(TaskFunction_t pxTaskCode,
                       const char * const pcName,
                       configSTACK_DEPTH_TYPE usStackDepth,
                       void *pvParameters,
                       UBaseType_t uxPriority,
                       TaskHandle_t *pxCreatedTask);

参数含义如下:

  • pxTaskCode:任务函数指针;
  • pcName:任务名称,便于调试;
  • usStackDepth:栈深度,单位是字(word)而不是字节;
  • pvParameters:传给任务函数的参数;
  • uxPriority:任务优先级;
  • pxCreatedTask:任务句柄,可用于后续删除、挂起、恢复该任务。

常用的任务控制接口包括:

函数作用
vTaskDelay()相对延时,阻塞指定 tick
vTaskDelayUntil()绝对延时,保证周期性执行
vTaskSuspend()挂起任务
vTaskResume()恢复挂起的任务
vTaskDelete()删除任务
vTaskPrioritySet()动态修改任务优先级

5. 常用内核对象

任务之间需要通信与同步,FreeRTOS 提供了丰富的内核对象。

5.1 队列

队列用于任务间传递数据,是 FreeRTOS 中最基础的通信机制。数据以拷贝的方式入队,发送方和接收方不共享内存。

QueueHandle_t xQueue = xQueueCreate(10, sizeof(int));

int tx_value = 42;
xQueueSend(xQueue, &tx_value, portMAX_DELAY);

int rx_value = 0;
xQueueReceive(xQueue, &rx_value, portMAX_DELAY);

5.2 信号量

信号量用于资源计数与同步,常见类型有:

  • 二值信号量:取值 0 或 1,常用于任务与中断之间的同步;
  • 计数信号量:可累计计数值,适合资源池管理;
  • 互斥量(Mutex):特殊的二值信号量,带优先级继承机制,专门用于保护共享资源,避免优先级翻转。
SemaphoreHandle_t xMutex = xSemaphoreCreateMutex();

if (xSemaphoreTake(xMutex, portMAX_DELAY) == pdTRUE)
{
    /* 访问临界资源 */
    xSemaphoreGive(xMutex);
}

5.3 事件组

事件组允许一个任务等待多个事件位同时满足,适合「多条件汇合」的场景:

EventGroupHandle_t xEvents = xEventGroupCreate();

/* 等待 bit0 和 bit1 同时置位 */
xEventGroupWaitBits(xEvents,
                    BIT0 | BIT1,
                    pdTRUE,      /* 等待后清除事件位 */
                    pdTRUE,      /* 全部满足才返回 */
                    portMAX_DELAY);

5.4 软件定时器

FreeRTOS 的软件定时器由内核服务任务统一管理,回调函数运行在定时器服务任务的上下文中,因此回调内不能调用会阻塞的 API:

TimerHandle_t xTimer = xTimerCreate("timer",
                                    pdMS_TO_TICKS(1000),
                                    pdTRUE,      /* 自动重载 */
                                    (void *)0,
                                    vTimerCallback);
xTimerStart(xTimer, 0);

6. 中断与临界区

FreeRTOS 通过「可管理的临界区」来保护共享资源。根据使用场景的不同,可以选择不同的保护手段:

  • 临界区宏:taskENTER_CRITICAL() 与 taskEXIT_CRITICAL(),通过关闭中断保护极短的代码段,但会延迟中断响应,不宜长时间使用;
  • 挂起调度器:vTaskSuspendAll() 与 xTaskResumeAll(),防止任务切换,但中断仍可响应;
  • 互斥量:适合保护访问时间较长的共享资源。

对于中断服务函数,FreeRTOS 提供专门的 ...FromISR() 系列 API 用于发送队列或信号量,例如 xQueueSendFromISR()、xSemaphoreGiveFromISR(),其末尾需要手动判断是否触发一次任务切换:

void UART_IRQHandler(void)
{
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;

    uint8_t ch = UART_ReadByte();
    xQueueSendFromISR(xUartQueue, &ch, &xHigherPriorityTaskWoken);

    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}

7. 内存管理

FreeRTOS 提供了多种内存分配方案(heap_1 到 heap_5),开发时需要在 FreeRTOSConfig.h 中选择其一:

方案特点适用场景
heap_1.c只分配不释放创建后不会删除对象
heap_2.c支持释放,无碎片合并旧版本常用
heap_3.c包装标准 malloc/free依赖系统 C 库
heap_4.c支持释放与相邻块合并最常用的推荐方案
heap_5.cheap_4 基础上支持多段内存内存分布不连续时

所有内核对象(任务、队列、信号量等)的内存都由所选 heap 方案分配,因此合理配置 configTOTAL_HEAP_SIZE 对系统稳定运行至关重要。

8. 关键配置项

FreeRTOS 的行为高度依赖 FreeRTOSConfig.h 中的配置,初学者需要重点关注以下几项:

#define configUSE_PREEMPTION            1   /* 使用抢占式调度 */
#define configUSE_TIME_SLICING          1   /* 同优先级时间片轮转 */
#define configTICK_RATE_HZ              1000 /* 系统节拍频率 */
#define configMAX_PRIORITIES             8   /* 最大优先级数 */
#define configMINIMAL_STACK_SIZE        128  /* 空闲任务最小栈深 */
#define configTOTAL_HEAP_SIZE            (8 * 1024) /* 内核可分配内存 */

其中 configTICK_RATE_HZ 决定了时间基准,常见取值为 1000,即每 1ms 产生一次系统节拍中断。所有基于时间的功能(如 vTaskDelay)都与它密切相关。

9. 一个完整的入门示例

下面是一个基于 FreeRTOS 的两任务示例,演示了任务创建与队列通信:

#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"

static QueueHandle_t xDataQueue;

void vProducerTask(void *pvParameters)
{
    int count = 0;
    for (;;)
    {
        xQueueSend(xDataQueue, &count, portMAX_DELAY);
        count++;
        vTaskDelay(pdMS_TO_TICKS(500));
    }
}

void vConsumerTask(void *pvParameters)
{
    int value = 0;
    for (;;)
    {
        if (xQueueReceive(xDataQueue, &value, portMAX_DELAY) == pdTRUE)
        {
            /* 处理接收到的数据 */
            (void)value;
        }
    }
}

int main(void)
{
    xDataQueue = xQueueCreate(8, sizeof(int));

    xTaskCreate(vProducerTask, "producer", 256, NULL, 1, NULL);
    xTaskCreate(vConsumerTask, "consumer", 256, NULL, 2, NULL);

    vTaskStartScheduler();

    for (;;);
    return 0;
}

10. 总结

FreeRTOS 的学习可以围绕一个主线展开:任务如何创建与调度,任务之间如何通信与同步,共享资源如何保护。掌握了任务、队列、信号量、互斥量这几个核心概念后,就能应对大多数常规的嵌入式应用需求。

初学时建议先基于官方移植好的开发板例程跑通多任务,再逐步深入源码理解调度器的实现,最后结合实际项目体会优先级设置、栈大小配置和内存分配对系统行为的影响。扎实掌握这些基础,是后续学习更复杂 RTOS 特性(如流缓冲区、消息缓冲区、协程等)的前提。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐