FreeRTOS学习笔记

一、RTOS基础

1.轮询系统:轮询系统即是在裸机编程的时候,先初始化好相关的硬件,然后让主程序在一个死循环里面不断循环,顺序地做各种事情,通常只适用于那些只需要顺序执行代码且不需要外部事件来驱动的就能完成的事情;前后台系统:相比轮询系统,前后台系统是在轮询系统的基础上加入了中断。外部事件的响应在中断里面(前台)完成,事件的处理(后台)还是回到轮询系统中完成,中断在这里我们称为前台,main()函数里面的无限循环我们称为后台;多任务系统:相比前后台系统,多任务系统的事件响应也是在中断中完成的,但是事件的处理是在任务中完成的。在多任务系统中,任务跟中断一样,也具有优先级,优先级高的任务会被优先执行。当一个紧急的事件在中断被标记之后,如果事件对应的任务的优先级足够高,就会立马得到响应。

2.每个任务都有自己的调用关系,自己的局部变量以及自己的寄存器内容(现场),所以每个任务都有自己的栈,这些栈是相互独立的。就和进程一样,每个进程的栈空间都是相互独立的,每个进程都有自己的地址空间,包括代码、数据和栈空间,栈空间用于存储函数调用时的局部变量、函数参数、返回地址等信息。

3.保存现场分三种情况:1子函数调用:此时只需要保存部分寄存器的值,并在子函数返回时由子函数进行恢复;2硬件中断:硬件会自动保存部分寄存器的值,剩下的寄存器按需要用户自己在程序中软件保存(比如在Cortex-M3中xPSR(状态寄存器),PC(任务入口地址),R14(LR),R12,R3,R2,R1,R0(任务的形参)这几个CPU寄存器的值会被硬件自动保存到任务的栈中,剩下的r4~r11需要手动保存),中断用的是主栈MSP(参考CM3权威指南CnR2的P141),而前述的任务用的是任务自己的任务栈PSP;任务切换:所有寄存器的值都需要被保存下来(FreeRTOS中任务切换是通过PendSVC异常来实现任务切换的)。

4.Cortex-M3中编号为 1-15 的对应系统异常,大于等于 16 的则全是外部中断,共支持240个外部中段,编号16-255。Cortex-M3中的异常除了复位, NMI 和硬 fault三个异常的优先级被定为-3、-2和-1,其他异常的优先级都是可编程的,但是不能被自定义为负数。往BASEPRI寄存器中写入值可以屏蔽中断(屏蔽优先级不高于所写入值的中断,优先级值大于等于写入值的中断都将被屏蔽),但写入0表示不屏蔽任何中断。

5.临界段:临界段用一句话概括就是一段在执行的时候不能被中断的代码段,即临界区。

二、FreeRTOS基础知识

1.FreeRTOS:它的时间片只能是一个Tick。

2.FreeRTOS相关文件夹介绍:FreeRTOS文件夹下的Source文件夹里面包含的是 FreeRTOS内核的源代码,移植FreeRTOS的时候就需要这部分源代码;FreeRTOS 文件夹下的Demo文件夹里面包含了FreeRTOS官方为各个单片机移植好的工程代码,FreeRTOS为了推广自己,会给各种半导体厂商的评估板写好完整的工程程序,这些程序就放在Demo这个目录下,这部分Demo非常有参考价值。Source文件夹下的include文件夹及Source文件夹下的.c文件包含的是FreeRTOS的通用的头文件和C文件,这两部分的文件适用于各种编译器和处理器,是通用的。需要移植的头文件和C文件放在Source文件夹下的portable文件夹中(keil编译环境使用RVDS文件夹)。

3.FreeRTOS 的任务可认为是一系列独立任务的集合。每个任务在自己的环境中运行。在任何时刻,只有一个任务得到运行,FreeRTOS调度器决定运行哪个任务。调度器会不断的启动、停止每一个任务,宏观看上去所有的任务都在同时在执行。作为任务,不需要对调度器的活动有所了解,在任务切入切出时保存上下文环境(寄存器值、堆栈内容)是调度器主要的职责。为了实现这点,每个FreeRTOS任务都需要有自己的栈空间。当任务切出时,它的执行环境会被保存在该任务的栈空间中,这样当任务再次运行时,就能从堆栈中正确的恢复上次的运行环境,任务越多,需要的堆栈空间就越大,而一个系统能运行多少个任务,取决于系统的可用的SRAM。FreeRTOS可以给用户提供多个任务单独享有独立的堆栈空间,系统可以决定任务的状态,决定任务是否可以运行,同时还能运用内核的IPC通信资源,实现了任务之间的通信,帮助用户管理业务程序流程。这样用户可以将更多的精力投入到业务功能的实现中。FreeRTOS中的任务是抢占式调度机制,高优先级的任务可打断低优先级任务,低优先级任务必须在高优先级任务阻塞或结束后才能得到调度。同时FreeRTOS也支持时间片轮转调度方式,任何数量的任务可以共享同一个优先级,如 果 宏configUSE_TIME_SLICING定义为1,处于就绪态的多个相同优先级任务将会以时间片切换的方式共享处理器。任务通常会运行在一个死循环中,也不会退出,如果一个任务不再需要,可以调用FreeRTOS中的任务删除 API 函数接口显式地将其删除。

4.FreeRTOS中提供的任务调度器是基于优先级的全抢占式调度:在系统中除了中断处理函数、调度器上锁部分的代码和禁止中断的代码是不可抢占的之外,系统的其他部分都是可以抢占的。系统理论上可以支持无数个优先级(0~N,优先级数值越小的任务优先级越低,0为最低优先级,分配给空闲任务使用,一般不建议用户来使用这个优先级。假如使能了configUSE_PORT_OPTIMISED_TASK_SELECTION这个宏(在 FreeRTOSConfig.h 文件定义), 一般强制限定最大可用优先级数目为32。在一些资源比较紧张的系统中,可以根据实际情况选择只支持8个或32个优先级的系统配置。在系统中,当有比当前任务优先级更高的任务就绪时,当前任务将立刻被换出,高优先级任务抢占处理器运行。一个操作系统如果只是具备了高优先级任务能够"立即"获得处理器并得到执行的特点,那么它仍然不算是实时操作系统。因为这个查找最高优先级任务的过程决定了调度时间是否具有确定性,例如一个包含n个就绪任务的系统中,如果仅仅从头找到尾,那么这个时间将直接和n相关,而下一个就绪任务抉择时间的长短将会极大的影响系统的实时性。FreeRTOS内核中采用两种方法寻找最高优先级的任务,第一种是通用的方法,在就绪链表中查找从高优先级往低查找uxTopPriority,因为在创建任务的时候已经将优先级进行排序,查找到的第一个uxTopPriority就是我们需要的任务,然后通过uxTopPriority获取对应的任务控制块。第二种方法则是特殊方法,利用计算前导零指令CLZ,直接在uxTopReadyPriority这个32位的变量中直接得出 uxTopPriority,这样子就知道哪一个优先级任务能够运行,这种调度算法比普通方法更快捷,但受限于平台(在STM32中我们就使用这种方法)。FreeRTOS 内核中也允许创建相同优先级的任务。相同优先级的任务采用时间片轮转方式进行调度(也就是通常说的分时调度器),时间片轮转调度仅在当前系统中无更高优先级就绪任务存在的情况下才有效。为了保证系统的实时性,系统尽最大可能地保证高优先级的任务得以运行。任务调度的原则是一旦任务状态发生了改变,并且当前运行的任务优先级小于优先级队列组中任务最高优先级时,立刻进行任务切换(除非当前系统处于中断处理程序中或禁止任务切换的状态)。

5.FreeRTOS系统中的每一任务都有多种运行状态。系统初始化完成后,创建的任务就可以在系统中竞争一定的资源,由内核进行调度。任务状态通常分为以下四种:

  • 就绪( Ready):该任务在就绪列表中,就绪的任务已经具备执行的能力,只等待调度器进行调度,新创建的任务会初始化为就绪态。
  • 运行(Running):该状态表明任务正在执行,此时它占用处理器,FreeRTOS调度器选择运行的永远是处于最高优先级的就绪态任务,当任务被运行的一刻,它的任务状态就变成了运行态。
  • 阻塞(Blocked):如果任务当前正在等待某个时序或外部中断,我们就说这个任务处于阻塞状态,该任务不在就绪列表中。包含任务被挂起、任务被延时、任务正在等待信号量、读写队列或者等待读写事件等。
  • 挂起态(Suspended):处于挂起态的任务对调度器而言是不可见的,让一个任务进入挂起状态的唯一办法就是调用vTaskSuspend()函数;而把一个挂起状态的任务恢复的唯一途径就是调用vTaskResume()或vTaskResumeFromISR()函数,我们可以这么理解挂起态与阻塞态的区别,当任务有较长的时间不允许运行的时候,我们可以挂起任务,这样子调度器就不会管这个任务的任何信息,直到我们调用恢复任务的 API 函数;而任务处于阻塞态的时候,系统还需要判断阻塞态的任务是否超时,是否可以解除阻塞。

6.FreeRTOS 中程序运行的上下文包括:中断服务函数、普通任务、空闲(idle)任务。

  • 中断服务函数:中断服务函数是一种需要特别注意的上下文环境,它运行在非任务的执行环境下(一般为芯片的一种特殊运行模式(也被称作特权模式)),在这个上下文环境中不能使用挂起当前任务的操作,不允许调用任何会阻塞运行的API函数接口。另外需要注意的是,中断服务程序最好保持精简短小,快进快出,一般在中断服务函数中只做标记事件的发生,然后通知任务,让对应任务去执行相关处理,因为中断服务函数的优先级高于任何优先级的任务,如果中断处理时间过长,将会导致整个系统的任务无法正常运行。所以在设计的时候必须考虑中断的频率、中断的处理时间等重要因素,以便配合对应中断处理任务的工作。
  • 普通任务:任务看似没有什么限制程序执行的因素,似乎所有的操作都可以执行。但是做为一个优先级明确的实时系统,如果一个任务中的程序出现了死循环操作(此处的死循环是指没有阻塞机制的任务循环体),那么比这个任务优先级低的任务都将无法执行,当然也包括了空闲任务,因为死循环的时候,任务不会主动让出 CPU,低优先级的任务是不可能得到CPU的使用权的,而高优先级的任务就可以抢占CPU。这个情况在实时操作系统中是必须注意的一点,所以在任务中不允许出现死循环。如果一个任务只有就绪态而无阻塞态,势必会影响到其他低优先级任务的执行,所以在进行任务设计时,就应该保证任务在不活跃的时候,任务可以进入阻塞态以交出CPU使用权,这就需要明确知道什么情况下让任务进入阻塞态,保证低优先级任务可以正常运行。在实际设计中,一般会将紧急的处理事件的任务优先级设置得高一些。
  • 空闲(idle)任务:空闲任务(idle任务)是FreeRTOS系统中没有其他工作进行时自动进入的系统任务。因为处理器总是需要代码来执行------所以至少要有一个任务处于运行态。FreeRTOS为了保证这一点,当调用vTaskStartScheduler()时, 调度器会自动创建一个空闲任务,空闲任务是一个非常短小的循环。 用户可以通过空闲任务钩子方式,在空闲任务上钩入自己的功能函数。通常这个空闲任务钩子能够完成一些额外的特殊功能,例如系统运行状态的指示,系统省电模式等。除了空闲任务钩子,FreeRTOS系统还把空闲任务用于一些其他的功能,比如当系统删除一个任务或一个动态任务运行结束时,在执行删除任务的时候,若删除的是当前正在运行的任务则并不会释放立即任务的内存空间,只会将任务添加到待结束列表中,真正的系统资源回收工作在空闲任务完成,空闲任务是唯一一个不允许出现阻塞情况的任务,因为FreeRTOS需要保证系统永远都有一个可运行的任务。对于空闲任务钩子上挂接的空闲钩子函数,它应该满足以下的条件:永远不会挂起空闲任务、不应该陷入死循环,需要留出部分时间用于系统处理系统资源回收。
  • 任务的执行时间:任务的执行时间一般是指两个方面,一是任务从开始到结束的时间,二是任务的周期。在系统设计的时候这两个时间候我们都需要考虑,例如对于事件 A 对应的服务任务Ta,系统要求的实时响应指标是10ms,而Ta的最大运行时间是1ms,那么10ms就是任务Ta的周期了,1ms则是任务的运行时间,简单来说任务Ta在10ms 内完成对事件A的响应即可。此时,系统中还存在着以50ms为周期的另一任务Tb,它每次运行的最大时间长度是100us。在这种情况下,即使把任务Tb的优先级抬到比Ta更高的位置,对系统的实时性指标也没什么影响,因为即使在Ta的运行过程中,Tb抢占了Ta的资源,等到Tb执行完毕,消耗的时间也只不过100us,还是在事件A规定的响应时间内(10ms), Ta能够安全完成对事件A的响应。但是假如系统中还存在任务Tc,其运行时间为20ms,假如将Tc的优先级设置比Ta更高,那么在Ta运行的时候,突然间被Tc打断,等到Tc执行完毕,那Ta已经错过对事件A(10ms)的响应了,这是不允许的。所以在我们设计的时候,必须考虑任务的时间,一般来说处理时间更短的任务优先级应设置更高一些。

7.队列又称消息队列,是一种常用于任务间通信的数据结构, 队列可以在任务与任务间、中断和任务间传递信息,实现了任务接收来自其他任务或中断的不固定长度的消息,任务能够从队列里面写入或读取消息,当队列中的消息是空时,读取消息的任务将被阻塞,用户还可以指定阻塞的任务时间xTicksToWait,在这段时间中,如果队列为空,该任务将保持阻塞状态以等待队列数据有效。当队列中有新消息时,被阻塞的任务会被唤醒并处理新消息;当等待的时间超过了指定的阻塞时间,即使队列中尚无有效数据,任务也会自动从阻塞态转为就绪态。消息队列是一种异步的通信方式。通过消息队列服务,任务或中断服务例程可以将一条或多条消息放入消息队列中。同样,一个或多个任务可以从消息队列中获得消息。当有多个消息发送到消息队列时,通常是将先进入消息队列的消息先传给任务,也就是说,任务先得到的是最先进入消息队列的消息,即先进先出原则(FIFO),但是也支持后进先出原则(LIFO)。FreeRTOS 中使用队列数据结构实现任务异步通信工作,具有如下特性:消息支持先进先出方式排队,支持异步读写工作方式、读写队列均支持超时机制、消息支持后进先出方式排队,往队首发送消息(LIFO)、可以允许不同长度(不超过队列节点最大值)的任意类型消息、一个任务能够从任意一个消息队列接收和发送消息、多个任务能够从同一个消息队列接收和发送消息、当队列使用结束后,可以通过删除队列函数进行删除。

8.使用FreeRTOS提供的消息队列函数的时候,需要了解以下几点:使用xQueueSend()、 xQueueSendFromISR()、xQueueReceive()等这些函数之前应先创建消息队列,并根据队列句柄进行操作;队列读取采用的是先进先出(FIFO)模式,会先读取先存储在队列中的数据。当然也FreeRTOS也支持后进先出(LIFO)模式,那么读取的时候就会读取到后进队列的数据;在获取队列中的消息时候,我们必须要定义一个存储读取数据的地方,并且该数据区域大小不小于消息大小,否则很可能引发地址非法的错误;无论是发送或者是接收消息都是以拷贝的方式进行,如果消息过于庞大,可以将消息的地址作为消息进行发送、接收;队列是具有自己独立权限的内核对象,并不属于任何任务。所有任务都可以向同一队列写入和读出。一个队列由多任务或中断写入是经常的事,但由多个任务读出倒是用的比较少。

9.信号量(Semaphore)是一种实现任务间通信的机制,可以实现任务之间同步或临界资源的互斥访问, 常用于协助一组相互竞争的任务来访问临界资源。在多任务系统中,各任务之间需要同步或互斥实现临界资源的保护,信号量功能可以为用户提供这方面的支持。抽象的来讲,信号量是一个非负整数,所有获取它的任务都会将该整数减一(获取它当然是为了使用资源),当该整数值为零时,所有试图获取它的任务都将处于阻塞状态。通常一个信号量的计数值用于对应有效的资源数,表示剩下的可被占用的互斥资源数。信号量分为:1二值信号量:二值信号量和互斥信号量(以下使用互斥量表示互斥信号量)非常相似,但是有一些细微差别,互斥量有优先级继承机制,二值信号量则没有这个机制。这使得二值信号量更偏向应用于同步功能(任务与任务间的同步或任务和中断间同步),而互斥量更偏向应用于临界资源的访问;2计数信号量:二值信号量可以被认为是长度为1的队列,而计数信号量则可以被认为长度大于1的队列,信号量使用者依然不必关心存储在队列中的消息,只需关心队列是否有消息即可;3互斥(信号)量:互斥信号量其实是特殊的二值信号量,由于其特有的优先级继承机制从而使它更适用于简单互锁,也就是保护临界资源;4递归信号量:就是可以重复获取调用的,本来按照信号量的特性,每获取一次可用信号量个数就会减少一个,但是递归则不然, 对于已经获取递归互斥量的任务可以重复获取该递归互斥量,该任务拥有递归信号量的所有权。任务成功获取几次递归互斥量,就要返还几次,在此之前递归互斥量都处于无效状态,其他任务无法获取,只有持有递归信号量的任务才能获取与释放。

10.互斥量的优先级继承机制:在 FreeRTOS 操作系统中为了降低优先级翻转问题利用了优先级继承算法。优先级继承算法是指,暂时提高某个占有某种资源的低优先级任务的优先级,使之与在所有等待该资源的任务中优先级最高那个任务的优先级相等,而当这个低优先级任务执行完毕释放该资源时,优先级重新回到初始设定值。因此,继承优先级的任务避免了系统资源被任何中间优先级的任务抢占。互斥量与二值信号量最大的不同是:互斥量具有优先级继承机制,而信号量没有。也就是说,某个临界资源受到一个互斥量保护,如果这个资源正在被一个低优先级任务使用,那么此时的互斥量是闭锁状态,也代表了没有任务能申请到这个互斥量,如果此时一个高优先级任务想要对这个资源进行访问,去申请这个互斥量,那么高优先级任务会因为申请不到互斥量而进入阻塞态,那么系统会将现在持有该互斥量的任务的优先级临时提升到与高优先级任务的优先级相同,这个优先级提升的过程叫做优先级继承。这个优先级继承机制确保高优先级任务进入阻塞状态的时间尽可能短,以及将已经出现的"优先级翻转"危害降低到最小。

11.事件是一种实现任务间通信的机制,主要用于实现多任务间的同步,但事件通信只能是事件类型的通信,无数据传输。与信号量不同的是,它可以实现一对多,多对多的同步。即一个任务可以等待多个事件的发生:可以是任意一个事件发生时唤醒任务进行事件处理;也可以是几个事件都发生后才唤醒任务进行事件处理。同样,也可以是多个任务同步多个事件。每一个事件组只需要很少的RAM空间来保存事件组的状态。事件组存储在一个EventBits_t类型的变量中,该变量在事件组结构体中定义。如果configUSE_16_BIT_TICKS定义为1,那么变量uxEventBits就是16位的,其中有8个位用来存储事件组;而如果宏configUSE_16_BIT_TICKS定义为0,那么变量uxEventBits就是32位的,其中有24个位用来存储事件组。任务可以通过设置事件位来实现事件的触发和等待操作。FreeRTOS的事件仅用于同步,不提供数据传输功能。

12.定时器:硬件定时器是芯片本身提供的定时功能。一般是由外部晶振提供给芯片输入时钟,芯片向软件模块提供一组配置寄存器,接受控制输入,到达设定时间值后芯片中断控制器产生时钟中断。硬件定时器的精度一般很高,可以达到纳秒级别,并且是中断触发方式。软件定时器,软件定时器是由操作系统提供的一类系统接口,它构建在硬件定时器基础之上,使系统能够提供不受硬件定时器资源限制的定时器服务,它实现的功能与硬件定时器也是类似的。使用硬件定时器时,每次在定时时间到达之后就会自动触发一个中断,用户在中断中处理信息;而使用软件定时器时,需要我们在创建软件定时器时指定时间到达后要调用的函数(回调函数),在回调函数中处理信息。定时精度与系统时钟的周期有关。一般系统利用 SysTick 作为软件定时器的基础时钟,软件定时器的回调函数类似硬件的中断服务函数,所以回调函数也要快进快出,而且回调函数中不能有任何阻塞任务运行的情况。

13.任务通知:FreeRTOS从V8.2.0版本开始提供任务通知这个功能,每个任务都有一个32位的通知值,在大多数情况下,任务通知可以替代二值信号量、计数信号量、事件组, 也可以替代长度为1的队列(可以保存一个32位整数或指针值)。相对于以前使用FreeRTOS内核通信的资源,必须创建队列、二进制信号量、计数信号量或事件组的情况,使用任务通知显然更灵活,任务通知的使用无需创建队列。想要使用任务通知,必须将FreeRTOSConfig.h中的宏定义configUSE_TASK_NOTIFICATIONS设置为1。

14.CPU利用率:CPU使用率其实就是系统运行的程序占用的CPU资源,表示机器在某段时间程序运行的情况,如果这段时间中,程序一直在占用CPU的使用权,那么可以认为CPU的利用率是100%。CPU的利用率越高,说明机器在这个时间上运行了很多程序,反之较少。FreeRTOS提供了测量各个任务占用CPU时间的函数接口,我们可以知道系统中的每个任务占用CPU的时间,从而得知系统设计的是否合理,想要使用CPU利用率统计的话,需要自定义配置,首先在FreeRTOSConfig.h配置与系统运行时间和任务状态收集有关的配置选项,portCONFIGURE_TIMER_FOR_RUN_TIME_STATS()与portGET_RUN_TIME_COUNTER_VALUE()这两个宏定义需要被实现。

三、FreeRTOSv9.0.0内核源码分析
list.c和list.h

list.h定义了与链表有关的数据结构,主要有typedef struct xLIST_ITEM ListItem_t定义了链表节点,typedef struct xMINI_LIST_ITEM MiniListItem_t定义了一个"小节点",用来在list结构中存储尾节点,typedef struct xLIST list定义了链表。宏configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES用来控制是否在上述结构体中加入校验值(某些特定值,在使用上述结构体时会检查这些值是否被修改来说明是否发生错误),其值默认是0表示不加入校验值。

list.c定义了双向链表操作。主要函数有:

  • 链表初始化函数vListInitialise:将pxIndex指向链表中的尾节点MiniListItem_t类型的xListEnd,并将尾节点的xItemValue值设置为最大值portMAX_DELAY,将尾节点的前驱节点指针后后继节点指针都指向尾节点自己,将链表成员数uxNumberOfItems设置为0;

  • 链表尾插函数vListInsertEnd:将新节点插入到pxIndex所指向节点的前一个位置;

  • 链表插入函数vListInsert:按照待插入节点的xItemValue值,将其按照升序插入链表,如果有节点有相同的值,则插入在具有同样值的节点的最后面;

  • 节点删除函数uxListRemove:删除链表中对应的节点,如果pxIndex指向待删除的节点,则将pxIndex重新指向前一个节点,修改uxNumberOfItems的值并返回uxNumberOfItems的值。

portmacro.h

主要定一些宏定义及开关中断的函数,这些宏定义和函数是与具体架构相关的,文件内容主要包括:

  • 宏定义portYIELD()将portNVIC_INT_CTRL_REG中断控制及状态寄存器的portNVIC_PENDSVSET_BIT位置1,可触发PendSV,产生上下文切换;

  • 宏定义portRECORD_READY_PRIORITY、portRESET_READY_PRIORITY、portGET_HIGHEST_PRIORITY利用clz指令来设置、清除相关任务的优先级和获取系统当前就绪任务最高优先级;

  • static portFORCE_INLINE void vPortSetBASEPRI( uint32_t ulBASEPRI )函数可以设置basepri寄存器的值,以此来打开或关闭中断;

  • static portFORCE_INLINE void vPortRaiseBASEPRI( void )函数用来关中断,不带返回值所以不能嵌套;

  • static portFORCE_INLINE void vPortClearBASEPRIFromISR( void )将寄存器basepri的值设置为0,以此来开启中断;

  • static portFORCE_INLINE uint32_t ulPortRaiseBASEPRI( void )是关中断函数,带返回值所以可以嵌套,返回的是之前的basepri寄存器的值;

  • static portFORCE_INLINE BaseType_t xPortIsInsideInterrupt( void )函数用来判断当前是否是在中断上下文中,若在中断上下文种则返回pdTRUE否则返回pdFALSE,ipsr寄存器中存储了当前执行的中断号。

port.c

主要是一些宏定义和中断处理函数(与具体架构相关的汇编函数),文件内容主要包括:

  • StackType_t *pxPortInitialiseStack( StackType_t *pxTopOfStack, TaskFunction_t pxCode, void *pvParameters )函数用来初始化任务栈,这个函数相当于模拟了任务正常运行时中断发生任务备切换后,任务栈中硬件自动操作后的结果,这个函数只有在任务刚被创建时才会被调用;

  • static void prvTaskExitError( void )是任务函数的返回地址所指向的函数(会在初始化任务栈时入写任务栈中,但Freertos的任务函数一般不会返回,所以一般不会运行到该函数处);

  • asm void vPortSVCHandler( void )是svc中断处理函数,在系统启动后调用第一个任务时是通过svc 0指令触发svc中断然后调用该函数开始执行第一个任务的;

  • asm void prvStartFirstTask( void )函数用来启动系统中的第一个任务,他最后会调用svc 0;

  • BaseType_t xPortStartScheduler( void )函数在调度器中被调用,用来在一开始设置PendSV和Systick的中断优先级,获取当前系统中的优先级分组相关信息,另外调用了vPortSetupTimerInterrupt()初始化了定时器,并调用prvStartFirstTask()开始第一个任务的执行;

  • void vPortEnterCritical( void )函数用来进入临界区,他不能在中断处理函数中被调用;

  • void vPortExitCritical( void )函数用来退出临界区,他不能在中断处理函数中被调用;

  • asm void xPortPendSVHandler( void )是PendSV的中断处理函数,他会调用vTaskSwitchContext改变pxCurrentTCB指针的指向,完成任务的切换;

  • void xPortSysTickHandler( void )是SysTick的中断处理函数,他会进入临界区然后调用xTaskIncrementTick()函数并根据返回值判断是否引起PendSV中断切换任务;

  • weak void vPortSuppressTicksAndSleep( TickType_t xExpectedIdleTime )该函数在设置了宏configUSE_TICKLESS_IDLE时,即提供低功耗运行功能时,可以根据需要使系统低功耗运行相应的tick;

  • void vPortSetupTimerInterrupt( void )函数用来设置systick相关的控制、重装载等寄存器的值;

  • __asm uint32_t vPortGetIPSR( void )函数用来获取ipsr寄存器的值(主要用于指示当前的中断处理状态);

  • void vPortValidateInterruptPriority( void )函数用于验证当前(用户)中断的优先级设置是否符合 FreeRTOS 的要求。

FreeRTOS.h和FreeRTOSConfig.h

FreeRTOS.h中主要是提供了一些可以配置的宏定义,用户可以根据需要在FreeRTOSConfig.h(FreeRTOSConfig.h是用户自定义的配置文件)根据实际应用需要进行配置。FreeRTOS.h中的StaticTask_t、StaticQueue_t、StaticEventGroup_t等结构体定义主要用于静态分配内存时使用,这些结构体和对应的实际定义的任务控制块、消息队列、事件组等结构体的大小是相同的,在静态分配内存时可以用来提前占据空间。

portable.h

该头文件主要包含一些与内存分配相关的宏定义和函数声明,如:宏定义portBYTE_ALIGNMENT表示内存对齐要;pvPortMalloc、vPortFree、vPortInitialiseBlocks分别是内存分配、回收和内存块初始化函数。

task.c和task.h

任务的结构体中的xStateListItem成员是一个链表项,可以被挂载到系统的就绪链表或者阻塞链表中,系统中的就绪链表共有pxReadyTasksLists[ configMAX_PRIORITIES ]个按照任务优先级排列,阻塞链表(也可叫做延时链表)有两个xDelayedTaskList1和xDelayedTaskList2,设置两个是为了在tick计数溢出时切换。任务的结构体中的xEventListItem成员也是一个链表项,可以被挂载到任务等待的事件链表上,这些事件链表可以是消息队列中的xTasksWaitingToSend和xTasksWaitingToReceive(表示任务在等待消息、信号量或互斥量),也可以是事件组中的xTasksWaitingForBits(表示任务在等待事件组相应位的事件发生)。xEventListItem还可以被挂载到xPendingReadyList上表示调度器暂停期间被恢复为就绪状态的任务。这两个文件中主要包括与任务task相关的一些结构体和函数定义,文件内容包括:

  • 宏定义configUSE_PORT_OPTIMISED_TASK_SELECTION用于选择是使用通用方法还是使用优化方法设置当前就绪任务优先级,优化方法借助clz指令获取当前就绪任务的最高优先级,且采用优先级位图记录系统当前就绪任务的优先级集合。

  • 宏定义taskSWITCH_DELAYED_LISTS()在系统tick计数发生溢出时进行pxDelayedTaskList和pxOverflowDelayedTaskList的切换。

  • 宏定义prvAddTaskToReadyList( pxTCB )将就绪任务插入对应的优先级就绪链表(不排序插入最后面),并更新当前系统就绪任务的最高优先级或更新系统优先级位图,此处并没有改变pxCurrentTCB的指向。

  • typedef struct tskTaskControlBlock定义了任务控制块结构体,结构体包含任务栈顶指针pxTopOfStack、任务优先级uxPriority、任务栈(指向栈底)指针pxStack、任务名称pcTaskName等,注意任务栈的sp是按照4个字节移动的。

  • 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 )是静态任务创建函数,创建一个新任务并将其加入任务就绪列表。

  • BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,const char * const pcName,const uint16_t usStackDepth,void * const pvParameters,UBaseType_t uxPriority,TaskHandle_t * const pxCreatedTask )是动态任务创建函数,创建一个新任务并将其加入任务就绪列表,任务所要用到的栈空间和控制块TCB的空间都是pvPortMalloc动态分配的。

  • static void prvInitialiseNewTask( TaskFunction_t pxTaskCode, const char * const pcName, const uint32_t ulStackDepth, void * const pvParameters,UBaseType_t uxPriority, TaskHandle_t * const pxCreatedTask,TCB_t *pxNewTCB, const MemoryRegion_t * const xRegions )是任务TCB结构体初始化函数,设置成功会将pxCreatedTask指向pxNewTCB,而pxNewTCB指向新初始化的任务TCB,这个函数是无法被用户调用的。

  • static void prvAddNewTaskToReadyList( TCB_t *pxNewTCB )函数将新任务加入对应的优先级就绪链表,并根据当前调度器的运行状态进行调度。

  • void vTaskDelete( TaskHandle_t xTaskToDelete )是任务删除函数,会将目标任务从就绪链表或延时链表以及事件链表删除(若果任务在等待消息队列的消息的话),若传入NULL则会删除当前正在运行的任务pxCurrentTCB。

  • void vTaskDelayUntil( TickType_t * const pxPreviousWakeTime, const TickType_t xTimeIncrement )是绝对时间任务阻塞函数,将当前任务相对于其上一次阻塞时间pxPreviousWakeTime阻塞xTimeIncrement 。

  • void vTaskDelay( const TickType_t xTicksToDelay )是相对时间任务阻塞函数,将当前任务相对于当前系统时间xTickCount阻塞xTicksToDelay。

  • eTaskState eTaskGetState( TaskHandle_t xTask )函数用于获取目标任务的当前状态。

  • UBaseType_t uxTaskPriorityGet( TaskHandle_t xTask )函数用于获取目标任务的优先级,传入NULL获取当前任务的优先级。

  • UBaseType_t uxTaskPriorityGetFromISR( TaskHandle_t xTask )函数用于获取目标任务的优先级(用于中断中的),传入NULL获取当前任务的优先级。

  • void vTaskPrioritySet( TaskHandle_t xTask, UBaseType_t uxNewPriority )函数用于设置目标任务的优先级,传入NULL设置当前任务的优先级。

  • void vTaskSuspend( TaskHandle_t xTaskToSuspend )函数用于将传入的任务挂起,被挂起的任务不会再参与调度,若传入NULL则会挂起当前正在运行的任务pxCurrentTCB。

  • static BaseType_t prvTaskIsTaskSuspended( const TaskHandle_t xTask )函数用于判断一个任务是否被挂起了,若任务被挂起了则返回pdTRUE。

  • void vTaskResume( TaskHandle_t xTaskToResume )函数用于将挂起的任务恢复就绪状态,无论任务被挂起多少次,只需要调用该恢复函数一次即可恢复到就绪态。

  • BaseType_t xTaskResumeFromISR( TaskHandle_t xTaskToResume )函数是可在中断中使用的,将被挂起的任务唤醒并转换为就绪状态的函数。

  • void vTaskStartScheduler( void )是调度器函数,创建一个IDLE任务(在创建任务的函数中会发现这是系统第一个任务所以会将pxCurrentTCB指向IDLE),该函数会调用xPortStartScheduler函数启动第一个任务,也就是IDLE(如果在调用该函数之前没创建其他任务的话),并开始调度。该函数还会根据配置选择是否创建软件定时器任务。

  • void vTaskSuspendAll( void )函数用于暂停调度器。

  • BaseType_t xTaskResumeAll( void )函数用于将调度器重新唤醒,调用了多少次vTaskSuspendAll就要调用多少次xTaskResumeAll才能唤醒调度器。

  • TickType_t xTaskGetTickCount( void )函数返回当前系统的tick计数时间值。

  • TickType_t xTaskGetTickCountFromISR( void )函数返回当前系统的tick计数时间值(用于中断中的)。

  • UBaseType_t uxTaskGetNumberOfTasks( void )函数用于获取当前系统总任务数。

  • char *pcTaskGetName( TaskHandle_t xTaskToQuery )函数用于获取任务控制块的名字。

  • BaseType_t xTaskIncrementTick( void )函数用于增加tick计数,时钟中断来临时,systick中断处理函数会调用该函数,视情况选择是否设置portNVIC_PENDSVSET_BIT进行任务调度。

  • void vTaskSwitchContext( void )是任务切换函数,调度器不在运行则不允许进行上下文切换。

  • void vTaskPlaceOnEventList( List_t * const pxEventList, const TickType_t xTicksToWait )函数用于将当前任务插入等待目标事件(可以是消息、信号量、事件组等)的链表,并将当前任务加入延时链表,任务可以被设置成永久阻塞地等待事件。

  • void vTaskPlaceOnUnorderedEventList( List_t * pxEventList, const TickType_t xItemValue, const TickType_t xTicksToWait )函数用于将当前任务加入到事件组(这里等待的事件是事件组)的阻塞等待链表中,并将当前任务加入到延时链表中。

  • void vTaskPlaceOnEventListRestricted( List_t * const pxEventList, TickType_t xTicksToWait, const BaseType_t xWaitIndefinitely )函数用于将当前任务加入指定的事件链表,并将当前任务加入延时链表,xWaitIndefinitely为pdTRUE则会无限阻塞。

  • BaseType_t xTaskRemoveFromEventList( const List_t * const pxEventList )函数用于将pxEventList第一个链表项移出并加入就绪队列(也即优先级最高的那个链表项)。

  • BaseType_t xTaskRemoveFromUnorderedEventList( ListItem_t * pxEventListItem, const TickType_t xItemValue )函数用于将阻塞在事件组(这里等待的事件是事件组)上的任务移除,并将其从延迟链表移除并添加到就绪链表。

  • void vTaskSetTimeOutState( TimeOut_t * const pxTimeOut )函数用于设置等待时间的结构体。

  • BaseType_t xTaskCheckForTimeOut( TimeOut_t * const pxTimeOut, TickType_t * const pxTicksToWait )函数用于检查任务等待消息队列满足要求的等待时间是否到达,pxTimeOut是之前记录的时间结构体,pxTicksToWait是可以等待的时间,返回pdTRUE表示到达时间了。

  • static portTASK_FUNCTION( prvIdleTask, pvParameters )是IDLE任务对应的函数,portTASK_FUNCTION是宏定义,转换后为void prvIdleTask( void *pvParameters )。

  • static void prvInitialiseTaskLists( void )函数用于初始化任务相关的所有链表的函数,用于初始化调度程序使用的所有链表的函数,在创建第一个任务时会自动调用此函数。

  • static void prvCheckTasksWaitingTermination( void )函数用于检查当前是否有任务待删除,这个函数只会被IDLE任务的函数调用。

  • static void prvDeleteTCB( TCB_t *pxTCB )是任务栈和TCB内存空间回收函数,可根据配置释放之前分配的内存空间。

  • static void prvResetNextTaskUnblockTime( void )函数用于获取下一个任务唤醒的时间。

  • TaskHandle_t xTaskGetCurrentTaskHandle( void )函数用于获取当前正在运行的任务。

  • BaseType_t xTaskGetSchedulerState( void )函数用于获取系统调度器的状态。

  • void vTaskPriorityInherit( TaskHandle_t const pxMutexHolder )函数用于实现互斥量的优先级翻转机制。

  • BaseType_t xTaskPriorityDisinherit( TaskHandle_t const pxMutexHolder )函数用于将之前的优先级继承恢复。

  • TickType_t uxTaskResetEventItemValue( void )函数用于重新设置任务控制块中事件链表项的值,将其设置为( TickType_t ) configMAX_PRIORITIES - ( TickType_t ) pxCurrentTCB->uxPriority,说明此时已经不再用于等待事件了,返回值为当前任务等待的目标事件。

  • void *pvTaskIncrementMutexHeldCount( void )函数用将当前正在运行任务持有的互斥量加1。

  • uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit, TickType_t xTicksToWait )是任务通知值获取函数(用于信号量),因为任务通知值只属于任务本身,所以它是私有的,xClearCountOnExit设置读取后是否清0,xTicksToWait设置阻塞时间。

  • BaseType_t xTaskNotifyWait( uint32_t ulBitsToClearOnEntry, uint32_t ulBitsToClearOnExit, uint32_t *pulNotificationValue, TickType_t xTicksToWait )是任务通知值获取函数(该函数一般用于将任务通知用作事件或消息时),因为任务通知值只属于任务本身,所以它是私有的,ulBitsToClearOnEntry用来设置进入时需要清除的位,ulBitsToClearOnExit用来设置退出时需要清除的位,pulNotificationValue获取原本的任务通知值,xTicksToWait设置阻塞时间。

  • BaseType_t xTaskGenericNotify( TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction, uint32_t *pulPreviousNotificationValue )是任务通知发送函数,xTaskToNotify为任务句柄,ulValue为发送的值,eAction为更新通知的方式,pulPreviousNotificationValue用于获取原本的任务通知的值,若传入NULL则不获取 。

  • BaseType_t xTaskGenericNotifyFromISR( TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction, uint32_t *pulPreviousNotificationValue, BaseType_t *pxHigherPriorityTaskWoken )函数用于在中断中向一个任务发送任务通知值。

  • void vTaskNotifyGiveFromISR( TaskHandle_t xTaskToNotify, BaseType_t *pxHigherPriorityTaskWoken )是任务通知值发送函数(用于中断中且通知用作信号量)。

  • BaseType_t xTaskNotifyStateClear( TaskHandle_t xTask )函数用于将任务的通知状态设置为taskNOT_WAITING_NOTIFICATION。

queue.c和queue.h

主要包括与消息队列queue相关的宏定义和函数,消息队列可用来传递消息(任意大小)、信号量(信号量值为0-n)、互斥量,以实现任务间的通信,文件内容主要包括:

  • xQueueRegistry[ configQUEUE_REGISTRY_SIZE ]是一个全局的数组,数组成员为指向各个消息队列的句柄。

  • 宏定义prvLockQueue( pxQueue )用于将消息队列锁定,而不允许在中断中操作阻塞的事件链表。

  • BaseType_t xQueueGenericReset( QueueHandle_t xQueue, BaseType_t xNewQueue )函数用于重置(清空)消息队列,xQueue为待重置的消息队列,xNewQueue若为pdTRUE则表示是初始化消息队列时调用的该函数。

  • QueueHandle_t xQueueGenericCreateStatic( const UBaseType_t uxQueueLength, const UBaseType_t uxItemSize, uint8_t *pucQueueStorage, StaticQueue_t *pxStaticQueue, const uint8_t ucQueueType )是消息队列的静态创建函数,uxQueueLength为消息队列长度(即数据队列可包含消息的个数),uxItemSize为单个消息大小,pucQueueStorage指向消息存储区域,pxStaticQueue指向待初始化的消息队列控制块,ucQueueType为消息类型。

  • QueueHandle_t xQueueGenericCreate( const UBaseType_t uxQueueLength, const UBaseType_t uxItemSize, const uint8_t ucQueueType )是消息队列动态创建函数,uxQueueLength为消息队列长度(即数据队列可包含消息的个数),uxItemSize为单个消息大小,ucQueueType为消息类型。

  • static void prvInitialiseNewQueue( const UBaseType_t uxQueueLength, const UBaseType_t uxItemSize, uint8_t *pucQueueStorage, const uint8_t ucQueueType, Queue_t *pxNewQueue )是消息队列初始化函数,会在消息队列的静态或动态创建函数中被调用,uxQueueLength为消息队列长度(即数据队列可包含消息的个数),uxItemSize为单个消息大小,pucQueueStorage指向消息存储区域,ucQueueType为消息类型,pxNewQueue指向待初始化的消息队列控制块。

  • static void prvInitialiseMutex( Queue_t *pxNewQueue )函数用于初始化互斥量,需要将pxNewQueue->uxQueueType即pxNewQueue->head设置为NULL表示当前消息队列为互斥量。

  • QueueHandle_t xQueueCreateMutex( const uint8_t ucQueueType )函数用于动态创建互斥量(包括普通互斥量和递归互斥量),ucQueueType指明互斥量类型。

  • QueueHandle_t xQueueCreateMutexStatic( const uint8_t ucQueueType, StaticQueue_t *pxStaticQueue )函数用于静态创建互斥量(包括普通互斥量和递归互斥量),ucQueueType指明互斥量类型。

  • BaseType_t xQueueGiveMutexRecursive( QueueHandle_t xMutex )函数用于释放递归互斥量。

  • BaseType_t xQueueTakeMutexRecursive( QueueHandle_t xMutex, TickType_t xTicksToWait )函数用于获取递归互斥量。

  • QueueHandle_t xQueueCreateCountingSemaphoreStatic( const UBaseType_t uxMaxCount, const UBaseType_t uxInitialCount, StaticQueue_t *pxStaticQueue )函数用于静态创建计数信号量并初始化信号量的值为uxInitialCount。

  • QueueHandle_t xQueueCreateCountingSemaphore( const UBaseType_t uxMaxCount, const UBaseType_t uxInitialCount )函数用于动态创建计数信号量,并将信号量初始值设置为uxInitialCount,uxMaxCount为信号量最大值。

  • BaseType_t xQueueGenericSend( QueueHandle_t xQueue, const void * const pvItemToQueue, TickType_t xTicksToWait, const BaseType_t xCopyPosition )是通用的消息发送函数(发送到消息队列,消息队列可以存储消息、信号量或互斥量),xQueue为目标消息队列,pvItemToQueue指向待发送的消息,xTicksToWait为等待时间,xCopyPosition为写入消息的位置。

  • BaseType_t xQueueGenericSendFromISR( QueueHandle_t xQueue, const void * const pvItemToQueue, BaseType_t * const pxHigherPriorityTaskWoken, const BaseType_t xCopyPosition )是通用的(中断中的,消息队列可以存储消息)消息发送函数(发送到消息队列),中断期间不能延时等待,xQueue为目标消息队列,pvItemToQueue指向待发送的消息,pxHigherPriorityTaskWoken指示退出函数后是否要进行上下文切换,xCopyPosition为写入消息的位置。

  • BaseType_t xQueueGiveFromISR( QueueHandle_t xQueue, BaseType_t * const pxHigherPriorityTaskWoken )函数用于在中断中使用,用于释放信号量,使得信号量的值加1。

  • BaseType_t xQueueGenericReceive( QueueHandle_t xQueue, void * const pvBuffer, TickType_t xTicksToWait, const BaseType_t xJustPeeking )是通用的消息接收函数(从消息队列接收,可用于获取消息、信号量或互斥量),xQueue为目标消息队列,pvBuffer指向接收的消息存储目标地址,xTicksToWait为等待时间,xJustPeeking指明是只读取还是读取后删除消息(传入pdFALSE表示读取后删除)。

  • BaseType_t xQueueReceiveFromISR( QueueHandle_t xQueue, void * const pvBuffer, BaseType_t * const pxHigherPriorityTaskWoken )是通用的(中断中的)消息接收函数(从消息队列接收,可用于获取消息、信号量),中断期间不能延时等待,xQueue为目标消息队列,pvBuffer指向接收的消息存储目标地址,pxHigherPriorityTaskWoken指示退出函数后是否要进行上下文切换。

  • BaseType_t xQueuePeekFromISR( QueueHandle_t xQueue, void * const pvBuffer )函数用于在中断中从消息队列读取一个消息,读取后不删除该消息。

  • UBaseType_t uxQueueMessagesWaiting( const QueueHandle_t xQueue )函数用于获取当前消息队列中的消息数量。

  • UBaseType_t uxQueueSpacesAvailable( const QueueHandle_t xQueue )函数用于获取目标消息队列中还能容纳的消息数,即空闲空间。

  • void vQueueDelete( QueueHandle_t xQueue )是消息队列删除函数。

  • static BaseType_t prvCopyDataToQueue( Queue_t * const pxQueue, const void *pvItemToQueue, const BaseType_t xPosition )函数用于将消息写入到目标消息队列的目标位置,pxQueue是目标消息队列,pvItemToQueue是待写入的消息,xPosition指定待写入的位置。

  • static void prvCopyDataFromQueue( Queue_t * const pxQueue, void * const pvBuffer )函数将pxQueue队列中的消息复制到pvBuffer中,实现recieve操作。

  • static void prvUnlockQueue( Queue_t * const pxQueue )函数用于给队列解锁,解锁状态下的消息队列中的两个阻塞链表才可以在中断中被修改,只有在调度器暂停的时候才能调用该函数。

  • static BaseType_t prvIsQueueEmpty( const Queue_t *pxQueue )函数用于判断当前队列是否为空,若是返回pdTRUE。

  • static BaseType_t prvIsQueueFull( const Queue_t *pxQueue )函数用于判断当前队列是否已满,若是返回pdTRUE。

  • void vQueueAddToRegistry( QueueHandle_t xQueue, const char *pcQueueName ) 函数用于将消息队列注册到全局的消息队列数组中。

  • const char *pcQueueGetName( QueueHandle_t xQueue )函数用于获取消息队列句柄对应的消息队列名字。

  • void vQueueUnregisterQueue( QueueHandle_t xQueue )函数用于从全局的消息队列数组中将相应的消息队列注销。

  • void vQueueWaitForMessageRestricted( QueueHandle_t xQueue, TickType_t xTicksToWait, const BaseType_t xWaitIndefinitely )若目标队列为空,则该函数会将当前任务加入等待事件链表并将当前任务阻塞。

semphr.h

该文件中主要是与信号量和互斥量相关的宏定义及函数声明,信号量和互斥量实际上还是通过消息队列实现的,此时的消息队列中的消息大小为0。

event_groups.c和event_groups.h

主要包括与事件组EventGroup相关的一些结构体和函数定义,文件内容包括:

  • EventGroupHandle_t xEventGroupCreateStatic( StaticEventGroup_t *pxEventGroupBuffer )是静态事件组创建函数,返回创建的事件组句柄。

  • EventGroupHandle_t xEventGroupCreate( void )是动态事件组创建函数,返回创建的事件组句柄。

  • EventBits_t xEventGroupSync( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToSet, const EventBits_t uxBitsToWaitFor, TickType_t xTicksToWait )函数的作用是将事件组的某些位uxBitsToSet置位,然后判断当前任务等待的事件位uxBitsToWaitFor是否满足,不满足则阻塞当前任务xTicksToWait。

  • EventBits_t xEventGroupWaitBits( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToWaitFor, const BaseType_t xClearOnExit, const BaseType_t xWaitForAllBits, TickType_t xTicksToWait )是事件等待函数,参数xEventGroup为事件组句柄,uxBitsToWaitFor指明要等待的事件(对应位置1),xClearOnExit指示事件满足后是否清除相应位,xWaitForAllBits指示是等待所有事件还是任一事件(即是与还是或),xTicksToWait为阻塞时间,该函数返回一个事件组状态,还是需要根据返回值判断是当前任务等待的事件满足了返回的还是超时返回的。

  • EventBits_t xEventGroupClearBits( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToClear )是事件组的位清除函数,可以将指定位uxBitsToClear清除。

  • BaseType_t xEventGroupClearBitsFromISR( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToClear )是事件组位清除函数(在中断中使用),清除事件组中的标志位是一个不确定的操作(可能耗时很长),FreeRTOS不允许不确定的操作在中断或临界区中发生,所以通过软件定时器实现。

  • EventBits_t xEventGroupGetBitsFromISR( EventGroupHandle_t xEventGroup )函数在中断在使用,用于获取目标事件组的事件位的状态。

  • EventBits_t xEventGroupSetBits( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToSet )是事件组置位函数,并唤醒在置位相关位之后应解除阻塞的任务。

  • void vEventGroupDelete( EventGroupHandle_t xEventGroup )是事件组删除函数,可将目标事件组删除。

  • static BaseType_t prvTestWaitCondition( const EventBits_t uxCurrentEventBits, const EventBits_t uxBitsToWaitFor, const BaseType_t xWaitForAllBits )函数用来判断任务等待的事件与已经发生的事件是否已经匹配。

  • BaseType_t xEventGroupSetBitsFromISR( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToSet, BaseType_t *pxHigherPriorityTaskWoken )是事件组置位函数(在中断中使用),置位事件组中的标志位是一个不确定的操作(可能耗时很长),FreeRTOS不允许不确定的操作在中断或临界区中发生,所以通过软件定时器实现。

timers.c和timers.h

系统中共设置了两个软件定时器延时链表xActiveTimerList1和xActiveTimerList2,链表上挂载的是将要执行的定时器,另外系统中还有一个软件定时器命令队列,队列上是发送给软件定时器的任务(这个队列可以在tick计数溢出时协助完成软件定时器的设置)。软件定时器任务的优先级是最高的,所以软件定时器任务总是最先被执行的。这两个文件中主要包括与软件定时器timers相关的宏定义和函数,文件内容主要包括:

  • BaseType_t xTimerCreateTimerTask( void )是软件定时器任务的创建函数,prvTimerTask为定时器任务对应的函数。

  • TimerHandle_t xTimerCreate( const char * const pcTimerName,const TickType_t xTimerPeriodInTicks,const UBaseType_t uxAutoReload,void * const pvTimerID,TimerCallbackFunction_t pxCallbackFunction ) 是软件定时器动态创建函数,返回所创建的软件定时器的句柄。

  • TimerHandle_t xTimerCreateStatic( const char * const pcTimerName,const TickType_t xTimerPeriodInTicks,const UBaseType_t uxAutoReload,void * const pvTimerID,TimerCallbackFunction_t pxCallbackFunction,StaticTimer_t *pxTimerBuffer )是软件定时器静态创建函数,返回所创建的软件定时器的句柄。

  • static void prvInitialiseNewTimer( const char * const pcTimerName,const TickType_t xTimerPeriodInTicks,const UBaseType_t uxAutoReload,void * const pvTimerID,TimerCallbackFunction_t pxCallbackFunction,Timer_t *pxNewTimer )是软件定时器初始化函数。

  • BaseType_t xTimerGenericCommand( TimerHandle_t xTimer, const BaseType_t xCommandID, const TickType_t xOptionalValue, BaseType_t * const pxHigherPriorityTaskWoken, const TickType_t xTicksToWait )是定时器命令发送函数,xTimer为定时器句柄,xCommandID代表发送的具体命令,这些命令会被添加到全局的定时器命令队列xTimerQueue上。

  • TaskHandle_t xTimerGetTimerDaemonTaskHandle( void )函数用于返回定时器任务句柄。

  • TickType_t xTimerGetPeriod( TimerHandle_t xTimer )函数用于获取目标定时器的定时周期。

  • TickType_t xTimerGetExpiryTime( TimerHandle_t xTimer )函数用于获取定时器结构体中的xTimerListItem链表项上的值。

  • const char * pcTimerGetName( TimerHandle_t xTimer )函数用于获取目标定时器的名字。

  • static void prvProcessExpiredTimer( const TickType_t xNextExpireTime, const TickType_t xTimeNow )函数用于在当前时间大于定时器应该被响应时间时调用,传入的xNextExpireTime应该是小于等于xTimeNow的,该函数会执行定时器注册的回调函数,并根据情况判断是否要发送命令到全局的定时器命令队列xTimerQueue上。

  • static void prvTimerTask( void *pvParameters )是软件定时器任务对应的函数。

  • static void prvProcessTimerOrBlockTask( const TickType_t xNextExpireTime, BaseType_t xListWasEmpty )函数用于根据实际情况将当前的定时器任务阻塞到下一个定时器时间来临或执行所有到期的定时器的回调函数。

  • static TickType_t prvGetNextExpireTime( BaseType_t * const pxListWasEmpty )函数用于获取当前定时器列表中下一个定时器到期时间。

  • static TickType_t prvSampleTimeNow( BaseType_t * const pxTimerListsWereSwitched )函数判断当前系统tick计数是否溢出,并按照实际情况选择是否切换定时器计数链表。

  • static BaseType_t prvInsertTimerInActiveList( Timer_t * const pxTimer, const TickType_t xNextExpiryTime, const TickType_t xTimeNow, const TickType_t xCommandTime )函数用于判断下一次定时器响应时间是否到达并做出相应的处理,xCommandTime是上一次定时器应该响应时间,xNextExpiryTime是下一次定时器应该响应时间,xTimeNow是当前时间,如果不存在溢出的话xCommandTime应该是小于xTimeNow的,这个函数能处理掉计数值溢出的问题,在prvProcessReceivedCommands中被调用实现定时器正常响应,很关键。

  • static void prvProcessReceivedCommands( void )函数用于接收定时器命令,并根据情况响应执行回调函数。

  • static void prvSwitchTimerLists( void )函数用来切换当前定时器列表,并执行所有到期的定时器的回调函数。

  • static void prvCheckForValidListAndQueue( void )函数用来检查定时器相关的链表和用来传递消息的命令队列是否已经初始化,若未初始化则进行初始化。

  • BaseType_t xTimerIsTimerActive( TimerHandle_t xTimer )函数用于判断目标定时器是否在使用。

  • void *pvTimerGetTimerID( const TimerHandle_t xTimer )函数用于获取目标定时器ID。

  • void vTimerSetTimerID( TimerHandle_t xTimer, void *pvNewID )函数用于设置目标定时器ID。

  • BaseType_t xTimerPendFunctionCallFromISR( PendedFunction_t xFunctionToPend, void *pvParameter1, uint32_t ulParameter2, BaseType_t *pxHigherPriorityTaskWoken )函数用于在中断中设置执行回调函数的软件定时器,可供事件组的xEventGroupSetBitsFromISR和xEventGroupClearBitsFromISR函数使用

heap_*.c

heap_*.c这几个文件主要实现内存管理,void *pvPortMalloc( size_t xWantedSize )函数用来分配内存,分配成功会返回分配的内存的首地址;void vPortFree( void *pv )函数用来释放已分配的内存;size_t xPortGetFreeHeapSize( void )函数返回当前空闲的内存大小;static void prvHeapInit( void );为内存初始化函数。其中:heap_1.c的内存分配算法很简单,就是从前往后按照顺序分配内存,并且已经分配的内存无法被释放并回收;heap_2.c内存分配算法采用最佳分配算法,采用链表管理空闲内存块,按照内存大小升序排列,heap_2.c不能把相邻的两个小的内存块合并成一个大的内存块;heqp_3.c内存分配算法是利用标准C库中的malloc和free实现的;heap_4.c采用的是首次适应算法,空闲内存块链表是按照内存地址大小的升序排列的,若空闲内存相邻则会被合并,heap_4.c使用了一个标记位(xBlockAllocatedBit,用最高位作标记位表示当前内存块已分配与否),所以按道理来说总内存大小在地址空间为32位时不能大于2GB否则可能会出错;heap_5.c内存分配算法在heap_4.c分配算法基础上添加了支持多块物理上不连续的物理内存块的功能。

projdefs.h

这个文件中定义了一些通用的返回值,例如:pdFALSE、pdTRUE、pdPASS、pdFAIL等等。

StackMacros.h

这个文件中定义了一些和栈相关的宏定义,用于栈空间检查。

mpu_prototypes.h和mpu_wrappers.h

这两个文件是和MPU(Memory Protection Unit,内存保护单元)相关的。

croutine.c和croutine.h

这是和协程相关的文件,开启configUSE_CO_ROUTINES宏定义之后才会用到,这个宏定义默认是0表示不使用协程。

相关推荐
leegong2311121 分钟前
学习PostgreSQL专家认证
数据库·学习·postgresql
敲敲敲-敲代码24 分钟前
【SQL实验】触发器
数据库·笔记·sql
Moonnnn.1 小时前
51单片机学习——动态数码管显示
笔记·嵌入式硬件·学习·51单片机
南宫生2 小时前
力扣每日一题【算法学习day.132】
java·学习·算法·leetcode
技术小齐2 小时前
网络运维学习笔记 016网工初级(HCIA-Datacom与CCNA-EI)PPP点对点协议和PPPoE以太网上的点对点协议(此处只讲华为)
运维·网络·学习
竹言笙熙2 小时前
代码审计初探
学习·web安全
日记成书2 小时前
物联网智能项目
物联网·学习
虾球xz3 小时前
游戏引擎学习第118天
学习·游戏引擎
gz927cool3 小时前
大模型做导师之开源项目学习(lightRAG)
学习·开源·mfc
电棍2334 小时前
verilog笔记
笔记·fpga开发