Digistump Forums
The Digispark => Digispark (Original) Support => Topic started by: albercook 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
-
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...
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
}
-
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
#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
}
-
Boink Boink. The sound of my head on the table when you suggested checking the website again. Thanks!!
-
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 :)
-
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.
-
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?
-
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>