STM32移植FreeRTOS
@firestaradmin 2020年12月2日15:47:03
STM32移植FreeRTOS操作记录
一、获取源码
- 获取STM32 官方STD 库
- 获取FreeRTOS V10.0.1 (版本不同,大致步骤相似,见招拆招即可)
二、移植步骤
1| 添加FreeRTOS 库
解压缩后,在工程目录创建OS\FreeRTOS 文件夹,将 FreeRTOSv10.0.1\FreeRTOS\Source 内的文件复制到工程目录
并在FreeRTOSv10.0.1\FreeRTOS\Demo 目录下寻找自己开发板的文件,这里以STM32F4 为例,打开目录 FreeRTOSv10.0.1\FreeRTOS\Demo\CORTEX_M4F_STM32F407ZG-SK ,并将 目录中的 FreeRTOSConfig.h 文件复制到 工程User目录下。
添加完毕后如图所示:
2| 配置STM32 工程
创建如下目录,并添加相应文件
FreeRTOS/port 目录下需要添加 OS\FreeRTOS\portable\目录中的 MemMang 内存操作文件(heap_4.c) 和 对于平台的 port.c 文件(对于STM32F4 使用\OS\FreeRTOS\portable\RVDS\ARM_CM4F目录中的port.c)
添加文件完毕后,配置头文件路径。配置完毕,如图所示:
编译报错如图:
原因是因为stm32f4xx_fmc.c 是针对于stm32f429 等芯片的,我们直接删除改文件即可。
再次编译报错:
打开FreeRTOSConfig.h 文件 将#ifdef __ICCARM__
修改为#if defined(__ICCARM__)||defined(__CC_ARM)||defined(__GNU__)
如图:
此处注意SystemCoreClock
的值要与你系统时钟移植,查看一下已确定。
再次编译显示
参考第三步配置FreeRTOS 时钟心跳。
3| 配置FreeRTOS 时钟心跳
将stm32f4xx_it.c 中的 SVC_Handler(void) PendSV_Handler(void) SysTick_Handler(void)
三个中断函数注释。
将FreeRTOSConfig.h 文件中 #define xPortSysTickHandler SysTick_Handler
注释,如图
在delay.c 中添加sysTick
中断 和 sysTick Init
如下代码:
/**
FreeRTOS
*/
#include "FreeRTOS.h"
#include "task.h"
static uint8_t fac_us=0; //us延时倍乘数, 1us 需要的 systick 计数次数
static uint16_t fac_ms=0; //ms延时倍乘数,在os下,代表每个节拍的ms数
/**
* @brief This function handles SysTick Handler.
* @param None
* @retval None
*/
//systick 中断服务函数
void SysTick_Handler(void)
{
#if (INCLUDE_xTaskGetSchedulerState == 1 )
if (xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED) {
#endif /* INCLUDE_xTaskGetSchedulerState */
xPortSysTickHandler();
#if (INCLUDE_xTaskGetSchedulerState == 1 )
}
#endif /* INCLUDE_xTaskGetSchedulerState */
}
/**
* @breif 初始化systick
SYSTICK 的时钟固定为 AHB 时钟,基础例程里面 SYSTICK 时钟频率为 AHB/8
这里为了兼容 FreeRTOS,所以将 SYSTICK 的时钟频率改为 AHB 的频率!
* @param SYSCLK 系统时钟频率 unit: MHz
*/
void delay_init(uint8_t SYSCLK)
{
uint32_t reload;
//SysTick 频率为 HCLK
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK);
fac_us=SYSCLK; //不论是否使用 OS,fac_us 都需要使用
//每秒钟的计数次数 单位为 K 根据 configTICK_RATE_HZ 设定溢出时间 reload = 168 K = 168 000
reload= SYSCLK * 1000000/configTICK_RATE_HZ;
//reload 为 24 位寄存器,最大值:16777216,
//在 168M 下,约合 0.0998s 左右
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
}
至此 心跳时基就已经OK了,但是有时候会使用延时函数(会引起任务调度或者不引起任务调度的延时),如下所示添加即可
/**
* @breif delay us. Will not cause task scheduling.
* @param nus time need to delay (unit|us)
range[0~204522252](max = 2^32/fac_us@fac_us=168)
*/
void delay_us(uint32_t nus)
{
uint32_t ticks;
uint32_t told, tnow, tcnt=0;
uint32_t reload = SysTick->LOAD; //LOAD的值
ticks = nus * 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; //时间超过/等于要延迟的时间,则退出.
}
};
}
/**
* @breif delay Ms. Will cause task scheduling.
* @param nms time need to delay (unit|Ms)
range[0~65535]
*/
void delay_ms(uint32_t nms)
{
if(xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED)//系统已经运行
{
if(nms >= fac_ms) //延时的时间大于 OS 的最少时间周期
{
vTaskDelay(nms / fac_ms); //FreeRTOS 延时
}
nms %= fac_ms; //OS 已经无法提供这么小的延时了,采用普通方式延时
}
delay_us((uint32_t)(nms*1000)); //普通方式延时
}
/**
* @breif delay Ms. Will not cause task scheduling.
* @param nms time need to delay (unit|Ms)
*/
void delay_xms(uint32_t nms)
{
uint32_t i;
for(i=0;i<nms;i++) delay_us(1000);
}
编译会提示 xPortSysTickHandler
函数隐式声明, 打开FreeRTOSConfig.h
文件,在最后添加如下代码声明即可:
void xPortSysTickHandler( void );
之后再次编译发现 几个Hook 函数报错,打开FreeRTOSConfig.h 文件 将如下几个宏定义定义改为0
#define configUSE_IDLE_HOOK 1
#define configUSE_TICK_HOOK 1
#define configCHECK_FOR_STACK_OVERFLOW 2
#define configUSE_MALLOC_FAILED_HOOK 1
如图所示:
三、 如何开始任务调度
编译无错后,打开main.c
添加如下代码:
/** -----------------------------------------
FreeRTOS
-----------------------------------------*/
#include "FreeRTOS.h"
#include "task.h"
/* Task handle */
/* 创建任务句柄 */
static TaskHandle_t AppTaskCreate_Handle = NULL;
/* Timer任务句柄 */
static TaskHandle_t Timer_Task_Handle = NULL;
static void AppTaskCreate(void); /* 用于创建任务 */
static void Timer_Task(void* pvParameters); /* LED_Task任务实现 */
static void bsp_init(void); /* bsp drivers init. */
int main(void)
{
BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
bsp_init();
printf("This is F4 FreeRTOS Demo!\r\n");
/* 创建AppTaskCreate任务 */
xReturn = xTaskCreate((TaskFunction_t )AppTaskCreate, /* 任务入口函数 */
(const char* )"AppTaskCreate",/* 任务名字 */
(uint16_t )128, /* 任务栈大小 */
(void* )NULL,/* 任务入口函数参数 */
(UBaseType_t )1, /* 任务的优先级 */
(TaskHandle_t* )&AppTaskCreate_Handle);/* 任务控制块指针 */
/* 启动任务调度 */
if(pdPASS == xReturn)
vTaskStartScheduler(); /* 启动任务,开启调度 */
else
return -1;
while(1); /* 正常不会执行到这里 */
}
static void AppTaskCreate(void)
{
BaseType_t xReturn = pdPASS; /* 定义一个创建信息返回值,默认为pdPASS */
taskENTER_CRITICAL(); //进入临界区
/* 创建Timer_Task任务 */
xReturn = xTaskCreate((TaskFunction_t )Timer_Task, /* 任务入口函数 */
(const char* )"Timer_Task", /* 任务名字 */
(uint16_t )128, /* 任务栈大小 */
(void* )NULL, /* 任务入口函数参数 */
(UBaseType_t )2, /* 任务的优先级 */
(TaskHandle_t* )&Timer_Task_Handle);/* 任务控制块指针 */
if(xReturn == pdPASS)
printf("Timer_Task Create succeed!\r\n");
else
printf("Timer_Task Create failed!\r\n");
vTaskDelete(AppTaskCreate_Handle);
taskEXIT_CRITICAL(); //退出临界区
}
/**
* @brief Timer_Task task main body.
*/
static void Timer_Task(void* parameter)
{
uint32_t t = 0;
while(1){
printf("time(1s): %d\r\n",t);
delay_ms(1000);
t++;
}
}
static void bsp_init(void)
{
SystemInit(); //晶振时钟初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//4位抢占优先级
delay_init(168);
usart1_init(115200);
}
编译运行测试。
四、优化配置
中断服务函数中代码如下:
//systick 中断服务函数
void SysTick_Handler(void)
{
#if (INCLUDE_xTaskGetSchedulerState == 1 )
if (xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED) {
#endif /* INCLUDE_xTaskGetSchedulerState */
xPortSysTickHandler();
#if (INCLUDE_xTaskGetSchedulerState == 1 )
}
#endif /* INCLUDE_xTaskGetSchedulerState */
}
可以看出,有宏定义判断#if (INCLUDE_xTaskGetSchedulerState == 1 )
, 所以我们可以打开FreeRTOS.h
文件 ,将宏#define INCLUDE_xTaskGetSchedulerState 0
改为1,启用任务可以获取调度器状态。
五、其他代码
delay.h
#ifndef __DELAY_H
#define __DELAY_H
#include "stm32f4xx.h"
void delay_init(uint8_t SYSCLK);
void delay_ms(uint32_t nms);
void delay_us(uint32_t nus);
#endif
delay.c
#include "delay.h"
/**
FreeRTOS
*/
#include "FreeRTOS.h"
#include "task.h"
/** Macro define */
#define SYSTEM_SUPPORT_OS 1 /* Define it to support OS. */
/** Private var define */
static uint8_t fac_us=0; //us延时倍乘数, 1us 需要的 systick 计数次数
static uint16_t fac_ms=0; //ms延时倍乘数,在os下,代表每个节拍的ms数
#if SYSTEM_SUPPORT_OS //如果SYSTEM_SUPPORT_OS定义了,说明要支持OS了(不限于UCOS).
/*****************SYSTEM_SUPPORT_OS********************/
/**
* @brief This function handles SysTick Handler.
* @param None
* @retval None
*/
void SysTick_Handler(void)
{
#if (INCLUDE_xTaskGetSchedulerState == 1 )
if (xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED) {
#endif /* INCLUDE_xTaskGetSchedulerState */
xPortSysTickHandler();
#if (INCLUDE_xTaskGetSchedulerState == 1 )
}
#endif /* INCLUDE_xTaskGetSchedulerState */
}
/**
* @breif 初始化systick
SYSTICK 的时钟固定为 AHB 时钟,基础例程里面 SYSTICK 时钟频率为 AHB/8
这里为了兼容 FreeRTOS,所以将 SYSTICK 的时钟频率改为 AHB 的频率!
* @param SYSCLK 系统时钟频率 unit: MHz
*/
void delay_init(uint8_t SYSCLK)
{
uint32_t reload;
//SysTick 频率为 HCLK
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK);
fac_us=SYSCLK; //不论是否使用 OS,fac_us 都需要使用
//每秒钟的计数次数 单位为 K 根据 configTICK_RATE_HZ 设定溢出时间 reload = 168 K = 168 000
reload= SYSCLK * 1000000/configTICK_RATE_HZ;
//reload 为 24 位寄存器,最大值:16777216,
//在 168M 下,约合 0.0998s 左右
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
}
/**
* @breif delay us. Will not cause task scheduling.
* @param nus time need to delay (unit|us)
range[0~204522252](max = 2^32/fac_us@fac_us=168)
*/
void delay_us(uint32_t nus)
{
uint32_t ticks;
uint32_t told, tnow, tcnt=0;
uint32_t reload = SysTick->LOAD; //LOAD的值
ticks = nus * 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; //时间超过/等于要延迟的时间,则退出.
}
};
}
/**
* @breif delay Ms. Will cause task scheduling.
* @param nms time need to delay (unit|Ms)
range[0~65535]
*/
void delay_ms(uint32_t nms)
{
if(xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED)//系统已经运行
{
if(nms >= fac_ms) //延时的时间大于 OS 的最少时间周期
{
vTaskDelay(nms / fac_ms); //FreeRTOS 延时
}
nms %= fac_ms; //OS 已经无法提供这么小的延时了,采用普通方式延时
}
delay_us((uint32_t)(nms*1000)); //普通方式延时
}
/**
* @breif delay Ms. Will not cause task scheduling.
* @param nms time need to delay (unit|Ms)
*/
void delay_xms(uint32_t nms)
{
uint32_t i;
for(i=0;i<nms;i++) delay_us(1000);
}
/*****************SYSTEM_SUPPORT_OS END********************/
#else
/*****************NO SYSTEM_SUPPORT_OS********************/
/**
* @breif 初始化systick
SYSTICK的时钟固定为AHB时钟的1/8
* @param SYSCLK 系统时钟频率 unit: MHz
*/
void delay_init(uint8_t SYSCLK)
{
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);
fac_us=SYSCLK/8; //不论是否使用OS,fac_us都需要使用
fac_ms=(uint16_t)fac_us*1000; //非OS下,代表每个ms需要的systick时钟数
}
/**
* @breif delay us.
* @param nus time need to delay (unit [us])
range[0~798915](max = 2^24/fac_us@fac_us=21)
*/
void delay_us(uint32_t nus)
{
uint32_t temp;
SysTick->LOAD=nus*fac_us; //时间加载
SysTick->VAL=0x00; //清空计数器
SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ; //开始倒数
do
{
temp=SysTick->CTRL;
}while((temp&0x01)&&!(temp&(1<<16))); //等待时间到达
SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk; //关闭计数器
SysTick->VAL =0X00; //清空计数器
}
/**
* @breif delay Ms.
注意nms的范围,SysTick->LOAD为24位寄存器,所以,最大延时为:nms<=0xffffff*8*1000/SYSCLK
SYSCLK单位为Hz,nms单位为ms。对168M条件下,nms<=798ms
* @param nms time need to delay (unit|Ms)
*/
void delay_xms(uint16_t nms)
{
uint32_t temp;
SysTick->LOAD=(uint32_t)nms*fac_ms; //时间加载(SysTick->LOAD为24bit)
SysTick->VAL =0x00; //清空计数器
SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ; //开始倒数
do
{
temp=SysTick->CTRL;
}while((temp&0x01)&&!(temp&(1<<16))); //等待时间到达
SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk; //关闭计数器
SysTick->VAL =0X00; //清空计数器
}
/**
* @breif delay Ms. Will cause task scheduling.
* @param nms time need to delay (unit|Ms)
range[0~65535]
*/
void delay_ms(uint16_t nms)
{
uint8_t repeat=nms/540; //这里用540,是考虑到某些客户可能超频使用,
//比如超频到248M的时候,delay_xms最大只能延时541ms左右了
uint16_t remain=nms%540;
while(repeat)
{
delay_xms(540);
repeat--;
}
if(remain)delay_xms(remain);
}
/*****************NO SYSTEM_SUPPORT_OS END********************/
#endif
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!