Basic Temperature Reading (LM35)- Problems with ADC values

Hi,

I tried searching around, but (as is with most programming questions I have) I can't seem to find what I need. Probably because I don't know exactly how to ask the question.

I went ahead and commented on my script itself the problems I am having, but will shortly describe them here:

  1. I don't understand why the sample floats the ADC variable in one of the equations. I don't understand why the equation doesn't work without it or when I float the entire equation vs just the ADC value.

(Equation: Line 51, Comments: Line 45 to 49)

  1. I especially don't understand why the print out of the ADC values only shows two digits, 41 and 42, instead of 410 and 420. The equations are calculating temperature using 410 and 420, but the print out is only showing 41 and 42. Furthermore, the software loses all precision between 410 and 420. That seems really odd to me.

(Comments: Line 58 to 62)

Attached is the code and a screenshot of the serial output.

Reading_Temperature.ino (1.73 KB)

Print_Temp_AnalogValue.jpg

You have not followed the rules for posting and as a result I cannot see your code on my mobile device.

C/C++ has rules for int arithmetic and for float arithmetic. An ADC value starts as an int. You cannot float an entire equation except by converting an early part of the equation (a constant or a variable) to a float.

I cannot see how the printing is done so I cannot comment on the values printed.

What board are you using?

Bad example sketch.
The LM35 outputs 10mV/degreeC, so 250mV (0.25volt) at room temp (25C/77F).
That results in 0.25/5*1024= 51 A/D values at 25C.
Very course steps of 0.5C per A/D value.
Trying to print that with two decimal places results in very large gaps.
You can improve that resolution about five times by switching to the internal 1.1volt Aref (assuming Uno).
Try this sketch. It could be further improved with smoothing code.
Or dump the LM35, and get a digital DS18B20 sensor.
Leo..

// LM35_TMP36 temp
// works on 5volt and 3.3volt Arduinos
// connect LM35 to 5volt A0 and ground
// connect TPM36 to 3.3volt A0 and ground
// calibrate temp by changing the last digit(s) of "0.1039"

const byte tempPin = A0;
float calibration = 0.1039;z
float tempC; // Celcius
float tempF; // Fahrenheit

void setup() {
  Serial.begin(9600);
  analogReference(INTERNAL); // use internal 1.1volt Aref
}

void loop() {
  tempC = analogRead(tempPin) * calibration; // use this line for an LM35
  //tempC = (analogRead(tempPin) * calibration) - 50.0; // use this line for a TMP36
  tempF = tempC * 1.8 + 32.0; // C to F
  Serial.print("Temperature is  ");
  Serial.print(tempC, 1); // one decimal place
  Serial.print(" Celcius  ");
  Serial.print(tempF, 1);
  Serial.println(" Fahrenheit");

  delay(1000); // use a non-blocking delay when combined with other code
}

For the benefit of others,

/*
 * Tutorial 1: Reading Temperature
 * 
 * Read the temperature in celcius over serial.
 *
 *
 * To see this sketch in action open up the serial
 * monitor. Clamp your fingers over the LM35
 * sensor. The temperature will rise and then fall
 * as you remove your fingers.
 *
 * The circuit:
 * - LM35 to 5v, GND, and Vo to analog 0
 *
 */

// the output pin of the LM35 temperature sensor
// Pin used must be A0.  
int lm35Pin = A0;

void setup()
{
    // set up serial at 9600 baud   
    Serial.begin(9600);
}

void loop()
{ 
    int analogValue;
    float temperature;

    // read our temperature sensor
    // I am having a hard time getting this to read anything but 410 and 420 at room temperature.  
    // 411 thru 419 seem to be impossible to read.  Why?
    
    analogValue = analogRead(lm35Pin);

    // convert the 10bit analog value to celcius
    // 10 mV = 0.1 degrees C
    // Why the float?  float(analogValue / 1024), I get temperature = 0
    // temperature = analogValue / 1024, I get zero
    // Isn't analogValue already defined as an interger?  Why do I need it to float?  
    
    temperature = float(analogValue) / 1024;
    temperature = temperature * 500;

    // convert to F
    
    temperature = temperature*1.8+32;

    // print the temperature over serial
    // printing analogValue gives me either 41 or 42 (68.04 F or 68.91 F)
    // 410 = 68.04 F & 420 = 68.91 F
    // 41 = 35.6 F & 42 = 35.69 F    
    // What is going on with analogValue?
      
    Serial.print("Temp: ");
    Serial.print(temperature);
    Serial.print(" F ");
    Serial.println(analogValue);
    
    // wait 1s before reading the temperature again
    
    delay(1000);
}

Output with nothing attached to A0,

Temp: 393.23 F 411
Temp: 459.15 F 486
Temp: 310.61 F 317
Temp: 519.79 F 555
Temp: 272.82 F 274
Temp: 511.00 F 545
Temp: 279.85 F 282
Temp: 402.90 F 422
Temp: 297.43 F 302
Temp: 314.13 F 321
Temp: 418.72 F 440
Temp: 286.88 F 290
Temp: 490.79 F 522

Works fine.

In answer to the questions posed in your comments, float(analogValue / 1024) returns zero because analogValue / 1024 will always be zero as both are integers and this operation is performed prior to casting. Either of the two values within the parentheses needs to be cast first as a float for the operation to work as desired. In this particular instance, placing a decimal point at the end of 1024 would have sufficed. Having done that, there would have been no need for the explicit cast. So the two immediate solutions would be,

temperature = float(analogValue) / 1024; //which you already employed, or
temperature = analogValue / 1024.;

The reason for this, which is also the answer to your second question, is that when performing a calculation the compiler/processor will set aside temporary storage, either in registers current unused or on the stack, for intermediate values. The amount and type of storage is determined by the largest type found in the values being used in the calculation. In your case (both first and second questions) analogValue and 1024 are both integers and thus storage space is allocated for an integer temporary value which, based on integer division, in this case will always be zero. In the first instance, this zero was then being cast as a float which would then simply become 0.0, and in the second case, passed into temperature, also as zero.

With respect to your printout, the value 42, which is analogValue, according to your first calculation float(analogValue) / 1024; equals 0.041.. then * 500 = 20.508. Presumably this is Celsius as then you convert temperature = temperature*1.8+32; to Fahrenheit which equals 68.91, exactly what is being printed. I fail to see where this is a problem.

engineer1984:
I tried searching around, but (as is with most programming questions I have) I can't seem to find what I need. Probably because I don't know exactly how to ask the question.

Let us start this way:

1. The LM35 Sensor produces --

250 mV (output signal) when the room temperature is 250C. //as per data sheet
350 mV when the room temperature is 350C. //as per data sheet

The response of the sensor is linear with some gain but there is no offset.

Let us designate the output signal as VDT when the room temperature is T0C.

The relation between T and VDT is --
(0.350 - 0.250)/(35 - 25) = (VDT - 0.350)/(T - 35)

==> T = 100*VDT ----------------------------------------(1)

2. VDT is an analog signal. The range of this signal is: 0 mV (at 00C) to 2000 mV (at 2000C). This signal will be digitized by the internal 10-bit ADC of the ATega328P MCU of the Arduino UNO Board. For better resollution (less mV for LSBit of ADC), let us use internal 1.1V of the MCU as the VREF of the ADC. Under this circumstance, let us find the relationship between VDT and the 'digital value of ADC (let us call it ADC)'.

when input analog voltage is 1.1V, all 10-bit of ADC will assume 1s; ADC value is: 1111111111 = 1023
when input analog voltage is VDT, ADC value is: (1023/1.1)*VDT. That is --

ADC = (1023/1.1)*VDT
==> VDT = (1.1/1023)*ADC ---------------------(2)

If the output signal of the LM35 sensor is connected with A0 (analog channel - 0) pin of UNO, the value of ADC parameter can be found by executing this instruction: analogRead(A0);. Now, Eqn(2) becomes as:

==> VDT = (1.1/1023)*analogRead(A0);-----------(2a)

Putting the value of VDT into Eqn(1), we can ahve --
T = 100*(1.1/1023)*analogRead(A0); ---------------------------------(3)

3. Let us carry out some numerical calculations on paper to see how the Eqn(3) introduces decimal points in the measured temperature value.

(1) Let us see how much minimum change in the room temperature that the ADC can detect.
The minimum analog input voltage that the ADC can detect (called resolution of ADC) =
Full Scale/2number_of_ADC_bit-1 = VREF/210-1 = 1.1/1023 = 0.001075 V. Let us see, how much change in the room temperature will produce this (0.001075 V) voltage.

For 0.200 V output, the room temperature is: 200C
For 0.001075 V output, the room temperature would be : 0.10750C.

(2) When room temperature is 20.10750C, the voltage at A0-pin is 0.201075 V. The value of T in Eqn(3) is:
T = 100*(1.1/1023)* 186.9997 //ADC = analogRead(A0) = VDT*1023/1.1 from Eqn(2a)
==> T = 20.107494 ----------------(4)

(3) If we want to keep only the integer part of the temperature value of T of Eqn(4), we tell the compiler to discard the fractional part and keep only the integer part (20 = 0x14 = 00010100b); it is done with the following style known as int casting: (the compiler stores 00010100 into the variable T.)

int T;
T = (int)100*(1.1/1023)*ananlogRead(A0);
Serial.println(T); // shows: 20

(4) If we want to keep both the integer part and the fractional part (called decimal value or float point number/value) of the temperature value of T of Eqn(4), we tell the compiler to keep both parts; it is done with the following style known as float casting:

float T;
T = (float)100*(1.1/1023)*ananlogRead(A0); --------------------------(5)
Serial.println(T, 6); // shows: 20.107494

What does the float casting do? It does the following:
Now, the compiler does not save 00010100 into the variable T; it now allocates four consecutive memory locations for the variable T and stores this 32-bit value (known as binary32 formatted value) : 41A0DC26 into these four memory locations as per binary32 'floating point number' convention of IEEE-754 Standard. I can show you how this value could be found, but I am unable to explain how the compiler does know Eqn(5) has 2-digit in the integer part and 6/more digits in the fractional part (someone else may explain!).

The Serial.println(T, 6); command takes 32-bit value from the four memory locations; it follows binary32 rules to extract the decimal number and finally shows the temperature value in Serial Monitor with 6-digit accuracy.

float-6.ino (274 Bytes)

This sketch will get you 1/10 degree resolution without floats.

/*
 LM35 thermometer, no floats, no delays
  http://www.ti.com/lit/gpn/lm35
*/

const byte sampleBin = 8, // number of samples for smoothing
           aInPin = A0;
const int calValue = 0; // adjust for calibration 
const int kAref = 1100, // analog ref voltage * 1000
                        // measured with accurate DMM
          kSampleBin = sampleBin * 1000,
          tEnd = 5000; // update time in mS
int tempC,
    tempF;
uint32_t total,  // sum of samples
         tStart; // timer start

void setup()
{
  Serial.begin(9600);
  analogReference(INTERNAL); // use 1.1V internal ref
  analogRead(aInPin);
  for(int i = 0;i < sampleBin;i++) // for smoothing, fill total
    total += analogRead(aInPin);   // with sampleBin * current
                                   // reading
}
void loop()
{
  if(millis() - tStart > tEnd)
  {
    tStart = millis(); // reset timer 
    total -= (total / sampleBin); // make room for new reading
    total += analogRead(aInPin); // add new reading
    tempC = total * kAref / kSampleBin + calValue;
    tempF = (tempC * 18 + 3200) / 10;
    Serial.print(analogRead(aInPin));
    Serial.print("\t");
    Serial.print(total); // sum of samples
    Serial.print("\t");
    prntTemp(tempC);
    prntTemp(tempF);
    Serial.println();
  }
}
    
void prntTemp(int temp){
  Serial.print(temp / 10); // whole degrees
  Serial.print(".");
  Serial.print(temp % 10); // tenths
  Serial.print("\t");
}

The following sketch provides temperature measurement value with 4-digit accuracy after the decimal point without float.

/*This sketch gives 4-digit accuracy after decimal point without float*/
unsigned long tempC;
byte bcdArray[8];

void setup()
{
  Serial.begin(9600);
  analogReference(INTERNAL);
}

void loop()
{
  tempC = (uint32_t)1075 * analogRead(A5);
  //Serial.println(tempC);

  for (int i = 0; i < 8; i++)
  {
    byte x = tempC % 10;
    bcdArray[i] = x;
    tempC = tempC / 10; //0, 1, 2, 3, ..., x7
  }

  for (int i = 7; i >= 4; i--)
  {
    Serial.print(bcdArray[i]);
  }
  Serial.print('.');
  for (int i = 3; i >= 0; i--)
  {
    Serial.print(bcdArray[i]);
  }

  Serial.println("===============");
  delay(1000);
}

The following sketch provides temperature measurement value with 4-digit accuracy after the decimal point without float.

That statement is rubbish. An LM35 can not possibly detect the difference between 27.0456 and 27.0457 degrees Celcius, so it is pointless to construct a string that contains more than one decimal point or to claim that there is accuracy involved in the meaningless string.

PaulS:
That statement is rubbish. An LM35 can not possibly detect the difference between 27.0456 and 27.0457 degrees Celcius, so it is pointless to construct a string that contains more than one decimal point or to claim that there is accuracy involved in the meaningless string.

But, please look at what is happening here:

tempC = 100*(1.1/1023)*analogRead(A5);

==> tempC = 0.107526881720analogRead(A5); ~= 0.1075analogRead(A5);

To retain the quantity 0.1075 (in order to make meaningful all the 4-digit after the decimal point), I would like to multiply tempC by 10000; as a result, I get:

amplifiedtempC = 1075*analogRead(A5);

While displaying the result, I divide the amplified value by 10000; I get 4-digit after the decimal point. Based on this analytical calculation, I have concluded that there is an accuracy of 4-digit after the decimal point.

If using float, am I allowed to execute this command : Serial.print(tempCFloat, 4); and then saying that I am presenting a temperature value with 4-digit accuracy? Or, should I say that the precision is 4-digit; but, the accuracy is 2-digit?

This is the screenshot of an LM35 based System.
scrshot-6.png

Moreover, The data sheets says that the output voltage of LM35 changes linearly with temperature.
lm35des.png

If this is the case, for a change of temperature from 20.00000C to 20.10750C, the output voltage will be changed from
0.200 V to 0.201075 V. In order to detect such a small change in voltage/temperature, we set the ADC's VREF at 1.1V instead of 5V.

scrshot-6.png

lm35des.png

You are assuming, incorrectly, that analogRead() will, with am LM35 sensor attached, return every possible value from 0 to 1023.

Thanks for all your help!

So the LM35 returns 1023/10?

Is that why I see the following output?

Temp: 68.91 F 42

Temp: 68.91 F 42

Temp: 68.91 F 42

Temp: 68.91 F 42

Temp: 68.91 F 42

Temp: 68.91 F 42

Temp: 68.91 F 42

Temp: 68.91 F 42

Temp: 68.91 F 42

Temp: 68.91 F 42

Temp: 68.91 F 42

Temp: 68.91 F 42

Temp: 68.91 F 42

Temp: 68.91 F 42

Also, I have a Osepp Uno R3 Plus (Osepp Starter Kit 201)

engineer1984:
So the LM35 returns 1023/10?

Based on what the data sheet says (Fig-1), the LM35 returns continuous voltage (the response) when the environmental temperature (the excitation) changes continuously. The range of temperature change is: -550C to 1500C. With a positive bias voltage (say, 5V), the LM35 returns 0V to 1.5V for a temperature change of 00C to 1500C. That means that for 10C change in temperature, the LM35's output voltage is augmented by 10 mV.

lm35des.png
Figure-1: Excerpt from data sheet

lm35des.png