[solved] Sensor freezes - Low memory available, stability problems may occur.



  • Hi,

    I have a temp/hum/baro/voltage sensor (BME280) with signing (ATSHA204) and debug and I am running on a low memory.

    Sketch uses 31158 bytes (96%) of program storage space. Maximum is 32256 bytes.
    Global variables use 1660 bytes (81%) of dynamic memory, leaving 388 bytes for local variables. Maximum is 2048 bytes.
    Low memory available, stability problems may occur.
    

    This is causing the sensor to freeze after a few hours.
    I have improved the sketch as much as I could. Can anyone help me to improve the sketch further please???

    //ID of node
    #define MY_NODE_ID 6
    //#define MY_PARENT_NODE_ID 0
    
    // Enable debug prints to serial monitor
    #define MY_DEBUG
    #define MY_DEBUG_VERBOSE_SIGNING
    
    // Enable and select radio type attached
    #define MY_RADIO_NRF24
    
    #define MY_SIGNING_ATSHA204
    //#define MY_SIGNING_REQUEST_SIGNATURES
    #define MY_SIGNING_ATSHA204_PIN 17
    #define MY_OTA_FIRMWARE_FEATURE
    
    #include <SPI.h>
    #include <MySensors.h>
    #include <Wire.h>
    #include <Adafruit_BME280.h>
    #include <Adafruit_Sensor.h>
    //#include <avr/power.h>
    
    #define BARO_CHILD 0
    #define TEMP_CHILD 1
    #define HUM_CHILD 2
    #define BATT_SENSOR 3
    
    #define RELEASE "1.1"
    
    const float ALTITUDE = 7; // <-- adapt this value to your own location's altitude.
    
    // Sleep time between reads (in seconds). Do not change this value as the forecast algorithm needs a sample every minute.
    const unsigned long SLEEP_TIME = 300000;
    const unsigned long FORCE_TRANSMIT_INTERVAL = SLEEP_TIME;
    
    const char *weather[] = { "stable", "sunny", "cloudy", "unstable", "thunderstorm", "unknown" };
    enum FORECAST
    
    {
      STABLE = 0, // "Stable Weather Pattern"
      SUNNY = 1,      // "Slowly rising Good Weather", "Clear/Sunny "
      CLOUDY = 2,     // "Slowly falling L-Pressure ", "Cloudy/Rain "
      UNSTABLE = 3,   // "Quickly rising H-Press",     "Not Stable"
      THUNDERSTORM = 4, // "Quickly falling L-Press",    "Thunderstorm"
      UNKNOWN = 5     // "Unknown (More Time needed)
    };
    
    Adafruit_BME280 bme; // I2C
    //Adafruit_BMP280 bme = Adafruit_BMP280();      // Digital Pressure Sensor
    
    
    // Pins
    #define LED_PIN 5 //8
    
    
    float lastPressure = -1;
    float lastTemp = -1;
    int lastForecast = -1;
    float lastHum = -1;
    
    const int LAST_SAMPLES_COUNT = 5;
    float lastPressureSamples[LAST_SAMPLES_COUNT];
    
    // this CONVERSION_FACTOR is used to convert from Pa to kPa in forecast algorithm
    // get kPa/h be dividing hPa by 10
    #define CONVERSION_FACTOR (1.0/10.0)
    
    int minuteCount = 0;
    bool firstRound = true;
    // average value is used in forecast algorithm.
    float pressureAvg;
    // average after 2 hours is used as reference value for the next iteration.
    float pressureAvg2;
    
    float dP_dt;
    boolean metric;
    MyMessage tempMsg(TEMP_CHILD, V_TEMP);
    MyMessage humMsg(HUM_CHILD, V_HUM);
    MyMessage pressureMsg(BARO_CHILD, V_PRESSURE);
    MyMessage forecastMsg(BARO_CHILD, V_FORECAST);
    MyMessage msgBatt(BATT_SENSOR, V_VOLTAGE);
    
    // Global settings
    int measureCount = 0;
    int sendBattery = 0;
    boolean isMetric = true;
    boolean highfreq = true;
    
    // Storage of old measurements
    float lastTemperature = -100;
    int lastHumidity = -100;
    long lastBattery = -100;
    
    
    void setup()
    {
      pinMode(LED_PIN, OUTPUT);
      digitalWrite(LED_PIN, LOW);
    
      Serial.begin(115200);
      Serial.print(F("BME280 Pressure Sensor"));
      Serial.print(RELEASE);
      Serial.flush();
    
      digitalWrite(LED_PIN, HIGH);
    
      if (!bme.begin())
      {
        Serial.println(F("Could not find a valid BME280 sensor, check wiring!"));
        while (1) {}
      }
    
      digitalWrite(LED_PIN, LOW);
    
      metric = getControllerConfig().isMetric;
      Serial.print(F("isMetric: ")); Serial.println(isMetric);
      sendBattLevel(false);
    }
    
    void presentation() {
    
      // Send the sketch version information to the gateway and Controller
      sendSketchInfo("High Precision Pressure Sensor", RELEASE);
    
      // Register sensors to gw (they will be created as child devices)
      present(BARO_CHILD, S_BARO);
      present(TEMP_CHILD, S_TEMP);
      present(HUM_CHILD, S_HUM);
      present(BATT_SENSOR, S_POWER);
    }
    
    void loop()
    {
    
      measureCount ++;
      sendBattery ++;
      bool forceTransmit = false;
    
      if ((measureCount == 5) && highfreq)
      {
        clock_prescale_set(clock_div_8); // Switch to 1Mhz for the reminder of the sketch, save power.
        highfreq = false;
      }
    
      if (measureCount > FORCE_TRANSMIT_INTERVAL) { // force a transmission
        forceTransmit = true;
        measureCount = 0;
      }
    
    
      float pressure = bme.readPressure() / 100.0F;
      float temperature = bme.readTemperature();
      float humidity = bme.readHumidity();
    
      if (!metric)
      {
        // Convert to fahrenheit
        temperature = temperature * 9.0 / 5.0 + 32.0;
      }
    
      int forecast = sample(pressure);
    
      Serial.print(F("Temperature = "));
      Serial.print(temperature);
      Serial.println(metric ? " *C" : " *F");
    
      Serial.print(F("Humidity = "));
      Serial.print(humidity);
      Serial.println(metric ? " %" : " *F");
    
    
      Serial.print(F("Pressure = "));
      Serial.print(pressure);
      Serial.println(F(" hPa"));
      Serial.print(F("Forecast = "));
      Serial.println(weather[forecast]);
    
    
      if (temperature != lastTemp)
      {
        send(tempMsg.set(temperature, 1));
        lastTemp = temperature;
      }
    
      if (humidity != lastHum)
      {
        send(humMsg.set(humidity, 1));
        lastHum = humidity;
      }
    
      if (pressure != lastPressure)
      {
        send(pressureMsg.set(pressure, 0));
        lastPressure = pressure;
      }
    
      if (forecast != lastForecast)
      {
        send(forecastMsg.set(weather[forecast]));
        lastForecast = forecast;
      }
    
      digitalWrite(LED_PIN, HIGH);
      delay(50);
      digitalWrite(LED_PIN, LOW);
    
    
      if (sendBattery > 60)
      {
        sendBattLevel(forceTransmit); // Not needed to send battery info that often
        sendBattery = 0;
      }
    
      sleep(SLEEP_TIME);
    }
    
    float getLastPressureSamplesAverage()
    {
      float lastPressureSamplesAverage = 0;
      for (int i = 0; i < LAST_SAMPLES_COUNT; i++)
      {
        lastPressureSamplesAverage += lastPressureSamples[i];
      }
      lastPressureSamplesAverage /= LAST_SAMPLES_COUNT;
    
      return lastPressureSamplesAverage;
    }
    
    
    
    // Algorithm found here
    // http://www.freescale.com/files/sensors/doc/app_note/AN3914.pdf
    // Pressure in hPa -->  forecast done by calculating kPa/h
    int sample(float pressure)
    {
      // Calculate the average of the last n minutes.
      int index = minuteCount % LAST_SAMPLES_COUNT;
      lastPressureSamples[index] = pressure;
    
      minuteCount++;
      if (minuteCount > 185)
      {
        minuteCount = 6;
      }
    
      if (minuteCount == 5)
      {
        pressureAvg = getLastPressureSamplesAverage();
      }
      else if (minuteCount == 35)
      {
        float lastPressureAvg = getLastPressureSamplesAverage();
        float change = (lastPressureAvg - pressureAvg) * CONVERSION_FACTOR;
        if (firstRound) // first time initial 3 hour
        {
          dP_dt = change * 2; // note this is for t = 0.5hour
        }
        else
        {
          dP_dt = change / 1.5; // divide by 1.5 as this is the difference in time from 0 value.
        }
      }
      else if (minuteCount == 65)
      {
        float lastPressureAvg = getLastPressureSamplesAverage();
        float change = (lastPressureAvg - pressureAvg) * CONVERSION_FACTOR;
        if (firstRound) //first time initial 3 hour
        {
          dP_dt = change; //note this is for t = 1 hour
        }
        else
        {
          dP_dt = change / 2; //divide by 2 as this is the difference in time from 0 value
        }
      }
      else if (minuteCount == 95)
      {
        float lastPressureAvg = getLastPressureSamplesAverage();
        float change = (lastPressureAvg - pressureAvg) * CONVERSION_FACTOR;
        if (firstRound) // first time initial 3 hour
        {
          dP_dt = change / 1.5; // note this is for t = 1.5 hour
        }
        else
        {
          dP_dt = change / 2.5; // divide by 2.5 as this is the difference in time from 0 value
        }
      }
      else if (minuteCount == 125)
      {
        float lastPressureAvg = getLastPressureSamplesAverage();
        pressureAvg2 = lastPressureAvg; // store for later use.
        float change = (lastPressureAvg - pressureAvg) * CONVERSION_FACTOR;
        if (firstRound) // first time initial 3 hour
        {
          dP_dt = change / 2; // note this is for t = 2 hour
        }
        else
        {
          dP_dt = change / 3; // divide by 3 as this is the difference in time from 0 value
        }
      }
      else if (minuteCount == 155)
      {
        float lastPressureAvg = getLastPressureSamplesAverage();
        float change = (lastPressureAvg - pressureAvg) * CONVERSION_FACTOR;
        if (firstRound) // first time initial 3 hour
        {
          dP_dt = change / 2.5; // note this is for t = 2.5 hour
        }
        else
        {
          dP_dt = change / 3.5; // divide by 3.5 as this is the difference in time from 0 value
        }
      }
      else if (minuteCount == 185)
      {
        float lastPressureAvg = getLastPressureSamplesAverage();
        float change = (lastPressureAvg - pressureAvg) * CONVERSION_FACTOR;
        if (firstRound) // first time initial 3 hour
        {
          dP_dt = change / 3; // note this is for t = 3 hour
        }
        else
        {
          dP_dt = change / 4; // divide by 4 as this is the difference in time from 0 value
        }
        pressureAvg = pressureAvg2; // Equating the pressure at 0 to the pressure at 2 hour after 3 hours have past.
        firstRound = false; // flag to let you know that this is on the past 3 hour mark. Initialized to 0 outside main loop.
      }
    
      int forecast = UNKNOWN;
      if (minuteCount < 35 && firstRound) //if time is less than 35 min on the first 3 hour interval.
      {
        forecast = UNKNOWN;
      }
      else if (dP_dt < (-0.25))
      {
        forecast = THUNDERSTORM;
      }
      else if (dP_dt > 0.25)
      {
        forecast = UNSTABLE;
      }
      else if ((dP_dt > (-0.25)) && (dP_dt < (-0.05)))
      {
        forecast = CLOUDY;
      }
      else if ((dP_dt > 0.05) && (dP_dt < 0.25))
      {
        forecast = SUNNY;
      }
      else if ((dP_dt > (-0.05)) && (dP_dt < 0.05))
      {
        forecast = STABLE;
      }
      else
      {
        forecast = UNKNOWN;
      }
    
      // uncomment when debugging
      //Serial.print(F("Forecast at minute "));
      //Serial.print(minuteCount);
      //Serial.print(F(" dP/dt = "));
      //Serial.print(dP_dt);
      //Serial.print(F("kPa/h --> "));
      //Serial.println(weather[forecast]);
    
      return forecast;
    }
    
    /********************************************
    
       Sends battery information (battery percentage)
    
       Parameters
       - force : Forces transmission of a value
    
     *******************************************/
    void sendBattLevel(bool force)
    {
      if (force) lastBattery = -1;
      long vcc = readVcc();
      if (vcc != lastBattery) {
        lastBattery = vcc;
    
    #ifdef BATT_SENSOR
        float siVolt = vcc / 1000.0;
        send(msgBatt.set(siVolt, 2));
    #endif
    
        // Calculate percentage
    
        vcc = vcc - 1800; // subtract 1.9V from vcc, as this is the lowest voltage we will operate at
    
        long percent = vcc / 14.0;
        sendBatteryLevel(percent);
      }
    }
    
    /*******************************************
    
       Internal battery ADC measuring
    
     *******************************************/
    long readVcc() {
      // Read 1.1V reference against AVcc
      // set the reference to Vcc and the measurement to the internal 1.1V reference
    #if defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
      ADMUX = _BV(REFS0) | _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
    #elif defined (__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__)
      ADMUX = _BV(MUX5) | _BV(MUX0);
    #elif defined (__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__)
      ADcdMUX = _BV(MUX3) | _BV(MUX2);
    #else
      ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
    #endif
    
      delay(2); // Wait for Vref to settle
      ADCSRA |= _BV(ADSC); // Start conversion
      while (bit_is_set(ADCSRA, ADSC)); // measuring
    
      uint8_t low  = ADCL; // must read ADCL first - it then locks ADCH
      uint8_t high = ADCH; // unlocks both
    
      long result = (high << 8) | low;
    
      result = 1125300L / result; // Calculate Vcc (in mV); 1125300 = 1.1*1023*1000
      return result; // Vcc in millivolts
    }
    

  • Mod

    Minor tweaks can be done, but nothing that should make a big difference. Cramming software signing and debug and externals libraries is probably just too much for your mcu. Maybe look at using STM32 or an ESP? Or disable debugging, but I guess you need that for something?



  • @mfalkvidd Disabling debugging does not help - I still get Low memory available, stability problems may occur.


  • Mod

    @alexsh1 strange. It works for me.
    Before disabling debug:

    Sketch uses 34,854 bytes (113%) of program storage space. Maximum is 30,720 bytes.
    Global variables use 1,671 bytes (81%) of dynamic memory, leaving 377 bytes for local variables. Maximum is 2,048 bytes.
    

    After disabling debug:

    Sketch uses 25,638 bytes (83%) of program storage space. Maximum is 30,720 bytes.
    Global variables use 1,298 bytes (63%) of dynamic memory, leaving 750 bytes for local variables. Maximum is 2,048 bytes
    

    I am using these versions of the libraries:

    Using library SPI at version 1.0
    Using library MySensors at version 2.1.1
    Using library Wire at version 1.0
    Using library Adafruit_BME280_Library at version 1.0.5
    Using library Adafruit_Unified_Sensor at version 1.0.2
    


  • @mfalkvidd Thanks. I have the same libraries. I understand what the problem is now.
    #define MY_DEBUG
    and
    #define MY_DEBUG_VERBOSE_SIGNING

    are enabled in MyConfig.h
    I disabled MY_DEBUG_VERBOSE_SIGNING in MyConfig.h and
    put an extra line #define MY_DISABLED_SERIAL in the sketch - this undefines MY_DEBUG in MyConfig.h though this is not required as MY_DEBUG_VERBOSE_SIGNING would do it.

    Sketch uses 29708 bytes (92%) of program storage space. Maximum is 32256 bytes.
    Global variables use 1360 bytes (66%) of dynamic memory, leaving 688 bytes for local variables. Maximum is 2048 bytes.```

  • Mod

    A friend of mine told me that atmel studio is more efficient at compiling than the arduino ide, I haven't tested it yet but maybe somebody could confirm.



  • @gohan Well could be, but it is too late to teach an old dog a new trick in my case. I am comfortable with Arduino IDE - I can flash atmega, ESP, SAMD etc.


  • Mod

    If that was the case, you could use the atmel studio just for sw upload. Myself I'm using visual studio preferably, it makes it faster to write code but for quick modification I still use the arduino ide


  • Hardware Contributor

    Another possible optimization, is to dig into the sensor libs you're using, and disable/remove/comment etc.. things you don't need. i'm pretty sure in BME280 there are stuff you don't need in some functions (just quick looked).
    When using multiple libs and sensors, you can save a lot like this. Libs are there for providing us lot of features, but not necessarily needed in your final fw.


  • Mod

    Shouldn't the compiler exclude all functions that aren't used in the code?



  • @gohan no, the compiler compiles whatever you ask to compile. It's your duty to "improve" your code by excluding stuff you are not using


  • Mod

    Back in the days when I was studying, compilers used to have code optimizers built in... Oh well... Never mind 😅



  • @scalz I'm with you, but just like you I just do not have time for it. There is Teensy 3.2 with excellent footprint and 10 times speed and memory. So I just feel like if I'm going to spend time, it has to be not yesterday's technology.


  • Mod

    Just for info, that's what I found on arduino forum

    Eighteen Hints to Reduce Code Size

    1. Compile with full size optimization.
    2. Use local variables whenever possible.
    3. Use the smallest applicable data type. Use unsigned if applicable.
    4. If a non-local variable is only referenced within one function, it should be declared static.
    5. Collect non-local data in structures whenever natural. This increases the possibility of indirect addressing without pointer reload.
    6. Use pointers with offset or declare structures to access memory mapped I/O.
    7. Use for(;;) { } for eternal loops.
    8. Use do { } while(expression) if applicable.
    9. Use descending loop counters and pre-decrement if applicable.
    10. Access I/O memory directly (i.e., do not use pointers).
    11. Declare main as C_task if not called from anywhere in the program.
    12. Use macros instead of functions for tasks that generates less than 2-3 lines assembly code.
    13. Reduce the size of the Interrupt Vector segment (INTVEC) to what is actually needed by the application. Alternatively, concatenate all the CODE segments into one declaration and it will be done automatically.
    14. Code reuse is intra-modular. Collect several functions in one module (i.e., in one file) to increase code reuse factor.
    15. In some cases, full speed optimization results in lower code size than full size optimization. Compile on a module by module basis to investigate what gives the best result.
    16. Optimize C_startup to not initialize unused segments (i.e., IDATA0 or IDATA1 if all variables are tiny or small).
    17. If possible, avoid calling functions from inside the interrupt routine.
    18. Use the smallest possible memory model.


  • A lot of debug code in this sketch has not been enclosed between #ifdef MYDEBUG/#endif blocks. Even if you disable MYDEBUG, most of the Serial.print() lines remain 'active'.

    Try to comment most of the Serial.print() code or add the #ifdef statements to have the compiler ignore these Serial.print() lines.
    Most memory in this sketch is eaten up by the strings ("xxx").



  • @ftw64 thanks - very helpful


  • Mod

    @ftw64 since the strings are wrapped in F() they actually do not use (global) ram. When using F(), the strings are only stored in flash.

    So while adding #ifdef around the print statements will save flash, it will not save ram.



  • @mfalkvidd Oh, cool. I missed that (and I didn't know that, and I learned something today :-)). Yep, in that case it wouldn't help much.


Log in to reply
 

Suggested Topics

17
Online

11.4k
Users

11.1k
Topics

112.7k
Posts