5

I've written a Arduino sketch that converts an analogue voltage to a temperature using a lookup table - code below. I've recently changed my LUT to include more values but ever since that my Serial monitor won't print out my debugging statements etc. So I limited the number of elements in the array and it was back to normal. But when I enter more debugging statements using the serial monitor, there is garbage before my statements (still readable though when I parse through it myself) or at the start nothing is printed. I've checked that the two baud rates are the same but I just find it peculiar that if I take some of my println() statements out it runs okay. Tested everything else with a "Hello World" example but in the simple exaples the monitor works fine.

    /*
    #####PROJECT DESCRIPTION##### 
    Measures temperature with an Arduino and NTC Thermistor (10Kohms @ 25^C)                                 in a voltage divider circuit
    Since the response is non-linear, a lookup table is used since there is a non linear response 
    The values in the LUT are the predicted values of the ADC at  temperature between 6^C and 40^C
    LUT has been derived from the calibration tests
    */
    //#####CONSTANTS#####
    const int constDelay = 3000; //constant delay for program 
    const int constNoOfAnaloguePins = 2; //number of analogue pins that are to be read
    const int errReturn = 998; //error value that is returned ***CHECK FOR CONSITENT VALUE ACROSS FUNCTIONS***
    //#####VARIABLE DECLARATIONS#####
    float avgTempC; //float var to store average of multiple temp readings

    //#####SETUP PROCEDURE#####
    void setup()
    {
      //this is the Setup part of your project - this is for code that is run only ONCE at startup
      for (int analoguePinCounter = 0; analoguePinCounter < constNoOfAnaloguePins; analoguePinCounter++) //for loop to set up numerous analogue pins
      {
        pinMode(analoguePinCounter, INPUT);  //set pin mode of the analogue thermistor input
      }//end for
      analogReference(INTERNAL); //use the interval votlage of 1.1V for the ADC resolution
      Serial.begin(9600); //serial monitor baud rate
    }//end Setup function

    //#####LOOP PROCEDURE#####
    void loop()
    {
      //this is the main part of your project - put all continuously running code here
      for (int analoguePinCounter = 0; analoguePinCounter < constNoOfAnaloguePins; analoguePinCounter++) //for loop to take temp readings from each analogue pin
      {
        //get LPF average reading over 25 samples
        avgTempC = LPF(analoguePinCounter, 25);
        //print out the readings
        if (avgTempC < float(errReturn)) //there hasn't been any temps outside the range ***CHECK THE VALUE RETURNED FROM LPF IF ERROR***
        {
          Serial.print(analoguePinCounter);
          Serial.print(" | Average Temperature Readings = ");
          Serial.print(avgTempC, 2);
          Serial.println(" degC.");
          Serial.println("-------------------------");
        }
        else //avgTempC has returned X indicating an error
        {
          Serial.print(analoguePinCounter);
          Serial.println("**ERROR IN READING AVERAGE TEMP**");
          Serial.println("-------------------------");
          delay(10);
        }//end if
        delay(constDelay/2); //wait X/2 seconds until moving onto next sensor
      }//end for
      delay(constDelay); //wait X seconds until next loop
    }//end Loop function

    /*
    #####LOW PASS FILTER DESCRIPTION#####
    This function is a LPF for smoothing out signals or averaging
    It uses a static buffer to store the last "n" readings, and each time it is called,
    the oldest reading is discarded, the new reading is added, and the stack average
    value is returned.
    The buffer size is user-adjustable, but is constrained between 2 and 50
    An initialization function fills the entire buffer with the input value. 
    This is useful when the LPF function is called for the first time.
    Note: To meet the definition of a true LPF, this function must be called         at regular intervals.
    */
    //#####LOW PASS FILTER FUNCTION#####
    float LPF(int pinAnalogue, int bufferSize)
    {
      //##PRE PROCESSOR##
      #define bufferCap 75 //maximum buffer capacity
      //##CONSTANTS##
      const int constMAXERRORS = 5; //max number of error that can occur
      const int errReturn = 998; //value return if there is an error
      //##VARIABLES##
      float tempC; //var to store instantaneous temp
      static float buffer[bufferCap]; //array to act like a 'stack' of temp values ***DOES IT NEED TO BE STATIC NOW?***
      float tempSum; //to store the sum of the temperatures
      float output; //return value of LPF which is the average of the temp readings
      int errCounter = 0; //initialise error counter to 0

      //make sure buffer size is between the range
      bufferSize = constrain(bufferSize, 2, bufferCap);

      for (int i=0; i<bufferSize; i++) //for loop to store temps in buffer array 
      {
        tempC = getTempFloat(pinAnalogue); //call function to get temperature from pin
        if ((tempC > -errReturn) && (tempC < errReturn)) //no error 
        {
          buffer[i] = tempC; //store temp value into array
        }
        else //error
        {
          errCounter++; //increment counter for number of erroneous readings
          i--; //decrement counter as to not skip a sample index
          if (errCounter > constMAXERRORS)
          {
            return(errReturn+1.0); //return the error value + 1
          }//end if
        }//end if
        delay(25); //allow the ADC to settle
      }//end for

      //calculate current stack average
      tempSum = 0; //initialise sum total
      for (int i=0; i<bufferSize; i++) //go through stack array
      {
        tempSum = tempSum + buffer[i]; //total up readings
      }//end for

      //average sum
      output = tempSum/bufferSize;

      return(output);
    }//end LPF function

    /*
    #####GET TEMP FLOAT DESCRIPTION#####  
    This function converts a thermistor reading into a corresponding temp in ^C
    The thermistor is incorporated into a Voltage Divider Circuit:

       +Vref---[Thermistor]---+--[1.8K]---GND
                              |
                             ADC @ thermPin

    ADC Values were externally calculated from the calibration table using:           ADC = 1023*10000/(Rtherm+10000)
    The LUT is an array of integer constants containing the predicted ADC         values for all temperatures between +6^C to +40.5^C.
    The array index starts at zero, which corresponds to a temperature of         +6^C.
    A linear interpolation between the two closest entries is performed to         give a finer output resolution.
    */

    float getTempFloat (int thermPin)
    {
      //##CONSTANTS##
      /*1K8*/
      const int constLUTArraySize = 431;
      const int LUT_Therm[constLUTArraySize] = //LUT containing ADC values
      {
        223, 224, 225, 226, 227, 228, 229, 230, 231, 232,         //7^C to 7.9^C
        233, 234, 235, 236, 237, 238, 239, 240, 241, 242,
        244, 245, 246, 247, 248, 249, 250, 251, 252, 253,
        255, 256, 257, 258, 259, 260, 261, 263, 264, 265,
        266, 267, 268, 269, 271, 272, 273, 274, 275, 277,
        278, 279, 280, 281, 283, 284, 285, 286, 287, 289,
        290, 291, 292, 294, 295, 296, 297, 299, 300, 301,           //13^C to 13.9^C
        302, 304, 305, 306, 308, 309, 310, 312, 313, 314,
        315, 317, 318, 319, 321, 322, 323, 325, 326, 327,
        329, 330, 332, 333, 334, 336, 337, 338, 340, 341,
        343, 344, 345, 347, 348, 350, 351, 353, 354, 355,
        357, 358, 360, 361, 363, 364, 366, 367, 369, 370,
        371, 373, 374, 376, 377, 379, 380, 382, 383, 385,
        387, 388, 390, 391, 393, 394, 396, 397, 399, 400,
        402, 404, 405, 407, 408, 410, 411, 413, 415, 416,
        418, 419, 421, 423, 424, 426, 428, 429, 431, 433,
        434, 436, 438, 439, 441, 443, 444, 446, 448, 449,
        451, 453, 454, 456, 458, 460, 461, 463, 465, 466,
        468, 470, 472, 473, 475, 477, 479, 480, 482, 484,
        486, 488, 489, 491, 493, 495, 497, 498, 500, 502,
        504, 506, 507, 509, 511, 513, 515, 517, 519, 520,
        522, 524, 526, 528, 530, 532, 534, 535, 537, 539,
        541, 543, 545, 547, 549, 551, 553, 555, 557, 559,
        560, 562, 564, 566, 568, 570, 572, 574, 576, 578, 
        580, 582, 584, 586, 588, 590, 592, 594, 596, 598,
        600, 602, 604, 606, 608, 611, 613, 615, 617, 619,
        621, 623, 625, 627, 629, 631, 633, 635, 638, 640,
        642, 644, 646, 648, 650, 652, 655, 657, 659, 661,
        663, 665, 667, 670, 672, 674, 676, 678, 680, 683,
        685, 687, 689, 691, 694, 696, 698, 700, 702, 705,
        707, 709, 711, 714, 716, 718, 720, 723, 725, 727,
        729, 732, 734, 736, 738, 741, 743, 745, 747, 750, 
        752, 754, 757, 759, 761, 764, 766, 768, 771, 773,
        775, 778, 780, 782, 785, 787, 789, 792, 794, 796,
        799, 801, 803, 806, 808, 811, 813, 815, 818, 820,
        822, 825, 827, 830, 832, 834, 837, 839, 842, 844,
        847, 849, 851, 854, 856, 859, 861, 864, 866, 868,
        871, 873, 876, 878, 881, 883, 886, 888, 891, 893,
        896, 898, 901, 903, 906, 908, 911, 913, 916, 918, 
        921, 923, 926, 928, 931, 933, 936, 938, 941, 943,
        946, 948, 951, 953, 956, 958, 961, 963, 966, 969,
        971, 974, 976, 979, 981, 984, 987, 989, 992, 994,
        997, 999, 1002, 1005, 1007, 1010, 1012, 1015, 1017, 1020, //40^C to 49.9^C
        1023                                                     //50^C
      };
      const int errReturn = 998; //error value that is returned
      //##VARIABLES##
      float _tempC; //intermediate results + final temperature return value
      int ADC_Lo; //the lower ADC matching value
      int ADC_Hi; //the higher ADC matching value
      float Temp_Lo; //the lower number matching temperature
      int mapTemp_Lo; //the lower number that will be entered into the map function
      float Temp_Hi; //the higher  number matching temperature
      int mapTemp_Hi; //the higher number that will be entered into the map function

      //prep ADC onto that particular analogue pin to sort out multiplexing etc
      analogRead(thermPin); 
      delay(10);
      //get raw ADC value from VDR
      int thermValue = analogRead(thermPin); 
      //Serial.println(thermValue);
      /*Serial.print("PinNo : ");
      Serial.println(thermPin);*/
      Serial.print("ADC : ");
      Serial.println(thermValue);
      //return dummy value if the sensor reading falls outside of the LUT
      if (thermValue < LUT_Therm[0]) //less than the least ADC value
        _tempC = -errReturn-1; //under range dummy value
      else if (thermValue > LUT_Therm[constLUTArraySize-1]) //more than the greatest ADC value
        _tempC = errReturn+1; //over range dummy value
      else //value falls in LUT range
      {
        for (int i=0; i<constLUTArraySize; i++) //step through LUT and look for a match
        {    
          if (LUT_Therm[i] > thermValue) //LUT value is greater than the reading
          {  
            //find the closest higher ADC value
            ADC_Hi = LUT_Therm[i];
            //record the closest higher temperature        
            Temp_Hi = float(i/10) + 13.0; //convert to temp
            //get the closest lower temperature - taking the lower table boundary into account
            if (i != 0) //general case -> as long as it is not the first entry
            {
              ADC_Lo = LUT_Therm[i-1]; //store the previous array element as the low
              Temp_Lo = float(i/10) + 13.0 - 0.1; //convert to temp
            }
            else //special case -> counter = 0 ie first array entry
            {
              ADC_Lo = LUT_Therm[i]; //store the first array element
              Temp_Lo = i - float(i/10) + 13.0; //convert to temp
            }//end if
            //interpolate the temperature value for greater precision
            //NB the map function does not use floating-point math, so the int values of temp are multiplied by 100 and then result is subsequently divided by 100
            mapTemp_Lo = Temp_Lo*100;
            mapTemp_Hi = Temp_Hi*100;
            _tempC = float(map(thermValue,ADC_Lo,ADC_Hi,mapTemp_Lo,mapTemp_Hi))/100;
            break;  //exit for loop after the match is detected
          }//end if
        }//end for
      }//end if
      return(_tempC);
    }//end getTempFloat function

In this particular sketch no output is seen on the serial monitor. Has anyone come across this before or have any ideas how I could fix this?

  • 2
    Try putting the data for LUT_Therm in progmem or compressing it somehow – BrettAM Mar 23 '16 at 16:57
  • ...because it sounds like you have run out of storage space. – CharlieHanson Mar 23 '16 at 17:28
4

Use the F() macro for all double-quoted strings you print. Change lines like this:

Serial.println(" degC.");

...to this:

Serial.println( F(" degC.") );

This will save about 100 bytes of RAM.

And as BrettAM suggested, put that table into PROGMEM:

  const int LUT_Therm[constLUTArraySize] PROGMEM = //LUT containing ADC values
  {
    223, 224, 225, 226, 227, 228, 229, 230, 231, 232,         //7^C to 7.9^C

This requires changing how you access the array, from this:

  if (thermValue < LUT_Therm[0]) //less than the least ADC value

...to this:

  if (thermValue < (int) pgm_read_word( &LUT_Therm[0] )) //less than the least ADC value

Note the cast, the function call and the ampersand wrapped around the desired array element. You must replace all uses of the array with this sequence. You can also read it once into a local int and then use it normally:

      int lut_therm = (int) pgm_read_word(LUT_Therm[i]); // read it in

      if (lut_therm > thermValue) //LUT value is greater than the reading

This saves almost 1000 bytes of RAM! The reported binary sketch size goes up by the same amount[1].

I had to pull the table out from inside getTempFloat, to the file scope. Here's the complete sketch with those mods:

/*
#####PROJECT DESCRIPTION##### 
Measures temperature with an Arduino and NTC Thermistor (10Kohms @ 25^C)                                 in a voltage divider circuit
Since the response is non-linear, a lookup table is used since there is a non linear response 
The values in the LUT are the predicted values of the ADC at  temperature between 6^C and 40^C
LUT has been derived from the calibration tests
*/

//#####CONSTANTS#####
const int constDelay = 3000; //constant delay for program 
const int constNoOfAnaloguePins = 2; //number of analogue pins that are to be read
const int errReturn = 998; //error value that is returned ***CHECK FOR CONSITENT VALUE ACROSS FUNCTIONS***

//#####VARIABLE DECLARATIONS#####
float avgTempC; //float var to store average of multiple temp readings

//#####SETUP PROCEDURE#####
void setup()
{
  //this is the Setup part of your project - this is for code that is run only ONCE at startup
  for (int analoguePinCounter = 0; analoguePinCounter < constNoOfAnaloguePins; analoguePinCounter++) //for loop to set up numerous analogue pins
  {
    pinMode(analoguePinCounter, INPUT);  //set pin mode of the analogue thermistor input
  }//end for
  analogReference(INTERNAL); //use the interval votlage of 1.1V for the ADC resolution
  Serial.begin(9600); //serial monitor baud rate
}//end Setup function

//#####LOOP PROCEDURE#####
void loop()
{
  //this is the main part of your project - put all continuously running code here
  for (int analoguePinCounter = 0; analoguePinCounter < constNoOfAnaloguePins; analoguePinCounter++) //for loop to take temp readings from each analogue pin
  {
    //get LPF average reading over 25 samples
    avgTempC = LPF(analoguePinCounter, 25);
    //print out the readings
    if (avgTempC < float(errReturn)) //there hasn't been any temps outside the range ***CHECK THE VALUE RETURNED FROM LPF IF ERROR***
    {
      Serial.print(analoguePinCounter);
      Serial.print( F(" | Average Temperature Readings = ") );
      Serial.print(avgTempC, 2);
      Serial.println( F(" degC.") );
      Serial.println( F("-------------------------") );
    }
    else //avgTempC has returned X indicating an error
    {
      Serial.print(analoguePinCounter);
      Serial.println( F("**ERROR IN READING AVERAGE TEMP**") );
      Serial.println( F("-------------------------") );
      delay(10);
    }//end if
    delay(constDelay/2); //wait X/2 seconds until moving onto next sensor
  }//end for
  delay(constDelay); //wait X seconds until next loop
}//end Loop function

/*
#####LOW PASS FILTER DESCRIPTION#####
This function is a LPF for smoothing out signals or averaging
It uses a static buffer to store the last "n" readings, and each time it is called,
the oldest reading is discarded, the new reading is added, and the stack average
value is returned.
The buffer size is user-adjustable, but is constrained between 2 and 50
An initialization function fills the entire buffer with the input value. 
This is useful when the LPF function is called for the first time.
Note: To meet the definition of a true LPF, this function must be called         at regular intervals.
*/
//#####LOW PASS FILTER FUNCTION#####
float LPF(int pinAnalogue, int bufferSize)
{
  //##PRE PROCESSOR##
  #define bufferCap 75 //maximum buffer capacity
  //##CONSTANTS##
  const int constMAXERRORS = 5; //max number of error that can occur
  //##VARIABLES##
  float tempC; //var to store instantaneous temp
  static float buffer[bufferCap]; //array to act like a 'stack' of temp values ***DOES IT NEED TO BE STATIC NOW?***
  float tempSum; //to store the sum of the temperatures
  float output; //return value of LPF which is the average of the temp readings
  int errCounter = 0; //initialise error counter to 0

  //make sure buffer size is between the range
  bufferSize = constrain(bufferSize, 2, bufferCap);

  for (int i=0; i<bufferSize; i++) //for loop to store temps in buffer array 
  {
    tempC = getTempFloat(pinAnalogue); //call function to get temperature from pin
    if ((tempC > -errReturn) && (tempC < errReturn)) //no error 
    {
      buffer[i] = tempC; //store temp value into array
    }
    else //error
    {
      errCounter++; //increment counter for number of erroneous readings
      i--; //decrement counter as to not skip a sample index
      if (errCounter > constMAXERRORS)
      {
        return(errReturn+1.0); //return the error value + 1
      }//end if
    }//end if
    delay(25); //allow the ADC to settle
  }//end for

  //calculate current stack average
  tempSum = 0; //initialise sum total
  for (int i=0; i<bufferSize; i++) //go through stack array
  {
    tempSum = tempSum + buffer[i]; //total up readings
  }//end for

  //average sum
  output = tempSum/bufferSize;

  return(output);
}//end LPF function

/*
#####GET TEMP FLOAT DESCRIPTION#####  
This function converts a thermistor reading into a corresponding temp in ^C
The thermistor is incorporated into a Voltage Divider Circuit:

   +Vref---[Thermistor]---+--[1.8K]---GND
                          |
                         ADC @ thermPin

ADC Values were externally calculated from the calibration table using:           ADC = 1023*10000/(Rtherm+10000)
The LUT is an array of integer constants containing the predicted ADC         values for all temperatures between +6^C to +40.5^C.
The array index starts at zero, which corresponds to a temperature of         +6^C.
A linear interpolation between the two closest entries is performed to         give a finer output resolution.
*/

//##CONSTANTS##
/*1K8*/
const int LUT_Therm[] PROGMEM = //LUT containing ADC values
{
  223, 224, 225, 226, 227, 228, 229, 230, 231, 232,         //7^C to 7.9^C
  233, 234, 235, 236, 237, 238, 239, 240, 241, 242,
  244, 245, 246, 247, 248, 249, 250, 251, 252, 253,
  255, 256, 257, 258, 259, 260, 261, 263, 264, 265,
  266, 267, 268, 269, 271, 272, 273, 274, 275, 277,
  278, 279, 280, 281, 283, 284, 285, 286, 287, 289,
  290, 291, 292, 294, 295, 296, 297, 299, 300, 301,           //13^C to 13.9^C
  302, 304, 305, 306, 308, 309, 310, 312, 313, 314,
  315, 317, 318, 319, 321, 322, 323, 325, 326, 327,
  329, 330, 332, 333, 334, 336, 337, 338, 340, 341,
  343, 344, 345, 347, 348, 350, 351, 353, 354, 355,
  357, 358, 360, 361, 363, 364, 366, 367, 369, 370,
  371, 373, 374, 376, 377, 379, 380, 382, 383, 385,
  387, 388, 390, 391, 393, 394, 396, 397, 399, 400,
  402, 404, 405, 407, 408, 410, 411, 413, 415, 416,
  418, 419, 421, 423, 424, 426, 428, 429, 431, 433,
  434, 436, 438, 439, 441, 443, 444, 446, 448, 449,
  451, 453, 454, 456, 458, 460, 461, 463, 465, 466,
  468, 470, 472, 473, 475, 477, 479, 480, 482, 484,
  486, 488, 489, 491, 493, 495, 497, 498, 500, 502,
  504, 506, 507, 509, 511, 513, 515, 517, 519, 520,
  522, 524, 526, 528, 530, 532, 534, 535, 537, 539,
  541, 543, 545, 547, 549, 551, 553, 555, 557, 559,
  560, 562, 564, 566, 568, 570, 572, 574, 576, 578, 
  580, 582, 584, 586, 588, 590, 592, 594, 596, 598,
  600, 602, 604, 606, 608, 611, 613, 615, 617, 619,
  621, 623, 625, 627, 629, 631, 633, 635, 638, 640,
  642, 644, 646, 648, 650, 652, 655, 657, 659, 661,
  663, 665, 667, 670, 672, 674, 676, 678, 680, 683,
  685, 687, 689, 691, 694, 696, 698, 700, 702, 705,
  707, 709, 711, 714, 716, 718, 720, 723, 725, 727,
  729, 732, 734, 736, 738, 741, 743, 745, 747, 750, 
  752, 754, 757, 759, 761, 764, 766, 768, 771, 773,
  775, 778, 780, 782, 785, 787, 789, 792, 794, 796,
  799, 801, 803, 806, 808, 811, 813, 815, 818, 820,
  822, 825, 827, 830, 832, 834, 837, 839, 842, 844,
  847, 849, 851, 854, 856, 859, 861, 864, 866, 868,
  871, 873, 876, 878, 881, 883, 886, 888, 891, 893,
  896, 898, 901, 903, 906, 908, 911, 913, 916, 918, 
  921, 923, 926, 928, 931, 933, 936, 938, 941, 943,
  946, 948, 951, 953, 956, 958, 961, 963, 966, 969,
  971, 974, 976, 979, 981, 984, 987, 989, 992, 994,
  997, 999, 1002, 1005, 1007, 1010, 1012, 1015, 1017, 1020, //40^C to 49.9^C
  1023                                                     //50^C
};
const int constLUTArraySize = sizeof(LUT_Therm)/sizeof(LUT_Therm[0]);

float getTempFloat (int thermPin)
{
  //##VARIABLES##
  float _tempC; //intermediate results + final temperature return value
  int ADC_Lo; //the lower ADC matching value
  int ADC_Hi; //the higher ADC matching value
  float Temp_Lo; //the lower number matching temperature
  int mapTemp_Lo; //the lower number that will be entered into the map function
  float Temp_Hi; //the higher  number matching temperature
  int mapTemp_Hi; //the higher number that will be entered into the map function

  //prep ADC onto that particular analogue pin to sort out multiplexing etc
  analogRead(thermPin); 
  delay(10);
  //get raw ADC value from VDR
  int thermValue = analogRead(thermPin); 
  //Serial.println(thermValue);
  /*Serial.print( F("PinNo : ") );
  Serial.println(thermPin);*/
  Serial.print( F("ADC : ") );
  Serial.println(thermValue);

  //return dummy value if the sensor reading falls outside of the LUT
  if (thermValue < (int) pgm_read_word( &LUT_Therm[0] )) //less than the least ADC value
    _tempC = -errReturn-1; //under range dummy value

  else if (thermValue > (int) pgm_read_word(LUT_Therm[constLUTArraySize-1])) //more than the greatest ADC value
    _tempC = errReturn+1; //over range dummy value

  else //value falls in LUT range
  {
    int prev_lut_therm = 0;
    for (int i=0; i<constLUTArraySize; i++) //step through LUT and look for a match
    {    
      int lut_therm = (int) pgm_read_word(LUT_Therm[i]);
      if (lut_therm > thermValue) //LUT value is greater than the reading
      {  
        //find the closest higher ADC value
        ADC_Hi = lut_therm;
        //record the closest higher temperature        
        Temp_Hi = float(i/10) + 13.0; //convert to temp
        //get the closest lower temperature - taking the lower table boundary into account
        if (i != 0) //general case -> as long as it is not the first entry
        {
          ADC_Lo = prev_lut_therm; //store the previous array element as the low
          Temp_Lo = float(i/10) + 13.0 - 0.1; //convert to temp
        }
        else //special case -> counter = 0 ie first array entry
        {
          ADC_Lo = lut_therm; //store the first array element
          Temp_Lo = i - float(i/10) + 13.0; //convert to temp
        }//end if
        //interpolate the temperature value for greater precision
        //NB the map function does not use floating-point math, so the int values of temp are multiplied by 100 and then result is subsequently divided by 100
        mapTemp_Lo = Temp_Lo*100;
        mapTemp_Hi = Temp_Hi*100;
        _tempC = float(map(thermValue,ADC_Lo,ADC_Hi,mapTemp_Lo,mapTemp_Hi))/100;
        break;  //exit for loop after the match is detected
      }//end if
      prev_lut_therm = lut_therm;
    }//end for
  }//end if
  return(_tempC);
}//end getTempFloat function

Note the technique for declaring the LUT_Therm array, using the empty [] brackets (line 135).

That's followed by the const int array size (line 182) that is "calculated" from the array declaration: (Total array size) / (size of one array element). If you ever change the lookup table, you don't have to count the elements by hand. Let the compiler do it for you! :)


[1] Although the IDE reports an increased binary sketch size, the total uploaded size is unchanged:

PROGMEM keyword   text    data     bss
No                5948     894     483
Yes               6810      32     483

As you can see, the 862 bytes for this table move from the data section to the text section. The uploaded HEX files are identical at 93,737 bytes.

  • I have a page about putting things into PROGMEM. – Nick Gammon Mar 23 '16 at 20:36
  • The binary sketch size goes up by the same amount”. That's wrong. Initialized arrays eat the same amount of flash whether they are PROGMEM or not. – Edgar Bonet Mar 23 '16 at 20:55
  • @EdgarBonet, true, but the reported program size goes up by the same amount. Try it! :) Just delete the PROGMEM keyword. Although it wouldn't run correctly, the size reported in build log changes. Must have something to do with which sections get added up? – slash-dev Mar 23 '16 at 21:39
  • ...now I'm wondering which number is correct... – slash-dev Mar 23 '16 at 22:08
  • Your avr-size must be broken. With PROGMEM I get a program size of 6646 bytes (.text: 6604 bytes, .data: 42 bytes). If I remove PROGMEM I get 6644 bytes (.text: 5606, .data: 1038). I checked the HEX file size (the stuff that is actually transferred to the Arduino flash) and in both cases the reported program size is correct. Please fix your answer. – Edgar Bonet Mar 24 '16 at 9:47
4

You have likely run out of RAM.

You have 431 integers in the look-up table alone. That equates to 862 bytes of SRAM. You also have a buffer of 75 floats, which equates to 300 bytes. This is in addition to all your other variables and function calls.

The Arduino Uno only has 2KB of SRAM.

Solutions:
1) Move your LUT to Flash (ie. program space) using the PROGMEM keyword (just before the equals sign):

const int LUT_Therm[constLUTArraySize] PROGMEM = //LUT containing ADC values

2) Use an Arduino with a greater amount of SRAM.

  • Arduino Mega - 8KB SRAM
  • Arduino ZERO - 32KB SRAM
  • Arduino Due - 96KB SRAM
  • Arduino MKR 1000/1010/1400/ZERO - 32KB SRAM
3

I went through your program and found a few bugs you may want to fix. I think only one of these (wastage of RAM) is really related to your problem, but anyway, here it goes:

Fist, there are two issues with LPF(). The description states that this is a rolling average low-pass filter. This is erroneous: a rolling average would take a single reading, then report the average of the last n readings. This function, in contrast, takes n readings and reports their average. This makes a big difference: a rolling average would need to store the last n values in static memory, while your function does this for no good reason. You are just wasting 300 bytes of RAM.

Here is a reimplementation of LPF() that does the same thing as yours without wasting RAM. I changed its name to be more consistent with it's real purpose:

/*
 * Take 'count' temperature readings from 'pin' and return their average.
 * Returns NaN (not a number) if too many readings where in error.
 */
float getAvgTemp(int pin, int count)
{
  const int MAXERRORS = 5; // max number of errors that can occur
  float temp;              // current temperature reading
  float tempSum = 0;       // sum of temperatures
  int errCounter = 0;      // number of erroneous readings

  for (int i = 0; i < count; i++) {

    // Try to get a valid reading.
    do {
      temp = getTempFloat(pin);
      if (isnan(temp))
        errCounter++;
      delay(25); // allow the ADC to settle
    } while (isnan(temp) && errCounter <= MAXERRORS);

    // Too many errors: return an error.
    if (errCounter > MAXERRORS)
      return NAN;

    tempSum += temp;
  }

  return tempSum / count;
}

Here I use NaN (not a number) as an error indicator, as it is semantically clearer than a random out-of-range value.

There are also a few errors in getTempFloat():

  • pgm_read_word(LUT_Therm[constLUTArraySize-1]) and pgm_read_word(LUT_Therm[i]) will not work: you have to pass pgm_read_word() the address where to read.
  • float(i/10) does not do what you want: it computes i/10 as an integer division (i.e. discarding the fractional part), and then it converts the result to a float. If you want a floating point division, you should make sure that at least one of the arguments is a float. The usual idiom is i/10.0.

Here is a version of getTempFloat() with these problems fixed, and also somewhat simplified:

float getTempFloat(int pin)
{
  int i, lutval, prev_lutval;

  // Take an analog reading.
  analogRead(pin);  // dummy reading to settle the MUX
  delay(10);
  int adc = analogRead(pin);
  Serial.print(F("ADC: "));
  Serial.println(adc);

  // Find i such that adc lies between LUT[i-1] and LUT[i].
  for (i = 0; i < constLUTArraySize; i++) {
    prev_lutval = lutval;
    lutval = pgm_read_word(&LUT_Therm[i]);
    if (lutval >= adc) break;
  }

  // Special case: adc == LUT[0].
  if (i == 0 && adc == lutval)
    return 13.0;

  // Report an error if out of range.
  if (i == 0 || i == constLUTArraySize)
    return NAN;

  // Return interpolated temperature.
  float fraction = float(adc - prev_lutval) / (lutval - prev_lutval);
  return 13.0 + (i-1 + fraction) / 10.0;
}

Addendum

I have noticed a few more problems and inconsistencies with your program.

Powering the thermistor from the internal reference

In setup(), you write

analogReference(INTERNAL); //use the interval votlage of 1.1V for the ADC resolution

and then, in a comment further down you have the schematic

+Vref---[Thermistor]---+--[1.8K]---GND

This suggests you are powering the voltage divider from the internal voltage reference of the MCU. You should not do that, as the datasheet states: “Note that VREF is a high impedance source, and only a capacitive load should be connected in a system.”

Relationship between ADC reading and thermistor resistance

In the same comment as above, you wrote:

ADC = 1023*10000/(Rtherm+10000)

Actually, the factor is 1024, not 1023. Again, see the datasheet. And the number 10000 should be 1800, i.e. the resistance you have put between the ADC input and GND. I understand that you may have simply changed the value and forgotten to update one of the comments. But you should consider a misleading comment as a bug, especially if it's not consistent with the actual code.

For this kind of measurement, you get the best precision if the pulldown resistor has a value close to the resistance of the thermistor. The comment at the beginning of the program says your thermistor has 10 kΩ @ 25°C. This means that, if you are mostly interested in the range around 25°C, a 10 kΩ pulldown is better than 1.8 kΩ.

Relationship between array index and temperature

The comment before the LUT says:

The array index starts at zero, which corresponds to a temperature of +6^C

Then, the very first line of the LUT has the comment

//7^C to 7.9^C

And later on, the implementation of getTempFloat() assumes index 0 means 13°C. You should sort this out and make everything consistent.

BTW, your LUT values go all the way up to 1023. A reading of 1023 would mean the ADC voltage is higher than 1022.5/1024×VREF. Which in turn would imply the thermistor has a resistance lower than 14.6 Ω (assuming a 10 kΩ pulldown). I find this dubious.

Removal of the LUT

Since your program is meant to run very slowly (constDelay = 3000), you can afford doing complex computations in it. You can get the same precision with a way smaller LUT if you use a higher order interpolation. For a function sampled at constant steps, the Catmull–Rom spline is easy to implement and is way better than linear interpolation.

Or you can remove the LUT altogether and use an analytical expression instead. If you derived the LUT from such an expression, why not use it in the program? If the LUT derives from experimental data, you can try to do an empirical fit, i.e. an arbitrary function that closely fits the data. The most obvious (though maybe not the best) empirical function would be a polynomial. Below is an example of such a function that reproduces you LUT with good accuracy using a 6th degree polynomial. It assumes the first LUT entry is for a temperature of 7°C:

float getTempFloat(int pin)
{
    const float T0 = 7;   // temperature at beginning of range
    int adc = analogRead(pin);
    float x = (adc-223.) / (1023.-223.) * 2 - 1;  // scale to [-1:1]
    if (x < -1 || x > 1) return NAN;              // out of range
    float y = 1.474 - x*(0.533 - x*(0.732 - x*0.458));
    return T0 + 26.107 + x*(19.287 - x*(3.596 - x*y));
}

Actually this may be even more accurate than your LUT-based function, as that LUT is made of integer values whereas the calibration curve should obviously be continuous.

  • Many thanks for your points some I wouldn't have considered. Yeah I've been changing the temp range so some comments don't make sense. Is the analogReference(Internal) call not use the 1.1V to get a better resolution between ADC steps and by using the Arduino's 3.3V power out as Vref and I've worked out that a 1k8ohm resistor gives me a range of 7-50degC (however as shown above I may be wrong!). I'm using this as a skin temperature logger so temps will be in the range of 30-40degC. I'm still unsure of how many measurements per minute I want but the interpolation looks nicer. Thanks – EoinScully Mar 31 '16 at 20:25

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.