Arduino часто зависает

arduino-nano error

Я использую Arduino Nano для программирования автоматического дезинфицирующего средства для рук с термометром. Но после запуска в течение некоторого времени плата зависает, и только перезапуск может решить проблему. При проверке последовательного монитора он внезапно перестает работать.

Программа включает в себя считывание расстояния с ультразвукового датчика и температуры с ИК-термометра (расстояние и температура окружающей среды считываются непрерывно, и последовательный монитор будет постоянно отображать значения). Случайным образом, когда температура считывается и отображается на OLED-экране, плата зависает (плата зависает только в это время), и перезапуск может заставить ее работать еще 1 раз, а затем снова зависнуть.

Вопрос 1 Это как-то связано с кодом или это аппаратная проблема?

Вопрос 2 Вызывает ли клонированная версия NANO такую проблему? (Я использую версию клона)

Это мой код:

#include <Wire.h>
#include <Adafruit_MLX90614.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

Adafruit_MLX90614 mlx = Adafruit_MLX90614();
Adafruit_SSD1306 display(-1);
//Adafruit_SSD1306 дисплей (128, 64 и провод, -1);

#define trigPin 12 // Триггерный штифт
#define echoPin 11 // echo pin

float roomTemp; // температура
float objectTemp, stemp; // температура объекта

int readcount = 0;
float threshold= 3.5 ;

int maximumRange = 15; // Необходимый максимальный диапазон
int minimumRange = 3; // Необходимый минимальный диапазон
long duration, distance; // Длительность используется для вычисления расстояния
int dtime;
unsigned long rememTime;

// кодирование двигателя
int set_time;
float distance_cm;
unsigned long ultra_time;
int set_cm = 20;
int motor = 2; // Выход для привода двигателя
int flag = 0;

void setup() {
  Serial.begin(9600);// инициализировать последовательную связь со скоростью 9600 бит в секунду:

  pinMode (trigPin, OUTPUT);
  pinMode (echoPin, INPUT);
  pinMode(motor, OUTPUT);

  display.begin(SSD1306_SWITCHCAPVCC, 0x3C) ;
  delay(200);
  display.clearDisplay();
  display.setTextColor(WHITE);
}

void loop() {
  // кодирование датчика температуры
  digitalWrite(trigPin, LOW);
  delayMicroseconds(2);
  digitalWrite(trigPin, HIGH);
  delayMicroseconds(10);
  digitalWrite(trigPin, LOW);
 
  duration = pulseIn(echoPin, HIGH);
  //Вычислить расстояние (в см) по скорости звука.
  distance= duration * 0.034/2;

  // считывание объекта и температуры окружающей среды
  objectTemp = threshold + mlx.readObjectTempC() ;  
  roomTemp = mlx.readAmbientTempC() ;  

  // печать на последовательный порт
  Serial.println("Object:" + String(objectTemp) + ", Ambient:" + String(roomTemp));
  Serial.println(distance);
 
  // дисплей на OLED
  display.clearDisplay();
  display.setTextSize(1);
  display.setCursor(0, 25);
  display.print("Dis:" + String(distance) + "cm");
  display.setCursor(65, 25);
  display.print("Room:" + String(roomTemp).substring(0, 4) + "C");
  display.display();
  display.setTextSize(2);
  display.setCursor(0, 0);
 
  if (distance < minimumRange) {
    display.print("TOO CLOSE!");
  }
  if ((distance >= minimumRange) && (distance <= maximumRange)) {
    if (readcount == 5) { // после чтения 5 раз подряд
      disptemp();
    } else {
      display.print("HOLD ON"); // находясь в зоне действия, попросите пользователя удерживать позицию
      stemp = stemp + objectTemp;
      readcount++; // до прибл. 5 х 200 мс = 1 сек .
    }
  } else { // если пользователь находится вне зоны действия, сбросить расчет
    dtime = 100;
    readcount = 0;
    stemp = 0;
  }
  display.display();
  delay(dtime);
  Serial.println("count  :"+String(readcount));
}

void disptemp() {
  objectTemp = stemp / 5; // получить среднее значение temp
  display.setTextSize(1);
  display.print("YOUR TEMP:");
  display.setTextSize(2);
  display.setCursor(60,5);
  display.print(String(objectTemp).substring(0, 4) + "C");
  display.display();
  readcount = 0;
  stemp = 0;
  if (objectTemp >= 38) {
    play_alert();
  } else {
    play_ok();
  }
  ultrasonicRead();
  dtime = 5000; // подождите 5 секунд.
}

void play_ok() {  // воспроизведение трех последовательных нот при температуре объекта ниже 37,5 °C
  tone(3, 600, 1000);  // контакт, частота, длительность
  delay(200);
  tone(3, 750, 500);
  delay(100);
  tone(3, 1000, 500);
  delay(200);
  noTone(3);
}

void play_alert() { // звуковой сигнал 3x при температуре объекта> = 37.5C
  tone(3, 2000, 1000);
  delay(1000);
  tone(3, 3000, 1000);
  delay(1000);
  tone(3, 4000, 1000);
  delay(1000);
  noTone(3);
}
 
void ultrasonicRead() {
  if (distance < set_cm) {
    digitalWrite(motor, HIGH);
    Serial.println("Motor On");
    delay(500);
    digitalWrite(motor, LOW);
    Serial.println("motor off");
  }
}

, 👍1

Обсуждение

Удалите все строковые объекты из вашего кода., @Majenko

Возможно, вы захотите использовать сторожевой таймер, чтобы заставить плату автоматически сбрасываться для такого рода вещей. Чтобы было ясно, я ** не ** предлагаю это как * решение *; вы все равно должны решить реальную проблему. Но сторожевой пес часто используется для добавления уровня защиты от замерзания при развертывании., @timemage

Это должен быть веселый проект. Так как вы не дали нам схему (не вьющуюся вещь) Я могу только догадываться. Мне кажется, вы используете интерфейс I2C и, возможно, зависите от подтягивающих резисторов. Кроме того, ранняя библиотека 1 wire зависала, если устройство пропускало ACK. Есть много небольших программ, которые отображают объем свободной памяти, включают его в свой код и смотрят, является ли он стабильным или медленно утекает память., @Gil

Вы можете поместить следующие две строки в верхней части скетча: void got_to_line(int n) {Serial.print(F("[Line:")); Serial.print(n); Serial.println(']'); Serial.flush();} и #define GOT_HERE() got_to_line(__LINE__) Затем вы можете засорить свой код GOT_HERE(); перемещая их, пока не найдете точную строку (или строки), где происходит замораживание. Если вы это сделаете, дайте нам знать точную линию (или линии), на которой он замерзает., @timemage


2 ответа


4

Любое выделение / удаление или перераспределение памяти [1] во время выполнения приведет к тому, что куча (пул памяти, из которого производятся эти выделения) будет расширяться до тех пор, пока куча и стек не вырастут друг к другу настолько, чтобы столкнуться, и в этот момент один перезапишет часть другого непредсказуемые результаты.

Строки выделяются из кучи. Строки, которые строятся понемногу, как в случае:
Serial.println("Object:" + String(objectTemp) + ", Ambient:" + String(roomTemp));
причина выделения несколько распределений / отмен.

В качестве простого в реализации теста закомментируйте операторы, использующие объект String, и просто распечатайте необработанные данные. Ваша программа должна работать без сбоев.

Чтобы воссоздать то, что вы сейчас делаете со строками, я бы предложил использовать snprintf() для создания выходного буфера фиксированного размера, который может быть локальным символьным массивом (выделенным в стеке и освобожденным при выходе содержащей функции). Это должно быть так же стабильно, как и сокращенный эксперимент, который я предложил выше.

[1] * Если только выделенная память не будет выделена самой последней-первой. И даже это предполагает предвидение используемого алгоритма распределения памяти, а значит, непереносимо).

Один из способов обойти это - написать собственный распределитель / деаллокатор памяти, который имеет предварительно назначенный пул блоков фиксированной длины, один из которых он предоставляет для любого запроса памяти любого размера, если:

  1. есть свободный блок для выделения; и
  2. запрошенный размер может поместиться в (пространстве данных) предварительно назначенного блока.
,

почему бы не использовать Serial.print()?, @Juraj

Серия Serial.print() вполне приемлема; я предложил snprintf() как семантически более близкую к конкатенации строк, как это было написано в OP., @JRobert


1

В дополнение к некоторым из вышеперечисленных комментариев;

Ваш Nano может не поддерживать сторожевой пес. Пожалуйста, проверьте этот пост на форуме Arduino

Если вы ищете "Есть много маленьких программ ..." Ниже я просто опубликую функции, которые я использую для получения информации о свободной памяти, куче и стеке.

int freeRam()
{
    extern int __heap_start, *__brkval;
    int v;
    return (int)&v - (__brkval == 0 ? (int)&__heap_start : (int)__brkval);
}

uint8_t * stack()
{
  uint8_t * ptr;
  ptr = (uint8_t *)malloc(4);
  free(ptr);
  return ((uint8_t *)(SP));
}

uint8_t * heap()
{
       uint8_t * ptr;
       ptr = (uint8_t *)malloc(4);
       free(ptr);
       return(ptr);
}
,

"может не поддерживать сторожевого пса" - пожалуй, не самый лучший способ выразить это. [Optiboot очищается](https://github.com/arduino/ArduinoCore-avr/blob/1.8.3/bootloaders/optiboot/optiboot.c#L289 ) "MCUSR" и поэтому "WDRF", то есть вы не можете сказать, что сторожевой таймер был * причиной* сброса, просто проверив "MCUSR". В качестве механизма сброса он в остальном работает совершенно нормально. Есть несколько способов в некоторой степени решить проблему очистки "MCUSR", помимо просто исправления загрузчика., @timemage