FreeRTOS 学习文档

发布时间:2026/7/29 9:00:26
FreeRTOS 学习文档 FreeRTOS 使用原因在代码量大、多任务要求实时进行时使用一个 while 循环进行逻辑开发就会很不方便如图裸机状态下追求任务同时完成。如果使用 FreeRTOS 只需要显而易见变简单了。理论学习部分OsKernelStart()函数运行后程序进入 FreeRTOS。任务Task任务调度的实现控制 PC 寄存器指向不同的任务。FreeRTOS 运行任务会给任务分配时间片。任务运行状态就绪态、运行态、阻塞态、挂起态任务刚生成是就绪态 → 分配时间片后任务开始运行进入运行态 → 时间片时间到时回到就绪态给其他就绪态任务分配时间片。当任务运行osDelay之类函数时任务进入阻塞态之后时间片分配给其他就绪态任务。挂起态类似。使用osDelay的原因就是它能让任务进入阻塞态让出时间片。任务优先级高优先级任务一直处于运行态时低优先级任务无法运行队列 Queue任务间需要数据交流时将数据塞入队列可以多对一、一对多队列任务被塞满队列有长度队列被塞满后的情况取决于设置的阻塞超时时间参数xTicksToWait。超时时间为 0 时函数会返回失败标志errQUEUE_FULL新数据会被直接丢弃队列中的现有数据不受任何影响。超时时间设为有限值xTicksToWait 0当前任务会被挂起Blocked 状态让出 CPU 给其他任务使用。在超时时间内如果有其他任务从该队列中读取并取走了一个数据腾出了空间当前任务会立刻被唤醒把新数据写入队列并返回pdPASS成功。如果超时时间到了队列依然是满的任务会被自动唤醒函数返回errQUEUE_FULL新数据抛弃写入失败。超时时间设为永久等待xTicksToWait portMAX_DELAY任务挂起态直到队列有空位。只有当其他任务从队列中取走数据腾出空间时该任务才会被唤醒并将数据写入。如果在 FreeRTOS 配置中启用了INCLUDE_vTaskSuspend它将无限期等待进入阻塞态永远不会因为超时而返回errQUEUE_FULL。基于队列实现的二进制信号量Binary Semaphores长度只有 1 的队列只有一位0/1 可以当成任务的开关。计数信号量Counting Semaphores长度任意。盘点事件记录事件发生了多少次。资源管理初始化时计数信号量初始化为某资源的数量有任务使用此资源时使用osSemaphoreAcquire()获取信号量信号量减一。资源使用完后可通过osSemaphoreRelease()释放信号量。信号量归零时任务无法获取此资源进入阻塞态。互斥锁Mutex用途互斥锁Mutex是 FreeRTOS 中用于保护共享资源的同步机制。它保证同一时刻只有一个任务访问某个资源避免多个任务同时操作导致数据冲突。常用于保护串口、CAN 总线、共享变量等资源确保系统运行稳定可靠。接下来进入 CubeMX 各部分配置和原理FreeRTOS 看着东西多其实大部分需要的都默认配置好了。开始Config parameters这里边的东西看着多其实绝大多数都不用管用默认的就行了下面列出一些可能需要自己配的东西。MPU/FPUENABLE_FPU浮点运算单元何时开启代码中有大量float/double浮点数计算如 PID 控制、DSP 数字信号处理、复杂滤波算法等且芯片硬件本身支持 FPU。何时关闭主要做逻辑控制、串口通信、GPIO 操作等没有密集浮点运算时。开启它会稍微增加任务切换时的堆栈Context Switch开销。Memory management settings这个需要配置一下TOTAL_HEAP_SIZE具体配置方法如下堆大小TOTAL_HEAP_SIZE怎么算看你要创建多少个任务每个任务的栈大小加起来再加上队列、信号量的内存一般 1KB乘以 1.5 倍留余量栈溢出检测选择 2非必须。User Constants这个相当于 CubeMX 的#define在User Constants中填入名称如STACK_LARGE与数值如1024之后生成代码后你的main.h文件里边会出现#define定义的你写的User Constants。4️⃣ User Constants用户常量— 定义常量便于管理打开User Constants标签在这里定义任务栈大小和优先级常量。为什么要定义常量改参数时只需改一个地方不用改代码。这个就是在手动创建任务时有用总体来说感觉比较鸡肋。Tasks and Queues任务和队列— 创建任务这应该是最重要的一个了。创建任务1. 任务句柄与结构体属性映射填入的参数界面上的Task Name、Priority、Stack Size等会被打包成一个叫osThreadAttr_t的结构体同时生成一个任务句柄C/* 1. 生成的任务句柄控制块指针 */ osThreadId_t myTask03Handle; /* 2. 映射弹窗里配置的所有属性 */ const osThreadAttr_t myTask03_attributes { .name myTask03, // -- 对应 Task Name .stack_size 128 * 4, // -- 对应 Stack Size (128 Words * 4 512 Bytes) .priority (osPriority_t) osPriorityLow, // -- 对应 Priority };2. 任务创建与启动代码在初始化函数中在MX_FREERTOS_Init()函数中系统会调用创建函数将任务拉起Cvoid MX_FREERTOS_Init(void) { /* ... 其他初始化 ... */ /* 映射 Parameter 和 动态/静态分配(Allocation) */ myTask03Handle osThreadNew( StartTask03, // -- 对应 Entry Function (入口函数) NULL, // -- 对应 Parameter (传递的参数) myTask03_attributes // -- 传入上面配置的属性 ); /* ... */ }3. 任务的执行函数实体代码末尾CubeMX 会在文件末尾自动为你生成这个任务的具体死循环函数模板C/* 对应 Entry Function 填写的函数名 */ void StartTask03(void *argument) { /* USER CODE BEGIN StartTask03 */ /* 函数的主循环你的代码写在这里 */ for(;;) { osDelay(1); // 避免死循环独占 CPU } /* USER CODE END StartTask03 */ }创建队列1. 队列句柄与属性声明C/* 1. 生成的队列句柄指针 */ osMessageQueueId_t myQueue01Handle; /* 2. 映射弹窗里的队列属性 */ const osMessageQueueAttr_t myQueue01_attributes { .name myQueue01 // -- 对应 Queue Name };2. 队列的创建在MX_FREERTOS_Init初始化函数中Cvoid MX_FREERTOS_Init(void) { /* ... */ /* 映射 Queue Size (16) 和 Item Size (uint16_t) */ myQueue01Handle osMessageQueueNew( 16, // -- 对应 Queue Size (队列深度) sizeof(uint16_t), // -- 对应 Item Size (单数据占用字节数) myQueue01_attributes // -- 传入上面定义的属性 ); /* ... */ }队列在代码里是怎么传数据的解释队列通过“内存数据拷贝”保存发送方数据当接收方读取空队列时自动阻塞有新数据进入时立刻唤醒执行。实际代码演示C/* 假设传一个遥控器摇杆结构体 */ typedef struct { int16_t vx; int16_t vy; } RC_Cmd_t; /* 任务 A发送方通信/接收任务 */ void CommsTask(void *argument) { RC_Cmd_t send_cmd {100, -50}; // 准备数据 // 将数据写入队列超时设为0满了直接跳过 osMessageQueuePut(myQueue01Handle, send_cmd, 0, 0); } /* 任务 B接收方底盘控制任务 */ void ChassisTask(void *argument) { RC_Cmd_t recv_cmd; // 从队列读数据若队列为空则阻塞死等osWaitForever不占 CPU if (osMessageQueueGet(myQ