对Mastering_the_FreeRTOS_Real_Time_Kernel-A_Hands-On_Tutorial_Guide做阅读笔记
官方网站路径: Free RTOS Book and Reference Manual
有不对的地方欢迎指正
目录
前言
1.小型嵌入式多任务系统
FreeRTOS 是一个非常适合使用微控制器或小型微处理器的深度嵌入式实时应用程序,这一类的应用程序通常包括硬件和软件的实时需求。
软实时需求是指规定了时间期限但超过最后期限也不会使系统出现问题,例如:对按键的响应太慢可能会使系统看起来响应迟钝,但不会使系统无法工作。
硬实时需求是指规定了时间期限但超过最后期限肯定会导致系统无法工作,例如:汽车中的安全气囊对碰撞传感器输入的响应太慢,则可能弊大于利。
FreeRTOS是一个实时内核(实时调度器),在这基础上可以构建嵌入式应用程序以满足其硬件实时需求。它允许将应用程序组织为独立执行线程的集合。在只有一个内核的处理器上,在任何时间点只能执行一个线程。内核通过检查用户分配给每个线程的优先级决定哪个线程应该执行。在简单的程序中,用户应该为实现硬件实时要求的线程分配更高的优先级,而实现软件实时要求的线程的优先级较低。这将确保硬实时线程始终在软实时线程之前执行,但优先级分配决策并不总是那么简单。
2.关于术语的说明
在FreeRTOS中,每个线程的执行被称为“任务”,对术语没有与嵌入式社区达成共识,但作者更喜欢用“任务”来代替“线程”,因为线程在某些应用领域可以具有更具体的含义。
3.为什么要使用实时内核
有许多成熟的技术可以在不使用内核的情况下编写好的嵌入式软件,如果正在开发的系统是简单的,那么这些技术可能会提供更合适的解决方案。在更复杂的情况下,使用内核可能更可取,但对内核的选取是主观性的。
正如前面所描述的,任务优先级可以帮助确保应用程序达到其处理最后期限,但是内核也可以带来其他不太明显的好处。下面非常简要地列出了一些:
抽象出时序信息
内核负责执行时间和提供了一个与时间相关的API应用程序。这允许应用程序代码的结构更简单、整体代码更小。
可维护性和可扩展性
时序细节的抽象可以使模块之间的相互依赖性更小,允许软件以受控和可预测的方式发展。并且内核负责计时,因此应用程序性能不太容易受到底层硬件变化的影响。
模块性
任务是独立的模块,每一个都应该有一个明确的目的。
团队开发
任务还应具有定义明确的接口,以便团队更轻松地进行开发。
更轻松的测试
如果任务是具有明确定义的独立模块和清晰明了的接口,则可以对它们做独立测试。
代码复用
更大的模块化和更少的相互依赖性使代码可以轻松地复用。
提高效率
使用内核允许软件完全由事件驱动, 所以,轮询未发生的事件不会浪费处理时间。代码只有在必须执行某些操作时才会执行。虽然 RTOS 滴答中断处理任务的切换时会降低效率,但不使用RTOS的应用程序通常也会包含某种形式的tick中断。
空闲时间
Idle任务在调度程序启动时自动创建
电源管理
通过使用RTOS获得的效率增益允许处理器在低功耗模式下花费更多的时间。
每次Idle任务运行时可以将处理器置于低功耗状态,可以显著降低功耗。FreeRTOS还有一个特殊的tick-less mode。使用tick-less mode允许处理器进入比其他模式可能更低的功率模式,并在低功率模式中保持更长时间。
灵活的中断处理
中断处理程序可以通过推迟用户创建的任务或FreeRTOS守护进程任务来执行非常短的时间。
混合处理需求
简单的设计模式可以在应用程序中实现周期性、连续和事件驱动处理的混合。此外,通过选择适当的任务和中断优先级,可以满足硬实时和软实时需求。
4.FreeRTOS特性
优先机制或合作机制
非常灵活的任务优先级分配
灵活、快速、轻量级的任务通知机制
队列
二进制信号量
计数信号量
互斥锁
递归互斥锁
软件定时器
事件组
钩子函数
空闲钩子函数
堆栈溢出检查
跟踪记录
任务运行时统计数据收集
完全中断嵌套模型(对于某些架构)
可选的商业许可和支持
为极低功耗应用提供无嘀嗒功能
适当的时候软件管理中断堆栈(这可以帮助节省RAM)
本书中给出的所有示例的源代码、预配置的项目文件和完整的构建说明都在附带的zip文件中提供。你可以从http://www。FreeRTOS。org/Documentation/code下载zip文件。该zip文件可能不包含最新版本的FreeRTOS。
第一章
1.1 章节简介及预览
FreeRTOS作为一个单独的压缩文件发布,其中包含所有的官方FreeRTOS端口,以及大量预先配置的演示应用程序。
本章旨在帮助用户熟悉FreeRTOS文件和目录:
提供的顶层视图FreeRTOS目录结构
描述任何特定FreeRTOS项目实际需要哪些文件
介绍演示应用程序
如何创建新项目
这里的描述只涉及官方的FreeRTOS发行版,本书附带的示例使用了稍微不同的架构。
1.2 理解FreeRTOS
FreeRTOS可以用大约20种不同的编译器构建,并可以在30多种不同的处理器架构上运行,不同编译器和架构的每个组合都被认为是一个独立FreeRTOS部分。
构建FreeRTOS
FreeRTOS可以被认为是一个库,它为裸机应用程序提供了多任务处理功能。
FreeRTOS是一组C源文件构建的。有些源文件对所有端口都是通用的,而另一些则特定于某个端口。构建源文件作为项目的一部分,使FreeRTOS API可用于您的应用程序。为了方便用户,每个官方FreeRTOS都提供了一个示例工程。示例工程被预先配置为构建正确的源文件,并包含正确的头文件。
示例工程应该“即开即用”,尽管有些工程比其他的更老,而且有时对构建工具的更改会导致程序问题。1.3节描述了演示应用程序。
FreeRTOSConfig.h
FreeRTOS 由一个头文件被称为FreeRTOSConfig.h 文件配置。
FreeRTOSConfig.h用于定制FreeRTOS,以便在特定应用程序中使用。例如,FreeRTOSConfig.h包含诸如configUSE_PREEMPTION这样的常量,它的设置定义了是使用合作调度算法还是使用抢占调度算法1。由于FreeRTOSConfig。h包含应用程序的特殊定义,因此它应该位于作为应用程序的一部分的目录中,而不是位于包含FreeRTOS源代码的目录中。
每个FreeRTOS都提供一个演示应用程序,每个演示应用程序都包含一个FreeRTOSConfig.h文件。因此没有必要从头创建FreeRTOSConfig.h文件。
所以建议从FreeRTOSConfig.h开始,该FreeRTOSConfig.h是演示应用程序为正在使用的FreeRTOS提供的。
官方FreeRTOS发行版
FreeRTOS以单个zip文件的形式发布。该zip文件包含所有FreeRTOS的源代码,以及所有FreeRTOS演示应用程序的项目文件。它还包含了FreeRTOS+生态系统组件的选择和FreeRTOS+生态系统演示应用程序的选择。
不要被FreeRTOS发行版中的文件数量吓倒!在任何一个应用程序中只需要非常少量的文件。
FreeRTOS文件的顶层目录
FreeRTOS的第一级和第二级目录如图所示:
核心FreeRTOS源代码包含在两个C文件中,这两个文件对所有FreeRTOS端口都是通用的。它们被称为tasks.c和list.c,它们直接位于FreeRTOS/Source目录中,如下图所示:
除了这两个文件之外,还有其他源文件位于同一个目录中:
queue.c
queue.c同时提供队列和信号量服务,如本书后面所述。queue.c几乎总是必需的。
timers.c
timers.c提供了软件计时器功能,如本书后面所述。只有在真正要使用软件计时器时,它才需要包含在构建中。
event_groups.c
Event_groups.c提供事件组功能,如本书后面所述。只有在实际要使用事件组时,才需要将其包含在构建中。
croutine.c
croutine.c实现了FreeRTOS的协同例程功能。只有在实际要使用协同例程时,它才需要包含在构建中。协同例程的目的是在非常小的微控制器上使用,现在很少使用,因此不能维护到与其他FreeRTOS功能相同的水平。本书没有描述协同例程。
特定端口的FreeRTOS源文件
关于编译器和架构的FreeRTOS源文件包含在FreeRTOS/Source/portable目录中,
可移植目录按层次结构排列,首先按编译器排列,然后按处理器体系结构,
如图所示:
如果你想在一个编译器上运行编译FreeRTOS,除了核心的FreeRTOS源文件,您还必须构建位于FreeRTOS/Source/portable/[compiler]/[architecture]目录下的文件。
在第二章“堆内存管理”中,FreeRTOS还将堆内存分配作为可移植层的一部分。
使用低于V9.0.0的FreeRTOS版本的项目必须包含堆内存管理器。在FreeRTOS V9.0.0中,只有当在FreeRTOSConfig.h中的configSUPPORT_DYNAMIC_ALLOCATION设置为1时,才需要堆内存管理器,否则不定义configSUPPORT_DYNAMIC_ALLOCATION。
FreeRTOS提供了五个堆分配方案。这五种方案分别命名为heap_1到heap_5,分别通过源文件heap_1.c到heap_5.c实现。
堆分配方案包含在FreeRTOS/Source/portable/MemMang目录中。如果您已将FreeRTOS配置为使用动态内存分配,则有必要在项目中构建这五个源文件中的一个,如果您的应用程序提供了替代实现则不需要构建。
包含路径
FreeRTOS需要包含在编译器的三个目录路径:
FreeRTOS核心头文件的路径:FreeRTOS/Source/include
指定使用FreeRTOS的编译器和架构的源文件的路径:
FreeRTOS/Source/portable/[compiler]/[architecture]
FreeRTOSConfig.h头文件的路径
头文件
使用FreeRTOS API的源文件必须包含 FreeRTOS.h ,后面跟着包含正在使用的API函数的头文件——task.h , queue.h , semphrr .h, timers.h 或 event_groups.h
1.3 示例工程
每个FreeRTOS部分都附带至少一个示例工程,在构建时应该不会生成任何错误或警告,尽管有些演示程序比其他的更老。对构建工具的更改可能会导致出现问题。
Linux用户的注意:FreeRTOS的开发和测试是在Windows主机上进行的。在Linux主机上构建演示项目时,偶尔会出现构建错误。构建错误几乎总是与引用文件名时使用的字母大小写有关,或者与文件路径中使用的斜杠字符的方向有关。
示例工程有以下几个目的:
提供一个工作的和预先配置的项目示例,其中包含正确的文件,并设置了正确的编译器选项。
为满足“即开即用”的实验需求,只需要最少的设置或先验知识
演示如何使用FreeRTOS API
作为创建实际应用程序的基础
每个演示工程都位于FreeRTOS/ demo目录下的唯一子目录中。子目录的名称说明示例工程与之相关的部分。
FreeRTOS.org网站上还描述了每个示例工程。内容包括:
如何在FreeRTOS目录结构中定位示例工程的项目文件
项目被配置为使用哪些硬件
如何设置运行示例工程的硬件
示例工程如何构建
示例工程将有怎样的现象
所有的示例工程包含在FreeRTOS/demo/common/Minimal目录中。 目的是为了演示如何使用FreeRTOS API,它们没有实现任何特定有用的功能。
初学者可以创建一个非常基础的点灯项目,只需要新建两个任务和一个队列。
每个示例工程都包含一个main.c文件,在main函数中创建所有示例工程的任务。通过查看main.c中的注释来获取该程序的主要作用。
1.4 创建FreeRTOS工程
熟悉提供的示例工程
每一个FreeRTOS部分都有至少一个可以编译且没有错误和警告的应用程序。建议通过修改示例工程中的一个来创建新工程,这可以保证工程包含了正确的文件、中断处理程序和设置正确的编译器。
从现有示例工程中创建新的应用程序:
打开提供的示例工程并确保可以编译和正确执行。
删除源文件中定义的示例任务,工程中任何位于Demo/common 文件夹下的文件都可以删除。
删除main中的所有函数调用,除了 prvSetupHardware() 和vTaskStartScheduler()。
确保工程可以编译
以上步骤将创建一个包括正确FreeRTOS源文件的项目,但是没有定义具体功能。
从头创建一个新项目
如前所述,建议从现有的示例工程创建新工程。如果你不想这样做,那么也可以使用下面的流程来创建:
使用你选择的IDE创建一个不包含任何FreeRTOS的新工程。
确保新工程能被编译下载到你的硬件平台中执行。
添加FreeRTOS源文件
如果工程使用是比V9.0.0更早的版本必须要包含heap_n.c 文件中的一个.c文件,在FreeRTOS V9.0.0中,只有当configSUPPORT_DYNAMIC_ALLOCATION在FreeRTOSConfig.h中设置为1或configSUPPORT_DYNAMIC_ALLOCATION未定义时才需要heap_n.c文件。
从示例工程中复制FreeRTOSConfig.h
添加以下头文件路径到工程中
FreeRTOS/Source/include
FreeRTOS/Source/portable/[compiler]/[architecture] (编译器和内核架构对应你的编译平台和芯片)
包含FreeRTOSConfig.h文件的路径
从相关的示例工程中复制编译器设置
安装可能需要的任何FreeRTOS中断处理程序
1.5 数据类型和编码风格指南
数据类型
FreeRTOS的每个部分都有一个唯一的portmacro.h头文件,其中包含两种特定于端口的数据类型的定义:TickType_t and BaseType_t。
TickType_t:FreeRTOS配置的一个周期中断称为tick中断。
从FreeRTOS应用程序启动后发生的tick中断的数量称为tick计数。tick计数被用来衡量时间。两个tick中断之间的时间称之为tick周期。时间指定为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。
在8位和16位架构体系中使用uint16_t类型可以更好的提高效率,但严重限制了可指定的最大阻塞周期。因此也没有理由在32位体系结构上使用16位类型。
BaseType_t:这总是定义为最有效的数据类型的架构。
通常,在32位架构上是32位的类型,在16位架构上是16位的类型,
在8位架构上是8位的类型。
BaseType_t通常用于只能接受非常限制范围值的返回类型,以及pdTRUE/pdFALSE类型的布尔值。
有些编译器让所有非限定char变量无符号,而另一些编译器使它们有符号。基于这个原因,FreeRTOS源代码中除非char用于保存ASCII字符,或者指向char的指针用于指向字符串,否则显式地用’有符号’或’无符号’限定char的每次使用。
PS:从不使用普通int类型。
变量名
变量的前缀代表了它们的类型:”c” 表示char,”s” 表示int16_t(short),
”I” 表示int32_t(long), ”x” 表示BaseType_t 和其他非标准类型(结构体、任务句柄、队列句柄等)
如果一个变量是无符号变量,那它的前缀是 “u”,如果变量是一个指针,前缀是”p”。
例如,一个变量类型是uint8_t类型,则前缀位 ”uc” ;一个char型指针前缀是”pc”。
函数名
函数的前缀是它们返回的类型和定义它们的文件位置。例如:
vTaskPrioritySet() 返回类型为 void,在task.c中被定义
xQueueReceive() 返回类型为BaseType_t,在queue.c中被定义
pvTimerGetTimerID()返回类型为一个空指针,在timers.c中被定义
用户定义的函数前缀为 ”prv”。
格式
一个Tab 等于四个空格。
宏名称
大多数宏都是大写字母,以小写字母作为前缀,以指示宏的定义位置。
PS:信号量API几乎完全被编写为一组宏,但遵循函数的命名约定,而不是宏的命名约定。
下图定义的宏使在整个FreeRTOS源代码中使用。
FreeRTOS源代码可以使用许多不同的编译器编译,所有这些在产生警告的方式和时间上都有所不同。特别是,不同的编译器有以不同的方式使用强制转换,因此,FreeRTOS源代码包含的类型强制转换比通常需要的要多。