FreeRTOS—队列
一、队列是什么?
队列是一种可以在任务与任务、任务与中断之间传递消息的数据结构。队列中可以存储有限的、大小固定的数据。
队列的长度:队列所能保存的最大数据项目数量,创建队列的时候会指定数据的大小和队列的长度。
二、队列的特点
1.数据存储:
数据发送到队列中会导致数据拷贝,将数据以“拷贝”的形式传入队列,也就是说存储的是数据的值。局部变量的值可以发送到队列中,后续即使函数退出、局部变量被回收,也不会影响队列中的数据。
当然,当数据量大时也可以以“引用”的形式,把数据的地址复制进队列里。
2.多任务访问:
任何任务都可以向队列发送数据或读取数据。
3.出队阻塞:
当任务尝试从队列中读取数据时可以指定“超时时间”,也叫“阻塞时间”,即当任务从队列中读取数据无效时该任务进入阻塞状态的时间。
注意:
如果设置为0就是不阻塞,拿不到数据就立刻返回,从而继续执行接下来的代码。
如果阻塞时间为0~portMAX_DELAY就是拿不到数据时进入阻塞并阻塞xxx时间,在阻塞阶段接收到数据会立刻返回执行接下来的代码。
如果阻塞时间为portMAX_DELAY,任务会一直阻塞直到接收到数据。
当多个任务读取空队列时,这些任务都会进入阻塞状态:有多个任务在等待同一个队列的数据。当队列中有数据时,哪个任务会进入就绪态?
优先级最高的任务
如果大家的优先级相同,那等待时间最久的任务会进入就绪态
4.入队阻塞
5.工作过程(FIFO先进先出)
任务 A 要向任务 B 发送消息,这个消息是 x 变量的值。首先创建一个队列,并且指定队列的长度和每条消息的长度。
这里我们创建了一个长度为 4 的队列,因为要传递的是x 值,而 x 是个 int 类型的变量,所以每条消息的长度就是 int 类型的长度
任务 A 的变量 x 值为 10,将这个值发送到消息队列中。此时队列剩余长度就是3 了。前面说了向队列中发送消息是采用拷贝的方式,所以一旦消息发送完成变量 x 就可以再次被使用,赋其他的值。
任务 A 又向队列发送了一个消息,即新的 x 的值,这里是 20。此时队列剩余长度为 2。
任务 B 从队列中读取消息,并将读取到的消息值赋值给 y,这样 y 就等于 10了。
任务 B 从队列中读取消息完成以后可以选择清除掉这个消息或者不清除。当选择清除这个消息的话其他任务或中断就不能获取这个消息了,而且队列剩余大小就会加一,变成 3。如果不清除的话其他任务或中断也可以获取这个消息,而队列剩余大小依旧是 2。
三、队列的常用API函数(待更新~)
1.创建队列
代码如下(示例):
QueueHandle_t xQueueCreate(UBaseType_t uxQueueLength,UBaseType_t uxItemSize);
参数:
1.UBaseType_t uxQueueLength:队列的长度,通俗点说就是一个队列最多容纳几个数据?
2.UBaseType_t uxItemSize:每个数据的大小,字节为单位。
返回值:
成功,返回队列句柄,所以,创建队列时应该是:
QueueHandle_t 句柄变量名=xQueueCreate(uxQueueLength,uxItemSize);
失败,NULL,因为内存不足,创建不了该队列。
实践用法Demo:
代码如下:
/* 定义队列句柄 */
QueueHandle_t xQueue;
int main( void )
{
/* 队列可以容纳的最大元素数量 */
const UBaseType_t uxQueueLength = 10;
/* 队列中每个元素的大小,以字节为单位 */
const UBaseType_t uxItemSize = sizeof( int );
/* 创建队列 */
xQueue = xQueueCreate( uxQueueLength, uxItemSize );
return 0;
}
2.写入队列
任务版
代码如下(示例):
/*从尾部写入的1方法*/
BaseType_t xQueueSend(QueueHandle_t xQueue,
const void* pvItemToQueue,
TickType_t xTicksToWait );
/*从尾部写入的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);
c
>参数:
1.xQueue:队列的句柄。
2.pvItemToQueue:数据指针,这个数据的值会被复制到队列中,复制的大小由创建队列时决定。
3.xTicksToWait :超时时间,当队列满时则无法写入新数据,这时可以让任务进入阻塞状态。
返回值:
成功,返回pdPASS,表示成功写入。
失败,返回errQUEUE_FULL,因为队列满了。
____
实践用法Demo:
>代码如下(示例):
```c
/* 定义队列句柄 */
QueueHandle_t xQueue;
void vATaskFunction( void * pvParameters )
{
/* 要发送的数据 */
int32_t lValueToSend = 10;
/* 用于接收函数调用结果的变量 */
BaseType_t xStatus;
/* 创建一个可以容纳10个整数的队列 */
xQueue = xQueueCreate( 10, sizeof( int32_t ) );
if( xQueue != NULL )
{
/* 队列创建成功,可以发送数据 */
/* 向队列发送数据 */
xStatus = xQueueSend( xQueue, &lValueToSend, portMAX_DELAY );
if( xStatus != pdPASS )
{
/* 数据未能成功发送到队列 */
}
}
/* ... 其他任务代码 ... */
}
int main( void )
{
/* 创建任务 */
xTaskCreate( vATaskFunction, "Task 1", 1000, NULL, 1, NULL );
/* 启动调度器 */
vTaskStartScheduler();
/* 如果所有任务都结束了,程序将到达这里 */
for( ;; );
}
中断版
代码如下(示例):
/*中断版本的从尾部写入数据的方法,不可以阻塞!!!*/
BaseType_t xQueueSendToBackFromISR(QueueHandle_t xQueue,
const void* pvItemToQueue,
BaseType_t* pxHigherPriorityTaskWoken);
/*中断版本的从头部写入数据的方法,不可以阻塞!!!*/
BaseType_t xQueueSendToFrontFromISR(QueueHandle_t xQueue,
const void* pvItemToQueue,
BaseType_t* pxHigherPriorityTaskWoken);
实践用法Demo:
代码如下(示例):
QueueHandle_t xQueue;
/* 假设这是一个中断服务例程 */
void Example_IRQHandler(void)
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
BaseType_t xStatus;
/* 假设要发送到队列的数据 */
int32_t lItemToQueue = 123;
/* 尝试向队列发送数据 */
xStatus = xQueueSendToBackFromISR(xQueue, &lItemToQueue, &xHigherPriorityTaskWoken);
if(xStatus == pdPASS)
{
/* 数据发送成功 */
}
else
{
/* 数据发送失败 */
}
/* 如果需要,触发上下文切换 */
if(xHigherPriorityTaskWoken == pdTRUE)
{
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
/* 清除中断标志位,具体方法依赖于你的硬件和库 */
}
3.读队列
使用xQueueReceive()函数读队列,读到一个数据后,队列中该数据会被移除。
任务版
代码如下(示例):
BaseType_t xQueueReceive( QueueHandle_t xQueue,
void* const pvBuffer,
TickType_t xTicksToWait );
参数:
1.xQueue:队列的句柄,需要读哪个队列?
2.pvBuffer:buffer指针,队列中的数据被复制到这里,复制的大小在创建时确定。
3.xTicksToWait:超时时间
返回值:
成功,返回pdPASS,表示成功读出数据。
失败,返回errQUEUE_EMPTY,因为队列空了
实践用法Demo:
代码如下(示例):
QueueHandle_t xQueue;
/* 任务函数 */
void vTaskFunction( void * pvParameters )
{
int32_t lReceivedValue;
BaseType_t xStatus;
/* 任务的无限循环 */
for( ;; )
{
/* 尝试从队列接收数据,等待最多 1000 个 tick */
xStatus = xQueueReceive( xQueue, &lReceivedValue, 0 );
if( xStatus == pdPASS )
{
/* 成功从队列接收到数据 */
printf("Received: %ld\n", lReceivedValue);
}
else
{
/* 指定时间内未能从队列接收到数据 */
printf("Could not receive from the queue.\n");
}
/* 其他任务相关的操作 */
}
}
中断版
代码如下(示例):
BaseType_t xQueueReceiveFromISR(
QueueHandle_t xQueue,
void *pvBuffer,
BaseType_t *pxTaskWoken
);
注意:一般不推荐在中断读取队列,很容易卡死!!!!中断一般做一个很迅速的事情,要尽可能快的结束它。
4.覆盖/偷看
覆盖:当队列长度为1时,可以使用xQueueOverwrite()或xQueueOverwriteFromISR()来覆盖数据。
代码如下(示例):
BaseType_t xQueueOverwrite(QueueHandle_t xQueue,
const void* pvItemToQueue);
BaseType_t xQueueOverwriteFromISR(QueueHandle_t xQueue,
const void* pvItemToQueue,
BaseType_t* pxHigherPriorityTaskWoken);
偷看:如果读取时不要移除数据,那么可以使用"偷看",也就是xQueuePeek()或xQueuePeekFromISR()。函数可以从队列中复制出数据,但是不移除数据。这也意味着,如果队列中没有数据,那么"偷看"时会导致阻塞;一旦队列中有数据,以后每次"偷看"都会成功。
代码如下(示例):
BaseType_t xQueuePeek(QueueHandle_t xQueue,
void* const pvBuffer,
TickType_t xTicksToWait);
BaseType_t xQueuePeekFromISR(QueueHandle_t xQueue,
void* pvBuffer);
5.删除队列
只能删除使用动态方法创建的队列,它会释放内存,即这个队列不再存在。
代码如下(示例):
void vQueueDelete( QueueHandle_t xQueue );
总结
以上就是我个人学习FreeRTOS的队列目前掌握的内容,本文仅仅简单介绍了队列的概念、特点以及常用方法,太深层的使用并未涉及,后期接触到了会进行补充。