本实验是基于正点原子 STM32F407ZG 探索者开发板完成的,所以需要一个STM32F407ZG 探索者开发板
用于移植的基础工程(下面会讲)
FreeRTOS源码(下面会讲)
由于后续需要用到 LED、 LCD、定时器、串口、内存管理等外设及功能,因此以正点原子标准例程-HAL库版本的内存管理的实验工程为基础工程进行 FreeRTOS 的移植 。
如下图所示
将此工程复制一份到我们需要移植 FreeRTOS 的文件中作为基础工程,同时修改名字,如下图所示
由于内存管理实验例程的 BSP 文件夹中可能不包含定时器的驱动文件,因此如果内存管理实验的 BSP 文件夹如果不包含 TIMER 文件夹,需要从定时器相关实验的 BSP 文件夹中拷贝一份 TIMER 到 FreeRTOS 移植基础工程当中,如从下列路径中找到 TIMER 文件夹拷贝到基础工程对应目录中,如下图所示。也可以在文章上方提供的代码包中直接找到基础工程。
标准例程-HAL库版本\实验9 通用定时器实验\实验9-1 通用定时中断器实验\Drivers\BSP
FreeRTOS 源码可以通过官方获取,这里直接从正点原子的资料中获取,可以在下列路径中找到,如下图所示。如果是从官方下载的源码,注意版本为 V10.4.6,最好还是使用正点原子提供的源码,这样不会出现版本问题,也可以在文章上方提供的代码包中直接找到 FreeRTOS 源码。
资料盘(A盘)\6,软件资料\13,FreeRTOS学习资料
打开我们基础工程的 Middlewares 文件夹,然后新建一个 FreeRTOS 子文件夹,如下图所示
然后打开 FreeRTOS 源码的下列路径
\FreeRTOSv202112.00\FreeRTOS\Source
将红框中的文件全部复制到基础工程的Middlewares/FreeRTOS
下,如下图所示
复制完成后的Middlewares/FreeRTOS
内容如下图所示
使用 Keil5 打开我们的基础工程,新建两个文件分组,如下图所示
下图为添加完成后的目录结构
Middlewares/FreeRTOS_CORE
分组用于存放 FreeRTOS 的内核 C 源码文件, 将“上述步骤中的 FreeRTOS 目录下所有的 FreeRTOS 的内核 C 源文件添加到 Middlewares/FreeRTOS_CORE
分组中 ,如下图所示
Middlewares/FreeRTOS_PORT
分组用于存放 FreeRTOS 内核的移植文件 ,需要添加两个文件到这个分组,分别为 heap_x.c
和 port.c
。
首先是 heap_x.c
。在路径 FreeRTOS/portable/MemMang
下有五个 C 语言源文件,这五个 C 语言源文件对应了五种 FreeRTOS 提供的内存管理算法 ,读者在进行 FreeRTOS 移植的时候可以根据需求选择合适的方法,具体这五种内存管理的算法 ,在后续 会具体分析,这里就先使用 heap_4.c
,将 heap_4.c
添加到 Middlewares/FreeRTOS_PORT
分组中。
port.c
是 FreeRTOS 与 MCU 这个硬件连接的桥梁,因此对于不同系列的 STM32 开发板,所使用的 port.c
文件是不同的。 port.c
文件的路径在 FreeRTOS/portable/RVDS
下。进入到 FreeRTOS/portable/RVDS
,可以看到 FreeRTOS 针对不同的 MCU 提供了不同的 port.c
文件,具体正点原子的STM32系列开发板与不同 port.c
的对应关系如下表所示:
正点原子的STM32系列开发板类型 | port.c所在文件夹 |
---|---|
STM32F1 | ARM_CM3 |
STM32F4 | ARM_CM4F |
STM32F7 | ARM_CM7/r0p1 |
STM32H1 | ARM_CM7/r0p1 |
演示的开发板为 STM32F4 ,所以我是在 ARM_CM4F 中寻找的 port.c
文件,其他开发板以此类推,全部添加完成后如下图所示(红框为本小节添加的所有内容)
接下来添加 FreeRTOS 源码的头文件路径,需要添加两个头文件路径,一个头文件路径是 FreeRTOS/include
,另外一个头文件路径为 port.c
文件的路径,根据不同类型开发板与 port.c
文件的对应关系进行添加即可(和上文中选择的文件保持一致)。 如下图所示
FreeRTOSConfig.h
是 FreeRTOS 操作系统的配置文件, FreeRTOS 操作系统是可裁剪的,用户可以根据需求对 FreeRTOS 进行裁剪,裁剪掉不需要用到的 FreeRTOS 功能,以此来节约 MCU 的内存资源。获取 FreeRTOSConfig.h
有三个途径:
Demo
文件夹, 包含了 FreeRTOS 官方提供的演示工程,在工程当中就包含了每个演示工程对应的 FreeRTOSConfig.h
文件,读者可以在 Demo
文件夹中找到与自己所使用芯片相似的演示工程中的 FreeRTOSConfig.h
文件,并根据自己的需求,稍作修改 。(不推荐,兼容性不太好)User
子文件夹下 找到 FreeRTOSConfig.h
文件资料盘(A盘)\4,程序源码\3,扩展例程\2,FreeRTOS例程\FreeRTOS实验例程2 FreeRTOS移植实验
这个文件是参考 FreeRTOS 官网中对 FreeRTOSConfig.h
文件的描述,并针对正点原子的 STM32 系列开发板编写的。此文件在 FreeRTOSConfig.h
文件中并没有对 FreeRTOS 的功能作过多的裁剪,大部分的功能都保留了,在后续的部分实验中还需要对 FreeRTOSConfig.h
文件作相应的修改,以满足实验的需求。
本实验采用途径三,将上面路径的 User
子文件夹下的 FreeRTOSConfig.h
文件添加到基础工程的 User
子目录下即可 (正点原子的STM32系列开发板对应的 FreeRTOSConfig.h
文件是不通用的),如下图所示
SYSTEM
文件夹中的文件一开始是针对 μC/OS 编写的,因此使用 FreeRTOS 的话,就需要作相应的修改。SYSTEM
文件夹中一共需要修改三个文件,分别是 sys.h
、 usart.c
、 delay.c
。
sys.h
文件的修改很简单, 在 sys.h
文件中使用了宏 SYS_SUPPORT_OS
来定义是否支持 OS 因为要支持 FreeRTOS,因此应当将宏 SYS_SUPPORT_OS
定义为 1,具体修改步骤如下所示
找到下图所示的位置,修改为1
usart.c
文件的修改也很简单,一共有两个地方需要修改, 首先就是串口的中断服务函数,原本在使用 μC/OS 的时候,进入和退出中断需要添加 OSIntEnter()
和 OSIntExit()
两个函数,这是 μC/OS
对于中断的相关处理机制,而 FreeRTOS
中并没有这种机制,因此将这两行代码删除,修改如下图所示中选中的代码(注意 F1 和 F4 系列的代码不一样,F1 系列下图不具有参考性)
删除完成后如下图
接下来 usart.c
要修改的第二个地方就是导入的头文件,因为在串口的中断服务函数当中已经删除了 μC/OS 的相关代码,并且也没有使用到 FreeRTOS 的相关代码,因此将 usart.c
中包含的关于 OS 的头文件删除,删除下图选中的代码
删除后如下图所示
接下来修改 SYSTEM
文件夹中的最后一个文件 delay.c
。大致可分为三个步骤:
一共需要删除 1 个全局变量、 6 个宏定义、 3 个函数 ,这些要删除的代码在使用 μC/OS 的时候会使用到,但是在使用 FreeRTOS 的时候无需使用 ,需要删除的代码如下所示
删除完成后如下图所示
只需要在 delay.c
文件中使用 extern
关键字导入一个 FreeRTOS 函数 xPortSysTickHandler()
即可,这个函数用于处理 FreeRTOS 系统时钟节拍的,我们使用 SysTick 作为 FreeRTOS 操作系统的心跳,因此需要在 SysTick 的中断服务函数中调用这个函数,因此将代码添加到 SysTick 中断服务函数之前,代码修改如下(标蓝部分为自己添加的代码):
extern void xPortSysTickHandler(void);
最后要修改的内容包括两个,分别是包含头文件和 4 个函数。
首先来看需要修改的 4 个函数,分别是 SysTick_Handler()
、 delay_init()
、 delay_us()
和delay_ms()
。
这个函数是 SysTick 的中断服务函数,需要在这个函数中调用上个步骤中导入的函数 xPortSysTickHandler()
,代码修改后如下图所示(删掉原来的SysTick_Handler()
,改为下面的)
/**
* @brief systick中断服务函数,使用OS时用到
* @param ticks : 延时的节拍数
* @retval 无
*/
void SysTick_Handler(void)
{
HAL_IncTick(); /* OS开始跑了,才执行正常的调度处理 */
if (xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED)
{
xPortSysTickHandler();
}
}
函数 delay_init()
主要用于初始化 SysTick。这里要说明的是, 在后续调用函数 vTaskStartScheduler()
的时候, FreeRTOS 会按照 FreeRTOSConfig.h
文件的配置对 SysTick 进行初始化,因此 delay_init()
函数初始化的 SysTick 主要使用在 FreeRTOS 开始任务调度之前。函数 delay_init()
要修改的部分主要为 SysTick 的重装载值以及删除不用的代码,代码修改后如下图所示(删掉原来的delay_init()
,改为下面的)
/**
* @brief 初始化延迟函数
* @param sysclk: 系统时钟频率, 即CPU频率(rcc_c_ck), 168MHz
* @retval 无
*/
void delay_init(uint16_t sysclk) {
#if SYS_SUPPORT_OS
uint32_t reload;
#endif
HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);
g_fac_us = sysclk;
#if SYS_SUPPORT_OS
reload = sysclk; /* 使用configTICK_RATE_HZ计算重装载值 * configTICK_RATE_HZ在FreeRTOSConfig.h中定义 */
reload *= 1000000 / configTICK_RATE_HZ; /* 删除不用的g_fac_ms相关代码 */
SysTick->CTRL |= SysTick_CTRL_TICKINT_Msk;
SysTick->LOAD = reload;
SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk;
#endif
}
注意:在正点原子 STM32 系列开发板的标准例程源码中, STM32F1 系列的函数 delay_init()
将 SysTick 的时钟频率设置为 ==CPU时钟频率的 1/8 ==,而 STM32F4/F7/H7 系列的函数 delay_init()
则将 SysTick 的时钟频率设置为与 CPU 相同的时钟频率,由于 FreeRTOS在配置 SysTick 时,并不会配置 SysTick 的时钟源,因此这将导致正点原子 STM32F1 系列与正点原 STM32F4/F7/H7系列的 FreeRTOSConfig.h
文件有所差异,并且也只有这一点存在差异,这是读者在使用正点原子提供的 FreeRTOSConfig.h
文件时需要注意的地方。
函数 delay_us()
用于微秒级的 CPU 忙延时 ,原本的函数 delay_us()
延时的前后加入了自定义函数 delay_osschedlock()
和 delay_osschedunlock()
用于锁定和解锁 μC/OS 的任务调度器,以此来让延时更加准确。在 FreeRTOS 中可以不用加入这两个函数,但是要注意的是,这会让函数 delay_us()
的微秒级延时的精度有所下降, 代码修改后如下图所示(删掉原来的delay_us()
,改为下面的)
/**
* @brief 延时nus
* @param nus: 要延时的us数
* @note nus取值范围 : 0 ~ 190887435us(最大值即 2^32 / fac_us @fac_us = 21)
* @retval 无
*/
void delay_us(uint32_t nus)
{
uint32_t ticks;
uint32_t told, tnow, tcnt = 0;
uint32_t reload = SysTick->LOAD; /* LOAD的值 */
ticks = nus * g_fac_us; /* 需要的节拍数 */
told = SysTick->VAL; /* 刚进入时的计数器值 */
while (1)
{
tnow = SysTick->VAL;
if (tnow != told)
{
if (tnow < told)
{
tcnt += told - tnow; /* 这里注意一下SYSTICK是一个递减的计数器就可以了 */
}
else
{
tcnt += reload - tnow + told;
}
told = tnow;
if (tcnt >= ticks)
{
break; /* 时间超过/等于要延迟的时间,则退出 */
}
}
}
}
函数 delay_ms()
用于毫秒级的 CPU 忙延时,原本的函数 delay_ms()
会判断 μC/OS 是否运行,如果 μC/OS 正在运行的话,就使用 μC/OS 的 OS 延时进行毫秒级的延时,否则就调用函数 delay_us()
进行毫秒级的 CPU 忙延时。在 FreeRTOS 中,可以将函数 delay_ms()
定义为只进行 CPU 忙延时,当需要 OS 延时的时候,调用 FreeRTOS 提供的 OS 延时函数 vTaskDelay()
进行系统节拍级延时,代码修改后如下图所示(删掉原来的delay_ms()
,改为下面的)
/**
* @brief 延时nms
* @param nms: 要延时的ms数 (0< nms <= 65535)
* @retval 无
*/
void delay_ms(uint16_t nms)
{
uint32_t i;
for (i=0; i<nms; i++)
{
delay_us(1000);
}
}
根据上述步骤的修改,delay.c 文件中使用到了 FreeRTOS 的相关函数,因此就需要在 delay.c 文件中包含 FreeRTOS 的相关头文件,并且移除掉原本存在的 μC/OS相关头文件。先看一下修改前 delay.c文件中包含的 μC/OS相关的头文件(标蓝部分):
修改成如下内容:
/* 添加公共头文件 (FreeRTOS 需要用到) */
#include "FreeRTOS.h"
#include "task.h"
至此,SYSTEM 文件夹针对 FreeRTOS 的修改就完成了。
在 FreeRTOS 的移植过程中会用到这三个重要的中断,分别是 FreeRTOS 系统时基定时器的中断( SysTick中断)、 SVC中断、 PendSV中断,这三个中断的中断服务函数在 HAL 库提供的文件中都有定义,对于正点原子不同的 STM32 开发板,对应了不同的文件,具体对应关系如下表所示:
正点原子 的 STM32系列开发板类型 | 中断服务函数所在文件 |
---|---|
STM32F1 | stm32f1xx_it.c |
STM32F4 | stm32f4xx_it.c |
STM32F7 | stm32f7xx_it.c |
STM32H7 | stm32h7xx_it.c |
其中,SysTick 的中断服务函数在 delay.c
文件中已经定义了,并且 FreeRTOS 也提供了 SVC 和 PendSV 的中断服务函数,因此需要将 HAL 库提供的这三个中断服务函数注释掉,这里采用宏开关的方式让 HAL 库中的这三个中断服务函数不加入编译 ,使用的宏在 sys.h
中定义,因此还需要导入 sys.h
头文件。大家可以按照上表找到对应的文件进行修改。下面是详细的修改步骤。
按照上表的文件名,找到和自己开发板对应的文件,如下图所示
导入 sys.h
头文件,导入后如下图所示
找到 SVC_Handler()
,然后按照下图的方式进行修改
/**
* @brief This function handles SVCall exception.
* @param None
* @retval None
*/
#if (!SYS_SUPPORT_OS)
void SVC_Handler(void)
{
}
#endif
找到 PendSV_Handler()
,然后按照下图的方式进行修改
/**
* @brief This function handles PendSVC exception.
* @param None
* @retval None
*/
#if (!SYS_SUPPORT_OS)
void PendSV_Handler(void)
{
}
#endif
找到 SysTick_Handler()
,然后按照下图的方式进行修改
/**
* @brief This function handles SysTick Handler.
* @param None
* @retval None
*/
#if (!SYS_SUPPORT_OS)
void SysTick_Handler(void)
{
HAL_IncTick();
}
#endif
最后,也是移植 FreeRTOS 要修改的最后一个地方 FreeRTOSConfig.h
文件中有如下定义:
#define configPRIO_BITS __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 |
STM32F7 | stm32f750xx.h或 stm32f767xx.h |
STM32H7 | stm32h750xx.h或 stm32h743xx.h |
原本的值为
#define __NVIC_PRIO_BITS 4U
这个值是对的,但是在FreeRTOS中会报错,于是改为下图所示的值
#define __NVIC_PRIO_BITS 4
到这里 FreeRTOS 已经移植完成了,不过还有一些可选步骤,也建议大家一起完成
USMART 是正点原子