FreeRTOS学习笔记——FreeRTOS 系统配置(STM32F103)

在实际使用FreeRTOS 的时候我们时常需要根据自己需求来配置FreeRTOS,而且不同架构的MCU 在使用的时候配置也不同。FreeRTOS 的系统配置文件为FreeRTOSConfig.h,在此配置文件中可以完成FreeRTOS 的裁剪和配置,这是非常重要的一个文件,本章就来讲解这个这个文件,本章分为如下几部分:
3.1 FreeRTOSConfig.h 文件
3.2 “INCLUDE_”开始的宏

3.3 “config”开始的宏

3.1 FreeRTOSConfig.h 文件
FreeRTOS 的配置基本是通过在FreeRTOSConfig.h 中使用“#define”这样的语句来定义宏定义实现的。在FreeRTOS 的官方demo 中,每个工程都有一个FreeRTOSConfig.h 文件,我们在使用的时候可以参考这个文件,甚至直接复制粘贴使用。

3.2 “INCLUDE_”开始的宏
使用“INCLUDE_”开头的宏用来表示使能或除能FreeRTOS 中相应的API 函数,作用就是用来配置FreeRTOS 中的可选API 函数的。比如当宏INCLUDE_vTaskPrioritySet 设置为0 的时候表示不能使用函数vTaskPrioritySet() , 当设置为1 的时候就表示可以使用函数vTaskPrioritySet()。这个功能其实就是条件编译,在文件tasks.c 中有如下图3.1.1 所示的代码。


图3.1.1 条件编译

从图3.1.1 可以看出当满足条件: NCLUDE_vTaskPrioritySet == 1 的时候, 函数vTaskPrioritySet()才会被编译,注意,这里为了缩小篇幅将函数vTaskPrioritySet()的内容进行了折叠。FreeRTOS 中的裁剪和配置就是这种用条件编译的方法来实现的,不止FreeRTOS 这么干,很多的协议栈、RTOS 系统和GUI 库等都是使用条件编译的方法来完成配置和裁剪的。条件编译的好处就是节省空间,不需要的功能就不用编译,这样就可以根据实际需求来减少系统占用的ROM 和RAM 大小,根据自己所使用的MCU 来调整系统消耗,降低成本。下面来看看“INCLUDE_”开始的都有哪些宏,它们的功能都是什么。

1、INCLUDE_xSemaphoreGetMutexHolder
如果要使用函数xQueueGetMutexHolder()的话宏INCLUDE_xSemaphoreGetMutexHolder 必须定义为1。

2、INCLUDE_xTaskAbortDelay

如果要使用函数xTaskAbortDelay()的话将宏INCLUDE_xTaskAbortDelay 定义为1。

3、INCLUDE_vTaskDelay

如果要使用函数vTaskDelay()的话需要将宏INCLUDE_vTaskDelay 定义为1。

4、INCLUDE_vTaskDelayUntil

如果要使用函数vTaskDelayUntil()的话需要将宏INCLUDE_vTaskDelayUntil 定义为1。

5、INCLUDE_vTaskDelete

如果要使用函数vTaskDelete()的话需要将宏INCLUDE_vTaskDelete 定义为1。

6、INCLUDE_xTaskGetCurrentTaskHandle
如果要使用函数xTaskGetCurentTaskHandle() 的话需要将宏INCLUDE_xTaskGetCurrentTaskHandle 定义为1。

7、INCLUDE_xTaskGetHandle

如果要使用函数xTaskGetHandle()的话需要将宏INCLUDE_xTaskGetHandle 定义为1。

8、INCLUDE_xTaskGetIdleTaskHandle
如果要使用函数xTaskGetIdleTaskHandle() 的话需要将宏INCLUDE_xTaskGetIdleTaskHandle 定义为1。
9、INCLUDE_xTaskGetSchedulerState
如果要使用函数xTaskGetSchedulerState()的话需要将宏INCLUDE_xTaskGetSchedulerState定义为1。
10、INCLUDE_uxTaskGetStackHighWaterMark
如果要使用函数uxTaskGetStackHighWaterMark() 的话需要将宏INCLUDE_uxTaskGetStackHighWaterMark 定义为1。
11、INCLUDE_uxTaskPriorityGet

如果要使用函数uxTaskPriorityGet()的话需要将宏INCLUDE_uxTaskPriorityGet 定义为1。

12、INCLUDE_vTaskPrioritySet

如果要使用函数vTaskPrioritySet()的话需要将宏INCLUDE_vTaskPrioritySet 定义为1。

13、INCLUDE_xTaskResumeFromISR
如果要使用函数xTaskResumeFromISR()的话需要将宏INCLUDE_xTaskResumeFromISR 和

INCLUDE_vTaskSuspend 都定义为1。

14、INCLUDE_eTaskGetState
如果要使用函数eTaskGetState()的话需要将宏INCLUDE_eTaskGetState 定义为1。

15、INCLUDE_vTaskSuspend

如果要使用函数vTaskSuspend() 、vTaskResume() 、prvTaskIsTaskSuspended() 、xTaskResumeFromISR()的话宏INCLUDE_vTaskSuspend 要定义为1。如果要使用函数xTaskResumeFromISR() 的话宏INCLUDE_xTaskResumeFromISR 和INCLUDE_vTaskSuspend 都必须定义为1。

16、INCLUDE_xTimerPendFunctionCall

如果要使用函数xTimerPendFunctionCall() 和xTimerPendFunctionCallFromISR() 的话宏INCLUDE_xTimerPendFunctionCall 和configUSE_TIMERS 都必须定义为1。

3.3 “config”开始的宏

“config”开始的宏和“INCLUDE_”开始的宏一样,都是用来完成FreeRTOS 的配置和裁剪的,接下来我们就看一下这些“config”开始的宏

1、configAPPLICATION_ALLOCATED_HEAP
默认情况下FreeRTOS 的堆内存是由编译器来分配的, 将宏configAPPLICATION_ALLOCATED_HEAP 定义为1 的话堆内存可以由用户自行设置,堆内存在heap_1.c、heap_2.c、heap_3.c、heap_4.c 和heap_5.c 中有定义,具体在哪个文件取决于用户
的选择哪种内存管理方式。比如我们的例程选择了heap_4.c,那么在heap_4.c 中就有如图3.2.1所示定义:


图3.2.1 堆内存

从图3.2.1 可以看出当宏configAPPLICATION_ALLOCATED_HEAP 定义为1 的话需要用户自行堆内存ucHeap,否则的话就是编译器来分配的。

2、configASSERT
断言,类似C 标准库中的assert()函数,调试代码的时候可以检查传入的参数是否合理,FreeRTOS 内核中的关键点都会调用configASSERT(x),当x 为0 的时候说明有错误发生,使用断言的话会导致开销加大,一般在调试阶段使用。configASSERT()需要在FreeRTOSConfig.h 文件中定义,如下实例:

#define configASSERT((x)) if((x)==0) vAssertCalled(__FILE_,__LINE__);
注意,vAssertCalled()函数需要用户自行去定义,可以是显示到LCD 上的函数,也可以是通过串口打印出来的函数,本教程的所有例程采用如下的定义:
//断言
#define vAssertCalled(char,int) printf("Error:%s,%d\r\n",char,int)
#define configASSERT(x) if((x)==0) vAssertCalled(__FILE__,__LINE__)

当参数x 错误的时候就通过串口打印出发生错误的文件名和错误所在的行号,调试代码的可以使用断言,当调试完成以后尽量去掉断言,防止增加开销!

3、configCHECK_FOR_STACK_OVERFLOW
设置堆栈溢出检测,每个任务都有一个任务堆栈,如果使用函数xTaskCreate()创建一个任务的话那么这个任务的堆栈是自动从FreeRTOS 的堆(ucHeap)中分配的,堆栈的大小是由函数xTaskCreate()的参数usStackDepth 来决定的。如果使用函数xTaskCreateStatic()创建任务的话任务堆栈是由用户设置的,参数pxStackBuffer 为任务堆栈,一般是一个数组。堆栈溢出是导致应用程序不稳定的主要因素,FreeRTOS 提供了两种可选的机制来帮助检测和调试堆栈溢出,不管使用哪种机制都要设置宏configCHECK_FOR_STACK_OVERFLOW。如果使能了堆栈检测功能的话,即宏configCHECK_FOR_STACK_OVERFLOW 不为0,那么用户必须提供一个钩子函数(回调函数),当内核检测到堆栈溢出以后就会调用这个钩子函数,此钩子函数原型如下:

void vApplicationStackOverflowHook( TaskHandle_t xTask,
char * pcTaskName );
参数xTask 是任务句柄,pcTaskName 是任务名字,要注意的是堆栈溢出太严重的话可能会损毁这两个参数,如果发生这种情况的话可以直接查看变量pxCurrentTCB 来确定哪个任务发生了堆栈溢出。有些处理器可能在堆栈溢出的时候生成一个fault 中断来提示这种错误,另外,堆栈溢出检测会增加上下文切换的开销,建议在调试的时候使用。

configCHECK_FOR_STACK_OVERFLOW==1,使用堆栈溢出检测方法1。上下文切换的时候需要保存现场,现场是保存在堆栈中的,这个时候任务堆栈使用率很可能达到最大值,方法一就是不断的检测任务堆栈指针是否指向有效空间,如果指向了无效空间的话就会调用钩子函数。方法一的优点就是快!但是缺点就是不能检测所有的堆栈溢出。
configCHECK_FOR_STACK_OVERFLOW==2,使用堆栈溢出检测方法2。使用方法二的话在创建任务的时候会向任务堆栈填充一个已知的标记值,方法二会一直检测堆栈后面的几个bytes(标记值)是否被改写,如果被改写的话就会调用堆栈溢出钩子函数,方法二也会使用方法一中的机制!方法二比方法一要慢一些,但是对用户而言还是很快的!方法二能检测到几乎所有的堆栈溢出,但是也有一些情况检测不到,比如溢出值和标记值相同的时候。

3、configCPU_CLOCK_HZ

设置CPU 的频率。

4、configSUPPORT_DYNAMIC_ALLOCATION

定义为1 的话在创建FreeRTOS 的内核对象的时候所需要的RAM 就会从FreeRTOS 的堆中动态的获取内存,如果定义为0 的所需的RAM 就需要用户自行提供,默认情况下宏configSUPPORT_DYNAMIC_ALLOCATION 为1。

5、configENABLE_BACKWARD_COMPATIBILITY
FreeRTOS.h 中由一些列的#define 宏定义,这些宏定义都是一些数据类型名字,如下图3.2.2所示:


图3.2.2 宏定义

在V8.0.0 之前的FreeRTOS 中会使用到这些数据类型,这些宏保证了你的代码从V8.0.0 之前的版本升级到最新版本的时候不需要做出修改, 默认情况下宏configENABLE_BACKWARD_COMPATIBILITY 为1。

6、configGENERATE_RUN_TIME_STATS
设置为1 开启时间统计功能,相应的API 函数会被编译,为0 时关闭时间统计功能。如果宏configGENERATE_RUN_TIME_STATS 为1 的话还需要定义表3.2.1 中的宏。


表3.2.1 宏定义

7、configIDLE_SHOULD_YIELD
此宏定义了与空闲任务(idle Task)处于同等优先级的其他用户任务的行为,当为0 的时候空闲任务不会为其他处于同优先级的任务让出CPU 使用权。当为1 的时候空闲任务就会为处于同等优先级的用户任务让出CPU 使用权,除非没有就绪的用户任务,这样花费在空闲任务上的时间就会很少,但是这种方法也带了副作用,见图3.2.3。


图3.2.3 中有三个用户任务:A、B、C,还有一个空闲任务I,用户任务和空闲任务处于同一优先级,任务切换发生在T0~T7 时刻。T0~T1 之间的时间为一个时间片,从图中可以看出一开始任务B、C 都执行了一个完成的时间片,在T2 时刻空闲任务I 开始执行,I 任务运行了一段时间以后被A 任务抢走了CPU 使用权,A 任务运行到T3 时刻发生任务切换,B 任务开始运行。可以看出其实任务I 和任务A 一起使用了一个时间片,所以任务A 运行的时间就比其他任务少!

一般建议大家关闭这个功能,毕竟空闲任务用不了多少时间,而且现在的MCU 性能都很强!

8、configKERNEL_INTERRUPT_PRIORITY、configMAX_SYSCALL_INTERRUPT_PRIORITY、configMAX_API_CALL_INTERRUPT_PRIORITY这三个宏和FreeRTOS 的中断配置有关,后面会有专门的章节来讲解!

9、configMAX_CO_ROUTINE_PRIORITIES

设置可以分配给协程的最大优先级,也就是协程的优先级数。设置号以后协程的优先级可以从0 到configMAX_CO_ROUTINE_PRIORITIES-1 , 其中0 是最低的优先级,configMAX_CO_ROUTINE_PRIORITIES-1 为最高的优先级。

10、configMAX_PRIORITIES

设置任务的优先级数量,设置好以后任务就可以使用从0 到configMAX_PRIORITIES-1 的优先级,其中0 是最低优先级,configMAX_PRIORITIES-1 是最高优先级。注意和UCOS 的区别,UCOS 中0 是最高优先级!

11、configMAX_TASK_NAME_LEN

设置任务名最大长度。

12、configMINIMAL_STACK_SIZE
设置空闲任务的最小任务堆栈大小,以字为单位,不是字节。比如在STM32 上设置为100的话,那么真正的堆栈大小就是100*4=400 字节。

13、configNUM_THREAD_LOCAL_STORAGE_POINTERS

设置每个任务的本地存储指针数组大小,任务控制块中有本地存储数组指针,用户应用程序可以在这些本地存储中存入一些数据。

14、configQUEUE_REGISTRY_SIZE

设置可以注册的队列和信号量的最大数量,在使用内核调试器查看信号量和队列的时候需要设置此宏,而且要先将消息队列和信号量进行注册,只有注册了的队列和信号量才会再内核调试器中看到,如果不使用内核调试器的话此宏设置为0 即可。

15、configSUPPORT_STATIC_ALLOCATION

当此宏定义为1,在创建一些内核对象的时候需要用户指定RAM,当为0 的时候就会自使用heap.c 中的动态内存管理函数来自动的申请RAM。

16、configTICK_RATE_HZ

设置FreeRTOS 的系统时钟节拍频率,单位为HZ,此频率就是滴答定时器的中断频率,需要使用此宏来配置滴答定时器的中断,前面在讲delay.c 文件的时候已经说过了。本教程中我们将此宏设置为1000,周期就是1ms。

17、configTIMER_QUEUE_LENGTH

此宏是配置FreeRTOS 软件定时器的,FreeRTOS 的软件定时器API 函数会通过命令队列向软件定时器任务发送消息,此宏用来设置这个软件定时器的命令队列长度。

18、configTIMER_TASK_PRIORITY

设置软件定时器任务的任务优先级。

19、configTIMER_TASK_STACK_DEPTH

设置定时器服务任务的任务堆栈大小。

20、configTOTAL_HEAP_SIZE

设置堆大小,如果使用了动态内存管理的话,FreeRTOS 在创建任务、信号量、队列等的时候就会使用heap_x.c(x 为1~5) 中的内存申请函数来申请内存。这些内存就是从堆ucHeap[configTOTAL_HEAP_SIZE]中申请的,堆的大小由configTOTAL_HEAP_SIZE 来定义。

21、configUSE_16_BIT_TICKS

设置系统节拍计数器变量数据类型, 系统节拍计数器变量类型为TickType_t,当configUSE_16_BIT_TICKS 为1 的时候TickType_t 就是16 位的,当configUSE_16_BIT_TICKS为0 的话TickType_t 就是32 位的。

22、configUSE_APPLICATION_TASK_TAG

此宏设置为1 的话函数configUSE_APPLICATION_TASK_TAGF() 和xTaskCallApplicationTaskHook()就会被编译。

23、configUSE_CO_ROUTINES

此宏为1 的时候启用协程,协程可以节省开销,但是功能有限,现在的MCU 性能已经非常强大了,建议关闭协程。

24、configUSE_COUNTING_SEMAPHORES

设置为1 的时候启用计数型信号量,相关的API 函数会被编译。

25、configUSE_DAEMON_TASK_STARTUP_HOOK
当宏configUSE_TIMERS 和configUSE_DAEMON_TASK_STARTUP_HOOK 都为1 的时需要定义函数vApplicationDaemonTaskStartupHook(),函数原型如下:
void vApplicationDaemonTaskStartupHook( void )
26、configUSE_IDLE_HOOK

为1 时使用空闲任务钩子函数,用户需要实现空闲任务钩子函数,函数的原型如下:

void vApplicationIdleHook( void )
27、configUSE_MALLOC_FAILED_HOOK

为1 时使用内存分配失败钩子函数,用户需要实现内存分配失败钩子函数,函数原型如下;

void vApplicationMallocFailedHook( void )
28、configUSE_MUTEXES

为1 时使用互斥信号量,相关的API 函数会被编译。

29、configUSE_PORT_OPTIMISED_TASK_SELECTION
FreeRTOS 有两种方法来选择下一个要运行的任务,一个是通用的方法,另外一个是特殊的方法,也就是硬件方法,使用MCU 自带的硬件指令来实现。

通用方法:

● 当宏configUSE_PORT_OPTIMISED_TASK_SELECTION 为0,或者硬件不支持的时候。
● 希望所有硬件通用的时候。
● 全部用C 语言来实现,但是效率比特殊方法低。
● 不限制最大优先级数目的时候。
特殊方法:
● 不是所有的硬件都支持。
● 当宏configUSE_PORT_OPTIMISED_TASK_SELECTION 为1 的时候。
● 硬件拥有特殊的指令,比如计算前导零(CLZ)指令。
● 比通用方法效率高。
● 会限制优先级数目,一般是32 个。

STM32 有计算前导零的指令, 所以我们可以使用特殊方法, 即将宏configUSE_PORT_OPTIMISED_TASK_SELECTION 定义为1。计算前导零的指令在UCOSIII 也用到了,也是用来查找下一个要运行的任务的。

30、configUSE_PREEMPTION
为1 时使用抢占式调度器,为0 时使用协程。如果使用抢占式调度器的话内核会在每个时钟节拍中断中进行任务切换,当使用协程的话会在如下地方进行任务切换:
● 一个任务调用了函数taskYIELD()。
● 一个任务调用了可以使任务进入阻塞态的API 函数。

● 应用程序明确定义了在中断中执行上下文切换。

31、configUSE_QUEUE_SETS

为1 时启用队列集功能。

32、configUSE_RECURSIVE_MUTEXES

为1 时使用递归互斥信号量,相关的API 函数会被编译。

33、configUSE_STATS_FORMATTING_FUNCTIONS

宏configUSE_TRACE_FACILITY 和configUSE_STATS_FORMATTING_FUNCTIONS 都为1 的时候函数vTaskList()和vTaskGetRunTimeStats()会被编译。

34、configUSE_TASK_NOTIFICATIONS

为1 的时候使用任务通知功能,相关的API 函数会被编译,开启了此功能的话每个任务会多消耗8 个字节。

35、configUSE_TICK_HOOK
为1 时使能时间片钩子函数,用户需要实现时间片钩子函数,函数的原型如下:
void vApplicationTickHook( void )
36、configUSE_TICKLESS_IDLE

为1 时使能低功耗tickless 模式。

37、configUSE_TIMERS

为1 时使用软件定时器,相关的API 函数会被编译,当宏configUSE_TIMERS 为1 的话,那么宏configTIMER_TASK_PRIORITY 、configTIMER_QUEUE_LENGTH 和configTIMER_TASK_STACK_DEPTH 必须定义。

38、configUSE_TIME_SLICING

默认情况下,FreeRTOS 使用抢占式调度器,这意味着调度器永远都在执行已经就绪了的最高优先级任务,优先级相同的任务在时钟节拍中断中进行切换,当宏configUSE_TIME_SLICING为0 的时候不会在时钟节拍中断中执行相同优先级任务的任务切换, 默认情况下宏configUSE_TIME_SLICING 为1。

39、configUSE_TRACE_FACILITY
为1 启用可视化跟踪调试,会增加一些结构体成员和API 函数。FreeRTOS 的配置文件基本就这些,还有一些其他的配置宏由于使用的比较少这里并没有列出来,这些配置具体使用到的时候在具体查看就行了。


猜你喜欢

转载自blog.csdn.net/tichimi3375/article/details/80659255