Solved: inaccurate conversion of LM35 output with atmega8

Go To Last Post
19 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi, I am interfacing lm35 with atmega8A but somehow I am getting wrong values.

When I measure LM35 output I get 231 mV which means 23.1 degrees. But my conversion somehow gets 21.X degrees (when I input 4.5 V into the circuit). Interetingly, when I tried to increase voltage to 5.5V, my conversion suddenly showed 19.X degrees.

I chose the reference voltage 2.56 in order it to be constant. why does the adc conversion doesn't work correctly? And why does it change depending on voltage?

Here is the diagram and the code. I have left out the LED display as it is not important, it is standard 3 digit anode led, which was originally not multiplexed, but I have done that with my wires as I dont have 28 pins to spare lol.

 

#ifndef F_CPU
#define F_CPU 1000000UL
#endif // F_CPU

#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>

#define DELAY_IN_MS 500 /* 0.5 sec */
#define NUM_OF_MEASUREMENTS 100
#define NUM_DISPLAYS 3

int numbers[] = {
	0b01000000,
	0b01110011,
	0b00100100,
	0b00100001,
	0b00010011,
	0b00001001,
	0b00001000,
	0b01100011,
	0b00000000,
	0b00000001,
	0b11111111 // off
};

int display = 0;
int measuring = 1;
uint8_t digits[NUM_DISPLAYS];
uint16_t adc_values[NUM_OF_MEASUREMENTS];

void initADC()
{
    // 	Internal 2.56 Voltage Reference
	ADMUX=(1<<REFS1)|(1<<REFS0);
	// Prescaler 62.5 kHz
	ADCSRA=(1<<ADEN)|(1<<ADPS2);
}

uint16_t ReadADC(uint8_t ch)
{
	//Select ADC Channel ch must be 0-7
	ch=ch&0b00000111;
	ADMUX|=ch;

	//Start Single conversion

	ADCSRA|=(1<<ADSC);

	//Wait for conversion to complete
	while(!(ADCSRA & (1<<ADIF)));

	//Clear ADIF by writing one to it
	ADCSRA|=(1<<ADIF);

	return(ADC); // ten times to get decimal, division by 4 because of degree ratio
}

uint16_t readDegrees()
{
    // i wanna multiply by ten to get all digits eg. 23.1 deg => 231
	return (ReadADC(0)*10)/4;
}

void initTimer0()
{
	  // Prescaler = FCPU/64
	  TCCR0|=(1<<CS01);

	  //Enable Overflow Interrupt Enable
	  TIMSK|=(1<<TOIE0);

	  //Initialize Counter
	  TCNT0=0;
}

int adc_read_cycle_index = 0;
uint32_t res;
ISR(TIMER0_OVF_vect)
{
    // 4 steps: 0-3 display, 4 conversion
	if(display == NUM_DISPLAYS) {
		PORTD = numbers[10]; // display off
		adc_values[adc_read_cycle_index] = readDegrees();

		if(adc_read_cycle_index%2 == 0) { // update digits only once per 8 cycles
			res = 0;
			for(int i = 0; i < NUM_OF_MEASUREMENTS; i++) {
				res += adc_values[i];
			}

			res /= NUM_OF_MEASUREMENTS;
			for(int j = 2; j >= 0; j--) {
			    // following if says to update digits not so often
			    // this is just temporary workaround to avoid annoying
			    // fast digits changes after decimal point
				if(adc_read_cycle_index%50 == 0) {
					digits[j] = res%10;
				}
				res = res / 10;
			}
		}

		if(adc_read_cycle_index + 1 == NUM_OF_MEASUREMENTS) {
			adc_read_cycle_index = 0;
		} else {
			adc_read_cycle_index++;
		}

		display = 0;
	} else {
		PORTB &= 0b11111000;
		PORTB |= (1<<display);
		PORTD = numbers[digits[display]];
		display++;
	}
}

int main()
{

	initADC();
	for(int i = 0; i < NUM_OF_MEASUREMENTS; i++) {
		adc_values[i] = readDegrees();
	}

    DDRD = 0xFF;
	PORTD = 0;

	DDRB |= 0b00000111;
	PORTB |= 1;

	initTimer0();

	//Enable Global Interrupts
	sei();

	// I am aware I could do the conversion here
	// but I have put it in the interrupt to avoid
	// having display on when converting
    while(1);

    return 0;
}

 

Last Edited: Sat. Feb 3, 2018 - 04:50 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Increase wrote:
But my conversion somehow gets 21.X degrees (when I input 4.5 V into the circuit). Interetingly, when I tried to increase voltage to 5.5V, my conversion suddenly showed 19.X degrees.

???  What are 4.5V and 5.5V?  The Vcc level, which is nominally 5V?  And your LM35 output changes when Vcc level changes?  In other words, the voltage seen at ADC0, right on the pin, changes from 231mV to something else?  That is then a problem with your LM35 then, isn't it?

 

Anyway, remember that the 2.56V internal reference is only nominally that; it can be off quite a bit.  Have you measured it?

 

 

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I see no capacitor on the AREF pin.

11 Internal 2.56V Voltage Reference with external capacitor at AREF pin

 

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

readDegrees is called twice; once from main(), once from ISR .

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

???  What are 4.5V and 5.5V?  The Vcc level, which is nominally 5V?

Vcc yes

And your LM35 output changes when Vcc level changes?

No, my LM35 output is still the same, but the result of the conversion changes.

In other words, the voltage seen at ADC0, right on the pin, changes from 231mV to something else?

No.

Anyway, remember that the 2.56V internal reference is only nominally that; it can be off quite a bit.  Have you measured it?

Sorry, I am not sure what this means. i am quite new to all this. Can you direct me to some article about it? If I put voltmeter between AREF and ground I read approximately 2.56 (+-20mV)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

dbrion0606 wrote:

readDegrees is called twice; once from main(), once from ISR .

And that is correct. When I start the program, it first setup adc, than fill in first 100 measurements to variable (this happens only once at the beginning) and only then it setup timer and interruption.

Last Edited: Fri. Feb 2, 2018 - 02:13 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

You should not call any functions from interruptions (fuction call/return eat cycles). Onlining will hide this behavior.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

@theusch said:

Anyway, remember that the 2.56V internal reference is only nominally that; it can be off quite a bit.  Have you measured it?

From the ATmega8 datasheet:

 

 

Changing the supply voltage from 4.5 to 5.5, a 22% change, is bound to change Aref.  You should have a 100nF cap between Aref and GND. 

Greg Muth

Portland, OR, US

Xplained/Pro/Mini Boards mostly

 

Make Xmega Great Again!

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Greg_Muth wrote:
You should have a 100nF cap between Aref and GND.

Agreed.  And maybe causing some of OP's symptoms?

 

Greg_Muth wrote:
Changing the supply voltage from 4.5 to 5.5, a 22% change, is bound to change Aref.

I'd have to do some experimenting to comment on that.

 

Quite a bit going on in that "simple" program.  The root cause could be the lack of capacitor on AREF, and the ADC doesn't have enough ref to "sip from".  But all a bit puzzling IME with AVR8 ADCs over the years.  As '88 family became mainstream over a decade ago I haven't done '8 or '8A since, so that model might well have idiosyncrasies.

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

In passing a couple of things:

uint16_t ReadADC(uint8_t ch)
{
	//Select ADC Channel ch must be 0-7
	ch=ch&0b00000111;
	ADMUX|=ch;

	//Start Single conversion

	ADCSRA|=(1<<ADSC);

	//Wait for conversion to complete
	while(!(ADCSRA & (1<<ADIF)));

	//Clear ADIF by writing one to it
	ADCSRA|=(1<<ADIF);

	return(ADC); // ten times to get decimal, division by 4 because of degree ratio
}

is probably simpler/easier as:

uint16_t ReadADC(uint8_t ch)
{
	//Select ADC Channel ch must be 0-7
	ch=ch&0b00000111;
	ADMUX|=ch;

	//Start Single conversion

	ADCSRA|=(1<<ADSC);

	//Wait for conversion to complete
	while(ADCSRA & (1<<ADSC));

	return(ADC); // ten times to get decimal, division by 4 because of degree ratio
}

That waits on ADSC remaining at 1 during the conversion. Now you don't have to involve ADIF at all. But also:

	return(ADC); // ten times to get decimal, division by 4 because of degree ratio

The comment on that line is mis-placed. It is actually a description of what's happening here:

	return (ReadADC(0)*10)/4;

which is where the *10 and /4 are actually being done.

 

When you ultimately split the ReadADC() stuff into a separate adc.c then it's important the comment is at the point of usage - not in the "library" code.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Greg_Muth wrote:

@theusch said:

Anyway, remember that the 2.56V internal reference is only nominally that; it can be off quite a bit.  Have you measured it?

From the ATmega8 datasheet:

 

 

Changing the supply voltage from 4.5 to 5.5, a 22% change, is bound to change Aref.  You should have a 100nF cap between Aref and GND. 

 

I have added it, thanks, it seems to work now.

Thanks man.

The strange thing happens now (not sure if it was happening before though) I will send it in the next post.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Ok now it works.

Funny thing, it goes crazy when I "cuddle" the wires (sorry dont know the proper name for it :D) - by cuddle i mean i touch the wires, anything that causes them to move (not the ends though).

I guess that has something to do with induction or something?
Can I be sure that when I solder this circuit onto a board, it will not have this issue?
Look at the nest I created:

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks man, I have used your enhancement. And thanks for pointing out the misplaced comment ;)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

As has been pointed out, in the analog world, things are not as absolute as one would like.   You must account for and provide for variances in your readings, such as offsets and slope differences.

The LM35 and its Fahrenheit twin LM34 do a pretty good job of reporting the temperature, but you must take into account an variances in your chosen reference in the ADC.

Add a 100nf cap to the AREF pin to ground, with the 2.56v internal ref.  In the commercial designs with that sensor, we placed a low pass filter (10k, 10uf) cap on the input of the ADC to filter any noise as we expected the temperature to change fairly slowly over time. 

In the software, we provided for an offset for initial calibration to room temperature and depending on the applications needs, a way to adjust the slope of the temperature curve to keep the readings with in the required precision range.   Note: the slope is different for temperature rising from single calibration point, from that of falling from cal point. 

 

Have fun with your project.

 

Jim

 

Click Link: Get Free Stock: Retire early! PM for strategy

share.robinhood.com/jamesc3274

 

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

As the ush wrote, you should use 2*100 nf decoupling capacitors (I only see one)

When you solder, things are likely to be much better than broad beards.

dis you look at  (google searched "solderable breadboard" + my favorite HW seller -in Paris-)

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks to all for your tips, it works nicely now.

 

PS ill check that solderable breadboard, looks good..

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Funny thing, it goes crazy when I "cuddle" the wires (sorry dont know the proper name for it :D) - by cuddle i mean i touch the wires, anything that causes them to move (not the ends though).

I guess that has something to do with induction or something?

You have 10 antennae (7 segments + 3 digits) transmitting EMI at the rate the display is multiplexed.  When you touch them you become a significantly larger antenna.  When laying the circuit out on a PCB, keep the analog and digital portions separate.  Use an L/C or R/C filter between the digital VCC and AVCC.  Power the LM35 from the filtered AVCC.

Greg Muth

Portland, OR, US

Xplained/Pro/Mini Boards mostly

 

Make Xmega Great Again!

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Greg_Muth wrote:

Funny thing, it goes crazy when I "cuddle" the wires (sorry dont know the proper name for it :D) - by cuddle i mean i touch the wires, anything that causes them to move (not the ends though).

I guess that has something to do with induction or something?

You have 10 antennae (7 segments + 3 digits) transmitting EMI at the rate the display is multiplexed.  When you touch them you become a significantly larger antenna.  When laying the circuit out on a PCB, keep the analog and digital portions separate.  Use an L/C or R/C filter between the digital VCC and AVCC.  Power the LM35 from the filtered AVCC.

 

Thank man, can you link some of the filters you would deem fit for it? Just to be sure :) btw I have updated my stuff on the bread boards just to see it gets better and it did indeed.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

For an L/C filter, I use the unscientific method of grabbing what I have in my parts drawer, which happens to be a bunch of 10uH RF chokes.  I put one of those between VCC and AVCC, with a 100nF between AVCC and the adjacent GND pin.  I've never done the math to determine if these values are reasonable.

Greg Muth

Portland, OR, US

Xplained/Pro/Mini Boards mostly

 

Make Xmega Great Again!