1.1 前课准备

1.1.1 课程内容

了解 FreeRTOS 的队列,掌握 FreeRTOS 队列的使用场景,学会如何利用队列实现任务间的通信,熟悉队列的 API。

1.1.2 课程资料

《FreeRTOS 中文实战教程》第 2 章。

1.2 FreeRTOS 队列概述

1.2.1 使用背景

在实际的应用中,常常会遇到一个任务或者中断服务函数需要和另外一个任务进行“沟通交流”,这个“沟通交流”的过程其实就是消息传递的过程。在没有操作系统的时候,两个应用程序进行消息传递一般使用全局变量的方式,但是如果在使用操作系统的应用中用全局变量来传递消息,就会涉及到“资源管理”的问题。FreeRTOS 对此提供了一个叫做“队列”的机制来完成任务与任务、任务与中断之间的消息传递。

1.2.2 队列介绍

队列是为了任务与任务、任务与中断之间的通信而准备的,可以在任务与任务、任务与中断之间传递消息。队列中可以存储有限的、大小固定的数据项目。任务与任务、任务与中断之间要交流的数据保存在队列中,叫做队列项目。队列所能保存的最大数据项目数量叫做队列的长度,创建队列的时候会指定数据项目的大小和队列的长度。由于队列是用来传递消息的,所以也称为消息队列。

1.2.3 数据存储

队列可以保存有限个具有确定长度的数据单元。队列可以保存的最大单元数目被称为队列的“深度”。在队列创建时需要设定其深度和每个单元的大小。

通常情况下,队列被作为 FIFO(先进先出)使用,即数据由队列尾写入,从队列首读出。当然,由队列首写入也是可能的。

往队列写入数据是通过字节拷贝把数据复制存储到队列中;从队列读出数据则是把队列中的数据拷贝删除。(读出一个字节数据,队列中的数据就少一个)

1.2.4 多任务操作队列

队列是具有自己独立权限的内核对象,并不属于或赋予任何任务。所有任务都可以向同一队列写入和读出。一个队列由多方写入是经常的事,但由多方读出倒是很少遇到。

1.2.5 读阻塞

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

由于队列可以被多个任务读取,所以对单个队列而言,可能存在多个任务处于阻塞状态,以等待数据有效。这种情况下,一旦队列数据有效,这个任务就是所有等待任务中优先级最高的任务。而如果所有等待任务的优先级相同,那么被解除阻塞的任务将是等待最久的任务。

1.2.6 写阻塞

同读队列一样,任务也可以在写队列时指定一个阻塞超时时间。这个时间是当被写队列已满时,任务进入阻塞态以等待队列空间有效的最长时间。

由于队列可以被多个任务写入,所以对单个队列而言,也可能有多个任务处于阻塞状态,以等待队列空间有效。这种情况下,一旦队列空间有效,只会有一个任务会被解除阻塞,这个任务就是所有等待任务中优先级最高的任务。而如果所有等待任务的优先级相同,那么被解除阻塞的任务将是等待最久的任务。

1.3 队列的 API

1.3.1 创建队列

函数原型

QueueHandle_t xQueueCreate(
    UBaseType_t uxQueueLength,
    UBaseType_t uxItemSize
);

函数功能

创建一个队列深度为 uxQueueLength、数据大小为 uxItemSize 的队列。

函数参数

  • uxQueueLength:队列深度,保存最大的数据单元数目。
  • uxItemSize:每个数据单元的大小,单位是字节。

函数说明

xQueueGenericCreate(
    ( uxQueueLength ),   // 队列深度
    ( uxItemSize ),      // 每个数据单元大小
    ( queueQUEUE_TYPE_BASE )  // 队列类型
)

函数返回值

  • NULL:表示没有足够的堆空间分配给队列而导致创建失败。
  • 非 NULL:创建成功,返回队列的句柄。

示例

定义队列的句柄变量:

QueueHandle_t Queue_t;

创建队列:

Queue_t = xQueueCreate(10, 5);

1.3.2 写入数据

函数原型

BaseType_t xQueueSend(
    QueueHandle_t xQueue,
    const void * pvItemToQueue,
    TickType_t xTicksToWait
);

函数功能

向指定的队列写入数据,写入的是队列的队尾。

函数参数

  • QueueHandle_t xQueue:写入的队列的句柄。
  • const void * pvItemToQueue:写入的数据缓存空间。
  • TickType_t xTicksToWait:超时时间。

函数说明

xQueueGenericSend(
    ( xQueue ),
    ( pvItemToQueue ),
    ( xTicksToWait ),
    queueSEND_TO_BACK
);

函数返回值

  • pdPASS:数据被成功发送到队列。
  • errQUEUE_FULL:1、写阻塞,没有设置超时时间;2、写阻塞,超时时间到达。

中断服务函数专用

xQueueSendFromISR( xQueue, pvItemToQueue, pxHigherPriorityTaskWoken )

函数原型

BaseType_t xQueueSendToBack(
    QueueHandle_t xQueue,
    const void * pvItemToQueue,
    TickType_t xTicksToWait
);

函数功能

向指定的队列写入数据,写入的是队列的队尾。

函数参数

  • QueueHandle_t xQueue:写入的队列的句柄。
  • const void * pvItemToQueue:写入的数据缓存空间。
  • TickType_t xTicksToWait:超时时间。

函数说明

xQueueGenericSend(
    ( xQueue ),
    ( pvItemToQueue ),
    ( xTicksToWait ),
    queueSEND_TO_BACK
);
xQueueSendToBackFromISR( xQueue, pvItemToQueue, pxHigherPriorityTaskWoken )

函数原型

BaseType_t xQueueSendToFront(
    QueueHandle_t xQueue,
    const void * pvItemToQueue,
    TickType_t xTicksToWait
);

函数功能

向指定的队列写入数据,写入的是队列的队首。

函数参数

  • QueueHandle_t xQueue:写入的队列的句柄。
  • const void * pvItemToQueue:写入的数据缓存空间。
  • TickType_t xTicksToWait:超时时间。

函数说明

xQueueGenericSend(
    ( xQueue ),
    ( pvItemToQueue ),
    ( xTicksToWait ),
    queueSEND_TO_FRONT
);
xQueueSendToFrontFromISR( xQueue, pvItemToQueue, pxHigherPriorityTaskWoken )

示例

定义队列的句柄变量:

QueueHandle_t Queue_t;

创建队列:

Queue_t = xQueueCreate(10, 5);

向队列写入数据:

xQueueSend(Queue_t, "ABCDE", 0);

1.3.3 读取数据

函数原型

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

函数功能

从指定的队列读取数据,读取的是队列的队首,并删除读取的数据。

函数参数

  • QueueHandle_t xQueue:读取的队列的句柄。
  • void * const pvBuffer:读取的数据存放缓存空间。
  • TickType_t xTicksToWait:超时时间。

函数返回值

  • pdPASS:数据被成功从队列读取。
  • errQUEUE_FULL:1、读阻塞,没有设置超时时间;2、读阻塞,超时时间到达。
xQueueReceiveFromISR(
    QueueHandle_t xQueue,
    void * const pvBuffer,
    BaseType_t * const pxHigherPriorityTaskWoken
) PRIVILEGED_FUNCTION;

函数原型

xQueuePeek(
    QueueHandle_t xQueue,
    void * const pvBuffer,
    TickType_t xTicksToWait
) PRIVILEGED_FUNCTION;

函数功能

从指定的队列读取数据,读取的是队列的队首,不会删除读取的数据。

函数参数

  • QueueHandle_t xQueue:读取的队列的句柄。
  • void * const pvBuffer:读取的数据存放缓存空间。
  • TickType_t xTicksToWait:超时时间。

函数返回值

  • pdPASS:数据被成功从队列读取。
  • errQUEUE_FULL:1、读阻塞,没有设置超时时间;2、读阻塞,超时时间到达。
xQueuePeekFromISR(
    QueueHandle_t xQueue,
    void * const pvBuffer
) PRIVILEGED_FUNCTION;

1.3.4 删除队列

函数原型

void vQueueDelete( QueueHandle_t xQueue );

函数参数

  • QueueHandle_t xQueue:删除队列的句柄。

1.4 队列实战

1.4.1 实战 1

创建两个任务,一个任务获取光照强度和 MCU 内部温度,然后写入到队列,另一个任务进行读取并打印。

注意:所使用的模块一定不能有 SysTick 延时函数,从现在开始就不能使用该延时函数。

1.4.2 实战 2

#include "main.h"

QueueHandle_t Quete_Handler;
TaskHandle_t TaskA_Handler;
TaskHandle_t TaskB_Handler;
TaskHandle_t TaskC_Handler;

typedef struct{
    char name[10];
    u8 flag;
} LED_Status;

void Start_Task(void *pvParameters)
{
    Quete_Handler = xQueueCreate(4, sizeof(LED_Status));
    xTaskCreate(Task_A, "LED1_FZ", 100, NULL, 2, &TaskA_Handler);
    xTaskCreate(Task_B, "LED2_FZ", 100, NULL, 1, &TaskB_Handler);
    xTaskCreate(Task_C, "READ_STATUS", 100, NULL, 0, &TaskC_Handler);
    vTaskDelete(NULL);
}

void Task_A(void *pvParameters)
{
    LED_Status LED1 = {"LED_1", 0};
    while(1)
    {
        LED_G_Tog;
        if(!GPIO_ReadOutputDataBit(GPIOB, GPIO_Pin_8))
            LED1.flag = 1;
        else
            LED1.flag = 0;
        xQueueSend(Quete_Handler, &LED1, 0);
        vTaskDelay(50);
    }
    vTaskDelete(NULL);
}

void Task_B(void *pvParameters)
{
    LED_Status LED2 = {"LED_2", 0};
    while(1)
    {
        LED_R_Tog;
        if(!GPIO_ReadOutputDataBit(GPIOB, GPIO_Pin_9))
            LED2.flag = 1;
        else
            LED2.flag = 0;
        xQueueSend(Quete_Handler, &LED2, 0);
        vTaskDelay(30);
    }
    vTaskDelete(NULL);
}

void Task_C(void *pvParameters)
{
    LED_Status Status;
    LED_Status Dev[2];
    while(1)
    {
        while(xQueueReceive(Quete_Handler, &Status, 0) == pdPASS)
        {
            if(strncmp(Status.name, "LED_1", 5) == 0)
            {
                Dev[0] = Status;
            }
            else
            {
                Dev[1] = Status;
            }
        }
        printf("name:%s,Led_Status:%d\r\n", Dev[0].name, Dev[0].flag);
        printf("name:%s,Led_Status:%d\r\n", Dev[1].name, Dev[1].flag);
        printf("\r\n");
        vTaskDelay(40);
    }
    vTaskDelete(NULL);
}
Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐