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目录下。

添加完毕后如图所示:

image-20201202160233250

image-20201202175426792

2| 配置STM32 工程

创建如下目录,并添加相应文件

image-20201202160915739

FreeRTOS/port 目录下需要添加 OS\FreeRTOS\portable\目录中的 MemMang 内存操作文件(heap_4.c) 和 对于平台的 port.c 文件(对于STM32F4 使用\OS\FreeRTOS\portable\RVDS\ARM_CM4F目录中的port.c)

添加文件完毕后,配置头文件路径。配置完毕,如图所示:

image-20201202161501611


编译报错如图:

image-20201202162234504

原因是因为stm32f4xx_fmc.c 是针对于stm32f429 等芯片的,我们直接删除改文件即可。


再次编译报错:

image-20201202162333690

打开FreeRTOSConfig.h 文件 将#ifdef __ICCARM__ 修改为
#if defined(__ICCARM__)||defined(__CC_ARM)||defined(__GNU__) 如图:

image-20201202162553823

此处注意SystemCoreClock 的值要与你系统时钟移植,查看一下已确定。


再次编译显示

image-20201202162816847

参考第三步配置FreeRTOS 时钟心跳。

3| 配置FreeRTOS 时钟心跳

将stm32f4xx_it.c 中的 SVC_Handler(void) PendSV_Handler(void) SysTick_Handler(void)三个中断函数注释。

将FreeRTOSConfig.h 文件中 #define xPortSysTickHandler SysTick_Handler注释,如图

image-20201202175620531


在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

如图所示:

image-20201202175936488


三、 如何开始任务调度

编译无错后,打开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 协议 ,转载请注明出处!