实时操作系统Freertos开坑学习笔记:(七):队列

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

`本文包括以下内容:
在这里插入图片描述

一、队列是什么?

综述:队列是一种特殊的数据结构,它遵循先进先出(FIFO)的原则。队列中的元素按照其插入的顺序进行访问和处理,新元素被插入到队列的末尾,而已存在的元素则在队列的前端进行操作和删除。队列的操作包括入队(enqueue)和出队(dequeue),入队表示将元素插入到队列的末尾,而出队则表示将队列的前端元素移除并返回。队列常用于需要按照先后顺序处理元素的场景,例如任务调度、消息传递等。

而在freertos中,队列是什么呢?

队列是任务到任务、任务到中断、中断到任务数据交流的一种机制(消息传递)
在这里插入图片描述

①如果要进行中断、任务的交流,那我用全局变量行吗?

答:不行。以这个图为例,比如一个全局变量a=0,两个任务里面都有对全局变量a的自增,如果两个任务优先级不同,当运行任务1时a++可能运行到读数据-修改数据,但还没有写数据时就已经被高优先级的任务二打断,导致a≠1,执行任务二后才=1,这样的话,执行两次任务却让全局变量a的值是错误的。

所以全局变量的弊端:数据无保护,导致数据不安全,当多个任务同时对该变量操作时,数据易受损

②那为什么队列就可以代替全局变量的功能呢?

首先来看队列的结构:
在这里插入图片描述
可以看到,任务A和B是写队列操作,写队列这个函数呢,它会进入临界区,完成实际操作后再退出临界区,所以:写队列时实际关闭了系统中断,使得临界区代码可以完整的运行不被打断,而读队列也是同理

所以,读写队列具有程序保护功能,防止多任务同时访问造成的数据冲突。

③看一看在freertos中队列的结构

在这里插入图片描述
在队列中可以存储数量有限、大小固定的数据。队列中的每一个数据叫做“队列项目”,队列能够存储“队列项目”的最大数量称为队列的长度。
所以队列的核心特征:队列长度和每个队列项目大小。需要我们自己创建时设置。
在Freertos中,队列的特点:
在这里插入图片描述

(1)FIFO是First-In First-Out的缩写,意为先进先出。在队列中,新元素被插入到队列的末尾,而已存在的元素则在队列的前端进行操作和删除。当需要访问或处理队列中的元素时,先访问或处理队列中最早插入的元素,然后按照插入的先后顺序依次访问或处理其他元素。这种先进先出的特性使得队列成为一种常用的数据结构,在任务调度、缓存管理、消息传递等场景中得到广泛应用。

(2)在FreeRTOS中,队列可以采用实际值传递或者传递指针的方式进行数据传递。实际值传递是指将数据的副本拷贝到队列中进行传递,这样操作的是数据的副本,对原始数据没有影响。而传递指针则是将指向实际数据的指针放入队列中,这样可以避免复制大量的数据,但需要注意在使用指针传递时,确保不会出现指针指向无效数据的情况。
在传递较大的数据时,采用指针传递可以避免频繁的数据复制,提高效率。但需要注意在使用指针传递时,要确保数据的有效性,即确保指针指向的数据在传递过程中不会被修改或释放,以免导致数据错误或悬挂指针的情况。

(3)队列在FreeRTOS中是一种通用的机制,可以被任何任务或中断使用来发送和读取消息。这是因为队列是一种共享的数据结构,用于在不同的任务或中断之间传递数据。任何任务或中断都可以使用队列的API函数来发送消息到队列或从队列中读取消息,无论它们属于哪个任务。
这种灵活性使得队列成为一种常用的通信机制,在多任务或多中断的系统中,可以方便地进行任务间的数据传递和同步。通过队列,任务和中断可以安全地共享数据,避免竞争条件和数据冲突的问题。同时,任务和中断可以根据需要进行阻塞或唤醒,以实现有效的同步和通信。

(4)当任务向一个队列发送消息时,可以通过指定一个阻塞时间来控制任务的行为
如果队列已满,无法将消息入队,任务可以选择以下几种行为
①阻塞等待:任务可以指定一个阻塞时间,如果队列已满,则任务会在队列有空闲位置之前被阻塞。任务将等待,直到队列有空闲位置并成功将消息入队,或者等待的超时时间到达
②非阻塞立即返回:任务可以选择在队列已满时立即返回,而不进行阻塞等待。任务可以根据返回的结果来判断是否消息成功发送到队列中
在这里插入图片描述

④问题:当多个任务写入消息给一个“满队列”时,这些任务都会进入阻塞状态,也就是说有多个任务 在等待同一 个队列的空间。那当队列中有空间时,哪个任务会进入就绪态?

在这里插入图片描述

⑤数据写队列、读队列操作过程在这里插入图片描述

二、队列的结构体

1.结构体内容

如下:

typedef struct QueueDefinition 
{
    
    
    int8_t * pcHead					/* 存储区域的起始地址 */
    int8_t * pcWriteTo;        				/* 下一个写入的位置 */
    union
    {
    
    
        	QueuePointers_t     xQueue; 
	SemaphoreData_t  xSemaphore; 
    } u ;
    List_t xTasksWaitingToSend; 			/* 等待发送列表 */
    List_t xTasksWaitingToReceive;			/* 等待接收列表 */
    volatile UBaseType_t uxMessagesWaiting; 	/* 非空闲队列项目的数量 */
    UBaseType_t uxLength;			/* 队列长度 */
    UBaseType_t uxItemSize;                 		/* 队列项目的大小 */
    volatile int8_t cRxLock; 				/* 读取上锁计数器 */
    volatile int8_t cTxLock;			/* 写入上锁计数器 */
   /* 其他的一些条件编译 */
} xQUEUE;

这段代码是一个队列的定义,具体的结构体成员解释如下:

int8_t * pcHead: 存储区域的起始地址,即队列的存储空间的首地址。

int8_t * pcWriteTo: 下一个写入位置的指针,用于指示下一个要写入数据的位置。

union { QueuePointers_t xQueue; SemaphoreData_t xSemaphore; } u: 一个联合体,用于保存队列指针或信号量数据。

List_t xTasksWaitingToSend: 等待发送列表,用于存储等待向队列发送消息的任务。

List_t xTasksWaitingToReceive: 等待接收列表,用于存储等待从队列接收消息的任务。

volatile UBaseType_t uxMessagesWaiting: 非空闲队列项目的数量,用于记录当前队列中等待接收的消息数量。

UBaseType_t uxLength: 队列长度,表示队列可以容纳的最大项目数量。

UBaseType_t uxItemSize: 队列项目的大小,表示每个项目占用的字节数。

volatile int8_t cRxLock: 读取上锁计数器,用于记录当前队列被读取操作锁定的次数。

volatile int8_t cTxLock: 写入上锁计数器,用于记录当前队列被写入操作锁定的次数。

2.结构体示意图

在这里插入图片描述

三、队列相关API函数介绍

使用队列的主要流程:创建队列 ->写队列 -> 读队列。主要包括创建队列、写队列、读队列三个部分。

1.创建队列

在这里插入图片描述
参数说明:

uxQueueLength:队列的长度,即队列可以容纳的最大项目数量。
uxItemSize:队列中每个项目的大小,即每个项目占用的字节数。
返回值:

成功创建队列时,返回一个有效的队列句柄(QueueHandle_t)。
创建队列失败时,返回 NULL。
在这里插入图片描述
示例用法:

#include "FreeRTOS.h"
#include "queue.h"

// 创建一个长度为10,每个项目大小为4字节的队列
QueueHandle_t xQueue = xQueueCreate(10, sizeof(int));
if (xQueue != NULL) {
    
    
    // 队列创建成功
} else {
    
    
    // 队列创建失败
}

xQueueCreate() 函数用于在运行时动态创建一个队列,并返回一个队列句柄,该句柄可用于后续对队列进行操作,如发送消息和接收消息。注意,在使用完队列后,需要使用 vQueueDelete() 函数来删除队列,以释放相关的资源。

2.写消息入队列

在这里插入图片描述
写消息到队列里,只能往队列头部、队列尾部、覆写方式写入队列这三种方法,覆写只有在队列的队列长度为 1 时,才能够使用

(1)前三个函数

xQueueSend()、xQueueSendToBack() 和 xQueueSendToFront() 函数都是用于向队列中写入消息的函数,它们的作用类似,但有一些细微的差别。

这些函数的函数原型如下:

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

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

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

参数说明:
xQueue:要写入的队列的句柄。
pvItemToQueue:指向要写入队列的消息的指针。
xTicksToWait:写入操作的超时时间,如果队列已满,则等待一段时间再尝试写入。可以使用 portMAX_DELAY 来表示无限等待。

返回值:
如果成功写入消息到队列,则返回 pdPASS。
如果写入消息失败(如队列已满),并且在指定的超时时间内未能成功写入,则返回 errQUEUE_FULL。

示例用法:

#include "FreeRTOS.h"
#include "queue.h"

// 创建一个长度为10,每个项目大小为4字节的队列
QueueHandle_t xQueue = xQueueCreate(10, sizeof(int));

int message = 42;

// 向队列尾部写入消息
if (xQueueSend(xQueue, &message, portMAX_DELAY) == pdPASS) {
    
    
    // 消息写入成功
} else {
    
    
    // 消息写入失败
}

// 向队列尾部写入消息(与 xQueueSend() 等效)
if (xQueueSendToBack(xQueue, &message, portMAX_DELAY) == pdPASS) {
    
    
    // 消息写入成功
} else {
    
    
    // 消息写入失败
}

// 向队列头部写入消息
if (xQueueSendToFront(xQueue, &message, portMAX_DELAY) == pdPASS) {
    
    
    // 消息写入成功
} else {
    
    
    // 消息写入失败
}

这些函数用于将消息写入队列中,xQueueSend() 和 xQueueSendToBack() 将消息写入队列的尾部,而 xQueueSendToFront() 将消息写入队列的头部。如果队列已满,则写入操作将会阻塞,直到队列有可用空间或超时。

(2)后面的函数

xQueueOverwrite() 和 xQueueOverwriteFromISR() 函数用于覆写队列中的消息,仅适用于队列长度为1的情况。

这些函数的函数原型如下:

BaseType_t xQueueOverwrite(QueueHandle_t xQueue, const void *pvItemToQueue);

BaseType_t xQueueOverwriteFromISR(QueueHandle_t xQueue, const void *pvItemToQueue, BaseType_t *pxHigherPriorityTaskWoken);

参数说明:

xQueue:要操作的队列的句柄。
pvItemToQueue:指向要写入队列的消息的指针。
pxHigherPriorityTaskWoken:一个指向 BaseType_t 类型变量的指针,用于指示是否有更高优先级的任务需要唤醒。在 xQueueOverwriteFromISR() 中使用,可以设置为 NULL。
返回值:

如果成功覆写队列中的消息,则返回 pdPASS。
如果队列为空或队列长度不为1,则返回 errQUEUE_FULL。
示例用法:

#include "FreeRTOS.h"
#include "queue.h"

// 创建一个长度为1,每个项目大小为4字节的队列
QueueHandle_t xQueue = xQueueCreate(1, sizeof(int));

int message = 42;

// 覆写队列中的消息
if (xQueueOverwrite(xQueue, &message) == pdPASS) {
    
    
    // 消息覆写成功
} else {
    
    
    // 消息覆写失败
}

// 在中断中覆写队列中的消息
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
if (xQueueOverwriteFromISR(xQueue, &message, &xHigherPriorityTaskWoken) == pdPASS) {
    
    
    // 消息覆写成功
} else {
    
    
    // 消息覆写失败
}

xQueueOverwrite() 和 xQueueOverwriteFromISR() 函数用于覆写队列中的消息。在队列长度为1的情况下,可以使用这些函数来覆盖队列中的现有消息,而不需要等待或创建新的消息。请注意,这些函数只适用于队列长度为1的情况。

在中断处理程序中使用 xQueueOverwriteFromISR() 函数时,需要将 pxHigherPriorityTaskWoken 参数设置为非空指针,并且根据实际情况判断是否需要唤醒更高优先级的任务。

3.从队列中读取消息

在这里插入图片描述

xQueueReceive()
从队列头部读取消息,并将消息从队列中删除。
如果队列为空,任务将进入阻塞状态,直到队列中有消息可读取。
返回pdPASS表示成功读取到消息,返回errQUEUE_EMPTY表示队列为空。

xQueuePeek()
从队列头部读取消息,但不删除消息。
如果队列为空,任务将进入阻塞状态,直到队列中有消息可读取。
返回pdPASS表示成功读取到消息,返回errQUEUE_EMPTY表示队列为空。

xQueueReceiveFromISR()
在中断中从队列头部读取消息,并将消息从队列中删除。
与xQueueReceive()函数类似,但是特别适用于在中断服务例程(ISR)中使用。
返回pdPASS表示成功读取到消息,返回errQUEUE_EMPTY表示队列为空。

xQueuePeekFromISR()
在中断中从队列头部读取消息,但不删除消息。
与xQueuePeek()函数类似,但是特别适用于在中断服务例程(ISR)中使用。
返回pdPASS表示成功读取到消息,返回errQUEUE_EMPTY表示队列为空。

这些函数都是用于读取队列中的消息,并根据需要删除消息或者保留消息。其中,xQueueReceiveFromISR()和xQueuePeekFromISR()函数专门用于在中断服务例程中使用。这些函数将任务或中断服务例程阻塞直到队列中有消息可读取。如果队列为空,任务或中断服务例程将进入阻塞状态,直到队列中有消息可读取。返回值用于指示读取操作是否成功。

代码例子:

下面是一个简单的示例代码,展示了如何使用xQueueReceive()和xQueuePeek()函数从队列中读取消息:

#include <stdio.h>
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"

// 定义一个全局队列
QueueHandle_t queue;

// 任务函数1,向队列中发送消息
void task1(void *pvParameters) {
    
    
    int msg = 100;

    while(1) {
    
    
        // 发送消息到队列中
        xQueueSend(queue, &msg, 0);

        // 任务延时1秒
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

// 任务函数2,从队列中读取消息并删除
void task2(void *pvParameters) {
    
    
    int receivedMsg;

    while(1) {
    
    
        // 从队列中读取并删除消息
        if(xQueueReceive(queue, &receivedMsg, portMAX_DELAY) == pdPASS) {
    
    
            printf("Received message: %d\n", receivedMsg);
        }

        // 任务延时500毫秒
        vTaskDelay(pdMS_TO_TICKS(500));
    }
}

int main() {
    
    
    // 创建队列
    queue = xQueueCreate(5, sizeof(int));

    // 创建任务1
    xTaskCreate(task1, "Task 1", configMINIMAL_STACK_SIZE, NULL, 1, NULL);

    // 创建任务2
    xTaskCreate(task2, "Task 2", configMINIMAL_STACK_SIZE, NULL, 2, NULL);

    // 启动调度器
    vTaskStartScheduler();

    // 如果调度器启动失败,则打印错误信息
    printf("Failed to start FreeRTOS scheduler!\n");

    return 0;
}

在这个示例中,我们创建了一个全局队列queue,然后创建了两个任务task1和task2。task1任务通过xQueueSend()函数将消息发送到队列中,而task2任务使用xQueueReceive()函数从队列中读取并删除消息。每个任务都使用vTaskDelay()函数延时一定的时间,以模拟任务执行的过程。

当task2任务成功从队列中读取到消息时,将打印消息内容。这里使用了portMAX_DELAY作为阻塞时间参数,表示如果队列为空,任务将一直阻塞,直到有消息可读取。

这个示例展示了如何使用xQueueReceive()函数从队列中读取消息并删除,以及如何使用xQueueSend()函数向队列中发送消息。实际应用中,可以根据需要在任务中使用这些函数来实现消息传递和同步。

四、队列入队和出队操作实验

1.实验目标

在这里插入图片描述
这次例程我会进行一个非常细致的讲解:

2.例程

①main.c

int main(void)
{
    
    
    HAL_Init();                                 /* 初始化HAL库 */
    sys_stm32_clock_init(360, 25, 2, 8);        /* 设置时钟,180Mhz */
    delay_init(180);                            /* 延时初始化 */
    usart_init(115200);                         /* 串口初始化为115200 */
    led_init();                                 /* 初始化LED */
    key_init();                                 /* 初始化按键 */
    sdram_init();                               /* SRAM初始化 */
    lcd_init();                                 /* 初始化LCD */
    
    my_mem_init(SRAMIN);                        /* 初始化内部内存池 */
    my_mem_init(SRAMEX);                        /* 初始化外部内存池 */
    my_mem_init(SRAMCCM);                       /* 初始化CCM内存池 */
    
    freertos_demo();
}

除了那些裸机也用到的函数外,还有这些内存初始化设置:
sdram_init():用于初始化SRAM(静态随机存取存储器)。SRAM是一种高速的存储器,通常用于存储数据、变量或者代码。

my_mem_init(SRAMIN):用于初始化内部内存池。内部内存池是指在芯片内部的一块存储空间,用于存储数据、变量或者代码。

my_mem_init(SRAMEX):用于初始化外部内存池。外部内存池是指连接在芯片外部的一块存储空间,通常是使用外部存储器(如SDRAM、NOR Flash)扩展的存储器。

my_mem_init(SRAMCCM):用于初始化CCM(Core-Coupled Memory)内存池。CCM内存是一种与CPU核心紧密耦合的存储器,它具有低延迟和高带宽的特点,适用于存储关键数据和代码。

最重要的当然就是freertos_demo();函数了。

②freertos_demo();


/**
 * @brief       FreeRTOS例程入口函数
 * @param       无
 * @retval      无
 */
void freertos_demo(void)
{
    
        
    /* 队列的创建 */
    key_queue = xQueueCreate( 2, sizeof(uint8_t) );
    if(key_queue != NULL)
    {
    
    
        printf("key_queue队列创建成功!!\r\n");
    }else printf("key_queue队列创建失败!!\r\n");
    
    big_date_queue = xQueueCreate( 1, sizeof(char *) );
    if(big_date_queue != NULL)
    {
    
    
        printf("big_date_queue队列创建成功!!\r\n");
    }else printf("big_date_queue队列创建失败!!\r\n");
    
    xTaskCreate((TaskFunction_t         )   start_task,
                (char *                 )   "start_task",
                (configSTACK_DEPTH_TYPE )   START_TASK_STACK_SIZE,
                (void *                 )   NULL,
                (UBaseType_t            )   START_TASK_PRIO,
                (TaskHandle_t *         )   &start_task_handler );
    vTaskStartScheduler();
}

这是一个名为freertos_demo()的函数,用于演示FreeRTOS中队列的创建和任务的创建与调度。

key_queue = xQueueCreate(2, sizeof(uint8_t)):创建一个容量为2,元素大小为uint8_t的队列,用于存储按键值。如果队列创建成功,会打印"key_queue队列创建成功!!“,否则打印"key_queue队列创建失败!!”。

big_date_queue = xQueueCreate(1, sizeof(char *)):创建一个容量为1,元素大小为char指针的队列,用于存储大数据块。如果队列创建成功,会打印"big_date_queue队列创建成功!!“,否则打印"big_date_queue队列创建失败!!”.

xTaskCreate(start_task, “start_task”, START_TASK_STACK_SIZE, NULL, START_TASK_PRIO, &start_task_handler):创建一个名为"start_task"的任务,使用start_task()函数作为任务函数。该任务的堆栈大小为START_TASK_STACK_SIZE,优先级为START_TASK_PRIO。任务句柄start_task_handler用于后续操作。
vTaskStartScheduler():启动FreeRTOS调度器,开始执行任务。

③操作队列和存储大数据块

QueueHandle_t key_queue;        /* 小数据句柄 */
QueueHandle_t big_date_queue;   /* 大数据句柄 */
char buff[100] = {
    
    "我是一个大数组,大大的数组 124214 uhsidhaksjhdklsadhsaklj"};

key_queue和big_date_queue是队列的句柄(或称为队列的指针),用于在程序中引用这两个队列。key_queue是一个指向小数据队列的句柄,big_date_queue是一个指向大数据队列的句柄。

另外,还定义了一个名为buff的字符数组,长度为100,用于存储大数据块。该数组中包含了一个字符串,表示一个大数据块的内容。

④任务一:实现入队

在这里插入图片描述

/* 任务一,实现入队 */
void task1( void * pvParameters )
{
    
    
    uint8_t key = 0;
    char * buf;
    BaseType_t   err = 0;
    buf = &buff[0]; /* buf = &buff[0] */
    while(1) 
    {
    
    
        key = key_scan(0);
        if(key == KEY0_PRES || key == KEY1_PRES)
        {
    
    
            err = xQueueSend( key_queue, &key, portMAX_DELAY );
            if(err != pdTRUE)
            {
    
    
                printf("key_queue队列发送失败\r\n");
            }
        }else if(key == WKUP_PRES)
        {
    
    
            err = xQueueSend( big_date_queue, &buf, portMAX_DELAY );
            if(err != pdTRUE)
            {
    
    
                printf("key_queue队列发送失败\r\n");
            }
        }
        vTaskDelay(10);
    }
}

在任务函数开始时,定义了一个key变量和一个buf指针变量。buf指针指向了之前提到的buff数组的第一个元素。

在任务的主循环中,首先调用key_scan(0)函数来获取按键值,并将其赋值给key变量。然后通过条件判断,判断按键值是否为KEY0_PRES或KEY1_PRES,如果是,则将key值发送到key_queue队列中,使用xQueueSend()函数来实现。如果发送失败,则打印"key_queue队列发送失败"。

另外,如果按键值为WKUP_PRES,则将buf指针发送到big_date_queue队列中,同样使用xQueueSend()函数来实现。如果发送失败,则打印"key_queue队列发送失败"。

最后,通过vTaskDelay(10)函数来延时10个系统时钟周期,然后继续下一次循环。

这个任务函数的功能是根据按键值将数据发送到不同的队列中,实现了数据入队的操作。

⑤任务二:小数据出队

在这里插入图片描述


/* 任务二,小数据出队 */
void task2( void * pvParameters )
{
    
    
    uint8_t key = 0;
    BaseType_t err = 0;
    while(1)
    {
    
    
        err = xQueueReceive( key_queue,&key,portMAX_DELAY);
        if(err != pdTRUE)
        {
    
    
            printf("key_queue队列读取失败\r\n");
        }else 
        {
    
    
            printf("key_queue读取队列成功,数据:%d\r\n",key);
        }
    }
}

在任务函数开始时,定义了一个key变量和一个err变量。

在任务的主循环中,调用xQueueReceive()函数从key_queue队列中接收数据,并将接收到的数据保存在key变量中。使用portMAX_DELAY作为阻塞时间,表示如果队列为空,任务将一直阻塞直到有数据可用。

接收数据后,通过条件判断,判断数据接收是否成功。如果接收失败,则打印"key_queue队列读取失败"。如果接收成功,则打印"key_queue读取队列成功,数据:"并打印出接收到的key值。

这个任务函数的功能是从key_queue队列中接收数据并进行处理

⑥任务三:大数据出队

在这里插入图片描述

/* 任务三,大数据出队 */
void task3( void * pvParameters )
{
    
    
    char * buf;
    BaseType_t err = 0;
    while(1)
    {
    
    
        err = xQueueReceive( big_date_queue,&buf,portMAX_DELAY);
        if(err != pdTRUE)
        {
    
    
            printf("big_date_queue队列读取失败\r\n");
        }else 
        {
    
    
            printf("数据:%s\r\n",buf);
        }
    }
}

根据你提供的代码,这是一个名为task3()的任务函数,用于实现大数据出队操作。

在任务函数开始时,定义了一个buf指针变量和一个err变量。

在任务的主循环中,调用xQueueReceive()函数从big_date_queue队列中接收数据,并将接收到的数据保存在buf指针变量中。使用portMAX_DELAY作为阻塞时间,表示如果队列为空,任务将一直阻塞直到有数据可用。

接收数据后,通过条件判断,判断数据接收是否成功。如果接收失败,则打印"big_date_queue队列读取失败"。如果接收成功,则打印"数据:"并打印出接收到的字符串数据。

这个任务函数的功能是从big_date_queue队列中接收大数据块,并进行处理。

3.例程运行结果:

在这里插入图片描述

五、队列相关API函数解析

1.队列的创建API函数:xQueueCreate( )

xQueueCreate()函数是一个FreeRTOS中用于创建队列的API函数。它的内部实现过程如下:

首先,函数会检查传入的队列长度和队列元素大小是否合法。如果不合法,函数会返回NULL,表示队列创建失败。

接着,函数会为队列分配内存空间,包括队列控制块和队列存储区。队列控制块是一个结构体,用于管理队列的各种属性和状态信息;队列存储区是一个连续的内存块,用于存储队列中的元素。

然后,函数会初始化队列控制块的各个字段。例如,设置队列的长度、元素大小、存储区的起始地址等。

接下来,函数会初始化队列的信号量,用于实现队列的同步和互斥访问。这个信号量用于控制任务对队列的读取和写入操作,确保只有一个任务在访问队列的时候。

最后,函数会返回创建的队列的指针。如果队列创建失败,函数将返回NULL。

需要注意的是,xQueueCreate()函数只是创建了队列的数据结构,并没有分配队列存储区的内存空间。实际的内存分配是在调用xQueueSend()和xQueueReceive()等函数时进行的。这是因为队列的存储区大小是根据队列长度和元素大小动态计算的,所以需要在运行时动态分配内存空间。

2.往队列写入数据API函数(入队):xQueueSend( )

xQueueSend()函数是一个FreeRTOS中用于往队列写入数据的API函数,也被称为入队操作。它的内部实现过程如下:

首先,函数会检查传入的队列指针和待写入的数据指针是否合法。如果队列指针或数据指针为空,函数会返回一个错误码,表示写入操作失败。

接着,函数会尝试获取队列的信号量。这是为了确保只有一个任务在访问队列的时候,避免多个任务同时写入队列导致数据混乱。

如果成功获取到队列的信号量,函数会将待写入的数据复制到队列的存储区中。具体的复制方式取决于队列的类型。例如,如果是一个字节队列,直接将数据复制到存储区即可;如果是一个结构体队列,需要按照结构体的大小逐个成员进行复制。

写入数据后,函数会更新队列的相关属性,例如队列中的元素数量、读取和写入指针等。

最后,函数会释放队列的信号量,表示写入操作完成。

需要注意的是,xQueueSend()函数在写入数据时,有两种写入模式可以选择:阻塞模式和非阻塞模式。在阻塞模式下,如果队列已满,写入操作将会阻塞当前任务,直到队列有空闲位置可写入;在非阻塞模式下,如果队列已满,写入操作将会立即返回一个错误码,表示写入操作失败。这种模式由函数调用时传入的阻塞时间参数决定。

3.从队列读取数据API函数(出队): xQueueReceive( )

xQueueReceive()函数是一个FreeRTOS中用于从队列读取数据的API函数,也被称为出队操作。它的内部实现过程如下:

首先,函数会检查传入的队列指针和接收数据的指针是否合法。如果队列指针或接收数据的指针为空,函数会返回一个错误码,表示读取操作失败。

接着,函数会尝试获取队列的信号量。这是为了确保只有一个任务在访问队列的时候,避免多个任务同时读取队列导致数据混乱。

如果成功获取到队列的信号量,函数会从队列的存储区中读取数据,并将读取到的数据复制到接收数据的指针中。具体的复制方式取决于队列的类型。例如,如果是一个字节队列,直接从存储区中读取数据即可;如果是一个结构体队列,需要按照结构体的大小逐个成员进行复制。

读取数据后,函数会更新队列的相关属性,例如队列中的元素数量、读取和写入指针等。

最后,函数会释放队列的信号量,表示读取操作完成。

需要注意的是,xQueueReceive()函数在读取数据时,有两种读取模式可以选择:阻塞模式和非阻塞模式。在阻塞模式下,如果队列为空,读取操作将会阻塞当前任务,直到队列有数据可读取;在非阻塞模式下,如果队列为空,读取操作将会立即返回一个错误码,表示读取操作失败。这种模式由函数调用时传入的阻塞时间参数决定。

猜你喜欢

转载自blog.csdn.net/qq_53092944/article/details/132663241