通过 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() 在创建 task1task2 的同一个函数中(队列必须在任务之前创建),像这样:

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()中的代码循环执行以下操作:

  1. 在堆栈中创建一个包含 8 个 dailyWeather 结构的新数组(在单个循环迭代的范围内)
  2. 将指向 weatherDATA[] 中第一个项目的指针复制到队列中(task2 稍后会在需要切换任务时收到它)
  3. 释放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;
    }
  }
}

此致。