Go Down

Topic: Measuring VCC with an Attiny 85 (Read 21220 times) previous topic - next topic

LastSamurai

Hey guys, I am using an Attiny 85 with a 3V battery. I do wanna measure the current voltage to know when the battery is empty.
I did some google search and found this:

Code: [Select]
long readVcc() {
  // Read 1.1V reference against AVcc
  // set the reference to Vcc and the measurement to the internal 1.1V reference
  ADMUX = _BV(MUX3) | _BV(MUX2);
  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
}


Sadly that doesn't give me any (right) values. I do get ~55-250 which cant be true (right?!). Does anyone know where the error is? Or is there a better methode?
Thanks!

Jiggy-Ninja

Working backwards, with the values you're getting from the division, result must be between 20,500 and 4,500. For an ADC result, this is absurd.

Instead of printing the result of the division, print the raw count value you're reading from the ADC. If it's not between 0 and 1023, you have a problem. (and if it is, that's even weirder).

However, you have only posted a snippet. It's good practice to post your FULL code in case the problem lies somewhere else.

How are you displaying this value?
Hackaday: https://hackaday.io/MarkRD
Advanced C++ Techniques: https://forum.arduino.cc/index.php?topic=493075.0

Coding Badly


Ugh.  More bad pennies.

Code: [Select]
  result = 1125300L / result; // Calculate Vcc (in mV); 1125300 = 1.1*1023*1000

1023 is not correct.  The correct value is 1024.

1.1 is rarely the correct value.  The value is between 1.0 and 1.2.  It is specific to each processor.  If you want readVcc to be accurate you will have to determine the correct value for your processor.

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


Splitting the read is a bad idea.  Just use ADC.

smithy

#3
Mar 03, 2014, 05:31 pm Last Edit: Mar 03, 2014, 05:54 pm by smithy Reason: 1
Try to go with this :

Code: [Select]

long Vrail_leo() { 
  // Read 1.1V reference against AVcc
  // set the reference to Vcc and the measurement to the internal 1.1V reference
  #if defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
    ADMUX = _BV(REFS0) | _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
  #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);
  #else
    ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
  #endif 

  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;
 
}


Oh sry when comparing to yours, it seems it should do the same on your 85. I used that for 328p, 32u4 and it worked fine. I think the settings should be right, are you testing it in a standalone sketch ?

Quote
Splitting the read is a bad idea.

No it´s how an analog read with 10 bits is done with all avrs i have messed with.
You must read ADCL first and ADCH immediatley after, else you may get a wrong value.
If you read 8 bit then its enough to read ADCH but you have to set the ADLAR bit first.
It´s all in the Datasheets, even a differential read is done the same way.

fungus


Quote
Splitting the read is a bad idea.

No it´s how an analog read with 10 bits is done with all avrs i have messed with.
You must read ADCL first and ADCH immediatley after, else you may get a wrong value.
If you read 8 bit then its enough to read ADCH but you have to set the ADLAR bit first.


If you just read ADC then the compiler knows to read ADCL followed by ADCH.

The clever part is you don't have to remember anything. Just do
Code: [Select]
unsigned int n = ADC;
Advanced Arduino

smithy

Well, if you do ADC or analogRead the analog read is done the same way as triggering the hardware registers themselves, even if you use an automatic trigger function via registers  (haven´t found any different info in the datasheets i read) so it should be exactly the same.

Coding Badly

...so it should be exactly the same.


Unless the compiler reorders the two lines of code.  Which it is allowed to do.  Splitting the read is a bad idea.  Just use ADC.

fungus


...so it should be exactly the same.


Unless the compiler reorders the two lines of code.  Which it is allowed to do. 


Not if they're declared "volatile" (which I hope they are...)

Advanced Arduino

Coding Badly

Not if they're declared "volatile" (which I hope they are...)


A subject which has been discussed, debated, and argued ad nauseam on not just AVR Freaks but wherever C++ developers congregate.  The conclusion is always the same.  volatile does not prevent the compiler from reordering.  I have no idea if it applies elsewhere but in the AVR-GCC world a "memory barrier" is used to guarantee ordering; which is most certainly not present in smithy's code.  And is also not necessary because the solution to the problem is trivial: just use ADC.

fungus

I just stuck this code into my firefly jar BIOS...

It can now read the battery voltage and display it by coded flashes of the flies :)
Advanced Arduino

Coding Badly


fungus

It seems to work, more or less.

Accuracy isn't very good.  :-(  I know the 1.1V is only a "nominal" but it reads very low on the chip I'm using. Still, it's a free feature so we mustn't complain.

I made the jar display the voltage to two decimal places. It outputs each digit as a series of pulses with a delay in between digits. A zero is a long pulse.

Here's one I made last May - it's been running 24/7 since then on the same button cell. and it's still going strong(!)

http://www.youtube.com/watch?v=MYXBIKArApk

There's a magnet in the butterfly "key" to switch it on/off via. a hidden reed switch. It also has a mercury switch inside so you can shake the jar to make them more active (they slow down over time to save battery).

We're making some more in a workshop next week so I was just revising the software. I replaced the mercury switch with capacitive touch sensing (touch the lid to make them more active) and now it has voltage display as well! (turn it off and hold the key in place for a few seconds extra to see the voltage).
Advanced Arduino

Jiggy-Ninja


...so it should be exactly the same.


Unless the compiler reorders the two lines of code.  Which it is allowed to do.  Splitting the read is a bad idea.  Just use ADC.

Arduino core's analogRead() function splits the read, and it doesn't seem to have problems.
Code: [Select]
int analogRead(uint8_t pin)
{
uint8_t low, high;

#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
if (pin >= 54) pin -= 54; // allow for channel or pin numbers
#elif defined(__AVR_ATmega32U4__)
if (pin >= 18) pin -= 18; // allow for channel or pin numbers
#elif defined(__AVR_ATmega1284__) || defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega644__) || defined(__AVR_ATmega644A__) || defined(__AVR_ATmega644P__) || defined(__AVR_ATmega644PA__)
if (pin >= 24) pin -= 24; // allow for channel or pin numbers
#elif defined(analogPinToChannel) && (defined(__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__))
pin = analogPinToChannel(pin);
#else
if (pin >= 14) pin -= 14; // allow for channel or pin numbers
#endif

#if defined(__AVR_ATmega32U4__)
pin = analogPinToChannel(pin);
ADCSRB = (ADCSRB & ~(1 << MUX5)) | (((pin >> 3) & 0x01) << MUX5);
#elif defined(ADCSRB) && defined(MUX5)
// the MUX5 bit of ADCSRB selects whether we're reading from channels
// 0 to 7 (MUX5 low) or 8 to 15 (MUX5 high).
ADCSRB = (ADCSRB & ~(1 << MUX5)) | (((pin >> 3) & 0x01) << MUX5);
#endif
 
// set the analog reference (high two bits of ADMUX) and select the
// channel (low 4 bits).  this also sets ADLAR (left-adjust result)
// to 0 (the default).
#if defined(ADMUX)
ADMUX = (analog_reference << 6) | (pin & 0x07);
#endif

// without a delay, we seem to read from the wrong channel
//delay(1);

#if defined(ADCSRA) && defined(ADCL)
// start the conversion
sbi(ADCSRA, ADSC);

// ADSC is cleared when the conversion finishes
while (bit_is_set(ADCSRA, ADSC));

// we have to read ADCL first; doing so locks both ADCL
// and ADCH until ADCH is read.  reading ADCL second would
// cause the results of each conversion to be discarded,
// as ADCL and ADCH would be locked when it completed.
low  = ADCL;
high = ADCH;
#else
// we dont have an ADC, return 0
low  = 0;
high = 0;
#endif

// combine the two bytes
return (high << 8) | low;
}
Hackaday: https://hackaday.io/MarkRD
Advanced C++ Techniques: https://forum.arduino.cc/index.php?topic=493075.0

cjdelphi

#13
Mar 09, 2014, 04:37 am Last Edit: Mar 09, 2014, 07:16 am by Coding Badly Reason: 1
Ugh.  More bad pennies.
Code: [Select]
 result = 1125300L / result; // Calculate Vcc (in mV); 1125300 = 1.1*1023*1000
1023 is not correct.  The correct value is 1024.


http://arduino.cc/en/Tutorial/ReadAnalogVoltage


0 - 1023
1 - 1024

The tutirial div's by 1023 ...  if a value of 0 is returned then it's div 1023 not 1024?

Moderator edit: quote trimmed

Coding Badly


When faced with conflicting information it is always best to check the datasheet.  According to the folks at Atmel the correct value is 1024.

Go Up