1

我从 ESP32 和 FREERTOS 开始,但在跨队列发送 Struct 数组时遇到问题。我已经发送了另一种变量,但从未发送过结构数组,并且我遇到了异常。

发送者和接收者位于不同的源文件中,我开始发现可能是问题(或至少部分问题)。

我的简化代码如下所示:

常见的.h

struct dailyWeather {
  // Day of the week starting in Monday (1)
  int dayOfWeek;

  // Min and Max daily temperature
  float minTemperature;
  float maxTemperature;

  int weather;
};

文件1.h

#pragma once
#ifndef _FILE1_
#define _FILE1_

// Queue
extern QueueHandle_t weatherQueue;

#endif

文件1.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
    }
  }
}

文件2.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.

我被这个问题锁定了,我无法找到解决它的方法,所以任何帮助都会非常感激。

编辑:我想也许一个解决方案只是向结构添加一个订单项,使队列更大(数量)并将所有结构分别发送到队列。然后在阅读器中使用该订单再次订购。

无论如何,很高兴了解我在上面的代码中做错了什么。

4

3 回答 3

2

当您调用时,freeRTOS 队列通过使用您在初始化期间指定的缓冲区和数据大小来操作xQueueCreate(),以制作您想要发送-接收的数据的副本。

当您调用xQueueSend()等效于 的 时xQueueSendToBack(),它会将副本复制到该缓冲区中。

如果另一个任务在对 的调用中等待队列xQueueReceive(),则在它准备好运行时,xQueueReceive()会将队列缓冲区前面的项目复制到您在调用中指定的目标缓冲区中xQueueReceive()

如果您要发送的数据是指针/数组类型,dailyWeather *那么在您的情况下,您需要确保指针指向的内存在被通过调用接收指针的任务读取之前不会超出范围xQueueReceive()。局部变量是在调用任务的堆栈中创建的,并且在函数返回后肯定会超出范围,并且很可能会被覆盖。

IMO,如果您确实需要传递指针,最好的解决方案是在生成数据的函数中分配结构数组,并在使用数据的任务中释放它。

对于许多情况,非常希望不要滥用动态内存处理,因此在几个通信堆栈中,您会发现缓冲池的使用,最后也是在应用程序启动期间初始化的队列。操作大致如下:

初始化:

  • 初始化缓冲池队列(简单的指针队列)。
  • 用适当大小的动态分配的缓冲区填充缓冲池。
  • 初始化任务间通信的队列。

提供数据的任务:

  • 从一个缓冲池中获取(接收)一个缓冲指针。
  • 用数据填充缓冲区。
  • 将缓冲区指针发送到通信队列。

接收数据的任务:

  • 从通信队列中获取(接收)数据缓冲区指针。
  • 使用数据。
  • 将缓冲区指针返回(发送)到缓冲池。

如果您的结构很小,因此您或多或少地限制了复制后复制开销,那么创建队列更有意义,因此您可以直接使用结构实例和结构副本而不是结构缓冲区指针。

于 2021-05-08T20:59:27.030 回答
1

首先,像您一样在全局范围内创建队列并不是一个好主意。全局队列句柄没问题。但是在创建和(必须在任务之前创建队列)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);
    }
  }
}

我不能推荐足够多的官方 FreeRTOS 书籍,它对于初学者来说是一个很好的资源。

于 2021-05-08T09:57:42.553 回答
0

感谢大家的回答。

最后,我添加了一个变量来跟踪项目位置,并将所有数据通过队列传递到目标任务。然后我将所有这些结构放回另一个数组中。

常见的.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;
};

文件1.h

// Queue
extern QueueHandle_t weatherQueue;

文件1.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
      }
    }
  }
}

文件2.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;
    }
  }
}

最好的祝福。

于 2021-05-09T22:05:52.920 回答