目录
第1章 FreeRTOS 实时操作系统
1.1 认识实时操作系统
1.1.1 裸机的概念
1.1.2 操作系统的概念
1.2 操作系统的分类
1.3 常见的操作系统
1.4 认识实时操作系统
1.4.1 可剥夺型内核与不可剥夺型内核
1.4.2 嵌入式操作系统的作用
1.4.3 嵌入式操作系统的发展
1.4.4 相关术语 – 理解
1.5 FreeRTOS
1.5.1 FreeRTOS 介绍
1.5.2 FreeRTOS 移植
1.5.2.1 FreeRTOS 源码的获取
1.5.2.2 FreeRTOS 如何移植到自己的工程中
1.5.3 移植的简单说明
1.5.3.1 FreeRTOSConfig.h 的作用
1.5.3.2 FreeRTOSConfig.h 中一些关键宏定义的解释
1.5.3.3 Heap_4.c 文件的作用
1.5.3.4 Port.c 文件的作用
1.6 学习 FreeRTOS 相关的函数
1.7 任务创建
1.7.1 任务创建的两种方法
1.7.2 任务创建的区别
1.7.3 任务创建相关的函数
1.7.4 任务创建任务的步骤
1.7.5 任务创建任务实验现象
1.7.6 编写任务的要求和任务如何释放 CPU
1.8 任务删除
1.8.1 任务删除的接口函数
1.8.2 任务删除的编程
1.8.3 任务删除的实验现象
1.9 任务之间的 4 种运行状态
1.10 临界区保护
1.10.1 临界区作用
1.10.2 临界区相关的函数
1.10.3 临界区使用的注意事项
1.10.4 临界区特点
1.10.5 代码未使用临界区
1.10.6 代码未使用临界区现象
1.10.7 代码使用临界区
1.10.8 代码使用临界区的现象
1.10.9 官网的介绍
1.10.10 中断和临界区关系
1.11 挂起和解除挂起
1.11.1 挂起和解除挂起相关的函数
1.11.2 挂起和解除挂起的特点
1.11.3 挂起和解除挂起的编程
1.11.4 挂起和解除挂起现象
1.11.5 挂起和删除的区别
1.12 FreeRTOS 内核运行机制
1.13 任务调度原理
1.14 任务间的通信和同步方式
1.14.1 二值信号量
1.14.1.1 信号量的概念
1.14.1.2 信号量的分类
1.14.1.3 二值信号量的使用特点
1.14.1.5 二值信号量的编程
1.14.1.6 二值信号量的编程现象
1.14.1.7 二值信号量的使用条件和具体应用
1.14.1.8 二值信号量的好处
1.14.2 计数信号量
1.14.2.1 计数信号量和二值信号量的区别
1.14.2.2 计数信号量的接口函数
1.14.2.3 计数信号量的编程
1.14.2.4 计数信号量的现象
1.14.2.5 计数信号量的使用场景
1.14.3 互斥信号量
1.14.3.1 互斥信号量和二值信号量的区别
1.14.3.2 验证二值信号的优先级翻转问题的前提条件
1.14.3.3 验证二值信号的优先级翻转问题的编程
1.14.3.4 验证二值信号的优先级翻转问题现象
1.14.3.5 互斥信号量的接口函数
1.14.3.6 验证互斥信号量通过优先级继承解决优先级翻转的条件
1.14.3.7 验证互斥信号的优先级继承的编程
1.14.3.8 验证互斥信号的优先级继承的现象
1.14.3.9 互斥信号量的作用和使用条件
1.14.3.10 互斥信号量的应用
1.14.4 消息队列
1.14.4.1 消息队列的概念
1.14.4.2 消息队列和信号量的区别
1.14.4.3 消息队列的应用场景
1.14.4.4 消息队列的接口函数
1.14.4.5 消息队列的特点
1.14.4.6 消息队列的的编程
1.14.4.7 消息队列的的现象
1.14.5 事件 – 多对一
1.14.5.1 事件的概念
1.14.5.2 事件、队列和信号量的区别
1.14.5.3 事件的接口函数
1.14.5.4 事件的编程
1.14.5.5 事件的现象
1.14.5.6 事件的使用场景
1.14.6 任务间的通信和同步方式的总结
1.15 软件定时器
1.15.1 软件定时器的概念
1.15.2 软件定时器的更新频率
1.15.3 软件定时器的模式
1.15.4 软件定时器回调函数和栈空间大小
1.15.5 软件定时器的接口函数
1.15.6 软件定时器的编程
1.15.7 软件定时器的现象
1.16 相对延时和绝对延时
1.16.1 延时的接口函数
1.16.2 绝对延时的用法
1.16.3 绝对延时应用
1.16.4 两个延时的区别
1.16.5 系统的延时和 Delay_ms(空指令)的区别
1.17 5 种内存分配的方法介绍
1.18 任务堆栈空间大小的确定
1.19 任务优先级的确定和修改优先级
1.20 任务划分的确定
1.21 钩子函数
1.21.1 钩子函数概念
1.21.2 常用的钩子函数
1.22 空闲任务的作用
第1章 FreeRTOS 实时操作系统
1.1 认识实时操作系统
1.1.1 裸机的概念
早期嵌入式开发没有嵌入式操作系统的概念,直接操作裸机,在裸机上写程序,比如用 51 单片机基本就没有操作系统的概念。通常把程序设计为前后台系统,主要分为两部分:前台系统和后台系统。这样的程序包括一个死循环和若干个中断服务程序(应用程序是一个无限循环,循环中调用 API 函数完成所需的操作,这个大循环就叫做后台系统;中断服务程序用于处理系统的异步事件,也就是前台系统),前台是中断级,后台是任务级。
问题:这里就是平时我们裸机的运行结果,现在比如我在运行 task3,突然又想马上运行 task1,这怎么办?前后台程序就会让后面的任务执行之后,再去执行 task1,这样实时性受到影响。如果是裸机,要实现也可以,用中断,可是这样会让程序结构变得复杂,因为我想什么时候跳过就跳过,想什么时候执行就执行,所以固定的中断触发方式虽然也可以实现一些简单的跳转功能,但是当程序复杂之后,这样的裸机程序难以阅读和维护。但是引入操作系统的任务调度之后,就会让系统响应更具有实时性。
1.1.2 操作系统的概念
RTOS 全称为:Real Time OS,就是实时操作系统,强调的是:实时性。在实时操作系统中,我们可以把要实现的功能划分为多个任务,每个任务负责实现其中的一部分,每个任务都是一个很简单的程序,通常是一个死循环。 RTOS 操作系统:FreeRTOS,UCOS,RTX,RT-Thread,DJYOS 等。 RTOS 操作系统的核心内容在于:实时内核。
1.2 操作系统的分类
分时操作系统:一台计算机采用时间片轮转的方式同时为几个、几十个甚至几百个用户服务的一种操作系统,如:UNIX 系统就采用剥夺式动态优先的 CPU 调度,有力地支持分时操作。 -- 每个任务分配固定的时间去调度 -- 没有优先级的概念
实时操作系统:当外界事件或数据产生时,能够接受并以足够快的速度予以处理,其处理的结果又能在规定的时间之内来控制生产过程或对处理系统做出快速响应,调度一切可利用的资源完成实时任务,并控制所有实时任务协调一致运行的操作系统。提供及时响应和高可靠性是其主要特点。永远执行最高优先级,最高优先级任务只有 1 个。实时操作系统中各个任务的优先级不相同,高优先级可以打断低优先级。如:UCOS-II/III。
永远执行优先级最高的任务
不允许任务有相同的优先级
半实时操作系统:允许任务具有相同的优先级,高优先级打断低优先级。如:FreeRTOS。
永远执行优先级最高的任务
允许多个任务可以有相同的优先级
当两个任务优先级相同的时候,比如任务 1 和任务 2 的优先级一致,1ms 执行任务 1,1ms 执行任务2,具体是不是 1ms 切换,是根据时间片决定的
1.3 常见的操作系统
FreeRTOS,UCOS,RT-Thread
FreeRTOS 相对比较主流,freertos 和 ucos 是很类似的
RT-Thread 不太一样
1.4 认识实时操作系统
1.4.1 可剥夺型内核与不可剥夺型内核
操心系统的核心:内核。在多任务系统中,负责管理各个任务,并且负责任务间通信。
不可剥夺型:
异步事件还是由中断服务来处理。中断服务可以使一个高优先级的任务由挂起状态变为就绪状态。但中断服务以后控制权还是回到原来被中断了的那个任务,直到该任务主动放弃 CPU 的使用权时,那个高优先级的任务才能获得 CPU 的使用权。运行着的任务占有 CPU,而不必担心被别的任务抢占。
可剥夺型:--实时性比较高 -- 常用
当一个运行着的任务使一个比它优先级高的任务进入了就绪态,当前任务的 CPU 使用权就被剥夺了,或者说被挂起了,那个高优先级的任务立刻得到了 CPU 的控制权。如果是中断服务子程序使一个高优先级的任务进入就绪态,中断完成时,中断了的任务被挂起,优先级高的那个任务开始运行。
1.4.2 嵌入式操作系统的作用
操作系统是个软件(管理底层硬件,并且上层应用提供接口)
嵌入式(实时)操作系统特点:用于嵌入式设备的操作系统,具有通用操作系统的基本特点,又具有系统实时性、硬件的相关依赖性、软件固态化以及应用的专用性等特点
评判嵌入式(实时)操作系统的重要指标:实时性(中断响应时间、任务切换时间等)、尺寸(可裁剪性 )、可扩展性(内核、中间件)
1.4.3 嵌入式操作系统的发展
1.4.4 相关术语 – 理解
任务
1.实现某种功能的代码(该程序认为CPU 完全只属于该程序自己)
2.每个任务都是 1 个无限循环程序。
难点:如何划分任务 -- 各个任务要 独立
内核管理任务。
任务切换
调度器实现 CPU 从一个任务跳转到另外一个任务。(内核提供算法实现)
难点:任务切换的算法以及栈空间的使用(汇编来写)
任务优先级
任务被调度时,执行的顺序。
数字越大,优先级越高(FreeRTOS)
系统栈空间
给操作系统(Freertos)的空间
任务栈空间是在任务创建的时候从 FreeRTOSConfig.h 文件中定义的 heap 空间中申请的#define configTOTAL_HEAP_SIZE( ( size_t ) ( 17 * 1024 ) )占用 64K 的 RAM 空间
任务栈空间
当任务切出时,它的执行环境会被保存在该任务的栈空间中,这样当任务再次运行时,就能从堆栈中正确的恢复上次的运行环境。任务栈空间大小不同。
任务越复杂,需要的堆栈空间就越大,而一个系统能运行多少个任务,取决于分配的系统栈空间,如何确定任务栈空间需要分配的大小:freertos 中有接口函数可以查询任务的栈空间剩余多少,先分配大一些,查询一下使用情况,根据使用情况留够余量,缩小回去
资源
任务所占用的实体(硬件资源:LED 灯软件资源:rs485.rx_buff[] -- 数组,printf)
共享资源
可以被 1 个以上的任务使用的资源。
临界区
临界区的代码执行的时候不会被其他任务打断,即使优先级低(中断可以配置是否打断临界区)
底层硬件通信时序 SPI 、IIC、DHT11 等,需要使用
钩子函数
Hook 函数: 因为内核中相关的函数尽量不修改,我想再执行内核内部一些函数时,添加一些代码,使用该函数的钩子函数。(内核底层给外部开发者提供一个接口)
内核规定好的,有些函数具有钩子函数。
栈溢出钩子函数
时间片钩子函数
内存申请失败钩子函数
1.5 FreeRTOS
1.5.1 FreeRTOS 介绍
⚫ FreeRTOS 即免费的,RTOS 全称是 Real Time Operating System,中文就是实时操作系 统。注意,RTOS 不是指某一个确定的系统,而是指一类系统。比如 uC/OS,FreeRTOS,RTX, RT-Thread 等这些都是 RTOS 类操作系统。
⚫ 操作系统允许多个任务同时运行,这个叫做多任务。实际上,一个处理器核心在某一时刻只能运行一个任务。操作系统中任务调度器的责任就是决定在某一时刻究竟运行哪个任务。任务调度在各个任务之间的切换非常快,就给人们造成了同一时刻有多个任务同时运行的错觉。
⚫ FreeRTOS 是 RTOS 系统的一种,FreeRTOS 十分的小巧,可以在资源有限的微控制器中运行,当然,FreeRTOS 不仅局限于在微控制器中使用。但从文件数量上来看 FreeRTOS 要比 uC/OSII 和uC/OSIII 小的多。
⚫ 选择 FreeRTOS:FreeRTOS 是免费的,学习 RTOS 操作系统的话 uC/OS 是首选,但要做产品的话,免费的 FreeRTOS 操作系统就是个不错的选择。许多半导体厂商产品的 SDK(Software Development Kit—软件开发工具包) 包就使用 FreeRTOS 作为其操作系统,尤其是 WIFI、蓝牙这些带协议栈的芯片或模块。简单,因为 FreeRTOS 的文件数量很少。
⚫ FreeRTOS 操作系统特点:-- 调度方法。
FreeRTOS 的内核支持抢占式,合作式和时间片调度。
抢占式:高优先级任务抢占低优先级的任务,实时操作系统
时间片调度:如果两个任务优先级一样,每个任务各执行 1ms,在各个任务之间快速切换--具体时间是由时间片决定的
合作式:任务必须主动让出 CPU,调度器才会切换 -- 用到少,几乎不用
⚫ 提供了一个用于低功耗的 Tickless 模式。
⚫ 系统的组件在创建时可以选择动态或者静态的 RAM,比如任务、消息队列、信号量、软件定时器等等。
⚫ FreeRTOS-MPU 支持 Corex-M 系列中的 MPU 单元,如 STM32F429。
⚫ FreeRTOS 系统简单、小巧、易用,通常情况下内核占用 4k-9k 字节的空间。
⚫ 高可移植性,代码主要 C 语言编写。
⚫ 高效的软件定时器。
⚫ 强大的跟踪执行功能。
⚫ 堆栈溢出检测功能。
⚫ 任务数量不限。
⚫ 任务优先级不限。
操作系统支持不同任务使用相同的优先级,最好不同任务分配不同的优先级
1.5.2 FreeRTOS 移植
1.5.2.1 FreeRTOS 源码的获取
1. 从官网获取
源文件获取:FreeRTOS 官方下载
FreeRTOS™ - FreeRTOS™
1.5.2.2 FreeRTOS 如何移植到自己的工程中
1. 上网搜博客
2. 照着正点原子、野火、韦东山的文档来
3. HAL 可以直接生成带 FreeRTOS 的工程
1.5.3 移植的简单说明
1.5.3.1 FreeRTOSConfig.h 的作用
FreeRTOSConfig.h 文件是 FreeRTOS 的工程配置文件,因为 FreeRTOS 是可以裁剪的实时操作内核,应用于不同的处理器平台, 用户可以通过修改这个 FreeRTOS 内核的配置头文件来裁剪 FreeRTOS 的功能。
相应宏的打开和关闭,就叫做内核的裁剪
1.5.3.2 FreeRTOSConfig.h 中一些关键宏定义的解释
下面的宏定,重点强调三个中断以及系统运行速率以及计数宏定义。
比如:消息队列,我们不使用,就把宏关闭,那么在编译的时候,就不会编译相关的内容,整个文件就会被裁剪掉消息队列的功能
告诉操作系统 CPU 的频率
内核的钩子函数
系统的节拍,操作系统的最小时间单元,将 1s 分成 1000 份,1ms 是一个节拍
分配给操作系统的总共的栈空间大小。 -- 从单片机 64K 的 RAM 中分配
任务分配优先级 0--configMAX_PRIORITIES -- 这个宏可以更改尽量每个任务使用单独的优先级
栈区溢出检测函数 如果宏为 1 需要编写钩子函数
1.5.3.3 Heap_4.c 文件的作用
内存分配方案:heap_4 将相邻的空闲内存块合并成一个更大的块,这将内存碎片的风险降到最低,利用率更高。
FreeRTOS 堆内存管理 - FreeRTOS™
Heap_4 相对其他的效率和空间利用率比较高,所以选择 heap_4
1.5.3.4 Port.c 文件的作用
内核底层的接口文件,一般由芯片厂家提供
里面有重写的 PendSV SVC Systick 的中断服务函数,所以要把原有工程中的函数屏蔽掉。
有操作系统之后:不能使用滴答定时器,把滴答定时器交给操作系统作为系统节拍
1.6 学习 FreeRTOS 相关的函数
参考官网 创建任务 - FreeRTOS™
参考官方配套的书籍 FreeRTOS 文档 - FreeRTOS™
第三方书籍
上网搜
1.7 任务创建
1.7.1 任务创建的两种方法
静态创建
动态创建 -- 相对用的比较多
1.7.2 任务创建的区别
区别在于任务的栈空间:
静态创建,需要开发者自己定义大数组,作为该任务的栈空间使用
动态创建,只管告诉需要多大,让操作系统自己创建
1.7.3 任务创建相关的函数
静态任务创建函数 xTaskCreateStatic()
动态任务创建函数 xTaskCreate()
官网创建任务相关的函数 创建任务 - FreeRTOS™
1.7.4 任务创建任务的步骤
/*
创建任务
1.定义任务的句柄
TaskHandle_t xLED1_Task_Handle = NULL;
2.指定任务函数
void vLED1_TaskCode( void * pvParameters )
{
for( ;; )
{
}
}
3.声明任务函数,在main.h
void vLED1_TaskCode( void * pvParameters );
4.通过创建任务函数创建
BaseType_t xReturned;
xReturned = xTaskCreate(vLED1_TaskCode, "vLED1_Task", 128, NULL, 1, &xLED1_Task_Handle );
if(xReturned==pdPASS)
{
printf("vLED1_Task创建成功\r\n");
}
vTaskDelay 会主动释放CPU
Delay_ms 不会释放CPU,想一直占用
*/
1.7.5 任务创建任务实验现象
LED1 以 1S 的周期翻转
LED2 以 2S 的周期翻转
1.7.6 编写任务的要求和任务如何释放 CPU
注意:每个任务都要有主动释放 CPU 的动作
也就是要使用系统的延时或者 等待信号量 等待事件 等待消息队列
1.8 任务删除
1.8.1 任务删除的接口函数
任务的删除函数 vTaskDelete
官网创建任务相关的函数 vTaskDelete - FreeRTOS™
1.8.2 任务删除的编程
删除任务
case 1: //按键 1 短按并松手
if(xLED1_Task_Handle!=NULL)
{
vTaskDelete(xLED1_Task_Handle);
xLED1_Task_Handle=NULL;
}
break;
case 2: //按键 2 短按并松手
if(xLED2_Task_Handle!=NULL)
{
vTaskDelete(xLED2_Task_Handle);
xLED2_Task_Handle=NULL;
}
break;
1.8.3 任务删除的实验现象
按键 1 按下,LED1 不再闪烁
按键 1 按下,LED2 不再闪烁
1.9 任务之间的 4 种运行状态
状态之间的切换
就绪态:新创建的任务处于就绪态,可以有多个任务处于就绪态,调度器会调度优先级最高的处于就绪态的任务去执行
挂起态:任务挂起,暂时不执行,只能等待解除挂起 -- 解挂之后任务的状态是就绪态
运行态:有且只有一个任务处于运行态 -- 单核处理器
阻塞态:运行的任务遇到系统的延时(vTaskDelay 包括相对和绝对延时),等待信号量,运行的任务进入阻塞态
1.10 临界区保护
1.10.1 临界区作用
如果有一部分代码,在执行的过程中,不希望被打断,就放入临界区
DHT11 SPI IIC 时序类的一般不能被打断 放入临界区中
临界资源的保护 比如:printf 或者 W25Q64
1.10.2 临界区相关的函数
taskENTER_CRITICAL(); 进入临界区
taskEXIT_CRITICAL(); 退出临界区
官网相关的函数 taskENTER_CRITICAL(), taskEXIT_CRITICAL() - FreeRTOS™
1.10.3 临界区使用的注意事项
进入临界区和退出临界区要成对使用
关键代码使用,不要大片使用
1.10.4 临界区特点
处于临界区的代码,不管其他任务优先级多高,都不能打断临界区,前提是临界区中未调用释放 CPU
中断是否可以打断临界区,取决于配置文件的配置。跟下面的宏有关。
1.10.5 代码未使用临界区
没有临界区
//任务必须是不退出的循环
//LED1 的任务函数
void vLED1_TaskCode( void * pvParameters )
{
for( ;; )
{
printf("\r\n-------------LED1 任务正在执行-------------\r\n");
vTaskDelay(10);
}
}
//LED2 的任务函数
void vLED2_TaskCode( void * pvParameters )
{
while(1)
{
printf("\r\n!!!!!!!!!!!!!LED2 任务正在执行!!!!!!!!!!!!!\r\n");
vTaskDelay(15);
}
}
//KEY 的任务函数
void vKEY_TaskCode( void * pvParameters )
{
while(1)
{
printf("\r\n%%%%%%%%%%%%%%KEY 任务正在执行%%%%%%%%%%%%%%\r\n");
vTaskDelay(5);
}
}
1.10.6 代码未使用临界区现象
1.10.7 代码使用临界区
使用临界区
//任务必须是不退出的循环
//LED1 的任务函数
void vLED1_TaskCode( void * pvParameters )
{
for( ;; )
{
taskENTER_CRITICAL();
printf("\r\n-------------LED1 任务正在执行-------------\r\n");
taskEXIT_CRITICAL();
vTaskDelay(10);
}
}
//LED2 的任务函数
void vLED2_TaskCode( void * pvParameters )
{
while(1)
{
taskENTER_CRITICAL();
printf("\r\n!!!!!!!!!!!!!LED2 任务正在执行!!!!!!!!!!!!!\r\n");
taskEXIT_CRITICAL();
vTaskDelay(15);
}
}
//KEY 的任务函数
void vKEY_TaskCode( void * pvParameters )
{
while(1)
{
taskENTER_CRITICAL();
printf("\r\n%%%%%%%%%%%%%%KEY 任务正在执行%%%%%%%%%%%%%%\r\n");
taskEXIT_CRITICAL();
vTaskDelay(5);
}
}
1.10.8 代码使用临界区的现象
1.10.9 官网的介绍
在 ARM Cortex-M Core 上运行 RTOS - FreeRTOS™
链接是讲解中断和临界区的关系
1.10.10 中断和临界区关系
FreeRTOS 官方建议选择中断分组都配置成抢占优先级,并且最好配置成中断不能打断临界区。
LED1 任务中,进入临界区有故意的 60s 的延时,在延时过程中,按下按键 1、2、3,发现只有按键 2 可以进入中断,其他无法进入中断。
抢占优先级<B 的可以打断临界区,抢占优先级>=B 的,无法打断临界区
1.11 挂起和解除挂起
1.11.1 挂起和解除挂起相关的函数
vTaskSuspend() 挂起某个任务
vTaskSuspendAll() 挂起所有任务
vTaskResume() 解挂某个任务
xTaskResumeFromISR() 在中断中解挂任务
xTaskResumeAll() 解挂所有任务
官网先关的函数vTaskSuspend() - FreeRTOS™
1.11.2 挂起和解除挂起的特点
处于挂起态的任务,不参与系统调度,只有等待解除挂起,再次参数调度。
解除挂起的任务,不管之前处于什么状态,只能回到就绪态。
1.11.3 挂起和解除挂起的编程
挂起和解挂
//任务必须是不退出的循环
//LED1 的任务函数
void vLED1_TaskCode( void * pvParameters )
{
for( ;; )
{
LED1_TOGGLE();
vTaskDelay(1000);
}
}
//LED2 的任务函数
void vLED2_TaskCode( void * pvParameters )
{
while(1)
{
LED2_TOGGLE();
vTaskDelay(2000);
}
}
//KEY 的任务函数
void vKEY_TaskCode( void * pvParameters )
{
while(1)
{
KEY_Handle();
vTaskDelay(10);
}
}
Key.c
case 3: //按键 3 短按并松手
vTaskSuspend(xLED1_Task_Handle);
break;
case 4: //按键 4 短按并松手
vTaskResume(xLED1_Task_Handle);
break;
1.11.4 挂起和解除挂起现象
未按下按键时候,LED1 和 LED2 依次按照自己的周期翻转。
按键 3 按下,LED1 不再翻转,任务被挂起
按键 4 按下,LED1 恢复翻转,任务解除挂起,参与任务调度
1.11.5 挂起和删除的区别
挂起的任务可以恢复
删除的任务无法恢复
1.12 FreeRTOS 内核运行机制
任务链表
FreeRTOS 中的链表作用与链表结构,源码 list.c 和野火第 6 章手册。
就绪链表 挂起链表 阻塞链表
就绪链表:根据任务的优先级进行插入
任务控制块 TCB:任务句柄就是指向任务控制块
任务控制块的作用,源码 task.c 和野火第 7 章手册。
就绪链表、阻塞链表等与任务控制块的关系以及四种运行状态有关
创建任务的函数:创建空闲任务,空闲任务的作用是回收资源,某个任务被删除之后,由空闲任务负责回收资源。有其他任务处于就绪态,就执行其他任务,如果没有任务处于就绪态,就执行空闲任务。
任务调度:
1.13 任务调度原理
1、正在运行的任务,遇到系统的延时,信号量的阻塞或者被挂起,需要执行任务调度,所谓的任务调度就是查询就绪列表中优先级最高的任务去执行,如果就绪列表中没有就绪任务,那就执行空闲任务
2、根据设置的节拍(系统定时器完成),每个节拍都要去查询就绪列表中是否有比当前运行任务优先级更高的任务就绪了,如果有,就打断当前正在执行的任务变成就绪态,去执行就绪列表中优先级最高的任务。
3、如果有两个处于就绪态的任务,并且优先级一样,并且都是就绪中的最高优先级,根据时间片轮转方式调度。任务 1 执行 1 个时间片(系统节拍),任务 2 执行 1 个时间片(系统节拍)。
4、加载 PENSV 中断服务函数,来实现任务切换。
1.14 任务间的通信和同步方式
1.14.1 二值信号量
1.14.1.1 信号量的概念
1.14.1.2 信号量的分类
信号量:分为二值信号量 计数信号量 互斥信号量
1.14.1.3 二值信号量的使用特点
1.14.1.4 二值信号量的接口函数
官网的函数 信号量 - FreeRTOS™ -- 信号量和互斥锁里面
创建二值信号量 xSemaphoreCreateBinary()
信号量删除函数 vSemaphoreDelete()
任务中释放信号量 xSemaphoreGive()(任务)
中断中释放信号量 xSemaphoreGiveFromISR()(中断)
任务中获取信号量 xSemaphoreTake()(任务)
中断中获取信号量 xSemaphoreTakeFromISR()(中断)
1.14.1.5 二值信号量的编程
/*
创建二值信号量
1.包含头文件
#include "semphr.h"
2.创建二值信号量的句柄
SemaphoreHandle_t xSemaphore = NULL;
3.创建二值信号量
xSemaphore = xSemaphoreCreateBinary();
4.在必要的地方声明二值信号量句柄
extern SemaphoreHandle_t xSemaphore;
5.在指定地方释放信号量 -- 实验是在按键1中释放
case 1: //按键1短按并松手
xSemaphoreGive(xSemaphore); //释放信号量
break;
6.在指定的地方等待信号量
void vBEEP_TaskCode( void * pvParameters )
{
BaseType_t xReturned;
while(1)
{
xReturned=xSemaphoreTake(xSemaphore,portMAX_DELAY);
}
}
7.注意等待的时间的三种写法
xReturned=xSemaphoreTake(xSemaphore,0); 不等,等不到就打印失败
xReturned=xSemaphoreTake(xSemaphore,1000); 等待指定时间,等不到打印失败,等到了打印成功
xReturned=xSemaphoreTake(xSemaphore,portMAX_DELAY); 一直等,直到等到信号量,打印成功
*/
1.14.1.6 二值信号量的编程现象
xReturned=xSemaphoreTake(xSemaphore,0); 不等,等不到就打印失败
xReturned=xSemaphoreTake(xSemaphore,1000); 等待指定时间,等不到打印失败,等到了打印成功
xReturned=xSemaphoreTake(xSemaphore,portMAX_DELAY); 一直等,直到等到信号量,打印成功
1.14.1.7 二值信号量的使用条件和具体应用
二值信号量:侧重于同步功能,用于任务和任务或者任务和中断的同步。
任务和任务之间的同步:
传感器采集任务,采集到传感器的数据,并且数据有变化,释放信号量
屏幕的显示任务,当传感器采集任务释放信号量,再进行数据的更新
任务和中断之间的同步:
KQM6600 的中断任务:在空闲中断中,释放信号量
KQM6600 的解析任务:在解析任务中等待信号量
1.14.1.8 二值信号量的好处
使用信号量的好处:实现任务和任务同步或者中断和任务的同步,响应比较及时,比较节省 CPU。
以前使用全局变量,不管是否满足条件,都需要一直去判断,使用信号量之后,直接等待信号量就可以,信号量不到来,不用管该任务。
1.14.2 计数信号量
1.14.2.1 计数信号量和二值信号量的区别
二值信号量:只有两个状态,信号量只有 1 个,它的状态就是有和无
计数信号量:信号量不止 1 个,有多个。
1.14.2.2 计数信号量的接口函数
创建计数信号量 xSemaphoreCreateCounting()
信号量删除函数 vSemaphoreDelete()
任务中释放信号量 xSemaphoreGive()(任务)
中断中释放信号量 xSemaphoreGiveFromISR()(中断)
任务中获取信号量 xSemaphoreTake()(任务)
中断中获取信号量 xSemaphoreTakeFromISR()(中断)
官网的函数介绍 xSemaphoreTake - FreeRTOS™-- 信号量和互斥锁里面
1.14.2.3 计数信号量的编程
/*
创建计数信号量
1.包含头文件
#include "semphr.h"
2.创建计数信号量的句柄
SemaphoreHandle_t xSemaphore_Count = NULL;
3.创建计数信号量
xSemaphore_Count = xSemaphoreCreateCounting(5,0);
4.在必要的地方声明计数信号量句柄
extern SemaphoreHandle_t xSemaphore_Count;
5.在指定地方释放信号量 -- 实验是在按键2中释放
case 2: //按键2短按并松手
xReturned=xSemaphoreGive(xSemaphore_Count); //释放信号量
6.在指定的地方等待信号量 -- 实验是按键3中
case 3: //按键3短按并松手
xReturned=xSemaphoreTake(xSemaphore_Count,0);
*/
1.14.2.4 计数信号量的现象
按键 2 按下,释放信号量,连续释放 5 次,会显示释放失败。
按键 3 按下,申请信号量,连续申请 5 次,会显示申请失败。
1.14.2.5 计数信号量的使用场景
停车场管理系统。
1.14.3 互斥信号量
1.14.3.1 互斥信号量和二值信号量的区别
1.14.3.2 验证二值信号的优先级翻转问题的前提条件
前提条件:高优先级和低优先级的任务,要申请相同的二值信号量;中优先级的任务和信号量没有关系
(1)L 任务执行,同时申请信号量成功
(2)H 任务因为优先级高,打断 L 任务(H 变成运行态,L 变成就绪态),执行 H 任务过程中,申请信号量,但是信号量被 L 任务占用,所以申请失败,然后 H 任务阻塞
(3)继续执行就绪的 L 任务,M 任务因为优先级高于 L 任务,会打断 L 任务(M 变成运行态,L 变成就绪态),执行 M 任务
(4)M 任务执行完毕,继续执行就绪的 L 任务,在 L 任务释放信号量之前,M 任务可以多次打断 L 任务
(5)L 任务执行完毕,释放信号量,H 任务申请信号量成功,继续执行 H 任务
1.14.3.3 验证二值信号的优先级翻转问题的编程
/*
验证二值信号量存在优先级的翻转
创建二值信号量
1.包含头文件
#include "semphr.h"
2.创建二值信号量的句柄
SemaphoreHandle_t xSemaphore = NULL;
3.创建二值信号量
xSemaphore = xSemaphoreCreateBinary();
4.在必要的地方声明二值信号量句柄
extern SemaphoreHandle_t xSemaphore;
5.编写高优先级任务,先申请信号量,执行完毕释放信号量
//KEY的任务函数
void vKEY_TaskCode( void * pvParameters )
{
BaseType_t xReturned;
while(1)
{
printf("高优先级任务准备申请信号量\r\n");
xReturned=xSemaphoreTake(xSemaphore,portMAX_DELAY);
if(xReturned == pdTRUE)
{
printf("高优先级任务申请信号量成功\r\n");
}
else
{
printf("高优先级任务申请信号量失败\r\n");
}
printf("高优先级任务准备释放信号量\r\n");
xReturned=xSemaphoreGive(xSemaphore); //释放信号量
vTaskDelay(1000);
}
}
6.编写中优先级任务,和信号量没有关系
//LED2的任务函数
void vLED2_TaskCode( void * pvParameters )
{
while(1)
{
printf("中优先级任务正在执行\r\n");
vTaskDelay(1000);
}
}
7.编写低优先级任务,先申请信号量,执行完毕释放信号量
void vLED1_TaskCode( void * pvParameters )
{
BaseType_t xReturned;
for( ;; )
{
printf("低优先级任务准备申请信号量\r\n");
xReturned=xSemaphoreTake(xSemaphore,portMAX_DELAY);
if(xReturned == pdTRUE)
{
printf("低优先级任务申请信号量成功\r\n");
}
else
{
printf("低优先级任务申请信号量失败\r\n");
}
Delay_ms(3000); //故意维持占用信号量的时间
printf("低优先级任务准备释放信号量\r\n");
xReturned=xSemaphoreGive(xSemaphore); //释放信号量
}
}
8.在main函数中,先释放1个信号量
xSemaphoreGive(xSemaphore); //释放信号量,让高低优先级任务申请
*/
1.14.3.4 验证二值信号的优先级翻转问题现象
1.14.3.5 互斥信号量的接口函数
官网的接口函数 xSemaphoreCreateMutex - FreeRTOS™ 互斥信号量接口函数
互斥量创建函数 xSemaphoreCreateMutex()
互斥量删除函数 vSemaphoreDelete()
互斥量获取函数 xSemaphoreTake()
互斥量释放函数 xSemaphoreGive()
1.14.3.6 验证互斥信号量通过优先级继承解决优先级翻转的条件
当低优先级的任务执行的时候,高优先级暂时把自己的优先级继承给低优先级,那么中优先级就无法打断低优先级,能够让低优先级尽快执行完毕,释放信号量,让高优先级任务使用信号量,这叫做优先级的继承
(1)L 任务申请信号量成功,正在执行
(2)H 任务抢占了 L 任务,但是 H 任务申请信号量不成功,因为信号量被 L 任务占用,H 任务把自己的优先级继承给 L 任务,那么 L 任务的优先级此时和 H 任务优先级相同
(3)M 任务因为优先级低于 H 任务,所以也无法打断 L 任务
(4)L 任务尽快执行完毕,释放信号量,然后 H 任务申请信号量成功,继续执行,执行完毕,释放信号量
(5)再执行处于就绪态的 M 任务,执行完毕
(6)再执行就绪态的其他任务
1.14.3.7 验证互斥信号的优先级继承的编程
/*
验证互斥信号量优先级的继承
创建互斥信号量
1.包含头文件
#include "semphr.h"
2.创建互斥信号量的句柄
SemaphoreHandle_t xSemaphore_Mutex = NULL; //创建互斥信号量句柄
3.创建互斥信号量
xSemaphore_Mutex = xSemaphoreCreateMutex();
4.编写高优先级任务,先申请信号量,执行完毕释放信号量
//KEY的任务函数
void vKEY_TaskCode( void * pvParameters )
{
BaseType_t xReturned;
while(1)
{
printf("高优先级任务准备申请信号量\r\n");
xReturned=xSemaphoreTake(xSemaphore_Mutex,portMAX_DELAY);
if(xReturned == pdTRUE)
{
printf("高优先级任务申请信号量成功\r\n");
}
else
{
printf("高优先级任务申请信号量失败\r\n");
}
printf("高优先级任务准备释放信号量\r\n");
xReturned=xSemaphoreGive(xSemaphore_Mutex); //释放信号量
vTaskDelay(1000);
}
}
5.编写中优先级任务,和信号量没有关系
//LED2的任务函数
void vLED2_TaskCode( void * pvParameters )
{
while(1)
{
printf("中优先级任务正在执行\r\n");
vTaskDelay(1000);
}
}
6.编写低优先级任务,先申请信号量,执行完毕释放信号量
void vLED1_TaskCode( void * pvParameters )
{
BaseType_t xReturned;
for( ;; )
{
printf("低优先级任务准备申请信号量\r\n");
xReturned=xSemaphoreTake(xSemaphore_Mutex,portMAX_DELAY);
if(xReturned == pdTRUE)
{
printf("低优先级任务申请信号量成功\r\n");
}
else
{
printf("低优先级任务申请信号量失败\r\n");
}
Delay_ms(3000); //故意维持占用信号量的时间
printf("低优先级任务准备释放信号量\r\n");
xReturned=xSemaphoreGive(xSemaphore_Mutex); //释放信号量
}
}
8.在main函数中,先释放1个信号量
xSemaphoreGive(xSemaphore_Mutex); //释放信号量,让高低优先级任务申请
*/
1.14.3.8 验证互斥信号的优先级继承的现象
1.14.3.9 互斥信号量的作用和使用条件
1. 互斥信号量解决普通二值信号量优先级翻转的问题,可以实现临界资源的访问
互斥访问:比如说 printf 函数,有两个任务都调用,准备调用之前申请信号量,使用完毕释放信号量。
2. 互斥信号量通过优先级的继承,来解决普通二值信号量优先级翻转的问题。
1.14.3.10 互斥信号量的应用
printf 函数或者很多地方要调用 W25Q64 的读写。
1.14.4 消息队列
1.14.4.1 消息队列的概念
1.14.4.2 消息队列和信号量的区别
信号量:一般用作同步或者临界资源的互斥访问
消息队列:不仅可以同步,还可以发送消息
1.14.4.3 消息队列的应用场景
任务和任务之间或者任务和中断之间。
消息队列:可以用来传递变量(u8,u16,u32),结构体,数组
1.14.4.4 消息队列的接口函数
官网的接口函数 xQueueSendToBack - FreeRTOS™ 队列
消息队列创建函数 xQueueCreate()
消息队列静态创建函数 xQueueCreateStatic()
消息队列删除函数 vQueueDelete()
消息队列发送函数 xQueueSend()
消息队列发送函数 xQueueSendToBack() -- 老版本
消息队列发送函数 xQueueSendFromISR() 中断中使用
消息队列发送函数 xQueueSendToBackFromISR() 中断中使用 -- 老版本
消息队列接收函数 xQueueReceive()与 xQueuePeek()
xQueueReceive 接收消息之后 删除
xQueuePeek 接收消息之后不删除
消息队列接收函数 xQueueReceiveFromISR()与 xQueuePeekFromISR()
1.14.4.5 消息队列的特点
FIFO -- 先进先出
传递的是值,不是地址
1.14.4.6 消息队列的的编程
/*
创建消息队列
1.包含头文件
#include "semphr.h"
2.创建消息队列的句柄
//句柄就是类似于身份证
QueueHandle_t xQueue=NULL; //创建消息队列的句柄
3.创建消息队列
xQueue = xQueueCreate(3,1);
4.在按键任务中发送消息
//按键处理函数
void KEY_Handle(void)
{
uint8_t Message1=0x31;
uint8_t Message2=0x32;
uint8_t Message3=0x33;
uint8_t Message4=0x34;
BaseType_t xReturned;
uint8_t KEY_State=0;
KEY_State=KEY_SCAN(); //调用按键扫描函数
switch(KEY_State)
{
case 0: //无按键按下
break;
case 1: //按键1短按并松手
xReturned=xQueueSend(xQueue,&Message1,portMAX_DELAY);
if(xReturned== pdTRUE)
{
printf("按键1按下,发送消息0x31成功\r\n");
}
break;
case 2: //按键2短按并松手
xReturned=xQueueSend(xQueue,&Message2,portMAX_DELAY);
if(xReturned== pdTRUE)
{
printf("按键2按下,发送消息0x32成功\r\n");
}
break;
case 3: //按键3短按并松手
xReturned=xQueueSend(xQueue,&Message3,portMAX_DELAY);
if(xReturned== pdTRUE)
{
printf("按键3按下,发送消息0x33成功\r\n");
}
break;
case 4: //按键4短按并松手
xReturned=xQueueSend(xQueue,&Message4,portMAX_DELAY);
if(xReturned== pdTRUE)
{
printf("按键4按下,发送消息0x34成功\r\n");
}
break;
default:
break;
}
}
5.在接收任务中接收消息
//LED2的任务函数
void vLED2_TaskCode( void * pvParameters )
{
BaseType_t xReturned;
uint8_t Rec_Message=0;
while(1)
{
xReturned=xQueueReceive(xQueue,&Rec_Message,portMAX_DELAY);
if(xReturned==pdTRUE)
{
printf("获取消息成功,收到的消息为%x\r\n",Rec_Message);
Rec_Message=0;
}
}
}
6.按键按下,查看串口的结果
*/
1.14.4.7 消息队列的的现象
1.14.5 事件 – 多对一
1.14.5.1 事件的概念
本质上就是全局变量
1.14.5.2 事件、队列和信号量的区别
信号量:能够实现任务和任务或者任务和中断的同步,大部分发生在两者之间
事件:能够实现任务和任务或者任务和中断的同步,可以发生在多对一
1.14.5.3 事件的接口函数
官网的接口函数 事件组 - FreeRTOS™ -- 事件组相关的接口函数
事件创建函数 xEventGroupCreate()
事件删除函数 vEventGroupDelete()
事件组任务中置位函数 xEventGroupSetBits()(任务)
事件组中断置位函数 xEventGroupSetBitsFromISR()(中断)
等待事件函数 xEventGroupWaitBits()
事件组清除函数 xEventGroupClearBits()
事件组在中断中清除函数 xEventGroupClearBitsFromISR()
1.14.5.4 事件的编程
/*
事件的使用
1.创建事件的句柄
EventGroupHandle_t xCreatedEventGroup; //创建事件句柄
2.main函数创建事件
//创建事件
xCreatedEventGroup = xEventGroupCreate();
3.在main.h文件中声明,包含#include "event_groups.h"
extern EventGroupHandle_t xCreatedEventGroup;
4.创建main.h必要的要设置位的宏
#define BIT_0 ( 1 << 0 )
#define BIT_4 ( 1 << 4 )
5.在按键中发送事件
case 1: //按键1短按并松手
printf("按键1按下,BIT_0置1\r\n");
xEventGroupSetBits(xCreatedEventGroup,BIT_0);
break;
case 2: //按键2短按并松手
printf("按键2按下,BIT_4置1\r\n");
xEventGroupSetBits(xCreatedEventGroup,BIT_4);
break;
6.在解析任务中,等待事件
//LED2的任务函数
void vLED2_TaskCode( void * pvParameters )
{
EventBits_t uxBits;
uint8_t Rec_Message=0;
while(1)
{
uxBits=xEventGroupWaitBits(xCreatedEventGroup,BIT_0|BIT_4,pdTRUE,pdTRUE,portMAX_DELAY);
if(uxBits==(BIT_0|BIT_4))
{
printf("两个按键事件都发生了\r\n");
}
else if(uxBits==BIT_0)
{
printf("按键1事件都发生了\r\n");
}
else if(uxBits==BIT_4)
{
printf("按键2事件都发生了\r\n");
}
else
{
printf("两个按键事件都没发生\r\n");
}
}
}
7.通过按键1和按键2分别按下,查看串口的结果
8.修改参数,查看串口的结果
*/
1.14.5.5 事件的现象
1.14.5.6 事件的使用场景
KQM6600 是一个任务
DHT11 烟雾 光照 是一个任务
往屏幕上更新数据,希望两个任务都采集到数据,再把数据更新到屏幕上,使用事件可以实现多个任务之间的同步
1.14.6 任务间的通信和同步方式的总结
⚫ 任务间的通信和同步方式:
通信: 消息队列
同步: 二值信号量 计数信号量 事件 互斥信号量
⚫ 二值和互斥的区别:
二值:侧重于同步
互斥:侧重于临界资源的互斥访问。
互斥可以通过优先级的继承解决优先级翻转的问题。
⚫ 信号量和事件的区别:
信号量侧重于一对一,事件可以多对一
1.15 软件定时器
1.15.1 软件定时器的概念
硬件:系统定时器和基本通用定时器
软件:在 1ms 的时间基准上,去构造任意时间周期
1.15.2 软件定时器的更新频率
在配置文件中 FreeRTOSConfig.h,RTOS 是知道单片机的主频的
1.15.3 软件定时器的模式
单次模式:定时器只触发一次
周期模式:定时器会多次按照固定周期触发
1.15.4 软件定时器回调函数和栈空间大小
软件定时器的回调函数(CallBack) -- 是可以退出的函数,不是死循环
软件定时器堆栈空间大小
如果软件定时器的堆栈空间不够,不许修改这个宏,改后面的倍数
1.15.5 软件定时器的接口函数
官网的接口函数 软件定时器 - FreeRTOS™
软件定时器创建函数 xTimerCreate()
软件定时器启动函数 xTimerStart()
软件定时器启动函数 xTimerStartFromISR() -- 中断
软件定时器停止函数 xTimerStop()
软件定时器停止函数 xTimerStopFromISR() -- 中断
软件定时器删除函数 xTimerDelete()
1.15.6 软件定时器的编程
/*
软件定时器
1.main.h中包含指定的头文件
#include "timers.h"
2.在main.c中创建软件定时器的句柄
TimerHandle_t xTimer1 = NULL; //软件定时器句柄
TimerHandle_t xTimer2 = NULL; //软件定时器句柄
3.在main.c中编写软件定时器的回调函数
//软件定时器回调函数,相当于之前定时器的中断服务函数 不是不退出的循环
void vCallbackFunction_Time1( TimerHandle_t xTimer )
{
printf("软件定时器1的回调函数触发\r\n");
}
void vCallbackFunction_Time2( TimerHandle_t xTimer )
{
printf("软件定时器2的回调函数触发\r\n");
}
4.在main.c中创建软件定时器并启动
xTimer1=xTimerCreate("xTimer1",1000,pdTRUE,( void * ) 1,vCallbackFunction_Time1);
if(xTimer1!=NULL)
{
printf("软件定时器1创建成功\r\n");
if(xTimerStart(xTimer1,0)==pdPASS) //启动软件定时器
{
printf("软件定时器1启动成功\r\n");
}
}
xTimer2=xTimerCreate("xTimer2",3000,pdFALSE,( void * ) 2,vCallbackFunction_Time2);
if(xTimer2!=NULL)
{
printf("软件定时器2创建成功\r\n");
if(xTimerStart(xTimer2,0)==pdPASS) //启动软件定时器
{
printf("软件定时器2启动成功\r\n");
}
}
5.查看串口的输出结果
软件定时器1每1s周期打印,软件定时器2在3s时,打印1次
*/
1.15.7 软件定时器的现象
1.16 相对延时和绝对延时
1.16.1 延时的接口函数
vTaskDelay(1000); 相对延时 野火书籍—16.5.4
vTaskDelayUntil(1000); 绝对延时 野火书籍—16.5.4
绝对延时使用示例 vTaskDelayUntil() - FreeRTOS™
1.16.2 绝对延时的用法
/*
//绝对延时
// Perform an action every 10 ticks.
void vTaskFunction( void * pvParameters )
{
TickType_t xLastWakeTime;
const TickType_t xFrequency = 10; //绝对延时的周期
// Initialise the xLastWakeTime variable with the current time.
xLastWakeTime = xTaskGetTickCount(); //获取当前系统运行时间
for( ;; )
{
// Wait for the next cycle.
vTaskDelayUntil( &xLastWakeTime, xFrequency ); //周期延时
// Perform action here.
}
}
*/
1.16.3 绝对延时应用
按键任务等
1.16.4 两个延时的区别
⚫ 不同点:绝对延时更准
FreeRTOS 中相对延时和绝对延时的区别_任务 (sohu.com)
⚫ 相同点:相对延时和绝对延时都叫做系统级别的延时,正在执行的任务,遇到系统的延时,就会任务调度,所以去查询就绪列表中优先级最高的任务去执行
1.16.5 系统的延时和 Delay_ms(空指令)的区别
1. 任务中使用,系统级别的延时,任务可以释放 CPU
2. Delay_ms 空指令的延时,不会释放 CPU,只能等着被高优先级的任务打断,如果该任务就是优先级最高,那么其他任务就无法执行了。
1.17 5 种内存分配的方法介绍
Heap_4 可以解决内存碎片的问题,内存利用率和效率比较高。
1.18 任务堆栈空间大小的确定
官网提供的函数接口查询: 任务实用程序 - FreeRTOS™
可以搞一个优先级最低的任务,去查询所有任务的栈空间使用情况
FreeRTOS任务函数 - 3获取任务状态信息_vtaskgetinfo-CSDN博客FreeRTOS-任务信息查询_vtaskgetinfo-CSDN博客
1.19 任务优先级的确定和修改优先级
最基本的要求:保证各个任务能够正常调度,比较重要的优先级高点
FreeRTOS 中的任务优先级划分策略-支付宝开发者社区 (alipay.com)
在执行任务中,也可以通过接口函数,修改某个任务的优先级,建议每个任务优先级不一样
vTaskPrioritySet()
优先级的范围:0--到下面宏-1 之间
1.20 任务划分的确定
原则:各个任务要独立,任务不是越多越好,建议每个任务优先级不一样
1.21 钩子函数
1.21.1 钩子函数概念
因为内核中相关的函数尽量不修改,我想再执行内核内部一些函数时,添加一些代码,使用该函数的钩子函数。(内核底层给外部开发者提供一个接口)
1.21.2 常用的钩子函数
任务栈溢出钩子函数:任务运行过程中,某个任务栈空间不够用,就会加载。里面可以写死循环
内存申请失败钩子函数:任务在创建的时候,申请内存,申请不成功,就会加载。里面可以写死循环
时间片(系统节拍)钩子函数:每个时间片触发,加载该函数。不能是死循环
1.22 空闲任务的作用
FreeRTOS 空闲任务 - FreeRTOS™
空闲任务:启动任务调度器的时候,创建,优先级是最低的,没有其他任务处于就绪态时候,就执行空闲任务。
空闲任务:用来回收资源,比如某个任务被删除,在空闲任务中回收