FreeRTOS学习笔记(6)-队列

 本文基于韦东山FreeRTOS和STM32cubeMX记录

队列通常可以作用与"任务到任务","任务到中断", "中断到任务"直接直接传输信息。


一、队列的特性

1.常规操作

  • 队列可以有若干项,这被称为队列的长度
  • 每个数据大小固定
  • 在创建队列的时候就要指定队列长度和数据大小
  • 数据保持FIFO的原则,即first in first out,也就是我们常说的先入先出,后入后出
  • 也可以强制写队列头部,覆盖头部数据;
队列操作简化

 

2.传输数据的两种方法

使用队列传输数据时,我们通常使用拷贝或者引用两种方法

  1. 拷贝:把数据,变量的值复制进入队列;
  2. 引用:把数据,变量的地址复制进队列;

在FreeRTOS中采用的拷贝这种方法,因为他相对于引用来说更加简单:

  • 局部变量的值可以发送到队列中,后续即使函数退出、局部变量被回收,也
  • 不会影响队列中的数据
  • 无需分配 buffer 来保存数据,队列中有 buffer
  • 局部变量可以马上再次使用
  • 发送任务、接收任务解耦:接收任务不需要知道这数据是谁的、也不需要发 送任务来释放数据
  • 如果数据实在太大,你还是可以使用队列传输它的地址
  • 队列的空间有 FreeRTOS 内核分配,无需任务操心
  • 对于有内存保护功能的系统,如果队列使用引用方法,也就是使用地址,必 须确保双方任务对这个地址都有访问权限。使用拷贝方法时,则无此限制:内核有足够的权限,把数据复制进队列、再把数据复制出队列。

3.队列的阻塞访问

只要知道队列句柄,那么谁都可以读队列,写队列。任务、ISR都可以。且可以多个任务读写队列。

任务读写队列时,若干读写不成功,则阻塞;也可以指定超时时间。某个任务读队列时,如果队列没有数据,则该任务可以进入阻塞状态:还可以指定阻塞 的时间。如果队列有数据了,则该阻塞的任务会变为就绪态。如果一直都没有数据,则时间 到之后它也会进入就绪态。

既然读取队列的任务个数没有限制,那么当多个任务读取空队列时,这些任务都会进入
阻塞状态:有多个任务在等待同一个队列的数据。当队列中有数据时,哪个任务会进入就绪
态?
  • 优先级最高的任务
  • 如果大家的优先级相同,那等待时间最久的任务会进入就绪态
跟读队列类似,一个任务要写队列时,如果队列满了,该任务也可以进入阻塞状态:还
可以指定阻塞的时间。如果队列有空间了,则该阻塞的任务会变为就绪态。如果一直都没有
空间,则时间到之后它也会进入就绪态。
既然写队列的任务个数没有限制,那么当多个任务写 " 满队列 " 时,这些任务都会进入阻
塞状态:有多个任务在等待同一个队列的空间。当队列中有空间时,哪个任务会进入就绪态?
  • 优先级最高的任务
  • 如果大家的优先级相同,那等待时间最久的任务会进入就绪态

二、队列函数

使用队列的流程:创建队列,写队列,读队列,删除队列

1.创建队列

队列的创建也分为动态分配内存和静态分配内存两种方法。

  • 动态分配内存:xQueueCreate,队列的内存在函数内部动态分配

函数原型如下:

QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength, UBaseType_t uxItemSize );

参数说明:

  • uxQueueLength:队列长度,最多能存放多少个数据
  • uxItemSize:每个数据的大小:以字节为单位
  • 返回值:非0:成功,返回句柄;NULL:失败,因为内存不足

  • 静态分配内存:xQueueCreateStatic,队列的内存要事先分配好

函数原型如下:

QueueHandle_t xQueueCreateStatic(
    UBaseType_t uxQueueLength,
    UBaseType_t uxItemSize,
    uint8_t *pucQueueStorageBuffer,
    StaticQueue_t *pxQueueBuffer
);

参数说明:

  • uxQueueLength:队列长度,最多能存放多少个数据(item)
  • uxItemSize:每个数据(item)的大小:以字节为单位
  • pucQueueStorageBuffer:如果 uxItemSize 非 0,pucQueueStorageBuffer 必须指向一个 uint8_t 数组, 此数组大小至少为"uxQueueLength * uxItemSize"
  • pxQueueBuffer:必须执行一个 StaticQueue_t 结构体,用来保存队列 的数据结构
  • 返回值:非 0:成功,返回句柄,以后使用句柄来操作队列 ;NULL:失败,因为 pxQueueBuffer 为 NULL

2.复位队列

队列刚被创建时,里面没有数据;使用过程中可以调用xQueueReset()把队列恢复为初始状态

函数原型为:

​
/* pxQueue : 复位哪个队列;
* 返回值: pdPASS(必定成功)
*/
BaseType_t xQueueReset( QueueHandle_t pxQueue);

3.删除队列

删除队列的函数为vQueueDelete(),只能删除使用动态方式创建的队列,他会释放内存。

函数原型如下:

void vQueueDelete( QueueHandle_t xQueue );

4.写队列

可以把数据写到队列头部,也可以写到队列尾部,此函数有两个版本:在任务中调用,在ISR中使用。

函数原型如下:

/* 等同于xQueueSendToBack
* 往队列尾部写入数据,如果没有空间,阻塞时间为xTicksToWait
*/
BaseType_t xQueueSend(
    QueueHandle_t xQueue,
    const void *pvItemToQueue,
    TickType_t xTicksToWait
 );
/* 
* 往队列尾部写入数据,如果没有空间,阻塞时间为xTicksToWait
*/
BaseType_t xQueueSendToBack(
    QueueHandle_t xQueue,
    const void *pvItemToQueue,
    TickType_t xTicksToWait
 );
/* 
* 往队列尾部写入数据,此函数可以在中断函数中使用,不可阻塞
*/
BaseType_t xQueueSendToBackFromISR(
    QueueHandle_t xQueue,
    const void *pvItemToQueue,
    BaseType_t *pxHigherPriorityTaskWoken
 );
/* 
* 往队列头部写入数据,如果没有空间,阻塞时间为xTicksToWait
*/
BaseType_t xQueueSendToFront(
    QueueHandle_t xQueue,
    const void *pvItemToQueue,
 =  TickType_t xTicksToWait
 );
/* 
* 往队列头部写入数据,此函数可以在中断函数中使用,不可阻塞
*/
BaseType_t xQueueSendToFrontFromISR(
    QueueHandle_t xQueue,
    const void *pvItemToQueue,
    BaseType_t *pxHigherPriorityTaskWoken
);

参数说明:

  • xQueue:队列句柄
  • pvItemToQueue:数据指针,这个数据会被复制进队列,复制多大的数据则是在创建队列时指定的
  • xTicksToWait:如果队列满则无法写入新数据,可以让任务进入阻塞状态, xTicksToWait 表示阻塞的最大时间(Tick Count)。 如果被设为 0,无法写入数据时函数会立刻返回; 如果被设为 portMAX_DELAY,则会一直阻塞直到有空间可写
  • 返回值:pdPASS:数据成功写入了队列;errQUEUE_FULL:写入失败,队列满

5.读队列

读到最后一个数据后,队列中的该数据会被移除。这个函数也分为在任务中使用和在ISR中使用。

函数原型如下:

BaseType_t xQueueReceive( QueueHandle_t xQueue,
     void * const pvBuffer,
     TickType_t xTicksToWait );
BaseType_t xQueueReceiveFromISR(
     QueueHandle_t xQueue,
     void *pvBuffer,
     BaseType_t *pxTaskWoken
);

参数说明如下:

  • xQueue:队列句柄
  • pvBuffer:buffer的指针,队列数据会复制到这个buffer;复制多大的数据则是在创建队列时指定的
  • xTicksToWait:如果队列空则无法读出新数据,可以让任务进入阻塞状态, xTicksToWait 表示阻塞的最大时间(Tick Count)。 如果被设为 0,无法读出数据时函数会立刻返回; 如果被设为 portMAX_DELAY,则会一直阻塞直到有数据可读
  • 返回值:pdPASS:数据成功读出了队列;errQUEUE_EMPTY:读出失败,队列空

6.查询

可以查询队列中有多少数据,有多少空闲空间。函数原型如下:

/*
* 返回队列中可用数据的个数
*/
UBaseType_t uxQueueMessagesWaiting( const QueueHandle_t xQueue );
/*
* 返回队列中可用空间的个数
*/
UBaseType_t uxQueueSpacesAvailable( const QueueHandle_t xQueue );

7.覆盖/偷看

当队列长度为 1 时,可以使用 xQueueOverwrite() xQueueOverwriteFromISR()
来覆盖数据。
注意,队列长度必须为 1。当队列满时,这些函数会覆盖里面的数据,这也以为着这
些函数不会被阻塞。
函数原型如下:
/* 覆盖队列
* xQueue: 写哪个队列
* pvItemToQueue: 数据地址
* 返回值: pdTRUE表示成功, pdFALSE表示失败
*/
BaseType_t xQueueOverwrite(
     QueueHandle_t xQueue,
     const void * pvItemToQueue
);
BaseType_t xQueueOverwriteFromISR(
     QueueHandle_t xQueue,
     const void * pvItemToQueue,
     BaseType_t *pxHigherPriorityTaskWoken
);
如果想让队列中的数据供多方读取,也就是说读取时不要移除数据,要留给后来人。那
么可以使用 " 窥视 " ,也就是 xQueuePeek() xQueuePeekFromISR() 。这些函数会从队列中
复制出数据,但是不移除数据。这也意味着,如果队列中没有数据,那么 " 偷看 " 时会导致阻
塞;一旦队列中有数据,以后每次 " 偷看 " 都会成功。
函数原型如下:
/* 偷看队列
* xQueue: 偷看哪个队列
* pvItemToQueue: 数据地址, 用来保存复制出来的数据
* xTicksToWait: 没有数据的话阻塞一会
* 返回值: pdTRUE表示成功, pdFALSE表示失败
*/
BaseType_t xQueuePeek(
     QueueHandle_t xQueue,
     void * const pvBuffer,
     TickType_t xTicksToWait
);
BaseType_t xQueuePeekFromISR(
     QueueHandle_t xQueue,
     void *pvBuffer,
);

三、队列集

要支持多个输入设备时,我们需要实现一个“InputTask”,它读取各个设备的队列,得
到数据后再分别转换为游戏的控制键。 InputTask 如何及时读取到多个队列的数据?要使用队列集。 队列集的本质也是队列,只不过里面存放的是“队列句柄”。使用过程如下:
a. 创建队列 A,它的长度是 n1
b. 创建队列 B,它的长度是 n2
c. 创建队列集 S,它的长度是“n1+n2”
d. 把队列 A、B 加入队列集 S
e. 这样,写队列 A 的时候,会顺便把队列 A 的句柄写入队列集 S
f. 这样,写队列 B 的时候,会顺便把队列 B 的句柄写入队列集 S
g. InputTask 先读取队列集 S,它的返回值是一个队列句柄,这样就可以知道哪个队列有数据了;然后 InputTask 再读取这个队列句柄得到数据。

1.创建队列集

函数原型如下:

QueueSetHandle_t xQueueCreateSet( const UBaseType_t uxEventQueueLength )

参数说明:

  • uxQueueLength:队列集长度,最多能存放多少个数据(队列句柄)
  • 返回值:非 0:成功,返回句柄,以后使用句柄来操作队列 ;NULL:失败,因为内存不足

2.将队列传入队列集

函数原型如下:

BaseType_t xQueueAddToSet( QueueSetMemberHandle_t xQueueOrSemaphore, QueueSetHandle_t xQueueSet );

参数说明:

  • xQueueOrSemaphore:队列句柄,这个队列要加入队列集
  • xQueueSet: 队列集句柄
  • 返回值:pdTRUE:成功 ;pdFALSE:失败

3.读取队列集

QueueSetMemberHandle_t xQueueSelectFromSet( QueueSetHandle_t xQueueSet, TickType_t const xTicksToWait );

参数说明:

  • xQueue:队列集句柄
  • xTicksToWait:如果队列集空则无法读出数据,可以让任务进入阻塞状态, xTicksToWait 表示阻塞的最大时间(Tick Count)。如果被设为 0,无 法读出数据时函数会立刻返回;如果被设为 portMAX_DELAY,则 会一直阻塞直到有数据可写
  • 返回值:NULL:失败;队列句柄:成功

四、使用STM32cubeMX中配置队列


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值