如何将FreeRTOS移植到STM32F767的代码中

准备工作

准备基础工程

要移植 FreeRTOS,肯定需要一个基础工程,基础工程越简单越好,这里我们就用基础例程中的跑马灯实验来作为基础工程。
FreeRTOS 系统源码
FreeRTOS 系统源码在上一章已经详细的讲解过如何获取了,这里我们会将 FreeRTOS 的系统源码放到开发板光盘中去,路径为:6,软件资料->14,FreeRTOS 学习资料->FreeRTOS 源码。

FreeRTOS 移植

向工程中添加相应文件

1、添加 FreeRTOS 源码
在基础工程中新建一个名为 FreeRTOS 的文件夹:
在这里插入图片描述
创建 FreeRTOS 文件夹以后就可以将 FreeRTOS 的源码添加到这个文件夹中:
在这里插入图片描述
在portable 文件夹,我们只需要留下 keil、MemMang 和 RVDS这三个文件夹,其他的都可以删除掉。

在这里插入图片描述
2、向工程分组中添加文件
打开基础工程,新建分组 FreeRTOS_CORE 和 FreeRTOS_PORTABLE,然后向这两个分组中添加文件:
在这里插入图片描述

分组 FreeRTOS_CORE 中的文件在什么地方就不说了,打开 FreeRTOS 源码一目了然。重点来说说 FreeRTOS_PORTABLE 分组中的 port.c 和 heap_4.c 是怎么来的,port.c 是 RVDS 文件夹下ARM_CM7 中的文件,因为 STM32F767 是 Cortex-M7 内核并且带有 FPU,因此要选择ARM_CM7 的 port.c 文件。heap_4.c 是 MemMang 文件夹中的,前面说了 MemMang 是跟内存管理相关的,里面有 5 个c 文件:heap_1.c、heap_2.c、heap_3.c、heap_4.c 和 heap_5.c。这 5 个 c 文件是五种不同的内存管理方法,就像从北京到上海你可以坐火车、坐飞机,如果心情好的话也可以走路,反正有很多种方法,只要能到上海就行。这里也一样的,这 5 个文件都可以用来作为 FreeRTOS 的内存管理文件,只是它们的实现原理不同,各有利弊。这里我们选择heap_4.c,至于原因,后面会有一章节专门来讲解 FreeRTOS 的内存管理,到时候大家就知道原因了。这里就先选择 heap_4.c,毕竟本章的重点是 FreeRTOS 的移植。
3、添加相应的头文件路径
添加完 FreeRTOS 源码中的 C 文件以后还要添加 FreeRTOS 源码的头文件路径:

在这里插入图片描述

头文件路径添加完成以后编译一下,看看有没有什么错误, 结果会发现提示打不开
“FreeRTOSConfig.h”这个文件:

在这里插入图片描述

这是因为缺少 FreeRTOSConfig.h 文件,这个文件在哪里找呢?你可以自己创建,显然这不是一个明智的做法。我们可以找找 FreeRTOS 的官方移植工程中会不会有这个文件,打开FreeRTOS 针对 STM32F756 的移植工程文件,文件夹是 CORTEX_M7_STM32F7_STM32756G-EVAL_IAR_Keil:
在这里插入图片描述

果然!官方的移植工程中有这个文件,二话不说复制到我们的工程中去,至于复制到什么地方大家可以自行决定,这里我为了方便放到了 FreeRTOS 源码中的 include 文件夹下。
FreeRTOSConfig.h 是何方神圣?看名字就知道,他是 FreeRTOS 的配置文件,一般的操作系统都有裁剪、配置功能,而这些裁剪及配置都是通过一个文件来完成的,基本都是通过宏定义来完成对系统的配置和裁剪的,关于 FreeRTOS 的配置文件 FreeRTOSConfig.h 后面也会有一章节来详细的讲解。
到这里我们再编译一次,可能会有下面的错误提示:
在这里插入图片描述

这个错误是 SystemCoreClock 未定义,注意,这个错误是在 F429 开发板上移植 FreeRTOS 的时候遇到的,F7 开发板移植应该不会遇到!但是为了以防万一,所以把这个错误列出来了。这个错误是因为在 FreeRTOSConfig.h 中使用到了 SystemCoreClock 来标记 MCU 的频率:
在这里插入图片描述
注意,这里有个条件编译!只有定义 ICCARM 以后下面的代码才有效:
我们需要修改这个条件编译,修改后的代码如下:

#include <stdint.h>
extern uint32_t SystemCoreClock;

我们需要修改这个条件编译,修改后的代码如下:

#if defined( ICCARM ) || defined( CC_ARM) || defined( GNUC )  #include <stdint.h>
extern uint32_t SystemCoreClock;
#endif

继续编译一次,还是有错误:
在这里插入图片描述

这是因为 port.c 和 stm32f7xx_it.c 这两个文件中有重复定义的函数:PendSV_Handler()、
SVC_Handler() 和 Systick_Handler() ,这里屏蔽掉 stm32f7xx_it.c 中的 PendSV_Handler() 、
SVC_Handler()和 Systick_Handler()这三个函数。
在编译一次,继续有错误,这次的错误是提示有一些未定义的函数:

在这里插入图片描述

函数 vAssertCalled()是跟断言函数有关的,移植的时候不需要,将其屏蔽掉就可以了,如图
所示:
在这里插入图片描述

剩下的 3 个未定义函数它们都是 Hook 结尾的,这些函数有个共同的名称:钩子函数,这是因为在 FreeRTOSConfig.h 中开启了这些钩子函数,但是却没有定义这些钩子函数而导致的, 我们在FreeRTOSConfig.h 中关闭这些钩子函数就行了,关闭的方法很简单,将相应的宏定义改为 0 即可,这里将宏 configUSE_TICK_HOOK、configUSE_MALLOC_FAILED_HOOK 和configCHECK_FOR_STACK_OVERFLOW 定义为 0。
最后编译一下,应该就没有错误了,如果还有错误的话大家自行根据错误类型查找和修改错误。

修改 SYSTEM 文件

SYSTEM 文件夹里面的文件一开始是针对 UCOS 而编写的,所以如果使用 FreeRTOS 的话就需要做相应的修改。本来打算让 SYSTEM 文件夹也支持 FreeRTOS,但是这样的话会导致
SYSTEM 里面的文件太过于复杂,这样非常不利于初学者学习,所以这里就专门针对 FreeRTOS
修改了 SYSTEM 里面的文件。
1、修改 sys.h 文件
sys.h 文件修改很简单,在 sys.h 文件里面用宏 SYSTEM_SUPPORT_OS 来定义是否使用 OS, 我们使用了 FreeRTOS,所以应该将宏 SYSTEM_SUPPORT_OS 改为 1

//0,不支持 os
//1,支持 os
#define SYSTEM_SUPPORT_OS	1	//定义系统文件夹是否支持 OS

2、修改 usart.c 文件

usart.c 文件修改也很简单,usart.c 文件有两部分要修改,一个是添加 FreeRTOS.h 头文件, 默认是添加的 UCOS 中的 includes.h 头文件,修改以后如下:

//如果使用os,则包括下面的头文件即可. 
#if SYSTEM_SUPPORT_OS
#include "FreeRTOS.h"	//os 使用
#endif

另外一个就是 USART1 的中断服务函数,在使用 UCOS 的时候进出中断的时候需要添加

OSIntEnter()和 OSIntExit(),使用 FreeRTOS 的话就不需要了,所以将这两行代码删除掉,修改以后如下:

//串口 1 中断服务程序
void USART1_IRQHandler(void)
{
    
    
u32 timeout=0;
u32 maxDelay=0x1FFFF;

HAL_UART_IRQHandler(&UART1_Handler); //调用 HAL 库中断处理公用函数

timeout=0;
while (HAL_UART_GetState(&UART1_Handler) != HAL_UART_STATE_READY)//等待就绪
{
    
    
timeout++;超时处理if(timeout>maxDelay) break;
}

timeout=0;
//一次处理完成之后,重新开启中断并设置 RxXferCount 为 1 while(HAL_UART_Receive_IT(&UART1_Handler, (u8 *)aRxBuffer, \
RXBUFFERSIZE) != HAL_OK)
{
    
    
timeout++; //超时处理if(timeout>maxDelay) break;
}
}

3、修改 delay.c 文件
delay.c 文件修改的就比较大了,因为涉及到 FreeRTOS 的系统时钟,delay.c 文件里面有 4
个函数,先来看一下函数 SysTick_Handler(),此函数是滴答定时器的中断服务函数,代码如下:

extern void xPortSysTickHandler(void);
//systick 中断服务函数,使用 OS 时用到
void SysTick_Handler(void)
{
    
    
if(xTaskGetSchedulerState()!=taskSCHEDULER_NOT_STARTED)//系统已经运行
{
    
    
xPortSysTickHandler();
}
HAL_IncTick();
}

FreeRTOS 的心跳就是由滴答定时器产生的,根据 FreeRTOS 的系统时钟节拍设置好滴答定时器的周期,这样就会周期触发滴答定时器中断了。在滴答定时器中断服务函数中调用
FreeRTOS 的 API 函数 xPortSysTickHandler(),如果使用 HAL 库的话还需要调用 HAL 库中的函数 HAL_IncTick()!因为 HAL 库中的延时是靠 HAL_IncTick()来完成的。

delay_init()是用来初始化滴答定时器和延时函数,代码如下:

//初始化延时函数
//当使用 FreeRTOS 的时候,此函数会初始化 FreeRTOS 的时钟节拍
//SYSTICK 的时钟固定为 AHB 时钟
//SYSCLK:系统时钟频率void delay_init(u8 SYSCLK)
{
    
    
u32 reload;
//SysTick 频 率 为 HCLK HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);
fac_us=SYSCLK;	//不论是否使用 OS,fac_us 都需要使用
reload=SYSCLK;	// 每 秒 钟 的 计 数 次 数 单 位 为 K reload*=1000000/configTICK_RATE_HZ;	//根据 configTICK_RATE_HZ 设定溢出时间
//reload 为 24 位寄存器,最大值:16777216,
//在 180M 下,约合 0.745s 左右fac_ms=1000/configTICK_RATE_HZ;	//代表 OS 可以延时的最少单位SysTick->CTRL|=SysTick_CTRL_TICKINT_Msk;//开启 SYSTICK 中断
SysTick->LOAD=reload;	//每 1/configTICK_RATE_HZ 断一次
SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk; //开启 SYSTICK
}

前面我们说了 FreeRTOS 的系统时钟是由滴答定时器提供的,那么肯定要根据 FreeRTOS 的
系统时钟节拍来初始化滴答定时器了,delay_init()就是来完成这个功能的。FreeRTOS 的系统时钟节拍由宏 configTICK_RATE_HZ 来设置,这个值我们可以自由设置,但是一旦设置好以后我们就要根据这个值来初始化滴答定时器,其实就是设置滴答定时器的中断周期。由于我们使用了 HAL 库,所以在设置滴答定时器的时候要照顾到 HAL 库,HAL 库里的延时函数要求滴答定时器周期为 1ms,因此 FreeRTOS 的系统节拍应该设置为 1000HZ,也就是 1ms 的周期。
接下来的三个函数都是延时的,代码如下:

//延时nus
//nus:要延时的 us 数.
//nus:0~190887435(最大值即 2^32/fac_us@fac_us=22.5) void delay_us(u32 nus)
{
    
    
u32 ticks;
u32 told,tnow,tcnt=0;
u32 reload=SysTick->LOAD;	//LOAD 的值
ticks=nus*fac_us;	//需要的节拍数
told=SysTick->VAL;	//刚进入时的计数器值while(1)
{
    
    
tnow=SysTick->VAL; if(tnow!=told)
{
    
    
//这里注意一下 SYSTICK 是一个递减的计数器就可以了.
if(tnow<told)tcnt+=told-tnow; else tcnt+=reload-tnow+told; told=tnow;
if(tcnt>=ticks)break;	//时间超过/等于要延迟的时间,则退出.
}
};
}

//延时nms,会引起任务调度
//nms:要延时的 ms 数
//nms:0~65535
void delay_ms(u32 nms)
{
    
    
if(xTaskGetSchedulerState()!=taskSCHEDULER_NOT_STARTED)//系统已经运行
{
    
    
if(nms>=fac_ms)	//延时的时间大于 OS 的最少时间周期
{
    
    
vTaskDelay(nms/fac_ms);	//FreeRTOS 延时
}
nms%=fac_ms;	//OS 已经无法提供这么小的延时了,
//采用普通方式延时
}
delay_us((u32)(nms*1000));	//普通方式延时
}
//延时nms,不会引起任务调度
//nms:要延时的 ms 数
void delay_xms(u32 nms)
{
    
    
u32 i;
for(i=0;i<nms;i++) delay_us(1000);
}

delay_us()是 us 级延时函数,delay_ms 和 delay_xms()都是 ms 级的延时函数,delay_us()和
delay_xms()不会导致任务切换。delay_ms()其实就是对 FreeRTOS 中的延时函数 vTaskDelay()的简单封装,所以在使用 delay_ms()的时候就会导致任务切换。
delay.c 修改完成以后编译一下,会提示如图 所示错误:
图 2.2.2.1  错误提示

从图 可以看出在 port.c 和 delay.c 中有重复定义的函数:SysTick_Handler(),二选一!很明显 delay.c 中的 SysTick_Handler()得留下来,打开 FreeRTOSConfig.h 文件,找到如下一个宏定义:
#define xPortSysTickHandler SysTick_Handler
屏蔽掉此宏定义,如图 所示:
在这里插入图片描述

至此,SYSTEM 文件夹就修改完成了

以上就是基本移植工作,下面就开始验证。

移植验证实验

实验设计
本实验设计四个任务:start_task()、led0_task ()、led1_task ()和 float_task(),这四个任务的任务功能如下:
start_task():用来创建其他三个任务。
led0_task ():控制 LED0 的闪烁,提示系统正在运行。
led1_task ():控制 LED1 的闪烁。
float_task():简单的浮点测试任务,用于测试 STM32F7 的 FPU 是否工作正常。

实验程序与分析

#include "sys.h"
 #include "delay.h" 
 #include "usart.h" 
 #include "led.h"
  #include "FreeRTOS.h"
   #include "task.h"
#define START_TASK_PRIO	1	//任务优先级
#define START_STK_SIZE	128	//任务堆栈大小
TaskHandle_t StartTask_Handler;		//任务句柄
void start_task(void *pvParameters);		//任务函数
#define LED0_TASK_PRIO	2	//任务优先级
#define LED0_STK_SIZE	50	//任务堆栈大小
TaskHandle_t LED0Task_Handler;		//任务句柄
void led0_task(void *p_arg);		//任务函数
#define LED1_TASK_PRIO	3	//任务优先级
#define LED1_STK_SIZE	50	//任务堆栈大小
TaskHandle_t LED1Task_Handler;		//任务句柄
void led1_task(void *p_arg);		//任务函数
#define FLOAT_TASK_PRIO	4	//任务优先级
#define FLOAT_STK_SIZE	128	//任务堆栈大小
TaskHandle_t FLOATTask_Handler;		//任务句柄
void float_task(void *p_arg);		//任务函数
int main(void)
{
    
    
Cache_Enable();	//打开 L1-Cache	
HAL_Init();	//初始化 HAL 库	
Stm32_Clock_Init(432,25,2,9);	//设置时钟,216Mhz	
delay_init(216);	//延时初始化	
uart_init(115200);	//串口初始化	
LED_Init();	//初始化 LED	
//创建开始任务		
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();
}
//开始任务任务函数
void start_task(void *pvParameters)
{
    
    
taskENTER_CRITICAL();	//进入临界区
//创建 LED0 任务
xTaskCreate((TaskFunction_t	)led0_task,
(const char*	)"led0_task",
(uint16_t	)LED0_STK_SIZE,
(void*	)NULL, (UBaseType_t	)LED0_TASK_PRIO,
(TaskHandle_t*	)&LED0Task_Handler);
//创建 LED1 任务
xTaskCreate((TaskFunction_t	)led1_task,
(const char*	)"led1_task",
(uint16_t	)LED1_STK_SIZE,
(void*	)NULL, (UBaseType_t	)LED1_TASK_PRIO,
(TaskHandle_t*	)&LED1Task_Handler);
//浮点测试任务xTaskCreate((TaskFunction_t	)float_task,
(const char*	)"float_task",
(uint16_t	)FLOAT_STK_SIZE,
(void*	)NULL,
(UBaseType_t	)FLOAT_TASK_PRIO, (TaskHandle_t*	)&FLOATTask_Handler);
vTaskDelete(StartTask_Handler);	//删除开始任务taskEXIT_CRITICAL();	//退出临界区
}

//LED0 任务函数
void led0_task(void *pvParameters)
{
    
    
while(1)
{
    
    
LED0_Toggle; vTaskDelay(500);
}
}
//LED1 任务函数
void led1_task(void *pvParameters)
{
    
    
while(1)
{
    
    
LED1(0);
vTaskDelay(200);
LED1(1);
vTaskDelay(800);
}
}

//浮点测试任务
void float_task(void *p_arg)
{
    
    
static double float_num=0.00; while(1)
{
    
    
float_num+=0.01f;
printf("float_num 的值为: %.4f\r\n",float_num); vTaskDelay(1000);
}
}

测试代码中创建了 3 个任务:LED0 测试任务、LED1 测试任务和浮点测试任务,它们的任务函数分别为:led0_task()、led1_task()和 float_task()。led0_task()和 led1_task()任务很简单,就是让 LED0 和 LED1 周期性闪烁。因为 STM32F767 支持双精度浮点,所以必须测试一下
FreeRTOS 是否也支持双精度浮点。float_task()任务就是用来完成这个功能,定义一个 double 变量,然后每隔 1s 加 0.01,并且通过串口打印出来。
由于我们只是用测试代码来测试 FreeRTOS 是否移植成功的,所以关于具体的函数的调用方法这些不要深究,后面会有详细的讲解!

猜你喜欢

转载自blog.csdn.net/weixin_43491077/article/details/109963772