花了很大功夫整理,看完这篇基本不需要看其他关于Free RTOS的介绍了!!!!!!
先介绍一个最精简的RTOS,不理解的地方往后翻翻,后面有几乎所有核心疑问的解答。正文开始!!!!!
-----------------------------------------------------------------------------------------------------------------------------
一、FreeRTOS最小系统
一个最基础的FreeRTOS系统可以精简到只包含任务调度和延时功能,几乎接近裸机开发,但仍保留RTOS的核心特性,以下是一个极简案例,实现LED闪烁功能:
1. 极简FreeRTOS系统需求
-
仅需2个文件:
main.c+FreeRTOSConfig.h -
只使用3个API:
xTaskCreate(),vTaskDelay(),vTaskStartScheduler() -
内存占用<6KB(包括栈)
-
保留抢占式调度
2. 完整代码实现
2.1 FreeRTOSConfig.h(最小配置)
#pragma once
#define configUSE_PREEMPTION 1 // 必须为1(启用抢占)
#define configUSE_IDLE_HOOK 0 // 关闭空闲钩子
#define configUSE_TICK_HOOK 0 // 关闭时钟钩子
#define configCPU_CLOCK_HZ ((unsigned long)72000000) // CPU频率
#define configTICK_RATE_HZ 1000 // 时钟节拍1kHz
#define configMAX_PRIORITIES 2 // 只需2个优先级
#define configMINIMAL_STACK_SIZE 64 // 最小任务栈
#define configTOTAL_HEAP_SIZE 1024 // 堆大小1KB
2.2 main.c(核心逻辑)
#include "FreeRTOS.h"
#include "task.h"
#include "stm32f1xx_hal.h" // 假设使用STM32F103
// LED引脚定义
#define LED_PIN GPIO_PIN_13
#define LED_PORT GPIOC
// 任务函数原型
void vBlinkTask(void *pvParameters);
int main(void) {
// 硬件初始化(裸机部分)
HAL_Init();
__HAL_RCC_GPIOC_CLK_ENABLE();
GPIO_InitTypeDef gpio = {0};
gpio.Pin = LED_PIN;
gpio.Mode = GPIO_MODE_OUTPUT_PP;
HAL_GPIO_Init(LED_PORT, &gpio);
// FreeRTOS启动(RTOS部分)
xTaskCreate(vBlinkTask, "Blink", configMINIMAL_STACK_SIZE, NULL, 1, NULL);
vTaskStartScheduler(); // 永远不会返回①
while(1); // 备用
}
// 最简单的任务:LED闪烁
void vBlinkTask(void *pvParameters) {
while(1) {
HAL_GPIO_Toggle(LED_PORT, LED_PIN);
vTaskDelay(pdMS_TO_TICKS(500)); // 500ms延时
}
}
3. 系统运行
3.1 调度时序
时间轴(ms): 0 500 1000 1500
Blink任务: [任务运行]───休眠───[任务运行]───休眠───...
↓ toggle LED ↓ toggle LED
硬件行为: LED亮 LED灭
这个最小系统执行起来的情况是,一直停留在这个小灯翻转任务中,在任务中的主循环里一直重复执行翻转和延时。通过vTaskDelay()实现了真正的任务切换。
3.2 任务组成
该最小系统的任务组成:
|
任务类型 |
是否可见 |
优先级 |
触发条件 |
作用 |
|---|---|---|---|---|
|
用户任务 |
用户创建 |
≥1 |
显式调用 |
实现业务逻辑(如LED翻转) |
|
空闲任务(IDLE) |
自动创建 |
0 |
所有用户任务阻塞时 |
清理内存/低功耗 |
延时阻塞不算任务,他隐性存在,没有优先级的概念,作用是任务挂起,释放CPU。
-
关键区别:
-
延时不是独立任务,而是任务状态切换的机制(从运行态→阻塞态)。
-
当任务调用
vTaskDelay()时,该任务被移入延时阻塞列表,CPU转而执行其他就绪任务(如空闲任务)。
-
3.2 任务切换
在这个最小系统中,当调用vTaskDelay()时:

这里延时机制的作用后面有详细解释。
4. 与裸机的关键区别
|
特性 |
裸机实现 |
本FreeRTOS方案 |
|---|---|---|
|
代码结构 |
while(1)循环 |
独立任务函数 |
|
延时精度 |
依赖阻塞延时(不精确) |
系统节拍调度(±1ms误差) |
|
扩展性 |
添加功能需修改主循环 |
直接创建新任务 |
|
资源占用 |
仅硬件所需资源 |
额外内存资源 |
*额外资源用于维护:任务上下文(TCB+栈)、调度器数据结构、系统节拍管理等。
二、按需扩展模块
1. 任务间通信(IPC)
(1) 队列(Queue)
-
作用:任务间传递数据(FIFO)
(2) 信号量(Semaphore)
-
作用:任务同步 / 资源管理
(3) 任务通知(Task Notification)
-
作用:轻量级任务间通信(类似信号量,但更高效)
2. 中断管理(ISR)
(1) 外设中断(UART/GPIO/Timer)
-
作用:硬件事件快速响应
(2) 软件定时器(Software Timer)
-
作用:周期性任务(如定时采集数据)
3. 资源保护(互斥锁 / 临界区)
(1) 互斥锁(Mutex)
-
作用:保护共享资源(如FIFO、全局变量)
(2) 临界区(Critical Section)
-
作用:短时间禁用中断,保护关键代码
4. 内存管理(动态分配 / 静态分配)
(1) 动态内存分配
-
作用:运行时动态申请内存(如
pvPortMalloc)
(2) 静态内存分配
-
作用:预分配任务栈 / 队列(减少运行时碎片)
5. 低功耗优化(Idle Hook)
(1) 空闲任务钩子(Idle Hook)
-
作用:CPU空闲时进入低功耗模式
6. 调试与监控
(1) 任务状态监控
-
作用:查看任务栈使用、运行时间等
三、一些细节帮助理解
1. 关于任务切换
RTOS必存在任务切换 → 任务切换必然伴随状态迁移 → 状态迁移需由任意事件触发(延时只是最基础事件,也可以是信号量/消息队列) → 事件驱动的状态迁移是RTOS本质
FreeRTOS任务的四种核心状态:

*关键点:
-
主动释放CPU = 从运行态→阻塞态(如调用
vTaskDelay()) -
被动释放CPU = 从运行态→就绪态(如被高优先级任务抢占)
-
挂起态是手动干预的特殊状态,不参与调度
2. 任务调度方式
configUSE_PREEMPTION=1:抢占式调度,高优先级任务可以立即抢占低优先级任务(如中断唤醒高优先级任务,低优先级包括空闲任务),但任务也可以因为其他原因主动让出CPU,与我们的最小系统案例不冲突。
configUSE_PREEMPTION=0:退化为协作式调度,任务必须主动让出CPU(如调用taskYIELD()或vTaskDelay(),也就是触发任务状态切换),这个就必须主动让出啦。
二者是互斥的基础调度策略,可叠加其他机制(如时间片轮转)
3. 关于永不返回的函数 vTaskStartScheduler();①
void vTaskStartScheduler() {
// 初始化调度器...
xPortStartScheduler(); // 硬件相关启动代码
/* 此处永远不会被执行 */
}
- 这是RTOS设计的核心特征,这种设计确保了系统资源的可靠管理和任务调度的确定性。在开发中应将其视为嵌入式系统的永久运行入口点。
- 也就是备用的那个while(1)永不执行;是死代码;开发者禁止在这条函数后面加任何内容。
- 启动调度器意味着将CPU控制权永久移交给RTOS内核;
- 多数嵌入式平台没有"退出OS"的概念
3.1 调度器运行机制
-
接管硬件:启动调度器后,FreeRTOS会:
-
配置系统节拍定时器(如SysTick)
-
初始化任务栈指针
-
触发第一个任务上下文切换
-
-
进入调度循环:
StartScheduler:
启动节拍定时器;
启用线程模式+PSP栈指针;
加载第一个任务控制块;
恢复任务上下文;
跳转到任务代码;从此RTOS进入无限调度循环,除非手动调用
vTaskEndScheduler();
3.2 与裸机开发的对比
|
行为 |
裸机main() |
FreeRTOS的main() |
|---|---|---|
|
程序流 |
while(1)循环 |
由调度器接管控制权 |
|
硬件控制权 |
始终在用户代码 |
移交RTOS内核 |
|
退出方式 |
复位或断电 |
需主动终止调度器 |
4. 优先级为0的空闲任务IDLE:
4.1 IDLE由内核创建
在vTaskStartScheduler()中,内核强制初始化空闲任务,因此只要调用该函数,即使没有用户任务,FreeRTOS也会强制创建空闲任务(用于内存清理、低功耗模式等),提供系统基线调度。
4.2 空闲任务的隐身特性
|
属性 |
描述 |
|---|---|
|
任务名 |
|
|
可见性 |
用户不可直接操作,但可通过 |
|
栈大小 |
使用 |
|
TCB位置 |
存储在 |
- 两个优先级是FreeRTOS的最低硬件要求(空闲任务+用户任务),因此在最小配置中,也要设置
configMAX_PRIORITIES=2。
4.3 与用户任务的协作
空闲任务优先级永远为0(最低);
硬件级约束:
-
用户任务优先级必须 ≥1
-
任何优先级>0的任务都能抢占空闲任务
-
最高优先级实际由
configMAX_PRIORITIES-1决定。
不管是什么项目,RTOS的任务都至少有2个,单任务RTOS是伪命题;
-
单任务FreeRTOS本质是伪命题,实际仍有两个任务(用户+空闲)
-
没有真正的多任务切换,但保留了RTOS基础设施
-
与裸机的区别在于:保留了任务抽象和调度框架;可随时扩展为真正的多任务系统
4.4 调度机制

空闲任务的存在意义:
-
防止CPU死锁:确保调度器永远有任务可运行
-
资源回收:清理
vTaskDelete()删除的任务栈 -
低功耗基础:在
vApplicationIdleHook()中实现睡眠
5. 关于延时机制
5.1 多任务时间共享的基础:延时机制
*在这个最小系统的例子中,程序是通过调用vTaskDelay()实现了任务状态的切换。这就是和裸机开发的本质区别,通过切换任务状态,让CPU去执行其他任务(这里是空闲任务),而裸机的HAL_Delay()是纯阻塞。在这个最小系统的例子中,想要实现的目标是让小灯翻转,而不是延时,延时只是为了让任务状态切换的一种方法,可以替换成别的。
如果直接取消延时功能,也就是移除所有vTaskDelay(),那么会造成:任务无法主动让出CPU,变成纯协作式调度,该任务会独占CPU,其他任务无法运行(不考虑中断),也就是纯中断+事件驱动,这就相当于裸机开发了。如果要保留RTOS特性,那么就需要taskYIELD()手动切换任务,但仍需某种调度机制,如时间片轮转或优先级抢占。
5.2 节拍中断
节拍中断是RTOS的核心,即使最简单的vTaskDelay()也依赖它;可以不用vTaskDelay,但节拍中断仍然用于任务超时管理、时间统计等。
作用:即使最简单的vTaskDelay()也依赖它,取消后RTOS失去时间管理功能。
如果完全取消节拍中断,即vTaskDelay()失效,在本篇的例子中,任务将无法被唤醒,调度器无法工作。

-
替代方案:
-
改用 事件驱动(如信号量、队列)唤醒任务。
-
但这样已经脱离了RTOS的时间管理功能,接近裸机开发。
-
6. 空闲钩子和时间钩子

808

被折叠的 条评论
为什么被折叠?



