目录
一、获取FreeRTOS源码
二、FreeRTOS源码简介
2.1、FreeRTOS源码文件内容
2.2、FreeRTOS内核
2.3、Source文件夹
2.4、portable文件夹
三、FreeRTOS手把手移植
3.1、FreeRTOS移植准备
3.2、FreeRTOS移植步骤
3.2.1、将 FreeRTOS 源码添加至基础工程、头文件路径等
3.2.2、添加 FreeRTOSConfig.h 配置文件
3.2.3、修改 SYSTEM 文件中的 sys.c、delay.c、usart.c
3.2.4、修改 Systick 中断、SVC 中断、PendSV 中断
3.2.5、验证移植是否成功
一、获取FreeRTOS源码
方法一:FreeRTOS 官网:freertos.orgfreertos.orgfreertos.org
方法二:正点原子官网获取,软件资料 —> FreeRTOS 学习资料
二、FreeRTOS源码简介
2.1、FreeRTOS源码文件内容
名称 | 描述 |
FreeRTOS | FreeRTOS 内核 |
FreeRTOS-Plus | FreeRTOS 组件 |
tools | 工具 |
GitHub-FreeRTOS-Home | FreeRTOS 的 GitHub 仓库链接 |
Quick_Start_Guide | 快速入门指南官方文档链接 |
Upgrading to FreeRTOS-xxx | 升级到指定 FreeRTOS 版本官方文档链接 |
History.txt | FreeRTOS 历史更新记录 |
其他 | 其他 |
2.2、FreeRTOS内核
名称 | 描述 |
Demo | FreeRTOS 演示例程 |
License | FreeRTOS 相关许可 |
Source | FreeRTOS 源码 |
Test | 公用以及移植层测试代码 |
2.3、Source文件夹
名称 | 描述 |
include | 包含了 FreeRTOS 的头文件 |
portable | 包含了 FreeRTOS 的移植文件 |
croutine.c | 协程相关文件 |
event_groups.c | 事件相关文件 |
list.c | 列表相关文件 |
queue.c | 队列相关文件 |
stream_buffer.c | 流式缓冲区相关文件 |
tasks.c | 任务相关文件 |
timers.c | 软件定时器相关文件 |
2.4、portable文件夹
FreeRTOS 操作系统归根到底是一个软件层面的东西,那 FreeRTOS 是如何跟硬件联系在一起的呢?
portable 文件夹里面的东西就是连接桥梁,由于我们使用 MDK 开发,因此这里只重点介绍其中的部分移植文件
名称 | 描述 |
Keil | 指向 RVDS 文件夹 |
RVDS | 不同内核芯片的移植文件 |
MemMang | 内存管理文件 |
三、FreeRTOS手把手移植
3.1、FreeRTOS移植准备
有了 FreeRTOS 源码之后,还需要一个 HAL 库版本的实验工程作为基础工程进行 FreeRTOS 的移植
这里以正点原子的 HAL 库版本内存管理实验作为基础工程
3.2、FreeRTOS移植步骤
3.2.1、将 FreeRTOS 源码添加至基础工程、头文件路径等
在Middlewares文件夹中新建FreeRTOS文件夹
将Source文件夹中的FreeRTOS源码复制进去,并删除非源码文件.gitmodules
打开portable文件夹,保留Keil、MemMang、RVDS文件夹,其余删除
打开项目工程,点击分组管理图标,新建分组名为Middlewares/FreeRTOS_CORE、Middlewares/FreeRTOS_PORT,点击OK
选择Middlewares/FreeRTOS_CORE分组,点击 Add Files,回到FreeRTOS目录,添加当前目录下的所有.c文件,点击 Add
选择Middlewares/FreeRTOS_PORT分组,点击 Add Files,回到FreeRTOS目录,打开portable/MemMang文件夹,选择heap_4.c内存管理算法,点击 Add
回到portable目录,打开RVDS文件夹,根据自己的芯片选择对应的连接桥梁,本人使用的是STM32F407的开发板,对应选择ARM_CM4F文件夹中的port.c文件,点击 Add
开发板芯片 | port.c 所在文件夹 |
STM32F1 | ARM_CM3 |
STM32F4 | ARM_CM4F |
STM32F7 | ARM_CM7 |
STM32H7 | ARM_CM7 |
完成设置后的分组
打开魔术棒,点击C/C++,设置头文件包含路径如下图
3.2.2、添加 FreeRTOSConfig.h 配置文件
FreeRTOSConfig.h 是FreeRTOS 操作系统的配置文件,FreeRTOS 操作系统是可裁剪的,用户可以根据需求对 FreeRTOS 进行裁剪,裁剪掉不需要用到的 FreeRTOS 功能,以此来节约 MCU 中寸土寸金的内存资源
FreeRTOSConfig.h 文件的获取途径有三种
1、用户自行编写(不建议新手使用)
2、在 FreeRTOS 内核文件中的 Demo 文件夹包含了 FreeRTOS 官方提供的演示工程,在这些演示工程中就包含了每个演示工程对应的 FreeRTOSConfig.h 文件
3、在正点原子 FreeRTOS 移植实验中复制
将获取到的 FreeRTOSConfig.h 文件放在 User 文件夹下
3.2.3、修改 SYSTEM 文件中的 sys.c、delay.c、usart.c
打开 sys.h 文件,将 SYS_SUPPORT_OS 宏定义修改为 1,支持os
打开 usart.c 文件,在中断服务函数中删除 OSIntEnter() 和 OSIntExit() 函数(在 μC/OS中 会使用到,FreeRTOS 中不会)
删除前
/**
* @brief 串口1中断服务函数
* @param 无
* @retval 无
*/
void USART_UX_IRQHandler(void)
{
#if SYS_SUPPORT_OS /* 使用OS */
OSIntEnter();
#endif
HAL_UART_IRQHandler(&g_uart1_handle); /* 调用HAL库中断处理公用函数 */
#if SYS_SUPPORT_OS /* 使用OS */
OSIntExit();
#endif
}
删除后
/**
* @brief 串口1中断服务函数
* @param 无
* @retval 无
*/
void USART_UX_IRQHandler(void)
{
HAL_UART_IRQHandler(&g_uart1_handle); /* 调用HAL库中断处理公用函数 */
}
删除以下代码(在 μC/OS中 会使用到,FreeRTOS 中不会)
打开 delay.c 文件,删除以下代码(在 μC/OS中 会使用到,FreeRTOS 中不会)
/* 定义g_fac_ms变量, 表示ms延时的倍乘数, 代表每个节拍的ms数, (仅在使能os的时候,需要用到) */
static uint16_t g_fac_ms = 0;
/*
* 当delay_us/delay_ms需要支持OS的时候需要三个与OS相关的宏定义和函数来支持
* 首先是3个宏定义:
* delay_osrunning :用于表示OS当前是否正在运行,以决定是否可以使用相关函数
* delay_ostickspersec:用于表示OS设定的时钟节拍,delay_init将根据这个参数来初始化systick
* delay_osintnesting :用于表示OS中断嵌套级别,因为中断里面不可以调度,delay_ms使用该参数来决定如何运行
* 然后是3个函数:
* delay_osschedlock :用于锁定OS任务调度,禁止调度
* delay_osschedunlock:用于解锁OS任务调度,重新开启调度
* delay_ostimedly :用于OS延时,可以引起任务调度.
*
* 本例程仅作UCOSII的支持,其他OS,请自行参考着移植
*/
/* 支持UCOSII */
#define delay_osrunning OSRunning /* OS是否运行标记,0,不运行;1,在运行 */
#define delay_ostickspersec OS_TICKS_PER_SEC /* OS时钟节拍,即每秒调度次数 */
#define delay_osintnesting OSIntNesting /* 中断嵌套级别,即中断嵌套次数 */
/**
* @brief us级延时时,关闭任务调度(防止打断us级延迟)
* @param 无
* @retval 无
*/
void delay_osschedlock(void)
{
OSSchedLock(); /* UCOSII的方式,禁止调度,防止打断us延时 */
}
/**
* @brief us级延时时,恢复任务调度
* @param 无
* @retval 无
*/
void delay_osschedunlock(void)
{
OSSchedUnlock(); /* UCOSII的方式,恢复调度 */
}
/**
* @brief us级延时时,恢复任务调度
* @param ticks: 延时的节拍数
* @retval 无
*/
void delay_ostimedly(uint32_t ticks)
{
OSTimeDly(ticks); /* UCOSII延时 */
}
在SysTick_Handler函数前添加extern void xPortSysTickHandler(void);代码,用于处理 FreeRTOS 系统时钟节拍
extern void xPortSysTickHandler(void);
/**
* @brief systick中断服务函数,使用OS时用到
* @param ticks : 延时的节拍数
* @retval 无
*/
void SysTick_Handler(void)
{
/* OS 开始跑了,才执行正常的调度处理 */
if (delay_osrunning == OS_TRUE)
{
/* 调用 uC/OS-II 的 SysTick 中断服务函数 */
OS_CPU_SysTickHandler();
}
HAL_IncTick();
}
修改SysTick_Handler函数如下
/**
* @brief systick中断服务函数,使用OS时用到
* @param ticks : 延时的节拍数
* @retval 无
*/
void SysTick_Handler(void)
{
HAL_IncTick();
/* OS 开始跑了,才执行正常的调度处理 */
if (xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED)
{
xPortSysTickHandler();
}
}
修改delay_init函数
修改前
/**
* @brief 初始化延迟函数
* @param sysclk: 系统时钟频率, 即CPU频率(rcc_c_ck), 168MHz
* @retval 无
*/
void delay_init(uint16_t sysclk)
{
#if SYS_SUPPORT_OS /* 如果需要支持OS */
uint32_t reload;
#endif
g_fac_us = sysclk; /* 由于在HAL_Init中已对systick做了配置,所以这里无需重新配置 */
#if SYS_SUPPORT_OS /* 如果需要支持OS. */
reload = sysclk; /* 每秒钟的计数次数 单位为M */
reload *= 1000000 / delay_ostickspersec; /* 根据delay_ostickspersec设定溢出时间,reload为24位
* 寄存器,最大值:16777216,在168M下,约合0.09986s左右
*/
g_fac_ms = 1000 / delay_ostickspersec; /* 代表OS可以延时的最少单位 */
SysTick->CTRL |= 1 << 1; /* 开启SYSTICK中断 */
SysTick->LOAD = reload; /* 每1/delay_ostickspersec秒中断一次 */
SysTick->CTRL |= 1 << 0; /* 开启SYSTICK */
#endif
}
修改后
/**
* @brief 初始化延迟函数
* @param sysclk: 系统时钟频率, 即CPU频率(rcc_c_ck), 168MHz
* @retval 无
*/
void delay_init(uint16_t sysclk)
{
#if SYS_SUPPORT_OS /* 如果需要支持OS */
uint32_t reload;
#endif
g_fac_us = sysclk; /* 由于在HAL_Init中已对systick做了配置,所以这里无需重新配置 */
#if SYS_SUPPORT_OS /* 如果需要支持OS. */
reload = sysclk; /* 每秒钟的计数次数 单位为M */
reload *= 1000000 / configTICK_RATE_HZ; /* 根据delay_ostickspersec设定溢出时间,reload为24位
* 寄存器,最大值:16777216,在168M下,约合0.09986s左右
*/
SysTick->CTRL |= 1 << 1; /* 开启SYSTICK中断 */
SysTick->LOAD = reload; /* 每1/delay_ostickspersec秒中断一次 */
SysTick->CTRL |= 1 << 0; /* 开启SYSTICK */
#endif
}
修改delay_us函数
修改前
/**
* @brief 延时nus
* @note 无论是否使用OS, 都是用时钟摘取法来做us延时
* @param nus: 要延时的us数
* @note nus取值范围: 0 ~ (2^32 / fac_us) (fac_us一般等于系统主频, 自行套入计算)
* @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; /* 需要的节拍数 */
#if SYS_SUPPORT_OS /* 如果需要支持OS */
delay_osschedlock(); /* 锁定 OS 的任务调度器 */
#endif
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; /* 时间超过/等于要延迟的时间,则退出 */
}
}
}
#if SYS_SUPPORT_OS /* 如果需要支持OS */
delay_osschedunlock(); /* 恢复 OS 的任务调度器 */
#endif
}
修改后
/**
* @brief 延时nus
* @note 无论是否使用OS, 都是用时钟摘取法来做us延时
* @param nus: 要延时的us数
* @note nus取值范围: 0 ~ (2^32 / fac_us) (fac_us一般等于系统主频, 自行套入计算)
* @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函数
修改前
/**
* @brief 延时nms
* @param nms: 要延时的ms数 (0< nms <= (2^32 / fac_us / 1000))(fac_us一般等于系统主频, 自行套入计算)
* @retval 无
*/
void delay_ms(uint16_t nms)
{
#if SYS_SUPPORT_OS /* 如果需要支持OS, 则根据情况调用os延时以释放CPU */
if (delay_osrunning && delay_osintnesting == 0) /* 如果OS已经在跑了,并且不是在中断里面(中断里面不能任务调度) */
{
if (nms >= g_fac_ms) /* 延时的时间大于OS的最少时间周期 */
{
delay_ostimedly(nms / g_fac_ms); /* OS延时 */
}
nms %= g_fac_ms; /* OS已经无法提供这么小的延时了,采用普通方式延时 */
}
#endif
delay_us((uint32_t)(nms * 1000)); /* 普通方式延时 */
}
修改后
/**
* @brief 延时nms
* @param nms: 要延时的ms数 (0< nms <= (2^32 / fac_us / 1000))(fac_us一般等于系统主频, 自行套入计算)
* @retval 无
*/
void delay_ms(uint16_t nms)
{
uint32_t i;
for (i = 0; i < nms; ++i)
{
delay_us(1000);
}
}
修改公共头文件
修改前
/* 添加公共头文件 ( ucos需要用到) */
#include "os.h"
修改后
/* 添加公共头文件 (FreeRTOS 需要用到) */
#include "FreeRTOS.h"
#include "task.h"
3.2.4、修改 Systick 中断、SVC 中断、PendSV 中断
打开stm32f4xx_it.c文件,引入sys.h头文件
采用宏定义的方式屏蔽Systick、SVC、PendSV函数的定义
/**
* @brief This function handles SVCall exception.
* @param None
* @retval None
*/
#if (!SYS_SUPPORT_OS)
void SVC_Handler(void)
{
}
#endif
/**
* @brief This function handles Debug Monitor exception.
* @param None
* @retval None
*/
void DebugMon_Handler(void)
{
}
/**
* @brief This function handles PendSVC exception.
* @param None
* @retval None
*/
#if (!SYS_SUPPORT_OS)
void PendSV_Handler(void)
{
}
#endif
/**
* @brief This function handles SysTick Handler.
* @param None
* @retval None
*/
#if (!SYS_SUPPORT_OS)
void SysTick_Handler(void)
{
HAL_IncTick();
}
#endif
编译项目工程(报错不用管),打开 FreeRTOSConfig.h 文件,找到如下宏定义转跳到定义
将4U修改成4
3.2.5、验证移植是否成功
点击全编译
编译完成后显示零错误零警告,工程模板创建完毕!!!