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我一般用72MHz
  • configTICK_RATE_HZ 是系统心跳频率,1000Hz就是1ms一个tick
  • configTOTAL_HEAP_SIZE 是堆大小,根据你的RAM来定。20KB对于小项目够用了

避坑指南:我曾经在某个项目里把 configCPU_CLOCK_HZ 写成了8MHz,结果系统时钟完全乱套。任务调度像抽风一样,查了两天才发现是这里的问题。所以时钟频率一定要核对清楚!

3.3.4 修改中断处理

FreeRTOS接管了两个重要的中断:

  • SysTick_Handler — 系统滴答定时器,用于产生时间片
  • PendSV_Handler — 可挂起异常,用于任务切换

在port.c里,这两个中断已经被实现了。所以你需要把工程里原有的 SysTick_HandlerPendSV_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 验证移植是否成功

代码写完了,怎么知道移植对不对?我教你一个最简单的验证方法:

  1. 创建一个任务,里面放一个LED闪烁的循环
  2. 编译下载,看LED是否按预期闪烁
  3. 如果LED不亮,或者闪烁频率不对,说明移植有问题

你想想看,如果任务能正常调度,说明SysTick和PendSV都工作正常。如果LED不亮,大概率是中断向量没对上,或者时钟配置错了。

调试技巧:vTaskStartScheduler() 调用前后各放一个GPIO翻转。如果前一个翻转了,后一个没翻转,说明调度器启动失败了。这时候检查 configTOTAL_HEAP_SIZE 是否足够,或者中断优先级配置是否正确。

3.5 知识体系总览

为了让你对整个移植过程有个全局认识,我画了一张图:

FreeRTOS移植知识体系 应用层 用户任务、API调用(xTaskCreate, vTaskDelay等) RTOS核心层 任务调度、队列管理、时间管理(tasks.c, queue.c, timers.c) 移植层(port层) port.c(上下文切换)、portmacro.h(数据类型定义) 硬件层 STM32 Cortex-M3内核、NVIC、SysTick定时器 API调用 硬件无关 硬件相关

这张图很清楚地展示了:我们写的应用代码在最上层,FreeRTOS核心在中间,port层是连接核心和硬件的桥梁。移植要做的就是把这个桥梁搭好。

好了,基础移植就讲到这里。你按照上面的步骤走一遍,应该能在STM32上跑起FreeRTOS了。如果遇到问题,别慌,先检查中断向量,再检查时钟配置,最后看看堆大小够不够。这三个地方出问题的概率最大。


专注资料整理