328p: trouble with reading internal 1.1v reference

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

well i'm using an UNO (atmega328p) and trying to 1- read the internal 1.1v reference and then 2- derive a rough value for Vcc (battery voltage) from that reading. i know that the 328 has 10-bit ADC, so if i point the ADC channel to the 1.1v reference, then-

1.1/Vcc = ADC reading/1023, which means that:

Vcc = 1.1 * 1023 / reading.

the code below is supposed to work but i keep getting Vcc = 556mV

here's the arduino code:

 long readVcc() {
  // Read 1.1V reference against AVcc
  // set the reference to Vcc and the measurement to the internal 1.1V reference
  ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
  delay(2); // Wait for Vref to settle
  ADCSRA |= _BV(ADSC); // Start conversion
  while (bit_is_set(ADCSRA,ADSC)); // measuring
  uint8_t low  = ADCL; // must read ADCL first - it then locks ADCH
  uint8_t high = ADCH; // unlocks both
  long result = (high<<8) | low;
  result = 1125300L / result; // Calculate Vcc (in mV); 1125300 = 1.1*1023*1000

  return result; // Vcc in millivolts
}

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

void loop() {
  delay(20);
  long v = readVcc;
  Serial.print("Vcc = ");
 Serial.print(v);
 Serial.println("mV");
}

can anybody see what's wrong here?

 

thanx, mike

This topic has a solution.
Last Edited: Sun. Oct 21, 2018 - 08:38 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

What are you using for the  ADC reference while measuring Vref? Are you using Vcc? Looks like you are. If so, your proportion is incorrect. In that case you should get:

 

Vref = Vcc * (N/1023)

 

where N is the value returned from the ADC register, right-justified.

 

Also, read ADC as a single operation. avr-libc will manage the read order and construct the 10 bit value packed into a 16-bit variable. MUCH less chance for error. Just

 

result = ADC;

 

will do it.

 

Jim

Jim Wagner Oregon Research Electronics, Consulting Div. Tangent, OR, USA http://www.orelectronics.net

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

well i get confused by the terminology. according to the datasheet, "Table 28-3. ADC Voltage Reference Selection" it says i'm supposed to do

ADMUX = _BV(REFS0)

so that the ADC voltage reference will be set to "AVCC with external capacitor at AREF pin"

so then i put 100nf between the AREF pin and G.

and in Table 32-11. ADC Characteristics, it says that "VCC - 0.3V < AVCC < VCC + 0.3V"

as for the equation you mention, in my code it says

  uint8_t low  = ADCL; // must read ADCL first - it then locks ADCH  
  uint8_t high = ADCH; // unlocks both
  long result = (high<<8) | low;
  result = 1125300L / result; // <--- this is the same as saying "Vcc(mV) = 1.1*1023*1000/ADC reading"

since Vref = 1.1, it should be the right number for Vcc.

 

are you saying that i can replace this:

  uint8_t low  = ADCL; // must read ADCL first - it then locks ADCH  
  uint8_t high = ADCH; // unlocks both
  long result = (high<<8) | low;
with this:

  int result = ADC

it means the same thing?

 

much obliged, mike

 

edit 12:05-

ok i tried that substitution result = ADC and yeah it works but still i'm getting Vcc = 556mVangry

 

Last Edited: Sun. Oct 21, 2018 - 07:07 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

You confuse Vref with  "ADC reference". Vref refers to the voltage generated internally that CAN be used as the ADC reference. Then the conversion between input voltage and ADC reading (your "result") is N = (Vin/ADC Reference) * 1023. When you choose Vcc as the ADC reference, then you have N = (Vin/3/3) * 1023. If you choose the internal reference as the ADC reference, then you have N = (Vin/1.1) * 1023, remembering that any Vin that is greater than ADC reference reads 1023. 

 

Second paragraph is correct. 

 

In first paragraph, writing result = 1125300L / result; is nonsense because this is exactly the same as (result)(result) = 1125300L and that would be a constant value, result then being the square root of 1125300L. 

 

Again, your confusion is between Vref and ADC reference. With the reference source you chose, ADC reference IS Vcc, not Vref.

 

Jim

 

Jim Wagner Oregon Research Electronics, Consulting Div. Tangent, OR, USA http://www.orelectronics.net

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

Vcc = 1.1 * 1023 / reading.   &  Vref = Vcc * (N/1023)  say the same thing (assuming Vref is 1.1)  ..so at least you are starting off on the right foot.

 

Did you try tossing out your first reading (often junk)?

When in the dark remember-the future looks brighter than ever.

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

In first paragraph, writing result = 1125300L / result; is nonsense because this is exactly the same as (result)(result) = 1125300L

I'm not sure I'd agree (or I'm missing something) mathwise, it is true; however this is an assignment, like:     var=result

 

cat=50;

frog=100/cat;

cat=100/cat;

 

now cat will have the value 2, and so does frog

When in the dark remember-the future looks brighter than ever.

This reply has been marked as the solution. 
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Try actually calling your read function:

  long v = readVcc;

Should be

  long v = readVcc();

 

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

From the code style this looks like an 20 year old example. Where does it come from?

The "bit_is_set( )" macro is such an extremely old left over, and for some decades GCC is smart enough to know how to access 16 bit registers, so you can just read ADC into a 16 bit variable.

 

Apart from that.

Divide your problem into smaller tasks.

Remove all calculations from your code and spit out the raw ADC values and decide if those make sense.

Then add a calculation to transform the raw ADC value into something more usefull.

Paul van der Hoeven.
Bunch of old projects with AVR's:
http://www.hoevendesign.com

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

SYNTAX ERROR!!

that was it:

long v = readVcc;

now i'm getting a stream of 3 alternating values: Vcc = 4748, 4768, 4788

which is not surprising, givin the error margin of the 1.1v reference value and the crap-ass voltage regulation in the computer's usb port.

my voltmeter is actually reading Vcc as 4.64

 

so you were all right. i was confusing the definition of Vref as ADC measurement reference with Vref as built-in 1.1v voltage source. and yeah i was using antiquated syntax and functions. i got the code here from a very old post by Scott Daniels. and the 1st reading does have to be disgarded. so now my code goes like this:

 

int readVcc() {  // Read 1.1V reference against AVcc
  ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
  ADCSRA |= _BV(ADSC); // Start conversion
  while (bit_is_set(ADCSRA,ADSC)); // measuring
  int result = ADC;
  result = 1125300L / result; // Calculate Vcc (in mV); 1125300 = 1.1*1023*1000
  return result; // Vcc in millivolts
}

 

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

 

void loop() {
  delay(20);
  int v = readVcc();
  Serial.print("     Vcc = ");
  Serial.println(v);

}

 

so what's the modern way to say "while (bit_is_set(ADCSRA,ADSC));" ?

Last Edited: Sun. Oct 21, 2018 - 08:51 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I don't know how accurate your DMM is. 2% is typical.
Likewise the Bandgap has a tolerance too.
You appear to be getting ADCW values of 237, 236, 235. The ADC is clearly 236 +- 1
.
In practice you monitor a battery voltage to predict when it needs changing. Or to determine whether you are running at 3.3V or 5V.
You don't need absolute accuracy.
.
David.

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

well i'll find a better DMM and just calibrate the "1.1" to improve the accuracy. actually this is for measuring the voltage of usb power supplies under different levels of load, and no i don't need more than 20mV accuracy. i just wish i could find the source of the noise. i think i'll just collect a stream of raw ADC #s and FFT* them with excel and see if i can figure anything out.

i'm gonna set the serial to 115200 and just do-

 

void loop() {
  ADCSRA |= _BV(ADSC); // Start conversion
  while (bit_is_set(ADCSRA,ADSC)); // measuring
  Serial.print(",");
  Serial.println(ADC);

}

does anyone know how what kind of frequency i can expect to get this way?

 

thanx you're right,

mike

*fast fourier transform

Last Edited: Sun. Oct 21, 2018 - 11:21 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I don't see where you've set the ADPS bits (which are in ADCSRA).

The datasheet explains how to set these for the best accuracy.

 

--Mike

 

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

Do something like reading=  (7*reading+newreading)/8 & keep repeating...it will form a moving exponential average & reduce noise...this is saying the latest value is composed of  7/8 of the oldvalue + 1/8 of the most recent reading.

 

Also as suggested, slow down the adc clock (set divider)  for somewhat better results.  

 

The common AVR 1.1V ref isn't accurate (+/-10% !!!), but it is very steady.

When in the dark remember-the future looks brighter than ever.

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

well i can get a voltmeter and calibrate the "1.1"

and now i put a 2.2uF and a 220nF across Vcc and ground with no difference in the #s. so then the noise must be inside the 328.

ok i'll do the math. 16MHz means a period of 62ns, and the manual says that for 10bit ADC, the ADC clock should be no faster than 200kHz, which means a period of 5us. so 80 cpu cycles is 5us and the manual says that if ADPS[2:0] is 111 then the ADC clock = cpu clock / 128. and that means an ADC period of 8us.

so if i do-

 

  ADCSRA |= _BV(ADPS2) | _BV(ADPS1) | _BV(ADPS0);

 

then each ADC reading has ample time to resolve 10 bits.

and that's a trick i've never heard of. so i do-

 

  ADCSRA |= _BV(ADSC); // Start conversion
  V7 = V * 7;
  while (bit_is_set(ADCSRA,ADSC)); // measuring
  V = (V7 + ADC) / 8;

 

correct?

Last Edited: Mon. Oct 22, 2018 - 02:03 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

  ADCSRA |= _BV(ADSC); // Start conversion
  V7 = V * 7;
  while (bit_is_set(ADCSRA,ADSC)); // measuring
  V = (V7 + ADC) / 8;

I suppose that's fine...why not keep it simple  (no need for an extra variable, v7):

 

answer=  (7*answer + adc_reading)/8 

When in the dark remember-the future looks brighter than ever.

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

well i'm dividing your equation into 2 parts because the ADC conversion takes about 100us. i can start the conversion, then have it do some of the computation while it's waiting for the reading to be available. i'm figuring that it will take less then 100us to do the V*7.