实时操作系统Freertos开坑学习笔记:(七):队列
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
- 前言
- 一、队列是什么?
- 而在freertos中,队列是什么呢?
- ①如果要进行中断、任务的交流,那我用全局变量行吗?
- ②那为什么队列就可以代替全局变量的功能呢?
- ③看一看在freertos中队列的结构
- ④问题:当多个任务写入消息给一个“满队列”时,这些任务都会进入阻塞状态,也就是说有多个任务 在等待同一 个队列的空间。那当队列中有空间时,哪个任务会进入就绪态?
- ⑤数据写队列、读队列操作过程
- 二、队列的结构体
- 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));// 创建任务1xTaskCreate(task1, "Task 1", configMINIMAL_STACK_SIZE, NULL, 1, NULL);// 创建任务2xTaskCreate(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()函数在读取数据时,有两种读取模式可以选择:阻塞模式和非阻塞模式。在阻塞模式下,如果队列为空,读取操作将会阻塞当前任务,直到队列有数据可读取;在非阻塞模式下,如果队列为空,读取操作将会立即返回一个错误码,表示读取操作失败。这种模式由函数调用时传入的阻塞时间参数决定。
相关文章:
实时操作系统Freertos开坑学习笔记:(七):队列
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、队列是什么?而在freertos中,队列是什么呢?①如果要进行中断、任务的交流,那我用全局变量行吗?②…...
专业游戏翻译公司怎么选择比较合适
近年来,游戏行业持续繁荣,市场需求也在不断扩大,其中游戏翻译的需求越来越旺盛。无论是引进游戏还是让游戏走向国际市场,都需要专业的翻译公司来帮忙。那么,怎么选择合适的游戏翻译公司呢?让我们一起来看看…...
阿里云Maven和Gradle仓库最新配置
文章目录 一、简介二、仓库地址三、如何配置1、Maven配置2、Gradle配置 一、简介 阿里云云效 Maven 是什么? 阿里云Maven中央仓库为 阿里云云效 提供的公共代理仓库,帮助研发人员提高研发生产效率,使用阿里云Maven中央仓库作为下载源&am…...
尚硅谷大数据项目《在线教育之离线数仓》笔记007
视频地址:尚硅谷大数据项目《在线教育之离线数仓》_哔哩哔哩_bilibili 目录 第12章 报表数据导出 P112 01、创建数据表 02、修改datax的jar包 03、ads_traffic_stats_by_source.json文件 P113 P114 P115 P116 P117 P118 P119 P120 P121 P122【122_在…...
python考研志愿填报模拟系统vue
本系统提供给管理员对学生、院校、研究生信息、专业信息、学院信息等诸多功能进行管理。本系统对于学生输入的任何信息都进行了一定的验证,为管理员操作提高了效率,也使其数据安全性得到了保障。本考研志愿填报模拟系统以Django作为框架,B/S模…...
【LeetCode-面试经典150题-day20】
目录 70.爬楼梯 198.打家劫舍 139.单词拆分 322.零钱兑换 300.最长递增子序列 70.爬楼梯 题意: 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢? 提示: 1 < n < …...
回归与聚类算法系列②:线性回归
目录 1、定义与公式 2、应用场景 3、特征与目标的关系分析 线性回归的损失函数 为什么需要损失函数 损失函数 ⭐如何减少损失 4、优化算法 正规方程 梯度下降 优化动态图 偏导 正规方程和梯度下降比较 5、优化方法GD、SGD、SAG 6、⭐线性回归API 7、实例&#…...
springBoot:redis使用
需求:查询酒店房间列表 1、引入依赖 <!--redis--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency> 2、配置yml文件 server:port: 80…...
cmake 选择 vs编译器
QQ:2967732156 QQ交流群:622684416 // 编译VS2017版本的Tars, Release版本 // win32 cmake .. -G "Visual Studio 15 2017" -D CMAKE_BUILD_TYPERelease // x64 cmake .. -G "Visual Studio 15 2017 Win64" -D CMAKE_BUILD_…...
项目(智慧教室)第一部分:cubemx配置,工程文件的移植,触摸屏的检测,项目bug说明
第一章:需求与配置 一。项目需求 二。实现外设控制 注意: 先配置引脚,再配置外设。否则会出现一些不可预料的问题 1.时钟,串口,灯,蜂鸣器配置 (1)RCC配置为外部时钟,修…...
Springboot集成redis--不同环境切换
1.单机配置 spring:redis:mode: singletonhost: 127.0.0.1port: 6379lettuce:pool:max-active: 8 #连接池最大阻塞等待时间(使用负值表示没有限制max-idle: 2 #连接池中的最大空闲连接min-idle: 1 #连接池最大阻塞等待时间(使用负值表示没有限…...
稀疏数组的实现
文章目录 目录 文章目录 前言 一 什么是稀疏数组? 二 稀疏数组怎么存储数据? 三 稀疏数组的实现 总结 前言 大家好,好久不见了,这篇博客是数据结构的第一篇文章,望大家多多支持! 一 什么是稀疏数组? 稀疏数组(Sparse Array)是一种数据结构&a…...
表达式语言的新趋势!了解SPEL如何改变开发方式
文章首发地址 SpEL(Spring Expression Language)是一种表达式语言,由Spring框架提供和支持。它可以在运行时对对象进行解析和计算,用于动态地构建和操作对象的属性、方法和表达式。以下是SpEL的一些特性和功能: 表达式…...
一套成熟的实验室信息管理系统(云LIS源码)ASP.NET CORE
一套成熟的实验室信息管理系统,集前处理、检验、报告、质控、统计分析、两癌等模块为一体的网络管理系统。它的开发和应用将加快检验科管理的统一化、网络化、标准化的进程。 LIS把检验、检疫、放免、细菌微生物及科研使用的各类分析仪器,通过计算机联…...
NPM使用技巧
NPM使用技巧 前言技巧全局模块位置PowerShell报错安装模块冲突 NPM介绍NPM命令使用方法基本命令模块命令查看模块运行命令镜像管理 常用模块rimrafyarn 前言 本文包含NodeJS中NPM包管理器的使用技巧,具体内容包含NPM介绍、NPM命令、常用模块等内容,还包…...
java学习一
目录 Java 与 C 的区别 Java程序是编译执行还是解释执行 编译型语言 解释型语言 Java 与 C 的区别 Java 是纯粹的面向对象语言,所有的对象都继承自 java.lang.Object,C 兼容 C ,不但支持面向对象也支持面向过程。Java 通过虚拟机从而实现…...
PV PVC in K8s
摘要 在Kubernetes中,PV(Persistent Volume)和PVC(Persistent Volume Claim)是用于管理持久化存储的重要资源对象。PV表示存储的实际资源,而PVC表示对PV的声明性要求。当应用程序需要使用持久化存储时&…...
SAP-PP:基础概念笔记-5(物料主数据的MRP1~4视图)
文章目录 前言一、MRP1视图Base Unit of Measure(UoM)MRP 组采购组ABC 指示器Plant-Specific Material Status 特定的工厂物料状态MRP 类型 MRP TypeMRP 类型 MRP TypeMaster Production Scheduling(MPS) 主生产计划基于消耗的计划(CBP)再订货点Reorder-…...
【C语言】初阶测试 (带讲解)
目录 ① 选择题 1. 下列程序执行后,输出的结果为( ) 2. 以下程序的输出结果是? 3. 下面的代码段中,执行之后 i 和 j 的值是什么() 4. 以下程序的k最终值是: 5. 以下程序的最终的输出结果为ÿ…...
用huggingface.Accelerate进行分布式训练
诸神缄默不语-个人CSDN博文目录 本文属于huggingface.transformers全部文档学习笔记博文的一部分。 全文链接:huggingface transformers包 文档学习笔记(持续更新ing…) 本部分网址:https://huggingface.co/docs/transformers/m…...
云计算——弹性云计算器(ECS)
弹性云服务器:ECS 概述 云计算重构了ICT系统,云计算平台厂商推出使得厂家能够主要关注应用管理而非平台管理的云平台,包含如下主要概念。 ECS(Elastic Cloud Server):即弹性云服务器,是云计算…...
在HarmonyOS ArkTS ArkUI-X 5.0及以上版本中,手势开发全攻略:
在 HarmonyOS 应用开发中,手势交互是连接用户与设备的核心纽带。ArkTS 框架提供了丰富的手势处理能力,既支持点击、长按、拖拽等基础单一手势的精细控制,也能通过多种绑定策略解决父子组件的手势竞争问题。本文将结合官方开发文档,…...
C++ 基础特性深度解析
目录 引言 一、命名空间(namespace) C 中的命名空间 与 C 语言的对比 二、缺省参数 C 中的缺省参数 与 C 语言的对比 三、引用(reference) C 中的引用 与 C 语言的对比 四、inline(内联函数…...
【android bluetooth 框架分析 04】【bt-framework 层详解 1】【BluetoothProperties介绍】
1. BluetoothProperties介绍 libsysprop/srcs/android/sysprop/BluetoothProperties.sysprop BluetoothProperties.sysprop 是 Android AOSP 中的一种 系统属性定义文件(System Property Definition File),用于声明和管理 Bluetooth 模块相…...
在鸿蒙HarmonyOS 5中使用DevEco Studio实现录音机应用
1. 项目配置与权限设置 1.1 配置module.json5 {"module": {"requestPermissions": [{"name": "ohos.permission.MICROPHONE","reason": "录音需要麦克风权限"},{"name": "ohos.permission.WRITE…...
【HTTP三个基础问题】
面试官您好!HTTP是超文本传输协议,是互联网上客户端和服务器之间传输超文本数据(比如文字、图片、音频、视频等)的核心协议,当前互联网应用最广泛的版本是HTTP1.1,它基于经典的C/S模型,也就是客…...
pikachu靶场通关笔记22-1 SQL注入05-1-insert注入(报错法)
目录 一、SQL注入 二、insert注入 三、报错型注入 四、updatexml函数 五、源码审计 六、insert渗透实战 1、渗透准备 2、获取数据库名database 3、获取表名table 4、获取列名column 5、获取字段 本系列为通过《pikachu靶场通关笔记》的SQL注入关卡(共10关࿰…...
什么是Ansible Jinja2
理解 Ansible Jinja2 模板 Ansible 是一款功能强大的开源自动化工具,可让您无缝地管理和配置系统。Ansible 的一大亮点是它使用 Jinja2 模板,允许您根据变量数据动态生成文件、配置设置和脚本。本文将向您介绍 Ansible 中的 Jinja2 模板,并通…...
技术栈RabbitMq的介绍和使用
目录 1. 什么是消息队列?2. 消息队列的优点3. RabbitMQ 消息队列概述4. RabbitMQ 安装5. Exchange 四种类型5.1 direct 精准匹配5.2 fanout 广播5.3 topic 正则匹配 6. RabbitMQ 队列模式6.1 简单队列模式6.2 工作队列模式6.3 发布/订阅模式6.4 路由模式6.5 主题模式…...
2025年渗透测试面试题总结-腾讯[实习]科恩实验室-安全工程师(题目+回答)
安全领域各种资源,学习文档,以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各种好玩的项目及好用的工具,欢迎关注。 目录 腾讯[实习]科恩实验室-安全工程师 一、网络与协议 1. TCP三次握手 2. SYN扫描原理 3. HTTPS证书机制 二…...

