一、移植前的准备
在开始移植 FreeRTOS 之前,需要提前准备好一个用于移植 FreeRTOS 的基础工程,和FreeRTOS 的源码。
1.基础工程
已经有基础工程的可以用自己的,没有的可以去附件下载我的或者根据下面的教程自己创建一个基础教程:
2. FreeRTOS 源码
STM32 F4 使用 FreeRTOS 时,选择合适的版本主要取决于稳定性、性能、兼容性和官方支持。其中FreeRTOS v10.4.6 的稳定性最好,CubeMX 兼容,所以本文以该版本为例进行移植。
下载途径:
(推荐)官方GitHub:Release V10.4.6 · FreeRTOS/FreeRTOS-Kernel · GitHub
官 网 : http://www.freertos.org/
代 码 托 管 网 站 : https://sourceforge.net/projects/freertos/files/FreeRTOS/
二、移植FreeRTOS
准备好基础工程和 FreeRTOS 的源码后,接下来就可以开始进行 FreeRTOS 的移植了。
1. 添加 FreeRTOS 源码
下载好FreeRTOS v10.4.6源码后解压后如图所示:
将解压后的源码原封不动直接拷贝到基础工程的Middlewares文件夹下:
2. 将文件添加到工程
打 开 基 础 工 程 , 新 建 两 个 文 件 分 组 , 分 别 为 Middlewares/FreeRTOS_CORE 和Middlewares/FreeRTOS_PORT,如下图所示:
Middlewares/FreeRTOS_CORE 分组用于存放 FreeRTOS 的内核 C 源码文件,FreeRTOS 目录下所有的 FreeRTOS 的内核 C 源文件添加到Middlewares/FreeRTOS_CORE 分组中。
Middlewares/FreeRTOS_PORT 分组用于存放 FreeRTOS 内核的移植文件,需要添加两类文件到这个分组,分别为 heap_x.c 和 port 文件。
首先是 heap_x.c, 在路径 FreeRTOS/portable/MemMang 下有五个 C 源文件,这五个 C 源文件对应了五种 FreeRTOS 提供的内存管理算法,读者在进行 FreeRTOS 移植的时候可以根据需求选择合适的方法,具体这五种内存管理的算法,在后续 FreeRTOS 内存管理章节会具体分析,这里就先使用 heap_4.c,将 heap_4.c 添加到 Middlewares/FreeRTOS_PORT 分组中。
接着是 port 文件, port 文件是 FreeRTOS 这个软件与 MCU 这个硬件连接的桥梁,因此对于 STM32 系列不同的芯片,所使用的 port 文件是不同的。 port 文件的路径在FreeRTOS/portable/RVDS 或 FreeRTOS/portable/GCC 下。进入到 FreeRTOS/portable/RVDS 或FreeRTOS/portable/GCC,可以看到 FreeRTOS 针对不同的 MCU 提供了不同的 port 文件,具体STM32 系列芯片与不同 port 文件的对应关系如下表所示:
STM32 系列芯片类型 | Port 文件所在文件夹 |
STM32F1(ARMCC) | RVDS/ARM_CM3 |
STM32F4/ STM32G4(ARMCC) | RVDS/ARM_CM4F |
STM32F7(ARMCC) | RVDS/ARM_CM7/r0p1 |
STM32H7(ARMCC) | RVDS/ARM_CM7/r0p1 |
STM32H5(ARMClang) | GCC/ ARM_CM33_NTZ |
源码文件添加成功后,如图所示:
3. 添加头文件路径
接下来添加 FreeRTOS 源码的头文件路径,需要添加两个头文件路径如图:
三、配置FreeRTOS
1. 添加 FreeRTOSConfig.h 文件
FreeRTOSConfig.h 是 FreeRTOS 操作系统的配置文件, FreeRTOS 操作系统是可裁剪的,用户可以根据需求对 FreeRTOS 进行裁剪,裁剪掉不需要用到的 FreeRTOS 功能,以此来节约 MCU中寸土寸金的内存资源,那么 FreeRTOSConfig.h 文件从哪里来呢?
(1) FreeRTOSConfig.h 获取途径一(推荐)
在我的文章末尾的附件里面下载即可。
(2) FreeRTOSConfig.h 获取途径二
第一种途径就是用户自行编写,用户可以根据自己的需求编写 FreeRTOSConfig.h 对FreeRTOS 操作系统进行裁剪。 FreeRTOS 官网的在线文档中就详细地对 FreeRTOSConfig.h 中各个配置项进行了描述,网页链接: https://www.freertos.org/a00110.html。当然,对于 FreeRTOS 新手来说,笔者是不建议自行编写的。
(2) FreeRTOSConfig.h 获取途径三
第二种途径就是 FreeRTOS 内核的演示工程(FreeRTOS v10.4.6版本的源码没有带演示工程,您可以去官网下载其他的版本)。Demo 文件夹中包含了 FreeRTOS 官方提供的演示工程,在这些演示工程当中就包含了每个演示工程对应的 FreeRTOSConfig.h 文件,需要注意的是,有些演示工程使用的是老版本的 FreeRTOS,因此部分演示工程的 FreeRTOSConfig.h 文件并不能够很好的适用于新版本的 FreeRTOS。任意打开其中一个演示工程, 如下图所示:
得到 FreeRTOSConfig.h 后,需要把它拷贝到User文件夹里面的头文件文件夹,如图:
2. 修改基础工程的 SYSTEM 文件
SYSTEM 文件夹中一共需要修改三个文件夹,分别是 sys、 usart、 delay。为了简化移植过程,您还是直接在本文末尾附录里面直接下载SYSTEM 里面的文件:
下载添加好后,需要将其源代码文件和头文件加入工程里面。
3.修改中断相关文件
在 FreeRTOS 的移植过程中会使用到这几到三个重要的中断,分别是 FreeRTOS 系统时基定时器的中断(SysTick 中断)、SVC 中断、PendSV 中断,这三个中断的中断服务函数在 HAL 库提供的文件中都有定义,对于不同的 STM32 芯片,对应了不同的文件,具体对应关系如下表所示:
STM32 系列芯片类型 | 中断服务函数所在文件 |
STM32F1 | stm32f1xx_it.c |
STM32F4 | stm32f4xx_it.c |
STM32G4 | stm32g4xx_it.c |
STM32F7 | stm32f7xx_it.c |
STM32H7 | stm32h7xx_it.c |
STM32H5 | stm32h5xx_it.c |
其中, SysTick 的中断服务函数在 delay.c 文件中已经定义了,并且 FreeRTOS 也提供了 SVC和 PendSV 的中断服务函数,因此需要将 HAL 库提供的这三个中断服务函数注释掉,这里采用宏开关的方式让 HAL 库中的这三个中断服务函数不加入编译,使用的宏SYS_SUPPORT_OS
在 sys.h 中定义,因此还需要导入 sys.h 头文件, 请读者按照上面的表找到对应的文件进行修改,修改后的代码如下所示:
最后,也是移植 FreeRTOS 要修改的最后一个地方:__NVIC_PRIO_BITS
宏定义
如果没有修改这关键的一步,后面移植好FreeRTOS编译 port.c 里面的汇编代码时就会报错:
..\Middlewares\FreeRTOS-Kernel-10.4.6\portable\RVDS\ARM_CM4F\port.c(482): error: A1586E: Bad operand types (UnDefOT, Constant) for operator (
如图所示:
具体修改流程:
在FreeRTOSConfig.h 文件中有如下定义:
对于这个__NVIC_PRIO_BITS宏定义,可以看到,这个宏定义将 configPRIO_BITS 定义成__NVIC_PRIO_BITS,而__NVIC_PRIO_BITS在 HAL 库中有相关定义,对于不同的 STM32 芯片, __NVIC_PRIO_BITS 定义在不同的文件中,具体的对应关系如下表所示:
STM32 系列芯片类型 | __NVIC_PRIO_BITS 所在文件 |
STM32F1 | stm32f103xe.h |
STM32F4 | stm32f407xx.h 或 stm32f429xx.h |
STM32G4 | stm32g474xx.h |
STM32F7 | stm32f750xx.h 或 stm32f767xx.h |
STM32H7 | stm32h750xx.h 或 stm32h743xx.h |
找到对应的文件进行修改。 虽然不同类型的芯片对应的文件不同,但是__NVIC_PRIO_BITS 都被定义成了相同的值,如下所示:
这个值是正确的,但是如果将__NVIC_PRIO_BITS 定义成 4U 的话,在编译 FreeRTOS 工程的时候, Keil 会报错,具体的解决方法就是将 4U 改成 4,代码修改后如下所示:
到此为止, FreeRTOS 就移植完毕了!!!
四、测试 FreeRTOS
移植好 FreeRTOS 之后, 当然要测试一下移植是否成功。 freertos_demo.c 和freertos_demo.h。对于 main.c 主要是在 main()函数中完成一些硬件的初始化,最后调用freertos_demo.c 文件中的 freertos_demo()函数。而 freertos_demo.c 则是用于编写 FreeRTOS 的相关应用程序代码。
freertos_demo.h
#ifndef __FREERTOS_DEMO_H
#define __FREERTOS_DEMO_H
void freertos_demo(void);
#endif
freertos_demo.c
#include "freertos_demo.h"
#include "./SYSTEM/usart/usart.h"
#include "./BSP/LED/led.h"
/*FreeRTOS*********************************************************************************************/
#include "FreeRTOS.h"
#include "task.h"
/******************************************************************************************************/
/*FreeRTOS配置*/
/* START_TASK 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define START_TASK_PRIO 1 /* 任务优先级 */
#define START_STK_SIZE 128 /* 任务堆栈大小 */
TaskHandle_t StartTask_Handler; /* 任务句柄 */
void start_task(void *pvParameters); /* 任务函数 */
/* TASK1 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK1_PRIO 2 /* 任务优先级 */
#define TASK1_STK_SIZE 128 /* 任务堆栈大小 */
TaskHandle_t Task1Task_Handler; /* 任务句柄 */
void task1(void *pvParameters); /* 任务函数 */
/* TASK2 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK2_PRIO 3 /* 任务优先级 */
#define TASK2_STK_SIZE 128 /* 任务堆栈大小 */
TaskHandle_t Task2Task_Handler; /* 任务句柄 */
void task2(void *pvParameters); /* 任务函数 */
/******************************************************************************************************/
/**
* @brief FreeRTOS例程入口函数
* @param 无
* @retval 无
*/
void freertos_demo(void)
{
xTaskCreate((TaskFunction_t )start_task, /* 任务函数 */
(const char* )"start_task", /* 任务名称 */
(uint16_t )START_STK_SIZE, /* 任务堆栈大小 */
(void* )NULL, /* 传入给任务函数的参数 */
(UBaseType_t )START_TASK_PRIO, /* 任务优先级 */
(TaskHandle_t* )&StartTask_Handler); /* 任务句柄 */
vTaskStartScheduler();
}
/**
* @brief start_task
* @param pvParameters : 传入参数(未用到)
* @retval 无
*/
void start_task(void *pvParameters)
{
taskENTER_CRITICAL(); /* 进入临界区 */
/* 创建任务1 */
xTaskCreate((TaskFunction_t )task1,
(const char* )"task1",
(uint16_t )TASK1_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK1_PRIO,
(TaskHandle_t* )&Task1Task_Handler);
/* 创建任务2 */
xTaskCreate((TaskFunction_t )task2,
(const char* )"task2",
(uint16_t )TASK2_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK2_PRIO,
(TaskHandle_t* )&Task2Task_Handler);
vTaskDelete(StartTask_Handler); /* 删除开始任务 */
taskEXIT_CRITICAL(); /* 退出临界区 */
}
/**
* @brief task1
* @param pvParameters : 传入参数(未用到)
* @retval 无
*/
void task1(void *pvParameters)
{
while(1)
{
LED0_TOGGLE(); /* LED0闪烁 */
vTaskDelay(1000); /* 延时1000ticks */
LED1_TOGGLE();
printf("task1:LEDx_TOGGLE...\r\n");
}
}
/**
* @brief task2
* @param pvParameters : 传入参数(未用到)
* @retval 无
*/
void task2(void *pvParameters)
{
float float_num = 0.0;
while(1)
{
float_num += 0.01f; /* 更新数值 */
printf("task2:float_num: %0.4f\r\n", float_num); /* 打印数值 */
vTaskDelay(1000); /* 延时1000ticks */
}
}
main.c
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/LED/led.h"
#include "freertos_demo.h"
int main(void)
{
HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(360, 25, 2, 8); /* 设置时钟,180Mhz */
delay_init(180); /* 延时初始化 */
usart_init(115200); /* 串口初始化为115200 */
led_init(); /* 初始化LED */
freertos_demo(); /*里面会调用vTaskStartScheduler(); 启动任务调度。*/
while (1) /*启动任务调度后,此时CPU开始由FreeRTOS任务调度接管,后面代码不会执行*/
{
printf("main\r\n");
delay_ms(1000);
}
}
移植好FreeRTOS后测试效果:
五、附件
1.SYSTEM里面的sys、usart 和 delay文件
2.移植好的STM32F4 FreeRTOS Keil 5 工程