Author Topic: Reading battery voltage without using an ADC pin  (Read 12698 times)

albercook

  • Newbie
  • *
  • Posts: 33
Reading battery voltage without using an ADC pin
« on: September 20, 2013, 06:56:40 pm »
I want to read the battery voltage without using an ADC input. I found the following  code and it worked for an arduino and for an ATtiny84 but I'm not sure how to change it to work for ATtiny85.

Can anyone help?

long readVcc() {
  // Source: http://provideyourown.com/2012/secret-arduino-voltmeter-measure-battery-voltage/
  // 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) ;
  #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
  result = 1211861L / result;
  return result; // Vcc in millivolts
}

Thanks

PeterF

  • Hero Member
  • *****
  • Posts: 877
Re: Reading battery voltage without using an ADC pin
« Reply #1 on: September 20, 2013, 09:51:59 pm »
You might want to re-check that website, as there is a different snippet of code available there now, which includes the ATTiny85-specific define.

I haven't had a chance to test it yet, but this is the version available right now:

Edit: code snippet didn't post properly...

Code: [Select]
long readVcc() {
  // 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; // Vcc in millivolts
}
« Last Edit: September 20, 2013, 09:54:21 pm by pfeerick »

PeterF

  • Hero Member
  • *****
  • Posts: 877
Re: Reading battery voltage without using an ADC pin
« Reply #2 on: September 21, 2013, 01:08:15 am »
I just managed to finish testing it. Using the Digispark LCD as a display device, the following code works as an input voltage meter. With some minor variation in accuracy.

When disconnected from the computer, and running from a 4.8v nimh pack (fully charged, so 5.1v) it is reporting 5.57v when powered from the 5v line, and 3.89v when run from the vin line (is only off by 0.1v, so that's ok). So you will get some variation in the reading, but it's worth it for not using a ADC pin just for that.

btw, if you want to make code snippets more readable, surround them with [ code ] and [ /code ] tags (minus the spaces). You can also just highlight the code and click the '#' button on the editor toolbar.

Have fun!
Pete

Code: [Select]
#include <TinyWireM.h>                  // I2C Master lib for ATTinys which use USI - comment this out to use with standard arduinos
#include <LiquidCrystal_I2C.h>          // for LCD w/ GPIO MODIFIED for the ATtiny85

LiquidCrystal_I2C lcd(0x27,16,2);  // set address (0x27 is the address of the Digispark LCD modules) & 16 chars / 2 lines

void setup()
{
  TinyWireM.begin();                    // initialize I2C lib
  lcd.init();                           // initialize the lcd
  lcd.backlight();                      // Turn on the backlight

  // Print startup message to the LCD
  lcd.setCursor(0,0); lcd.print("Powered by");
  lcd.setCursor(0,1); lcd.print("Digispark!");       

  //delay so we can see the message
  delay(1000);
}

void loop()
{
  //read and convert the voltage to a decimal
  long voltage = readVcc();
  double decimalVoltage = doubleMap(double(voltage),0,6000,0,6);

  //update the LCD
  lcd.clear();
  lcd.setCursor(0,0); lcd.print("Voltage:");
  lcd.setCursor(9,0); lcd.print(decimalVoltage); 
 
  // delay so this updates approximatley 4 times per second
  delay(250);
}

double doubleMap(double x, double in_min, double in_max, double out_min, double out_max)
{
  return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}

long readVcc() {
  // 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<< | low;
 
  result = 1125300L / result; // Calculate Vcc (in mV); 1125300 = 1.1*1023*1000
  return result; // Vcc in millivolts
}

« Last Edit: September 21, 2013, 01:13:33 am by pfeerick »

albercook

  • Newbie
  • *
  • Posts: 33
Re: Reading battery voltage without using an ADC pin
« Reply #3 on: September 21, 2013, 04:39:26 pm »
Boink Boink. The sound of my head on the table when  you suggested checking the website again. Thanks!!



PeterF

  • Hero Member
  • *****
  • Posts: 877
Re: Reading battery voltage without using an ADC pin
« Reply #4 on: September 21, 2013, 07:25:30 pm »
lol... No problem. Was just lucky you posted the link... Otherwise delving the murky depths of the data sheets and google searching would have ensued. I usually just face palm...doesn't hurt as much :)

semicolo

  • Full Member
  • ***
  • Posts: 137
Re: Reading battery voltage without using an ADC pin
« Reply #5 on: September 22, 2013, 05:17:23 pm »
That's quite a find, and you have to dig deep in the datasheet to actually find it, this is not mentioned in the features, nor does it appear on the ADC schematic, only on the registers description is it written that you can select VBG as input with the mux, not explaining what it is. Then if you search the datasheet, you finally see that VBG is the 1.1V internal reference voltage.

I've read this part of the datasheet a few times for a project that will use the differential feature and it never occured to me the 1.1V Ref could be used as an input.

It's as if Atmel didn't want you to use it.

PeterF

  • Hero Member
  • *****
  • Posts: 877
Re: Reading battery voltage without using an ADC pin
« Reply #6 on: September 22, 2013, 09:28:28 pm »
Surely Atmel wouldn't do a thing like that (not want you to find it). I first came across this for the Atmel328s as it was a handy thing to do one the Arduino Unos... as well as the 12bit oversampling on the ADC. And the internal temperature sensing was an interesting extra also - hm, do you know if that also around on the attiny85s?

digistump

  • Administrator
  • Hero Member
  • *****
  • Posts: 1465
Re: Reading battery voltage without using an ADC pin
« Reply #7 on: September 23, 2013, 12:18:58 am »

Yep there is an internal temp sensor on the attiny85 - here is some example code for it - it logs the temp to the internal EEPROM once per second. If you plug it in a short pin 5 to ground it will type out (as if a keyboard) all of the logged temps - I call it the "SimplestTempLogger"

<code>



#include "DigiKeyboard.h"
#include <EEPROM.h>   
 
int tempOffset = 10; //set the offset here after calibrating against known source
unsigned int logInterval = 120; //log a temperature every X seconds
unsigned int addressLimit = 511;
unsigned int address = 0;
unsigned int mode = 1;


void setup(void)
{


  pinMode(5, INPUT);
  pinMode(1, OUTPUT);
  digitalWrite(5,HIGH);
  int modeInput = digitalRead(5);
  if(modeInput == LOW){
    mode = 0;
  }
 
  if(mode == 1){
    analogReference(INTERNAL1V1);
  }


}




void ledState(int state){
  digitalWrite(1,state);
}


void readTemp(){


  ledState(HIGH);
  int returned = EEPROM.read(address);
 
  if(returned == 255){ //stop when we hit 255 - which means we got it all
    address = addressLimit+1;
    return;
  }
   
  address++;
  // this is generally not necessary but with some older systems it seems to
  // prevent missing the first character after a delay:
  DigiKeyboard.sendKeyStroke(0);
 
  // Type out this string letter by letter on the computer (assumes US-style
  // keyboard)
 
  DigiKeyboard.println(returned);
 
  // It's better to use DigiKeyboard.delay() over the regular Arduino delay()
  // if doing keyboard stuff because it keeps talking to the computer to make
  // sure the computer knows the keyboard is alive and connected
 
  DigiKeyboard.delay(15);
  ledState(LOW);
  DigiKeyboard.delay(15);
 




 
}


void writeTemp(){
  ledState(HIGH);
  int raw = analogRead(A0+15);
  /* Original code used a 13 Cdeg adjustment. But based on my results, I didn't seem to need it. */
  // raw -= 13; // raw adjust = kelvin
  //int in_c = raw - 273; // celcius
  //in_c = round(in_c);
 
  int in_f = ((raw - 273)*1.8)+32; // temp in f
  in_f = round(in_f)+tempOffset;
 
  EEPROM.write(address, 255);//set next block to 255 so we know where it stopped
 
  EEPROM.write(address, in_f);
 
  address++; //increment address
 
  //delay until next log interval but blink while delaying
  int i =0;
  while (i<logInterval){
    ledState(HIGH);
    DigiKeyboard.delay(500);
    ledState(LOW);
    DigiKeyboard.delay(500);
    i++;
  }
 
}
 
void loop(){


   
  if(address>addressLimit){
    ledState(LOW);
    DigiKeyboard.delay(1000);
  }
  else if(mode ==1){
    writeTemp();
  }
  else if(mode ==0){
    readTemp();
  }




}
 
</code>