提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
- 前言
- 一、队列是什么?
- 而在freertos中,队列是什么呢?
- ①如果要进行中断、任务的交流,那我用全局变量行吗?
- ②那为什么队列就可以代替全局变量的功能呢?
- ③看一看在freertos中队列的结构
- ④问题:当多个任务写入消息给一个“满队列”时,这些任务都会进入阻塞状态,也就是说有多个任务 在等待同一 个队列的空间。那当队列中有空间时,哪个任务会进入就绪态?
- ⑤数据写队列、读队列操作过程![在这里插入图片描述](https://img-blog.csdnimg.cn/02032663fbd14548b0b9f6e699186440.png)
- 二、队列的结构体
- 1.结构体内容
- 2.结构体示意图
- 三、队列相关API函数介绍
- 1.创建队列
- 2.写消息入队列
- (1)前三个函数
- (2)后面的函数
- 3.从队列中读取消息
- 代码例子:
- 四、队列入队和出队操作实验
- 1.实验目标
- 2.例程
- ①main.c
- ②freertos_demo();
- ③操作队列和存储大数据块
- ④任务一:实现入队
- ⑤任务二:小数据出队
- ⑥任务三:大数据出队
- 3.例程运行结果:
- 五、队列相关API函数解析
- 1.队列的创建API函数:xQueueCreate( )
- 2.往队列写入数据API函数(入队):xQueueSend( )
- 3.从队列读取数据API函数(出队): xQueueReceive( )
前言
`本文包括以下内容:
一、队列是什么?
综述:队列是一种特殊的数据结构,它遵循先进先出(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()函数在读取数据时,有两种读取模式可以选择:阻塞模式和非阻塞模式。在阻塞模式下,如果队列为空,读取操作将会阻塞当前任务,直到队列有数据可读取;在非阻塞模式下,如果队列为空,读取操作将会立即返回一个错误码,表示读取操作失败。这种模式由函数调用时传入的阻塞时间参数决定。