第一章 FreeRTOS总览
1.1 章节介绍
FreeRTOS发行包内含所有官方FreeRTOS组件和大量预先配置好的Demo代码。
本章节旨在通过以下几个方面帮助读者了解FreeRTOS文档和目录
- 介绍FreeRTOS顶层目录结构
- 说明FreeRTOS的核心文件
- 介绍Demo代码
- 介绍帮助怎样创建一个新的工程
本章节的讲述仅仅针对于FreeRTOS官方的发行包。与本书其他例子有些微差别。
1.2了解FreeRTOS发行包
定义:FreeRTOS移植
FreeRTOS可以在大约二十多种不同的编译器上编译,并且可以在超过30中架构的处理器上运行。任何支持的编译器和处理器的组合都需要分开移植。
编译FreeRTOS
FreeRTOS可以被认为是一个库,与那些裸机程序相比,它提供多任务处理的能力。
FreeRTOS提供一系列C语言代码文件。有一些源文件对于所有的移植都是必须的,而其他的则是针对部分特定的移植。在工程中添加FreeRTOS源代码并编译就能够在你的代码中调用FreeRTOS的API。为方便读者,每个官方的移植都提供了相应的例程。
例程代码即取即用(out of the box),尽管有一些例程比较老,并且编译方式的些微改变可能导致一点小问题。在1.3部分会详细讲述例程代码的使用。
FreeRTOSConfig.h
FreeRTOS由文件名“FreeRTOScconfig.h”的头文件配置。
FreeRTOSConfig.h用于在特定应用条件下裁剪(tailor)FreeRTOS。例如,FreeRTOSConfig.h包含一个叫“configUSE_PREEMPTION”的常量,用于选择是使用“合作式”(co-operative)还是“抢占式”(pre-emptive)调度算法。由于FreeRTOSConfig.h包含一些特殊定义,故应该将其含与你工程的编译代码中,而不是在FreeRTOS的源码目录中。
一个例程对应于一个FreeRTOS的移植,并且每个例程都有一个对应的FreeRTOSConfig.h文件。因此没必要为如何创建FreeRTOSConfig.h而抓耳挠腮(scratch)。相反,我们建议直接使用例程中的FreeRTOSConfig.h来做微调并用于你自己的移植中。
官方Free RTOS发行包
FreeRTOS打包成单个压缩文件。压缩文件内包含了所有的FreeRTOS移植源码和所有例程的工程文件。它还包含了一些FreeRTOS+系统组件和一些FreeRTOS+系统的例程。
不必为发行包中这么多的文件而头疼,在单个应用中,只有一小部分文件是必须的。
FreeRTOS发行包的顶层目录
发行包的第一层和第二层目录的详细描述见图一:
注意,所有的例程共享一套FreeRTOS源码,如果源码目录变化,有可能导致例程无法编译。
所有移植都要用到的FreeRTOS代码
FreeRTOS的核心代码仅仅包含在2个C文件中,二者两个文件对于所有的FreeRTOS移植都是必须的。名称分别为“tasks.c”和“list.c”。如图二所示,都保存在FreeRTOS/Source目录下。除此以外,下列源码也存放在同一目录下。
-
queue.c
queue.c提供队列和信号相关API,在后文会详细描述。在绝大部分的移植中都需要用到。 -
timers.c
timers.c提供软件定时器功能,在后文会详细描述。仅在需要该功能时候加入工程。 -
event_groups.c
event_groups.c提供事件组功能,在后文会详细描述。仅在需要该功能时候加入工程。 -
croutine.c
croutine.c提供FreeRTOS合作式调度功能。仅在需要该功能时候加入工程。合作式调度通常用于非常小型的控制器,目前已经很少使用了,所以并没有把这项功能当作FreeRTOS的特点。合作式调度在本书中没有提及。
由于许多工程都是提前把文件加入,相同文件名有可能导致命名冲突。在此情况下改变源码文件名很有可能造成不必要的麻烦,也会破坏使用到FreeRTOS的数千个例程的相容性。
在一个FreeRTOS移植工程中需要使用到的源码
需要用到的源码放在FreeRTOS/Source/portable目录下。portable目录是一个连接编译器到处理器架构的层。层次结构详见图3。
如果你在一个“architecture”架构的处理器上运行FreeRTOS,并且使用“compiler”来编译它,除了FreeRTOS的核心代码意外,你还需要使用到此目录下的文件FreeRTOS/Source/portable/[compiler]/[architecture]。
至于在第二章将会讲到的堆栈和内存管理,FreeRTOS还把堆内存分配的内容放到portable层中。那些使用比FreeRTOS V9.0.0更老版本的项目必须单独包含一个堆内存管理组件。在FreeRTOS V9.0.0版本中只要FreeRTOSConfig.h头文件中的configSUPPORT_DYNAMIC_ALLOCATION宏定义被设为1,或者不定义configSUPPORT_DYNAMIC_ALLOCATION即可成功调用。
FreeRTOS提供五种示例堆分配策略。五种分配策略分别命名为 heap_1 到 heap_5,并分别封装在 heap_1.c 到 heap_5.c 五个C文件中。示例堆分配策略文件存放在 FreeRTOS/Source/portable/MenMang 目录下。如果你配置Free RTOS使用活动的内存分配方式,那么这五个文件你有必要选择一个加入你的工程中,除非你的程序有可以替换的解决方案。
引用路径
FreeRTOS要求在编译器的引用路径中添加三个路径。
- 到Free RTOS核心头文件路径,FreeRTOS/Source/include。
- 到FreeRTOS移植所用的特定文件,在前文所提到的。FreeRTOS/Source/portable/[compiler]/[architecture].
- 到FreeRTOSConfig.h的路径
头文件
调用FreeRTOS系统API的源文件必须把“FreeRTOS.h”添加到引用中此头文件下包含所需要用到的API原型函数“task.h”、“queue.h”、“semphr.h”、“timers.h”、“event_groups.h”。
1.3例程代码
每一个FreeRTOS移植都带有最少一个例程代码,这些例程代码编译时带有任何错误和警告。有一些代码比其他的时间要久。并且编译器版本的些微改变可能会产生一些问题。
对于Linux用户的提示:FreeRTOS原本实在Windows环境下开发测试的。这可能会导致例程代码在Linux环境下编译产生错误。编译错误总是与以下几点相关:文件名称的引用,文件路径的斜杠方向。其他错误可以通过FreeRTOS的官网给我们反馈FreeRTOS反馈
例程代码有以下几个目的:
- 用于提供带有正确的文件引用和正确的编译选项设置的例子和预先设置好的工程。
- 允许开发人员最少步骤的直接拿例程代码来测试。
- 演示FreeRTOS的API式的使用方法。
- 作为实际工程创建的基础。
任何实例工程都在FreeRTOS/Demo 专门的子目录下。子目录的名称表示这个移植与哪个例程代码相关。
任意实例工程在FreeRTOS.org站点都有一个页面详细说明。网页包含如下信息:
- 如何定位该工程文件在FreeRTOS目录结构的位置。
- 该工程配置使用了哪些硬件。
- 如何配置硬件来运行例程。
- 怎么编译例程。
- 该例程有什么现象。
所有的例程都会创建一些类似的任务,而具体的实现方法可以查看FreeRTOS/Demo/Common/Minimal目录。公共例程纯粹是为了演示FreeRTOS系统API怎么使用,没有其他功能。
许多新例程都可以以闪光灯工程作为基础。闪光灯工程非常基础,它只新建两个任务和一个队列。
每个实例工程都包含一个main.c的文件,它包含主函数。任何例程的任务都在此新建。可以查看某一个例程各自的main.c文件了解更多。
FreeRTOS例程目录层次如图四所示:
1.4 新建一个FreeRTOS工程
改编一个支持的例程
每个FreeRTOS移植都有一个新预设好的能正确编译没有任何错误和警告的例程。建议利用已经写好的工程改编成新工程。那样新工程将会有一个正确的文件引用结构,正确的中断处理和正确的编译器选项设置。
从现有例程开始改编新工程:
- 打开支持的例程并确定其能正确编译并正确执行。
- 去掉例程中所定义的任务。任何放在Demo/Common目录下的文件都可以从工程中去掉。
- 除了清单1中的 prvSetupHardware() 和vTaskStartScheduler() 这两个函数, main()函数中的别的函数申明全部删除。
- 检查项目是否仍能正常编译。
以上四步会新建一个引用正确的空FreeRTOS项目,它没有任何功能。
从0开始新建一个新工程
如前文所述,建议从已经存在的工程中修改得到新工程。若没有适合的例程,则可以用以下步骤新建一个新的工程。
-
用你的IDE创建一个不含任何FreeRTOS的新工程。
-
确保你的新工程能正确编译并能下载到目标板,正确执行。
-
在你确定该工程能够正常工作后,把表1中的FreeRTOS源码加入工程。
-
复制一个移植例程中用到的FreeRTOSConfig.h头文件到工程目录中。
-
把以下文件路径添加到引用中,以便工程能定位到头文件:
FreeRTOS/Source/include
FreeRTOS/Source/portable/[compiler]/[architecture]
[compiler]和[architecture]要对应与你所用的编译器和芯片
包含FreeRTOSConfig.h头文件的路径。 -
从相关例程复制对于的编译器设置。
-
插入任何有可能用到的FreeRTOS中断处理函数。可以参考描述FreeRTOS移植的网站和移植给出的例程。
使用比FreeRTOS V9.0.0更老的版本必须设计heap_n.c文件。对于FreeRTOS V9.0.0一个heap_n.c文件只需要只要把FreeRTOSConfig.h头文件中的configSUPPORT_DYNAMIC_ALLOCATION宏定义被设为1,或者不定义configSUPPORT_DYNAMIC_ALLOCATION 即可成功调用。更多相关信息请参考第二章,堆内存管理。
1.5 数据类型和代码风格指导
数据类型
每个FreeRTOS的移植例程都有一个唯一的的portmacro.h头文件。其中包含了两种移植的数据类型定义:TickType_t和BaseType_t。这两种数据类型在表二中有详细描述:
宏指令和类型定义 | 实际类型 |
---|---|
TickTypt_t | FreeRTOS把周期性的中断称之为“滴答中断”。 从FreeRTOS启动后开始对中断次数计数,称之为“滴答计数”。滴答计数被用于做时间测量。两次滴答中断的所经过的时间称之为滴答周期(tick period)。时间则是复数个滴答周期的相加。TickType_t 则是用于保存滴答计数值来指示时间的一种数据类型。TickType_t可以被设定为无符号16bit类型或者无符号32bit数据类型,这取决于FreeRTOSConfig.h中configUSE_16_BIT_TICKS宏定义是否为1。当被定义为1时,TickType_t被定义为uint16_t。若为0,则是uint32_t类型。在8位或16位处理器上使用16位数据类型可以显著增加执行效率,但应严格限制指定的最大块区间。在32位处理器上没有理由使用16位数据类型。 |
BaseType_t | 它总是被定义为处理器最有执行效率的数据类型。简单来说,对于32位处理器就是32bit类型,16位处理器就是16bit类型,8位处理器就是8bit。BaseType_t通常作为有限大小的数值返回类型,和pdTRUE/pdfalse类型的布尔量。 |
一些编译器把所有的符号类型变量视为无符号类型。而有一些则当作有符号类型。因此,FreeRTOS源码明确定义每一个用到的char变量是"signed"还是“unsigned”。除非该变量是用来存储ASCII符号,或是保存符号或字符串的指针。普通的int类型则不会用到。
变量名
变量都会使用其类型作为前缀:char类型用‘c’,int16_t类型‘s’,int32_t类型用‘s’,BaseType_t和其他非标准类型(结构体,任务句柄,队列句柄等)使用‘x’。
如果变量是无符号类型,还会额外使用‘u’作为前缀。如果变量是一个指针,则会额外使用‘p’。例如,一个uint8_t类型变量,则会使用‘uc’作为前缀、一个符号类型的指针,则会使用‘pc’前缀。
函数名
函数则会使用他们的返回值类型和其所在的文件名一起作为前缀。例如:
- vTaskPrioritySet()会返回一个空类型(void),并且在task.c中定义
- xQueueReceive()会返回一个BaseType_t类型,并且在queue.c中定义
- pvTimerGetTimerID()返回一个空指针(pointer to void)并且在timers.c中定义
文件范围内的函数(私有函数)都使用‘prv’作为前缀。
格式
一个tab键总是被设置成4个空格键的长度。
宏定义名
大部分宏定义都是大写形式,并且用小写形式的前缀来表明这个宏是在哪被定义的。表三提供了一些例子:
注意到信号API几乎都是以宏的形式定义的,但是遵循函数命名习惯而不是宏的命名习惯。
定义在表四中的宏都是通过FreeRTOS源码调用。
强制转换类型过多的根本原因
FreeRTOS源码可以被许多编译环境编译,其区别在于怎样和何使产生警告。特别地,不同编译器使用不同的方式进行强制转换。因此FreeRTOS源码更多使用强制类型转换。