zoukankan      html  css  js  c++  java
  • 【FreeRTOS学习04】小白都能懂的 Queue Management 消息队列使用详解

    消息队列作为任务间同步扮演着必不可少的角色;

    相关文章

    【FreeRTOS实战汇总】小白博主的RTOS学习实战快速进阶之路(持续更新)

    1 前言

    任务之间的同步(同步就是任务之间做数据交互,或为两个任务之间的通讯),任务和中断之间的同步都可以依靠消息队列,从而实现异步处理,FreeRTOS的队列采用FIFO(先进先出)缓冲区,具体如下图所示;


    在这里插入图片描述

    2 xQUEUE

    FreeRTOS消息队列的实现主要是queue.c,需要包含头文件queue.h,下面先看一下queue.c中的数据类型xQUEUE,源码如下所示;

    typedef struct QueueDefinition
    {
    	int8_t *pcHead;					/*< Points to the beginning of the queue storage area. */
    	int8_t *pcTail;					/*< Points to the byte at the end of the queue storage area.  Once more byte is allocated than necessary to store the queue items, this is used as a marker. */
    	int8_t *pcWriteTo;				/*< Points to the free next place in the storage area. */
    
    	union							/* Use of a union is an exception to the coding standard to ensure two mutually exclusive structure members don't appear simultaneously (wasting RAM). */
    	{
    		int8_t *pcReadFrom;			/*< Points to the last place that a queued item was read from when the structure is used as a queue. */
    		UBaseType_t uxRecursiveCallCount;/*< Maintains a count of the number of times a recursive mutex has been recursively 'taken' when the structure is used as a mutex. */
    	} 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;
    

    本文暂时不需要深入源码,在queue.h的接口已经封装得相当好,无需对细节太过于关注,下面会是对常用接口的使用的总结,如果英文好,直接看源码中的函数注释也是很好的选择。

    3 相关概念

    3.1 数据结构

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


    laizi
    注意
    上面提到的
    数据单元
    可以是一个charint类型的数,但是相对比较合理的设计是,封装成一个合理的类,或者称之为结构体,可以明确当前数据单元的数据类型,数据来源(来自哪个任务)等等,因为一个队列可以被多个任务进行读取和发送函数,这样就避免了传输数据出现混淆的情况。通常设计是一个队列被多个任务写入数据,然后有一个任务读取,暂时称之为多写一读,反之,多读一写则较少遇到。

    3.2 收发数据堵塞

    当某个任务试图读或者写一个队列时,其可以指定一个阻塞超时时间,

    • 读取:
      任务读取数据时,在设置堵塞超时时间内,如果队列为空,该任务将保持阻塞状态以等待队列数据有效。当其它任务或中断服务例程
      往其等待的队列中写入了数据,该任务将自动由阻塞态转移为就绪态

    • 写入:如果队列被多个任务写入,那么将导致多个任务堵塞以等待队列有效,当队列有效的时候,这些任务中的优先级最高的任务优先进入就绪态。

    4 常用函数

    FreeRTOS的消息队列常用接口都封装在queue.h中,通过宏定义统一将接口函数的命名风格整理得十分统一;具体如下图所示;


    在这里插入图片描述

    这里有两种需要注意;

    • 任务与任务之间同步:例如图中处的API适用于任务间同步;
      • xQueueSendToFront
      • xQueueSendToFront
      • xQueueSend
      • xQueueOverwrite
    • 任务与中断之间同步:图中②处的API适用于任务于中断间同步,xxxISR()后缀的函数都是FreeRTOS中保证了线程安全的;
      • xQueueSendToFrontFromISR
      • xQueueSendToBackFromISR
      • xQueueOverwriteFromISR
      • xQueueSendFromISR

    4.1 创建队列

    • QueueHandle_t
      QueueHandle_t是一个void类型的指针变量,定义在queue.h中,具体如下;
    /**
     * Type by which queues are referenced.  For example, a call to xQueueCreate()
     * returns an QueueHandle_t variable that can then be used as a parameter to
     * xQueueSend(), xQueueReceive(), etc.
     */
    typedef void * QueueHandle_t;
    

    基本上每个队列函数都会使用这个变量,这里我们统一称为队列的句柄;

    • xQueueCreate
      这个函数可以创建一个队列,创建成功则会返回一个队列句柄,如果创建失败则返回NULL,其函数原型是xQueueGenericCreate,具体如下所示;
    #define xQueueCreate( uxQueueLength, uxItemSize ) 
    xQueueGenericCreate( ( uxQueueLength ), ( uxItemSize ), ( queueQUEUE_TYPE_BASE ) )
    

    xQueueCreate如下所示;

     QueueHandle_t xQueueCreate(
    							  UBaseType_t uxQueueLength,
    							  UBaseType_t uxItemSize
    						  );
    
    参数 描述
    uxQueueLength 队列能够存储的最大单元数目,即队列深度
    uxItemSize 队列中数据单元的长度,以字节为单位
    retval NULL:创建失败;否则为创建成功

    4.2 发送数据

    下面都是从任务发送数据到队列,

     BaseType_t xQueueSendToBack(
    								   QueueHandle_t	xQueue,
    								   const void		*pvItemToQueue,
    								   TickType_t		xTicksToWait
    							   );
    
     BaseType_t xQueueSendToFront(
    							  QueueHandle_t xQueue,
    							  const void * pvItemToQueue,
    							  TickType_t xTicksToWait
    						 );
    
    • xQueueSend
      xQueueSendToBack入队顺序相同,函数声明如下所示;
     BaseType_t xQueueSend(
    							  QueueHandle_t xQueue,
    							  const void * pvItemToQueue,
    							  TickType_t xTicksToWait
    						 );
    

    具体的参数描述如下:

    参数 描述
    xQueue 创建队列时返回的句柄
    pvItemToQueue 需要从任务发送到队列的数据
    xTicksToWait 阻塞超时时间。如果在发送时队列已满,这个时间即是任务处于阻塞态等待队列空间有效的最长等待时间。
    retval pdPass:发送数据成功
    errQUEUE_FULL:无法写入数据

    关于xTicksToWait

    • xTicksToWait设为0 ,且队列已满,则xQueueSendToFront()与xQueueSendToBack()均会立即返回。阻塞时间是以系统心跳周期为单位的,所以绝对时间取决于系统心跳频率。常量 portTICK_RATE_MS 可以用来把心跳时间单位转换为毫秒时间单位
    • xTicksToWait 设置为 portMAX_DELAY , 并且在FreeRTOSConig.h 中设定 INCLUDE_vTaskSuspend 为 1,那么阻塞等待将没有超时限制。

    4.3 接收数据

    • xQueueReceive
      xQueueReceive()用于从队列中接收(读取)数据单元。接收到的单元同时会从队列
      中删除
      。函数声明如下;
     BaseType_t xQueueReceive(
    								 QueueHandle_t xQueue,
    								 void *pvBuffer,
    								 TickType_t xTicksToWait
    							);
    
    • xQueuePeek
      xQueuePeek()也是从从队列中接收数据单元,不同的是并不从队列中删出接收到的单元。 xQueuePeek()从队列首接收到数据后,不会修改队列中的数据,也不会改变数据在队列中的存储序顺。函数声明如下;
     BaseType_t xQueuePeek(
    							 QueueHandle_t xQueue,
    							 void * const pvBuffer,
    							 TickType_t xTicksToWait
    						 );
    

    具体的参数描述如下:

    参数 描述
    xQueue 队列的句柄
    pvBuffer 接收缓存指针。其指向一段内存区域,用于接收从队列中拷贝来的数据
    xTicksToWait 阻塞超时时间
    retavl pdPASS:接收成功
    errQUEUE_FULL:接收失败
    • uxQueueSpacesAvailable
      uxQueueSpacesAvailable()用于查询队列中可用的空闲空间数量;函数声明如下;
    UBaseType_t uxQueueSpacesAvailable( const QueueHandle_t xQueue );
    
    参数 描述
    xQueue 队列的句柄
    retval 当前队列中空余的数据单元个数,0表示队列已满
    • uxQueueMessagesWaiting
      uxQueueMessagesWaiting()用于查询队列中当前有效数据单元个数;函数声明如下;
    UBaseType_t uxQueueMessagesWaiting( const QueueHandle_t xQueue );
    
    参数 描述
    xQueue 队列的句柄
    retval 当前队列中空余的数据单元个数,0表示队列为空

    4.4 删除队列

    • vQueueDelete
      用来删除一个队列,直接传入已创建的队列句柄即可,函数声明如下;
    void vQueueDelete( QueueHandle_t xQueue );
    

    5 举例

    多个任务写入一个任务读取的时候应该怎么做呢?如下图所示2
    在这里插入图片描述
    这里有三个任务,所以为了搞清楚数据来自哪个任务,因此将数据单元封装起来,使用
    iMeaning表示数据单元的源头,当然这里还是比较简单的应用。

    6 总结

    本文介绍了FreeRTOS的消息队列比价常用的方法,当然是相对简单的,侧重在了解概念上,需要实际的应用从而加深理解,更加详细已经灵活的应用可以参考FreeRTOS作者撰写的Mastering_the_FreeRTOS_Real_Time_Kernel-A_Hands-On_Tutorial_Guide


    1. Mastering_the_FreeRTOS_Real_Time_Kernel-A_Hands-On_Tutorial_Guide.pdf ↩︎

    2. FREERTOS 实时内核实用指南,Zou Changjun ↩︎

  • 相关阅读:
    笔记-归并排序
    Repeated Substring Pattern
    Assign Cookies
    Number of Boomerangs
    Paint Fence
    Path Sum III
    Valid Word Square
    Sum of Two Integers
    Find All Numbers Disappeared in an Array
    First Unique Character in a String
  • 原文地址:https://www.cnblogs.com/unclemac/p/12783313.html
Copyright © 2011-2022 走看看