Measuring VCC with an Attiny 85

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:

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!

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?

Ugh. More bad pennies.

  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.

  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.

Try to go with this :

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 ?

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.

smithy:

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 unsigned int n = ADC;

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.

smithy:
...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.

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

fungus:
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.

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 :slight_smile:

That's 8)!

It seems to work, more or less.

Accuracy isn't very good. :frowning: 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(!)

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).

Arduino core's analogRead() function splits the read, and it doesn't seem to have problems.

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

[quote author=Coding Badly link=topic=222847.msg1616200#msg1616200 date=1393805469]Ugh. More bad pennies.

  result = 1125300L / result; // Calculate Vcc (in mV); 1125300 = 1.1*1023*1000

1023 is not correct. The correct value is 1024.[/quote]

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

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

Its 1024 as there are 1024 levels.

Essentially it comes down to the way these ADCs work, they measure fence posts not fences.
There are 1024 levels, so for a 3V reference, each level DeltaV=3/1024=2.9296875 [mV]
So this means:

Level Voltage [mV]
0 0
1 2.93
2 5.86
... ...
1022 2994.14
1023 2997.07
(1024) (3000)

Notice how regardless of starting at 0 that it is actually the number '1024' which represents the full reference voltage. Now clearly you can never get that reading, but that doesn't change the fact the calculation has to use 1024.

[quote author=Coding Badly link=topic=222847.msg1617378#msg1617378 date=1393879313]

smithy:
...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. [/quote]

You are right, the compiler is allowed to reorder but as fas as i know not the logical meaning behind it.
If you set one register and then another this should always result into the same order just the structure surrounding theese 2 commands may change. An immediate execution is not guaranteed, but it will never change register 2 first because you manifested to set register 1 first in your code. If no order is guaranteed, coding would be nonsense.

Correct me if i´m wrong but theese are my thoughts about this issue.

smithy:
You are right, the compiler is allowed to reorder but as fas as i know not the logical meaning behind it.

a = 1 + 2;
a = 2 + 1;

The compiler sees those two lines of code as equivalent. Why? Because addition is commutative making the semantics identical. As long as the semantics are not changed, the compiler can do what ever it wants.

  uint8_t low  = ADCL;
  uint8_t high = ADCH;
  long result = (high<<8) | low;
  uint8_t high = ADCH;
  uint8_t low  = ADCL;
  long result = (high<<8) | low;
  long result = (ADCH<<8) | ADCL;
  long result = ADCL | (ADCH<<8);

The semantics are all the same. ADCH is read, as requested. ADCL is read, as requested. The value from ADCH is shifted, as requested. The two values are combined using a logical-or, as requested. Why are the semantics all the same? Because logical-or is commutative and the compiler has no sense that ADCH and ADCL have a relationship.

Well after some research, i found out arduino uses bitwise & for setting a variable which is commutative.

But then i don´t understand why its explained that way in the atmel datasheets and what is ADC then ?

If it´s a macro what code does it replace ?

For the latter part :

long result = (ADCH<<8) | ADCL;

the order doesn´t matter since you are using OR and shift one value 8 to the left.
Example :

01 1 1 001 1
11111100 00000000
|
11111100 01 1 1 001 1

There will always be zeros in the first 8 bit so it has no effect with OR.

smithy:
But then i don´t understand why its explained that way in the atmel datasheets and what is ADC then ?

#define _MMIO_WORD(mem_addr) (*(volatile uint16_t *)(mem_addr))
#define _SFR_MEM16(mem_addr) _MMIO_WORD(mem_addr)

...

#define ADC     _SFR_MEM16(0x04)