FreeRTOS笔记之队列

本文详细解释了FreeRTOS中的队列概念,包括队列的工作原理、特点,如数据拷贝、多任务访问和阻塞机制。还介绍了常用API函数,如创建、写入、读取队列的方法及其参数。最后提到动态队列的删除功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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的队列目前掌握的内容,本文仅仅简单介绍了队列的概念、特点以及常用方法,太深层的使用并未涉及,后期接触到了会进行补充。

FreeRTOS是一个开源的实时操作系统(RTOS),专门用于嵌入式系统。它提供了一套可移植的API,使开发者能够轻松地编写多任务应用程序。 以下是一些关于FreeRTOS笔记: 1. 任务(Tasks):FreeRTOS使用任务实现并行执行。每个任务都有自己的独立堆栈空间和优先级。任务可以创建、删除和挂起。 2. 调度器(Scheduler):FreeRTOS的调度器负责决定哪个任务应该在给定时间运行。它使用优先级调度算法,并且可以配置为使用抢占式或协作式调度。 3. 信号量(Semaphores):信号量是一种用于同步和通信的对象。它可以用于任务之间的互斥访问共享资源或任务之间的通信。 4. 队列(Queues):队列提供了一种任务之间传递数据的方式。任务可以将数据发送到队列,另一个任务可以从队列中接收数据。 5. 事件标志组(Event Flags):事件标志组用于任务之间的同步和通信。一个任务可以等待一组特定的事件标志发生,另一个任务可以设置或清除这些事件标志。 6. 定时器(Timers):FreeRTOS提供了软件定时器,可以在指定时间间隔内运行回调函数。 7. 内存管理(Memory Management):FreeRTOS提供了内存管理功能,可以动态分配和释放内存。 8. 中断(Interrupts):FreeRTOS可以与硬件中断一起使用,以实现任务的抢占式调度。 这些只是FreeRTOS的一些基本概念和功能,它还有很多其他特性和组件。如果你对某个特定方面有更多的兴趣,请告诉我,我会提供更详细的信息。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值