概念

队列又称消息队列,是一种常用于任务间通信的数据结构
消息队列是一种异步的通信方式。

作用

队列可以在任务与任务间、中断和任务间传递信息,实现了任务接收来自其他任务或中断的不固定长度的消息,任务能够从队列里面读取消息。

当队列中的消息是空时,读取消息的任务将被阻塞,
用户还可以指定阻塞的任务时间 xTicksToWait,在这段时间中,如果队列为空,该任务将保持阻塞状态以等待队列数据有效。

当队列中有新消息时,被阻塞的任务会被唤醒并处理新消息;

当等待的时间超过了指定的阻塞时间,即使队列中尚无有效数据,任务也会自动从阻塞态转为就绪态。

创建消息队列时 FreeRTOS 会先给消息队列分配一块内存空间,这块内存的大小等于消息队列控制块大小加上(单个消息空间大小与消息队列长度的乘积),接着再初始化消
息队列,此时消息队列为空。

FreeRTOS 的消息队列控制块由多个元素组成,当消息队列被 创建时,系统会为控制块分配对应的内存空间,
用于保存消息队列的一些信息如消息的存储位置,头指针pcHead、尾指针 pcTail、消息大小 uxItemSize 以及队列长度 uxLength等。

同时每个消息队列都与消息空间在同一段连续的内存空间中,在创建成功的时候,这些内存就被占用了,只有删除了消息队列的时候,这段内存才会被释放掉,创建成功的时候就
已经分配好每个消息空间与消息队列的容量,无法更改,每个消息空间可以存放不大于消息大小 uxItemSize 的任意类型的数据,所有消息队列中的消息空间总数即是消息队列的长
度,这个长度可在消息队列创建时指定

特点

  • 消息支持先进先出方式排队,支持异步读写工作方式。
  • 读写队列均支持超时机制。
  • 消息支持后进先出方式排队,往队首发送消息(LIFO)。
  • 可以允许不同长度(不超过队列节点最大值)的任意类型消息。
  • 一个任务能够从任意一个消息队列接收和发送消息。
  • 多个任务能够从同一个消息队列接收和发送消息。
  • 当队列使用结束后,可以通过删除队列函数进行删除。

队列是 FreeRTOS 主要的任务间通讯方式,可以在任务与任务间、中断和任务间传送信息,发送到队列的消息是通过拷贝方式实现的,这意味着队列存储的数据是原数据,而不是原数据的
引用。

运作机制

消息队列空间

消息队列的空间大小:消息队列控制块大小+单个消息空间大小 * 消息队列的长度
消息队列的长度 = 所有消息队列中的消息空间的总数

消息队列的运行

任务或者中断服务程序都可以给消息队列发送消息
image.png
发送消息时
**

  • 如果队列未满或者允许覆盖入队,FreeRTOS 会将消息拷贝到消息队列队尾
  • 如果满了,会根据用户指定的阻塞超时时间进行阻塞
    1. 队列不允许入队
    2. 任务保持阻塞状态等待队列允许入队
    3. 当其它任务从其等待的队列中读取入了数据(队列未满),该任务将自动由阻塞态转移为就绪态。
    4. 当等待的时间超过了指定的阻塞时间,即使队列中还不允许入队,任务也会自动从阻塞态转移为就绪态,
    5. 此时发送消息的任务或者中断程序会收到一 个错误码 errQUEUE_FULL

读取消息时
**

  • 当某个任务试图读一个队列时,其可以指定一个阻塞超时时间。
  • 在这段时间中,如果队列为空,该任务将保持阻塞状态以等待队列数据有效。
  • 当其它任务或中断服务程序往其等待的队列中写入了数据,该任务将自动由阻塞态转移为就绪态。
  • 当等待的时间超过了指定的阻塞时间,即使队列中尚无有效数据,任务也会自动从阻塞态转移为就绪态。


发送紧急消息

当发送紧急消息时,发送的位置是消息队列队头而非队尾,这样,接收者就能够优先接收到紧急消息,从而及时进行消息处理。

消息队列的阻塞机制


阻塞机制:

接收数据

每个任务在进行读取消息队列的数据时,进入阻塞,保证当前任务能够正常完成读写操作,不受任务干扰。
每一个任务读写消息队列的函数都有该机制。

任务A对某个队列进行读写操作时,检测消息队列中没有数据,有3个选择:

  1. 跳过当前读写数据的操作,继续执行其他操作
  2. 等待队列中的数据,这里可以设置等待的时间
    1. 例如等待1000个系统时钟,那么A在1000系统时钟内处于阻塞状态。
      1. 当消息队列中有数据后,A从阻塞状态转为就绪态,执行代码
      2. 当超过1000个系统时钟后,依然没有数据,则A转为就绪状态,得到没有等到消息的错误代码,继续执行代码
  3. 一直等待队列中的数据
    1. 如果没有数据,A会一直处于阻塞状态。

      发送数据

      当队列允许入队时,发送者才能成功发送消息。

队列中无可用消息空间时,说明消息队列已满,此时,系统会根据用户指定的阻塞超时时间将任务阻塞,在指定的超时时间内如果还不能完成入队操作,发送消
息的任务或者中断服务程序会收到一个错误码 errQUEUE_FULL,然后解除阻塞状态;

当然,只有在任务中发送消息才允许进行阻塞状态,而在中断中发送消息不允许带有阻塞机制的,需要调用在中断中发送消息的 API函数接口,因为发送消息的上下文环境是在中断
中,不允许有阻塞的情况。

消息控制块

作用:保存以下内容:

  • 头指针 pcHead
  • 尾指针 pcTail
  • 消息大小 uxItemSize
  • 队列长度 uxLength


  1. typedef struct QueueDefinition {
  2. int8_t *pcHead; (1)
  3. int8_t *pcTail; (2)
  4. int8_t *pcWriteTo; (3)
  5. union {
  6. int8_t *pcReadFrom; (4)
  7. UBaseType_t uxRecursiveCallCount; (5)
  8. } u;
  9. List_t xTasksWaitingToSend; (6)
  10. List_t xTasksWaitingToReceive; (7)
  11. volatile UBaseType_t uxMessagesWaiting; (8)
  12. UBaseType_t uxLength; (9)
  13. UBaseType_t uxItemSize; (10)
  14. volatile int8_t cRxLock; (11)
  15. volatile int8_t cTxLock; (12)
  16. #if( ( configSUPPORT_STATIC_ALLOCATION == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )
  17. uint8_t ucStaticallyAllocated;
  18. #endif
  19. #if ( configUSE_QUEUE_SETS == 1 )
  20. struct QueueDefinition *pxQueueSetContainer;
  21. #endif
  22. #if ( configUSE_TRACE_FACILITY == 1 )
  23. UBaseType_t uxQueueNumber;
  24. uint8_t ucQueueType;
  25. #endif
  26. } xQUEUE;
  27. typedef xQUEUE Queue_t;

代码清单 17-1(1):pcHead 指向队列消息存储区起始位置,即第一个消息空间。
代码清单 17-1(2):pcTail 指向队列消息存储区结束位置地址。
代码清单 17-1(3):pcWriteTo 指向队列消息存储区下一个可用消息空间。
代码清单 17-1(4):pcReadFrom 与 uxRecursiveCallCount 是一对互斥变量,使用联合体用来确保两个互斥的结构体成员不会同时出现。当结构体用于队列时,pcReadFrom 指向出
队消息空间的最后一个,见文知义,就是读取消息时候是从 pcReadFrom 指向的空间读取消息内容。
代码清单 17-1(5):当结构体用于互斥量时,uxRecursiveCallCount 用于计数,记录递归互斥量被“调用”的次数。
代码清单 17-1(6):xTasksWaitingToSend 是一个发送消息阻塞列表,用于保存阻塞在此队列的任务,任务按照优先级进行排序,由于队列已满,想要发送消息的任务无法发送 消息。
代码清单 17-1(7):xTasksWaitingToReceive 是一个获取消息阻塞列表,用于保存阻塞 在此队列的任务,任务按照优先级进行排序,由于队列是空的,想要获取消息的任务无法 获取到消息。
代码清单 17-1(8):uxMessagesWaiting 用于记录当前消息队列的消息个数,如果消息队列被用于信号量的时候,这个值就表示有效信号量个数。
代码清单 17-1(9):uxLength 表示队列的长度,也就是能存放多少消息。
代码清单 17-1(10):uxItemSize 表示单个消息的大小。
代码清单 17-1(11):队列上锁后,储存从队列收到的列表项数目,也就是出队的数量,如果队列没有上锁,设置为 queueUNLOCKED。

消息队列的函数

消息队列创建函数 xQueueCreate()

xQueueCreate()用于创建一个新的队列并返回可用于访问这个队列的队列句柄。

函 数 原 型 QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength,UBaseType_t uxItemSize );
功 能 用于创建一个新的队列。
参 数 uxQueueLength 队列能够存储的最大消息单元数目,即队列长度。
uxItemSize 队列中消息单元的大小,以字节为单位。
返回值 如果创建成功则返回一个队列句柄,用于访问创建的队列。
如果创建不成功则返回

NULL,可能原因是创建队列需要的 RAM 无法分配成功。
| |

  1. #if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
  2. QueueHandle_t xQueueGenericCreate( const UBaseType_t uxQueueLength, const UBaseType_t uxItemSize,const uint8_t ucQueueType )
  3. {
  4. Queue_t *pxNewQueue;
  5. size_t xQueueSizeInBytes;
  6. uint8_t *pucQueueStorage;
  7. configASSERT( uxQueueLength > ( UBaseType_t ) 0 );
  8. if ( uxItemSize == ( UBaseType_t ) 0 ) {
  9. /* 消息空间大小为 0*/
  10. xQueueSizeInBytes = ( size_t ) 0; (1)
  11. }
  12. else {
  13. /* 分配足够消息存储空间,空间的大小为队列长度*单个消息大小 */
  14. xQueueSizeInBytes = ( size_t ) ( uxQueueLength * uxItemSize ); (2)
  15. }
  16. /* 向系统申请内存,内存大小为消息队列控制块大小+消息存储空间大小 */
  17. pxNewQueue=(Queue_t*)pvPortMalloc(sizeof(Queue_t)+xQueueSizeInBytes); (3)
  18. if ( pxNewQueue != NULL ) {
  19. /* 计算出消息存储空间的起始地址 */
  20. pucQueueStorage = ( ( uint8_t * ) pxNewQueue ) + sizeof( Queue_t ); (4)
  21. #if( configSUPPORT_STATIC_ALLOCATION == 1 )
  22. {
  23. pxNewQueue->ucStaticallyAllocated = pdFALSE;
  24. }
  25. #endif
  26. prvInitialiseNewQueue( uxQueueLength, (5)
  27. uxItemSize,
  28. pucQueueStorage,
  29. ucQueueType,
  30. pxNewQueue );
  31. }
  32. return pxNewQueue;
  33. }
  34. #endif
  35. /*-----------------------------------------------------------*/

消息队列静态创建函数 xQueueCreateStatic()

xQueueCreateStatic()用于创建一个新的队列并返回可用于访问这个队列的队列句柄。
队列句柄其实就是一个指向队列数据结构类型的指针。

| 函 数 原 型 | QueueHandlet xQueueCreateStatic(UBaseType_t uxQueueLength,UBaseType_t uxItemSize,uint8_t pucQueueStorageBuffer,
StaticQueue_t
pxQueueBuffer );
| | | —- | —- | —- | | 功 能 | 用于创建一个新的队列。 | | | 参 数 | uxQueueLength | 队列能够存储的最大单元数目,即队列深度。 | | | uxItemSize | 队列中数据单元的长度,以字节为单位。 | | | pucQueueStorageBuffer | 指针,指向一个uint8
t类型的数组,数组的大小至少有 uxQueueLength* uxItemSize 个字节。当uxItemSize 为0时, pucQueueStorageBuffer 可以为NULL。 | | | pxQueueBuffer | 指针,指向StaticQueue_ t 类型的变量,该变量用于存储队列 的数据结构。 | | 返 回 值 | 如果创建成功则返回一个队列句柄,用于访问创建的队列。如果创建不成功则返回 NULL,可能原因是创建队列需要的RAM无法分配成功。 | |

  1. /* 创建一个可以最多可以存储 10 个 64 位变量的队列 */
  2. #define QUEUE_LENGTH 10
  3. #define ITEM_SIZE sizeof( uint64_t )
  4. /* 该变量用于存储队列的数据结构 */
  5. static StaticQueue_t xStaticQueue;
  6. /* 该数组作为队列的存储区域,大小至少有 uxQueueLength * uxItemSize 个字节 */
  7. uint8_t ucQueueStorageArea[ QUEUE_LENGTH * ITEM_SIZE ];
  8. void vATask( void *pvParameters )
  9. {
  10. QueueHandle_t xQueue;
  11. /* 创建一个队列 */
  12. xQueue = xQueueCreateStatic( QUEUE_LENGTH, /* 队列深度 */
  13. ITEM_SIZE, /* 队列数据单元的单位 */
  14. ucQueueStorageArea,/* 队列的存储区域 */
  15. &xStaticQueue ); /* 队列的数据结构 */
  16. /* 剩下的其他代码 */

消息队列删除函数 vQueueDelete()

队列删除函数是根据消息队列句柄直接删除的,删除之后这个消息队列的所有信息都会被系统回收清空,而且不能再次使用这个消息队列了

  1. void vQueueDelete( QueueHandle_t xQueue )
  2. {
  3. Queue_t * const pxQueue = ( Queue_t * ) xQueue;
  4. /* 断言 */
  5. configASSERT( pxQueue ); (1)
  6. traceQUEUE_DELETE( pxQueue );
  7. #if ( configQUEUE_REGISTRY_SIZE > 0 )
  8. {
  9. /* 将消息队列从注册表中删除,我们目前没有添加到注册表中,暂时不用理会 */
  10. vQueueUnregisterQueue( pxQueue ); (2)
  11. }
  12. #endif
  13. #if( ( configSUPPORT_DYNAMIC_ALLOCATION == 1 )&& ( configSUPPORT_STATIC_ALLOCATION == 0 ) ) {
  14. /* 因为用的消息队列是动态分配内存的,所以需要调用 vPortFree 来释放消息队列的内存 */
  15. vPortFree( pxQueue ); (3)
  16. }
  17. }

向消息队列发送消息函数

实现_消息队列 - 图2

xQueueSend()与 xQueueSendToBack()

  1. #define xQueueSend( xQueue, pvItemToQueue, xTicksToWait ) \
  2. xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), \
  3. ( xTicksToWait ), queueSEND_TO_BACK )
  1. #define xQueueSendToBackFromISR(xQueue,pvItemToQueue,pxHigherPriorityTaskWoken) \
  2. xQueueGenericSendFromISR( ( xQueue ), ( pvItemToQueue ), \
  3. ( pxHigherPriorityTaskWoken ), queueSEND_TO_BACK )

| 函 数 原 型 | BaseType_t xQueueSendFromISR(QueueHandle_t xQueue,
const void pvItemToQueue,
BaseType_t
pxHigherPriorityTaskWoken);
| | | —- | —- | —- | | 功 能 | 在中断服务程序中用于向队列尾部发送一个消息。 | | | 参 数 | xQueue | 队列句柄。 | | | pvItemToQueue | 指针,指向要发送到队列尾部的消息。 | | | pxHigherPriorityTaskWoken | 如果入队导致一个任务解锁,并且解锁的任务优先级高 于当前被中断的任务,则将*pxHigherPriorityTaskWoken 设置成pdTRUE, 然后在中断退出前需要进行一次上下 文切换,去执行被唤醒的优先级更高的任务。从 FreeRTOS V7.3.0起,pxHigherPriorityTaskWoken作为一 个可选参数,可以设置为NULL. | | 返 回 值 | 消息发送成功返回pdTRUE,否则返回errQUEUE FULL。 | |

xQueueSendFromISR()与 xQueueSendToBackFromISR()

  1. #define xQueueSendToFrontFromISR(xQueue,pvItemToQueue,pxHigherPriorityTaskWoken) \
  2. xQueueGenericSendFromISR( ( xQueue ), ( pvItemToQueue ),\
  3. ( pxHigherPriorityTaskWoken ), queueSEND_TO_FRONT )
  4. #define xQueueSendToBackFromISR(xQueue,pvItemToQueue,pxHigherPriorityTaskWoken) \
  5. xQueueGenericSendFromISR( ( xQueue ), ( pvItemToQueue ), \
  6. ( pxHigherPriorityTaskWoken ), queueSEND_TO_BACK )



BaseType txQueueSendFromISR(QueueHandle t xQueue,
const void pvItemToQueue,
BaseType_ t
pxHigherPriorityTaskWoken);

在中断服务程序中用于向队列尾部发送一个消息。

xQueue 队列句柄。
pvItemToQueue 指针,指向要发送到队列尾部的消息。
pxHigherPriorityTaskWoken 如果入队导致一个任务解锁,并且解锁的任务优先级高
于当前被中断的任务,则将*pxHighertPriorityTaskWoken
设置成pdTRUE, 然后在中断退出前需要进行一次上下
文切换,去执行被唤醒的优先级更高的任务。从
FreeRTOS V7.3.0起,pxHigherPriorityTaskWoken 作为-
个可选参数,可以设置为NULL。


消息发送成功返回pdTRUE,否则返回errQUEUE_ FULL。
  1. void vBufferISR( void )
  2. {
  3. char cIn;
  4. BaseType_t xHigherPriorityTaskWoken;
  5. /* 在 ISR 开始的时候,我们并没有唤醒任务 */
  6. xHigherPriorityTaskWoken = pdFALSE;
  7. /* 直到缓冲区为空 */
  8. do {
  9. /* 从缓冲区获取一个字节的数据 */
  10. cIn = portINPUT_BYTE( RX_REGISTER_ADDRESS );
  11. /* 发送这个数据 */
  12. xQueueSendFromISR( xRxQueue, &cIn, &xHigherPriorityTaskWoken );
  13. } while ( portINPUT_BYTE( BUFFER_COUNT ) );
  14. /* 这时候 buffer 已经为空,如果需要则进行上下文切换 */
  15. if ( xHigherPriorityTaskWoken ) {
  16. /* 上下文切换,这是一个宏,不同的处理器,具体的方法不一样 */
  17. taskYIELD_FROM_ISR ();
  18. }
  19. }

xQueueSendToFront()

  1. #define xQueueSendToFront( xQueue, pvItemToQueue, xTicksToWait ) \
  2. xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), \
  3. ( xTicksToWait ), queueSEND_TO_FRONT )

image.png

xQueueSendToFrontFromISR()

  1. #define xQueueSendToFrontFromISR( xQueue,pvItemToQueue,pxHigherPriorityTaskWoken ) \
  2. xQueueGenericSendFromISR( ( xQueue ), ( pvItemToQueue ),\
  3. ( pxHigherPriorityTaskWoken ), queueSEND_TO_FRONT )

image.png

通用消息队列发送函数 xQueueGenericSend()(任务)

  1. /*-----------------------------------------------------------*/
  2. 2 BaseType_t xQueueGenericSend( QueueHandle_t xQueue, (1)
  3. 3 const void * const pvItemToQueue, (2)
  4. 4 TickType_t xTicksToWait, (3)
  5. 5 const BaseType_t xCopyPosition ) (4)
  6. 6 {
  7. 7 BaseType_t xEntryTimeSet = pdFALSE, xYieldRequired;
  8. 8 TimeOut_t xTimeOut;
  9. 9 Queue_t * const pxQueue = ( Queue_t * ) xQueue;
  10. 10
  11. 11 /* 已删除一些断言操作 */
  12. 12
  13. 13 for ( ;; ) {
  14. 14 taskENTER_CRITICAL(); (5)
  15. 15 {
  16. 16 /* 队列未满 */
  17. 17 if ( ( pxQueue->uxMessagesWaiting < pxQueue->uxLength )
  18. 18 || ( xCopyPosition == queueOVERWRITE ) ) { (6)
  19. 19 traceQUEUE_SEND( pxQueue );
  20. 20 xYieldRequired =
  21. 21 prvCopyDataToQueue( pxQueue, pvItemToQueue, xCopyPosition ); (7)
  22. 22
  23. 23 /* 已删除使用队列集部分代码 */
  24. 24 /* 如果有任务在等待获取此消息队列 */
  25. 25 if ( listLIST_IS_EMPTY(&(pxQueue->xTasksWaitingToReceive))==pdFALSE){ (8)
  26. 26 /* 将任务从阻塞中恢复 */
  27. 27 if ( xTaskRemoveFromEventList(
  28. 28 &( pxQueue->xTasksWaitingToReceive ) )!=pdFALSE) { (9)
  29. 29 /* 如果恢复的任务优先级比当前运行任务优先级还高,
  30. 30 那么需要进行一次任务切换 */
  31. 31 queueYIELD_IF_USING_PREEMPTION(); (10)
  32. 32 } else {
  33. 33 mtCOVERAGE_TEST_MARKER();
  34. 34 }
  35. 35 } else if ( xYieldRequired != pdFALSE ) {
  36. 36 /* 如果没有等待的任务,拷贝成功也需要任务切换 */
  37. 37 queueYIELD_IF_USING_PREEMPTION(); (11)
  38. 38 } else {
  39. 39 mtCOVERAGE_TEST_MARKER();
  40. 40 }
  41. 41
  42. 42 taskEXIT_CRITICAL(); (12)
  43. 43 return pdPASS;
  44. 44 }
  45. 45 /* 队列已满 */
  46. 46 else { (13)
  47. 47 if ( xTicksToWait == ( TickType_t ) 0 ) {
  48. 48 /* 如果用户不指定阻塞超时时间,退出 */
  49. 49 taskEXIT_CRITICAL(); (14)
  50. 50 traceQUEUE_SEND_FAILED( pxQueue );
  51. 51 return errQUEUE_FULL;
  52. 52 } else if ( xEntryTimeSet == pdFALSE ) {
  53. 53 /* 初始化阻塞超时结构体变量,初始化进入
  54. 54 阻塞的时间 xTickCount 和溢出次数 xNumOfOverflows */
  55. 55 vTaskSetTimeOutState( &xTimeOut ); (15)
  56. 56 xEntryTimeSet = pdTRUE;
  57. 57 } else {
  58. 58 mtCOVERAGE_TEST_MARKER();
  59. 59 }
  60. 60 }
  61. 61 }
  62. 62 taskEXIT_CRITICAL(); (16)
  63. 63 /* 挂起调度器 */
  64. 64 vTaskSuspendAll();
  65. 65 /* 队列上锁 */
  66. 66 prvLockQueue( pxQueue );
  67. 67
  68. 68 /* 检查超时时间是否已经过去了 */
  69. 69 if (xTaskCheckForTimeOut(&xTimeOut, &xTicksToWait)==pdFALSE){ (17)
  70. 70 /* 如果队列还是满的 */
  71. 71 if ( prvIsQueueFull( pxQueue ) != pdFALSE ) { (18)
  72. 72 traceBLOCKING_ON_QUEUE_SEND( pxQueue );
  73. 73 /* 将当前任务添加到队列的等待发送列表中
  74. 74 以及阻塞延时列表,延时时间为用户指定的超时时间 xTicksToWait */
  75. 75 vTaskPlaceOnEventList(
  76. 76 &( pxQueue->xTasksWaitingToSend ), xTicksToWait );(19)
  77. 77 /* 队列解锁 */
  78. 78 prvUnlockQueue( pxQueue ); (20)
  79. 79
  80. 80 /* 恢复调度器 */
  81. 81 if ( xTaskResumeAll() == pdFALSE ) {
  82. 82 portYIELD_WITHIN_API();
  83. 83 }
  84. 84 } else {
  85. 85 /* 队列有空闲消息空间,允许入队 */
  86. 86 prvUnlockQueue( pxQueue ); (21)
  87. 87 ( void ) xTaskResumeAll();
  88. 88 }
  89. 89 } else {
  90. 90 /* 超时时间已过,退出 */
  91. 91 prvUnlockQueue( pxQueue ); (22)
  92. 92 ( void ) xTaskResumeAll();
  93. 93
  94. 94 traceQUEUE_SEND_FAILED( pxQueue );
  95. 95 return errQUEUE_FULL;
  96. 96 }
  97. 97 }
  98. 98 }
  99. 99 /*-----------------------------------------------------------*/

消息队列发送函数 xQueueGenericSendFromISR()(中断)

  1. 1 BaseType_t xQueueGenericSendFromISR( QueueHandle_t xQueue, (1)
  2. 2 const void * const pvItemToQueue, (2)
  3. 3 BaseType_t * const xHigherPriorityTaskWoken, (3)
  4. 4 const BaseType_t xCopyPosition ) (4)
  5. 5 {
  6. 6 BaseType_t xReturn;
  7. 7 UBaseType_t uxSavedInterruptStatus;
  8. 8 Queue_t * const pxQueue = ( Queue_t * ) xQueue;
  9. /* 已删除一些断言操作 */
  10. uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR();
  11. 13 {
  12. 14 /* 队列未满 */
  13. 15 if ( ( pxQueue->uxMessagesWaiting < pxQueue->uxLength )
  14. 16 || ( xCopyPosition == queueOVERWRITE ) ) { (5)
  15. 17 const int8_t cTxLock = pxQueue->cTxLock;
  16. 18 traceQUEUE_SEND_FROM_ISR( pxQueue );
  17. 19
  18. 20 /* 完成消息拷贝 */
  19. 21 (void)prvCopyDataToQueue(pxQueue,pvItemToQueue,xCopyPosition ); (6)
  20. 22
  21. 23 /* 判断队列是否上锁 */
  22. 24 if ( cTxLock == queueUNLOCKED ) { (7)
  23. 25 /* 已删除使用队列集部分代码 */
  24. 26 {
  25. 27 /* 如果有任务在等待获取此消息队列 */
  26. 28 if ( listLIST_IS_EMPTY(
  27. 29 &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE ) {(8)
  28. 30 /* 将任务从阻塞中恢复 */
  29. 31 if ( xTaskRemoveFromEventList(
  30. 32 &( pxQueue->xTasksWaitingToReceive )) != pdFALSE ) {(9)
  31. 33 if ( pxHigherPriorityTaskWoken != NULL ) {
  32. 34 /* 解除阻塞的任务优先级比当前任务高,记录上下文切换请求,
  33. 35 等返回中断服务程序后,就进行上下文切换 */
  34. 36 *pxHigherPriorityTaskWoken = pdTRUE; (10)
  35. 37 } else {
  36. 38 mtCOVERAGE_TEST_MARKER();
  37. 39 }
  38. 40 } else {
  39. 41 mtCOVERAGE_TEST_MARKER();
  40. 42 }
  41. 43 } else {
  42. 44 mtCOVERAGE_TEST_MARKER();
  43. 45 }
  44. 46 }
  45. 47
  46. 48 } else {
  47. 49 /* 队列上锁,记录上锁次数,等到任务解除队列锁时,
  48. 50 使用这个计录数就可以知道有多少数据入队 */
  49. 51 pxQueue->cTxLock = ( int8_t ) ( cTxLock + 1 ); (11)
  50. 52 }
  51. 53
  52. 54 xReturn = pdPASS;
  53. 55 } else {
  54. 56 /* 队列是满的,因为 API 执行的上下文环境是中断,
  55. 57 所以不能阻塞,直接返回队列已满错误代码 errQUEUE_FULL */
  56. 58 traceQUEUE_SEND_FROM_ISR_FAILED( pxQueue ); (12)
  57. 59 xReturn = errQUEUE_FULL;
  58. 60 }
  59. 61 }
  60. 62 portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus );
  61. 63
  62. 64 return xReturn;
  63. 65 }

从消息队列读取消息函数

实现_消息队列 - 图5

xQueueReceive()与 xQueuePeek()

  1. #define xQueueReceive( xQueue, pvBuffer, xTicksToWait ) \
  2. xQueueGenericReceive( ( xQueue ), ( pvBuffer ), \
  3. ( xTicksToWait ), pdFALSE )

image.png

  1. static void Receive_Task(void* parameter)
  2. {
  3. BaseType_t xReturn = pdTRUE;/* 定义一个创建信息返回值,默认为 pdPASS */
  4. uint32_t r_queue; /* 定义一个接收消息的变量 */
  5. while (1)
  6. {
  7. xReturn = xQueueReceive( Test_Queue, /* 消息队列的句柄 */
  8. &r_queue, /* 发送的消息内容 */
  9. portMAX_DELAY); /* 等待时间 一直等 */
  10. if (pdTRUE== xReturn)
  11. printf("本次接收到的数据是:%d\n\n",r_queue); 11 else
  12. printf("数据接收出错,错误代码: 0x%lx\n",xReturn);
  13. }
  14. }

xQueueReceiveFromISR()与 xQueuePeekFromISR()

image.png
image.png
image.png

  1. QueueHandle_t xQueue;
  2. /* 创建一个队列,并往队列里面发送一些数据 */
  3. void vAFunction( void *pvParameters )
  4. {
  5. char cValueToPost;
  6. const TickType_t xTicksToWait = ( TickType_t )0xff;
  7. /* 创建一个可以容纳 10 个字符的队列 */
  8. xQueue = xQueueCreate( 10, sizeof( char ) );
  9. if ( xQueue == 0 ) {
  10. /* 队列创建失败 */
  11. }
  12. /* ... 任务其他代码 */
  13. /* 往队列里面发送两个字符如果队列满了则等待 xTicksToWait 个系统节拍周期*/
  14. cValueToPost = 'a';
  15. xQueueSend( xQueue, ( void * ) &cValueToPost, xTicksToWait );
  16. cValueToPost = 'b';
  17. xQueueSend( xQueue, ( void * ) &cValueToPost, xTicksToWait );
  18. /* 继续往队列里面发送字符
  19. 当队列满的时候该任务将被阻塞*/
  20. cValueToPost = 'c';
  21. xQueueSend( xQueue, ( void * ) &cValueToPost, xTicksToWait );
  22. }
  23. /* 中断服务程序:输出所有从队列中接收到的字符 */
  24. void vISR_Routine( void )
  25. {
  26. BaseType_t xTaskWokenByReceive = pdFALSE;
  27. char cRxedChar;
  28. while ( xQueueReceiveFromISR( xQueue,
  29. ( void * ) &cRxedChar,
  30. &xTaskWokenByReceive) ) {
  31. /* 接收到一个字符,然后输出这个字符 */
  32. vOutputCharacter( cRxedChar );
  33. /* 如果从队列移除一个字符串后唤醒了向此队列投递字符的任务,
  34. 那么参数 xTaskWokenByReceive 将会设置成 pdTRUE,这个循环无论重复多少次,
  35. 仅会有一个任务被唤醒 */
  36. }
  37. if ( xTaskWokenByReceive != pdFALSE ) {
  38. /* 我们应该进行一次上下文切换,当 ISR 返回的时候则执行另外一个任务 */
  39. /* 这是一个上下文切换的宏,不同的处理器,具体处理的方式不一样 */
  40. taskYIELD ();
  41. }
  42. }

xQueueGenericReceive()

  1. /*-----------------------------------------------------------*/
  2. BaseType_t xQueueGenericReceive( QueueHandle_t xQueue, (1)
  3. void * const pvBuffer, (2)
  4. TickType_t xTicksToWait, (3)
  5. const BaseType_t xJustPeeking ) (4)
  6. {
  7. BaseType_t xEntryTimeSet = pdFALSE;
  8. TimeOut_t xTimeOut;
  9. int8_t *pcOriginalReadPosition;
  10. Queue_t * const pxQueue = ( Queue_t * ) xQueue;
  11. /* 已删除一些断言 */
  12. for ( ;; ) {
  13. taskENTER_CRITICAL(); (5)
  14. {
  15. const UBaseType_t uxMessagesWaiting = pxQueue->uxMessagesWaiting;
  16. /* 看看队列中有没有消息 */
  17. if ( uxMessagesWaiting > ( UBaseType_t ) 0 ) { (6)
  18. /*防止仅仅是读取消息,而不进行消息出队操作*/
  19. pcOriginalReadPosition = pxQueue->u.pcReadFrom; (7)
  20. /* 拷贝消息到用户指定存放区域 pvBuffer */
  21. prvCopyDataFromQueue( pxQueue, pvBuffer ); (8)
  22. if ( xJustPeeking == pdFALSE ) { (9)
  23. /* 读取消息并且消息出队 */
  24. traceQUEUE_RECEIVE( pxQueue );
  25. /* 获取了消息,当前消息队列的消息个数需要减一 */
  26. pxQueue->uxMessagesWaiting = uxMessagesWaiting - 1; (10)
  27. /* 判断一下消息队列中是否有等待发送消息的任务 */
  28. if ( listLIST_IS_EMPTY( (11)
  29. &( pxQueue->xTasksWaitingToSend ) ) == pdFALSE ) {
  30. /* 将任务从阻塞中恢复 */
  31. if ( xTaskRemoveFromEventList( (12)
  32. &( pxQueue->xTasksWaitingToSend ) ) != pdFALSE ) {
  33. /* 如果被恢复的任务优先级比当前任务高,会进行一次任务切换 */
  34. queueYIELD_IF_USING_PREEMPTION(); (13)
  35. } else {
  36. mtCOVERAGE_TEST_MARKER();
  37. }
  38. } else {
  39. mtCOVERAGE_TEST_MARKER();
  40. }
  41. } else { (14)
  42. /* 任务只是看一下消息(peek),并不出队 */
  43. traceQUEUE_PEEK( pxQueue );
  44. /* 因为是只读消息 所以还要还原读消息位置指针 */
  45. pxQueue->u.pcReadFrom = pcOriginalReadPosition; (15)
  46. /* 判断一下消息队列中是否还有等待获取消息的任务 */
  47. if ( listLIST_IS_EMPTY( (16)
  48. &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE ) {
  49. /* 将任务从阻塞中恢复 */
  50. if ( xTaskRemoveFromEventList(
  51. &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE ) {
  52. /* 如果被恢复的任务优先级比当前任务高,会进行一次任务切换 */
  53. queueYIELD_IF_USING_PREEMPTION();
  54. } else {
  55. mtCOVERAGE_TEST_MARKER();
  56. }
  57. } else {
  58. mtCOVERAGE_TEST_MARKER();
  59. }
  60. }
  61. taskEXIT_CRITICAL(); (17)
  62. return pdPASS;
  63. } else { (18)
  64. /* 消息队列中没有消息可读 */
  65. if ( xTicksToWait == ( TickType_t ) 0 ) { (19)
  66. /* 不等待,直接返回 */
  67. taskEXIT_CRITICAL();
  68. traceQUEUE_RECEIVE_FAILED( pxQueue );
  69. return errQUEUE_EMPTY;
  70. } else if ( xEntryTimeSet == pdFALSE ) {
  71. /* 初始化阻塞超时结构体变量,初始化进入
  72. 阻塞的时间 xTickCount 和溢出次数 xNumOfOverflows */
  73. vTaskSetTimeOutState( &xTimeOut ); (20)
  74. xEntryTimeSet = pdTRUE;
  75. } else {
  76. mtCOVERAGE_TEST_MARKER();
  77. }
  78. }
  79. }
  80. taskEXIT_CRITICAL();
  81. vTaskSuspendAll();
  82. prvLockQueue( pxQueue ); (21)
  83. 91
  84. 92 /* 检查超时时间是否已经过去了*/
  85. 93 if ( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE ) {(22)
  86. 94 /* 如果队列还是空的 */
  87. 95 if ( prvIsQueueEmpty( pxQueue ) != pdFALSE ) {
  88. 96 traceBLOCKING_ON_QUEUE_RECEIVE( pxQueue ); (23)
  89. 97 /* 将当前任务添加到队列的等待接收列表中
  90. 98 以及阻塞延时列表,阻塞时间为用户指定的超时时间 xTicksToWait */
  91. 99 vTaskPlaceOnEventList(
  92. 100 &( pxQueue->xTasksWaitingToReceive ), xTicksToWait );
  93. 101 prvUnlockQueue( pxQueue );
  94. 102 if ( xTaskResumeAll() == pdFALSE ) {
  95. 103 /* 如果有任务优先级比当前任务高,会进行一次任务切换 */
  96. 104 portYIELD_WITHIN_API();
  97. 105 } else {
  98. 106 mtCOVERAGE_TEST_MARKER();
  99. 107 }
  100. 108 } else {
  101. 109 /* 如果队列有消息了,就再试一次获取消息 */
  102. 110 prvUnlockQueue( pxQueue ); (24)
  103. 111 ( void ) xTaskResumeAll();
  104. 112 }
  105. 113 } else {
  106. 114 /* 超时时间已过,退出 */
  107. 115 prvUnlockQueue( pxQueue ); (25)
  108. 116 ( void ) xTaskResumeAll();
  109. 117
  110. 118 if ( prvIsQueueEmpty( pxQueue ) != pdFALSE ) {
  111. 119 /* 如果队列还是空的,返回错误代码 errQUEUE_EMPTY */
  112. 120 traceQUEUE_RECEIVE_FAILED( pxQueue );
  113. 121 return errQUEUE_EMPTY; (26)
  114. 122 } else {
  115. 123 mtCOVERAGE_TEST_MARKER();
  116. 124 }
  117. 125 }
  118. 126 }
  119. 127 }
  120. 128 /*----*/