Getting more precision out of TMP36 temperature sensor on Arduino Uno

I've been playing with Circuit #7: temperature sensor, from the Sparkfun Inventor's Kit. I've noticed that it is subject to electrical noise and have seen some methods to address that. I'm also currently just averaging lots of readings to get better stability. Except for when my hand gets near it, that works OK. The issue I have is with the 10 bit ADC which operates over a 0-5V range trying to deal with output that operates under 1.0V (I would hate to be in a 50C room!). To do this I employed the P2N2222A transistor with a 330 Ohm resistor. I'm not sure I've got it right. I've also added a Pot to calibrate with a reference thermometer. To make things even more fun, I've doubled up on the sensors for comparative purposes.

Here are the relevant data sheets:

http://ee-classes.usc.edu/ee459/library/datasheets/P2N2222A-D.PDF

My code, adapted from the Circuit #7 example code from the SIK:

/*
Based on

SparkFun Inventor's Kit
Example sketch 07

TEMPERATURE SENSOR

  Use the "serial monitor" window to read a temperature sensor.
  
  The TMP36 is an easy-to-use temperature sensor that outputs
  a voltage that's proportional to the ambient temperature.
  You can use it for all kinds of automation tasks where you'd
  like to know or control the temperature of something.
  
  More information on the sensor is available in the datasheet:
  http://dlnmh9ip6v2uc.cloudfront.net/datasheets/Sensors/Temp/TMP35_36_37.pdf
  
Hardware connections:

  Be careful when installing the temperature sensor, as it is
  almost identical to the transistors! The one you want has 
  a triangle logo and "TMP" in very tiny letters. The
  ones you DON'T want will have "222" on them.

  When looking at the flat side of the temperature sensor
  with the pins down, from left to right the pins are:
  5V, SIGNAL, and GND.
  
  Connect the 5V pin to 5 Volts (5V).
  Connect the SIGNAL pin to ANALOG pin 0.
  Connect the GND pin to ground (GND).

This sketch was written by SparkFun Electronics,
with lots of help from the Arduino community.
This code is completely free for any use.
Visit http://learn.sparkfun.com/products/2 for SIK information.
Visit http://www.arduino.cc to learn about the Arduino.

Version 2.0 6/2012 MDG
*/

/*
 *  Changes made to sketch to deal with a second temperature sensor and
 *  a pot to make sensor B agree with sensor A at some initial temperature.
 *  I've also added a 330 Ohm resister and PN2222A transistor to each
 *  temperature sensor to amplify the range for more precission output.
 */

double getTemperature(int digitalSample);
double convertCtoF(double c);
 
const int temperaturePinA = 0;
const int temperaturePinB = 2;
const int potPinA = 1;
const int potPinB = 3;

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


void loop()
{
  double degreesCA, degreesFA;
  double degreesCB, degreesFB;
  long vA, vB, vPotA, vPotB;
  const int samples = 50;
  int delayTime = 1000 / samples;
  unsigned long timeStamp = 0;

  vPotA = vPotB = vA = vB = 0;

  for (int i = 0; i < samples; i++) {
    vA += analogRead(temperaturePinA);
    vB += analogRead(temperaturePinB);
    vPotA += analogRead(potPinA);
    vPotB += analogRead(potPinB);
    delay(delayTime);
  }

  timeStamp = millis();

  vA /= samples;
  vB /= samples;
  vPotA /= samples;
  vPotB /= samples;
  
/*
 *  vPotA is used to calibrate sensor A to a reference thermometer.
 *  vPotB is used to calibrate sensor B to a reference thermometer.
 *  
 *  Due to the apparent nature of these sensors, while they agree with
 *  each other reasonably well, they don't seem to agree with the actual
 *  temperaure according to my laboratory grade thermometer. In this case,
 *  that thermometer is an Eastman Kodak thermometer for developing film.
 *  
 *  To make adjustment easier, the range of each pot is reduced in precision.
 */

  const byte bitShift = 3;
  const byte subtract = 63;
  vPotA >>= bitShift;
  vA += vPotA - subtract;
  vPotB >>= bitShift;
  vB += vPotB - subtract;

  degreesCA = getTemperature(vA);
  degreesCB = getTemperature(vB);
  degreesFA = convertCtoF(degreesCA);
  degreesFB = convertCtoF(degreesCB);

  Serial.print("Time:  ");
  Serial.print(timeStamp);
  Serial.print("  A: ");
  Serial.print(vA);
  Serial.print("  PotA:  ");
  Serial.print(vPotA - subtract);
  Serial.print("  C: ");
  Serial.print(degreesCA);
  Serial.print("  F: ");
  Serial.print(degreesFA);
  Serial.print("  B: ");
  Serial.print(vB);
  Serial.print("  PotB: ");
  Serial.print(vPotB - subtract);
  Serial.print("  C: ");
  Serial.print(degreesCB);
  Serial.print("  F: ");
  Serial.println(degreesFB);
   
  delay(200);
}

/*
 *  This function is based on the TMP36 data sheet pages 5 and 8.
 *  Remember also that the Arduino analog inputs map 0V - 5V over
 *  a range of 0 - 1023. I've added a 330 Ohm resistor to the base
 *  of a P2N222A. Without the transistor, the TMP36 only puts out
 *  750mV @ 25C. This goes to 1.0V @ 50C. This means that only about
 *  8 bits are used in the analog inputs. I'm actually seeing less
 *  than 7 bits @ 25C.
 */
double getTemperature(int digitalSample)
{
  return (digitalSample * (0.0048875855L / 4.0L) - 0.5L) * 100.0L;
}

double convertCtoF(double c)
{
  return c * 9.0L / 5.0L + 32.0L;
}

Some sample output:

Time:  1567150  A: 558  PotA:  1  C: 18.18  F: 64.73  B: 557  PotB: 7  C: 18.06  F: 64.51
Time:  1568400  A: 558  PotA:  1  C: 18.18  F: 64.73  B: 557  PotB: 7  C: 18.06  F: 64.51
Time:  1569650  A: 558  PotA:  1  C: 18.18  F: 64.73  B: 557  PotB: 7  C: 18.06  F: 64.51
Time:  1570900  A: 558  PotA:  1  C: 18.18  F: 64.73  B: 557  PotB: 7  C: 18.06  F: 64.51
Time:  1572150  A: 558  PotA:  1  C: 18.18  F: 64.73  B: 557  PotB: 7  C: 18.06  F: 64.51
Time:  1573400  A: 558  PotA:  1  C: 18.18  F: 64.73  B: 557  PotB: 7  C: 18.06  F: 64.51
Time:  1574650  A: 558  PotA:  1  C: 18.18  F: 64.73  B: 557  PotB: 7  C: 18.06  F: 64.51
Time:  1575901  A: 558  PotA:  1  C: 18.18  F: 64.73  B: 557  PotB: 7  C: 18.06  F: 64.51
Time:  1577151  A: 558  PotA:  1  C: 18.18  F: 64.73  B: 557  PotB: 7  C: 18.06  F: 64.51
Time:  1578401  A: 558  PotA:  1  C: 18.18  F: 64.73  B: 557  PotB: 7  C: 18.06  F: 64.51

My worry is that my output is more dependent on the potentiometer settings than the actual sensor output. I am also not sure about my function getTemperature(). I'm attempting to expand the range of the temperature sensor using the transistor and then hoping I still have a linear(ish) output that gets the benefit of a full ten bits of sampling.

I'll try to attach a photo of my breadboard wiring. The temperature sensors are next to the pots I use to adjust the readings to match a reference thermometer.

Try this.
It uses the internal 1.1volt Aref (IMHO default 5volt Aref is useless here).
Ignore the LCD part.
Read the comments..
Leo..

// Thermometer in Celcius and Fahrenheit
// displays on serial monitor and/or LCD
// TMP35 or TMP36 temp sensor connected to Analogue input A1, 3.3volt and ground
// or LM35 temp sensor connected to A1, 5volt and ground
// TMP35 and LM35 temp range ~+2C to ~+105C
// TMP36 temp range ~-40C to ~+55C
// see line 46 for TMP35/LM35/TMP36 code selection

#include <LiquidCrystal.h>
LiquidCrystal lcd(8, 9, 4, 5, 6, 7); // your LCD pins could be different
byte ledPin = 10; // backlight pin
const byte numReadings = 32; // number of readings for smoothing (max 64)
int readings[numReadings]; // readings from the analogue input
byte index = 0; // index of the current reading
unsigned int total = 0; // running total
int inputPin = A1; // the pin that the sensor is connected to
float Aref = 1.0759; // temp calibration | change in small 0.000x steps to the actual Aref voltage of ---YOUR--- Arduino for accurate temp readings
float tempC; // Celcius
float tempF; // Fahrenheit

void setup() {
  //analogWrite(ledPin, 200); // optional dimming
  analogReference(INTERNAL); // use the internal ~1.1volt reference | change (INTERNAL) to (INTERNAL1V1) for a Mega
  Serial.begin(115200); // ---set the serial monitor to this value---
  lcd.begin(16, 2); // shield with 2x16 characters
  lcd.print("Thermometer"); // info text
  lcd.setCursor(0, 1); // second row
  lcd.print("0-100 Celcius");
  for (index = 0; index < numReadings; index++) { // fill the array for faster startup
    readings[index] = analogRead(inputPin);
    total = total + readings[index];
  }
  index = 0; // reset
  delay(2000); // info display time
}

void loop() {
  total = total - readings[index]; // subtract the last reading
  readings[index] = analogRead(inputPin); // one unused reading to clear ghost charge
  readings[index] = analogRead(inputPin); // read from the sensor
  total = total + readings[index]; // add the reading to the total
  index = index + 1; // advance to the next position in the array
  if (index >= numReadings) // if we're at the end of the array
    index = 0; // wrap around to the beginning

  // convert value to temp | leave only one of these two lines uncommented
  //tempC = total * Aref * 0.1 / numReadings; // value to celcius conversion for TMP35 or LM35
  tempC = total * Aref * 0.1 / numReadings - 50.0; // value to celcius conversion for TMP36

  // Celcius to Fahrenheit conversion
  tempF = tempC * 1.8 + 32;

  // print to LCD
  if (total == 1023 * numReadings) { // if overflow
    lcd.clear();
    lcd.print("---TOO HOT---");
  }
  else {
    lcd.clear();
    lcd.print(tempC, 2); // two decimal places
    lcd.setCursor(6, 0); // position 6, first row
    lcd.print("Celcius");
    lcd.setCursor(0, 1); // second row
    lcd.print(tempF, 1); // one decimal place
    lcd.setCursor(6, 1); // position 6, second row
    lcd.print("Fahrenheit");
  }

  // print to serial monitor
  Serial.print("Raw average = ");
  Serial.print(total / numReadings);
  if (total == 1023 * numReadings) {
    Serial.println("  ----too hot----");
  }
  else {
    Serial.print("   The temperature is  ");
    Serial.print(tempC, 2);
    Serial.print(" Celcius  ");
    Serial.print(tempF, 1);
    Serial.println(" Fahrenheit");
  }

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

All very well going for precision, but is the sensor accurate enough in the first place? No point homing in closer to what the sensor is the temp if it's wrong....

Stability is usually a bigger problem than accuracy.

When Arduino's default 5volt Aref is used, running on USB or external power, or even a flashing LED or PWM-ed backlight can make temp display jump a few degrees.
Leo..

Wawa:
Stability is usually a bigger problem than accuracy.

I ran into that issue first. I was able to fix that without too much difficulty. I'm currently sampling about fifty times per second and averaging that together in floating point. It seems to work OK for my purposes. Accuracy is another matter. The data sheet does say that there is a +-2C error over the range. Between two sensors that's up to a 4C difference that they stay within. It's a bit of a buzz kill but I'll have to live with it. The divergence increases with temperature change from where I "synchronize" the two sensors.

I clearly don't understand something about how transistors work. When I removed them and went back to sampling temperature sensor output directly, I was able to get working data. With the transistors, nothing changed with temperature at all! I thought I was amplifying the signal. Instead I was freezing it in place.

Another problem I had with the transistors in place was that adjusting one pot affected both sensor readings. Some weird electrical thing was going on. I have no idea what though.

I didn't know about the Aref thing. I thought I would give EXTERNAL a go with a + lead from the bread board to the Aref input on the Arduino. It's probably acting exactly the same as DEFAULT. I don't know.

Thanks for the help.

dsteuber:
I ran into that issue first. I was able to fix that without too much difficulty. I'm currently sampling about fifty times per second and averaging that together in floating point. It seems to work OK for my purposes.

The returned A/D value depends on two things.
The voltage on the analogue pin, and the reference voltage (the ruler that measures it).

Default Aref is the MCU supply. It can come from the onboard 5volt regulator, or from USB.
Both have accuracy and stability problems.
If you measure with 5.07volt, and with 4.95volt five minutes later, you will get a different result.
Oversampling is not going to fix this.

dsteuber:
Accuracy is another matter. The data sheet does say that there is a +-2C error over the range. Between two sensors that's up to a 4C difference that they stay within. It's a bit of a buzz kill but I'll have to live with it. The divergence increases with temperature change from where I "synchronize" the two sensors.

There is a maths line in the code that converts the A/D value to temperature.
You should have a maths line for each sensor, and calibrate each sensor there.
This.
tempC = total * Aref * 0.1 / numReadings - 50.0;

Could be.
tempC = total * Aref * 0.1004 / numReadings - 49.7;

dsteuber:
I clearly don't understand something about how transistors work. When I removed them and went back to sampling temperature sensor output directly, I was able to get working data. With the transistors, nothing changed with temperature at all! I thought I was amplifying the signal. Instead I was freezing it in place.

Another problem I had with the transistors in place was that adjusting one pot affected both sensor readings. Some weird electrical thing was going on. I have no idea what though.

Bad idea to fix a problem with another problem.
Connect the sensor directly to the analogue pin, and do the rest in code.

dsteuber:
I didn't know about the Aref thing. I thought I would give EXTERNAL a go with a + lead from the bread board to the Aref input on the Arduino. It's probably acting exactly the same as DEFAULT. I don't know.

Read the Aref page carefully. There are some dangers with using an external reference voltage.
But why. The low 1.1volt Aref is perfect for reading these relative low sensor output voltages.
Leo..

Wawa:
The returned A/D value depends on two things.
The voltage on the analogue pin, and the reference voltage (the ruler that measures it).

I see. So by using INTERNAL, I at least have a ruler that remains the same length? If so, I guess I can lose the external Aref connection. There is actually a warning about it:

Wawa:
There is a maths line in the code that converts the A/D value to temperature.
You should have a maths line for each sensor, and calibrate each sensor there.
This.
tempC = total * Aref * 0.1 / numReadings - 50.0;

Could be.
tempC = total * Aref * 0.1004 / numReadings - 49.7;

So, in your code, the Aref value adjusts for your board? The reason I use the pots is to move the line (the "y intercept" of y=mx +b) of one sensor to match the other (or an external lab thermometer). The number you place after Aref would seem to also alter the slope of the line. If that is the case, then I need ArefA and ArefB. Is that right?

Wawa:
Connect the sensor directly to the analogue pin, and do the rest in code.

Read the Aref page carefully. There are some dangers with using an external reference voltage.
But why. The low 1.1volt Aref is perfect for reading these relative low sensor output voltages.
Leo..

I removed the transistors. That fixes an immediate problem. It doesn't fix my understanding of why they weren't amplifying a signal at all. I really do need a better understanding of transistors and how to read their data sheets.

I thought using the board as the external Aref would keep the ruler, as you put it, in proportion with the voltage being measured. That is, cancel out any instability in the voltage supply since they would presumably use the same figure.

Thanks.

I have now removed the POTs as well since their range is so narrow using the 1.1V reference. Surprisingly, this changed the temperature results I was getting. How does THAT happen?

Assuming that the TMP36 is relatively linear in spite of the +-2 error over range and +- 0.1 error at 25C, I've reworked my code so that the corrections are performed in the getTemperature function. That now looks like this.

/*
 *  This function is based on the TMP36 data sheet pages 5 and 8.
 *  Remember also that the Arduino analog inputs map 0V - 1.1V over
 *  a range of 0 - 1023 when using the INTERNAL (1.1V) reference. The 
 *  temperature line is linear with voltage. Slope intercept form
 *  for a line is y = mx + b where m is the slope and b is the intercept.
 *  
 *  In theory, 500.00mV = 0 deg C, 750.00mV = 25.00 deg C and 1.00V = 50 deg C.
 *  In reality, the temperature sensors vary according to the spec sheet and
 *  the reference voltage may not be exactly 1.1V.
 */
double getTemperature(double digitalSample, double slope, double intercept)
{
  return round((digitalSample * 0.0010752688L * slope - (0.5L * intercept)) * 1000.0L) / 10.0L;
}

The 0.0010752688L figure comes from dividing 1.1 / 1023.

I struggle over choosing variable names ;).

dsteuber:
I have now removed the POTs as well since their range is so narrow using the 1.1V reference. Surprisingly, this changed the temperature results I was getting. How does THAT happen?

1024 A/D values are now spread out over ~1.1volt, not over 5volt.

A temp of 25C gives 750mV on the sensor output.
With ~5volt Aref, you get an A/D value of 0.75/5 *1023 = ~153
With ~1.1volt Aref, you get an A/D value of 0.75/1.1 *1023 = ~697

When the ruler gets smaller, the value of what you measure increases.
Leo..

Wawa:
1024 A/D values are now spread out over ~1.1volt, not over 5volt.

I was actually asking about removal of the POTs causing changes. I already understood the ADC values would be reflected over 1.1V instead of 5V. I was under the impression that the POTs were essentially variable voltage dividers and I would still have 1024 values to work with. That's not the case. So I pulled them out of my bread board.

All "calibration" is now handled in software. I'm getting fair agreement with a Kodak darkroom thermometer. I used the Numbers program to help me work out the adjustments to apply to bring the two TMP36 sensors into better agreement. I also switched to using a running average. It didn't reduce noise as much as I thought it would vs the algorithm I was using. I could extend the time range for the sampling. I'm not going to bother though. The maximum disagreement at temperatures I was able to test was 0.5ºC. I can live with that.

BTW, out of curiosity and totally off topic, are you living in a region of the USA served by WaWa convenience stores? They are all over the place where I live.

Thanks.

dsteuber:
BTW, out of curiosity and totally off topic, are you living in a region of the USA served by WaWa convenience stores? They are all over the place where I live.

Haha, no. I live in New Zealand.
A friend used to call me Wawa as a toddler, before he could say Leo.
Don'ty ask me why. It's more than 60 years ago.
Leo..