small function() eats up RAM

I wrote a small function that seems simple. It simply adds an offset value each time a button is pressed. I have 2 buttons, one for increment and 1 for decrement. When the function is called, it displays the current offset (default 0) and then either increments or decrements the global variable offset by 1. this global variable is then simply added to my sensor output reading. When a third button is pressed, it returns to the loop which is constantly printing the sensor value.

Problem is the sketch hangs after executing this function a few times. I have optimised the memory usage by using the Flash library Flash | Arduiniana for most strings which is why the print statements may look strange but it works really well. I used a memory check utility » ATmega memory use » JeeLabs
This utility confirms, when my sketch is running, it reports free ram is about 1K (Atmega 328p). Each time the function is run, the memory drops by 200 - 300 bytes, so after 4 or 5 times the sketch hangs.
The answer is probably staring me in the face but the function seems so simple.

void fCalibrate()
{

  lcd.clear ();
  lcd.setCursor(0, 0);
  adjOffset_msg.print(lcd);   
  lcd.setCursor(1, 0); 
    adjOffset_msg_inst.print(lcd);

  do{
 
    if (digitalRead (6) == HIGH)
    {
      offSet = offSet -1;
      lcd.clear ();
      lcd.setCursor(0, 0);
      offsetCurrent_msg.print(lcd);
      lcd.print(offSet);
      lcd.setCursor(1, 0); 
       offsetAdj_msg_func.print(lcd);

      delay (500);
    }
    if (digitalRead (7) == HIGH)
    {
      offSet = offSet +1;
      lcd.clear ();
      lcd.setCursor(0, 0);
      offsetCurrent_msg.print(lcd);
      lcd.print(offSet);
      lcd.setCursor(1, 0); 
      offsetAdj_msg_func.print(lcd); 
      delay (500);
    }

  } 
  while (digitalRead (5) == LOW);
  lcd.clear ();
  lcd.setCursor(0, 0);
  starting_msg.print(lcd); 
  lcd.setCursor(1, 0); 
  measurement_msg.print(lcd);


  delay (1000);
  return;
}

Are you using String anywhere?

Please note that in versions of the IDE up to and including 1.0.3, the String library has bugs as discussed here and here.

In particular, the dynamic memory allocation used by the String class may fail and cause random crashes.

I recommend reworking your code to manage without String. Use C-style strings instead (strcpy, strcat, strcmp, etc.), as described here for example.

Alternatively, install the fix described here: Fixing String Crashes

Preferably upgrade your IDE to version 1.0.4 or above at: http://arduino.cc/en/Main/Software

I have optimised the memory usage by using the Flash library Flash | Arduiniana for most strings which is why the print statements may look strange but it works really well.

except that it might be causing your problem.

Please post your whole code

Does the function consume memory each time it is used if you use conventional methods of printing messages to the lcd ? Are the messages defined as Strings by any chance ?

The function uses delay in several places which cause the code to block.
In the middle of the function there is a do {...} while loop which can be blocked by one of those delays.

So if the digitalRead() that ends the loop misses the button press the next delay can blocks again....

I would propose to rewrite the code without delay.
what might help is to put separate parts in separate functions.

void fCalibrate()
{
  firstPart();
  if (digitalRead (6) == HIGH && digitalRead (7) == LOW && digitalRead (5) == LOW)
  {
    decrOffset();
  }
  if (digitalRead (6) == LOW && digitalRead (7) == HIGH && digitalRead (5) == LOW)
  {
    incrOffset();
  }
  lastPart();
}

and call this function every 500 millis() from your main loop()...

get the idea?

Nick, Bob and Rob
I continue to be humbled by your willingness to assist.
Nick, No I am not using String anywhere.
Bob, No it was doing the same thing but worse when I was using the conventional RAm for strings. The FLASH allowed me to store all strings in Flash.
Rob, I tried to fragment into smaller functions and it had the same result, although I admit I am not sure what you meant by calling the function every 500mS.
The way it works is that the sensor reading is continuously printing. When D5 button is pressed it goes to a Menu function. D6 will take you to fCalibrate. In that function D6 and D7 will decrement and increment offset variable. D5 will return.
It wouldnt allow me to post the entire code so I will post the code following this in 2 posts.

here is the first bit.
[code#include <Wire.h>
#include <Adafruit_ADS1015.h>
#include "ST7036.h"
#include "LCD_C0220BiZ.h"
#include <Flash.h>

FLASH_STRING(start_msg, "  Hello  \n");
FLASH_STRING(function_msg, " Press FUNCTION \n");
FLASH_STRING(adjOffset_msg, " Adjust  Offset \n");
FLASH_STRING(adjOffset_msg_inst, "<2 Dec--Inc 4>");
FLASH_STRING(offsetCurrent_msg, "Offset = ");
FLASH_STRING(offsetAdj_msg_func, "< 2--4 > or FUNC");
FLASH_STRING(starting_msg, "   Starting   \n");
FLASH_STRING(measurement_msg, "  Measurement  \n");
FLASH_STRING(attach_msg, "Attach  Sensor\n");
FLASH_STRING(remove_msg, "Remove  Sensor\n");
FLASH_STRING(wait_msg, "  Please  Wait  \n");
FLASH_STRING(calibration_msg, "  Calibration   \n");
FLASH_STRING(succeed_msg, "   Succeeded    \n");
FLASH_STRING(replaceBall_msg, "Replace Sensor\n");
FLASH_STRING(range_msg, " Out of Range ! \n");
FLASH_STRING(range_msg_serial, "$ Out of Range ! \n");
FLASH_STRING(range_asterisk_msg, "****************\n");
FLASH_STRING(failed_msg, "    Failed      \n");
FLASH_STRING(press2_msg, "Press < 2 Offset\n");
FLASH_STRING(press4_msg, "> 4 Re-Calibrate\n");
FLASH_STRING(smallBall_msg, " Small Sense \n");
FLASH_STRING(bigBall_msg, "  Big Sense  \n");
FLASH_STRING(detected_msg, "    Detected    \n");

#define CHAR_WIDTH  5
ST7036 lcd = ST7036 ( 2, 16, 0x7C );

Adafruit_ADS1115 ads1115(0x48); // construct an ads1115 at address 0x49
int16_t adc0;  

unsigned int adjmilsOutput;

static int sampleLow = 31000;
static int sampleHigh = 0;

int milsOutput;
unsigned int holdVal;
unsigned int swVal;
float metricOutput;

const int numReadings = 50;
int readings[numReadings];      // the readings from the analog input
int index = 0;                  // the index of the current reading
unsigned long total = 0;                  // the running total

int offSet = 0;
int  sensorReading;

//=============================================================
void fMenu()
{
  lcd.clear ();
  lcd.setCursor(0, 0);
  press2_msg.print(lcd); 
  lcd.setCursor(1, 0); 
  press4_msg.print(lcd);

  delay (200); 

  while (digitalRead (6)== LOW && digitalRead (7) == LOW);
  if (digitalRead (6) == HIGH)
  {
    fCalibrate();
  }
  if (digitalRead (7) == HIGH)
  {
    fFieldChoice();
  }
  delay (1000);
  loop();

}
//===========================================================================
void setup() {
  // setup serial - diagnostics - port
  Serial.begin(9600);
  ads1115.begin();



  lcd.init ();
  delay(250);
  lcd.setContrast(1);

  analogReference(INTERNAL);
  pinMode(5, INPUT); // Function Button
  pinMode (6, INPUT); // Left button
  pinMode (7, INPUT); // Right Button
  digitalWrite(A1, HIGH);  // set pullup on analog pin 1 
  digitalWrite(A2, HIGH);  // set pullup on analog pin 2
  digitalWrite(A3, HIGH);  // set pullup on analog pin 3
  digitalWrite(A4, HIGH);  // set pullup on analog pin 4
  digitalWrite(A5, HIGH);  // set pullup on analog pin 5

  //welcome message
  start_msg.print(Serial); 

  lcd.setCursor(0, 0);
  start_msg.print(lcd); 
  lcd.setCursor(1, 0); 
  function_msg.print(lcd);

  delay (200);

  while (digitalRead (5)== LOW);
  delay (1000);
  fFieldChoice();
}

//===============================================================

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

2nd part

void loop() 
{ 
  while (sampleLow < 8000){
    while (digitalRead (5)== LOW){

      Serial.println("\n[memCheck]");
      Serial.println(freeRam());

#define arraySize 23
#define limitLow 3700
#define limitHigh 29252
#define limitHold 25615
      //read sensor
      static int holdVal = 31000;
      adc0 = ads1115.readADC_SingleEnded(0);
      sensorReading = adc0;

      int adjmilsOutput = map (sensorReading, sampleLow, sampleHigh, limitLow , limitHigh); 

      int mils[] = {
        0,     12,    31,   61,   92,     120,    150,    180,   211,   241,   270,   300,   330,  360,    390,   420,    450,   480,   510,   539,   570,   600,  2850                              }; //output required
      int anlogin[] = {   
        3700,  4330,  6400, 9400, 11440,  13375,  15300,  16620, 17940, 19185, 20000, 20935, 21725, 22375, 23000, 23500,  24000, 24427, 24833, 25205, 25515, 25615, 29252                              }; //sampled anlogread


      int milsOutputA = multiMap(adjmilsOutput, anlogin, mils, arraySize); 
      // subtract the last reading:
      total= total - readings[index];         
      // read from the sensor:  
      readings[index] = milsOutputA; 
      // add the reading to the total:
      total= total + readings[index];       
      // advance to the next position in the array:  
      index = index + 1;   
      //     Serial.println("total=");
      // if we're at the end of the array...
      if (index >= numReadings)              
        // ...wrap around to the beginning: 
        index = 0;                            

      // calculate the average:
      milsOutput = (total / numReadings)+offSet;         

      int swVal = analogRead(A1);  // hold switch on A1

        if(swVal <=250) {       // hold position

        holdVal = min(holdVal,milsOutput);   // the smallest mil value is locked in
      }

      if (swVal >=250) {      // toggled to unhold position
        holdVal = 31000;
      }
      if (holdVal <= milsOutput) {
        milsOutput = holdVal;
      }

      metricOutput = (milsOutput*25.4)/1000;  // convert to milsOutput

      if (adjmilsOutput  > limitHold   )  { 
        lcd.clear();
        lcd.setCursor(0, 0);
        range_msg.print(lcd);
        lcd.setCursor(1, 0); 
        range_asterisk_msg.print(lcd);
        range_msg_serial.print(Serial);
        delay (200);
      }
      else {
        lcd.setCursor(0, 0);
        if (swVal <=250) {
          lcd.clear();
          delay(10);
          lcd.print("H");
          lcd.print(milsOutput);
          lcd.print(" mil  ");  
          lcd.setCursor(1, 0);
          lcd.print(" ");
          lcd.print(metricOutput);
          lcd.print(" mm ");

          Serial.print("H");
          Serial.print("M");
          Serial.print(metricOutput);
          Serial.print("|");
          Serial.print("I");
          Serial.println(milsOutput);
          //delay(150);

        }

        else    {
          lcd.clear();
          delay(10);
          lcd.setCursor(0, 0);
          lcd.print(" ");
          lcd.print(milsOutput);
          lcd.print(" mil   ");
          lcd.setCursor(1, 0);

          lcd.print(" ");
          lcd.print(metricOutput);
          lcd.print(" mm ");
          //delay(150); 
          Serial.print("M");
          Serial.print(metricOutput);
          Serial.print("|");
          Serial.print("I");
          Serial.println(milsOutput);
          // delay(50);
        }

      } 
    }
    fMenu();
  }

  while (sampleLow > 8000  ){

    while (digitalRead (5)== LOW){
#define arraySize2 17
#define limitLow2 12800
#define limitHigh2 29252
#define limitHold2 25935

      //Read Sensor
      static int holdVal = 31000;
      adc0 = ads1115.readADC_SingleEnded(0);
      sensorReading = adc0;

      int adjmilsOutput = map (sensorReading, sampleLow, sampleHigh, limitLow2 , limitHigh2); 

      int mils[] = { 
        0,    12,        31,    61,     92,    120,    150,    180,   211,   241,   270,   300,   330,   360,   390,   420,    2300                               }; //output required
      int anlogin[] = { 
        12800,  13740,  15690, 17350, 19028,  20175,  21175,  22083, 22810, 23540, 24000, 24540, 24950, 25320, 25650, 25935,  29252                              }; //sampled anlogread

      int milsOutputA = multiMap(adjmilsOutput, anlogin, mils, arraySize2); 

      // subtract the last reading:
      total= total - readings[index];         
      // read from the sensor:  
      readings[index] = milsOutputA; 
      // add the reading to the total:
      total= total + readings[index];       
      // advance to the next position in the array:  
      index = index + 1;   
      // if we're at the end of the array...
      if (index >= numReadings)              
        // ...wrap around to the beginning: 
        index = 0;                            

      // calculate the average:
      milsOutput = (total / numReadings)+offSet;         

      int swVal = analogRead(A1);  // hold switch on digital 7

      if(swVal <=250) {       // hold position

        holdVal = min(holdVal,milsOutput);   // the smallest mil value is locked in
      }

      if (swVal >=250) {      // toggled to unhold position
        holdVal = 31000;
      }
      if (holdVal <= milsOutput) {
        milsOutput = holdVal;
      }

      metricOutput = (milsOutput*25.4)/1000;  // convert to milsOutput

      if (adjmilsOutput  > limitHold2   )  { 
        lcd.clear();
        delay(10);
        lcd.setCursor(0, 0);
        range_msg.print(lcd); 
        lcd.setCursor(1, 0); 
        range_asterisk_msg.print(lcd);
        range_msg_serial.print(Serial);
        delay (200);
      }
      else {
        lcd.clear();
        delay(10);
        lcd.setCursor(0, 0);
        if (swVal <=250) {
          lcd.print("H");
          lcd.print(milsOutput);
          lcd.print(" mil  ");  
          lcd.setCursor(1, 0);
          lcd.print(" ");
          lcd.print(metricOutput);
          lcd.print(" mm ");

          Serial.print("H");
          Serial.print("M");
          Serial.print(metricOutput);
          Serial.print("|");
          Serial.print("I");
          Serial.println(milsOutput);
          //delay(150);
        }

        else    {
          lcd.clear();
          delay(10);
          lcd.setCursor(0, 0);
          lcd.print(" ");
          lcd.print(milsOutput);
          lcd.print(" mil   ");
          lcd.setCursor(1, 0);
          lcd.print(" ");
          lcd.print(metricOutput);
          lcd.print(" mm ");
          //delay(150); 
          Serial.print("M");
          Serial.print(metricOutput);
          Serial.print("|");
          Serial.print("I");
          Serial.println(milsOutput);
          // delay(50);
        }
      } 
    }
    fMenu();
  } 
}
//

last part

void fCalibrate()
{
  delay (400);
  lcd.clear ();
  lcd.setCursor(0, 0);
  adjOffset_msg.print(lcd);   
  lcd.setCursor(1, 0); 
  adjOffset_msg_inst.print(lcd);

  do{
    if (digitalRead (6) == HIGH)
    {
      offSet = offSet -1;
      lcd.clear ();
      lcd.setCursor(0, 0);
      offsetCurrent_msg.print(lcd);
      lcd.print(offSet);
      lcd.setCursor(1, 0); 
      offsetAdj_msg_func.print(lcd);

      delay (500);
    }
    if (digitalRead (7) == HIGH)
    {
      offSet = offSet +1;
      lcd.clear ();
      lcd.setCursor(0, 0);
      offsetCurrent_msg.print(lcd);
      lcd.print(offSet);
      lcd.setCursor(1, 0); 
      offsetAdj_msg_func.print(lcd); 
      delay (500);
    }

  } 
  while (digitalRead (5) == LOW);
  lcd.clear ();
  lcd.setCursor(0, 0);
  starting_msg.print(lcd); 
  lcd.setCursor(1, 0); 
  measurement_msg.print(lcd);

  delay (1000);
  return;
}
//=================================================================================
int multiMap(int adjmilsOutput, int* _anlogin, int* _mils, uint8_t size)
{

  // take care the value is within range
  adjmilsOutput = constrain(adjmilsOutput, _anlogin[0], _anlogin[size-1]);

  if (adjmilsOutput <= _anlogin[0]) return _mils[0];

  if (adjmilsOutput >= _anlogin[size-1]) return _mils[size-1];

  // search right interval
  uint8_t pos = 1;  // _in[0] allready tested

  while(adjmilsOutput > _anlogin[pos]) pos++;

  // this will handle all exact "points" in the _in array
  if (adjmilsOutput == _anlogin[pos]) return _mils[pos];

  // interpolate in the right segment for the rest
  return map(adjmilsOutput, _anlogin[pos-1], _anlogin[pos], _mils[pos-1], _mils[pos]);

}
//=================================================================================

//=================================================================================
void fFieldChoice()
{
  sampleLow = 31000;
  sampleHigh = 0;

  lcd.setCursor(0, 0);
  attach_msg.print(lcd); 
  lcd.setCursor(1, 0); 
  function_msg.print(lcd);

  while (digitalRead (5) == LOW);

  // measure sensor
  for (int i=0; i<100; i++) {
    adc0 = ads1115.readADC_SingleEnded(0);
    sensorReading = adc0; 
    sampleLow = min(sampleLow, sensorReading);
  }

  lcd.setCursor(0, 0);
  remove_msg.print(lcd);

  while (digitalRead (5) == LOW);

  // measure sensor
  for (int i=0; i<100; i++) {
    adc0 = ads1115.readADC_SingleEnded(0);
    sensorReading = adc0; 
    sampleHigh = max(sampleHigh, sensorReading);
  }

  if ((sampleHigh - sampleLow) <= 3000)
  {
    lcd.setCursor(1, 0); 
    lcd.clear ();
    lcd.setCursor(0, 0);
    calibration_msg.print(lcd); 
    lcd.setCursor(1, 0); 
    failed_msg.print(lcd);
    delay (2000);
    fFieldChoice();  
    loop();
  }
  else{

    lcd.clear ();
    lcd.setCursor(0, 0);
    calibration_msg.print(lcd); 
    lcd.setCursor(1, 0); 
    succeed_msg.print(lcd);

    delay (2000);

    if (sampleLow <= 6000)
    {  
      bigBall_msg.print(Serial);
      detected_msg.print(Serial);

      lcd.clear ();
      lcd.setCursor(0, 0);
      bigBall_msg.print(lcd); 
      lcd.setCursor(1, 0); 
      detected_msg.print(lcd);
      delay (2000);
    }  
    else{
      smallBall_msg.print(Serial);
      detected_msg.print(Serial);

      lcd.clear ();
      lcd.setCursor(0, 0);
      smallBall_msg.print(lcd); 
      lcd.setCursor(1, 0); 
      detected_msg.print(lcd);
      delay (2000);
    }

    lcd.clear ();
    lcd.setCursor(0, 0);
    replaceBall_msg.print(lcd); 
    lcd.setCursor(1, 0); 
    function_msg.print(lcd);
  }    

  while (digitalRead (5) == LOW);  
  lcd.clear ();
  lcd.setCursor(0, 0);
  starting_msg.print(lcd);
  lcd.setCursor(1, 0); 
  measurement_msg.print(lcd);
  delay (1000);

}

Presto!
I solved it. It seems using the global variable in the fCalibrate function was the issue. Making the function pass the value of a local variable adjusted by the buttons back to the calling function has solved the memory drain.
The explanation why? is up to you pro's. :slight_smile:

Does 1.0.4 fix the buggy string library?

Does 1.0.4 fix the buggy string library?

There was nothing wrong with the string library.

The String class, on the other hand, relied on the free() function to release memory, and that function had a bug that is fixed in 1.0.4.