1. 前言
因为工作的需要,学习FreeRTOS已经有一段时间了。接下来一段时间会定期更新本人学习FreeRTOS的系列笔记。系列笔记主要参考了官方的说明手册和FreeRTOS的源代码。其主要思想是先了解FreeRTOS的对外接口,即理清其对外可见的功能,以此作为切入点,逐步深入其内涵(即内部实现)。这是一种由外而内的学习方法,其好处很明显:
- 由浅入深,逐渐深入;
- 满足不同需求层析的人的需求:对于只需要了解如何使用FreeRTOS的童鞋,只需要学习外延部分即可;对于有更加深入要求的童鞋,也可以从中求得解答。
另外,实现部分主要基于FreeRTOS v9.0.0中ARMv7(确切而言是ARMCA9)Port版本。
FreeRTOS的特性:
- 高度可裁剪性(通过编译宏实现)
- Pre-emptive或Co-operative操作
- 非常灵活的任务优先级
- 灵活、快速和轻量级的任务通知机制
- 队列
- 二值信号量(Binary semaphores)
- 计数信号量(Counting semaphores)
- 互斥
- 循环互斥(Recursive Mutexes)
- 软件定时器
- 事件组(Event groups)
- Tick hook functions
- Idle hook functions
- 栈溢出检测
- Trace recording
- Task run-time statistics gathering
- 可选的商业Licensing和支持
- Full interrupt nesting model(对于某些体系结构可用)
- 提供了实现睡眠功能的框架,适用于某些具有极低功耗需求的应用
- 支持软件管理的中断栈(可帮助节省RAM)
2. FreeRTOS发行版(The FreeRTOS Distribution)
2.1. 理解FreeRTOS发行版
FreeRTOS port:每个所支持的编译器和处理器的组合,称为一个独立的FreeRTOS port。
2.1.1. 构建FreeRTOS
在项目中,可以将FreeRTOS视为一个提供多任务能力的函数库;
FreeRTOS用C语言实现;
源代码将作为项目的一部分存在。
2.1.2. 配置FreeRTOS
方法:修改FreeRTOSConfig.h,该头文件位于实际应用工程所在的目录。
2.1.3. 官方发行版
主要组成:一个zip文件,其中包含了所有FreeRTOS port的源码与demo应用的项目文件,以及可选的FreeRTOS+ 生态系统组件和相应的demo工程。
顶层目录结构:
所有Ports共享的源文件:
与Port相关的源文件:
2.1.4. 包含路径(Include Paths)
为了FreeRTOS能正常使用,需要添加到编译器的头文件路径的路径:
- FreeRTOS的核心头文件:/FreeRTOS/Source/include
- 实际使用的FreeRTOS port的源文件路径:FreeRTOS/Source/portable/[compiler]/[architecture]
- FreeRTOSConfig.h所在的路径
2.1.5. 关键的头文件
所有使用了FreeRTOS API的源文件都需要包含:
- FreeRTOS.h
- 实际使用的API的原型定义头文件:例如"task.h"、"queue.h"等。
2.2. Demo应用
Demo应用的主要目的:
- 提供一个可用的例子工程,包含正确的文件,和正确的编译器配置集
- 提供“开箱即用(Out of the Box)”的使用场景,减少配置和前置知识
- 作为如何使用FreeRTOS的说明
- 实际项目的起始点
所有Demo的描述可以在FreeRTOS.org查询。
FreeRTOS/Demo的目录结构如下:
2.3. 创建FreeRTOS Project
2.3.1. 移植一个已经提供的Demo Project
官方建议的方式
步骤如下:
- 打开已有的demo project,确保它能够正常的编译和执行
- 删除定义demo tasks的源文件:在Demo/Common目录下的文件都可以被删除
- 除了
prvSetupHardware()
和vTaskStartScheduler()
,删除所有main()
内其他的函数调用 - 确保工程仍然可以正常编译。
2.3.2. 从头创建一个新的工程
不建议。
2.4. 数据类型和编码风格
2.4.1. 数据类型(Data Types)
平台相关的数据类型定义:portmacro.h
-
TickType_t
:tick interrupt:freertos中的周期性中断。
tick count:从freertos应用开始后,tick interrupt发生的次数。用于度量时间。
tick period:两个tick interrupt之间的时间。时间用tick periods来定义。
TickType_t
类型的变量用于存储tick count值。根据
configUSE_16_BIT_TICKS
来确定TickType_t
被定义为unsigned 16-bit类型(1)或是unsigned 32-bit类型(0) -
BaseType_t
被定义为当前体系结构的最大的有效数据类型(长度为机器字长),例如32位体系结构则为32-bit类型。
该类型使用场景有限。
-
StackType_t
与
BaseType_t
类似。它表示栈的槽的大小。
其他类型:
char
需要显式定义为signed
或unsigned
- 不使用无修饰的
int
2.4.2. 变量命名
变量名中带有表明其类型的前缀:
- c -
char
- s -
int16_t
(short
) - l -
int32_t
(long
) - x -
BaseType_t
和其他非标准的类型(structures, task handler, queue handle等)
变量的符号前缀:
- u -
unsigned
:例如uc表示uint8_t
变量的指针前缀:
- p - pointer:例如pc表示指向
char
的指针
2.4.3. 函数命名
函数以其返回类型和其被定义的文件名为前缀:
vTaskPrioritySet()
返回void
,定义在task.c
pvTimerGetTimerID()
返回指向void
类型的指针,定义在timers.c
仅文件内可见的函数以prv(private)作为前缀。
2.4.4. 宏命名
以小写字母作为前缀,表明其所定义的文件,大写字母为宏定义的名字:
- port:
portMAX_DELAY
定义在portable.h
或portmacro.h
- task:
taskENTER_CRITICAL()
定义在task.h
- pd:
pdTRUE
定义在projdefs.h
- config:
configUSE_PREEMPTION
定义在FreeRTOSConfig.h
- err:
errQUEUE_FULL
定义在projdefs.h
semaphore API用宏定义实现,但遵循函数的命名方式。
FreeRTOS全局使用的宏定义:
pdTRUE
: 1pdFALSE
: 0pdPASS
: 1pdFAIL
: 0
2.4.5. 文本格式化
1 tab = 4 spaces