Проблемы с памятью? dtostr() и strcat()

Я занимаюсь этим уже довольно давно и повсюду ищу ответы.. Я пытаюсь объединить два float в char*. Разделяются символом ",". Это код, с которым я работаю.

#include <DHT.h>
#include <RH_ASK.h>
#include <SPI.h>

#define DHT_pin 10
#define DHT_type DHT11

DHT dht(DHT_pin, DHT_type);
RH_ASK rf_driver;

float temp;
float hum;

char ch_temp[6];
char ch_hum[6];
char msg_out[12] = "";

void setup() {
  Serial.begin (115200);
  dht.begin();
  if (rf_driver.init()) {
    Serial.println("Radio transmitter initiated..");
  }
  else {
    Serial.println("Radio transmitter failed to initiate..");
    delay(1000);
  }
}
void loop() {
  temp = dht.readTemperature();
  hum = dht.readHumidity();

  dtostrf(temp, 5, 2, ch_temp);
  dtostrf(hum, 5, 2, ch_hum);
  strcat(msg_out, ch_temp);
  strcat(msg_out, ",");
  strcat(msg_out, ch_hum);

  Serial.println(msg_out);
  rf_driver.send((uint8_t *)msg_out, strlen(msg_out));
  rf_driver.waitPacketSent();
  Serial.print("data sent, package size: ");
  Serial.println(strlen(msg_out));
  delay(1000);

}

Я пробовал использовать разные размеры массивов символов, но это просто сводит с ума. Это вывод на консоль:

Radio transmitter initiated..
22.00,34.00
data sent, package size: 11
⸮⸮Radio transmitter initiated..
22.00,34.00
data sent, package size: 11

Таким образом, на своем пути через второй цикл он выходит из строя, перезапускается, а затем на своем третьем пути просто сдается... Что я делаю не так? :( Очень грустно из-за этого.

, 👍2


3 ответа


2

Если вы удалите RF и код датчика, скетч, похоже, будет работать правильно при печати на последовательный монитор. При первом вызове rf_driver.send() в цикле это работает. Во второй раз через цикл, и каждый раз после этого, он начинает печатать поврежденные данные. У меня нет окончательного ответа, почему это происходит, но у меня есть способ "исправить" это (или "взломать", если вы предпочитаете называть это так).

Использование memset() для установки массива символов msg_out во все 0 перед использованием, похоже, устраняет проблему. Вот тестовый скетч, который очень похож на ваш. Я использую некоторые жестко закодированные значения для данных датчиков, потому что у меня нет ни одного из этих датчиков.

#include <RH_ASK.h>
#include <SPI.h>
RH_ASK rf_driver;

float temp;
float hum;

char ch_temp[6];
char ch_hum[6];
char msg_out[12] = "";

void setup() {
  Serial.begin (9600);
  if (rf_driver.init()) {
    Serial.println("Radio transmitter initiated..");
  }
  else {
    Serial.println("Radio transmitter failed to initiate..");
    delay(1000);
  }
}
void loop() {

  // Установите содержимое буфера на все 0
  memset(msg_out, 0, sizeof(msg_out));

  temp = 12.345;
  hum = 67.890;

  dtostrf(temp, 5, 2, ch_temp);
  dtostrf(hum, 5, 2, ch_hum);
  strcat(msg_out, ch_temp);
  strcat(msg_out, ",");
  strcat(msg_out, ch_hum);

  Serial.println(msg_out);
  rf_driver.send((uint8_t *)msg_out, strlen(msg_out));
  rf_driver.waitPacketSent();
  Serial.print("data sent, package size: ");
  Serial.println(strlen(msg_out));
  delay(1000);
}
,

2

Ваш скетч выполняется на основе заимствованной памяти.
Каждый раз, когда выполняется функция цикла, новый текст объединяется с strcat(), и буфер msg_out никогда не сбрасывается. В первый раз используется 12 байт, во второй раз 23 байта, в третий раз 34 и так далее.

Решение в ответе @VE7JRO исправит это, но я предпочитаю не использовать dtostrf() и передавать двоичные данные.

Я вижу и несколько других проблем:

  • Почему вы хотите передавать читаемый текст в формате ASCII? Используйте двоичные данные. Либо числа с плавающей запятой, либо целые числа. Например, целое число в 1/100 градусов.
  • Библиотека RadioHead использует много памяти. Вы не можете использовать другую библиотеку, которая также использует много памяти.
  • Значение '5' для dtostrf - это минимальная ширина, поэтому вам лучше сделать эти массивы, например, 20 байтами. Я предпочитаю временно помещать эти массивы в стек.
  • DHT11 не является точным. Если вы перейдете на более качественный датчик с помощью I2C, то вам потребуется больше библиотек (библиотека проводов и библиотека датчиков), и у вас может закончиться память sram.
  • При использовании библиотеки RadioHead в режиме по умолчанию используются три контакта (rxPin =11, txPin = 12, pttPin =10). Вам лучше добавить раздел комментариев к своему скетчу, чтобы объяснить, что вам не следует использовать эти контакты. У вас уже есть конфликт на выводе 10 для датчика DHT и pttPin.

В старой и больше не поддерживаемой библиотеке VirtualWire есть некоторые ошибки, но она использует меньше памяти, чем библиотека RadioHead. Библиотека RadioHead не оптимизирована для плат Arduino, таких как Uno, Nano, Leonardo и других. Для этого даже требуется #включить <SPI.h>, потому что это часто используется с приемопередатчиками, даже если шина SPI не используется в режиме RH_ASK (спасибо @VE7JRO за объяснение этого в комментарии ниже).


Самый простой способ передать более одного значения - это использовать массив.

float myData[2];
myData[0] = 23.456;
myData[1] = 68.0;
rf_driver.send((uint8_t *)myData, sizeof(myData));

Sizeof() не является реальной функцией, она сообщает компилятору заполнить размер этой переменной. Поскольку число с плавающей запятой равно 4 байтам, вы также можете ввести число 8 для второго параметра.

Существует несколько решений для приемника. Мне нравится использовать указатель на буфер, но, возможно, проще скопировать их в переменные с помощью memcpy().

float myData[2];
if (driver.recv(buf, &buflen)) {
  if (buflen == sizeof(myData) {  // дополнительная проверка
    memcpy((uint_8 *)myData, buf, sizeof(myData));
    Serial.println(myData[0]);
    Serial.println(myData[1]);
  }
}

Когда приемник также является платой Arduino, тогда проблем быть не должно. Когда получателем является что-то другое, вам, возможно, придется проверить, совпадает ли порядок байтов. Переменная с плавающей запятой Arduino на 100% совместима с IEEE, но иногда проще использовать только целые числа.

Наиболее распространенным способом является использование структуры. Структура - это пакет, который может содержать все виды данных (целые числа, байты, массивы и так далее). Конечно, получатель и отправитель должны иметь точно такое же определение для структуры.

,

Возможно, операционная система не использует SPI. Если вы удалите включаемый элемент из скетча, он не будет компилироваться. Существует скетч под названием ask_transmitter.pde, который поставляется вместе с библиотекой. Рядом с SPI include есть комментарий, в котором говорится: "Фактически не используется, но необходим для компиляции". Я новичок в Arduino, поэтому я не понимаю этого комментария. Если ни один из файлов RadioHead не использует какие-либо функции и т.д. в библиотеке SPI, почему он не будет компилироваться без нее? Я поддерживаю вашу рекомендацию избавиться от датчика "низкого качества", который использует операционная система. За 1,28 доллара США вы можете приобрести приличный датчик Bosch I2C, такой как BMP280, по цене Aliexpress.com ., @VE7JRO

@VE7JRO верно, я знал это, но забыл об этом., @Jot

Спасибо вам за этот обстоятельный ответ! По-настоящему оцените это!, @Love.Berg

@Jot Спасибо вам за этот подробный ответ! По-настоящему оцените это!! Причина, по которой я конвертирую в Char *, заключается в том, чтобы собрать его вместе, чтобы я мог отправить его в одном пакете, а затем позже другой микроконтроллер легко разобрал его и отобразил отдельно. Как бы я собрал поплавки вместе и смог бы разделить их позже? Я пробовал VirtualWire, но в данном случае это все, что должен делать этот MCU. Но я буду иметь это в виду в будущем! Спасибо! Этот проект предназначен для школьного задания, но я заметил, что DHT11 не очень надежен.. Спасибо, что указали на конфликт выводов!!, @Love.Berg

@Любовь.Berg Я добавил дополнительный раздел для передачи массива. Он передается как двоичные данные и принимается как двоичные данные, поэтому все, что вам нужно сделать, это взять двоичные данные из переменных с плавающей запятой, передать их и в приемнике поместить двоичные данные обратно в переменные с плавающей запятой., @Jot


1

Я опаздываю на вечеринку, но вот что происходит:

msg_out[] является глобальным, но это не обязательно. Переместите его определение и инициализацию в loop() , единственную функцию, которая его использует. Затем он инициализируется при каждом входе в loop(), а не только один раз при инициализации программы. Нет необходимости очищать массив; просто обнуление нулевого элемента инициализирует его для использования в виде символьной строки, и это то, что теперь делает ваш инициализатор (но только один раз).

,