3. FreeRTOS移植实战(上):源码结构分析、port层详解、在STM32上完成基础移植
说实话,很多朋友学RTOS卡就卡在移植这一步。源码下载下来,打开一看,好家伙,几十个文件夹,几百个文件,瞬间就懵了。别急,今天我就带你把这层窗户纸捅破。
移植FreeRTOS,说白了就是让这个操作系统能在你的芯片上跑起来。STM32是ARM Cortex-M内核,FreeRTOS官方已经帮我们做好了大部分工作。我们要做的,就是找到正确的文件,配置好参数,然后编译通过。
核心思想:移植的本质是「适配」。把通用的RTOS代码,和具体的芯片、编译器绑定在一起。
3.1 源码结构:别被文件夹吓到
我们先看看FreeRTOS的源码长什么样。你下载的压缩包解压后,会看到这么几个关键目录:
- FreeRTOS/Source/ — 核心源码,所有平台通用
- FreeRTOS/Source/portable/ — 移植层,不同芯片的适配代码
- FreeRTOS/Source/include/ — 头文件,API声明都在这里
- FreeRTOS/Demo/ — 官方示例,参考价值很大
我个人习惯,从来不去动 Source/ 目录下的通用代码。那些是FreeRTOS团队维护的,我们只管用。真正需要动手的,是 portable/ 这个文件夹。
小技巧:你只需要关注三个文件:FreeRTOSConfig.h(配置)、port.c(移植实现)、portmacro.h(移植宏定义)。其他文件基本不用改。
3.2 port层详解:RTOS和硬件的桥梁
port层,全称是「移植层」。它负责把RTOS的通用操作,翻译成芯片能听懂的语言。举个例子:
- RTOS说「我要切换任务」→ port层就去操作CPU的寄存器,保存当前任务现场,恢复新任务现场
- RTOS说「我要关中断」→ port层就去操作CPU的中断控制寄存器
为什么会需要这一层?因为不同芯片的寄存器、指令集、中断控制器都不一样。ARM Cortex-M3/M4/M7用一套方式,RISC-V用另一套,8051又完全不同。
对于STM32(Cortex-M3/M4),FreeRTOS官方已经提供了现成的port文件。你只需要在 portable/GCC/ARM_CM3/ 或 portable/RVDS/ARM_CM3/ 里找到对应编译器的版本就行。
注意:编译器不同,port文件也不同。Keil用RVDS版本,GCC用GCC版本,IAR用IAR版本。千万别搞混了,否则编译报错会让你怀疑人生。
3.3 基础移植步骤:手把手教你
好了,理论说完了,咱们直接上手。我以STM32F103(Cortex-M3) + Keil MDK为例,带你走一遍完整流程。
3.3.1 准备工程
首先,你得有一个能正常运行的裸机工程。GPIO能点灯,定时器能中断,这就够了。我建议用STM32CubeMX生成一个最小工程,省事。
3.3.2 添加FreeRTOS源码
把以下文件复制到你的工程目录里:
FreeRTOS/Source/
├── tasks.c
├── list.c
├── queue.c
├── timers.c
├── event_groups.c
├── croutine.c (可选,很少用)
└── portable/
└── RVDS/
└── ARM_CM3/
├── port.c
└── portmacro.h
嗯,这里要注意:portmacro.h 虽然放在portable目录下,但编译时它会被当作头文件引用。所以你的编译器头文件路径里,要包含这个目录。
3.3.3 配置FreeRTOSConfig.h
这个文件是FreeRTOS的「开关面板」。你需要从Demo目录里找一个现成的,然后根据自己的需求修改。我一般这样配:
#define configUSE_PREEMPTION 1
#define configCPU_CLOCK_HZ ((unsigned long)72000000)
#define configTICK_RATE_HZ ((TickType_t)1000)
#define configMAX_PRIORITIES (5)
#define configMINIMAL_STACK_SIZE ((unsigned short)128)
#define configTOTAL_HEAP_SIZE ((size_t)(20 * 1024))
#define configUSE_IDLE_HOOK 0
#define configUSE_TICK_HOOK 0
这里有几个关键点:
configCPU_CLOCK_HZ必须和你的系统时钟一致。STM32F103我一般用72MHzconfigTICK_RATE_HZ是系统心跳频率,1000Hz就是1ms一个tickconfigTOTAL_HEAP_SIZE是堆大小,根据你的RAM来定。20KB对于小项目够用了
避坑指南:我曾经在某个项目里把 configCPU_CLOCK_HZ 写成了8MHz,结果系统时钟完全乱套。任务调度像抽风一样,查了两天才发现是这里的问题。所以时钟频率一定要核对清楚!
3.3.4 修改中断处理
FreeRTOS接管了两个重要的中断:
- SysTick_Handler — 系统滴答定时器,用于产生时间片
- PendSV_Handler — 可挂起异常,用于任务切换
在port.c里,这两个中断已经被实现了。所以你需要把工程里原有的 SysTick_Handler 和 PendSV_Handler 注释掉,否则会重复定义。
另外,还有一个 SVC_Handler,用于启动第一个任务。同样要注释掉。
3.3.5 修改启动文件
在STM32的启动文件(startup_stm32f103xe.s)里,找到这三个中断向量:
DCD SysTick_Handler ; SysTick Handler
DCD PendSV_Handler ; PendSV Handler
DCD SVC_Handler ; SVC Handler
确保它们指向FreeRTOS的实现。如果你用的是官方port文件,名字是匹配的,不用改。但如果你自己改了函数名,这里就要对应修改。
3.4 验证移植是否成功
代码写完了,怎么知道移植对不对?我教你一个最简单的验证方法:
- 创建一个任务,里面放一个LED闪烁的循环
- 编译下载,看LED是否按预期闪烁
- 如果LED不亮,或者闪烁频率不对,说明移植有问题
你想想看,如果任务能正常调度,说明SysTick和PendSV都工作正常。如果LED不亮,大概率是中断向量没对上,或者时钟配置错了。
调试技巧:在 vTaskStartScheduler() 调用前后各放一个GPIO翻转。如果前一个翻转了,后一个没翻转,说明调度器启动失败了。这时候检查 configTOTAL_HEAP_SIZE 是否足够,或者中断优先级配置是否正确。
3.5 知识体系总览
为了让你对整个移植过程有个全局认识,我画了一张图:
这张图很清楚地展示了:我们写的应用代码在最上层,FreeRTOS核心在中间,port层是连接核心和硬件的桥梁。移植要做的就是把这个桥梁搭好。
好了,基础移植就讲到这里。你按照上面的步骤走一遍,应该能在STM32上跑起FreeRTOS了。如果遇到问题,别慌,先检查中断向量,再检查时钟配置,最后看看堆大小够不够。这三个地方出问题的概率最大。