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 ↩︎

  • 相关阅读:
    js 获取两个日期相差的天数--自定义方法
    C# Dictionary类型转json方法之一
    C# Obsolete(已弃用方法属性)
    css 样式计算器
    edge 浏览器自动识别电话号码解问题解决方法
    js 中止程序继续进行(break continue return throw)
    js根据等号(=)前名称获取参数值
    JS 时间格式为/Date(1332919782070)/ 转化为正常的格式
    input 内容发生改变时触发事件
    自我介绍for软件工程课程
  • 原文地址:https://www.cnblogs.com/unclemac/p/12783313.html
Copyright © 2011-2022 走看看