深入FreeRTOS内核——第一章、FreeRTOS基础:核心概念与入门指南
文章目录
前言
今天我们一起学习一下FreeRTOS-Kernel-Book,原书连接放在文章最后
这一章的主要内容具体有:
- FreeRTOS目录结构
- 特定FreeRTOS项目所需源文件
- 演示应用程序
- 如何创建一个新的FreeRTOS项目
一、了解FreeRTOS
1.1 FreeRTOS Port
FreeRTOS支持20多种不同的编译器,并可以在40多种处理器架构上运行。对于每种支持的编译器和处理器的组合,成为一个FreeRTOS Port
1.2 构建FreeRTOS
- FreeRTOS是什么:
FreeRTOS是一个库,为单线程裸机应用程序提供多任务处理能力。裸机应用程序指不依赖于操作系统,硬件直接操作的程序 - FreeRTOS的组成:
FreeRTOS以一组C语言源文件的形式提供。这些源文件种,有些是所有端口通用的,有些是特定于某个端口的。将这些源文件构建到项目中,应用则可以使用FreeRTOS提供的API - FreeRTOS演示:
每个官方的FreeRTOS端口,都提供了一个演示应用程序作为参考。这些演示配置了源文件和头文件的设置。
1.3 FreeRTOSConfig.h
- FreeRTOSConfig.h是一个头文件,定义了一些常量,用于配置FreeRTOS内核的行为。该头文件允许用户根据特定应用的需求定制FreeRTOS内核
- 在使用时,应该包含FreeRTOS.h,然后在合适时机包含FreeRTOSConfig.h
- FreeRTOSConfig.h文件包含了一些常量,例如configUSE_PREEMPTION,该常量定义了FreeRTOS是使用合作式调度还是抢占式调度
- 由于FreeRTOSConfig.h是为特定应用定制的,因此它应该位于应用程序的目录种,而不是FreeRTOS源码的目录种
- 主要的FreeRTOS发行版为每个FreeRTOS端口提供了一个演示应用程序,每个演示应用程序都有自己的FreeRTOSConfig.h文件。建议从这些演示应用程序使用的FreeRTOSConfig.h开始,然后根据你使用的FreeRTOS端口进行调整,而不是从头开始创建这个文件
1.4 官方发行版
- FreeRTOS的各个库,包括内核,可以通过它们的Github仓库或者下载zip文件获取
- 尽管可以单独获取各个库,但如果你是刚开始使用FreeRTOS,建议下载主FreeRTOS分布。主分布包含了所有FreeRTOS库的源代码、所有FreeRTOS内核端口,以及所有FreeRTOS演示应用的项目文件。
- 不要担心文件太多,实际上应用程序只需要其中一小部分
- 下载zip文件:
https://github.com/FreeRTOS/FreeRTOS/releases/latest - Git:
git clone https://github.com/FreeRTOS/FreeRTOS.git --recurse-submodules
git clone git@github.com:FreeRTOS/FreeRTOS.git --recurse-submodules
- FreeRTOS发行版的一级和二级目录
FreeRTOS
│ │
│ ├─Source Contains the FreeRTOS kernel source files
│ │
│ └─Demo Contains pre-configured and port specific FreeRTOS kernel demo projects
│
FreeRTOS-Plus
│
├─Source Contains source code for other FreeRTOS and ecosystem libraries
│
└─Demo Contains demo projects for other FreeRTOS and ecosystem libraries
1.5 通用FreeRTOS源文件
- task.c 和 list.c:这两个文件实现了FreeRTOS内核的核心功能,对于任何FreeRTOS项目来说都是必须的。他们位于FreeRTOS/Source目录下
- queue.c:该文件提供队列和信号量服务。该文件几乎全程需要,是FreeRTOS进行任务间通信的基础
- timers.c:该文件提供了软件定时器功能。仅当应用程序使用软件定时器时,才需要构建该文件
- event_groups.c:该文件提供事件组功能,允许任务等待一个或多个事件的发生。仅当应用程序使用事件组时,才需要构建该文件
- stream_buffer.c:该文件提供了流缓冲区和消息缓冲区功能,这些功能允许更复杂的数据传输。只有当应用程序使用流缓冲区或消息缓冲区时,才需要构建该文件
- croutine.c:该文件实现了FreeRTOS的协程功能。协程主要用于非常小的微控制器上,目前很少使用。因此停止了对它的维护,并且不推荐在新设计中使用。
FreeRTOS
│
└─Source
│
├─tasks.c FreeRTOS source file - always required
├─list.c FreeRTOS source file - always required
├─queue.c FreeRTOS source file - nearly always required
├─timers.c FreeRTOS source file - optional
├─event_groups.c FreeRTOS source file – optional
├─stream_buffer.c FreeRTOS source file - optional
└─croutine.c FreeRTOS source file – optional and no longer maintained
1.6 特定FreeRTOS源文件
- FreeRTOS/Source/portable目录:该目录包含了特定于FreeRTOS端口的源文件。该文件根据不同编译器和处理器架构进行组织,形成了一个层级结构
- 构建FreeRTOS项目:如果你想在具有特定架构的处理器上使用特定的编译器来运行FreeRTOS,除了核心FreeRTOS源文件之外,还需要构建位于
FreeRTOS/Source/portable/[compiler]/[architecture]
目录中的文件 - 堆内存管理:FreeRTOS也将堆内存分配视为可移植层的一部分。如果
configSUPPORT_DYNAMIC_ALLOCATION
设置为0,那么项目中就不应该包含堆内存分配方案 - 堆分配方案示例:Free RTOS在
FreeRTOS/Source/portable/MemMang
目录下提供了实例堆分配方案。如果FreeRTOS配置为使用动态内存分配,那么需要在项目中包含这些堆实现源文件中的一个,或者自己实现一个
!!!项目中的示例堆分配实现不得多于一个
FreeRTOS
│
└─Source
│
└─portable Directory containing all port specific source files
│
├─MemMang Directory containing the alternative heap allocation source files
│
├─[compiler 1] Directory containing port files specific to compiler 1
│ │
│ ├─[architecture 1] Contains files for the compiler 1 architecture 1 port
│ ├─[architecture 2] Contains files for the compiler 1 architecture 2 port
│ └─[architecture 3] Contains files for the compiler 1 architecture 3 port
│
└─[compiler 2] Directory containing port files specific to compiler 2
│
├─[architecture 1] Contains files for the compiler 2 architecture 1 port
├─[architecture 2] Contains files for the compiler 2 architecture 2 port
└─[etc.]
1.7 Include 路径
在使用FreeRTOS时,Include路径需要设置的三个目录
- FreeRTOS核心内核的头文件路径:路径为
FreeRTOS/Source/include
,其中头文件包含了FreeRTOS API的定义和声明,任何使用FreeRTOS的项目都必须包含 - 特定使用的FreeRTOS端口的源文件路径:该路径包含了特定于正在使用的FreeRTOS端口的源文件,路径格式为
FreeRTOS/Source/portable/[compiler]/[architecture]
。其中[compiler]
和[architecture]
需要替换为实际使用的编译器和处理器架构名称。这些源文件是特定于某个编译器和处理器架构的,对于不同的端口,需要不同的实现。 - 正确的FreeRTOSConfig.h头文件的路径:
FreeRTOSConfig.h
是用户自定义配置头文件,它允许用户根据具体应用需求配置FreeRTOS内核行为
1.8 头文件
使用FreeRTOS API源文件必须包含FreeRTOS.h,然后包含需要使用的头文件如task.h
、queue.h
、semphr.h
、timers.h
、event_groups.h
、stream_buffer.h
、message_buffer.h
或 croutine.h
。除了FreeRTOS.h
之外,不应该显式地包含任何其他的FreeRTOS头文件,因为FreeRTOS.h
会自动包含FreeRTOSConfig.h
。
二、演示应用
每个FreeRTOS端口至少附带一个演示应用程序。这些演示应用程序在创建时,可以“即开即用”,没有任何编译器错误或警告即可构建。
FreeRTOS开发和测试在多个平台,如Windows,Linux和MacOS,支持多种工具链,嵌入式的和传统的
FreeRTOS 的演示应用程序(Demo applications)的多个目的:
- 演示应用程序提供了一个已经设置好的项目模板,其中包含了正确的文件和编译器选项。用户可以直接使用这些项目作为起点,而不需要从头开始配置环境。
- 这些演示应用程序设计得非常简单,用户可以快速开始实验和学习 FreeRTOS,而不需要深入了解复杂的设置或事先拥有大量的相关知识。
- 演示应用程序通过实际的代码示例来展示 FreeRTOS 应用编程接口(API)的使用方法,帮助用户理解如何在自己的项目中利用这些API。
- 演示应用程序可以作为开发真实应用的基础,用户可以在这些示例的基础上添加自己的功能和逻辑。
- 演示应用程序还可以用来对 FreeRTOS 内核的实现进行压力测试,以确保其在各种条件下的稳定性和可靠性。
每个FreeRTOS演示项目都位于FreeRTOS/Demo目录下的一个独特子目录中。这个子目录的名称表明了演示项目与之相关的端口(port)
FreeRTOS.org网站为每个演示应用程序提供了一个页面。这些页面包含了以下信息:
- 如何在FreeRTOS目录结构中找到演示项目的项目文件
- 项目配置使用的硬件或模拟器
- 如何设置硬件以运行演示项目
- 如何构建演示项目
- 演示项目的预期行为
所有的演示项目都会创建一部分“公共演示任务”的子集。这些任务的实现代码位于FreeRTOS/Demo/Common/Minimal
目录下。这些公共演示任务的目的是为了展示如何使用FreeRTOS的API一级测试FreeRTOS内核端口。
许多演示项目还可以被配置为创建一个简单的“Blinky”风格的启动项目。这种类型的项目通常创建两个RTOS(实时操作系统)任务和一个队列。“Blinky”项目通常用于演示RTOS的基本功能,比如任务切换和简单的事件通知。
每个演示项目都包含一个名为main.c的文件。这个文件中包含了main()函数,该函数负责在启动FreeRTOS内核之前创建演示应用的任务。如果需要针对特定演示项目的具体信息,可以在各个main.c文件中的注释部分找到。
FreeRTOS
│
└─Demo Directory containing all the demo projects
│
├─[Demo x] Contains the project file that builds demo 'x'
├─[Demo y] Contains the project file that builds demo 'y'
├─[Demo z] Contains the project file that builds demo 'z'
└─Common Contains files that are built by all the demo applications
三、创建一个FreeRTOS项目
3.1 修改一个提供的演示项目
每个FreeRTOS的移植版本至少会附带一个预配置的演示应用程序。建议在创建新项目时,通过修改这些现有的项目来确保新项目包含了正确的文件、安装了正确的中断处理程序,以及设置了正确的编译器选项。这样做的目的是为了确保新项目能够顺利运行,避免因为缺少必要的配置而导致的问题。
从现有demo项目中创建一个新的应用
- 打开提供的demo项目并确保能够按照预期构建和执行
- 移除实现demo任务的源文件,这些文件位于
Demo/Common
目录 - 删除所有函数调用,除了
mian()
、prvSetupHardware()
和vTaskStartScheduler()
- 验证项目是否可以构建
通过以上步骤,创建一个包含正确FreeRTOS源文件的项目,但是没有任何具体功能
新的main()函数模板:
int main( void )
{
/* Perform any hardware setup necessary. */
prvSetupHardware();
/* --- APPLICATION TASKS CAN BE CREATED HERE --- */
/* Start the created tasks running. */
vTaskStartScheduler();
/* Execution will only reach here if there was insufficient heap to
start the scheduler. */
for( ;; );
return 0;
}
3.2 从头创建新项目
我们建议从已有demo中创建项目。如果你不想这么做,那么使用如下方法:
- 使用你选择的工具链,创建一个新的项目,该项目不包含任何FreeRTOS源文件
- 确保新项目的构建,下载你的目标硬件,然后执行
- 确保有一个工作的项目,添加FreeRTOS源文件,详细如下表
- 找到demo项目使用的头文件,并且该头文件是为使用端口提供的,将这些文件复制到你的项目目录中
- 添加如下目录到路径中,项目会寻找本地头文件:
·FreeRTOS/Source/include
·FreeRTOS/Source/portable/[compiler]/[architecture]
·目录包含头文件FreeRTOSConfig.h
- 从相关demo项目中复制编译器设置
- 安装所有可能需要的FreeRTOS终端处理程序。参考两个资料,一个是描述正在使用的端口的网页,另一个是为该端口提供的演示项目。
注意堆内存:如果configSUPPORT_DYNAMIC_ALLOCATION
为0
,那么在你的项目中不应该包含任何内存分配方案,否则包含一个堆内存方案,要么是heap_n.c
文件,要么自己设计一个方案。
四、数据类型和编码方法指导
4.1 数据类型
每个FreeRTOS端口都包含唯一的portmacro.h
,该文件包含了两种特定端口的数据类型:TickType
和BaseType
。如下列表描述了宏定义和类型定义
-
TickType_t
用于存储自FreeRTOS应用程序启动以来发生的滴答中断(tick interrupt)的次数,这个次数被称为滴答计数(tick count)
滴答中断(tick interrupt)
:FreeRTOS配置了一个周期性的中断,称为滴答中断。这个中断会定期发生,是FreeRTOS时间管理的基础。
滴答计数(tick count)
:自FreeRTOS应用程序启动以来发生的滴答中断的次数。这个计数被用来作为时间的度量。
滴答周期(tick period)
:两个滴答中断之间的时间被称为滴答周期。在FreeRTOS中,时间通常被指定为滴答周期的倍数。
TickType_t的数据类型
:TickType_t可以是一个无符号的16位类型、32位类型或64位类型,具体取决于FreeRTOSConfig.h中configTICK_TYPE_WIDTH_IN_BITS的设置。这个设置是依赖于架构的,FreeRTOS端口也会检查这个设置是否有效。
效率与限制
:在8位和16位架构上使用16位类型可以大大提高效率,但会严重限制在FreeRTOS API调用中可以指定的最大阻塞时间。在32位或64位架构上没有理由使用16位的TickType_t类型。
配置的变更
:之前使用的configUSE_16_BIT_TICKS配置已经被configTICK_TYPE_WIDTH_IN_BITS取代,以支持超过32位的滴答计数。新的设计应该使用configTICK_TYPE_WIDTH_IN_BITS而不是configUSE_16_BIT_TICKS。
-
BaseType_t
BaseType_t
总是被定义为对于特定计算机架构来说最高效的数据类型。这意味着它会根据计算机的处理能力自动选择最合适的数据类型大小。
在64位架构上,BaseType_t是一个64位的数据类型。
在32位架构上,BaseType_t是一个32位的数据类型。
在16位架构上,BaseType_t是一个16位的数据类型。
在8位架构上,BaseType_t是一个8位的数据类型。
用途
:BaseType_t通常用于那些只取值范围非常有限的返回类型,以及用于表示布尔值(如pdTRUE和pdFALSE)。布尔值是一种只有两个可能值的数据类型,通常用于表示逻辑上的真(true)或假(false)。
4.2 变量命名
在FreeRTOS的代码中,变量的命名会根据其类型有一个前缀,以便于快速识别变量的类型。以下是具体的命名规则:
c
:表示变量类型为char。
s
:表示变量类型为int16_t(短整型)。
l
:表示变量类型为int32_t(长整型)。
x
:表示变量类型为BaseType_t或其他非标准类型,比如结构体、任务句柄、队列句柄等。
除了这些基本类型前缀外,还有两个额外的前缀:
u
:如果变量是无符号的(unsigned),那么在类型前缀前还会加上u。例如,uint8_t类型的变量会以uc为前缀。
p
:如果变量是一个指针,那么在类型前缀前还会加上p。例如,指向char的指针(char *)类型的变量会以pc为前缀。
4.3 函数命名
在FreeRTOS中,函数的名称通常由两部分组成:返回值的类型和函数定义所在的文件。
函数前缀规则:FreeRTOS中的函数名称通常以它们的返回类型和定义它们的文件名作为前缀。这样做的目的是为了提高代码的可读性和便于维护。
举例说明:
vTaskPrioritySet()
:这个函数的名称表明它返回void类型(v前缀表示void),并且定义在tasks.c文件中(名称中的Task部分暗示了这一点)。
xQueueReceive()
:这个函数返回一个BaseType_t类型的值(x前缀通常表示返回一个状态码或者布尔值),并且定义在queue.c文件中(名称中的Queue部分暗示了这一点)。
pvTimerGetTimerID()
:这个函数返回一个指向void类型的指针(pv前缀表示返回一个指针),并且定义在timers.c文件中(名称中的Timer部分暗示了这一点)。
私有函数命名:文件作用域内的私有函数(即只在定义它们的文件中使用的函数)通常以prv作为前缀。
4.4 格式
一般情况下tab
缩进4空格
4.5 宏命名
大多数宏(macros)都是用大写字母书写的,并且前面加上了小写字母作为前缀,这些小写字母表示宏定义的位置。
注意在编程中,信号量(semaphore)的应用程序接口(API)几乎完全是用宏(macros)编写的,但是它遵循的是函数命名约定(function naming convention),而不是宏命名约定(macro naming convention)。
4.6 大量的类型转换
FreeRTOS源代码需要与许多不同的编译器兼容,这些编译器在何时以及如何生成警告方面存在差异。特别是,不同的编译器对类型转换的使用有不同的要求。因此,为了确保代码能够在各种编译器下编译通过,FreeRTOS源代码中包含了比通常情况下更多的类型转换。这种设计选择是为了确保代码的广泛兼容性,即使这可能导致代码中出现一些不必要的类型转换。
总结
本文详细介绍了FreeRTOS内核的文件结构和目录,解释了构建FreeRTOS所需的源文件,介绍了演示应用程序,并提供了创建新FreeRTOS项目的信息。文章强调了FreeRTOSConfig.h文件在配置内核中的重要性,并讨论了如何根据特定应用进行调整。同时,还介绍了官方发行版的内容和结构,以及如何从GitHub获取和克隆FreeRTOS。
资料来源:https://github.com/FreeRTOS/FreeRTOS-Kernel-Book/blob/main/ch02.md