预备知识
符号列表
- ADC:Analog to Digital Converter 模数转换器
- API:Application Programming Interface 应用编程接口
- DMA:Direct Memory Access 直接内存访问
- FAQ:Frequently Asked Question 常见问题
- FIFO:First In First Out 先进先出
- IDE:Integrated Development Environment 集成开发环境
- IRQ:Interrupt Request 中断请求
- ISR:Interrupt Service Routine 中断服务例程
- LCD:Liquid Crystal Display 液晶显示器
- MCU:Microcontroller 微控制器
- RMS:Rate Monotonic Scheduling 速率单调调度
- RTOS:Real-time Operating System 实时操作系统
- SIL:safety Integrity Level 安全完整性级别
- SPI:Serial Peripheral Interface 串行外围接口
- TCB:task controll block 任务控制块
- UART:Universal Asynchronous Revercer/Transmmitter 通用异步收发器
前言
FreeRTOS非常适用于微控制器或小型微处理器的深入嵌入式实时应用程序。这种类型的应用程序通常混合了硬实时需求和软实时需求。
- 软实时需求是那些规定了时间截止日期的需求——但是违反截止日期并不会使系统无用。例如,对击键的响应太慢可能会使系统看起来没有响应,但实际上并没有使它不可用。
- 硬性实时需求是那些规定了时间截止日期的需求,而违反截止日期将导致系统的绝对失败。例如,如果驾驶员的安全气囊对碰撞传感器输入的响应太慢,它可能造成的伤害大于好处。
FreeRTOS是一个实时内核(或实时调度程序),可以在其之上构建嵌入式应用程序,来满足它们的硬实时需求。它允许将应用程序组织成一组独立的执行线程。
在只有一个核心的处理器上,任何时候只能执行一个线程。内核通过检查应用程序设计器分配给每个线程的优先级来决定应该执行哪个线程。
最简单的情况下,应用程序设计人员可以将较高的优先级分配给实现硬实时需求的线程,将较低的优先级分配给实现软实时需求的线程。这将确保硬实时线程总是先于软实时线程执行,但优先级分配决策并不总是那么简单。
理解FreeRTOS发行版
- 定义:FreeRTOS端口
FreeRTOS可以用大约20种不同的编译器构建,并可以在30多种不同的处理器架构上运行。编译器和处理器的每个支持组合都被认为是一个独立的FreeRTOS端口。 - 编译FreeRTOS
FreeRTOS可以被认为是一个库,它为一个应用程序提供了多任务处理功能。
FreeRTOS是作为一组C源文件提供的。有些源文件对所有端口都是通用的,而另一些则特定于某个端口。编译源文件作为项目的一部分,使FreeRTOS API可以用于应用程序。
每个官方FreeRTOS端口都提供了一个演示应用程序。演示应用程序被预先配置为构建正确的源文件,并包含正确的头文件。 - FreeRTOSConfig.h
FreeRTOS通过一个名为FreeRTOSConfig.h的头文件进行配置。
FreeRTOSConfig.h用于制定FreeRTOS,以便在特定应用程序中使用。例如,FreeRTOSConfig.h包含诸如configUSE_PREEMPTION这样的常量,其定义是决定使用合作调度算法还是抢占调度算法。
由于FreeRTOSConfig.h包含特定于应用程序的定义,因此它应该位于作为正在编译的应用程序的一部分的目录中。
每个FreeRTOS端口都提供一个演示应用程序,每个演示应用程序都包含一个FreeRTOSConfig.h文件。因此,没有必要从头创建FreeRTOSConfig.h文件。 - FreeRTOS发布
FreeRTOS以单个zip文件的形式发布。该zip文件包含所有FreeRTOS端口的源代码,以及所有FreeRTOS演示应用程序的项目文件。它还包含了FreeRTOS+生态系统组件的选择和FreeRTOS+生态系统演示应用程序的选择。
FreeRTOS发行版的第一级和第二级目录如图所示:
该zip文件只包含FreeRTOS源文件的一个副本;所有的FreeRTOS演示项目,以及所有的FreeRTOS+演示项目,都期望在FreeRTOS/ source目录中找到FreeRTOS源文件,如果目录结构发生变化,可能无法构建。
所有端口通用的FreeRTOS源文件
核心FreeRTOS源代码包含在两个C文件中,这两个文件对所有FreeRTOS端口都是通用的。它们被称为tasks.c和list.c,直接位于FreeRTOS/Source目录下,如图2所示。
除了tasks.c和list.c,以下源文件也位于同一个目录下。
- queue.c
queue.c提供了队列和信号量服务。 - timers.c
提供了软件计时器功能,真正要使用软件计时器时,才需要包含在编译中。 - event_groups.c
提供事件组功能。 - croutine.c
实现了FreeRTOS的协同例程功能。
特定端口的FreeRTOS源文件
**特定于FreeRTOS端口的源文件包含在FreeRTOS/Souce/portable目录中。**portable目录按层次结构排列,首先按编译器排列,其次是处理器体系结构排列,如图3所示。
如果你在一个使用编译器’compiler’的处理器上运行FreeRTOS,那么除了核心的FreeRTOS源文件外,还必须构建位于FreeRTOS/source/portable/[compiler]/[architecture]目录下的文件。
FreeRTOS还将堆内存分配作为portable的一部分。使用低于V9.0.0的FreeRTOS版本的项目必须包含堆内存管理器。在FreeRTOS V9.0.0版本中,只有当configSUPPORT_DYNAMIC_ALLOCATION在FreeRTOSConfig.h中设置为1,或者configSUPPORT_DYNAMIC_ALLOCATION未定义时,才需要堆内存管理器。
FreeRTOS提供了五个堆分配方案实例。这五个方案分别命名为heap_1到heap_5,分别通过源文件heap_1.c到heap_5.c实现。
堆分配方案包含在FreeRTOS/Source/portable/MemMa目录中。如果已经将FreeRTOS配置为使用动态内存分配,则有必要在项目中构建这五个源文件中的一个。
FreeRTOS需要在编译器的include路径中包含三个目录,这些都是:
- 核心FreeRTOS头文件的路径,始终为FreeRTOS/Souce/include。
- 指定正在使用的FreeRTOS端口的源文件的路径。FreeRTOS/Source/portable/[compiler]。
- FreeRTOSConfig.h头文件的路径。
使用FreeRTOS API的源文件必须包含FreeRTOS.h,后面跟着包含正在使用的API函数原型的头文件——task.h,queue.h,semphrr.h、timers.h或event_groups.h。
演示应用程序
每个FreeRTOS端口都至少有一个演示应用程序,构建时应该不会生成错误或警告,尽管有些演示程序比其他演示程序更老,而且有时演示程序发布后对构建工具的更改可能导致问题。
Linux用户注意:FreeRTOS是在Windows主机上开发和测试的。在Linux主机上编译演示项目时。偶尔会出现编译错误。编译错误几乎总是与引用文件名时使用的字母大小写有关,或者与文件路径中使用的斜杠字符的方向有关。
演示应用程序有几个目的:
- 提供一个工作的和预先配置好的项目实例,其中包含了正确的文件,并设置了正确的编译器选项。
- 允许以最小的设置或先验知识“开箱即用”。
- 作为如何使用FreeRTOS API的演示。
- 作为创建实际应用程序的基础。
每个演示项目都位于FreeRTOS/demo目录下的唯一子目录中。子目录的名称指示演示项目与之相关的端口。
FreeRTOS.org网站上的网页还描述了每个演示应用程序。本网页包括以下资料:
- 如何在FreeRTOS目录结构中定位演示的项目文件。
- 项目配置使用的硬件。
- 如何运行演示的硬件。
- 如何编译演示。
- 演示程序的预期表示。
所有的演示项目都创建了一个通用演示任务的子集,其包含在FreeRTOS/demo/common/Minimal目录中。通用演示任务的存在是为了演示如何使用FreeRTOS API——它们没有实现任何特定的有用功能。
FreeRTOS/Demo目录层次结构如图4所示。
创建FreeRTOS项目
调整所提供的演示项目之一
每个FreeRTOS端口都附带至少一个预先配置的演示应用程序,构建时没有错误或警告。建立通过调整其中一个现有项目来创建新项目。这将使得项目包含正确的文件,安装正确的中断处理程序,并设正确的编译器选项。
从现有的演示项目启动一个新的应用程序:
- 打开所提供的演示项目,并确保它按预期编译和执行。
- 删除定义演示任务的源文件。可以从项目中删除位于Demo/Common目录中的任何文件。
- 删除main()中的所有函数调用,除了prvSetupHardware()和vTaskStartScheduler(),如list1所示。
- 检查项目仍然可以编译。
按照这些步骤将创建一个包含正确的FreeRTOS源文件,但不定义任何功能的项目。
从零开始创建一个新项目
如前所述,建议从现有的演示项目创建新项目。如果不希望这样,可以使用以下步骤创建一个新项目:
- 使用你选择的工具链,创建一个尚未包含任何FreeRTOS源文件的新项目。
- 确保可以编译新项目,并将其下载到目标硬件并执行。
- 只有确定已经有了一个工作的项目时,才将表1中详细描述的FreeRTOS源文件添加到项目中。
- 将演示项目使用的端口提供的FreeRTOSConfig.h头文件复制到项目目录中。
- 将以下目录添加到项目将搜索的定位头文件的路径。
数据类型和编码风格指南
数据类型
FreeRTOS的每个端口都有一个唯一的portmacro.h头文件,其中包含了两种特定于端口的数据类型的定义:TickType_t和basettype_t。Table 2描述了这些数据类型。
- FreeRTOS配置了一个称为tick中断的周期性中断。
- 自FreeRTOS应用程序启动以来,发生的tick中断的数量称为tick计数。tick计数是用来衡量时间的。
- 两个tick中断之间的时间称为tick周期。时间指定为tick周期的倍数。
- TickType_t是用于保存tick计数值和指定时间的数据类型。
- TickType_t可以是无符号16位类型,也可以是无符号32位类型,取决于FreeRTOSConfig.h中ConfigUSE_16_BIT_TICKS的设置。ConfigUSE_16_BIT_TICKS设为1,则TickType_t定义为uint16_t。如果ConfigUSE_16_BIT_TICKS设为0,那么TickType_t定义为uint32_t。
- 使用16位类型可以极大地提高8位和16位体系结构上的效率,但严重限制了可以指定的最大块周期。没有理由在32位体系结构上使用16位类型。
- 这总是被定义为体系结构中最有效的数据类型。通常,这是32位体系结构上的32位类型,16位体系结构上的16位类型,以及8位体系结构上的8位类型。
- BaseType_t通常用于只能接受非常有限范围值的返回类型,以及pdTrue/pdFalse类型的布尔值。
有些编译器使所有非限定的char变量无符号,而另一些编译器使它们有符号。因此,FreeRTOS源代码显示地用’有符号’或’无符号’限定了char的每次使用,除非char用于保存ASCII字符,或用于指向字符串的char指针。
从来不使用普通int类型。
变量名称
变量以它们的类型作为前缀,'c’用于char,'s’用于int16_t,'l’用于int32_t,'x’用于basetype_t和其它非标准类型(结构、任务句柄、队列句柄等)。
如果一个变量是无符号的,它也会以’u’作为前缀。如果一个变量是一个指针,以’p’作为前缀。一个类型为uint8_t的变量以’uc’为前缀,一个类型为char的指针变量以’pc’作为前缀。
函数名
函数的前缀时它们返回的类型和定义它们的文件。例如:
- vTaskPrioritySet()返回一个空值,在task.c中定义。
- xQueueReceive()返回一个basettype_t类型的变量,在queue.c中定义。
- pvTimerGetTimerID()返回一个指向void的指针,在timers.c中定义。
- 文件作用域(私有)函数的前缀是prv。
宏名称
大多数宏都用大写字母书写,并以小写字母作为前缀,来指示宏的定义位置。表3提供了一个前缀列表。
信号量API几乎是作为一组宏编写的,但遵循函数命名约定,而不是宏命名约定。