移植 FreeRTOS 的主要原因是将其核心功能与目标硬件平台结合,从而为嵌入式开发提供实时操作系统支持,解决裸机开发中的不足。
准备
开发板。案例是以立创天空星为开发板。
Windows 系统的电脑。当前是以 Win11 的电脑来实现案例的。
Keil 开发工具。并且已经安装好 GD32 依赖环境。
FreeRTOS 源码包。下载地址为: https://github.com/FreeRTOS/FreeRTOS/releases
我们下载好 FreeRTOS 源码包之后可以参照官方示例代码,也就是 Demo 中的例程进行参考。
移植 FreeRTOS 移植指南
熟悉目录结构 从顶部开始,下载被分割成两个子目录:FreeRTOS 和 FreeRTOS-Plus。如下所示:
1 2 3 +-FreeRTOS-Plus | +-FreeRTOS
FreeRTOS: 包含 FreeRTOS 内核(Kernel)的源代码(例如 task.c, queue.c, timers.c 等核心文件)。
FreeRTOS-Plus: 包含 FreeRTOS Plus 系列库和组件,这些是对 FreeRTOS 内核功能的扩展。
FreeRTOS 内核目录结构
1 2 3 4 5 FreeRTOS | +-Demo | +-Source
FreeRTOS/Source 目录的结构如下所示
1 2 3 4 5 6 7 8 9 10 FreeRTOS | +-Source | +-include | +-Portable | +-MemMang +-....
FreeRTOS/Demo 目录的结构如下所示
1 2 3 4 5 6 7 FreeRTOS | +-Demo | +-Common The demo application files that are used by all the demos. +-Dir x The demo application build files for port x +-Dir y The demo application build files for port y
拷贝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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 /FreeRTOS │ ├── /portable │ ├── /GCC/ARM_CM4F # 针对 ARM Cortex-M4 架构和 GCC 编译器的移植代码 │ │ ├── port.c │ │ └── portmacro.h │ └── /MemMang # 内存管理相关代码 │ └── heap_4.c │ ├── /include # FreeRTOS 公共头文件 │ ├── FreeRTOS.h │ └── … │ ├── croutine.c # 协程相关代码 ├── event_groups.c # 事件组相关代码 ├── list .c # 链表操作代码 ├── queue .c # 队列操作代码 ├── stream_buffer.c # 流缓冲区相关代码 ├── tasks.c # 任务管理相关代码 └── timers.c # 定时器管理相关代码 /User │ └── FreeRTOSConfig.h # FreeRTOS 配置文件
简单点来说就是将 FreeRTOS/Source 下所有的文件都复制到我们 FreeRTOS 目录中;删除 Portable 中不需要的文件,再将 FreeRTOSConfig.h 放置在 main.c 同级目录下即可;
将上述 FreeRTOS 文件添加到到 Keil 工程中,添加后的目录如下:
添加完成后运行,解决对应错误即可。
添加 FreeRTOSConfig.h 配置文件 FreeRTOSConfg.h 是 FreeRTOS 操作系统的配置文件,FreeRTOS 操作系统是可裁剪的,用户可以根据需求对 FreeRTOS 进行裁剪,裁剪掉不需要用到的 FreeRTOS 功能,以此来节约 MCU中寸土寸金的内存资源。
该文件可以从示例代码中进行复制。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 #ifndef FREERTOS_CONFIG_H #define FREERTOS_CONFIG_H #if defined(__ICCARM__) || defined(__CC_ARM) || defined(__GNUC__) #include <stdint.h> extern uint32_t SystemCoreClock; #endif #define configUSE_PREEMPTION 1 #define configUSE_IDLE_HOOK 0 #define configUSE_TICK_HOOK 0 #define configCPU_CLOCK_HZ ( SystemCoreClock ) #define configTICK_RATE_HZ ( ( TickType_t ) 1 ) #define configMAX_PRIORITIES ( 5 ) #define configMINIMAL_STACK_SIZE ( ( unsigned short ) 128 ) #define configTOTAL_HEAP_SIZE ( ( size_t ) ( 17 * 1024 ) ) #define configMAX_TASK_NAME_LEN ( 16 ) #define configUSE_TRACE_FACILITY 0 #define configUSE_16_BIT_TICKS 0 #define configIDLE_SHOULD_YIELD 1 #define INCLUDE_vTaskPrioritySet 1 #define INCLUDE_uxTaskPriorityGet 1 #define INCLUDE_vTaskDelete 1 #define INCLUDE_vTaskCleanUpResources 0 #define INCLUDE_vTaskSuspend 1 #define INCLUDE_vTaskDelayUntil 1 #define INCLUDE_vTaskDelay 1 #define configKERNEL_INTERRUPT_PRIORITY 255 #define configMAX_SYSCALL_INTERRUPT_PRIORITY 191 #define configLIBRARY_KERNEL_INTERRUPT_PRIORITY 15 #define xPortPendSVHandler PendSV_Handler #define vPortSVCHandler SVC_Handler #define xPortSysTickHandler SysTick_Handler #endif
修改中断相关文件 来到 stm32f10x_it.c 文件中。修改三个函数 SVC_Handler, PendSV_Handler, SysTick_Handler 将函数通过宏定义包裹:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 #ifndef SYS_SUPPORT_OS void SVC_Handler (void ) { } #endif #ifndef SYS_SUPPORT_OS void PendSV_Handler (void ) { } #endif #ifndef SYS_SUPPORT_OS void SysTick_Handler (void ) { } #endif
配置时钟 在 FreeRTOS 成功移植并且不报错之后,系统就已经具备了基本的任务调度能力,但要让它完全正常运行,还需要完成一些硬件初始化和配置工作。这些配置工作通常与时钟、外设、系统节拍等相关,确保 FreeRTOS 的调度器能够正确地运行并与硬件配合。
系统时钟 配置当前 FreeRTOS 的 CPU 主频 (即系统时钟频率)。
1 2 3 4 5 6 7 8 #if defined(__ICCARM__) || defined(__CC_ARM) || defined(__GNUC__) #include <stdint.h> extern uint32_t SystemCoreClock; #endif #define configCPU_CLOCK_HZ ( SystemCoreClock )
SystemCoreClock 通常是由硬件库定义的全局变量,它会根据系统时钟初始化过程自动更新。
SysTick 滴答时钟用于产生系统节拍中断,驱动 FreeRTOS 的任务调度器。
滴答时钟的频率由 configTICK_RATE_HZ 定义;
1 #define configTICK_RATE_HZ ( ( TickType_t ) 1000 )
这里的 1000 表示每秒产生 1000 次节拍中断,即 1 毫秒一次。
滴答频率的选择需要根据应用需求决定:
高频(如 1000 Hz):调度更频繁,但 CPU 负载增加。
低频(如 100 Hz):调度较少,适合实时性要求不高的场景。
时间片的长度 由滴答时钟(tick rate)与调度方式决定。如果启用了时间片轮转 ,每个任务的时间片是基于系统的滴答周期来分配的,如果启用了抢占式调度(configUSE_PREEMPTION 1) 系统会在每个滴答周期内检查是否有更高优先级的任务需要执行,确保高优先级任务及时获得 CPU 时间。
由于滴答时钟已经交予 FreeRTOS 进行是使用,我们就不能在 main 中初始化 SysTick 了(systick_config()),因为二者同时去操作 SysTick 寄存器就会产生一些错误问题;
所以这里我们还是和前面一样通过宏进行判断,如果移植了 FreeRTOS 就让 FreeRTOS 控制滴答时钟的配置,否则就是用我们芯片自己的;
自定义延时函数 在某些情况下我们需要使用我们自定义的延时函数,这里我们还是和之前一样过读取 SysTick 定时器的计数值 ,利用系统的时钟频率来精确计算经过的时间。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 #include "stm32f10x.h" #include "systick.h" volatile static uint32_t delay;void systick_config (void ) { if (SysTick_Config(SystemCoreClock / 1000U )) { while (1 ) { } } NVIC_SetPriority(SysTick_IRQn, 0x00 U); } #ifndef SYS_SUPPORT_OS void delay_1ms (uint32_t count) { delay = count; while (0U != delay) { } } void delay_decrement (void ) { if (0U != delay) { delay--; } } #else void delay_1us (uint32_t count) { uint32_t ticks; uint32_t told, tnow, reload, tcnt = 0 ; reload = SysTick->LOAD; ticks = count * (SystemCoreClock / 1000000 ); told = SysTick->VAL; while (1 ) { tnow=SysTick->VAL; if (tnow != told) { if (tnow<told) tcnt+=told-tnow; else tcnt+=reload-tnow+told; told=tnow; if (tcnt>=ticks)break ; } } } void delay_1ms (uint32_t count) { uint32_t i; for (i=0 ; i<count; i++) { delay_1us(1000 ); } } #endif
验证FreeRTOS移植是否正常 在”魔术棒中进行如下设置”
编译没问题后我们点击图中右上角的调试按钮,在调试界面中点击左上角的”View” -> “Analysis Windows” 选择 “Logic Analyzer”。
在 main.c 中分别右键 a1,a2,选择 “Add ‘a1’ to” -> “Analyzer”。
设置 a1 ,a2 只显示 0 和 1,在 Logic Analyzer 中分别右键然后将默认选项的 “Analog” 改为 “bit”;
点击左上角 “Run”运行可以观测到如下结果:
注意查看切换时间是否一与 FreeRTOSConfig.h 中设置的是否一致;
问题 一、我没有调用 FreeRTOS 相关函数他为啥会自动运行并报错呢
FreeRTOS 的部分代码(例如任务调度器、内存管理等)可能依赖于特定的硬件初始化或全局配置。如果你的工程中没有正确配置它的基础环境,编译或链接时就可能报错。
FreeRTOS 的部分代码可能会在工程初始化或链接时被调用,即使未在 main
中使用。
二、error: use of undeclared identifier ‘SystemCoreClock’错误
1 2 3 4 #ifdef __ICCARM__ #include <stdint.h> extern uint32_t SystemCoreClock; #endif
在 port.c 文件中,这个变量只有在 ICCARM 这个宏存在,也就是说编译器是 ICC 的时候才执行其中的代码,而我们是 Keil 编译器,所以我们需要修改为:
1 2 3 4 #if defined(__ICCARM__)||defined(__CC_ARM)||defined(__GNUC__) #include <stdint.h> extern uint32_t SystemCoreClock; #endif
三、重复定义错误
1 2 3 4 Error: L6200E: Symbol SysTick_Handler multiply defined (by port.o and gd32f4xx_it.o) . Error: L6200E: Symbol SVC_Handler multiply defined (by port.o and gd32f4xx_it.o) . Error: L6200E: Symbol PendSV_Handler multiply defined (by port.o and gd32f4xx_it.o) .
中断处理函数重复定义错误原因:
FreeRTOS 的移植代码(port.c 文件)会定义 Cortex-M 核心的中断处理函数,例如:
SysTick_Handler:用于系统节拍定时器(SysTick)的中断服务。
PendSV_Handler:用于上下文切换。
SVC_Handler:用于启动第一个任务。
同时,你的工程中可能有一个文件(例如 gd32f4xx_it.c),也定义了这些中断处理函数,导致冲突。
我们希望的是,如果移植了 FreeRTOS 就使用 FreeRTOS 中的函数,否则就使用我们自己函数;这就可以通过宏定义来实现:
当我们移植 FreeRTOS 时,我们可以手动在 Keil 中添加一个自定义宏( SYS_SUPPORT_OS 这个宏定义),将重复的函数包裹起来;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 #ifndef SYS_SUPPORT_OS void SVC_Handler (void ) ;void PendSV_Handler (void ) ;void SysTick_Handler (void ) ;#endif #ifndef SYS_SUPPORT_OS void SVC_Handler (void ) { while (1 ) { } } void PendSV_Handler (void ) { while (1 ) { } } void SysTick_Handler (void ) { delay_decrement(); } #endif
四、变量未定义错误
1 2 3 4 Error: L6218E: Undefined symbol vApplicationStackOverflowHook (referred from tasks.o) . Error: L6218E: Undefined symbol vApplicationIdleHook (referred from tasks.o) . Error: L6218E: Undefined symbol vApplicationTickHook (referred from tasks.o) . Error: L6218E: Undefined symbol vApplicationMallocFailedHook (referred from heap_4_1.o) .
上述错误表明你的工程中缺少一些 FreeRTOS 要求的钩子函数的定义。这些函数用于处理特定的系统事件,如任务堆栈溢出、空闲任务的操作、系统时钟节拍钩子等。虽然它们在某些场景中是可选的,但如果在配置文件 FreeRTOSConfig.h 中启用了相关功能而未定义这些钩子函数,就会导致链接时的 undefined symbol 错误。
这里我们以 vApplicationStackOverflowHook 为例了解如何解决
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #if ( configCHECK_FOR_STACK_OVERFLOW > 0 ) void vApplicationStackOverflowHook ( TaskHandle_t xTask, char * pcTaskName ) ; #endif #if ( ( configCHECK_FOR_STACK_OVERFLOW == 1 ) && ( portSTACK_GROWTH > 0 ) ) #define taskCHECK_FOR_STACK_OVERFLOW() { if ( pxCurrentTCB->pxTopOfStack >= pxCurrentTCB->pxEndOfStack - portSTACK_LIMIT_PADDING ) { vApplicationStackOverflowHook( ( TaskHandle_t ) pxCurrentTCB, pxCurrentTCB->pcTaskName ); } } #endif #configCHECK_FOR_STACK_OVERFLOW 0 #define configUSE_IDLE_HOOK 0 #define configUSE_TICK_HOOK 0 #define configUSE_MALLOC_FAILED_HOOK 0
可以看到 vApplicationStackOverflowHook 的声明与使用是通过 configCHECK_FOR_STACK_OVERFLOW 这个宏是否 > 0 来控制的,所以我们这里有两个解决方案,1 是将 configCHECK_FOR_STACK_OVERFLOW 定义小于 0,而是手动实现这个函数;这里我们简单一点我们将这个宏的值改为 0;
其他三个也是同理;