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

物联沃分享整理
物联沃-IOTWORD物联网 » STM32 HAL库移植freeRTOS V9.0.0至Keil

发表回复