尚硅谷嵌入式技术之FreeRTOS
实时操作系统
(作者:尚硅谷研究院)
版本:V1.0.3
第 1 章 操作系统介绍
1.1 什么是裸机开发
裸机开发指的是在没有操作系统(OS)或者其他高级软件支持的情况下,直接在裸机硬件上进行软件开发的过程。在裸机开发中,开发者需要直接面对硬件层面的操作和控制,亲自管理CPU、内存以及I/O资源,而不依赖于任何操作系统提供的抽象层或者服务。
我们前面学习的STM32单片机代码都属于裸机开发。
1.2 什么是操作系统
一个计算机系统可以大致分为三个部分:硬件(Hardware),操作系统(operating system),应用程序(application programs)。
(1)硬件包含了芯片,存储空间,输入输出等设备为整个系统提供了基础的计算资源。
(2)操作系统是一个控制程序,作为硬件和应用程序之间的桥梁,主要是和硬件打交道,负责协调分配计算资源和内存资源给不同的应用程序使用,并防止系统出现故障。面对来自不同应用程序的大量且互相竞争的资源请求,操作系统通过一个调度算法和内存管理算法尽可能把资源公平且有效率地分配给不同的程序。
(3)应用程序则通过调用操作系统提供的API接口获得相应资源完成指定的任务。
操作系统从整体上分为两大类:通用操作系统和实时操作系统。
1.2.2 通用操作系统
通用操作系统包括Linux,Windows,MACOS等主流的操作系统。这些操作系统大家每天都在使用,功能也十分强大,只是它们有时为了保障系统的流畅运行,就不能保证每个程序都能实时响应,在易用性和实时性之间有所取舍。而且单片机有限的片上资源也不足以支撑通用操作系统的运行。
1.2.3 实时操作系统
实时操作系统(RTOS-Real Time Operating System)中实时(Real Time)指的是任务(Task)或者说实现一个功能的线程(Thread)必须在给定的时间(Deadline)内完成。
人们总有种误解认为如果能堆砌更多的处理器核心数目,更高的处理器频率,更大的内存,更快的总线速度系统就一定能达到实时性。然而事与愿违强大的计算能力并不能保证系统的实时性。举一个简单的例子比如汽车中的安全气囊,在传感器检测到汽车发生碰撞后,安全气囊需要在30ms内完全打开,不然司机和乘客的人身安全将受到极大的威胁。倘若车载ECU有很强大的计算能力,但是如果因为要执行其他复杂计算任务或者任务调度的问题导致对汽车异常状态的监测和安全气囊的响应时间超过了规定的时间,系统实时性将无法得到保障从而导致系统失效和人员伤亡,这将会是非常严重的问题。
为了保障这些实时任务能在给定的时间内完成,需要一个实时系统对这些任务进行调度和管理。一个实时操作系统能尽力保障每个任务的运行时间在规定时间内完成,这包括
(1)对中断和内部异常的处理
(2)对安全相关的事件的处理
(3)任务调度机制等
正所谓术业有专攻,在嵌入式领域中,嵌入式实时操作系统(RTOS)可以更合理、更有效地利用CPU的资源,简化应用软件的设计,缩短系统开发时间,从而更好地保证系统的实时性和可靠性。
目前比较流行的实时操作系统包括黑莓QNX,FreeRTOS,uCOS,RT-Thread等
1.3 FreeRTOS简介
RTOS(实时操作系统)是指一类系统,如 FreeRTOS,uC/OS,RTX,RT-Thread 等,都是 RTOS 类操作系统。
FreeRTOS是所有实时操作系统中最受欢迎的一款.
1.3.1 FreeRTOS发展历史
FreeRTOS 由美国的 Richard Barry 于 2003 年发布。
FreeRTOS 于 2017 年被亚马逊收购,改名为 AWS FreeRTOS。
1.3.2 FreeRTOS优势
FreeRTOS 是市场领先的面向微控制器和小型微处理器的实时操作系统 (RTOS),与世界领先的芯片公司合作开发,现在每 170 秒下载一次。
FreeRTOS 通过 MIT 开源许可免费分发,包括一个内核和一组不断丰富的 IoT 库,适用于所有行业领域。FreeRTOS 的构建突出可靠性和易用性。
FreeRTOS是一款受欢迎、广泛应用于嵌入式系统的RTOS,其开源、轻量级、可移植的特点使其成为许多嵌入式开发者的首选,主要优势如下:
Ø 开源和免费:FreeRTOS是一款开源的RTOS,采用MIT许可证发布,可以免费使用、修改和分发。
Ø 轻量级设计:FreeRTOS注重轻量级设计,适用于资源受限的嵌入式系统,不占用过多内存和处理器资源。
Ø 广泛应用:FreeRTOS在嵌入式领域得到广泛应用,包括工业自动化、医疗设备、消费电子产品、汽车电子等。
Ø 多平台支持:FreeRTOS的设计注重可移植性,可以轻松地移植到不同的硬件平台,支持多种处理器架构。
Ø 丰富的功能:提供了多任务调度、任务通信、同步等功能,适用于复杂的嵌入式应用场景。
1.3.3 FreeRTOS特点
官网:https://freertos.org/,并且支持中文。
Ø 任务调度:FreeRTOS通过任务调度器管理多个任务,支持不同优先级的任务,实现任务的有序执行。
Ø 任务通信和同步:提供了队列、信号量等机制,支持任务之间的通信和同步,确保数据的安全传递。
Ø 内存管理:提供简单的内存管理机制,适用于嵌入式环境,有效利用有限的内存资源。
Ø 定时器和中断处理:支持定时器功能,能够处理中断,提供了可靠的实时性能。
Ø 开发社区:拥有庞大的用户社区,开发者可以在社区中获取支持、解决问题,并分享经验。
Ø 可移植性:设计注重可移植性,可以轻松地移植到不同的硬件平台,提高了代码的重用性。
第 2 章 FreeRTOS基础知识
2.1 多任务处理
内核是操作系统的核心组件。诸如 Linux 这样的操作系统采用的内核, 看似允许用户同时访问计算机。很明显,多个用户可以同时执行多个程序 。
每个执行程序都是受操作系统控制的任务(或线程)。如果一个操作系统能够以这种方式执行多个任务, 则可称其为多任务操作系统。
使用多任务操作系统可以简化原本复杂的软件应用程序的设计 :
(1)操作系统的多任务处理和任务间通信功能允许将复杂的应用程序分割成一组更小、更易于管理的任务。
(2)通过分割,您可以更轻松地执行软件测试、分解团队内部工作以及复用代码。
(3)复杂的时序和排序细节可以从应用程序代码中移除,由操作系统负责。
即使单核处理器一次只能执行一项任务。 多任务操作系统可以通过任务之间的快速切换制造并发执行的假象。下图展示了与时间相关的三项任务的执行模式。 任务名称采用颜色编码,并写在左手边。 时间从左向右移动, 彩色线条显示了在任何特定时间正在执行的任务。 上方展示了所感知的并发执行模式, 下方展示了实际的多任务执行模式。
2.2 任务调度
一个处理器核心在某一时刻只能运行一个任务,如在各个任务之间迅速切换,这样看起来就像多个任务在同时运行。操作系统中任务调度器的责任就是决定在某一时刻要执行哪个任务。
调度器是内核中负责决定在任何特定时间应执行哪些任务的部分。内核可以在任务生命周期内多次挂起并且稍后恢复一个任务。
调度策略是调度器用来决定在任何时间点执行哪个任务的算法。
FreeRTOS 默认使用固定优先级的抢占式调度策略,对同等优先级的任务执行时间片轮询调度:
Ø 抢占式调度:FreeRTOS采用抢占式调度方式,允许更高优先级的任务在任何时刻抢占正在执行的低优先级任务。这确保了高优先级任务能够及时响应,并提高了系统的实时性。
Ø 时间片轮询:在相同优先级的任务之间,FreeRTOS采用时间片轮转策略。每个任务执行一个时间片,如果有其他同优先级的任务等待执行,则切换到下一个任务。这有助于公平地分配CPU时间。
但是并不是说高优先级的任务会一直执行,导致低优先级的任务无法得到执行。如果高优先级任务等待某个资源(延时或等待信号量等)而无法执行,调度器会选择执行其他就绪的高优先级的任务。
2.3 任务状态
FreeRTOS中任务共存在4种状态:
Ø 运行态:当任务实际执行时,它被称为处于运行状态。如果运行 RTOS 的处理器只有一个内核, 那么在任何给定时间内都只能有一个任务处于运行状态。注意在STM32中,同一时间仅一个任务处于运行态。
Ø 就绪态:准备就绪任务指那些能够执行(它们不处于阻塞或挂起状态), 但目前没有执行的任务, 因为同等或更高优先级的不同任务已经处于运行状态。
Ø 阻塞态:如果任务当前正在等待延时或外部事件,则该任务被认为处于阻塞状态。
Ø 挂起态:类似暂停,调用函数 vTaskSuspend() 进入挂起态,需要调用解挂函数vTaskResume()才可以进入就绪态。
只有就绪态可转变成运行态,其他状态的任务想运行,必须先转变成就绪态。转换关系如下:
这四种状态中,除了运行态,其他三种任务状态的任务都有其对应的任务状态列表:
Ø 就绪列表:pxReadyTasksLists[x],其中x代表任务优先级数值。
Ø 阻塞列表:pxDelayedTaskList。
Ø 挂起列表:xSuspendedTaskList。
列表类似于链表,后面章节会专门介绍。
以就绪列表为例。如果在32位的硬件中,会保存一个32位的变量,代表0-31的优先级。当某个位,置一时,代表所对应的优先级就绪列表有任务存在。
如果有多个任务优先级相同,会连接在同一个就绪列表上:
调度器总是在所有处于就绪列表的任务中,选择具有最高优先级的任务来执行。
2.4 FreeRTOS的滴答
休眠时,RTOS 任务将指定需要“唤醒”的时间。 阻塞时,RTOS 任务可以指定希望等待的最长时间。
FreeRTOS 实时内核通过滴答计数变量测量时间。定时器中断(RTOS 滴答中断)以严格的时间精度增加滴答数——允许实时内核以所选择的定时器中断频率的分辨率来测量时间。
每次滴答数增加时,实时内核必须检查是否现在是解除阻塞或唤醒任务的时间。在滴答 ISR 期间唤醒或解除阻塞的任务的优先级可能高于被中断任务的优先级。
2.5 上下文切换
1)什么上下文切换
当一个任务执行时,它会利用处理器/微控制器寄存器,并像其他程序一样访问 RAM 和 ROM。这些资源(处理器寄存器,堆栈等)一起组成了任务执行上下文。
一个任务是一段有顺序的代码——它不知道什么时候会被内核挂起(换出或换入)或恢复(换入或换入), 甚至不知道什么时候自己被挂起或恢复过。
一个任务在即将执行将两个处理器寄存器内包含的数值相加之前被挂起。 当该任务被挂起时,其他任务会执行,还可能会修改处理器寄存器的数值。恢复时, 该任务不会知道处理器寄存器已经被修改过了——如果它使用经修改过的数值, 那么求和会得到一个错误的数值。
为了防止这种类型的错误,任务在恢复时必须有一个与挂起之前相同的上下文 。通过在任务挂起时保存任务的上下文,操作系统内核负责确保上下文保持不变。任务恢复时,其保存的上下文在执行之前由操作系统内核恢复。
保存被挂起的任务的上下文和恢复被恢复的任务的上下文的过程被称为 上下文切换。
(1)将 TaskA在相应的处理器寄存器中的上下文保存到其任务堆栈中。
(2)将 TaskB 的上下文从其任务堆栈中恢复到相应的处理器寄存器中
2)什么时候进行上下文切换
在需要切换任务的时候进行上下文切换,真正执行上下文切换是在PendSV的ISR中处理的。使用PendSV是因为其可以手动触发,并且可以在其他更高中断优先级的ISR中来进行设置,比较灵活。具体触发操作是将中断控制和状态寄存器 ICSR 的 bit28,也就是 PendSV 的挂起位置 1 来触发PendSV 中断。FreeRTOS会将PendSV设置为最低中断优先级,避免任务切换影响到其他正常的ISR。
在FreeRTOS中有以下几个情况会触发PendSV异常产生切换:
(1)RTOS 滴答中断:会处理就绪列表,判断是否要切换任务(包括抢占式、时间片轮转)。
(2)任务执行完毕:主动调用任务切换函数进行强制切换。
2.6 空闲任务
RTOS 调度器启动时,自动创建空闲任务,以确保始终存在一个能够运行的任务。
空闲以最低优先级创建,以确保如果有更高的优先级应用程序任务处于准备就绪状态,空闲任务则不使用任何 CPU 时间。
空闲任务负责释放被删除的任务的内存。
第 3 章 FreeRTOS移植
3.1 FreeRTOS源码结构介绍
3.1.1 获取源码
1)官网下载
官网地址:https://www.freertos.org/
这里我们选择当前最新的分发包202212.01版本下载。
2)Github下载
Github地址:https://github.com/FreeRTOS/FreeRTOS/releases
现在FreeRTOS已经将源码迁移到Github上,可以直接下载。
3.1.2 源码结构介绍
1)源码整体结构
名称
描述
FreeRTOS
FreeRTOS内核
FreeRTOS-Plus
FreeRTOS组件,一般我们会选择使用第三方的组件
tools
工具
GitHub-FreeRTOS-Home
FreeRTOS的GitHub仓库链接
Quick_Start_Guide
快速入门指南官方文档链接
Upgrading-to-FreeRTOS-xxx
升级到指定FreeRTOS版本官方文档链接
History.txt
FreeRTOS历史更新记录
其他
其他
2)FreeRTOS文件夹结构
名称
描述
Demo
FreeRTOS演示例程,支持多种芯片架构、多种型号芯片
License
FreeRTOS相关许可
Source
FreeRTOS源码,最重要的文件夹
Test
公用以及移植层测试代码
3)Source文件夹结构如下
名称
描述
include
内包含了FreeRTOS的头文件
portable
包含FreeRTOS移植文件:与编译器相关、keil编译环境
croutine.c
协程相关文件
event_groups.c
事件相关文件
list.c
列表相关文件
queue.c
队列相关文件
stream_buffer.c
流式缓冲区相关文件
tasks.c
任务相关文件
timers.c
软件定时器相关文件
include文件夹和.c文件是通用的头文件和 C 文件,这两部分的文件适用于各种编译器和处理器,是通用的。标红的是移植必需的,其他.c文件根据需要选取。
portable文件夹里根据编译器、内核等实际环境对应选取。
4)portable文件夹结构
FreeRTOS操作系统归根到底是一个软件层面的东西,需要跟硬件联系在一起,portable文件夹里面的东西就是连接桥梁。由于我们使用MDK开发,因此这里只重点介绍其中的部分移植文件。
名称
描述
Keil
指向RVDS文件夹
RVDS
不同内核芯片的移植文件
MemMang
内存管理相关文件
Keil文件夹里只有一个See-also-the-RVDS-directory.txt,意思是让我们看RVDS文件夹。
(1)RVDS文件夹
RVDS 文件夹包含了各种处理器相关的文件夹,FreeRTOS 是一个软件,单片机是一个硬件,FreeRTOS 要想运行在一个单片机上面,它们就必须关联在一起。
关联还是得通过写代码来关联,这部分关联的文件叫接口文件,通常由汇编和 C 联合编写。这些接口文件都是跟硬件密切相关的,不同的硬件接口文件是不一样的,但都大同小异。编写这些接口文件的过程我们就叫移植,移植的过程通常由 FreeRTOS 和 mcu 原厂的人来负责,移植好的这些接口文件就放在 RVDS 这个文件夹的目录下。
FreeRTOS 为我们提供了 cortex-m0、m3、m4 和 m7 等内核的单片机的接口文件,根据mcu的内核选择对应的接口文件即可。其实准确来说,不能够叫移植,应该叫使用官方的移植, 因为这些跟硬件相关的接口文件,RTOS 官方都已经写好了,我们只是使用而已。
以 ARM_CM3 这个文件夹为例,里面只有“port.c”与“portmacro.h” 两个文件,
Ø port.c文件:里面的内容是由 FreeRTOS 官方的技术人员为 Cortex-M3 内核的处理器写的接口文件,里面核心的上下文切换代码是由汇编语言编写而成,对技术员的要求比较高,我们只是使用的话只需拷贝过来用即可。
Ø portmacro.h文件:port.c文件对应的头文件,主要是一些数据类型和宏定义。
(2)MemMang文件夹
MemMang 文件夹下存放的是跟内存管理相关的,总共有五个 heap 文件以及一个 readme 说明文件。
这五个 heap 文件在移植的时候必须使用一个,因为 FreeRTOS 在创建内核对象的时候使用的是动态分配内存,而这些动态内存分配的函数则在这几个文件里面实现,不同的分配算法会导致不同的效率与结果,后面在内存管理中我们会讲解每个文件的区别,由于现在是初学,所以我们选用 heap4.c 即可。
3.2 FreeRTOS在基于HAL库项目中移植步骤
3.2.1 目录添加源码文件
在例程的根路径下,新建“FreeRTOS”文件夹,并且在里面新建“portable”和“source”两个空文件夹。
拷贝FreeRTOS源码的Source文件夹的7个.c文件到例程的source文件夹。
拷贝FreeRTOS源码portable文件夹下的Keil、RVDS、MemMang到例程的portable文件夹下。
其中例程的MemMang可只保留heap_4.c:
其中例程的RVDS可只保留ARM_CM3(对应我们的芯片内核)。
拷贝FreeRTOS源码include文件夹到例程的FreeRTOS文件夹下。
FreeRTOSConfig.h 文件是 FreeRTOS 的工程配置文件,因为 FreeRTOS 是可以裁剪的 实时操作内核,应用于不同的处理器平台,用户可以通过修改这个 FreeRTOS 内核的配置 头文件来裁剪 FreeRTOS 的功能,所以我们把它拷贝一份放在 user 这个文件夹下面。
在源码“..\FreeRTOS\Demo”文件夹下面找到 “ CORTEX_STM32F103_Keil ” 这个文件夹下,找到 “FreeRTOSConfig.h”文件,然后拷贝到我们工程下的 “Core/Inc” 文件夹下即可,等下我们需要对这个文件进行修改。
3.2.2 工程添加源码文件
工程新建Group“FreeRTOS/Source”和“FreeRTOS/Portable”。
FreeRTOS/Source添加.c文件。
FreeRTOS/Portable添加port.c和heap_4.c文件。
添加配置头文件。
添加头文件。
FreeRTOS 的源码已经添加到开发环境的组文件夹下面,编译的时候需要为这些源文件指定头文件的路径,不然编译会报错。FreeRTOS 的源码里面只有 include 和RVDS\ARM_CM3这两个文件夹下面有头文件,只需要将这两个头文件的路径在开发环境里面指定即可。
同时我们还将 FreeRTOSConfig.h 这个头文件拷贝到了工程根目录下的 Core/Inc 文件夹下,这个路径本身就在开发环境里面。(放其他路径也可以, 就是一个.h文件)
3.2.3 系统配置文件修改
FreeRTOSConfig.h中添加如下3个配置:
#define xPortPendSVHandler PendSV_Handler
#define vPortSVCHandler SVC_Handler
#define INCLUDE_xTaskGetSchedulerState 1
3.2.4 修改stm32f1xx_it.c
1)引入头文件
#include "FreeRTOS.h"
#include "task.h"
2)注释掉2个函数
// void SVC_Handler(void)
// {
// }
// void PendSV_Handler(void)
// {
// }
3)添加SysTick时钟中断服务函数
extern void xPortSysTickHandler(void);
void SysTick_Handler(void)
{
if (xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED)
{
xPortSysTickHandler();
}
}
注意:HAL本身和FreeRTOS都默认依赖SysTick,可能出现卡死的问题。
为了保险起见,可以考虑在SYS选择HAL时钟源的时候换成其他的,并且中断优先级设为较高,比如1。
3.3 FreeRTOS在基于寄存器项目中移植步骤
3.3.1 目录添加源码文件
在例程的根路径下,新建“FreeRTOS”文件夹,并且在里面新建“portable”和“source”两个空文件夹。
拷贝FreeRTOS源码的Source文件夹的7个.c文件到例程的source文件夹。
拷贝FreeRTOS源码portable文件夹下的Keil、RVDS、MemMang到例程的portable文件夹下。
其中例程的MemMang可只保留heap_4.c:
其中例程的RVDS可只保留ARM_CM3(对应我们的芯片内核)。
拷贝FreeRTOS源码include文件夹到例程的FreeRTOS文件夹下。
FreeRTOSConfig.h 文件是 FreeRTOS 的工程配置文件,因为 FreeRTOS 是可以裁剪的 实时操作内核,应用于不同的处理器平台,用户可以通过修改这个 FreeRTOS 内核的配置头文件来裁剪 FreeRTOS 的功能,所以我们把它拷贝一份放在 user 这个文件夹下面。
3.3.2 工程添加源码文件
工程新建Group“FreeRTOS/Source”和“FreeRTOS/Portable”。
3.3.3 系统配置文件修改
FreeRTOSConfig.h中添加如下3个配置:
#define xPortPendSVHandler PendSV_Handler
#define vPortSVCHandler SVC_Handler
#define INCLUDE_xTaskGetSchedulerState 1
3.3.4 main.c中添加如下代码
FreeRTOS使用滴答定时器来实现的系统时基, 需要实现滴答定时器的中断,并在中断中添加下面的代码.
extern void xPortSysTickHandler(void);
void SysTick_Handler(void)
{
if(xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED)
{
xPortSysTickHandler();
}
}
3.4 系统配置文件说明
FreeRTOSConfig.h 配置文件作用:对FreeRTOS的功能进行配置和裁剪,以及API函数的使能等。
官网中文说明:https://www.freertos.org/zh-cn-cmn-s/Documentation/02-Kernel/03-Supported-devices/02-Customization
整体的配置项可以分为三类:
Ø INCLUDE开头:一般是“INCLUDE_函数名”,函数的使能,1表示可用,0表示禁用。
Ø config开头:FreeRTOS的一些功能配置,比如基本配置、内存配置、钩子配置、中断配置等。
Ø 其他配置:PendSV宏定义、SVC宏定义。
根据需要去配置,后续章节的知识点和案例,会涉及到其中一些配置,再去熟悉即可。
#ifndef FREERTOS_CONFIG_H
#define FREERTOS_CONFIG_H
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include <stdint.h>
extern uint32_t SystemCoreClock;
#define configUSE_PREEMPTION 1
#define configUSE_PORT_OPTIMISED_TASK_SELECTION 1
#define configUSE_TICKLESS_IDLE 0
#define configCPU_CLOCK_HZ SystemCoreClock
//#define configSYSTICK_CLOCK_HZ (configCPU_CLOCK_HZ / 8)
#define configTICK_RATE_HZ 1000
#define configMAX_PRIORITIES 32
#define configMINIMAL_STACK_SIZE 128
#define configMAX_TASK_NAME_LEN 16
#define configUSE_16_BIT_TICKS 0
#define configIDLE_SHOULD_YIELD 1
#define configUSE_TASK_NOTIFICATIONS 1
#define configTASK_NOTIFICATION_ARRAY_ENTRIES 1
#define configUSE_MUTEXES 1
#define configUSE_RECURSIVE_MUTEXES 1
#define configUSE_COUNTING_SEMAPHORES 1
#define configUSE_ALTERNATIVE_API 0
#define configQUEUE_REGISTRY_SIZE 8
#define configUSE_QUEUE_SETS 1
#define configUSE_TIME_SLICING 1
#define configUSE_NEWLIB_REENTRANT 0
#define configENABLE_BACKWARD_COMPATIBILITY 0
#define configNUM_THREAD_LOCAL_STORAGE_POINTERS 0
#define configSTACK_DEPTH_TYPE uint16_t
#define configMESSAGE_BUFFER_LENGTH_TYPE size_t
#define configSUPPORT_STATIC_ALLOCATION 0
#define configSUPPORT_DYNAMIC_ALLOCATION 1
#define configTOTAL_HEAP_SIZE ((size_t)(10 * 1024))
#define configAPPLICATION_ALLOCATED_HEAP 0
#define configSTACK_ALLOCATION_FROM_SEPARATE_HEAP 0
#define configUSE_IDLE_HOOK 0
#define configUSE_TICK_HOOK 0
#define configCHECK_FOR_STACK_OVERFLOW 0
#define configUSE_MALLOC_FAILED_HOOK 0
#define configUSE_DAEMON_TASK_STARTUP_HOOK 0
#define configGENERATE_RUN_TIME_STATS 0
#if configGENERATE_RUN_TIME_STATS
#include "./BSP/TIMER/btim.h"
#define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS() ConfigureTimeForRunTimeStats()
extern uint32_t FreeRTOSRunTimeTicks;
#define portGET_RUN_TIME_COUNTER_VALUE() FreeRTOSRunTimeTicks
#endif
#define configUSE_TRACE_FACILITY 1
#define configUSE_STATS_FORMATTING_FUNCTIONS 1
#define configUSE_CO_ROUTINES 0
#define configMAX_CO_ROUTINE_PRIORITIES 2
#define configUSE_TIMERS 1
#define configTIMER_TASK_PRIORITY ( configMAX_PRIORITIES - 1 )
#define configTIMER_QUEUE_LENGTH 5
#define configTIMER_TASK_STACK_DEPTH ( configMINIMAL_STACK_SIZE * 2)
#define INCLUDE_vTaskPrioritySet 1
#define INCLUDE_uxTaskPriorityGet 1
#define INCLUDE_vTaskDelete 1
#define INCLUDE_vTaskSuspend 1
#define INCLUDE_xResumeFromISR 1
#define INCLUDE_vTaskDelayUntil 1
#define INCLUDE_vTaskDelay 1
#define INCLUDE_xTaskGetSchedulerState 1
#define INCLUDE_xTaskGetCurrentTaskHandle 1
#define INCLUDE_uxTaskGetStackHighWaterMark 1
#define INCLUDE_xTaskGetIdleTaskHandle 1
#define INCLUDE_eTaskGetState 1
#define INCLUDE_xEventGroupSetBitFromISR 1
#define INCLUDE_xTimerPendFunctionCall 1
#define INCLUDE_xTaskAbortDelay 1
#define INCLUDE_xTaskGetHandle 1
#define INCLUDE_xTaskResumeFromISR 1
#ifdef __NVIC_PRIO_BITS
#define configPRIO_BITS __NVIC_PRIO_BITS
#else
#define configPRIO_BITS 4
#endif
#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY 15
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5
#define configKERNEL_INTERRUPT_PRIORITY ( configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
#define configMAX_SYSCALL_INTERRUPT_PRIORITY ( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
#define configMAX_API_CALL_INTERRUPT_PRIORITY configMAX_SYSCALL_INTERRUPT_PRIORITY
#define xPortPendSVHandler PendSV_Handler
#define vPortSVCHandler SVC_Handler
#define vAssertCalled(char, int) printf("Error: %s, %d\r\n", char, int)
#define configASSERT( x ) if( ( x ) == 0 ) vAssertCalled( __FILE__, __LINE__ )
//#define configINCLUDE_APPLICATION_DEFINED_PRIVILEGED_FUNCTIONS 0
//#define configTOTAL_MPU_REGIONS 8
//#define configTEX_S_C_B_FLASH 0x07UL
//#define configTEX_S_C_B_SRAM 0x07UL
//#define configENFORCE_SYSTEM_CALLS_FROM_KERNEL_ONLY 1
//#define configALLOW_UNPRIVILEGED_CRITICAL_SECTIONS 1
//#define secureconfigMAX_SECURE_CONTEXTS 5
#endif
3.5 FreeRTOS数据类型
针对每个移植定义四种类型。即:
3.5.1 TickType_t
如果 configUSE_16_BIT_TICKS 设置为非零 (true) ,则将 TickType_t 定义为无符号的 16 位类型。如果 configUSE_16_BIT_TICKS 设置为零(假),则将 TickType_t 定义为无符号的 32 位类型。
这个类型的变量, 通常用来表示系统节拍计数器的值。FreeRTOS系统中,每隔一段时间会进行一次滴答定时器中断处理,这个时间间隔就是系统的节拍周期。TickType_t类型的变量记录了系统过去的节拍次数。
3.5.2 BaseType_t
架构中最有效、最自然的类型。例如,在 32 位架构上,BaseType_t 会被定义为 32 位类型。在 16 位架构上,BaseType_t 会被定义为 16 位类型。是有符号的.
3.5.3 UBaseType_t
是无符号的BaseType_t
3.5.4 StackType_t
意指架构用于存储堆栈项目的 类型。通常是 16 位架构上的 16 位类型和 32 位架构上的 32 位类型,但也有例外情况。供 FreeRTOS 内部使用。
3.6 FreeROS的命名规范
了解FreeRTOS的编码规范,有助于我们理解和学习FreeRTOS的使用.
3.6.1 变量
(1)变量名称使用驼峰式大小写,具有明确的描述性,并使用完整的单词(没有缩写,但普遍接受的缩写除外)。
(2)uint32_t 类型变量以 ul 为前缀,其中“u”表示“unsigned” ,“l”表示“long”。
(3)uint16_t 类型变量以 us 为前缀,其中“u”表示“unsigned” , “s”表示“short”。
(4)uint8_t 类型变量以 uc 为前缀,其中“u”表示“unsigned” , “c”表示“char ”。
(5)非 stdint 类型的变量以 x 为前缀。例如,BaseType_t 和 TickType_t,二者分别是可移植层定义的定义类型,主要架构的自然类型或最有效类型,以及用于保存 RTOS ticks 计数的类型。
(6)非 stdint 类型的未签名变量存在附加前缀 u。例如,UBaseType_t(无符号的BaseType_t)类型变量以 ux 为前缀。
(7)size_t 类型变量也带有 x 前缀。
(8)枚举变量以 e 为前缀
(9)指针以附加 p 为前缀,例如,指向 uint16_t 的指针将以 pus 为前缀。
(10)根据 MISRA 指南,无符号 char 类型仅可包含 ASCII 字符,并以 c 为前缀。
(11)根据 MISRA 指南,char * 类型变量仅可包含指向 ASCII 字符串的指针,并以 pc 为前缀。
3.6.2 函数
(1)函数名称使用驼峰式大小写,具有明确的描述性,并使用完整的单词(无缩写,但普遍接受的缩写除外)。
(2)文件作用域静态(私有)函数以 prv 为前缀。
(3)根据变量定义的相关规定,API 函数以其返回类型为前缀,并为 void 添加前缀 v。
(4)API 函数名称以定义 API 函数文件的名称开头。
比如一个函数 vTaskDelay , 从函数名可以得到如下信息:v表示这个函数的返回值是void, Task表示这个函数定义在Task.c文件中, Delay表示函数的功能
3.6.3 宏
(1)宏具有明确的描述性,并使用完整的单词(无缩写,但普遍接受的缩写除外)。
(2)宏以定义宏的文件为前缀。前缀为小写。例如,在 FreeRTOSConfig.h 中定义 configUSE_PREEMPTION。
(3)除前缀外,所有宏均使用大写字母书写,并使用下划线来分隔单词。
第 4 章 FreeRTOS的任务创建和删除
4.1 任务创建和删除的API函数(熟悉)
任务的创建和删除本质就是调用FreeRTOS的API函数,主要如下:
API函数
描述
xTaskCreate()
动态方式创建任务
xTaskCreateStatic()
静态方式创建任务
vTaskDelete()
删除任务
Ø 动态创建任务:任务的任务控制块以及任务的栈空间所需的内存,均由 FreeRTOS 从 FreeRTOS 管理的堆中分配。
Ø 静态创建任务:任务的任务控制块以及任务的栈空间所需的内存,需用户分配提供。
4.1.1 动态创建任务函数
1)函数说明
BaseType_t xTaskCreate
(
TaskFunction_t pxTaskCode,
const char * const pcName,
const configSTACK_DEPTH_TYPE usStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
TaskHandle_t * const pxCreatedTask
)
返回值说明如下:
Ø pdPASS:任务创建成功。
Ø errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY:任务创建失败。
2)动态创建任务步骤
(1)将宏configSUPPORT_DYNAMIC_ALLOCATION 配置为 1。
(2)定义函数入口参数。
(3)编写任务函数。
此函数创建的任务会立刻进入就绪态,由任务调度器调度运行。
3)动态创建任务函数内部实现
(1)申请堆栈内存&任务控制块内存。
(2)TCB结构体成员赋值。
(3)添加新任务到就绪列表中。
任务控制块结构体成员介绍。
typedef struct tskTaskControlBlock
{
volatile StackType_t * pxTopOfStack;
ListItem_t xStateListItem;
ListItem_t xEventListItem;
UBaseType_t uxPriority;
StackType_t * pxStack;
char pcTaskName[ configMAX_TASK_NAME_LEN ];
…
省略很多条件编译的成员
} tskTCB;
任务栈栈顶,在任务切换时的任务上下文保存、任务恢复息息相关。每个任务都有属于自己的任务控制块,类似身份证。
4.1.2 静态创建任务函数
1)函数说明
TaskHandle_t xTaskCreateStatic
(
TaskFunction_t pxTaskCode,
const char * const pcName,
const uint32_t ulStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
StackType_t * const puxStackBuffer,
StaticTask_t * const pxTaskBuffer
)
返回值如下:
Ø NULL:用户没有提供相应的内存,任务创建失败。
Ø 其他值:任务句柄,任务创建成功。
2)静态创建任务步骤
(1)将宏configSUPPORT_STATIC_ALLOCATION 配置为 1。
(2)定义空闲任务&定时器任务的任务堆栈及TCB。
(3)实现接口函数:
Ø vApplicationGetIdleTaskMemory()
Ø vApplicationGetTimerTaskMemory()(如果开启软件定时器)
(4)定义函数入口参数。
(5)编写任务函数。
此函数创建的任务会立刻进入就绪态,由任务调度器调度运行。
3)静态创建内部实现
(1)TCB结构体成员赋值
(2)添加新任务到就绪列表中
4.1.3 任务删除函数
1)函数说明
void vTaskDelete( TaskHandle_t xTaskToDelete )
参数说明:xTaskToDelete待删除任务的任务句柄。当传入的参数为NULL,则代表删除任务自身(当前正在运行的任务)。
该函数用于删除已被创建的任务,被删除的任务将从就绪态任务列表、阻塞态任务列表、挂起态任务列表和事件列表中移除。
需要注意的是,空闲任务会负责释放被删除任务中由系统分配的内存,但是由用户在任务删除前申请的内存,则需要由用户在任务被删除前提前释放,否则将导致内存泄露。
2)删除任务流程
(1)使用删除任务函数,需将宏INCLUDE_vTaskDelete 配置为 1
(2)入口参数输入需要删除的任务句柄(NULL代表删除本身)
3)内部实现过程
(1)获取所要删除任务的控制块
通过传入的任务句柄,判断所需要删除哪个任务,NULL代表删除自身。
(2)将被删除任务,移除所在列表
将该任务在所在列表中移除,包括:就绪、阻塞、挂起、事件等列表。
(3)判断所需要删除的任务
如果删除任务自身,需先添加到等待删除列表,内存释放将在空闲任务执行;如果删除其他任务,释放内存,任务数量--。
(4)更新下个任务的阻塞时间
更新下一个任务的阻塞超时时间,以防被删除的任务就是下一个阻塞超时的任务。
4.2 任务创建和删除实验(动态方法)(掌握)
动态创建,堆栈是在FreeRTOS管理的堆内存里,注意任务不要重复创建。
xxxxx_STACK_SIZE 128
uxTaskGetStackHighWaterMark()获取指定任务的任务栈的历史剩余最小值,根据这个结果适当调整启动任务的大小。
4.2.1 实验目标
学会 xTaskCreate( ) 和 vTaskDelete( ) 的使用:
Ø start_task:用来创建其他的三个任务。
Ø task1:实现LED1每500ms闪烁一次。
Ø task2:实现LED2每500ms闪烁一次。
Ø task3:判断按键KEY1是否按下,按下则删掉task1。
4.2.2 FreeRTOSConfig.h代码清单
#define configSUPPORT_DYNAMIC_ALLOCATION 1
4.2.3 freertos_demo.c代码清单
1)任务设置
#define START_TASK_PRIORITY 1
#define START_TASK_STACK_DEPTH 128
TaskHandle_t start_task_handler;
void Start_Task(void *pvParameters);
#define TASK1_PRIORITY 2
#define TASK1_STACK_DEPTH 128
TaskHandle_t task1_handler;
void Task1(void *pvParameters);
#define TASK2_PRIORITY 3
#define TASK2_STACK_DEPTH 128
TaskHandle_t task2_handler;
void Task2(void *pvParameters);
#define TASK3_PRIORITY 4
#define TASK3_STACK_DEPTH 128
TaskHandle_t task3_handler;
void Task3(void *pvParameters);
2)入口函数
void FreeRTOS_Start(void)
{
xTaskCreate((TaskFunction_t)Start_Task,
(char *)"Start_Task",
(configSTACK_DEPTH_TYPE)START_TASK_STACK_DEPTH,
(void *)NULL,
(UBaseType_t)START_TASK_PRIORITY,
(TaskHandle_t *)&start_task_handler);
vTaskStartScheduler();
}
3)启动任务函数
void Start_Task( void * pvParameters )
{
taskENTER_CRITICAL();
xTaskCreate((TaskFunction_t ) Task1,
(char * ) "Task1",
(configSTACK_DEPTH_TYPE ) TASK1_STACK_DEPTH,
(void * ) NULL,
(UBaseType_t ) TASK1_PRIORITY,
(TaskHandle_t * ) &task1_handler );
xTaskCreate((TaskFunction_t ) Task2,
(char * ) "Task2",
(configSTACK_DEPTH_TYPE ) TASK2_STACK_DEPTH,
(void * ) NULL,
(UBaseType_t ) TASK2_PRIORITY,
(TaskHandle_t * ) &task2_handler );
xTaskCreate((TaskFunction_t ) Task3,
(char * ) "Task2",
(configSTACK_DEPTH_TYPE ) TASK3_STACK_DEPTH,
(void * ) NULL,
(UBaseType_t ) TASK3_PRIORITY,
(TaskHandle_t * ) &task3_handler );
vTaskDelete(NULL);
taskEXIT_CRITICAL();
}
4)task1函数
void Task1(void * pvParameters)
{
while(1)
{
printf("task1运行....\r\n");
LED_Toggle(LED1_Pin);
vTaskDelay(500);
}
}
5)task2函数
void Task2(void * pvParameters)
{
while(1)
{
printf("task2运行....\r\n");
LED_Toggle(LED2_Pin);
vTaskDelay(500);
}
}
6)task3函数
void Task3(void * pvParameters)
{
uint8_t key = 0;
while(1)
{
printf("task3正在运行...\r\n");
key = Key_Detect();
if(key == KEY1_PRESS)
{
if(task1_handler != NULL)
{
printf("删除task1任务...\r\n");
vTaskDelete(task1_handler);
task1_handler = NULL;
}
}
vTaskDelay(10);
}
}
4.3 任务创建和删除实验(静态方法)(掌握)
4.3.1 实验目标
学会 xTaskCreateStatic( )和 vTaskDelete( ) 的使用:
Ø start_task:用来创建其他的三个任务。
Ø task1:实现LED1每500ms闪烁一次。
Ø task2:实现LED2每500ms闪烁一次。
Ø task3:判断按键KEY1是否按下,按下则删掉task1。
4.3.2 FreeRTOSConfig.h代码清单
#define configSUPPORT_STATIC_ALLOCATION 1
4.3.3 freertos_demo.c代码清单
1)任务设置
#define START_TASK_PRIO 1
#define START_TASK_STACK_SIZE 128
TaskHandle_t start_task_handler;
StackType_t start_task_stack[START_TASK_STACK_SIZE];
StaticTask_t start_task_tcb;
void start_task( void * pvParameters );
#define TASK1_PRIO 2
#define TASK1_STACK_SIZE 128
TaskHandle_t task1_handler;
StackType_t task1_stack[TASK1_STACK_SIZE];
StaticTask_t task1_tcb;
void task1( void * pvParameters );
#define TASK2_PRIO 3
#define TASK2_STACK_SIZE 128
TaskHandle_t task2_handler;
StackType_t task2_stack[TASK2_STACK_SIZE];
StaticTask_t task2_tcb;
void task2( void * pvParameters );
#define TASK3_PRIO 4
#define TASK3_STACK_SIZE 128
TaskHandle_t task3_handler;
StackType_t task3_stack[TASK3_STACK_SIZE];
StaticTask_t task3_tcb;
void task3( void * pvParameters );
2)空闲任务置及接口函数
StaticTask_t idle_task_tcb;
StackType_t idle_task_stack[configMINIMAL_STACK_SIZE];
StaticTask_t timer_task_tcb;
StackType_t timer_task_stack[configTIMER_TASK_STACK_DEPTH];
void vApplicationGetIdleTaskMemory( StaticTask_t ** ppxIdleTaskTCBBuffer,
StackType_t ** ppxIdleTaskStackBuffer,
uint32_t * pulIdleTaskStackSize )
{
* ppxIdleTaskTCBBuffer = &idle_task_tcb;
* ppxIdleTaskStackBuffer = idle_task_stack;
* pulIdleTaskStackSize = configMINIMAL_STACK_SIZE;
}
3)入口函数
void FreeRTOS_Start(void)
{
start_task_handler = xTaskCreateStatic((TaskFunction_t)Start_Task,
(char *)"Start_Task",
(uint32_t)START_TASK_STACK_DEPTH,
(void *)NULL,
(UBaseType_t)START_TASK_PRIORITY,
(StackType_t *)start_task_stack,
(StaticTask_t *)&start_task_tcb);
vTaskStartScheduler();
}
4)启动函数
void Start_Task(void *pvParameters)
{
taskENTER_CRITICAL();
task1_handler = xTaskCreateStatic((TaskFunction_t)Task1,
(char *)"Task1",
(uint32_t)TASK1_STACK_DEPTH,
(void *)NULL,
(UBaseType_t)TASK1_PRIORITY,
(StackType_t *)task1_stack,
(StaticTask_t *)&task1_tcb);
task2_handler = xTaskCreateStatic((TaskFunction_t)Task2,
(char *)"Task2",
(uint32_t)TASK2_STACK_DEPTH,
(void *)NULL,
(UBaseType_t)TASK2_PRIORITY,
(StackType_t *)task2_stack,
(StaticTask_t *)&task2_tcb);
task3_handler = xTaskCreateStatic((TaskFunction_t)Task3,
(char *)"Task3",
(uint32_t)TASK3_STACK_DEPTH,
(void *)NULL,
(UBaseType_t)TASK3_PRIORITY,
(StackType_t *)task3_stack,
(StaticTask_t *)&task3_tcb);
vTaskDelete(start_task_handler);
taskEXIT_CRITICAL();
}
5)task1函数
void Task1(void * pvParameters)
{
while(1)
{
printf("task1运行....\r\n");
LED_Toggle(LED1_Pin);
vTaskDelay(500);
}
}
6)task2函数
void Task2(void * pvParameters)
{
while(1)
{
printf("task2运行....\r\n");
LED_Toggle(LED2_Pin);
vTaskDelay(500);
}
}
7)task3函数
void Task3(void * pvParameters)
{
uint8_t key = 0;
while(1)
{
printf("task3正在运行...\r\n");
key = Key_Detect();
if(key == KEY1_PRESS)
{
if(task1_handler != NULL)
{
printf("删除task1任务...\r\n");
vTaskDelete(task1_handler);
task1_handler = NULL;
}
}
vTaskDelay(10);
}
}