Voltage measurements with ATMEGA8 ADC

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

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

  sbi(ADCSRA, ADPS0);   // divide clock for accurate reading
  sbi(ADCSRA, ADPS1);   // divide clock for accurate reading
  sbi(ADCSRA, ADPS2);   // divide clock for accurate reading

  sbi(ADCSRA, ADEN);    // enable converter
  sbi(ADCSRA, ADSC);    // start conversion
  while ((ADCSRA & 0x40) > 0); // wait until conversion is complete
  uint8_t lowByte = ADCL;
  uint8_t highByte = ADCH;
  cbi(ADCSRA, ADEN);    // disable converter
  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!!

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

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

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

Quote:

cbi(ADCSRA, ADEN); // disable converter

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.

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

theusch wrote:
Quote:

cbi(ADCSRA, ADEN); // disable converter

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!

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

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, ADPS0);   // divide clock for accurate reading
  sbi(ADCSRA, ADPS1);   // divide clock for accurate reading
  sbi(ADCSRA, ADPS2);   // divide clock for accurate reading

  sbi(ADCSRA, ADEN);    // enable converter
  sbi(ADCSRA, ADSC);    // start conversion and ignore first measurement
  while ((ADCSRA & 0x40) > 0); // wait until conversion is complete
  sbi(ADCSRA, ADSC);    // start conversion
  while ((ADCSRA & 0x40) > 0); // wait until conversion is complete
  uint8_t lowByte = ADCL;
  uint8_t highByte = ADCH;
  cbi(ADCSRA, ADEN);    // disable converter
  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

:-(

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

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.

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

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!

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

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!

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

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

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

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, ADPS0);   // divide clock for accurate reading
  sbi(ADCSRA, ADPS1);   // divide clock for accurate reading
  sbi(ADCSRA, ADPS2);   // divide clock for accurate reading

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

uint16_t readAnalag(uint8_t num)
{
  ADMUX &= 0xe0;                // reset MUX bits
  ADMUX |= num;                 // set MUX bits with num

  sbi(ADCSRA, ADSC);    // start conversion
  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! :-(

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

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

  sbi(ADCSRA, ADSC);    // start conversion
  while ((ADCSRA & 0x40) > 0); // wait until conversion is complete
  lowByte = ADCL;
  highByte = ADCH;
  sum += ((highByte << 8) + lowByte);

  sbi(ADCSRA, ADSC);    // start conversion
  while ((ADCSRA & 0x40) > 0); // wait until conversion is complete
  lowByte = ADCL;
  highByte = ADCH;
  sum += ((highByte << 8) + lowByte);

  sbi(ADCSRA, ADSC);    // start conversion
  while ((ADCSRA & 0x40) > 0); // wait until conversion is complete
  lowByte = ADCL;
  highByte = ADCH;
  sum += ((highByte << 8) + lowByte);

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

I now get

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

That's still much fluctuation!

:-(

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

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.

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

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.

// ADC setup
// shift left until you hit a bit named adpsx
// setting all 3 sets prescaler to 128 page 246 (111) or 7 = 128 divider
ADCSRA |= (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0);

// 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
ADMUX |= (1 << ADLAR);

// Set aden
ADCSRA |= (1 << ADEN);

// Start meas on adc0
ADCSRA |= (1 << ADSC);

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
}

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

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.

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

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.

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

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)
   
	ADCSRA = (1<<ADEN | 1<<ADPS2 | 1<<ADPS1 | 1<<ADPS0);
	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
	ADCSRA |= (1<<ADSC);					//Start conversion
	loop_until_bit_is_set(ADCSRA, ADIF);	//Wait for conversion complete
	ADCSRA |= (1<<ADIF);					//Clear conversion flag
	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

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

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?

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

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.

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

Hi all,

thanks a lot for your responses, especially for the voltage divider resistor values recommendation. Since iteration (mutipl reads -> average) did not improve my results, I discarded this approach. I finally soldered 22nF caps directly on the pins of the Arduin board. This fixed the problem for me. I get steady readings now. However, I calculated that it takes 20ms do discharge this cap through my 500K lower resistor (100V measurement) and most likely much longer to charge it through the upper resistors, so that's far form being an optimal solution. I am using these high resistors since I need to measure the voltage in caps (2-3 times per second) and want the leakage to be as low as possible.

It seems I need to find a compromise between quick readings and low measurement distortion. I will have to experiment some more but I at least know what knobs to turn now. Thanks for the OP-amp suggestion. I might give that a try as well. Yes, I am using a MAX232.

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

Buffer with a decent single supply op amp like the mcp6022 and then use a low resistor like 1k on the input to the avr. The low pass filter cap can be somewhere arount 0.47uF to 1uF. With proper reference on the inverting input and proper resistors scaling the sampled volage you will be able to minimize offsets and get just the range and gain that you want.

-Tony