Зависание Arduino при записи на SD

lengast

✩✩✩✩✩✩✩
1 Дек 2020
3
0
Добрый день.

Плата должна включать/отключать нагреватель в зависимости от температуры. А также параллельно записывать ЛОГ файл на СД карту.
Проблема - спустя 1,5-2 часа работы плата полностью встает, В чем может быть проблема.
Код прикладываю.
И господа, заранее спасибо.
Arduino mega
BME-280
MicroSd card adapter - логгер.


Код:
//#include "logger.h"
#include "pin_map.h"
#include "settings.h"

// библиотека для работы I²C
#include <Wire.h>

#include <SPI.h>
#include <SD.h>

#include <TimerOne.h>

// библиотека для работы с метеосенсором
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>

// создаём объект для работы с датчиком в камере
Adafruit_BME280 bme;

// Переменные для хранения метеоданных
volatile float pressure = 0;
volatile float temperature = 0;
volatile float humidity = 0;

unsigned long timestampTelemetryWrite = 0;

//переменные для диода
boolean InitSD;
boolean InitMeteo;

void writeSD(String str)
{
    File logFile = SD.open("log1.txt", FILE_WRITE);

    // if the file is available, write to it:
    if (logFile)
    {
        logFile.println(String(millis()) + ":::" + " "+ str);
        logFile.close();
        Serial.println("Write to log1.txt---OK");
        InitSD = true;
    }
    // if the file isn't open, pop up an error:
    else
    {
        Serial.println("error opening log1.txt");
        InitSD = false;
    }
}

void initMeteoSensor()
{
    // печатаем сообщение об успешной инициализации Serial-порта
    Serial.println("Meteo begin init...");
    writeSD("Meteo begin init...");
    // начало работы с датчиком
    unsigned status;

    status = bme.begin(0x76);
    if (!status)
    {
        Serial.println("Meteo Sensor init FAIL");
        writeSD("Meteo Sensor init FAIL");
        InitMeteo = false;
    }
    else
    {
        Serial.println("Meteo Sensor init OK");
        writeSD("Meteo Sensor init OK");
        InitMeteo = true;
    }
}

void updateSensorData()
{
    pressure = bme.readPressure() / 100.0f;
    temperature = bme.readTemperature();
    humidity = bme.readHumidity();
}

void warmControl()
{
    Serial.println("Temperature in camera:" + String(temperature));
    writeSD("Temperature in camera:" + String(temperature));
    // Если температура ниже минимальной
    if (temperature < TEMPERATURE_IN_CAMERA_MIN)
    {
        // то включаем подогрев
        digitalWrite(WARM_CONTROL_PIN, ON);
        Serial.println("Warm is ON");
        Serial.println("Write WARM_CONTROL_PIN = " + String(ON));
        writeSD("Warm is ON");
    }
    // Если больше максимальной
    else if (temperature > TEMPERATURE_IN_CAMERA_MAX)
    {
        // То выключаем подогрев
        digitalWrite(WARM_CONTROL_PIN, OFF);
        Serial.println("Warm is OFF");
        Serial.println("Write WARM_CONTROL_PIN = " + String(OFF));
        writeSD("Warm is OFF");
    }
    else
    {
        /* code */
    }

    if (millis() - timestampTelemetryWrite > TELEMETRY_DELAY_MS)
    {
        Serial.println("Telemetry Write Start");
        writeSD("Telemetry Write Start");
        String result = "";

        result += String(millis()/60000);
        result += ":min";
        result += ":::";
        result += " TemperatureInCamera = " + String(temperature) + " ";
        result += " PressureInCamera = " + String(pressure) + " ";
        result += " HumidityInCamera = " + String(humidity) + " ";
        result += "\n";
    
        Serial.println(result);
    
        File dataFile = SD.open("datalog1.txt", FILE_WRITE);

        // if the file is available, write to it:
        if (dataFile)
        {
            dataFile.println(result);
            dataFile.close();
            Serial.println("Write to datalog1.txt---OK");
        }
        // if the file isn't open, pop up an error:
        else
        {
            Serial.println("error opening datalog1.txt");
        }
        timestampTelemetryWrite = millis();
        Serial.println("Telemetry Write End");
        writeSD("Telemetry Write End");
    }
}

void setup()
{
    Serial.begin(115200);
    // Init Sensor
    initMeteoSensor();

    if (!SD.begin(SD_CARD_ENABLE_PIN))
    {
        Serial.println("Card failed, or not present");
        // don't do anything more:
    }
    Serial.println("card initialized.");

    // Настройка пина управления подогревателем
    pinMode(WARM_CONTROL_PIN, OUTPUT);
    pinMode(LED_PIN, OUTPUT);
}

void loop()
{
    static unsigned long timestampSensorUpdate = 0;
    static unsigned long warmControlTimestamp = 0;
  if (millis() - warmControlTimestamp > 1000)
  {
        // контроль температуры в барокамере
        //Serial.println("warmControl");
        //writeSD("warmControl");
        warmControl();
        warmControlTimestamp = millis();
  }



    if (millis() - timestampSensorUpdate > 1000)
    {
        updateSensorData();
        timestampSensorUpdate = millis();
    }

    if (InitSD and InitMeteo)
    {
      digitalWrite(LED_PIN, HIGH);
    }
    else
    {
      digitalWrite(LED_PIN, LOW);
    }
}
 

Старик Похабыч

★★★★★★★
14 Авг 2019
4,159
1,267
Москва
А что при этом выведено в мониторе порта ? может она зависает каждый раз на одном и том же месте ?
Еще небольшой лайфхак. Т.к. время срабатывания у вас одно и то же - 1 секунда, то лучше сделать разбежку между этими процедурами, например сделав imestampSensorUpdate=500 в самом начале.
А состояние светодиода, который зажигается при наличии карты и датчика ? Если датчик определен в самом начале, в setup, то уже нет смысла все время проверять его. Он есть и все. А если его нет, то нет смысла продолжать работу. Лучше зациклить прям там и сигнально мигать например. В лог писать нечго, температуру брать неоткуда.
 

DAK

★★★✩✩✩✩
8 Окт 2020
517
137
И ещё очень интересно почитать log.txt. Судя по детализации логов, он может помочь сообразить в чём проблема.
 

bort707

★★★★★★✩
21 Сен 2020
2,859
850
Проблема - спустя 1,5-2 часа работы плата полностью встает, В чем может быть проблема.
думаю, память заканчивается. Вот так никогда не надо делать:
C++:
        result += String(millis()/60000);
        result += ":min";
        result += ":::";
        result += " TemperatureInCamera = " + String(temperature) + " ";
        result += " PressureInCamera = " + String(pressure) + " ";
        result += " HumidityInCamera = " + String(humidity) + " ";
        result += "\n";
это приводит к фрагментации, и, как следствие, к утечке памяти.
Либо уберите все новые вызовы String во время формирования строки, то есть вместо
C++:
result += " HumidityInCamera = " + String(humidity) + " ";
пишите просто
C++:
result += " HumidityInCamera = " + humidity;
- класс String ведь умеет автоматически преобразовывать числовые типы в строку.

Либо заключите всю конструкцию, где упоминается переменная result - в фигурные скобки. Это создаст вокруг этой переменной блок, а саму переменную сделает локальной для этого блока. При выходе из блока переменная автоматически почистится. Только в этом блоке не должно быть работы с другими динамическими переменными
 
  • Лойс +1
Реакции: kostyamat

bort707

★★★★★★✩
21 Сен 2020
2,859
850
А еще советую подумать, что вы пишете в лог. Не пишите туда сотни и тысячи одинаковых строчек типа
"HumidityInCamera = ". Лог - это таблица. В таблице ведь вы не пишете обозначение величин на каждой строчке, верно? Вы пишете его один раз над колонкой. Вот и тут так же - отдайте под каждую величину свою колонку и пишите все значения подряд, разделяя каким-то знаком, например
22:34;33.4; 17.6;66;
тут в первой колонке время, 22:34, потом идет температура - 33.4, потом давление, скажем 17.6, а потом влажность - 66%
Скажете сложно читать? Но лог файл и не предназначен для чтения человеком. Значительно удобнее обрбатывать логи, например, в Экселе. Экспортируете свой файл в Эксель, данные автоматически расставляются по колонкам, можно рассчитывать статистику и строить графики.
А насколько будет короче файл и насколько меньше программа!
 
  • Лойс +1
Реакции: DAK

Старик Похабыч

★★★★★★★
14 Авг 2019
4,159
1,267
Москва
Ну я прикинул если писать 2 часа раз в секунду это 7200 записей. пусть по 200 байт, 1.44 мб, дискета. Вряд ли память
 

lengast

✩✩✩✩✩✩✩
1 Дек 2020
3
0
думаю, память заканчивается. Вот так никогда не надо делать:
C++:
        result += String(millis()/60000);
        result += ":min";
        result += ":::";
        result += " TemperatureInCamera = " + String(temperature) + " ";
        result += " PressureInCamera = " + String(pressure) + " ";
        result += " HumidityInCamera = " + String(humidity) + " ";
        result += "\n";
это приводит к фрагментации, и, как следствие, к утечке памяти.
Либо уберите все новые вызовы String во время формирования строки, то есть вместо
C++:
result += " HumidityInCamera = " + String(humidity) + " ";
пишите просто
C++:
result += " HumidityInCamera = " + humidity;
- класс String ведь умеет автоматически преобразовывать числовые типы в строку.

Либо заключите всю конструкцию, где упоминается переменная result - в фигурные скобки. Это создаст вокруг этой переменной блок, а саму переменную сделает локальной для этого блока. При выходе из блока переменная автоматически почистится. Только в этом блоке не должно быть работы с другими динамическими переменными
Спасибо большое за ответ.
Если на выходе из фигурной скобки переменная будет чистится, то и вызывать мне ее нужно в этом же блоке.
Будет ли ошибкой сделать так?
1606851704415.png
Выделенное цветом я заключаю в фигурные скобки.
 

poty

★★★★★★✩
19 Фев 2020
2,956
886
Коллеги, result и так находится в блоке if, он там локально определён (линии 109-141). Уничтожается вместе с окончанием блока. Кроме того, компилятор весьма оптимизирован в таких делах, фрагментация практически исключена.
Можно ли сообщить, сколько после компиляции остаётся свободного места в памяти для данных? С моей точки зрения, вольное написание кода приводит к исчерпанию стека.
 

bort707

★★★★★★✩
21 Сен 2020
2,859
850
Коллеги, result и так находится в блоке if, он там локально определён (линии 109-141). Уничтожается вместе с окончанием блока. Кроме того, компилятор весьма оптимизирован в таких делах, фрагментация практически исключена.
компилятор может и соптимизирует все. а может и нет. Чтобы уменьшить риск фрагментации - важно захватывать и очищать память в определенном порядке. Чем больше операций в блоке - тем труднее это соблюсти.
Указанный кусок я бы переписал вот так:
C++:
if (millis() - timestampTelemetryWrite > TELEMETRY_DELAY_MS)
    {
        Serial.println("Telemetry Write Start");
        writeSD("Telemetry Write Start");
      
  
        File dataFile = SD.open("datalog1.txt", FILE_WRITE);

        // if the file is available, write to it:
        if (dataFile)
        {
            String result = "";

            result += String(millis()/60000);
            result += ":min";
            result += ":::";
            result += " TemperatureInCamera = " + String(temperature) + " ";
            result += " PressureInCamera = " + String(pressure) + " ";
            result += " HumidityInCamera = " + String(humidity) + " ";
            result += "\n";
  
            Serial.println(result);
            dataFile.println(result);
            dataFile.close();
            Serial.println("Write to datalog1.txt---OK");
        }
        // if the file isn't open, pop up an error:
        else
        {
            Serial.println("error opening datalog1.txt");
        }
        timestampTelemetryWrite = millis();
        Serial.println("Telemetry Write End");
        writeSD("Telemetry Write End");
    }
тогда переменная существует только в блоке записи лога. Открыли файл, создали переменную, записали в файл - и тут же закрыли блок. Дополнительным плюсом будет то, что если файл на СД почему-то не открылся - то и создавать эту монстрозную строчку незачем.

И, кстати, @lengast, проверить есть тут фрагментация или нет - очень просто.
Перепишите кусок создания переменной result без дополнительных String как я писал выше - и запустите программу. Зависоны, возможно, и не уйдут - но думаю что время до зависания возрастет в несколько раз

А еще @lengast, - все-таки подумайте. зачем всю эту "лапшу" - я имею в виду длинные строки - писать в лог. Пишите только данные
 
Изменено: