同读队列一样,任务也可以在写队列时指定一个阻塞超时时间。这个时间是当被写队列已满时,任务进入阻塞态以等待队列空间有效的最长时间。
2-2队列的使用
队列由声明为xQueueHandle 的变量进行引用。xQueueCreate()用于创建一个队列,并返回一个xQueueHandle 句柄以便于对其创建的队列进行引用。
当创建队列时,FreeRTOS 从堆空间中分配内存空间。分配的空间用于存储队列数据结构本身以及队列中包含的数据单元。如果内存堆中没有足够的空间来创建队列,xQueueCreate()将返回NULL。
xQueueSendToBack() 与xQueueSendToFront() API 函数
xQueueSendToBack()用于将数据发送到队列尾;而xQueueSendToFront()用于将数据发送到队列首。
切记不要在中断服务例程中调用xQueueSendToFront() 或
xQueueSendToBack()。系统提供中断安全版本xQueueSendToFrontFromISR()与xQueueSendToBackFromISR()用于在中断服务中实现相同的功能。
xQueueReceive()与xQueuePeek() API 函数 xQueueReceive()用于从队列中接收(读取)数据单元。接收到的单元同时会从队列中删除。
xQueuePeek()也是从从队列中接收数据单元,不同的是并不从队列中删出接收到的单元。xQueuePeek()从队列首接收到数据后,不会修改队列中的数据,也不会改变数据在队列中的存储顺序。
uxQueueMessagesWaiting() API 函数
uxQueueMessagesWaiting()用于查询队列中当前有效数据单元个数。 使用队列传递复合数据类型
一个任务从单个队列中接收来自多个发送源的数据是经常的事。通常接收方收到数据后,需要知道数据的来源,并根据数据的来源决定下一步如何处理。一个简单的方式就是利用队列传递结构体,结构体成员中就包含了数据信息和来源信息。图2对这一方案进行了展现。
图2结构体被用于队列传递的一种情形
从图2 中可以看出:
? 创建一个队列用于保存类型为xData 的结构体数据单元。结构体成员包括了一个数据值和表示数据含义的编码,两者合为一个消息可以一次性发送到队列。
? 中央控制任务用于完成主要的系统功能。其必须对队列中传来的输入和其它系统状态的改变作出响应。 ? CAN 总线任务用于封装CAN 总线的接口功能。当CAN 总线任务收到并解码一个消息后,其将把解码后的消息放到xData 结构体中发往控制任务。结构体的iMeaning成员用于让中央控制任务知道这个数据是用来干什么的— 从图中的描述可以看出,这个数据表示电机速度。结构体的iValue 成员可以让中央控制任务知道电机的实际速度值。
? 人机接口(HMI)任务用于对所有的人机接口功能进行封装。设备操作员可能通过各种方式进行命令输入和参数查询,人机接口任务需要对这些操作进行检测并解析。当接收到一个新的命令后,人机接口任务通过xData 结构将命令发送到中央控制任务。结构体的iMeaning 成员用于让中央控制任务知道这个数据是用来干什么的— 从图中的描述可以看出,这个数据表示一个新的参数设置。结构体的iValue 成员可以让中央控制任务知道具体的设置值。
3 FreeRTOS中断管理
3-1延迟中断处理(采用二值信号量同步)
二值信号量可以在某个特殊的中断发生时,让任务解除阻塞,相当于让任务与中断同步。这样就可以让中断事件处理量大的工作在同步任务中完成,中断服务例程(ISR)中只是快速处理少部份工作。中断处理可以说是被“推迟(deferred)”到一个“处理(handler)”任务。
如果某个中断处理要求特别紧急,其延迟处理任务的优先级可以设为最高,以保证延迟处理任务随时都抢占系统中的其它任务。这样,延迟处理任务就成为其对应的ISR退出后第一个执行的任务,在时间上紧接着ISR 执行,相当于所有的处理都在ISR 中完成一样。
延迟处理任务对一个信号量进行带阻塞性质的“take”调用,意思是进入阻塞态以等待事件发生。当事件发生后,ISR 对同一个信号量进行“give”操作,使得延迟处理任务解除阻塞,从而事件在延迟处理任务中得到相应的处理。
在这种中断同步的情形下,信号量可以看作是一个深度为1 的队列。这个队列由于最多只能保存一个数据单元,所以其不为空则为满(所谓“二值”)。延迟处理任务调用xSemaphoreTake()时,等效于带阻塞时间地读取队列,如果队列为空的话任务则进入阻塞态。当事件发生后,ISR 简单地通过调xSemaphoreGiveFromISR()放置一个令牌(信号量)到队列中,使得队列成为满状态。这也使得延迟处理任务切出阻塞态,并移除令牌,使得队列再次成为空。当任务完成处理后,再次读取队列,发现队列为空,又进入阻塞态,等待下一次事件发生。
4 FreeRTOS时间管理
FreeRTOS提供的典型时间管理函数是vTaskDelay(),调用此函数可以实现将任务延时一段特定时间的功能。在FreeRT0S中,若一个任 务要延时
xTicksToDelay个时钟节拍,系统内核会把当前系统已运行的时钟节拍总数(定义为xTickCount,32位长度)加上xTicksToDelay得到任务下次唤醒时的时钟节拍数xTimeToWake。然后,内核把此任务的任务控制块从就绪链表中删除,把 xTimeToWake作为结点值赋予任务的xItemValue,再根据xTimeToWake的值把任务控制块按照顺序插入不同的链表。若 xTimeToWake>xTickCount,即计算中没有出现溢出,内核把任务控制块插入到pxDelayedTaskList链表;若xTimeToWak e 每发生一个时钟节拍,内核就会把当前的xTick-Count加1。若xTickCount的结果为0,即发生溢出,内核会把pxOverflowDelayedTaskList作为当前链表;否则,内核把pxDelaycdTaskList作为当前链表。内核依次比较 xTickCotlrtt和链表各个结点的xTimcToWake。若xTick-Count等于或大于xTimeToWake,说明延时时间已到,应该 把任务从等待链表中删除,加入就绪链表。 由此可见,不同于μC/OS—II,FreeRTOS采用“加”的方式实现时间管理。其优点是时间节拍函数的执行时间与任务数量基本无关,而μC/OS— II的OSTimcTick()的执行时间正比于应用程序中建立的任务数。因此当任务较多时,FreeRTOS采用的时间管理方式能有效加快时钟节拍中断程序的执行速度。 5 FreeRTOS内存分配 每当任务、队列和信号量创建的时候,FreeRTOS要求分配一定的RAM。虽然采用malloc()和free()函数可以实现申请和释放内存的功能, 但这两个函数存在以下缺点:并不是在所有的嵌入式系统中都可用,要占用不定的程序空间,可重人性欠缺以及执行时间具有不可确定性。为此,除了可采用 malloc()和free()函数外,FreeRTOS还提供了另外两种内存分配的策略,用户可以根据实际需要选择不同的内存分配策略。 第1种方法是,按照需求内存的大小简单地把一大块内存分割为若干小块,每个小块的大小对应于所需求内存的大小。这样做的好处是比较简单,执行时间可严格确 定,适用于任务和队列全部创建完毕后再进行内核调度的系统;这样做的缺点是,由于内存不能有效释放,系统运行时应用程序并不能实现删除任务或队列。 第2种方法是,采用链表分配内存,可实现动态的创建、删除任务或队列。系统根据空闲内存块的大小按从小到大的顺序组织空闲内存链表。当应用程序申请一块内 存时,系统根据申请内存的大小按顺序搜索空闲内存链表,找到满足 申请内存要求的最小空闲内存块。为了提高内存的使用效率,在空闲内存块比申请内存大的情况 下,系统会把此空闲内存块一分为二。一块用于满足申请内存的要求,一块作为新的空闲内存块插入到链表中。 下面以图3为例介绍方法2的实现。假定用于动态分配的RAM共有8KB,系统首先初始化空闲内存块链表,把8KB RAM全部作为一个空闲内存块。当应用程序分别申请1KB和2KB内存后,空闲内存块的大小变为5KB。2KB的内存使用完毕后,系统需要把2KB插入 到现有的空闲内存块链表。由于2 KB<5KB,所以把这2 KB插入5KB的内存块之前。若应用程序又需要申请3 KB的内存,而在空闲内存块链表中能满足申请内存要求的最小空闲内存块为5KB,因此把5KB内存拆分为2部分,3KB部分用于满足申请内存的需 要,2KB部分作为新的空闲内存块插入链表。随后1KB的内存使用完毕需要释放,系统会按顺序把1KB内存插入到空闲内存链表中。 图3 采用空闲内存块链表进行内存管理 方法2的优点是,能根据任务需要高效率地使用内存,尤其是当不同的任务需要不同大小的内存的时候。方法二的缺点是,不能把应用程序释放的内存和原有的空闲 内存混合为一体,因此,若应用程序频繁申请与释放“随机”大小的内存,就可能造成大量的内存碎片。这就要求应用程序申请与释放内存的大小为“有限个”固定 的值(如图3中申请与释放内存的大小固定为l KB、2 KB或3 KB)。方法2的另一个缺点是,程序执行时间具有一定的不确定性。 μC/OS—II提供的内存管理机制是把连续的大块内存按分区来管理,每个分区中包含整数个大小相同的内存块。由于每个分区的大小相同,即使频繁地申请和 释放内存也不会产生内存碎片问题,但其缺点是内存的利用率相对不高。当申请和释放的内存大小均为一个固定值时(如均为2 KB),FreeRTOS的方法2内存分配策略就可以实现类似μC/OS—Ⅱ的内存管理效果。 6 STM32中FreeRTOS的移植 FreeRTOS源码包结构 FreeRTOS的实现主要由list.c、queue.c、croutine.c和tasks.c4个文件组成。list.c是一个链表的实现,主要供给内核调度器使用;queue.c是一个队列的实现,支持中断环境和信号量控制;croutine.c和task.c是两种任务的组织实现。对于croutine,各任务共享同一个堆栈,使RAM的需求进一步缩小,但也正因如此,他的使用受到相对严格的限制。而task则是传统的实现,各任务使用各自的堆栈,支持完全的抢占式调度。 1)与FreeRTOS内核有关的文件数量仅为5个,分别是list.c queue.c tasks.ccroutine.c timers.c 这些文件位于FreeRTOS\\Source 2)与内存分配有关的文件共有4个,分别是heap_1.c,heap_2.c,heap_3.c,heap_4.c。4个文件只需选择其中的1个,STM32选择heap_2.c。 该文件位于FreeRTOS\\Source\\portable\\MemMang 3)与移植相关的代码包括port.c, portmacro.h。这些代码不但和编译器有关还和平台(MCU)有关。FreeRTOS先以编译器为大类,然后再以平台(MCU)为小类。在这里选择KEIL编译器,平台为ARM_CM3。 该文件位于FreeRTOS\\Source\\portable\\RVDS\\ARM_CM3(KEIL与RVDS用同一个源码,所以KEIL里没有提供源码,直接从RVDS里取) 4)除了上述内容之外,还包括FreeRTOS内核相关的头文件。 该文件FreeRTOS\\Source\\include FreeRTOS添加到KEIL 1、在KEIL工程目录下添加一个FreeRTOS的目录文件并添加上述列举STM32需要的.C文件具体如下图: