通过 FREERTOS 队列发送结构数组
Send an struct array across a FREERTOS queue
我开始使用 ESP32 和 FREERTOS,但在跨队列发送 Struct 数组时遇到问题。我已经发送了另一种变量,但从未发送过 Structs 数组,我遇到了异常。
发送方和接收方在不同的源文件中,我开始认为这可能是问题所在(或至少是部分问题)。
我的简化代码如下所示:
common.h
struct dailyWeather {
// Day of the week starting in Monday (1)
int dayOfWeek;
// Min and Max daily temperature
float minTemperature;
float maxTemperature;
int weather;
};
file1.h
#pragma once
#ifndef _FILE1_
#define _FILE1_
// Queue
extern QueueHandle_t weatherQueue;
#endif
file1.cpp
#include "common.h"
#include "file1.h"
// Queue
QueueHandle_t weatherQueue = xQueueCreate( 2, sizeof(dailyWeather *) ); // also tried "dailyWeather" without pointer and "Struct dailyWeather"
void task1(void *pvParameters) {
for (;;) {
dailyWeather weatherDATA[8] = {};
// Code to fill the array of structs with data
if (xQueueSend( weatherQueue, &weatherDATA, ( TickType_t ) 0 ) == pdTRUE) {
// The message was sent sucessfully
}
}
}
file2.cpp
#include "common.h"
#include "file1.h"
void task2(void *pvParameters) {
for (;;) {
dailyWeather *weatherDATA_P; // Also tried without pointer and as an array of Structs
if( xQueueReceive(weatherQueue, &( weatherDATA_P ), ( TickType_t ) 0 ) ) {
Serial.println("Received");
dailyWeather weatherDATA = *weatherDATA_P;
Serial.println(weatherDATA.dayOfWeek);
}
}
}
当我在我的 ESP32 上 运行 这段代码时,它一直有效,直到我尝试使用 Serial.println 打印数据。打印了“已收到”消息,但它在下一个 Serial.println 中崩溃并出现此错误。
Guru Meditation Error: Core 1 panic'ed (LoadProhibited). Exception was unhandled.
我被这个问题困住了,我找不到解决它的方法,所以非常感谢任何帮助。
编辑:
我在想,也许一个解决方案就是向结构中添加一个订单项,使队列更大(在数量上)并将所有结构分别发送到队列中。然后在 reader 中使用该订单再次订购。
无论如何,了解我在上面的代码中做错了什么会很高兴。
首先,像您这样在全局范围内创建队列并不是一个好主意。全局队列句柄是可以的。但是 运行 xQueueCreate()
在创建 task1
和 task2
的同一个函数中(队列必须在任务之前创建),像这样:
QueueHandle_t weatherQueue = NULL;
void main() {
weatherQueue = xQueueCreate(32, sizeof(struct dailyWeather));
if (!weatherQueue) {
// Handle error
}
if (xTaskCreate(task1, ...) != pdPASS) {
// Handle error
}
if (xTaskCreate(task2, ...) != pdPASS) {
// Handle error
}
}
其次,task1()
中的代码循环执行以下操作:
- 在堆栈中创建一个包含 8 个
dailyWeather
结构的新数组(在单个循环迭代的范围内)
- 将指向
weatherDATA[]
中第一个项目的指针复制到队列中(task2
稍后会在需要切换任务时收到它)
- 释放8的数组
dailyWeather
(因为我们正在退出循环作用域)
稍后 task2()
执行并尝试读取指向 weatherDATA[]
中第一项的指针。然而,这段内存可能已经被释放了。你不能取消引用它。
所以您正在通过队列传递指向无效内存的指针。
如果您只传递要发送的数据而不是指针,那么使用队列会容易得多。您的结构很小并且由基本数据类型组成,因此最好将其整个传递给队列,一次传递一个(如果需要,您可以传递整个数组,但这种方式更简单)。
像这样:
void task1(void *pvParameters) {
for (;;) {
dailyWeather weatherDATA[8] = {};
// Code to fill the array of structs with data
for (int i = 0; i < 8; i++) {
// Copy the structs to queue, one at a time
if (xQueueSend( weatherQueue, &(weatherDATA[i]), ( TickType_t ) 0 ) == pdTRUE) {
// The message was sent successfully
}
}
}
}
接收端:
void task2(void *pvParameters) {
for (;;) {
dailyWeather weatherDATA;
if( xQueueReceive(weatherQueue, &( weatherDATA ), ( TickType_t ) 0 ) ) {
Serial.println("Received");
Serial.println(weatherDATA.dayOfWeek);
}
}
}
我怎么推荐 official FreeRTOS book 都不为过,它对初学者来说是很好的资源。
当您调用 xQueueCreate()
时,freeRTOS 队列使用您在初始化期间指定的缓冲区和数据大小来运行,以复制您想要发送-接收的数据。
当您调用 xQueueSend()
时,相当于 xQueueSendToBack()
,它会复制到该缓冲区。
如果另一个任务在调用 xQueueReceive()
时等待队列,在它准备好 运行 的那一刻,xQueueReceive()
将复制前面的项目将队列的缓冲区放入您在调用 xQueueReceive()
.
时指定的目标缓冲区
如果你要发送的数据是pointer/array类型,在你的情况下是dailyWeather *
,那么你需要确保指针指向的内存没有超出范围被通过调用 xQueueReceive()
接收指针的任务读取。局部变量是在调用任务的堆栈中创建的,并且肯定会超出范围,并且很可能会在函数 returns.
之后被覆盖。
IMO,如果你真的需要传递指针,最好的解决方案是在生成数据的函数中分配结构数组,并在使用数据的任务中释放它。
对于许多场景,非常希望不要滥用动态内存处理,因此在几个通信堆栈中,您会发现缓冲池的使用,缓冲池最后也是在应用程序启动期间初始化的队列。操作大致如下:
初始化:
- 初始化缓冲池队列(简单的指针队列)。
- 用适当大小的动态分配缓冲区填充缓冲池。
- 初始化任务间通信的队列。
提供数据的任务:
- 从一个缓冲池中获取(接收)一个缓冲指针。
- 用数据填充缓冲区。
- 将缓冲区指针发送到通信队列。
接收数据的任务:
- 从通信队列中获取(接收)数据缓冲区指针。
- 使用数据。
- Return(发送)缓冲池指针。
如果您的结构很小,因此您或多或少地限制了先复制再复制的开销,那么创建队列更有意义,这样您就可以直接使用结构实例和结构副本而不是结构缓冲区指针.
感谢大家的回答。
最后,我添加了一个变量来跟踪项目位置,并将所有数据通过队列传递到目标任务。然后我将所有这些结构放回另一个数组中。
common.h
#include <stdint.h>
struct dailyWeather {
// Day of the week starting in Monday (1)
uint8_t dayOfWeek;
// Min and Max daily temperature
float minTemperature;
float maxTemperature;
uint8_t weather;
uint8_t itemOrder;
};
file1.h
// Queue
extern QueueHandle_t weatherQueue;
file1.cpp
#include "common.h"
#include "file1.h"
// Queue
QueueHandle_t weatherQueue = xQueueCreate( 2 * 8, sizeof(struct dailyWeather) );
void task1(void *pvParameters) {
for (;;) {
dailyWeather weatherDATA[8];
// Code to fill the array of structs with data
for (uint8_t i = 0; i < 8; i++) {
weatherDATA[i].itemOrder = i;
if (xQueueSend( weatherQueue, &weatherDATA[i], ( TickType_t ) 0 ) == pdTRUE) {
// The message was sent sucessfully
}
}
}
}
file2.cpp
#include "common.h"
#include "file1.h"
void task2(void *pvParameters) {
dailyWeather weatherDATA_D[8];
for (;;) {
dailyWeather weatherDATA;
if( xQueueReceive(weatherQueue, &( weatherDATA ), ( TickType_t ) 0 ) ) {
Serial.println("Received");
weatherDATA_D[weatherDATA.itemOrder] = weatherDATA;
}
}
}
此致。
我开始使用 ESP32 和 FREERTOS,但在跨队列发送 Struct 数组时遇到问题。我已经发送了另一种变量,但从未发送过 Structs 数组,我遇到了异常。
发送方和接收方在不同的源文件中,我开始认为这可能是问题所在(或至少是部分问题)。
我的简化代码如下所示:
common.h
struct dailyWeather {
// Day of the week starting in Monday (1)
int dayOfWeek;
// Min and Max daily temperature
float minTemperature;
float maxTemperature;
int weather;
};
file1.h
#pragma once
#ifndef _FILE1_
#define _FILE1_
// Queue
extern QueueHandle_t weatherQueue;
#endif
file1.cpp
#include "common.h"
#include "file1.h"
// Queue
QueueHandle_t weatherQueue = xQueueCreate( 2, sizeof(dailyWeather *) ); // also tried "dailyWeather" without pointer and "Struct dailyWeather"
void task1(void *pvParameters) {
for (;;) {
dailyWeather weatherDATA[8] = {};
// Code to fill the array of structs with data
if (xQueueSend( weatherQueue, &weatherDATA, ( TickType_t ) 0 ) == pdTRUE) {
// The message was sent sucessfully
}
}
}
file2.cpp
#include "common.h"
#include "file1.h"
void task2(void *pvParameters) {
for (;;) {
dailyWeather *weatherDATA_P; // Also tried without pointer and as an array of Structs
if( xQueueReceive(weatherQueue, &( weatherDATA_P ), ( TickType_t ) 0 ) ) {
Serial.println("Received");
dailyWeather weatherDATA = *weatherDATA_P;
Serial.println(weatherDATA.dayOfWeek);
}
}
}
当我在我的 ESP32 上 运行 这段代码时,它一直有效,直到我尝试使用 Serial.println 打印数据。打印了“已收到”消息,但它在下一个 Serial.println 中崩溃并出现此错误。
Guru Meditation Error: Core 1 panic'ed (LoadProhibited). Exception was unhandled.
我被这个问题困住了,我找不到解决它的方法,所以非常感谢任何帮助。
编辑: 我在想,也许一个解决方案就是向结构中添加一个订单项,使队列更大(在数量上)并将所有结构分别发送到队列中。然后在 reader 中使用该订单再次订购。
无论如何,了解我在上面的代码中做错了什么会很高兴。
首先,像您这样在全局范围内创建队列并不是一个好主意。全局队列句柄是可以的。但是 运行 xQueueCreate()
在创建 task1
和 task2
的同一个函数中(队列必须在任务之前创建),像这样:
QueueHandle_t weatherQueue = NULL;
void main() {
weatherQueue = xQueueCreate(32, sizeof(struct dailyWeather));
if (!weatherQueue) {
// Handle error
}
if (xTaskCreate(task1, ...) != pdPASS) {
// Handle error
}
if (xTaskCreate(task2, ...) != pdPASS) {
// Handle error
}
}
其次,task1()
中的代码循环执行以下操作:
- 在堆栈中创建一个包含 8 个
dailyWeather
结构的新数组(在单个循环迭代的范围内) - 将指向
weatherDATA[]
中第一个项目的指针复制到队列中(task2
稍后会在需要切换任务时收到它) - 释放8的数组
dailyWeather
(因为我们正在退出循环作用域)
稍后 task2()
执行并尝试读取指向 weatherDATA[]
中第一项的指针。然而,这段内存可能已经被释放了。你不能取消引用它。
所以您正在通过队列传递指向无效内存的指针。
如果您只传递要发送的数据而不是指针,那么使用队列会容易得多。您的结构很小并且由基本数据类型组成,因此最好将其整个传递给队列,一次传递一个(如果需要,您可以传递整个数组,但这种方式更简单)。
像这样:
void task1(void *pvParameters) {
for (;;) {
dailyWeather weatherDATA[8] = {};
// Code to fill the array of structs with data
for (int i = 0; i < 8; i++) {
// Copy the structs to queue, one at a time
if (xQueueSend( weatherQueue, &(weatherDATA[i]), ( TickType_t ) 0 ) == pdTRUE) {
// The message was sent successfully
}
}
}
}
接收端:
void task2(void *pvParameters) {
for (;;) {
dailyWeather weatherDATA;
if( xQueueReceive(weatherQueue, &( weatherDATA ), ( TickType_t ) 0 ) ) {
Serial.println("Received");
Serial.println(weatherDATA.dayOfWeek);
}
}
}
我怎么推荐 official FreeRTOS book 都不为过,它对初学者来说是很好的资源。
当您调用 xQueueCreate()
时,freeRTOS 队列使用您在初始化期间指定的缓冲区和数据大小来运行,以复制您想要发送-接收的数据。
当您调用 xQueueSend()
时,相当于 xQueueSendToBack()
,它会复制到该缓冲区。
如果另一个任务在调用 xQueueReceive()
时等待队列,在它准备好 运行 的那一刻,xQueueReceive()
将复制前面的项目将队列的缓冲区放入您在调用 xQueueReceive()
.
如果你要发送的数据是pointer/array类型,在你的情况下是dailyWeather *
,那么你需要确保指针指向的内存没有超出范围被通过调用 xQueueReceive()
接收指针的任务读取。局部变量是在调用任务的堆栈中创建的,并且肯定会超出范围,并且很可能会在函数 returns.
IMO,如果你真的需要传递指针,最好的解决方案是在生成数据的函数中分配结构数组,并在使用数据的任务中释放它。
对于许多场景,非常希望不要滥用动态内存处理,因此在几个通信堆栈中,您会发现缓冲池的使用,缓冲池最后也是在应用程序启动期间初始化的队列。操作大致如下:
初始化:
- 初始化缓冲池队列(简单的指针队列)。
- 用适当大小的动态分配缓冲区填充缓冲池。
- 初始化任务间通信的队列。
提供数据的任务:
- 从一个缓冲池中获取(接收)一个缓冲指针。
- 用数据填充缓冲区。
- 将缓冲区指针发送到通信队列。
接收数据的任务:
- 从通信队列中获取(接收)数据缓冲区指针。
- 使用数据。
- Return(发送)缓冲池指针。
如果您的结构很小,因此您或多或少地限制了先复制再复制的开销,那么创建队列更有意义,这样您就可以直接使用结构实例和结构副本而不是结构缓冲区指针.
感谢大家的回答。
最后,我添加了一个变量来跟踪项目位置,并将所有数据通过队列传递到目标任务。然后我将所有这些结构放回另一个数组中。
common.h
#include <stdint.h>
struct dailyWeather {
// Day of the week starting in Monday (1)
uint8_t dayOfWeek;
// Min and Max daily temperature
float minTemperature;
float maxTemperature;
uint8_t weather;
uint8_t itemOrder;
};
file1.h
// Queue
extern QueueHandle_t weatherQueue;
file1.cpp
#include "common.h"
#include "file1.h"
// Queue
QueueHandle_t weatherQueue = xQueueCreate( 2 * 8, sizeof(struct dailyWeather) );
void task1(void *pvParameters) {
for (;;) {
dailyWeather weatherDATA[8];
// Code to fill the array of structs with data
for (uint8_t i = 0; i < 8; i++) {
weatherDATA[i].itemOrder = i;
if (xQueueSend( weatherQueue, &weatherDATA[i], ( TickType_t ) 0 ) == pdTRUE) {
// The message was sent sucessfully
}
}
}
}
file2.cpp
#include "common.h"
#include "file1.h"
void task2(void *pvParameters) {
dailyWeather weatherDATA_D[8];
for (;;) {
dailyWeather weatherDATA;
if( xQueueReceive(weatherQueue, &( weatherDATA ), ( TickType_t ) 0 ) ) {
Serial.println("Received");
weatherDATA_D[weatherDATA.itemOrder] = weatherDATA;
}
}
}
此致。