移植 FreeRTOS 的主要原因是将其核心功能与目标硬件平台结合,从而为嵌入式开发提供实时操作系统支持,解决裸机开发中的不足。
准备
- 开发板。案例是以立创天空星为开发板。
- Windows 系统的电脑。当前是以 Win11 的电脑来实现案例的。
- Keil 开发工具。并且已经安装好 GD32 依赖环境。
- FreeRTOS 源码包。下载地址为: https://github.com/FreeRTOS/FreeRTOS/releases
我们下载好 FreeRTOS 源码包之后可以参照官方示例代码,也就是 Demo 中的例程进行参考。
移植
熟悉目录结构
从顶部开始,下载被分割成两个子目录:FreeRTOS 和 FreeRTOS-Plus。如下所示:
1 | +-FreeRTOS-Plus // 包含[FreeRTOS Plus](/文档/03库/02 FreeRTOS Plus/01简介)组件和演示项目。 |
FreeRTOS:包含 FreeRTOS 内核(Kernel)的源代码(例如 task.c, queue.c, timers.c 等核心文件)。
FreeRTOS-Plus:包含 FreeRTOS Plus 系列库和组件,这些是对 FreeRTOS 内核功能的扩展。
FreeRTOS 内核目录结构
1 | FreeRTOS |
FreeRTOS/Source 目录的结构如下所示
1 | FreeRTOS |
FreeRTOS/Demo 目录的结构如下所示
1 | FreeRTOS |
拷贝
FreeRTOS 官方提供了丰富且详细的示例代码,我们只需根据需求拷贝并适配即可。
在 FreeRTOS/Demo 目录下,有大量针对不同架构平台的示例项目。我们需要找到与当前使用的架构和开发环境匹配的示例代码进行移植。
当前我们的开发环境为 Keil,编译器是 GCC,目标处理器的内核架构为 Cortex-M4F。按照这些条件选择合适的示例代码即可。
CORTEX_M4F_STM32F407ZG-SK 这里先不要参考这个, SK 指代的是 STM32 官方自带的 IDE,它可以在创建项目的时候自动添加 FreeRTOS 相关的库文件,所有我们打开会发现除了一个 .h 外它不包含任何与 FreeRTOS 相关的库文件;
在示例代码中我们可以找 CORTEX_M4F_CEC1302_Keil_GCC 这个示例代码进行参考,通过 Keil 将其打开:
会发现它包含的文件如下:
- FreeRTOS/Source/include 目录下所有的头文件
- FreeRTOS/Source/Portable 目录下处理器相关的代码
- FreeRTOS/Source 目录下 FreeRTOS 处理器相关代码
- FreeRTOSConfig.h 头文件;
我们参照例程将这些文件拷贝到我们自己对应的 Middleware/FreeRTOS 目录下;
1 | /FreeRTOS |
简单点来说就是将 FreeRTOS/Source 下所有的文件都复制到我们 FreeRTOS 目录中;删除 Portable 中不需要的文件,再将 FreeRTOSConfig.h 放置在 main.c 同级目录下即可;
运行
将上述 FreeRTOS 文件添加到到 Keil 工程中,添加后的目录如下:
添加完成后运行,解决对应错误即可;
配置时钟
在 FreeRTOS 成功移植并且不报错之后,系统就已经具备了基本的任务调度能力,但要让它完全正常运行,还需要完成一些硬件初始化和配置工作。这些配置工作通常与时钟、外设、系统节拍等相关,确保 FreeRTOS 的调度器能够正确地运行并与硬件配合。
系统时钟
配置当前 FreeRTOS 的 CPU 主频(即系统时钟频率)。
1 |
SystemCoreClock 通常是由硬件库定义的全局变量,它会根据系统时钟初始化过程自动更新。
SysTick
滴答时钟用于产生系统节拍中断,驱动 FreeRTOS 的任务调度器。
滴答时钟的频率由 configTICK_RATE_HZ 定义;
1 |
这里的 1000
表示每秒产生 1000 次节拍中断,即 1 毫秒一次。
滴答频率的选择需要根据应用需求决定:
- 高频(如 1000 Hz):调度更频繁,但 CPU 负载增加。
- 低频(如 100 Hz):调度较少,适合实时性要求不高的场景。
时间片的长度由滴答时钟(tick rate)与调度方式决定。如果启用了时间片轮转,每个任务的时间片是基于系统的滴答周期来分配的,如果启用了抢占式调度(configUSE_PREEMPTION 1)系统会在每个滴答周期内检查是否有更高优先级的任务需要执行,确保高优先级任务及时获得 CPU 时间。
由于滴答时钟已经交予 FreeRTOS 进行是使用,我们就不能在 main 中初始化 SysTick 了(systick_config()),因为二者同时去操作 SysTick 寄存器就会产生一些错误问题;
所以这里我们还是和前面一样通过宏进行判断,如果移植了 FreeRTOS 就让 FreeRTOS 控制滴答时钟的配置,否则就是用我们芯片自己的;
自定义延时函数
在某些情况下我们需要使用我们自定义的延时函数,这里我们还是和之前一样过读取 SysTick 定时器的计数值,利用系统的时钟频率来精确计算经过的时间。
1 | void delay_1us(uint32_t count) { |
问题
一、我没有调用 FreeRTOS 相关函数他为啥会自动运行并报错呢
FreeRTOS 的部分代码(例如任务调度器、内存管理等)可能依赖于特定的硬件初始化或全局配置。如果你的工程中没有正确配置它的基础环境,编译或链接时就可能报错。
FreeRTOS 的部分代码可能会在工程初始化或链接时被调用,即使未在 main
中使用。
二、error: use of undeclared identifier ‘SystemCoreClock’错误
1 |
|
在 port.c 文件中,这个变量只有在 ICCARM 这个宏存在,也就是说编译器是 ICC 的时候才执行其中的代码,而我们是 Keil 编译器,所以我们需要修改为:
1 |
|
三、重复定义错误
1 | // 中断处理函数重复定义 |
中断处理函数重复定义错误原因:
FreeRTOS 的移植代码(port.c 文件)会定义 Cortex-M 核心的中断处理函数,例如:
- SysTick_Handler:用于系统节拍定时器(SysTick)的中断服务。
- PendSV_Handler:用于上下文切换。
- SVC_Handler:用于启动第一个任务。
同时,你的工程中可能有一个文件(例如 gd32f4xx_it.c),也定义了这些中断处理函数,导致冲突。
我们希望的是,如果移植了 FreeRTOS 就使用 FreeRTOS 中的函数,否则就使用我们自己函数;这就可以通过宏定义来实现:
当我们移植 FreeRTOS 时,我们可以手动在 Keil 中添加一个自定义宏( SYS_SUPPORT_OS 这个宏定义),将重复的函数包裹起来;
1 | // gd32f407xx.it.h |
四、变量未定义错误
1 | Error: L6218E: Undefined symbol vApplicationStackOverflowHook (referred from tasks.o). |
上述错误表明你的工程中缺少一些 FreeRTOS 要求的钩子函数的定义。这些函数用于处理特定的系统事件,如任务堆栈溢出、空闲任务的操作、系统时钟节拍钩子等。虽然它们在某些场景中是可选的,但如果在配置文件 FreeRTOSConfig.h 中启用了相关功能而未定义这些钩子函数,就会导致链接时的 undefined symbol 错误。
这里我们以 vApplicationStackOverflowHook 为例了解如何解决
1 | // task.h |
可以看到 vApplicationStackOverflowHook 的声明与使用是通过 configCHECK_FOR_STACK_OVERFLOW 这个宏是否 > 0 来控制的,所以我们这里有两个解决方案,1 是将 configCHECK_FOR_STACK_OVERFLOW 定义小于 0,而是手动实现这个函数;这里我们简单一点我们将这个宏的值改为 0;
其他三个也是同理;