FreeRTOS移植

发布时间 2023-08-25 13:31:40作者: ike_li

一、知识总结

运行FreeRTOS的系统与裸机系统主要区别在于执行业务逻辑的方式发生了改变。裸机系统一般会采用主循环轮询+定时器中断轮询+其他中断抢占的方式来处理复杂的多任务。
而FreeRTOS则采用根据功能的不同创建不同优先级的任务,然后借助任务调度器、任务通知等一系列的机制自动分配任务执行的顺序与时间。
1.好的资源
STM32F103/F407的FreeRTOS移植 https://www.codenong.com/cs110642632/
FreeRTOS源码结构分析与移植(基于STM32F407) https://bbs.huaweicloud.com/blogs/296103
移植讲的好视频https://www.bilibili.com/video/BV1GN4y157fy?spm_id_from=333.337.search-card.all.click&vd_source=2f31a001ad4383960409e00f558f4d7d
2.任务优先级
2.1 任务优先级数字越大,任务优先级越高。
2.2 当某个任务需要延时,调用vTaskDelay(),则该任务进入阻塞态,此时调度器会从就绪列表中找到优先级最高的就绪任务开始执行。

二、移植

keil STM32F407ZGT6
1.在项目新建文件夹FreeRTOS,把FreeRTOSv202112.00\FreeRTOS\Source 所有文件拷贝到项目下新建的文件夹FreeRTOS。

2.STM32F40x_FreeRTOS_Test\FreeRTOS\portable ,protable中保留如下三个文件,其它删除掉

3.添加头文件路径
STM32F40x_FreeRTOS_Test\FreeRTOS\include
STM32F40x_FreeRTOS_Test\FreeRTOS\portable\RVDS\ARM_CM4F

4.FreeRTOSConfig.h
FreeRTOSv202112.00\FreeRTOS\Demo\CORTEX_M4F_STM32F407ZG-SK\FreeRTOSConfig.h 拷贝到 STM32F40x_FreeRTOS_Test\FreeRTOS\include

5.编译,出错“..\FreeRTOS\portable\RVDS\ARM_CM4F\port.c(734): error: #20: identifier "SystemCoreClock" is undefined”
因为在"FreeRTOSConfig.h" : 中使用了SysyemCoreClock来标记MCU的频率。
在FreeRTOSConfig.h 第45行,#ifdef ICCARM 修改成 #if defined(ICCARM) || defined(__CC_ARM) || defined(GNU)

6.编译,出以下错误信息
.\Objects\STM32F40x_FreeRTOS_Test.axf: Error: L6200E: Symbol SVC_Handler multiply defined (by stm32f4xx_it.o and port.o).
.\Objects\STM32F40x_FreeRTOS_Test.axf: Error: L6200E: Symbol PendSV_Handler multiply defined (by stm32f4xx_it.o and port.o).
.\Objects\STM32F40x_FreeRTOS_Test.axf: Error: L6200E: Symbol SysTick_Handler multiply defined (by stm32f4xx_it.o and port.o).

因为在FreeRTOSConfig.h 中已定义,和stm32f4xx_it.c中冲突

在stm32f4xx_it.c注释掉SVC_Handler,PendSV_Handler,SysTick_Handler 三个函数。

7.编译,出以下错误信息

这是因为在"FreeRTOSConfig.h"中定义了这些钩子函数,但未找到函数定义,我们先注释掉这些定义,

就是将以下宏定义定义为0即可
configUSE_IDLE_HOOK
configUSE_TICK_HOOK
configCHECK_FOR_STACK_OVERFLOW
configUSE_MALLOC_FAILED_HOOK

8.编译,无错误信息,成功。

9.配置MCU的中断优先级。移植了FreeRTOS的MCU一般采用第四中断优先级分组,即全部的四位都用来配置抢占优先级(共16个抢占式中断优先级)。

NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);

10.修改延时函数

延时函数在STM32单片机开发的项目中有广泛的应用,微秒级延时在一些时间要求严格的场景下(例如软件模拟I2C通讯)是必不可少的。由于FreeRTOS默认采用了system tick作为时间片分配的时基定时器,可能与利用system tick设计的延时函数出现冲突。再加上FreeRTOS提供的延时函数void vTaskDelay( const TickType_t xTicksToDelay )最小的延时时间等于FreeRTOS的tick时间(一般设置为1ms),因此需要重新设计一套不基于system tick的微秒级延时函数。利用CM3/4内核中的数据观察点与跟踪(DWT)寄存器,可以在不占用硬件外设定时器的情况下实现微秒级的精准延时。

具体见另一篇文章:https://www.cnblogs.com/ike_li/p/17447986.html

11.FreeRTOSConfig.h 文件配置注释
来源:https://www.codenong.com/cs110642632/

/*
 * FreeRTOS 支持的调度方式FreeRTOS 操作系统支持三种调度方式:
 * 抢占式调度,时间片调度和合作式调度。
 * 实际应用主要是抢占式调度和时间片调度,合作式调度用到的很少。
 *
 * 抢占式调度:
 * 每个任务都有不同的优先级,任务会一直运行直到被高优先级任务抢占或者遇到阻塞式的 API 函数,比如 vTaskDelay。
 *
 * 时间片调度:
 * 每个任务都有相同的优先级,任务会运行固定的时间片个数或者遇到阻塞式的 API 函数,比如vTaskDelay,才会执行同优先级任务之间的任务切换。
 * 在 FreeRTOS 操作系统中只有同优先级任务才会使用时间片调度
 */

/**
 * 配置为 1, 使能抢占式调度器
 * 配置为 0, 使能合作式调度器
 */
#define configUSE_PREEMPTION                     1

/**
 * 只能使用编译器提供的RAM创建RTOS对象
 */
#define configSUPPORT_STATIC_ALLOCATION          0

/**
 * 使用从FreeRTOS堆自动分配的RAM创建RTOS对象
 */
#define configSUPPORT_DYNAMIC_ALLOCATION         1

/**
 * 钩子函数的主要功能是用于函数的扩展,用户可以根据自己的需要往里面添加相关的测试函数。
 */
//使能空闲任务的钩子函数
#define configUSE_IDLE_HOOK                      0
//使能滴答定时器中断里面执行的钩子函数
#define configUSE_TICK_HOOK                      0

/**
 * 此参数用于定义 CPU 的主频,单位 Hz
 */
#define configCPU_CLOCK_HZ                       ( SystemCoreClock )

/**
 * 此参数用于定义系统时钟节拍数,单位 Hz,一般取 1000Hz 即可
 */
#define configTICK_RATE_HZ                       ((TickType_t)1000)

/**
 * 此参数用于定义可供用户使用的最大优先级数,
 * 如果这个定义的是 5,那么用户可以使用的优先级号是 0,1,2,3,4,不包含 5
 */

#define configMAX_PRIORITIES                     ( 32 )

/**
 * 此参数用于定义空闲任务的栈空间大小,单位字,即 4 字节
 */
#define configMINIMAL_STACK_SIZE                 ((uint16_t)128)

/**
 * 定义堆大小,FreeRTOS 内核,用户动态内存申请,任务栈,任务创建,信号量创建,消息队列创建等都需要用这个空间
 */
#define configTOTAL_HEAP_SIZE                    ((size_t)10*1024)

/**
 * 定义任务名最大的字符数,末尾的结束符 '\0'也要计算在内
 */
#define configMAX_TASK_NAME_LEN                  ( 16 )

/**
 * 系统时钟节拍计数使用 TickType_t 数据类型定义的,
 * 如果用户使能了宏定义 configUSE_16_BIT_TICKS,那么 TickType_t 定义的就是 16 位无符号数,
 * 如果没有使能,那么 TickType_t 定义的就是 32 位无符号数。
 * 对于 32 位架构的处理器,一定要禁止此宏定义,即设置此宏定义数值为 0 即可。
 * 而 16 位无符号数类型主要用于 8 位和 16 位架构的处理器。
 */
#define configUSE_16_BIT_TICKS                   0

/**
 * 使能互斥信号量
 */
#define configUSE_MUTEXES                        1

/**
 * 通过此定义来设置可以注册的信号量和消息队列个数。
 */
#define configQUEUE_REGISTRY_SIZE                8

/* 某些运行FreeRTOS的硬件有两种方法选择下一个要执行的任务:
 * 通用方法和特定于硬件的方法(以下简称“特殊方法”)。
 *
 * 通用方法:
 *      1.configUSE_PORT_OPTIMISED_TASK_SELECTION 为 0 或者硬件不支持这种特殊方法。
 *      2.可以用于所有FreeRTOS支持的硬件
 *      3.完全用C实现,效率略低于特殊方法。
 *      4.不强制要求限制最大可用优先级数目
 * 特殊方法:
 *      1.必须将configUSE_PORT_OPTIMISED_TASK_SELECTION设置为1
 *      2.依赖一个或多个特定架构的汇编指令(一般是类似计算前导零[CLZ]指令)
 *      3.比通用方法更高效
 *      4.一般强制限定最大可用优先级数目为32
 * 一般是硬件计算前导零指令,如果所使用的,MCU没有这些硬件指令的话此宏应该设置为0
 */

/**
 * 此配置用于优化优先级列表中要执行的最高优先级任务的算法。对 CM 内核的移植文件,默认已经在文件 portmacro.h 文件中使能。
 */
#define configUSE_PORT_OPTIMISED_TASK_SELECTION  1

/* Co-routine definitions. */
/**
 * 使能合作式调度相关函数
 */
#define configUSE_CO_ROUTINES       0

/**
 * 此参数用于定义可供用户使用的最大的合作式任务优先级数
 */
#define configMAX_CO_ROUTINE_PRIORITIES ( 2 )

/* Software timer definitions. */
//禁能软件定时器
#define configUSE_TIMERS                0
//配置软件定时器任务的优先级
#define configTIMER_TASK_PRIORITY       ( 3 )
//配置软件定时器命令队列的长度
#define configTIMER_QUEUE_LENGTH        5
//配置软件定时器任务的栈空间大小
#define configTIMER_TASK_STACK_DEPTH    (configMINIMAL_STACK_SIZE)

/* Set the following definitions to 1 to include the API function, or zero
to exclude the API function. */
#define INCLUDE_vTaskPrioritySet        1
#define INCLUDE_uxTaskPriorityGet       1
#define INCLUDE_vTaskDelete             1
#define INCLUDE_vTaskCleanUpResources   1
#define INCLUDE_vTaskSuspend            1
#define INCLUDE_vTaskDelayUntil         1
#define INCLUDE_vTaskDelay              1

/*
 * Cortex-M内核使用8bit来配置优先级,但是STM32只使用了高4bit,数值越小,优先级越高。
 * 在往寄存器里面写数值配置的时候,是按照8bit来写的,所以真正写的时候需要经过转换,公式为:
 * ((priority << (8 - __NVIC_PRIO_BITS)) & 0xff),其中的priority就是我们配置的真正的优先级
 */
//Use the system definition, if there is one
#ifdef __NVIC_PRIO_BITS
    #define configPRIO_BITS       __NVIC_PRIO_BITS
#else
    #define configPRIO_BITS       4        /* 15 priority levels */
#endif

#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY         15

/**
 * SysTick和PendSV 都是配置为了最低优先级,即0xf 。这样可以提高系统的实时响应能力,即其他的外部中断可以及时的得到响应。
 *
 */
/* The lowest priority. */
#define configKERNEL_INTERRUPT_PRIORITY     ( configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )

/*
 * 用于配置STM32的特殊寄存器basepri寄存器的值,用于屏蔽中断,当大于basepri值的优先级的中断将被全部屏蔽。basepri只有4bit有效,
 * 默认只为0,即全部中断都没有被屏蔽。configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY配置为:5,意思就是中断优先级大于5的中断都被屏蔽。
 * 当把配置好的优先级写到寄存器的时候,是按照8bit来写的,所以真正写的时候需要经过转换,公式为:
 * ((priority << (8 - __NVIC_PRIO_BITS)) & 0xff),其中的priority就是我们配置的真正的优先级。经过这个公式之后得到的是下面的这个宏:
 * configMAX_SYSCALL_INTERRUPT_PRIORITY
 *
 * 在FreeRTOS中,关中断是通过配置basepri寄存器来实现的,关掉的中断由配置的basepri的值决定,
 * 小于basepri值的中断FreeRTOS是关不掉的,这样做的好处是可以系统设计者可以人为的控制那些非常重要的中断不能被关闭,在紧要的关头必须被响应。
 * 而在UCOS中,关中断是通过控制PRIMASK来实现的,PRIMASK是一个单1的二进制位,写1则除能除了NMI和硬 fault的所有中断。
 * 当UCOS关闭中断之后,即使是你在系统中设计的非常紧急的中断来了都不能马上响应,这加大了中断延迟的时间,如果是性命攸关的场合,那后果估计挺严重。
 * 相比UCOS的关中断的设计,FreeRTOS的设计则显得人性化很多。
 */
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY    5

/* Priority 5, or 95 as only the top four bits are implemented. */
/* !!!! configMAX_SYSCALL_INTERRUPT_PRIORITY must not be set to zero !!!!
See http://www.FreeRTOS.org/RTOS-Cortex-M3-M4.html. */
#define configMAX_SYSCALL_INTERRUPT_PRIORITY    ( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )

#define configASSERT( x ) if( ( x ) == 0 ) { taskDISABLE_INTERRUPTS(); for( ;; ); }

/**
 * 在 FreeRTOS 的移植文件 port.c 中有用到 SVC 中断的 0 号系统服务,即 SVC 0。此中断在 FreeRTOS中仅执行一次, 用于启动第一个要执行的任务。
 * 另外, 由于 FreeRTOS 没有配置 SVC 的中断优先级,默认没有配置的情况下, SVC 中断的优先级就是最高的 0。
 */
#define vPortSVCHandler SVC_Handler

/**
 * SysTick和PendSV 都是配置为了最低优先级
 */
#define xPortPendSVHandler PendSV_Handler
#define xPortSysTickHandler SysTick_Handler

#endif /* FREERTOS_CONFIG_H */

三、测试代码

/*

STM32F407ZGT6
168MHz
Flash size 1024Kbytes
RAM size 192KB

*/
#include "main.h"
#include <string.h>

#include "delay.h"
#include "led.h"
#include "key.h"
#include "timer2.h"
#include "timer3.h"

#include "usart1.h"
#include "usart2_timer.h"
#include "usart3.h"

#include "FreeRTOS.h"
#include "task.h"

//数字越大任务优先级越高
#define START_TASK_PRIO 1   //任务优先级
#define START_TASK_SIZE 128 //任务堆栈大小
TaskHandle_t START_TASK_HANDLER;//任务句柄

#define TASK1_TASK_PRIO 2   //任务优先级
#define TASK1_TASK_SIZE 100 //任务堆栈大小
TaskHandle_t TASK1_TASK_HANDLER;//任务句柄

#define TASK2_TASK_PRIO 3   //任务优先级
#define TASK2_TASK_SIZE 100 //任务堆栈大小
TaskHandle_t TASK2_TASK_HANDLER;//任务句柄

void start_task(void *pvParameters);
void task1_task(void *pvParameters);
void task2_task(void *pvParameters);


uint16_t TASK1_RUN_COUNT = 0;
uint16_t TASK2_RUN_COUNT = 0;

int main(void)
{
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
    
    timer2_init();    
    usart1_init(115200);    
    
    //创建开始任务
    xTaskCreate((TaskFunction_t )start_task,           
                (const char*    )"start_task",          
                (uint16_t       )START_TASK_SIZE,        
                (void*          )NULL,                  
                (UBaseType_t    )START_TASK_PRIO,      
                (TaskHandle_t*  )&START_TASK_HANDLER); 
                
    //开启任务调度                
    vTaskStartScheduler();          


}
void start_task(void *pvParameters)
{
    taskENTER_CRITICAL(); //进入临界区  
 
    xTaskCreate((TaskFunction_t )task1_task,         
                (const char*    )"task1_task",       
                (uint16_t       )TASK1_TASK_SIZE, 
                (void*          )NULL,                
                (UBaseType_t    )TASK1_TASK_PRIO,    
                (TaskHandle_t*  )&TASK1_TASK_HANDLER);   
 
    xTaskCreate((TaskFunction_t )task2_task,     
                (const char*    )"task2_task",   
                (uint16_t       )TASK2_TASK_SIZE, 
                (void*          )NULL,
                (UBaseType_t    )TASK2_TASK_PRIO,
                (TaskHandle_t*  )&TASK2_TASK_HANDLER);  
                
    vTaskDelete(START_TASK_HANDLER);//删除开始任务
                
    taskEXIT_CRITICAL();//退出临界区
                
}

void task1_task(void *pvParameters)
{
    while(1)
    {
        TASK1_RUN_COUNT++;
        
        if(TASK1_RUN_COUNT == 10 && TASK2_TASK_HANDLER != NULL)
        {
            vTaskDelete(TASK2_TASK_HANDLER);//删除任务2            
            printf("task1 delete task2_task\r\n");
        }       
        vTaskDelay(3000);        
        printf("task1_task,%d \r\n",TASK1_RUN_COUNT);
    }
    
}   


void task2_task(void *pvParameters)
{
    
    while(1)
    {
        TASK2_RUN_COUNT++;        
        vTaskDelay(1000);        
        printf("task2_task,%d \r\n",TASK2_RUN_COUNT);
    }
    
}

测试结果
因为任务2的优先级比任务1高,所以先执行任务2,再执行任务1.
任务1执行耽误的时间长,在等待任务1的时候,任务2继续执行.

[16:13:30.043]收←◆task1_task,1 
task2_task,3 

[16:13:31.044]收←◆task2_task,4 

[16:13:32.045]收←◆task2_task,5 

[16:13:33.042]收←◆task1_task,2 
task2_task,6 

[16:13:34.046]收←◆task2_task,7 

[16:13:35.048]收←◆task2_task,8 

[16:13:36.043]收←◆task1_task,3 
task2_task,9 

[16:13:37.049]收←◆task2_task,10 

[16:13:38.051]收←◆task2_task,11 

[16:13:39.044]收←◆task1_task,4 
task2_task,12