FREERTOS任务详解

本文基础内容参考的是正点原子的FREERTOS课程。

这是基于HAL库的

正点原子手把手教你学FreeRTOS实时系统

这是基于标准库的

正点原子FreeRTOS手把手教学-基于STM32

基础知识,直接参考正点原子《FreeRTOS开发指南V1.1》基于标准库的,此处不再赘述。

本文主要对不理解的地方进行查缺补漏,并且先用起来,涉及到原理的部分,可以观看上述教学视频或者开发指南。

多任务系统

回想一下我们以前在使用 51AVRSTM32 单片机裸机(未使用系统)的时候一般都是在 main 函数里面用 while(1)做一个大循环来完成所有的处理,即应用程序是一个无限的循环,循环中调用相应的函数完成所需的处理。有时候我们也需要中断中完成一些处理。相对于多任务系统而言,这个就是单任务系统,也称作前后台系统,中断服务函数作为前台程序,大循环while(1)作为后台程序,如下图所示:

前后台系统的实时性差,前后台系统各个任务(应用程序)都是排队等着轮流执行,不管你

这个程序现在有多紧急,没轮到你就只能等着!相当于所有任务(应用程序)的优先级都是一样的。但是前后台系统简单啊,资源消耗也少啊!在稍微大一点的嵌入式应用中前后台系统就明显力不从心了,此时就需要多任务系统出马了。

多任务系统会把一个大问题(应用)“分而治之”,把大问题划分成很多个小问题,逐步的把

小问题解决掉,大问题也就随之解决了,这些小问题可以单独的作为一个小任务来处理。这些小任务是并发处理的,注意,并不是说同一时刻一起执行很多个任务,而是由于每个任务执行的时间很短,导致看起来像是同一时刻执行了很多个任务一样。多个任务带来了一个新的问题,究竟哪个任务先运行,哪个任务后运行呢?完成这个功能的东西在 RTOS 系统中叫做任务调度器。不同的系统其任务调度器的实现方法也不同,比如 FreeRTOS 是一个抢占式的实时多任务系统,那么其任务调度器也是抢占式的,运行过程如下图所示:

在上图中,高优先级的任务可以打断低优先级任务的运行而取得CPU的使用权,这样就保证了那些紧急任务的运行。这样我们就可以为那些对实时性要求高的任务设置一个很高的 优先级,比如自动驾驶中的障碍物检测任务等。高优先级的任务执行完成以后重新把 CPU 的使用权归还给低优先级的任务,这个就是抢占式多任务系统的基本原理。

任务的特性

在使用 RTOS 的时候一个实时应用可以作为一个独立的任务。每个任务都有自己的运行环 境,不依赖于系统中其他的任务。任何一个时间点只能有一个任务运行,具体运行哪个任务是由 RTOS 调度器来决定的,RTOS 调度器因此就会重复的开启、关闭每个任务。任务不需要了解 RTOS 调度器的具体行为,RTOS 调度器的职责是确保当一个任务开始执行的时候其上下文环境(寄存器值,堆栈内容等)和任务上一次退出的时候相同。为了做到这一点,每个任务都必须有个堆栈,当任务切换的时候将上下文环境保存在堆栈中,这样当任务再次执行的时候就可以从堆栈中取出上下文环境,任务恢复运行。

任务特性:

1、简单。

2、没有使用限制。

3、支持抢占。

4、支持优先级。

5、每个任务都拥有堆栈导致了 RAM 使用量增大。

6、如果使用抢占的话的必须仔细地考虑重入的问题。

任务调度器

RTOS 的任务调度器被设计为可预测的,而这正是嵌入式实时操作系统所需要的,实时环境中要求操作系统必须对某一个事件做出实时的响应,因此系统任务调度器的行为必须是可预测的。像 FreeRTOS 这种传统的 RTOS 类操作系统是由用户给每个任务分配一个任务优先级,任务调度器就可以根据此优先级来决定下一刻应该运行哪个任务。

也就是说,各任务的执行先后顺序不是随意的,而是可以由程序来设置的。

抢占式调度

时间片调度

时间片一般为1ms,不要用我们的思维去看待1ms,以为1ms啥都干不了,其实,计算机能做很多事情,毕竟程序执行都是ns级别的。

任务状态

FreeRTOS 中的任务永远处于下面几个状态中的某一个:

● 运行态

当一个任务正在运行时,那么就说这个任务处于运行态,处于运行态的任务就是当前正在使用处理器的任务。如果使用的是单核处理器的话那么不管在任何时刻永远都只有一个任务处 于运行态。

● 就绪态

处于就绪态的任务是那些已经准备就绪(这些任务没有被阻塞或者挂起),可以运行的任务, 但是处于就绪态的任务还没有运行,因为有一个同优先级或者更高优先级的任务正在运行!

● 阻塞态

如果一个任务当前正在等待某个外部事件的话就说它处于阻塞态,比如说如果某个任务调

用了函数 vTaskDelay()的话就会进入阻塞态,直到延时周期完成。任务在等待队列、信号量、事件组、通知或互斥信号量的时候也会进入阻塞态。任务进入阻塞态会有一个超时时间,当超过这个超时时间任务就会退出阻塞态,即使所等待的事件还没有来临!

● 挂起态

像阻塞态一样,任务进入挂起态以后也不能被调度器调用进入运行态,但是进入挂起态的

任务没有超时时间。任务进入和退出挂起态通过调用函数vTaskSuspend()和 xTaskResume()。

可以说,阻塞是被动的,而挂起是一种我们的主动行为,是我们主动调用函数来执行的,解挂也是。

这几种任务状态的转换关系如下:

任务创建后即进入就绪态;

挂起态恢复后进入就绪态;

阻塞态满足条件后进入就绪态;

也就是说,任务如果想要进入运行态,必须先进入就绪态。

就绪态无法直接进入阻塞态。

除了运行态,挂起态、就绪态、阻塞态各自有一个任务列表。

这四种状态中,除了运行态,其他三种任务状态的任务都有其对应的任务状态列表

阻塞列表和挂起列表都只有一个列表;

但是就绪列表是一个列表数组,如上图右侧展开所示,这是为了同时管理任务的抢占式调度和时间片调度,这个数组的下标序号0—31就表示任务的优先级,序号越高,优先级越高,所以任务调度时,会先从上到下根据标志位去判断各优先级列表里有没有任务需要执行,如果有,则从上到下开始执行,也就是优先执行高优先级的任务。这是纵向的角度。如果是同一个优先级的列表,里面放的就都是同一优先级的任务,这时的调度就是时间片调度了。

也就是说,从纵向看,是不同优先级任务之间的抢占式调度;从横向看,是同一优先级任务之间的时间片轮转调度。

任务优先级

每个任务都可以分配一个从0~(configMAX_PRIORITIES-1)的优先级 ,configMAX_PRIORITIES 在文件FreeRTOSConfig.h中有定义,前面我们讲解 FreeRTOS 系统配置的时候已经讲过了。如果所使用的硬件平台支持类似计算前导零这样的指令(可以通过该指令选择下一个要运行的任务,Cortex-M处理器是支持该指令的),并且宏configUSE_PORT_OPTIMISED_TASK_SELECTION 也设置为了1 , 那么宏

configMAX_PRIORITIES不能超过 32!也就是优先级不能超过 32 级。其他情况下宏 configMAX_PRIORITIES可以为任意值,但是考虑到RAM的消耗,宏 configMAX_PRIORITIES最好设置为一个满足应用的最小值。

优先级数字越低表示任务的优先级越低,0的优先级最低,configMAX_PRIORITIES-1的优

先级最高。空闲任务的优先级最低,为 0。

FreeRTOS 调度器确保处于就绪态或运行态的高优先级的任务获取处理器使用权,换句话说

就是处于就绪态的最高优先级的任务才会运行。当宏configUSE_TIME_SLICING定义为1的时候多个任务可以共用一个优先级,数量不限。默认情况下宏 configUSE_TIME_SLICING在文件FreeRTOS.h 中已经定义为 1。此时处于就绪态的优先级相同的任务就会使用时间片轮转调度器获取运行时间。

任务控制块

FreeRTOS 的每个任务都有一些属性需要存储,FreeRTOS 把这些属性集合到一起用一个结

构体来表示,这个结构体叫做任务控制块:TCB_t,在使用函数 xTaskCreate()创建任务的时候就会自动的给每个任务分配一个任务控制块。在老版本的 FreeRTOS 中任务控制块叫做 tskTCB,新版本重命名为 TCB_t,但是本质上还是 tskTCB,本教程后面提到任务控制块的话均用 TCB_t表示,此结构体在文件 tasks.c 中有定义,FreeRTOS 的任务控制块中的成员变量大多数与裁剪有关,当不使用某些功能的时候与其相关的变量就不参与编译,任务控制块大小就会进一步的减小。详细内容可自行去文件内查看,如下提供一些重点的属性介绍:

注意,栈顶是会不断变化的,栈起始地址是固定的。

任务堆栈 

FreeRTOS 之所以能正确的恢复一个任务的运行就是因为有任务堆栈在保驾护航,任务调

度器在进行任务切换的时候会将当前任务的现场(CPU 寄存器值等)保存在此任务的任务堆栈中,等到此任务下次运行的时候就会先用堆栈中保存的值来恢复现场,恢复现场以后任务就会接着从上次中断的地方开始运行。

创建任务的时候需要给任务指定堆栈,如果使用的函数 xTaskCreate()创建任务(动态方法)

的话那么任务堆栈就会由函数 xTaskCreate()自动创建,后面分析 xTaskCreate()的时候会讲解。如果使用函数 xTaskCreateStatic()创建任务(静态方法)的话就需要程序员自行定义任务堆栈,然后堆栈首地址作为函数的参数 puxStackBuffer 传递给函数,如下:

注意,动态创建时会自动创建,不必手动传递。所以这里只是以静态创建函数作为示例。

任务创建和删除的API函数

FreeRTOS最基本的功能就是任务管理,而任务管理最基本的操作就是创建和删除任务, FreeRTOS 的任务创建和删除 API 函数如下表所示:

函数xTaxkCreate()

此函数用来创建一个任务,任务需要RAM来保存与任务有关的状态信息(任务控制块),任务也需要一定的RAM来作为任务堆栈。如果使用函数 xTaskCreate()来创建任务的话那么这些所需的RAM就会自动的从FreeRTOS的堆中分配,因此必须提供内存管理文件,默认我们使用heap_4.c这个内存管理文件,而且宏configSUPPORT_DYNAMIC_ALLOCATION必须为1。如果使用函数 xTaskCreateStatic()创建的话这些 RAM 就需要用户来提供了。新创建的任务默认就是就绪态的,如果当前没有比它更高优先级的任务运行那么此任务就会立即进入运行态开始运行,不管在任务调度器启动前还是启动后,都可以创建任务。此函数也是我们以后经常用到的。

其原型如下:

pxTaskCode:任务函数。

pcName:任务名字,一般用于追踪和调试,任务名字长度不能超过configMAX_TASK_NAME_LEN。

usStackDepth:任务堆栈大小,注意实际申请到的堆栈是 usStackDepth 的 4 倍。其中空闲任务的任务堆栈大小为 configMINIMAL_STACK_SIZE。

pvParameters:传递给任务函数的参数。

uxPriotiry:任务优先级,范围 0~ configMAX_PRIORITIES-1。

pxCreatedTask:任务句柄,任务创建成功以后会返回此任务的任务句柄,这个句柄其实就是 任务的任务控制块,可以理解成任务对象。

补充说明:

一般,任务名称字符串和调用的任务名保持一致即可。

这里任务堆栈的单位是字word,所以占的字节数需要乘以4来计算,别搞错了。

另外就是,任务函数的参数为NULL即可。

操作某个任务,实际就是操作该任务的任务句柄。

函数xTaskCreateStatic()

此函数和 xTaskCreate()的功能相同,也是用来创建任务的,但是使用此函数创建的任务所需 的 RAM 需 要 用 用 户 来 提 供 。 如 果 要 使 用 此 函 数 的 话 需 要 将 宏

configSUPPORT_STATIC_ALLOCATION 定义为 1。函数原型如下:

参数:

pxTaskCode:任务函数。

pcName:任务名字,一般用于追踪和调试,任务名字长度不能超过configMAX_TASK_NAME_LEN。

usStackDepth:任务堆栈大小,由于本函数是静态方法创建任务,所以任务堆栈由用户给出, 一般是个数组,此参数就是这个数组的大小。

pvParameters:传递给任务函数的参数。

uxPriotiry:任务优先级,范围 0~ configMAX_PRIORITIES-1。

puxStackBuffer:任务堆栈,一般为数组,数组类型要为 StackType_t 类型。

pxTaskBuffer:任务控制块。

返回值:

NULL:任务创建失败,puxStackBuffer 或 pxTaskBuffer 为 NULL 的时候会导致这个错误的发生。

其他值:任务创建成功,返回任务的任务句柄。

注意,动态创建时是自行传入任务句柄,而静态创建是返回任务句柄。

函数xTaskCreateRestricted()

此函数也是用来创建任务的,只不过此函数要求所使用的 MCU MPU(内存保护单元)

用此函数创建的任务会受到MPU的保护。其他的功能和函数 xTaxkCreate()一样。

略。

函数vTaskDelete()

删除一个用函数 xTaskCreate()或者 xTaskCreateStatic()创建的任务,被删除了的任务不再存

在,也就是说再也不会进入运行态。任务被删除以后就不能再使用此任务的句柄!

如果此任务是使用动态方法创建的,也就是使用函数 xTaskCreate()创建的,那么在此任务被删除以后此任务之前申请的堆栈和控制块内存会在空闲任务中被释放掉,因此当调用函数 vTaskDelete()删除任务以后必须给空闲任务一定的运行时间。只有那些由内核分配给任务的内存才会在任务被删除以后自动的释放掉,用户分配给任务的内存需要用户自行释放掉,比如某个任务中用户调用函数 pvPortMalloc()分配了 500 字节的内存,那么在此任务被删除以后用户也必须调用函数 vPortFree()将这 500 字节的内存释放掉,否则会导致内存泄露。

动态创建和删除任务实例

认识了创建任务和删除任务的API,那么具体怎么用起来呢?

先来看看动态创建任务函数xTaskCreate()

以及删除任务函数xTaskDelete()

我们从main函数开始看,顺便由此体会下系统开发跟裸机开发的不同之处。

进入main函数后,先照常进行初始化,不过main函数里没有while主循环了,取而代之的是创建各个任务,有几个任务就创建几个。

不过,除了这些常规任务之外,我们还需要一个特殊的开始任务,作用是用来创建初始时需要运行的所有常规任务。也就是用来创建其他任务的任务。

如下的开始任务:

使用xTaskCreate函数创建任务之后,任务还不会调度,只有使用vTaskStartScheduler函数开启任务调度器之后,才会开始进行任务调度,调度规则就是抢占式调度+时间片调度。

开始任务里面做什么呢?就是创建其他所需要的任务,如下所示:

在开始任务里,我们创建了任务1和任务2这两个任务,当然可以创建更多的任务。

这两个任务就是我们的应用真正需要执行的那些业务逻辑。

除此之外,开始任务里还要干两件事:

●第一个就是删除开始任务,也就是说,开始任务在创建完其他任务之后,它的使命就结束了,start_task 任务的职责就是用来创建其他的任务或者信号量、消息队列等的,当创建完成以后就可以删除掉 start_task 任务。所以需要调用xTaskDelete函数并传入NULL来删除自身,随后由空闲任务来释放其所使用的内存空间。

●第二个就是开始任务进入时和结束时需要分别调用进入临界区和退出临界区的函数,进入临界区内部其实就是临时关闭中断,因为任务调度是通过中断来实现的,所以进入临界区其实就是为了关闭任务调度,为什么呢?因为一般开始任务的优先级是除了空闲任务之外最低的,空闲任务的优先级默认为0,所以我们一般设置开始任务的优先级为1,其他任务的优先级通常都会比开始任务的优先级要高,这种情况下,如果创建其他任务时不关闭任务调度,会发生什么呢?比如上面的例子中,如果开始任务的优先级为1,task1的优先级为2,那么,当开始任务创建,开启任务调度之后,就会开始调度开始任务,开始任务会先创建任务1,任务1被创建之后,就会进入就绪态,因为其优先级比开始任务要高,所以,会抢占开始任务从而被执行,如果此时task1一直在执行,那么开始任务将无法得到执行,那么,task2就永远无法被创建,显然,这并不是我们想要的。所以,才会在进入开始任务时先关闭任务调度,等到所有需要的任务都被创建、开始任务被删除之后,才会开始任务调度,此时,各个任务就会按照我们预期的设定去执行。

另外要注意:

其他任务的创建,其实是基于我们所需要的任务的,所以,我们一般是先确定了有哪些任务函数需要执行,然后再在开始任务中添加对应的任务。并且,常规任务是在while(1)主循环里面的,其实就是把裸机里堆叠在一个while里的任务进行分散管理。

比如:

while主循环从main里面分散到各个任务,这是系统开发和裸机开发的一个区别;

另外,从这里的任务1和任务2也能看出来,对延时函数的使用,系统开发和裸机开发的思路也是有所区别的。在裸机里,是绝对不允许在主循环里使用延时函数的,因为裸机所有任务都在一个while里面,如果一个任务有延时,其他所有的任务都会被阻塞,所以如果想要实现闪灯效果,得结合定时器来反转LED灯,但是在系统开发的分散主循环里面,可以直接使用延时函数,因为这个任务延时了,也不会影响其他任务的执行。

细心一些就会发现,开始任务并没有放在while死循环里面,这是因为开始任务只需要被执行一次即可,这一点需要注意下。

整体框架问题已经确定好了,那么接下来就要开始讲解下创建函数时的这些入口参数要如何确定了。就以上述的几个任务为例说明。

比如开始任务

入口参数需要强转,防止报错。

✔任务函数就是函数指针,也就是需要执行的任务的函数名称;

✔任务名称一般跟任务函数的名称保持一致即可;

✔任务堆栈大小,注意下单位是字,所以实际占用字节数为这里的数字*4;

✔传给任务函数的参数写NULL即可;

✔任务优先级根据需要自行定义;

✔任务句柄自行定义然后传递进入,我们删除指定任务时,就是传入该任务的任务句柄;

其他任务也是一样的思路,其中的一些参数可用宏定义来表示。

比如:

堆栈一般开始时设定得稍大一些,后续执行时可以调用相关函数来获取历史剩余最小堆栈空间,从而进行调整。

至此,动态创建和删除任务的基本使用就差不多了。

简单的总结分析一下此例程的流程,因为这是我们使用 FreeRTOS 写的第一个程序,很多习惯是我们后面要用到的。比如使用任务宏定义任务优先级,堆栈大小等,一般有关一个任务的东西我们的放到一起,比如任务堆栈、任务句柄、任务函数声明等,这样方便修改。这些东西可以放到一个.h 头文件里面去,只是例程里面任务数比较少,所以就直接放到 main.c 文件里面了,要是工程比较大的话最好做一个专用的头文件来管理。

在main函数中一开始肯定是初始化各种硬件外设,初始化完外设以后调用函数xTaskCreate()创建一个开始任务,注意创建开始任务是在调用函数 vTaskStartScheduler()开启任务调度器之前,这样当后面开启任务调度器以后就会直接运行开始任务了。其他任务的创建就放到开始任务的任务函数中,由于开始任务的职责就是创建其他应用任务和信号量、队列等这些内核对象的,所以它只需要执行一次,当这些东西创建完成以后就可以删除掉开始任务了。 

注意,已删除的任务如果再调用删除函数来删除,就会报错, 所以实际使用时需要使用标志位来判断,只有当没有被删除的情况下才能被删除。

动态创建任务函数的内部实现

大致内容如下:

注意这里面有两块内存需要申请,一个是任务所需的堆栈内存,另一个就是任务控制块的内存;

以下结合源码说明

该函数在task.c文件里

函数里先创建了一个任务控制块

然后判断栈是增栈还是减栈,STM32使用的是满减栈,所以portSTACK_GROWTH <0,因此执行第二个分支

这个分支里面先定义了一个堆栈指针变量,然后为堆栈分配了内存空间,接着为任务控制块分配了内存空间,再然后把堆栈指针赋值到任务控制块中,所以,动态创建任务函数就是在这里自动分配内存空间的。

继续看

紧接着,就是给任务控制块赋值,最后会把任务控制块加入到就绪列表,放到就绪列表时会根据任务的优先级放到对应的就绪列表中的对应位置。

这里的函数prvInitialiseNewTask会根据入口参数对任务控制块进行初始化,我们重点看看入口参数是怎么被处理的。

以开始任务为例,其入口参数如下:

跳转进入

再看看控制块的初始化函数

其中,所有形参都传递进去了,并且,将刚才内部创建的任务控制块也传递进去了,并且,多了个NULL,里面具体干了什么呢?

注意,任务控制块里的堆栈地址上面已经赋过值了,这个函数是用来赋值剩下的属性值的。

这个函数也是在文件task.c中

里面的操作很多,我们就不一一解释了,具体按需看正点的视频吧。

不过有一个很重要的点,那就是这条语句,我们要知道。

这句话的意思是将任务控制块的内容给到任务句柄里面,所以我们后续操作任务句柄就能操作任务了,实际就是操作任务控制块。

注意:任务句柄不是个指针,而是个变量。

注意,任务控制块虽然是在函数里面分配的,但是是通过malloc函数分配的,手动分配的内存是存放在堆内存里,即使出了函数,如果不手动释放该堆空间,就不会被自动释放。

由此也能知道,任务句柄其实就是用来访问任务的一个入口,就像一个锅的手柄,我们拿住了锅的手柄,就等于是拿住了锅。

删除任务函数的内部实现

任务删除的内部过程如下:

该函数也在task.c中

具体结合正点视频自行查看源码,此处略。

静态创建和删除任务实例

自行查看正点视频

第12讲 任务创建和删除(静态方式)_哔哩哔哩_bilibili

暂时了解即可,后续有需要再补充。

此处略。

关于任务句柄补充

如果使用函数 xTaskCreate()创建任务的话那么函数的参数pxCreatedTask 就是此任务的任务句柄,如果使用函数 xTaskCreateStatic()创建任务的话那么函数的返回值就是此任务的任务句柄。也可以通过函数 xTaskGetHandle()来根据任务名字来获取某个任务的任务句柄。

任务挂起和恢复的API函数

有时候我们需要暂停某个任务的运行,过一段时间以后在重新运行。这个时候要是使用任务删除和重建的方法的话那么任务中变量保存的值肯定丢失了!FreeRTOS 给我们提供了解决 这种问题的方法,那就是任务挂起和恢复,当某个任务要停止运行一段时间的话就将这个任务挂起,当要重新运行这个任务的话就恢复这个任务的运行。FreeRTOS 的任务挂起和恢复 API 函数如下所示:

接下来分别进行说明

挂起任务函数

进入挂起态的任务永远都不会进入运行态。退出挂起态的唯一方法就是调用任务恢复函数vTaskResume()xTaskResumeFromISR()

常规的恢复任务函数

中断中恢复任务的函数

返回值不是说恢复成功还是恢复失败,说的是恢复后是否需要进行上下文切换。

pdTRUE:

恢复运行的任务的任务优先级等于或者高于正在运行的任务(被中断打断的任务),这意味着在退出中断服务函数以后必须进行一次上下文切换。

pdFALSE:

恢复运行的任务的任务优先级低于当前正在运行的任务(被中断打断的任务),这意味着在退出中断服务函数的以后不需要进行上下文切换。

上下文切换需要我们调用相应函数来执行。

注意:

使用中断中恢复函数,要将 include_vTaskSuspend 和 INCLUDE_xTaskResumeFromISR 定义为 1 才能使用此函数。

具体参考官网手册:

FreeRTOS - The Free RTOS configuration constants and configuration options - FREE Open Source RTOS for small real time embedded systems

正点的配置文件里没有这个宏定义,需要自己手动添加。

实际上,,,,,

不加上面的INCLUDE_xTaskResumeFromISR宏定义,编译时也不会报错。

这是因为,在FreeRTOS.h里面默认开启了

如果手动在用户配置文件中将这个头文件改为0,编译时就会报错了

挂起和恢复任务实例

任务挂起和常规恢复直接在需要的地方调用相应函数即可。

此处重点讲解下在中断里恢复任务的情况。

一个示例如下:

定义一个返回值变量,然后将调用恢复任务的返回值赋给这个返回值变量,之后,调用函数portYIELD_FROM_ISR函数来进行一次上下文切换。不过在切换上下文之前,需要判断下是不是需要进行切换。

另外, 这里要注意个问题,那就是:如果某个中断的中断服务程序中要调用freeRTOS的API函数,那么这个中断的优先级不能高于FreeRTOS所管理的最高优先级。

FreeRTOS所管理的优先级范围在配置文件FreeRTOSConfig.h中定义:

我们知道,中断优先级数值越低优先级越高,所以这里rtos管理的最高优先级为5,最低优先级为15,也就是说,如果某个中断的中断服务程序中要调用freeRTOS的API函数,那么这个中断的优先级不能高于5。

如果中断优先级不在这个范围之内,却在中断服务程序里调用freeRTOS的API函数,就会报错,如下所示:

但是有个问题,那就是STM32的中断优先级有抢占优先级和子优先级,到底以哪个为准呢?

官网中有句话:

这里说明了,推荐将优先级全都设置成抢占优先级,从而方便rtos的管理,避免复杂化。

除此之外,中断的优先级设置也有要求:

从而,我们需要将中断的优先级设置成5~15之间,而不能设置在0~4之间。

任务挂起和恢复函数的内部实现

任务挂起xTaskSuspend函数内部实现过程总体如下:

该函数在task.c文件中

任务恢复xTaskResume函数的内部实现

也在文件task.c中

更多参考正点视频:

第15讲 挂起和恢复函数详细过程(函数解析)_哔哩哔哩_bilibili

此处暂略。

补充:函数的前缀

在使用任务相关的函数时,需要包含task.h头文件,并且,需要在包含task.h头文件之后先包含FreeRTOS.h头文件。

如下:

#include "FreeRTOS.h"
#include "task.h"

另外,发现一个问题,那就是调用函数时别搞错了,有的是v开头有的是x开头等等,有的地方可能会写错了,我们要注意识别。

比如:

这里调用了函数里恢复函数就报错了,头文件包含了,相应的宏定义也开启了,但就是报错,就是因为把函数xTaskResumeFromISR写成了vTaskResumeFromISR

补充,函数的前缀:

在 FreeRTOS 中,能经常看到函数名前有 x、v 等类似的前缀,这些前缀的含义如下:

v 函数返回值是 void 类型,也就是说没有返回值

x 函数返回值是 结构体、队列、任务等的变量名类型

c 函数返回值是 char 类型

u 函数返回值是 unsigned 类型

s 函数返回值是 short 类型

p 函数返回值是指针类型

prv 说明该函数是 static,属于私有函数,无法被外界调用

另外还有些不带前缀的好像是宏定义。

其他任务API函数

前面我们花费了大量的精力来学习 FreeRTOS 的任务管理,但是真正涉及到的与任务相关的 API 函数只有那么几个。但是 FreeRTOS 还有很多与任务相关的 API 函数,不过这些 API函数大多都是辅助函数了,接下来我们就来看一下这些与任务相关的其他的 API 函数。

先通过一个表来概览一下这些与任务相关的其他 API 函数都有哪些:

这些 API 函数在 FreeRTOS 官网上都有,如下图所示:

具体根据需要去官网查看对应API的使用示例即可。

此处仅说明几个便于调试和优化的查询函数

函数 uxTaskGetStackHighWaterMark()

每个任务都有自己的堆栈,堆栈的总大小在创建任务的时候就确定了,此函数用于检查任务从创建好到现在的历史剩余最小值,这个值越小说明任务堆栈溢出的可能性就越大! FreeRTOS 把这个历史剩余最小值叫做“高水位线”。此函数相对来说会多耗费一点时间,所以在代码调试阶段可以使用,产品发布的时候最好不要使用。要使用此函数的话宏 INCLUDE_uxTaskGetStackHighWaterMark 必须为 1,此函数原型如下:

UBaseType_t uxTaskGetStackHighWaterMark( TaskHandle_t xTask )

参数:

xTask: 要查询的任务的任务句柄,当这个参数为 NULL 的话说明查询自身任务(即调用函数 uxTaskGetStackHighWaterMark()的任务)的“高水位线”。

返回值: 任务堆栈的“高水位线”值,也就是堆栈的历史剩余最小值。

函数 vTaskList()

此函数会创建一个表格来描述每个任务的详细信息,如下图所示:

表中的信息如下:

Name:创建任务的时候给任务分配的名字。

State:任务的壮态信息,B 是阻塞态,R 是就绪态,S 是挂起态,D 是删除态。

Priority:任务优先级。

Stack:任务堆栈的“高水位线”,就是堆栈历史最小剩余大小。

Num:任务编号,这个编号是唯一的,当多个任务使用同一个任务名的时候可以通过此编号来做区分。

函数原型如下:

void vTaskList( char * pcWriteBuffer )

参数:

pcWriteBuffer: 保存任务壮态信息表的存储区。存储区要足够大来保存任务状态信息表。

函数 vTaskGetRunTimeStats()

FreeRTOS 可以通过相关的配置来统计任务的运行时间信息,任务的运行时间信息提供了每个任务获取到 CPU 使用权总的时间。函数 vTaskGetRunTimeStats()会将统计到的信息填充到 一个表里面,表里面提供了每个任务的运行时间和其所占总时间的百分比,如下图所示:

函数vTaskGetRunTimeStats() 是 一 个 很 实 用 的 函 数 , 要 使 用 此 函 数 的 话 宏configGENERATE_RUN_TIME_STATS和 configUSE_STATS_FORMATTING_FUNCTIONS 必须都为 1。如果宏 configGENERATE_RUN_TIME_STATS 为 1 的话还需要实现一下几个宏定义:

● portCONFIGURE_TIMER_FOR_RUN_TIME_STATS(),此宏用来初始化一个外设来提供时间统计功能所需的时基,一般是定时器/计数器。这个时基的分辨率一定要比 FreeRTOS的系统时钟高,一般这个时基的时钟精度比系统时钟的高 10~20 倍就可以了。

●portGET_RUN_TIME_COUNTER_VALUE()

或者 portALT_GET_RUN_TIME_COUNTER_VALUE(Time),这两个宏实现其中一个就行了,这两个宏用于提供当前的时基的时间值。

函数原型如下:

void vTaskGetRunTimeStats( char *pcWriteBuffer )

参数:

pcWriteBuffer: 保存任务时间信息的存储区。存储区要足够大来保存任务时间信息。

附:系统内核控制函数

顾名思义,内核控制函数就是 FreeRTOS 内核所使用的函数,一般情况下应用层程序不使

用这些函数,在 FreeRTOS 官网可以找到这些函数,如下图所示:

这些函数的含义如下表所示:

按需详解,此处不赘述。

补充

任务头文件

注意,与任务相关的这些函数,基本全都在task.h这个头文件中被声明

并且在三大主要文件之一task.c里被实现

 

跑任务的时候直接报错

注意,报错是打印出了哪个文件的第几行,比如这里出错的地方在port.c文件的第244行。

这错误啥情况?

经排查,原来是任务没有放到while(1)里面

程序跑飞了。。。。

应该是如下所示:

由此可以看到,freertos可以直接使用延时,而不必使用定时器来置标志位实现led的闪烁。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值