## Voltage measurements with ATMEGA8 ADC

20 posts / 0 new
Author
Message

Hi all,

I need to measure voltages between 0..12V and 0..100V with an ATMEGA8. Since the ATMEGA8 can only measure between 0..5V I am downscaling with corresponding voltage dividers. In one case I have 560k and 270k in series, in the other case 10M and 470k. These should (theoretically scale down to 5V..0V in both cases. I have a 5.1V zener diode over the lower resistors to make sure the voltage never exceeds 5.1V.

I have implemented the following code:

```uint16_t readAnalag(uint8_t num)
{
ADMUX = num;          // select channel
sbi(ADMUX, REFS0);    // use Vcc as reference voltage

while ((ADCSRA & 0x40) > 0); // wait until conversion is complete
uint8_t lowByte = ADCL;
uint8_t highByte = ADCH;
return ((highByte << 8) + lowByte);
}
```

which seems to return a resonable value at the first glance. At the second glance these values make no sense. At least I have to linear relation between the voltage to be measured and the returned integer of this funtion. I must be doing something wrong.

How high is the resistance of the ATMEGA8? Is the internal resistance of the ATMEGA8 influencing the measurement? I am using the PINs PC0 (ADC0) and PC1 (ADC1). Do I have to program these PINs anyhow first via

`        cbi(DDRC,PC0);`

or the like? I haven't done anything like that. Actually I am only doing

```        DDRB = _BV (PB5);               /* digital output */
DDRB = _BV (PB4);               /* digital output */
DDRD = _BV (PD7);               /* digital output */```

for my digital outputs.

Hints greatly appreciated!!

Quote:
theoretically scale down to 5V..0V in both cases....I have a 5.1V zener diode over the lower resistors
Whith such high resistor values the zener's leakage current will be far too high. Remove them and try again.

Then, if it works, let's see how we can come up with some protection for the inputs.

John Samperi

Ampertronics Pty. Ltd.

www.ampertronics.com.au

* Electronic Design * Custom Products * Contract Assembly

Quote:

What does the datasheet say about using the result of the first conversion? "Discard", in short terms.

You haven't said what results you are getting, that are confusing.

Quote:

How high is the resistance of the ATMEGA8?

What does it say in the datasheet? Something about 100 meg?

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.

theusch wrote:
Quote:

What does the datasheet say about using the result of the first conversion? "Discard", in short terms.

You haven't said what results you are getting, that are confusing.

Quote:

How high is the resistance of the ATMEGA8?

What does it say in the datasheet? Something about 100 meg?

I am getting the following values:

5V -> 334
20V -> 888
30V -> 932
40V -> 952
50V -> 963
60V -> 971

That is not even close to linear!? I will remove the zener diodes and try again!

theusch wrote:

What does the datasheet say about using the result of the first conversion? "Discard", in short terms.

I have modified my code as follows to discard the first value:

```uint16_t readAnalag(uint8_t num)
{
ADMUX = num;          // select channel
sbi(ADMUX, REFS0);    // use Vcc as reference voltage

sbi(ADCSRA, ADSC);    // start conversion and ignore first measurement
while ((ADCSRA & 0x40) > 0); // wait until conversion is complete
while ((ADCSRA & 0x40) > 0); // wait until conversion is complete
uint8_t lowByte = ADCL;
uint8_t highByte = ADCH;
return ((highByte << 8) + lowByte);
}

```

I have 20V on the voltage divider and 0.42V on PC1. I executed a bunch of readAnalag(1) calls and get a totally different result every time.

V1=18
V1=73
V1=84
V1=100
V1=20

:-(

Just say No! to disabling the A/D converter each time. Please?

As you don't have much drive on your inputs, you may have to read the same channel a number of times consecutively to get a good reading, as the S/H takes a while to charge up.

Try you second test again, with the A/D enabled at astartup and continuing to be enabled.

Do you have a cap on the 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.

I have replaced the zener diode with a 1nF cap to stablize the voltage on ADC1. No big improvement. I still get

96
97
112
96
...

:-(

Any more ideas? Thanks a lot!

theusch wrote:
Just say No! to disabling the A/D converter each time. Please?

As you don't have much drive on your inputs, you may have to read the same channel a number of times consecutively to get a good reading, as the S/H takes a while to charge up.

Try you second test again, with the A/D enabled at astartup and continuing to be enabled.

I will give this a try!

theusch wrote:

Do you have a cap on the AREF pin?

100nF!

I usually do 10 reads and average them. The large resistor values will most likely create some problems. The cap will lower the impedance seen by the ADC.

May want to slow down the ADC clock as low as possible too. (you seem to be doing that anyway)

John Samperi

Ampertronics Pty. Ltd.

www.ampertronics.com.au

* Electronic Design * Custom Products * Contract Assembly

Hi theusch,

theusch wrote:
Just say No! to disabling the A/D converter each time. Please?

As you don't have much drive on your inputs, you may have to read the same channel a number of times consecutively to get a good reading, as the S/H takes a while to charge up.

Try you second test again, with the A/D enabled at astartup and continuing to be enabled.

Do you have a cap on the AREF pin?

I now have

```void initADC()
{
ADMUX = 0;          // select channel
sbi(ADMUX, REFS0);    // use Vcc as reference voltage

sbi(ADCSRA, ADSC);    // start conversion and ignore first measurement
while ((ADCSRA & 0x40) > 0); // wait until conversion is complete
}

{
ADMUX &= 0xe0;                // reset MUX bits
ADMUX |= num;                 // set MUX bits with num

while ((ADCSRA & 0x40) > 0); // wait until conversion is complete
uint8_t lowByte = ADCL;
uint8_t highByte = ADCH;
return ((highByte << 8) + lowByte);
}
```

and am calling initADC() once at program start. I get no better results!

V1=88
V1=74
V1=88
V1=79
V1=96

The same for ADC0! :-(

js wrote:
I usually do 10 reads and average them. The large resistor values will most likely create some problems. The cap will lower the impedance seen by the ADC.

May want to slow down the ADC clock as low as possible too. (you seem to be doing that anyway)

Yes, I already use the slowest speed. Lowering the impedance with the cap is a good thing or not? The 1nF does not seem to make a big difference. I don't want to add too much capacity otherwise changes will probably be reflected too slowly.

You do 10 reads? I just tried to to 4 with

```uint16_t readAnalag(uint8_t num)
{
uint16_t sum = 0;
uint8_t lowByte, highByte;

ADMUX &= 0xe0;                // reset MUX bits
ADMUX |= num;                 // set MUX bits with num

while ((ADCSRA & 0x40) > 0); // wait until conversion is complete
sum += ((highByte << 8) + lowByte);

while ((ADCSRA & 0x40) > 0); // wait until conversion is complete
sum += ((highByte << 8) + lowByte);

while ((ADCSRA & 0x40) > 0); // wait until conversion is complete
sum += ((highByte << 8) + lowByte);

while ((ADCSRA & 0x40) > 0); // wait until conversion is complete
sum += ((highByte << 8) + lowByte);

return (sum >> 2);
}```

I now get

V1=82
V1=85
V1=88
V1=81
...

That's still much fluctuation!

:-(

Put op-amps between the voltage dividers and the AVR inputs. The cheap quad op-amp LM324 works with +5V and ground on its VCC and VEE power inputs. (I'm a moron for actually suggesting using a LM324? So shoot me! It's cheap, widely available, easy-to-get at Radio Shack, and works at low voltages.) Use the op-amp in a non-inverting buffer configuration by connecting the output directly to the inverting input and the voltage from the divider resistors to the non-inverting input. This will buffer the signal from the voltage-divider resistors before the voltage level gets digitized by the AVR's ADC.

When you tap off the mid-point of the two resistors and feed it into the AVR's ADC, the ADC acts as a resistor in parallel with the bottom resistor of the divider. If the bottom resistor is 50K or more, then the resistance of the AVR's ADC input will distort the ADC reading. Using an op-amp buffer will fix this problem.

If you have an RS232 interface connected to the AVR's USART and this RS232 uses a MAX232 chip, then you can use the +9 and -9 voltages that are created by the MAX232 to power a few op-amps. If you use the AVR's VCC and ground to power cheap op-amps like the LM324, you often can't get the full range (the 'rail-to-rail' readings) of ADC values. It won't read below 0x025 (or so) and above 0x3c0 (for example) on the AVR ADC range of 0x000 to 0x3ff (1024 ADC levels_10 bit conversions). Using the +9/-9 volts from the MAX232 chip to power the op-amp buffers will give you the full range of ADC values.

Here is the code below I used on the mega168, some of it was taken from a tutorial on avrfreaks. I am using it for a vacuum pressure data logger. The output of a transducer is 4-20ma that I put across a resistor to make 0-4'ish volts.

When i setup the system I correlated my measurements to a fluke dmm I had. I seem to remember +/- 1 count fluctuation max. +/- 1 count is within the margin of error of my transducer.

// shift left until you hit a bit named adpsx
// setting all 3 sets prescaler to 128 page 246 (111) or 7 = 128 divider

// set ref to avcc
ADMUX |= (1 << REFS0);

// set free running mode
ADCSRA |= (1 << ADATE); // was adfr on other chips? where is adfr defined

//set to 8 bit meas

// Start meas on adc0

for( ;; ) // Loop Forever
{
if(ADCH < 128) // pin 23 of micro
{

PORTB |= (1 << 2); // Turn on LED1
PORTB &= ~(1 << 1); // Turn off LED2
}

else
{

PORTB &= ~(1 << 2); // Turn off LED1
PORTB |= (1 << 1); // Turn on LED2
}

One problem using buffering opamps is the offset that introduce in the signal,a typical value for LM324 is
+- 2mv up to +-7mv.This voltage added or subtracted from the divided voltage in the input of the opamp leading in error of about one bit.Depending the division factor the program multiplies then the reading of the ADC with the same factor leading in even greater error readings,much more than one bit.I would propose if is neccesary to use an opamp with offset trimming for best accuracy like TL081 or OP07.
For readings up to 12V divide input by 3,using for example 3 x 10K and for 100V divide by 22 using 180K+10K+10K+10K and 10K.

Quote:

That's still much fluctuation!

Quote:

js wrote:
I usually do 10 reads and average them. The large resistor values will most likely create some problems. The cap will lower the impedance seen by the ADC.

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.

As a matter of interest what compiler are you using? You seem to do things the hard way with all those sbi and the way you read the registers. ie this happens during init:

```// enable ADC, select ADC clock = F_CPU / 16 (i.e. 115.2 kHz)

DIDR0 = (1<<ADC1D | 1<<ADC0D);				//Disable digital buffers on input 1 and 2```

and does not change. This is the voltage reading function

```void read_supply_voltage (void)
{
volatile uint8_t a;
volatile uint16_t b=0;

for (a=0; a < 10; a++)
{
ADMUX=(1<<REFS0 | 1<<ADLAR | ADC_1);	//Conversion on channel 1, AVCC reference,  8 bit mode
b=b+ADCH;								//Get last conversion result, top 8 bits only.
_delay_ms(1);
}

supply_voltage=(b/10)-4;				//Get average and subtract diode drop and resistor error
}```

it can be done a bit shorter if you use ADSC to find when the conversion is complete instead of ADIF.

I see you are NOT clearing ADIF between conversions?? ADIF by the way it's easier to understand than 0x40.

John Samperi

Ampertronics Pty. Ltd.

www.ampertronics.com.au

* Electronic Design * Custom Products * Contract Assembly

According to the datasheet ADC is optimized for analog signals with an output impedance of approximately 10 kOhm or less.

I would either less the resistor values in divider
or put a greater capacitor on ADC input (say 100nF).

How often the voltage is to be read? Every second, or milisecond?

Also:

-- During sanity check, read all the ADC count bits, and report in ADC counts. It has happened in the past that posters had problems in the "filter" rather than the A/D.
-- Are you absolutely positively sure that there is >>no<< ripple on your input signal or AVcc or AREF? Even on our "good layout" industrial apps there is often some ripple; often on the control signal being measured which comes from elsewhere. Do an average of a number of conversions, repeatedly done. Do those averages vary as much as your individual readings? I suspect not; and there is some ripple somewhere.

-- We almost always have series R to the "world" and a small cap just before the pin. Even on higher-impedance signals such as thermistors I get steady average signals.

Lee

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.