STM32 HAL库移植freeRTOS V9.0.0至Keil
Keil 版本:Keil MDK uVision5
基础程序:正点原子(hal库实验1 跑马灯实验)
硬件:正点原子Mini开发板
1.打开工程
2.下载FreeRTOS V9.0.0
FreeRTOS Real Time Kernel (RTOS) – Browse /FreeRTOS at SourceForge.net
3.添加 FreeRTOS 源码
1)在基础工程中新建一个名为 FreeRTOS 的文件夹;
2)创建 FreeRTOS 文件夹以后就可以将 FreeRTOS 的源码添加到这个文件夹中;
3)在portable
文件夹中,我们只需要留下
keil
、
MemMang
和 RVDS这三个文件夹,其他的都可以删除掉;
4.向工程分组中添加文件
打开基础工程,新建分组 FreeRTOS_CORE 和 FreeRTOS_PORTABLE,然后向这两个分组
中添加文件;
port.c
是
RVDS
文件夹下的 ARM_CM3
中的文件,因为
STM32F103
是
Cortex-M3
内核的,因此要选择
ARM_CM3 中的 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。
5.添加相应的头文件路径
6.添加FreeRTOSConfig.h 文件在FreeRTOS\include。
FreeRTOSConfig.h具体内容见下篇文章。FreeRTOSConfig.h 是何方神圣?看名字就知道,他是 FreeRTOS 的配置文件,一般的操作系统都有裁剪、配置功能,而这些裁剪及配置都是通过一个文件来完成的,基本都是通过宏定 义来完成对系统的配置和裁剪的。
7.修改 SYSTEM 文件
1)
修改 sys.h
文件 ,
将宏
SYSTEM_SUPPORT_OS
改为
1;
#define SYSTEM_SUPPORT_OS 1 //定义系统文件夹是否支持
OS
2)修改
usart.c
文件
usart.c
文件修改也很简单,
usart.c
文件有两部分要修改,一个是添加
FreeRTOS.h
头文件,
默认是添加的
UCOS
中的
includes.h
头文件,修改以后如下:
另外一个就是 USART1
的中断服务函数,在使用
UCOS
的时候进出中断的时候需要添加
OSIntEnter()
和
OSIntExit()
,使用
FreeRTOS
的话就不需要了,所以将这两行代码删除掉。
3)修改delay.c文件;
替换成以下代码:
#include "delay.h"
#include "sys.h"
//
//如果需要使用OS,则包括下面的头文件即可.
#if SYSTEM_SUPPORT_OS
#include "FreeRTOS.h" //os 使用
#include "task.h"
#endif
static u32 fac_us=0; //us延时倍乘数
#if SYSTEM_SUPPORT_OS
static u16 fac_ms=0; //ms延时倍乘数,在os下,代表每个节拍的ms数
#endif
#if SYSTEM_SUPPORT_OS //如果需要支持OS.
extern void xPortSysTickHandler(void);
//systick中断服务函数,使用ucos时用到
void SysTick_Handler(void)
{
if(xTaskGetSchedulerState()!=taskSCHEDULER_NOT_STARTED)//系统已经运行
{
xPortSysTickHandler();
}
HAL_IncTick();
}
//初始化延迟函数
//当使用ucos的时候,此函数会初始化ucos的时钟节拍
//SYSTICK的时钟固定为AHB时钟
//SYSCLK:系统时钟频率
void delay_init(u8 SYSCLK)
{
u32 reload;
HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);//SysTick频率为HCLK
fac_us=SYSCLK; //不论是否使用OS,fac_us都需要使用
reload=SYSCLK; //每秒钟的计数次数 单位为K
reload*=1000000/configTICK_RATE_HZ; //根据delay_ostickspersec设定溢出时间
//reload为24位寄存器,最大值:16777216,在72M下,约合0.233s左右
fac_ms=1000/configTICK_RATE_HZ; //代表OS可以延时的最少单位
SysTick->CTRL|=SysTick_CTRL_TICKINT_Msk;//开启SYSTICK中断
SysTick->LOAD=reload; //每1/OS_TICKS_PER_SEC秒中断一次
SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk; //开启SYSTICK
}
//延时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)
{
if(tnow<told)tcnt+=told-tnow; //这里注意一下SYSTICK是一个递减的计数器就可以了.
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);
}
#else //不用ucos时
void delay_init(u8 SYSCLK)
{
HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);//SysTick频率为HCLK
fac_us=SYSCLK; //不论是否使用OS,fac_us都需要使用
}
//延时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)
{
if(tnow<told)tcnt+=told-tnow; //这里注意一下SYSTICK是一个递减的计数器就可以了.
else tcnt+=reload-tnow+told;
told=tnow;
if(tcnt>=ticks)break; //时间超过/等于要延迟的时间,则退出.
}
};
}
//延时nms
//nms:要延时的ms数
void delay_ms(u16 nms)
{
u32 i;
for(i=0;i<nms;i++) delay_us(1000);
}
#endif
解释:
delay.c 文件修改的就比较大了,因为涉及到 FreeRTOS 的系统时钟,delay.c 文件里面有 4
个函数,先来看一下函数
SysTick_Handler()
,此函数是滴答定时器的中断服务函数,代码如下:
extern void xPortSysTickHandler(void); //systick 中断服务函数
,
使用
OS
时用到
void SysTick_Handler(void)
{
if(xTaskGetSchedulerState()!=taskSCHEDULER_NOT_STARTED)//
系统已经运行
{
xPortSysTickHandler();
}
}
FreeRTOS
的心跳就是由滴答定时器产生的,根据
FreeRTOS
的系统时钟节拍设置好滴答定
时器的周期,这样就会周期触发滴答定时器中断了。在滴答定时器中断服务函数中调用
FreeRTOS
的
API
函数
xPortSysTickHandler()
。
delay_init()
是用来初始化滴答定时器和延时函数,代码如下:
//
初始化延迟函数
//SYSTICK
的时钟固定为
AHB
时钟,基础例程里面
SYSTICK
时钟频率为
AHB/8
//
这里为了兼容
FreeRTOS
,所以将
SYSTICK
的时钟频率改为
AHB
的频率!
//SYSCLK:
系统时钟频率
void delay_init()
{
u32 reload;
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK);//
选择外部时钟
HCLK
fac_us=SystemCoreClock/1000000;
//
不论是否使用
OS,fac_us
都需要使用
reload=SystemCoreClock/1000000;
//
每秒钟的计数次数 单位为
M
reload*=1000000/configTICK_RATE_HZ;
//
根据
configTICK_RATE_HZ
设定溢出
//
时间
reload
为
24
位寄存器
,
最大值
:
//16777216,
在
72M
下
,
约合
0.233s
左右
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
来设置,这个值我们可以自由设置,但是一旦设置好以后我
们就要根据这个值来初始化滴答定时器,其实就是设置滴答定时器的中断周期。在基础例程中
滴答定时器的时钟频率设置的是
AHB
的
1/8
,这里为了兼容
FreeRTOS
将滴答定时器的时钟频
率改为了
AHB
,也就是
72MHz
!
接下来的三个函数都是延时的,代码如下:
//
延时
nus
//nus:
要延时的
us
数
.
//nus:0~204522252(
最大值即
2^32/fac_us@fac_us=168)
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()
的时候就会导致任务切换。
8.屏蔽stm32f10x_it.c 中的三个函数。
port.c、delay.c 和 stm32f10x_it.c 中三个重复定义的函数:SysTick_Handler()、SVC_Handler()和 PendSV_Handler(),这三个函数分别为滴答定时器中断服 务函数、SVC 中断服务函数和 PendSV 中断服务函数,将 stm32f10x_it.c 中的三个函数屏蔽掉。
9.替换main.c文件。
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "FreeRTOS.h"
#include "task.h"
#include "FreeRTOSConfig.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 20
//任务句柄
TaskHandle_t LED0Task_Handler;
//任务函数
void led0_task(void *pvParameters);
//任务优先级
#define LED1_TASK_PRIO 3
//任务堆栈大小
#define LED1_STK_SIZE 20
//任务句柄
TaskHandle_t LED1Task_Handler;
//任务函数
void led1_task(void *pvParameters);
int main(void)
{
HAL_Init(); //初始化HAL库
Stm32_Clock_Init(RCC_PLL_MUL10); //设置时钟,72M
delay_init(70); //初始化延时函数
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(); //开启任务调度
return(1);
// while(1){
// PAout(6)=~PAout(6);
// delay_ms(500);
// PAout(7)=~PAout(7);
// delay_ms(1000);
// };
}
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);
vTaskDelete(StartTask_Handler); //删除开始任务
taskEXIT_CRITICAL(); //退出临界区
}
//LED0任务函数
void led0_task(void *pvParameters)
{
while(1)
{
LED0=~LED0;
vTaskDelay(1000);
}
}
//LED1任务函数
void led1_task(void *pvParameters)
{
while(1)
{
LED1=~LED1;
vTaskDelay(2000);
}
}
有错误。
10.修改错误。
1)方法1:
找这个定义 configMAX_SYSCALL_INTERRUPT_PRIORITY
再找 configPRIO_BITS
再找 __NVIC_PRIO_BITS
发现定为4U , 去掉U改成4就可以了
一般在 Drivers\CMSIS\Device\ST\STM32FXxx\Include\stm32xxx.h
但是我修改不了这个文件,所以采用方法2)。
2)方法2:在port.c的__asm void xPortPendSVHandler( void )之前加上
#undef configPRIO_BITS
#define configPRIO_BITS 4
11.参考文献
1)《STM32F1 FreeRTOS 开发手册 V1.1》第二章;
2)STM32 HAL库 +freeRTOS+Keil 移植 – 孤燕 – 博客园 (cnblogs.com)
3)FreeRTOS STM32CubeMX port.c(483): error: A1586E: Bad operand types (UnDefOT, Constant) …-CSDN博客
作者:qq_42918764