latest emonlib.cpp voltage measurement

Edited after reply from Robert...

Using the latest emonlib.cpp - the voltage reference gives this when used with the 2560

I haven't run it on the 32U4 yet or the 1280 (as I don't have one of these).

Using this test program I am getting 4752mV instead of 1100mV

See after this program, where I substituted ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);

for ADMUX = _BV(REFS0) | _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);

  ADCSRB &= ~_BV(MUX5); (This line is fine as I have run the program with MUX5)

 

The test program follows...

long readVcc(){
  long int result;

  #if defined(__AVR_ATmega168__) || defined(__AVR_ATmega328__) || defined (__AVR_ATmega328P__)
  ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1); 
  #elif defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
  ADMUX = _BV(REFS0) | _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
  ADCSRB &= ~_BV(MUX5);  
// Without this the function always returns -1 on the ATmega2560 http://openenergymonitor.org/emon/node/2253#comment-11432
  #elif defined (__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__)
  ADMUX = _BV(MUX5) | _BV(MUX0);
  #elif defined (__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__)
  ADMUX = _BV(MUX3) | _BV(MUX2);
  #endif

  delay(2);     // Wait for Vref to settle
  ADCSRA |= _BV(ADSC);    // Convert
  while (bit_is_set(ADCSRA,ADSC));
  result = ADCL;
  result |= ADCH<<8;
  result = 1126400L / result;   //1100mV*1024 ADC steps http://openenergymonitor.org/emon/node/1186
  return result;

}

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

void loop()
{
Serial.println(readVcc(),DEC);
  delay(1000);
 
}

Serial Monitor data

4752
4772
4752
4752
4752
4752
4752

____________________________________________

 

Substituting ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);

for  ADMUX = _BV(REFS0) | _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);

  ADCSRB &= ~_BV(MUX5);   // Without this the function always returns -1 on the ATmega2560

gave this data below.

Serial data direct from serial monitor

1102
1108
1104
1105
1104
1101
1107
1107
1106
1106
1106

 

And here is the serial monitor data after removing ONLY  _BV(MUX4)     from ADMUX

1101
1103
1106
1106
1105
1103
1105
1105
1107
1106
1103

Robert Wall's picture

Re: latest emonlib.cpp voltage measurement

Do I understand you correctly, and do you think the method should read and return the internal voltage reference itself, i.e. 1.1 V ?

If so, I'm afraid you're wrong. The code changes the ADC's reference to the internal band-gap reference, then uses that to measure the voltage of the ADC's normal reference voltage AVcc. It is that voltage that you are reading as 4752 mV.

The band-gap reference is stable to a high degree but the initial value can lie anywhere within the band 1.0 - 1.2 V, thus your 4.752 V is a valid value that is within tolerance for the measurement of a 5 V supply to AVcc.

platypus's picture

Re: latest emonlib.cpp voltage measurement

Thanks for clarifying Robert.

platypus's picture

Re: latest emonlib.cpp voltage measurement

Perhaps you could help all of us  understand how this code works - either give us a detailed description or an in depth reference so that we too can understand the nature of voltage referencing according to bandgap. From what I have read on forums there are not too many who understand the registers and how they work - a good tutorial is needed, perhaps you can help.

Also I say again that this site needs a "notify" check box under each post.

Thanks Robert.

dBC's picture

Re: latest emonlib.cpp voltage measurement

Do I understand you correctly, and do you think the method should read and return the internal voltage reference itself, i.e. 1.1 V ?   .....   The code changes the ADC's reference to the internal band-gap reference, then uses that to measure the voltage of the ADC's normal reference voltage AVcc.

I don't think that's quite right.  If you selected the 1.1V bandgap reference how would you be able to measure AVcc?  Everything above ~1.1V would clip at 1023.

What the code actually does is measures the 1.1V against AVcc, and then "back calculates" AVcc from what it sees.

platypus the best reference for all those register accesses is always the Atmel datasheet for your part.  Check out the "Input Channel Selections" table in the A/D section.  On a 2560 for example, MUX5:0 selects which input pin gets fed to the A/D.  But if you set it to 011110 then it doesn't select any input pin, but rather selects the 1.1V bandgap.

Robert Wall's picture

Re: latest emonlib.cpp voltage measurement

That's a big ask. It is actually all in the data sheet, but until you've fought that battle a few times, it can be more than a little mysterious. With Glyn & Trystan's permission, I'll put it on my "to do" list. No promises for when, though.

For now what everyone needs to know is readVcc( ) returns the value of AVcc in mV (nominally 3300 or 5000) using the internal band-gap reference. The uncertainty in that measurement could be ±9%. The repeatability and stability of that measurement is in the order of a few hundred parts per million.

I should have added to my reply that there are two sources of error when you measure power: variations in component values (resistors, transformers) and variation in this reference voltage. The purpose of calibration is to correct for errors from both these sources.

It has been said that the mains-powered processor should not use readVcc( ) because the value of the internal 1.1 V reference is known with less certainty ( ±9%) than the 3.3 V or the 5 V from the regulator i.c. However, the internal reference is more stable than the regulator in the face of external influences. readVcc( ) is of course essential when operating from batteries.

dBC's picture

Re: latest emonlib.cpp voltage measurement

Perhaps you could help all of us  understand how this code works - either give us a detailed description

I'll have a crack.  The code is necessarily cluttered by all the AVR variants it supports,  so start by focusing on your part of interest.  In this example I'll assume a 2560 because I have the datasheet handy.   And then examine it line by line:

ADMUX = _BV(REFS0) | _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
ADCSRB &= ~_BV(MUX5);  

The above is basically saying:

REFS1:0 = 01  and MUX5:0 = 011110

(MUX5 happens to live in a different register from MUX4:0 which is why it's done separately).

Looking at the attached tables from the 2560 datasheet, you'll see that:

REFS1:0 = 01  means select "AVCC with external capacitor at AREF pin" as the reference voltage.

MUX5:0 = 011110 means measure the 1.1V Vbg

ADCSRA |= _BV(ADSC);

The above starts a conversion running.

while (bit_is_set(ADCSRA,ADSC));

The above waits for the conversion to finish.

result = ADCL;
result |= ADCH<<8;

The above fetches the 10-bit result.  So at this stage result now holds the result of measuring the 1.1V Vbg using AVCC as the reference voltage.   In other words:

result = Vbg / AVCC * 1024

So for instance, if AVCC is 5V and the 1.1V bandgap is spot on at 1.1V, then you would expect result to now hold 1.1 / 5 * 1024  =  225.

The premise of the routine is that if you know one of either AVCC or Vbg, and you have the above result then you can easily deduce the other.

If you trust AVCC, and you want to know what Vbg is for this part, then you can rearrange the above formula to:

Vbg = result * AVCC / 1024.

If you trust Vbg, and you want to know what AVCC is, then you can rearrange it to:

AVCC  = Vbg * 1024 / result

             = 1.1 * 1024 / result         (since you trust Vbg to be 1.1)

             =  1126.4 / result              (in volts)

             = 1126400 / result            (in mV)

which gives rise to the final line of code:

result = 1126400L / result;   

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.