NerdKits - electronics education for a digital generation

You are not logged in. [log in]

NEW: Learning electronics? Ask your questions on the new Electronics Questions & Answers site hosted by CircuitLab.

Everything Else » How to get a more accurate temperature

October 04, 2011
by glendon
glendon's Avatar

Hi,

I got the temperature sensor project working but it gives 29.5 degree celsius. I feels it's slightly too high for my room (esp. when it just rained and I have my windows opened). The PDF guide says that to get a more accurate reading, we need to calibrate the reference voltage (which is a constant +5V for the project). Can someone suggest some simple calibration ways I can try out to achieve a more accurate reading?

By the way, I have a computer science degree (so programming is second nature to me), but all my understanding of electronics comes solely from my experimenting with the NerdKits. Hence, kindly keep your suggestions/explanations simple for a layman like me.

Thanks in advance.

October 05, 2011
by Ralphxyz
Ralphxyz's Avatar

Hi glendon, first thing the "reference voltage" is most likely not 5 volts.

You need to test your power supply to find the true reference voltage.

The Nerdkit furnished tempsensor actually come in three versions with the more accurate sensor costing a lot more, relatively speaking.

You can go to a electronics supplier to get a more accurate sensor.

There are discussions here in the forum where people got more accuracy out of the supplied tempsensor.

Also since you are comfortable with the programing you could also program in a curve once you have established how far off the accuracy is. You would need a reference thermometer to do the programing calibration.

Ralph

October 05, 2011
by bretm
bretm's Avatar

You can also tell the ADC to measure the 1.1V band gap voltage reference built in to the MCU instead of an external voltage. You can use that result to calculate the AREF value. But that's still not super accurate. There's also a register called DIDR or something like that, whcih you can use to disengage the digital input circuitry on the analog input pins. Also, don't use the other PORTC pins while taking a measurement. You can also put the MCU to sleep. When you do that while ADC is enabled, it will start a conversion. The quiet sleepy time will eliminate some more measurement noise. But external calibration is best.

October 05, 2011
by mrobbins
(NerdKits Staff)

mrobbins's Avatar

Hi glendon,

I'd suggest you mentally split the calibration problem into two pieces:

  1. calibrating for your particular LM34's temperature-to-voltage relationship
  2. calibrating the ADC versus the analog voltage measured using a multimeter

You have to address both pieces in order to get an accurate reading.

I'd suggest you do #2 first, because it's relatively easy (using a digital multimeter that you trust) to do. If you remove the LM34 and make a little voltage divider with two resistors (VCC -- 10K --- 10K --- GND, with the middle point connected to the analog in pin), you'll have a voltage that's roughly 2.5V. Measure that voltage to ground with a digital multimeter, and grab as many digits as you can. Then, using the tempsensor code, look at the average temperature reading that's shown.

Just to play with numbers, let's say your multimeter measured 2.480 volts, and the NerdKits LCD with the normal tempsensor code says it says 252.67 degrees. The normal tempsensor code has a line like this in it:

return sample * (5000.0 / 1024.0 / 10.0); // overall factor 0.4883

But if you've actually measured otherwise, we can "fix" this expression. If our code says 252.67 degrees, then its average 10-bit ADC sample must have been:

252.67 / (5000/1024/10) = 517.5  (on a scale of 0 to 1023)

Then, we can change our expression to be something like:

return sample * (2.480 / 517.5 / 0.010); // overall factor 0.4792

That means we take sample, multiply by the ratio (2.480 volts / 517.5 ADC scale), to get a voltage, and then multiply by the ratio (1 degree F / 0.010 volts). So now recompile and give that a try. If you still have the same resistors in, and if we're still pretending you measured 2.480 volts with the multimeter, now you ought to see roughly 248.00 degrees showing on the LCD.

(Side note: this assumes that the zero-offset of the ADC is good. But we're going to take care of offset in the next step. Also, if the power supply voltage changes, such as if your battery gets low or you start drawing more current through the 7805, or you're relying on your computer to provide USB power, you're still in trouble. bretm's comment about using a band gap voltage reference or other external voltage reference is a good one. Let me put it in bold: the accuracy of your measurement is indeed limited by the accuracy (or at the very least consistency) of your voltage reference.)

The second part is to calibrate for your particular LM34's voltage offset. Remove your two resistors and put the LM34 back in place. Take a look at this thread which looks at the accuracy of the LM34s and how much variation you might expect -- real devices have offset which is unavoidable, even if you pay big bucks for the more accurate versions of the sensor. In-system calibration is often unavoidable for something like this. For this, you're just going to need a "reference thermometer", just as Ralph suggests. But if you believe that it's truly X degrees in your room, and the system (post-ADC calibration above) says that it's Y degrees, then you can just add an offset, subtracting the difference.

Hope that helps!

Mike

October 10, 2011
by glendon
glendon's Avatar

Thanks for all your responses.

Mike, I'll give the voltage divider a try soon. But may I know why halving the voltage will give a more accurate reference voltage? Thanks.

October 11, 2011
by bretm
bretm's Avatar

It doesn't make it more accurate, it just moves it away from +5V. If the voltage is approximately 5V it might be slightly over 5V in which case the ADC won't be able to make a valid measurement. If you move it down to 2.5V and it's slightly off, then it's still well within the linear range of the ADC.

October 11, 2011
by glendon
glendon's Avatar

Understood. Thanks a bunch, bretm! :)

October 31, 2011
by thomas0826
thomas0826's Avatar

Good morning Glendon, another thought to help stabilize the voltage woule be to put an electrolytic capacitor (maybe 100uf at 10 volts [watch the polarity]) across the positive and negitave on your breadboard after the regulator, this will absorbe small voltage swings, and using 1% precision resistors also as they very with temperature.

Tom

April 24, 2012
by jlaskowski
jlaskowski's Avatar

Just breaking out my digital multimeter and testing the voltage between the breadboard rails gave me 4910mV. Making this change in the formula gave me a more accurate temperature reading. Of all the suggestions above, this test and code change seems like the easiest way to get a more accurate temp reading.

return sample * (4910 / 1024.0 / 10.0);
June 22, 2012
by HansRoaming
HansRoaming's Avatar

To get rid of some of the noise I converted the code to be interrupt driven, that is to shut down the MCU whilst taking the measurement and having it wake up when the conversion is done.

    // tempsensor.c
    // for NerdKits with ATmega168
    // mrobbins@mit.edu

    #define F_CPU 14745600

    #include <stdio.h>
    #include <math.h>

    #include <avr/io.h>
    #include <avr/interrupt.h>
    #include <avr/pgmspace.h>
    #include <avr/sleep.h>
    #include <inttypes.h>
    #include <util/atomic.h>

    #include "../libnerdkits/delay.h"
    #include "../libnerdkits/lcd.h"
    #include "../libnerdkits/uart.h"

    // Variable to hold the sample as loaded by the ADC complete interrupt
    volatile uint16_t adc_sample = 0;

    // PIN DEFINITIONS:
    //
    // PC0 -- temperature sensor analog input

    void adc_init() {
        // set analogue to digital converter
        // for external reference (5v), single ended input ADC0
        ADMUX = 0;

        // set analogue to digital converter
        // to be enabled, with a clock prescale of /1/128
        // so that the ADC clock runs at 115.2kHz.
        ADCSRA = (1<<ADEN) | (1<<ADPS2) | (1<<ADPS1) | (1<<ADPS0);

        // Set up free running mode
        ADCSRB = 0;
        ADCSRA |= (1<<ADATE);

        // Enable the AD interrupt & global interrupts
        ADCSRA |= (1<<ADIE);
        sei();

        // fire a conversion to get it all going
        ADCSRA |= (1<<ADSC);
    }

    ISR(ADC_vect)
    {
        // Code to be executed when ISR fires

        // read from the ADCL/ADCH registers, and combine the result
        // Note: ADCL must be read first (datasheet pp. 259)
        uint16_t result = ADCL;
        uint16_t temp = ADCH;
        adc_sample = result + (temp<<8);
    }

    double sampleToFahrenheit(uint16_t sample) {
        // conversion ratio in DEGREES / STEP:
        // (5000mV / 1024 STEPS) * (1 degree / 10mV)
        //    ^^^^^^^^^^              ^^^^^^^^^^
        //     from ADC                from LM34

        return sample * (5000.0 / 1024.0 / 10.0);
    }

    double farenheitToCentigrade (double farenheit) {
        // C = F-32 * (5/9)
        return (farenheit - 32.0) * (5.0/9.0);
    }

    int main() {
        // holder variables for temperature data
        uint16_t last_sample = 0;
        double this_temp;
        double temp_avg;
        uint8_t tmp;

        //Set the sleep mode
        set_sleep_mode(SLEEP_MODE_ADC);

        // start up the LCD
        lcd_init();
        FILE lcd_stream = FDEV_SETUP_STREAM(lcd_putchar, 0, _FDEV_SETUP_WRITE);
        lcd_home();

        // start up the Analog to Digital Converter
        adc_init();

        // start up the serial port
        // uart_init();
        // FILE uart_stream = FDEV_SETUP_STREAM(uart_putchar, uart_getchar, _FDEV_SETUP_RW);
        // stdin = stdout = &uart_stream;

        while(1) {
            // take 100 samples and average them
            temp_avg = 0.0;

            for (tmp=0; tmp<100; tmp++) {

                ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
                {
                    last_sample = adc_sample;
                }

                this_temp = sampleToFahrenheit(last_sample);

                // add this contribution to the average
                temp_avg = temp_avg + this_temp/100.0;

                // Go to sleep until the next adc conversion
                sleep_enable();
                sleep_cpu(); // Waits here until the ADC finishes its conversion
                sleep_disable();
                }

            // write message to LCD
            lcd_home();
            lcd_write_string(PSTR("ADC : "));
            lcd_write_int16(last_sample);
            lcd_write_string(PSTR(" : 1024   "));
            lcd_line_two();
            fprintf_P(&lcd_stream, PSTR("Temp: %.2f"), temp_avg);
            lcd_write_data(0xdf);
            lcd_write_string(PSTR("F      "));
            lcd_line_three();
            fprintf_P(&lcd_stream, PSTR("      %.2f"), farenheitToCentigrade(temp_avg));
            lcd_write_data(0xdf);
            lcd_write_string(PSTR("C      "));

            // write message to serial port
            // printf_P(PSTR("%.2f degrees F\r\n"), temp_avg);
        }

        return 0;
    }

Post a Reply

Please log in to post a reply.

Did you know that reading a double floating point variable with scanf requires "%lf" for "long float"? Learn more...