基于 FreeRTOS 的应用程序由一组独立的任务构成——每个任务都是具有独立权限的小程序。这些独立的任务之间很可能会通过相互通信以提供有用的系统功能。FreeRTOS 中所有的通信与同步机制都是基于队列实现的。

一、队列的特性

1.1、数据存储

  队列可以保存有限个具有确定长度的数据单元。队列可以保存的最大单元数目被称为队列的“深度”。在队列创建时需要设定其深度和每个单元的大小。通常情况下,队列被作为 FIFO(先进先出)使用,即数据由队列尾写入,从队列首读出。当然,由队列首写入也是可能的。往队列写入数据是通过字节拷贝把数据复制存储到队列中;从队列读出数据使得把队列中的数据拷贝删除。

1.2、可被多任务存取

  队列是具有自己独立权限的内核对象,并不属于或赋予任何任务。所有任务都可以向同一队列写入和读出。一个队列由多方写入是经常的事,但由多方读出倒是很少遇到

1.3、读队列时阻塞

  当某个任务试图读一个队列时,其可以指定一个阻塞超时时间。在这段时间中,如果队列为空,该任务将保持阻塞状态以等待队列数据有效。当其它任务或中断服务例程往其等待的队列中写入了数据,该任务将自动由阻塞态转移为就绪态。当等待的时间超过了指定的阻塞时间,即使队列中尚无有效数据,任务也会自动从阻塞态转移为就绪态。
  由于队列可以被多个任务读取,所以对单个队列而言,也可能有多个任务处于阻塞状态以等待队列数据有效。这种情况下,一旦队列数据有效,只会有一个任务会被解除阻塞,这个任务就是所有等待任务中优先级最高的任务。而如果所有等待任务的优先级相同,那么被解除阻塞的任务将是等待最久的任务。

1.4、写队列时阻塞

  同读队列一样,任务也可以在写队列时指定一个阻塞超时时间。这个时间是当被写队列已满时,任务进入阻塞态以等待队列空间有效的最长时间。
  由于队列可以被多个任务写入,所以对单个队列而言,也可能有多个任务处于阻塞状态以等待队列空间有效。这种情况下,一旦队列空间有效,只会有一个任务会被解除阻塞,这个任务就是所有等待任务中优先级最高的任务。而如果所有等待任务的优先级相同,那么被解除阻塞的任务将是等待最久的任务。

二、使用队列

2.1、创建队列

  队列在使用前必须先被创建。队列由声明为 xQueueHandle 的变量进行引用。 xQueueCreate()用于创建一个队列,并返回一个 xQueueHandle 句柄以便于对其创建的队列进行引用。
  当创建队列时, FreeRTOS 从堆空间中分配内存空间。分配的空间用于存储队列数据结构本身以及队列中包含的数据单元。如果内存堆中没有足够的空间来创建队列,xQueueCreate()将返回 NULL。

xQueueHandle xQueueCreate( unsigned portBASE_TYPE uxQueueLength,unsigned portBASE_TYPE uxItemSize );

  uxQueueLength 队列能够存储的最大单元数目,即队列深度。
  uxItemSize 队列中数据单元的长度,以字节为单位。
  返回值 NULL 表示没有足够的堆空间分配给队列而导致创建失败。非 NULL 值表示队列创建成功。此返回值应当保存下来,以作为操作此队列的句柄。

2.2、向队列发送消息

  创建好队列以后就可以向队列发送消息了, FreeRTOS 提供的向队列发送消息的 API 函数有xQueueSendToBack() 与 xQueueSendToFront()
  xQueueSendToBack()用于将数据发送到队列尾;而 xQueueSendToFront()用于将数据发送到队列首。xQueueSend()完全等同于 xQueueSendToBack()。
  但不 要 在 中 断 服 务 例 程 中 调 用 xQueueSendToFront() 或xQueueSendToBack()。系统提供中断安全版本的xQueueSendToFrontFromISR()与xQueueSendToBackFromISR()用于在中断服务中实现相同的功能。

portBASE_TYPE xQueueSendToFront( xQueueHandle xQueue,const void * pvItemToQueue,portTickType xTicksToWait );
portBASE_TYPE xQueueSendToBack( xQueueHandle xQueue,const void * pvItemToQueue,portTickType xTicksToWait );

  xQueue 目标队列的句柄。这个句柄即是调用 xQueueCreate()创建该队列时的返回值。
  pvItemToQueue 发送数据的指针。其指向将要复制到目标队列中的数据单元。由于在创建队列时设置了队列中数据单元的长度,所以会从该指针指向的空间复制对应长度的数据到队列的存储区域。
  xTicksToWait 阻塞超时时间。如果在发送时队列已满,这个时间即是任务处于阻塞态等待队列空间有效的最长等待时间。如 果 xTicksToWait 设 为 0 , 并 且 队 列 已 满 , 则xQueueSendToFront()与 xQueueSendToBack()均会立即返回。阻塞时间是以系统心跳周期为单位的,所以绝对时间取决于系统心跳频率。常量 portTICK_RATE_MS 可以用来把心跳时间单位转换为毫秒时间单位。如 果 把 xTicksToWait 设 置 为 portMAX_DELAY , 并 且 在FreeRTOSConig.h 中设定 INCLUDE_vTaskSuspend 为 1,那么阻塞等待将没有超时限制

  返回值 有两个可能的返回值:
  1、 pdPASS 返回 pdPASS 只会有一种情况,那就是数据被成功发送到队列中。如果设定了阻塞超时时间(xTicksToWait 非 0),在函数返回之前任务将被转移到阻塞态以等待队列空间有效—在超时到来前能够将数据成功写入到队列,函数则会返回 pdPASS。
  2、 errQUEUE_FULL 如 果 由 于 队 列 已 满 而 无 法 将 数 据 写 入 , 则 将 返 回errQUEUE_FULL。如果设定了阻塞超时时间( xTicksToWait 非 0),在函数返回之前任务将被转移到阻塞态以等待队列空间有效。但直到超时也没有其它任务或是中断服务例程读取队列而腾出空间,函数则会返回 errQUEUE_FULL。

2.3、从队列读取消息

  出队就是从队列中获取队列项(消息), FreeRTOS 中出队函数xQueueReceive()与 xQueuePeek()
  QueueReceive()用于从队列中接收(读取)数据单元。接收到的单元同时会从队列中删除。xQueuePeek()也是从从队列中接收数据单元,不同的是并不从队列中删出接收到的单元。 xQueuePeek()从队列首接收到数据后,不会修改队列中的数据,也不会改变数据在队列中的存储序顺。
  不要在中断服务例程中调用 xQueueRceive()和 xQueuePeek()。中断安全版本的替代 API 函数 xQueueReceiveFromISR()

portBASE_TYPE xQueueReceive( xQueueHandle xQueue,const void * pvBuffer,portTickType xTicksToWait );
portBASE_TYPE xQueuePeek( xQueueHandle xQueue,const void * pvBuffer,portTickType xTicksToWait );

  xQueue 被读队列的句柄。这个句柄即是调用 xQueueCreate()创建该队列时的返回值。
  pvBuffer 接收缓存指针。其指向一段内存区域,用于接收从队列中拷贝来的数据。数据单元的长度在创建队列时就已经被设定,所以该指针指向的内存区域大小应当足够保存一个数据单元。
  xTicksToWait 阻塞超时时间。如果在接收时队列为空,则这个时间是任务处于阻塞状态以等待队列数据有效的最长等待时间。如果 xTicksToWait 设为 0,并且队列为空,则 xQueueRecieve()与 xQueuePeek()均会立即返回。阻塞时间是以系统心跳周期为单位的,所以绝对时间取决于系统心跳频率。常量 portTICK_RATE_MS 可以用来把心跳时间单位转换为毫秒时间单位。如 果 把 xTicksToWait 设 置 为 portMAX_DELAY , 并 且 在FreeRTOSConig.h 中设定 INCLUDE_vTaskSuspend 为 1,那么阻塞等待将没有超时限制。
  返回值 有两个可能的返回值:
  1、pdPASS 只有一种情况会返回 pdPASS,那就是成功地从队列中读到数据。如果设定了阻塞超时时间(xTicksToWait 非 0),在函数返回之前任务将被转移到阻塞态以等待队列数据有效—在超时到来前能够从队列中成功读取数据,函数则会返回 pdPASS。
  2、errQUEUE_FULL 如果在读取时由于队列已空而没有读到任何数据,则将返回errQUEUE_FULL。如果设定了阻塞超时时间( xTicksToWait 非 0),在函数返回之前任务将被转移到阻塞态以等待队列数据有效。但直到超时也没有其它任务或是中断服务例程往队列中写入数据,函数则会返回errQUEUE_FULL。

2.4、查询队列

  uxQueueMessagesWaiting()用于查询队列中当前有效数据单元个数。
  不要在中断服务例程中调用 uxQueueMessagesWaiting()。应当在中断服务中使用其中断安全版本uxQueueMessagesWaitingFromISR()。

unsigned portBASE_TYPE uxQueueMessagesWaiting( xQueueHandle xQueue );

  xQueue 被查询队列的句柄。这个句柄即是调用 xQueueCreate()创建该队列时的返回值。
  返回值 当前队列中保存的数据单元个数。返回 0 表明队列为空。

三、队列使用实例

  本示范创建一个队列,由多个任务往队列中写数据,以及从队列中把数据读出。这个队列创建出来保存 long 型数据单元。往队列中写数据的任务没有设定阻塞超时时间,而读队列的任务设定了超时时间。往队列中写数据的任务的优先级低于读队列任务的优先级。这意味着队列中永远不会保持超过一个的数据单元。因为一旦有数据被写入队列,读队列任务立即解除阻塞,抢占写队列任务,并从队列中接收数据,同时数据从队列中删除—队列再一次变为空队列

3.1、创建队列

   main()函数在启动调度器之前创建了一个队列和三个任务。尽管对任务的优先级的设计使得队列实际上在任何时候都不可能多于一个数据单元,本例代码还是创建了一个可以保存最多 5 个 long 型值的队列。

/* 声明一个类型为 xQueueHandle 的变量. 其用于保存队列句柄,以便三个任务都可以引用此队列 */
xQueueHandle xQueue;
int main( void )
{/* 创建的队列用于保存最多5个值,每个数据单元都有足够的空间来存储一个long型变量 */xQueue = xQueueCreate( 5, sizeof( long ) );if( xQueue != NULL ){/* 创建两个写队列任务实例,任务入口参数用于传递发送到队列的值。所以一个实例不停地往队列发送100,而另一个任务实例不停地往队列发送200。两个任务的优先级都设为1。 */xTaskCreate( vSenderTask, "Sender1", 1000, ( void * ) 100, 1, NULL );xTaskCreate( vSenderTask, "Sender2", 1000, ( void * ) 200, 1, NULL );/* 创建一个读队列任务实例。其优先级设为2,高于写任务优先级 */xTaskCreate( vReceiverTask, "Receiver", 1000, NULL, 2, NULL );/* 启动调度器,任务开始执行 */vTaskStartScheduler();}else{/* 队列创建失败*/}
}

3.2、写队列

  这个任务被创建了两个实例,一个不停地往队列中写数值 100,而另一个实例不停地往队列中写入数值 200。任务的入口参数被用来为每个实例传递各自的写入值。

static void vSenderTask( void *pvParameters )
{long lValueToSend;portBASE_TYPE xStatus;/* 该任务会被创建两个实例,所以写入队列的值通过任务入口参数传递 – 这种方式使得每个实例使用不同的值。队列创建时指定其数据单元为long型,所以把入口参数强制转换为数据单元要求的类型 */lValueToSend = ( long ) pvParameters;/* 和大多数任务一样,本任务也处于一个死循环中 */for( ;; ){/* 往队列发送数据第一个参数是要写入的队列。队列在调度器启动之前就被创建了,所以先于此任务执行。第二个参数是被发送数据的地址,本例中即变量lValueToSend的地址。第三个参数是阻塞超时时间 – 当队列满时,任务转入阻塞状态以等待队列空间有效。本例中没有设定超时时间,因为此队列决不会保持有超过一个数据单元的机会,所以也决不会满。*/xStatus = xQueueSendToBack( xQueue, &lValueToSend, 0 );if( xStatus != pdPASS ){/* 发送操作由于队列满而无法完成 – 这必然存在错误,因为本例中的队列不可能满。 */vPrintString( "Could not send to the queue.\r\n" );}/* 允许其它发送任务执行。 taskYIELD()通知调度器现在就切换到其它任务,而不必等到本任务的时间片耗尽 */taskYIELD();}
}

3.3、读队列

  读队列任务设定了 100 毫秒的阻塞超时时间,所以会进入阻塞态以等待队列数据有效。一旦队列中数据单元有效,或者即使队列数据无效但等待时间超过 100 毫秒,此任务将会解除阻塞。在本例中,将永远不会出现 100 毫秒超时,因为有两个任务在不停地往队列中写数据。

static void vReceiverTask( void *pvParameters )
{/* 声明变量,用于保存从队列中接收到的数据。 */long lReceivedValue;portBASE_TYPE xStatus;const portTickType xTicksToWait = 100 / portTICK_RATE_MS;/* 本任务依然处于死循环中。 */for( ;; ){/* 此调用会发现队列一直为空,因为本任务将立即删除刚写入队列的数据单元。 */if( uxQueueMessagesWaiting( xQueue ) != 0 ){vPrintString( "Queue should have been empty!\r\n" );}/* 从队列中接收数据第一个参数是被读取的队列。队列在调度器启动之前就被创建了,所以先于此任务执行。第二个参数是保存接收到的数据的缓冲区地址,本例中即变量lReceivedValue的地址。此变量类型与队列数据单元类型相同,所以有足够的大小来存储接收到的数据。第三个参数是阻塞超时时间 – 当队列空时,任务转入阻塞状态以等待队列数据有效。本例中常量portTICK_RATE_MS用来将100毫秒绝对时间转换为以系统心跳为单位的时间值。*/xStatus = xQueueReceive( xQueue, &lReceivedValue, xTicksToWait );if( xStatus == pdPASS ){/* 成功读出数据,打印出来。 */vPrintStringAndNumber( "Received = ", lReceivedValue );}else{/* 等待100ms也没有收到任何数据。必然存在错误,因为发送任务在不停地往队列中写入数据 */vPrintString( "Could not receive from the queue.\r\n" );}}
}

3.4、运行结果显示

  写队列任务在每次循环中都调用 taskYIELD()。 taskYIELD()通知调度器立即进行任务切换,而不必等到当前任务的时间片耗尽。某个任务调用 taskYIELD()等效于其自愿放弃运行态。由于本例中两个写队列任务具有相同的任务优先级,所以一旦其中一个任务调用了 taskYIELD(),另一个任务将会得到执行 — 调用 taskYIELD()的任务转移到就绪态,同时另一个任务进入运行态。这样就可以使得这两个任务轮翻地往队列发送数据。
下图 中可以看到输出结果。

FreeRTOS 队列管理相关推荐

  1. FreeRTOS学习-队列管理

    1. 简介 在FreeRTOS中,提供了多种任务间通讯的机制,包括消息队列.信号量和互斥锁.事件组.任务通知,他们的总体特征如下图所示: 从图中可以看出,消息队列.信号量和互斥锁.事件组都是间接的任务 ...

  2. 41 freertos内存管理试验 1

    四十一.freertos内存管理试验 1 /** *************************************************************************** ...

  3. com.ibm.msg.client.jms.DetailedJMSSecurityException: JMSWMQ2013: 为队列管理器提供的安全性认证无效...

    com.ibm.msg.client.jms.DetailedJMSSecurityException: JMSWMQ2013: 为队列管理器"zm_queue_manager"提 ...

  4. tensorflow随笔-队列管理器QueueRunner-生产者与消费者

    # -*- coding: utf-8 -*- """ Spyder EditorThis is a temporary script file. "" ...

  5. IBM MQ - 连接远程队列管理器报AMQ4036错误

    解决方法 :  首先确定好服务器连接通道是否正常,如SERVER_CHL: 修改其相关属性 :  ALTER CHL('SERVER_CHL') CHLTYPE(SVRCONN) MCAUSER('m ...

  6. 队列管理器连接数设置_详解!基于Redis解决业务场景中延迟队列的应用实践,你不得不服啊...

    一.业务概述 我们假定设置两个队列,一个队列维护正式工单,另一个队列维护挂起工单.对于挂起操作,我们通过Redis设置key有效时间,当key失效时,客户端监听失效事件,获取工单,实现 挂起工单队列的 ...

  7. tensorflow : 队列管理 FIFOQueue amp;amp; RandomShuffleQueue

    『TensorFlow』第十弹_队列&多线程_道路多坎坷 目录 一.基本队列: tf.FIFOQueue(2,'int32') tf.RandomShuffleQueue(capacity=1 ...

  8. NS2 队列管理机制

    两种传统的包的调度策略 在介绍Drop Tail之前,我们先介绍两种传统的包的调度策略-决定包的传送顺序. FIFO (First In First  Out,先进先出) 是一种经典的包调度策略,它的 ...

  9. 网络中常用的队列管理方法比较

    队列管理属于链路IP层的拥塞控制策略,主要是在路由器中采用排队算法和数据包丢弃策略.排队算法通过决定哪些包可以传输来分配带宽,而丢弃策略通过决定哪些包被丢弃来分配缓存. 1.先进先出(FIFO,Fir ...

最新文章

  1. 基因序列算法:编辑距离( Levenshtein 距离)和LD算法
  2. 图神经网络的二阶池化:从节点表示中学习图的表示
  3. 基于 jQuery支持移动触摸设备的Lightbox插件
  4. 《大话设计模式》读书笔记-建造者模式
  5. 快乐学习 Ionic Framework+PhoneGap 手册1-5 {IO开关}
  6. Django中的cookie与session
  7. yii 提交表单报400错误,提示 “您提交的数据无法验证”,问题处理。
  8. myeclipse 创建work set 后不显示的解决方法
  9. 许晓斌_Maven实战(八)——常用Maven插件介绍(下)
  10. 线性方程组matlab解法,线性方程组解法及其MATLAB实践
  11. Electron屏幕截图的技术方案MacWindows
  12. html中盒子的定位,css盒子的定位有哪些方法?
  13. 如何打造企业短视频账号的人设?
  14. Spring Security登录成功后重定向到登陆前页面 解决方案
  15. esxi设置群晖核显直通
  16. 机器学习笔记(吴恩达老师)
  17. Kalibr安装教程
  18. Q-learning例1探索者【学习笔记3】
  19. mysql sin() 函数
  20. stm32输入捕获,捕获高电平

热门文章

  1. 选择一个手持式频谱分析仪 需要参考哪些条件
  2. GTX960M安装tensorflow-gpu心得
  3. 文献管理软件 linux,5款常用的文献管理软件
  4. vuepress(六)阿里云二级域名配置与添加SSL证书
  5. 钱颖一:人的创造力从哪里来?(转)
  6. C# 特性Description的值的获取
  7. Halcon 第三章『Morphology形态学』◆第3节:顶帽运算与底帽运算
  8. html跳动的心,css3跳动的心制作
  9. 自学SQL网习题答案
  10. Suzy找到实习了吗Day 17 | 二叉树进行中:110. 平衡二叉树,257 二叉树的所有路径,404. 左叶子之和