一、queue在freertos中有什么作用?

Queues’ provide a task-to-task, task-to-interrupt, and interrupt-to-task communication  mechanism.

队列提供了一种任务间或者任务和中断间的通讯机制。

二、什么是队列?

队列是一种数据结构,可以保存固定大小的数据。在创建队列时,队列的长度和大小就确认下来了。通常情况下队列是先进先出(First In First Out),即新数据被发送到队列的尾部,从头部取数据。不过数据也可以发送到队列头部,取数据也是从头部。

如下示意图,一个队列用于TaskA和TaskB之间的通讯,该队列能够保存五个整数。TaskA先后向队列写入了“1”、“2”、“3”三个消息。TaskB依次从quene中取了三条消息。

多个任务可以对同一个队列操作(读、写)

运行过程主要有以下两种情况:

如果放数据的速度快于取数据的速度,那么会出现消息队列存放满的情况,FreeRTOS的消息存放函数xQueueSend支持超时等待,用户可以设置超时等待,直到有空间可以存放消息或者设置的超时时间溢出。队列可以有多个写入者,因此一个队列可能会阻塞多个任务以等待完成发送操作。 在这种情况下,当队列上的空间可用时,优先级最高的任务会被解除阻塞,从而向队列写数据;如果被阻塞的任务具有相同的优先级,那么等待空间最长的任务将被解除阻塞。

如果放数据的速度慢于取数据的速度,那么会出现消息队列为空的情况,FreeRTOS的消息获取函数xQueueReceive支持超时等待,用户可以设置超时等待,任务将保持在阻塞状态以等待队列中可用数据或者设置的超时时间溢出。如果多个任务都处在阻塞状态等待数据,那么一旦数据准备完毕,优先级最高的任务可以首先获得数据。如果优先级相同,等待时间越长的任务先获得数据。

三、使用队列

freertos中使用一个结构体来管理队列,下面的所有API函数都是围绕这个结构体展开,结构体如下:

typedef struct QueueDefinition   * QueueHandle_t;
typedef struct QueueDefinition /* The old naming convention is used to prevent breaking kernel aware debuggers. */
{int8_t * pcHead;           /*< Points to the beginning of the queue storage area. */int8_t * pcWriteTo;        /*< Points to the free next place in the storage area. */union{QueuePointers_t xQueue;     /*< Data required exclusively when this structure is used as a queue. */SemaphoreData_t xSemaphore; /*< Data required exclusively when this structure is used as a semaphore. */} u;List_t xTasksWaitingToSend;             /*< List of tasks that are blocked waiting to post onto this queue.  Stored in priority order. */List_t xTasksWaitingToReceive;          /*< List of tasks that are blocked waiting to read from this queue.  Stored in priority order. */volatile UBaseType_t uxMessagesWaiting; /*< The number of items currently in the queue. */UBaseType_t uxLength;                   /*< The length of the queue defined as the number of items it will hold, not the number of bytes. */UBaseType_t uxItemSize;                 /*< The size of each items that the queue will hold. */volatile int8_t cRxLock;                /*< Stores the number of items received from the queue (removed from the queue) while the queue was locked.  Set to queueUNLOCKED when the queue is not locked. */volatile int8_t cTxLock;                /*< Stores the number of items transmitted to the queue (added to the queue) while the queue was locked.  Set to queueUNLOCKED when the queue is not locked. */#if ( ( configSUPPORT_STATIC_ALLOCATION == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )uint8_t ucStaticallyAllocated; /*< Set to pdTRUE if the memory used by the queue was statically allocated to ensure no attempt is made to free the memory. */#endif#if ( configUSE_QUEUE_SETS == 1 )struct QueueDefinition * pxQueueSetContainer;#endif#if ( configUSE_TRACE_FACILITY == 1 )UBaseType_t uxQueueNumber;uint8_t ucQueueType;#endif
} xQUEUE;
API原型 函数说明 参数说明 返回值
QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength, UBaseType_t uxItemSize );
用于创建一个队列,并返回一个 xQueueHandle 句柄 。当创建队列时, FreeRTOS 从堆空间中分配内存空间(用于存储队列数据结构本身以及队列中包含的数据单元),并进行相关初始化
 
  • uxQueueLength:队列能够存储的最大单元数目,即队列深度。
  • uxItemSize:队列中数据单元的长度,以字节为单位
NULL 表示没有足够的堆空间分配给队列而导致创建失败。
非 NULL 值表示队列创建成功。此返回值应当保存下来,以作为
操作此队列的句柄
BaseType_t xQueueSendToBack( QueueHandle_t xQueue,
const void * pvItemToQueue,
TickType_t xTicksToWait );
xQueueSendToBack()用于将数据发送到队列尾。一般都是用的这种方式。中断中使用xQueueSendToBackFromISR
  • xQueue:目标队列的句柄
  • pvItemToQueue:发送数据的指针
  • xTicksToWait:阻塞超时时间,设置为0表示不等待

pdPASS:数据被成功发送到队列

errQUEUE_FULL
:如 果 由 于 队 列 已 满 而 无 法 将 数 据 写 入(超时时间过了依旧不能写入)

BaseType_t xQueueSendToFront( QueueHandle_t xQueue,
const void * pvItemToQueue,
TickType_t xTicksToWait );

用于将数据发送到队列首,中断中使用xQueueSendToFrontFromISR

同上

同上

xQueueOverwrite( QueueHandle_t xQueue, const void * pvItemToQueue )
该API仅仅在队列长度为1的情况下使用(也就是mailbox)。和xQueueSendToBack不同的是,当队列满的时候,直接覆盖写。
  • xQueue:目标队列的句柄
  • pvItemToQueue:发送数据的指针
只有pdPASS
BaseType_t xQueueReceive( QueueHandle_t xQueue,
void * const pvBuffer,
TickType_t xTicksToWait );

用于从队列头部接收(读取)数据元素。接收到的元素同时会从队列中删除。中断中使用xQueueReceiveFromISR函数

  • xQueue:目标队列的句柄
  • pvBuffer:接受数据的指针
  • xTicksToWait:阻塞超时时间,设置为0表示不等待,立即返回

pdPASS:成功从队列中读取到数据

errQUEUE_EMPTY
:由 于 队 列 为空 导致读取数据失败

BaseType_t xQueuePeek( QueueHandle_t xQueue,
void * const pvBuffer,
TickType_t xTicksToWait )
从队列头部读取数据元素。和xQueueReceive不同的是,读取之后,不会将队列中的内容删除掉。在中断中使用

xQueuePeekFromISR()
同上 同上
uxQueueMessagesWaiting

用于查询队列中当前有效数据单元个数。中断中使用uxQueueMessagesWaitingFromISR

四、使用示例

示例1、

Task 发送/接收 指定超时时间 优先级
Sender1
循环发送100 0 1
Sender2
循环发送200 0 1
Receiver
接收 100ms 2

源码:

QueueHandle_t xQueue;
int main( void )
{xQueue = xQueueCreate( 5, sizeof( int32_t ) );if( xQueue != NULL ){xTaskCreate( vSenderTask, "Sender1", 1000, ( void * ) 100, 1, NULL );xTaskCreate( vSenderTask, "Sender2", 1000, ( void * ) 200, 1, NULL );xTaskCreate( vReceiverTask, "Receiver", 1000, NULL, 2, NULL );vTaskStartScheduler();}for( ;; );
}static void vSenderTask( void *pvParameters )
{int32_t lValueToSend;BaseType_t xStatus;lValueToSend = ( int32_t ) pvParameters;for( ;; ){xStatus = xQueueSendToBack( xQueue, &lValueToSend, 0 );if( xStatus != pdPASS ){vPrintString( "Could not send to the queue.\r\n" );}}
}static void vReceiverTask( void *pvParameters )
{int32_t lReceivedValue;BaseType_t xStatus;const TickType_t xTicksToWait = pdMS_TO_TICKS( 100 );for( ;; ){/* This call should always find the queue empty because this task willimmediately remove any data that is written to the queue. */if( uxQueueMessagesWaiting( xQueue ) != 0 ){vPrintString( "Queue should have been empty!\r\n" );}xStatus = xQueueReceive( xQueue, &lReceivedValue, xTicksToWait );if( xStatus == pdPASS ){vPrintStringAndNumber( "Received = ", lReceivedValue );}else{vPrintString( "Could not receive from the queue.\r\n" );}}
}

执行结果:

分析:

  • 两个发送任务的优先级相等,因此,轮流被调度
  • 接受任务优先级高于发送队列优先级,这就导致队列中元素个数永远小于等于1,因为一旦有数据放入队列,接受任务就会立马抢占,将数据取走

示例2、接受处理多个源发送的数据

一项任务从多个源接收数据是很常见的。 接收任务需要知道数据来自哪里,以确定应该如何处理数据。 一个简单的设计解决方案是使用单个队列来传输结构体,结构体中包含数据值和数据源信息。

Task 发送/接收 指定超时时间 优先级
Sender1
循环发送xStructsToSend[0] 100ms 2
Sender2
循环发送xStructsToSend[1] 100ms 2
Receiver
接收 0 1

源码:

typedef enum
{eSender1,eSender2
} DataSource_t;typedef struct
{uint8_t ucValue;DataSource_t eDataSource;
} Data_t;static const Data_t xStructsToSend[ 2 ] =
{{ 100, eSender1 }, /* Used by Sender1. */{ 200, eSender2 } /* Used by Sender2. */
};int main( void )
{xQueue = xQueueCreate( 3, sizeof( Data_t ) );if( xQueue != NULL ){xTaskCreate( vSenderTask, "Sender1", 1000, &( xStructsToSend[ 0 ] ), 2, NULL );xTaskCreate( vSenderTask, "Sender2", 1000, &( xStructsToSend[ 1 ] ), 2, NULL );xTaskCreate( vReceiverTask, "Receiver", 1000, NULL, 1, NULL );vTaskStartScheduler();}for( ;; );
}static void vSenderTask( void *pvParameters )
{BaseType_t xStatus;const TickType_t xTicksToWait = pdMS_TO_TICKS( 100 );for( ;; ){xStatus = xQueueSendToBack( xQueue, pvParameters, xTicksToWait );if( xStatus != pdPASS ){vPrintString( "Could not send to the queue.\r\n" );}}
}static void vReceiverTask( void *pvParameters )
{Data_t xReceivedStructure;BaseType_t xStatus;for( ;; ){if( uxQueueMessagesWaiting( xQueue ) != 3 ){vPrintString( "Queue should have been full!\r\n" );}xStatus = xQueueReceive( xQueue, &xReceivedStructure, 0 );if( xStatus == pdPASS ){if( xReceivedStructure.eDataSource == eSender1 ){vPrintStringAndNumber( "From Sender 1 = ", xReceivedStructure.ucValue );}else{vPrintStringAndNumber( "From Sender 2 = ", xReceivedStructure.ucValue );}}else{vPrintString( "Could not receive from the queue.\r\n" );}}
}

执行结果:

分析:

  • t1:sender1 开始发送数据到queue
  • t2:由于sender1发送了三次数据导致queue满,从而sender1进行Blocked state;此时sender2具有最高优先级,sender2进入Running state
  • t3:sender2发现queue满,也进入Blocked state。此时,receiver进入Running state
  • t4:receiver从队列中取出一个元素后,该任务立马sender1被抢占(sender1和sender2优先级相等,sender1等待的时间比sender2长)。
  • t5:sender1向队列中写入一个元素后,队列满,又进入阻塞状态。此时sender2也处于阻塞状态,因此不会被调度器调度。从而receiver进入Running state,从队列中取出一个元素后,立马被sender2抢占(sender2等待的时间比sender1长)。
  • t6:重复以上过程

和示例1不同的是,示例2的队列几乎一直是满的!

示例3、如何使用队列传输大数据?

队列中保存的数据很大,更好的办法是不直接传输数据本身,传输数据的指针。在使用的时候需要注意两点:

  • 确保发送任务和接受任务不会同时修改指针指向的内存
  • 如果指针指向的内存是动态分配的,则要确保使用这块内存之前没有被释放
QueueHandle_t xPointerQueue;
xPointerQueue = xQueueCreate( 5, sizeof( char * ) );void vStringSendingTask( void *pvParameters )
{char *pcStringToSend;const size_t xMaxStringLength = 50;BaseType_t xStringNumber = 0;for( ;; ){//申请存储字符串的内存空间,可能是动态申请,也可能是静态申请pcStringToSend = ( char * ) prvGetBuffer( xMaxStringLength );/* 向申请到的空间中写数据 */snprintf( pcStringToSend, xMaxStringLength, "String number %d\r\n", xStringNumber );xStringNumber++;/* 向队列中写地址 */ xQueueSend( xPointerQueue, &pcStringToSend, portMAX_DELAY );}
}void vStringReceivingTask( void *pvParameters )
{char *pcReceivedString;for( ;; ){/* Receive the address of a buffer. */xQueueReceive( xPointerQueue, &pcReceivedString, portMAX_DELAY );/* The buffer holds a string, print it out. */vPrintString( pcReceivedString );/* The buffer is not required any more - release it so it can be freed, or re-used. */prvReleaseBuffer( pcReceivedString );}
}

示例4、如何使用队列传输数据类型和数据大小不确定的数据?

将示例2和示例3的方法结合起来,就可以达到传输数据类型与大小不确定的数据!

如freertos中TCP/IP stack的源码所示:

typedef enum
{eNetworkDownEvent = 0, eNetworkRxEvent, eTCPAcceptEvent,
} eIPEvent_t;typedef struct IP_TASK_COMMANDS
{eIPEvent_t eEventType;/* A generic pointer that can hold a value, or point to a buffer. */void *pvData;
} IPStackEvent_t;

五、源码解析

1、创建队列

#if ( configSUPPORT_DYNAMIC_ALLOCATION == 1 )#define xQueueCreate( uxQueueLength, uxItemSize )    xQueueGenericCreate( ( uxQueueLength ), ( uxItemSize ), ( queueQUEUE_TYPE_BASE ) )
#endif/*@param uxQueueLength:队列长度,也就是队列最多能存储多少个元素@param uxItemSize:元素大小,队列中每个元素占多少byte@param ucQueueType :队列类型,内部使用,不必太多关心#define queueQUEUE_TYPE_BASE                  ( ( uint8_t ) 0U )#define queueQUEUE_TYPE_SET                   ( ( uint8_t ) 0U )#define queueQUEUE_TYPE_MUTEX                 ( ( uint8_t ) 1U )#define queueQUEUE_TYPE_COUNTING_SEMAPHORE    ( ( uint8_t ) 2U )#define queueQUEUE_TYPE_BINARY_SEMAPHORE      ( ( uint8_t ) 3U )#define queueQUEUE_TYPE_RECURSIVE_MUTEX       ( ( uint8_t ) 4U )@return QueueHandle_t 类型的结构体,也就是队列管理的句柄
*/
QueueHandle_t xQueueGenericCreate( const UBaseType_t uxQueueLength,const UBaseType_t uxItemSize,const uint8_t ucQueueType )
{Queue_t * pxNewQueue;size_t xQueueSizeInBytes;uint8_t * pucQueueStorage;/*分配队列结构体和队列元素存储空间*/xQueueSizeInBytes = ( size_t ) ( uxQueueLength * uxItemSize );pxNewQueue = ( Queue_t * ) pvPortMalloc( sizeof( Queue_t ) + xQueueSizeInBytes ); if( pxNewQueue != NULL )//分配空间成功{pucQueueStorage = ( uint8_t * ) pxNewQueue;pucQueueStorage += sizeof( Queue_t ); //定位到存储队列元素内容的区域#if ( configSUPPORT_STATIC_ALLOCATION == 1 ){pxNewQueue->ucStaticallyAllocated = pdFALSE;}#endif /* configSUPPORT_STATIC_ALLOCATION */prvInitialiseNewQueue( uxQueueLength, uxItemSize, pucQueueStorage, ucQueueType, pxNewQueue );}else{traceQUEUE_CREATE_FAILED( ucQueueType );mtCOVERAGE_TEST_MARKER();}return pxNewQueue;
}

待补充。。。。。。

ref:

FreeRTOS高级篇5---FreeRTOS队列分析_朱工的专栏-CSDN博客

FreeRTOS--消息队列 - M&D - 博客园

freeRTOS中文实用教程2--队列 - jasonactions - 博客园

freertos---队列管理相关推荐

  1. FreeRTOS 队列管理

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

  2. FreeRTOS学习-队列管理

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

  3. 41 freertos内存管理试验 1

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

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

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

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

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

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

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

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

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

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

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

  9. NS2 队列管理机制

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

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

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

最新文章

  1. [bzoj 4869] [六省联考2017] 相逢是问候
  2. **23.m阶的B-树和B+树的主要区别
  3. 关于 IntelliJ IDEA 的Maven 版本修改
  4. VTK:网格之CellEdges
  5. 计算机编程试讲教案,2016教师资格证面试试讲高中信息技术教案:QBASIC分支结构程序...
  6. java 返回第k小的数_java – 给定n和k,返回第k个置换序列
  7. Java设计模式透析之 —— 适配器(Adapter)
  8. OSChina 周六乱弹 —— 表白有风险,装逼需谨慎
  9. zk kafka常识
  10. pattern.compile java_Java Pattern compile(String)用法及代码示例
  11. layim即时通讯实例各功能整合
  12. 婆媳兵法之——短兵相接15天
  13. 【Coursera】深度神经网络的改进:超参数调整、正则化和优化(更新中2023/04/12)
  14. Android移动开发-Android开发日历时常用的农历和公历换算代码工具类
  15. 网易云课堂web安全第一天
  16. 蓝牙基带数据传输机理分析
  17. Oday安全 11.6利用加载模块之外的地址绕过SafeSEH一节注记---jmp [ebp+N] (上)
  18. Python求助攻,哥哥姐姐们帮忙一下,网上下载的这个程序需要什么数据参数读入吗?帮忙写个主程序调用一下SPAC出结果,必有重谢
  19. 隔离网络那点事(物理隔离网络)
  20. 【精通内核】Linux内核自旋锁实现原理与源码解析

热门文章

  1. Robotaxi进入“大洗牌”时代,主机厂成接盘侠
  2. 固态硬盘寿命天梯榜 2021.7
  3. 常见的 PHP IDE 开发工具汇总 (LAMP)
  4. 独家 | 5G已起跑,目前有哪些应用抢先落地?
  5. 【ZCMU1930】帽子戏法(并查集)
  6. Legendre多项式
  7. mac alfred和dash安装配置
  8. dash live 播放
  9. 淘宝天猫开放平台店铺商品发布(新)-淘宝店铺发布API接口流程代码对接说明
  10. can总线短距离不用双绞线_can总线(一)物理层—屏蔽双绞线