Attiny85 Lipo battery cutoff/monitor

So I've googled a lot and found a lot of people asking simular questions with no final project or answers. I did find this: Make The Future: Flat Mate - protect your LiPo cells from over-discharge

Which is great, but isnt Arduino. So I've been working on the code the last couple days, it compiles without errors, but doesnts work. Im new to coding so maybe I'm missing something obvious.

Ideas?

/* 
  Attiny85_batteryMonitor
  Ideas borrowed from: https://github.com/unixbigot/Flat-Mate
  modified for arduion tiny core by DWhacks




 *@@ Voltage trigger levels.
 *
 * Battery voltage is read through a voltage divider and compared to the internal voltage reference.
 *
 * If 
 *    Vin ----+
 *            R1
 *            +----- Vout (BATI)
 *            R2
 *            |
 *            =
 *            .  (gnd)
 *
 * Then Vout = Vin * ( R2 / (R1 + R2) ) 
 *
 * ; Use this Emacs lisp function to calculate divisors
 * (defun rn2div (rup rdown) (/ (float rdown) (+ rup rdown)))
 *
 * 
 * eg. R1=12k R2=1k => Vout = Vin * (1000 / (1000 + 12000))
 *                            Vin * 0.0769
 *
 *     R1=20k R2=10k => Vout = Vin * 0.3333   Ileak = 0.4mA @ 12v
 *     R1=2k2 R2=1k  => Vout = Vin * 0.3125
 *     R1=3k3 R2=1k  => Vout = Vin * 0.232
 *     R1=3k9 R2=1k  => Vout = Vin * 0.204    Ileak = 2.4mA @ 12v
 *     R1=39k R2=10k => Vout = Vin * 0.204    Ileak = 0.24mA @ 12v
 *     R1=4k7 R2=1k  => Vout = Vin * 0.175
 *     R1=10k R2=1k  => Vout = Vin * 0.0909   Ileak = 1mA @ 12v
 *     R1=12k R2=1k  => Vout = Vin * 0.0769   Ileak = 0.92mA @ 12v
 *
 * Fully charged LiPo is 4.23v/cell, discharged is 2.7v/cell (nominal voltage 3.7v/cell)
 * For battery endurance, do not discharge below 3.0v/cell (aircraft users commonly use 2.9v/cell as limit)
 *
 * A 2-cell battery (nominally 7.46v)  varies     from 8.46v to 5.40v, with low-volt alert at 6.00v
 * A 3-cell battery (nominally 11.1v) thus varies from 12.9v to 8.10v, with low-volt alert at 9.00v
 * A 4-cell battery (nominally 14.8v) thus varies from 16.9v to 10.8v, with low-volt alert 12 12.0v
 *   NOTE: a 4-cell battery requires a different voltage divider than 2-and-3 cells (use 15:1 not 12:1)
 *
 *
 *@@ Analog read values for defined voltage levels
 *
 * For a 3-cell battery, we consider 12v+ to be "full", 11v "good", 10v "low" and 9v "critical" 
 * (BMV_foo constants are these values in millivolts)
 *
 * In AVR-worldview, we use 12:1 voltage divider and read 10-bit ADC comparisons versus AREF (1.1v)
 *  
 * So 12v becomes 1.00V when divided.   
 * Compared to 1.1v reference this gives an ADC result of 1024*(1.0/1.1) == 859
 *
 * An alternative approach is to use a smaller voltage divisor and compare
 * against Vcc (5.0v), but in practice a 12:1 divisor is easier to achieve
 * due to the standard first preference resistor value series.
 *
 * You can use these Emacs lisp defuns to calculate threshold analog values for your voltage levels
 *
 * (defun volts2int (v sf ref) (round (/ (* 1024.0 (* (float v) sf) ) (float ref))))
 * (defun vlist2int (sf ref levels) (mapcar (lambda (v) (volts2int (float v) sf ref)) levels))
 * eg. (volts2int 12 0.333 5.0) => 818
 *     (vlist2int (rn2div 10000 1000) 1.1 '(12 11 10 9)) => (1016 931 846 762)
 *     (vlist2int (rn2div 20000 10000) 5.0 '(12 11 10 9)) => (819 751 683 614)
 *     (vlist2int (rn2div 12000 1000) 1.1 '(12 11 10 9))=> (859 788 716 644)
 *
 * for 4-cell, use a 15:1 divider
 *     (vlist2int (rn2div 15000 1000) 1.1 '(16 14.5 13 12)) => (931 844 756 698)
 *
 * The above lines calculate the VL_* values shown below
 /* Use a 12:1 voltage divider */
 
 
 
 //#define INTERNAL (2) 
 #define CELL_COUNT 2  // DEFINE THE NUMBER OF CELLS 2,3,4


#if CELL_COUNT == 4
/* Use a 15:1 voltage divider */

#define BMV_FULL 16000
#define VL_FULL    931

#define BMV_GOOD 14500
#define VL_GOOD    844

#define BMV_LOW  13000
#define VL_LOW     756

#define BMV_CRIT 12000
#define VL_CRIT    698

#elif CELL_COUNT == 3
/* Use a 12:1 voltage divider */

#define BMV_FULL 12000
#define VL_FULL    859

#define BMV_GOOD 11000
#define VL_GOOD    788

#define BMV_LOW  10000
#define VL_LOW     716

#define BMV_CRIT  9000
#define VL_CRIT    644

#elif CELL_COUNT == 2
/* Use a 12:1 voltage divider */

#define BMV_FULL  8000
#define VL_FULL    573

#define BMV_GOOD  7300
#define VL_GOOD    523

#define BMV_LOW   6650
#define VL_LOW     476

#define BMV_CRIT  6000
#define VL_CRIT    430

#endif
 



const int batteryPin = 1; //V+ from battery connected to analog1 physical pin 7
const int switchPin = 0; //physical pin 5
const int led = 3; //physical pin 2

// Define the number of samples to keep track of.  The higher the number,
// the more the readings will be smoothed, but the slower the output will
// respond to the input.  Using a constant rather than a normal variable lets
// use this value to determine the size of the readings array.
const int numReadings = 4;

int readings[numReadings];      // the readings from the analog input
int index = 0;                  // the index of the current reading
int total = 0;                  // the running total
int average = 0;                // the average





void setup()
{
  analogReference(INTERNAL);
  pinMode(batteryPin, INPUT);
  pinMode(switchPin, OUTPUT);
  pinMode(led, OUTPUT);
   for (int thisReading = 0; thisReading < numReadings; thisReading++)
      readings[thisReading] = 0; // initialize all the readings to 0
    
  
}

void loop()
{
  
    averageVoltage(); //get average voltage from function  
    int sensorValue = average;
    float voltage = sensorValue * (1.1 / 1023.0);
    
    if (voltage > VL_CRIT){ //if battery is above critical, turn on transistor swtich
      digitalWrite(switchPin, HIGH);
    }
    
    if (voltage >= VL_FULL){ //if the battery is full or higher
      digitalWrite(led, HIGH);
    }
    
    else if (voltage >= VL_GOOD){
      digitalWrite(led, HIGH);
    }
    
    else if (voltage >= VL_LOW){
           /*Fade Up*/
          for(byte i=1; i<100; i++) {
            byte on  = i;
            byte off = 100-on;
            for( byte a=0; a<100; a++ ) {
              digitalWrite(led, HIGH);
              delayMicroseconds(on);
              digitalWrite(led, LOW);
              delayMicroseconds(off);
            }
          }
            /*Fade Down*/
            for(byte i=1; i<100; i++) {
            byte on  = 100-i;
            byte off = i;
            for( byte a=0; a<100; a++ ) {
              digitalWrite(led, HIGH);
              delayMicroseconds(on);
              digitalWrite(led, LOW);
              delayMicroseconds(off);
            }  
           } 
    }  
    
    
    
    
    
    
    
  
}

      void averageVoltage() {
        // subtract the last reading:
        total= total - readings[index];        
        // read from the sensor:  
        readings[index] = analogRead(batteryPin);
        // add the reading to the total:
        total= total + readings[index];      
        // advance to the next position in the array:  
        index = index + 1;                    
      
        // if we're at the end of the array...
        if (index >= numReadings)              
          // ...wrap around to the beginning:
          index = 0;                          
      
        // calculate the average:
        average = total / numReadings;          
        delay(1);        // delay in between reads for stability            
      }

//end

dwhacks:
...but doesnts work...

  • ? -

Oh Im sorry. It doesnt turn on the LED or anything.
The voltage coming out of my voltgage divider of 12:1 is .63mv, so that should be in the range for the 2 cell code.

Im wondering if the analogReference is correct. Im guessing that could be the problem.

Does a simple Blinky sketch blink the LED? (wiring check)

Grounds connected together?

Start simple.

On a Uno write a sketch that reads the voltage on the pin with the voltage divider you intend to use and just print the value from the analog read to the screen. That way you can see what numbers are actually being returned.

It's also possible to include the ability to get the Attiny to send stuff to the Serial Monitor - but it's a little more complicated.

Shouldn't you be checking if voltage <= VL_CRIT not greater?

...R

Yes, I tried adding a digitalWrite(led, HIGH); with delay and then low to setup and it blinks once at power on as expected.

All grounds are together and I've checked voltages. Thats why my guess is the analogReference, I have never used that before and I didnt really understand the implementation. The reason I suspect this is because if its referencing 5v and not 1.1v then my value will be much lower then calculated.

AREF is physical pin 5 / PB0 / lower-right corner. What is connected to it?

Which core are you using?

Pin 5 is my digital out for controlling the mosfet switch. it shouldnt matter what its doing when using analogrefence(internal) though no? As I understood it, Internal was a 1.1vref inside the attiny.

I am using the tiny core.

I will add some more more blinking leds to the sketch when I get home from work.

dwhacks:
it shouldnt matter what its doing when using analogrefence(internal) though no?

Let's see...
http://code.google.com/p/arduino-tiny/source/browse/cores/tiny/wiring.h#114
According to the datasheet there is no bypass capacitor for that value so you can use the AREF pin for whatever you want.

Do you have a 0.1 uF capacitor connected from VCC to GND as close as possible to the processor?

this is the part I dont get, whats the 2 for?

#define INTERNAL (2)

And yes I have the capacitor.

In the ATtiny85 datasheet, search for the ADMUX – ADC Multiplexer Selection Register section.

float voltage = sensorValue * (1.1 / 1023.0);

1023 is the wrong value. It should be 1024.

float voltage = sensorValue * (1.1 / 1023.0);

voltage is >= 0.0 and < 1.1.

if (voltage > VL_CRIT){ //if battery is above critical, turn on transistor swtich

The lowest possible value for VL_CRIT is 430 so this condition is always false.

if (voltage >= VL_FULL){ //if the battery is full or higher

The lowest possible value for VL_FULL is 573 so this condition is always false.

else if (voltage >= VL_GOOD){

The lowest possible value for VL_GOOD is 523 so this condition is always false.

else if (voltage >= VL_LOW){

The lowest possible value for VL_LOW is 476 so this condition is always false.

There are no more conditions. All conditions always evaluate to false.

OHHHHHH
wow! so basically my problem is I convert my reading back into "voltage" and I dont want to do that! I wonder why i did that in the first place. It was late, thank you. I think I will have it figured out when I get home.

Great! it works now! that was a silly mistake, thank you for your help!

Now I have another question. The values I had for the voltage "readings" are way off. I dont know if its just like that but my calculated voltages are about 40 below what seams to work. I cant imagine my DMM being that much off.

As I understand it, the voltage after the divider, on the pin should fit into this equation to find the byte value:
1024*(VOLTAGE/1.1) = VALUE
But like I said It seams off.

Next question, I have used the smoothing example to smooth out my readings and I have made it its own functions. As I though, it should run the amount of numReadings before getting the average. How can I make it loop that function without running the rest of my loop?

Heres the updated code:

/* 
  Attiny85_batteryMonitor
  Ideas borrowed from: https://github.com/unixbigot/Flat-Mate
  modified for arduino tiny core by DWhacks




 *@@ Voltage trigger levels.
 *
 * Battery voltage is read through a voltage divider and compared to the internal voltage reference.
 *
 * If 
 *    Vin ----+
 *            R1
 *            +----- Vout (BATI)
 *            R2
 *            |
 *            =
 *            .  (gnd)
 *
 * Then Vout = Vin * ( R2 / (R1 + R2) ) 
 *
 * ; Use this Emacs lisp function to calculate divisors
 * (defun rn2div (rup rdown) (/ (float rdown) (+ rup rdown)))
 *
 * 
 * eg. R1=12k R2=1k => Vout = Vin * (1000 / (1000 + 12000))
 *                            Vin * 0.0769
 *
 *     R1=20k R2=10k => Vout = Vin * 0.3333   Ileak = 0.4mA @ 12v
 *     R1=2k2 R2=1k  => Vout = Vin * 0.3125
 *     R1=3k3 R2=1k  => Vout = Vin * 0.232
 *     R1=3k9 R2=1k  => Vout = Vin * 0.204    Ileak = 2.4mA @ 12v
 *     R1=39k R2=10k => Vout = Vin * 0.204    Ileak = 0.24mA @ 12v
 *     R1=4k7 R2=1k  => Vout = Vin * 0.175
 *     R1=10k R2=1k  => Vout = Vin * 0.0909   Ileak = 1mA @ 12v
 *     R1=12k R2=1k  => Vout = Vin * 0.0769   Ileak = 0.92mA @ 12v
 *
 * Fully charged LiPo is 4.23v/cell, discharged is 2.7v/cell (nominal voltage 3.7v/cell)
 * For battery endurance, do not discharge below 3.0v/cell (aircraft users commonly use 2.9v/cell as limit)
 *
 * A 2-cell battery (nominally 7.46v)  varies     from 8.46v to 5.40v, with low-volt alert at 6.00v
 * A 3-cell battery (nominally 11.1v) thus varies from 12.9v to 8.10v, with low-volt alert at 9.00v
 * A 4-cell battery (nominally 14.8v) thus varies from 16.9v to 10.8v, with low-volt alert 12 12.0v
 *   NOTE: a 4-cell battery requires a different voltage divider than 2-and-3 cells (use 15:1 not 12:1)
 *
 *
 *@@ Analog read values for defined voltage levels
 *
 * For a 3-cell battery, we consider 12v+ to be "full", 11v "good", 10v "low" and 9v "critical" 
 * (BMV_foo constants are these values in millivolts)
 *
 * In AVR-worldview, we use 12:1 voltage divider and read 10-bit ADC comparisons versus AREF (1.1v)
 *  
 * So 12v becomes 1.00V when divided.   
 * Compared to 1.1v reference this gives an ADC result of 1024*(1.0/1.1) == 859
 *
 * An alternative approach is to use a smaller voltage divisor and compare
 * against Vcc (5.0v), but in practice a 12:1 divisor is easier to achieve
 * due to the standard first preference resistor value series.
 *
 * You can use these Emacs lisp defuns to calculate threshold analog values for your voltage levels
 *
 * (defun volts2int (v sf ref) (round (/ (* 1024.0 (* (float v) sf) ) (float ref))))
 * (defun vlist2int (sf ref levels) (mapcar (lambda (v) (volts2int (float v) sf ref)) levels))
 * eg. (volts2int 12 0.333 5.0) => 818
 *     (vlist2int (rn2div 10000 1000) 1.1 '(12 11 10 9)) => (1016 931 846 762)
 *     (vlist2int (rn2div 20000 10000) 5.0 '(12 11 10 9)) => (819 751 683 614)
 *     (vlist2int (rn2div 12000 1000) 1.1 '(12 11 10 9))=> (859 788 716 644)
 *
 * for 4-cell, use a 15:1 divider
 *     (vlist2int (rn2div 15000 1000) 1.1 '(16 14.5 13 12)) => (931 844 756 698)
 *
 * The above lines calculate the VL_* values shown below
 /* Use a 12:1 voltage divider */
 
 
 
 //#define INTERNAL (2) 
 #define CELL_COUNT 2  // DEFINE THE NUMBER OF CELLS 2,3,4


#if CELL_COUNT == 4
/* Use a 15:1 voltage divider */

#define BMV_FULL 16000
#define VL_FULL    931

#define BMV_GOOD 14500
#define VL_GOOD    844

#define BMV_LOW  13000
#define VL_LOW     756

#define BMV_CRIT 12000
#define VL_CRIT    698

#elif CELL_COUNT == 3
/* Use a 12:1 voltage divider */

#define BMV_FULL 12000
#define VL_FULL    859

#define BMV_GOOD 11000
#define VL_GOOD    788

#define BMV_LOW  10000
#define VL_LOW     716

#define BMV_CRIT  9000
#define VL_CRIT    644

#elif CELL_COUNT == 2
/* Use a 12:1 voltage divider */


#define VL_FULL    613  //about 7.8v


#define VL_GOOD    583  //about 7.3v


#define VL_LOW     510  //about 6.3v


#define VL_CRIT    470

#endif
 



const int batteryPin = 1; //V+ from battery connected to analog1 physical pin 7
const int switchPin = 0; //physical pin 5
const int led = 3; //physical pin 2

// Define the number of samples to keep track of.  The higher the number,
// the more the readings will be smoothed, but the slower the output will
// respond to the input.  Using a constant rather than a normal variable lets
// use this value to determine the size of the readings array.
const int numReadings = 3;

int readings[numReadings];      // the readings from the analog input
int index = 0;                  // the index of the current reading
int total = 0;                  // the running total
int average = 0;                // the average





void setup()
{
  analogReference(INTERNAL);
  pinMode(batteryPin, INPUT);
  pinMode(switchPin, OUTPUT);
  pinMode(led, OUTPUT);
   for (int thisReading = 0; thisReading < numReadings; thisReading++)
      readings[thisReading] = 0; // initialize all the readings to 0
    
  
}

void loop()
{
  
    averageVoltage(); //get average voltage from function  
    int voltage = average;
    
    if (voltage > VL_CRIT){ //if battery is above critical, turn on transistor swtich
      digitalWrite(switchPin, HIGH);
    }

    
    if (voltage >= VL_FULL){ //if the battery is full or higher
      digitalWrite(led, HIGH);
    }
    
    else if (voltage >= VL_GOOD){
           /*Fade Up*/
          for(byte i=1; i<100; i++) {
            byte on  = i;
            byte off = 100-on;
            for( byte a=0; a<100; a++ ) {
              digitalWrite(led, HIGH);
              delayMicroseconds(on);
              digitalWrite(led, LOW);
              delayMicroseconds(off);
            }
          }
            /*Fade Down*/
            for(byte i=1; i<100; i++) {
            byte on  = 100-i;
            byte off = i;
            for( byte a=0; a<100; a++ ) {
              digitalWrite(led, HIGH);
              delayMicroseconds(on);
              digitalWrite(led, LOW);
              delayMicroseconds(off);
            }  
           }
    }
    
    else if (voltage >= VL_LOW){
           /*Fade Up*/
          for(byte i=1; i<50; i++) {
            byte on  = i;
            byte off = 50-on;
            for( byte a=0; a<100; a++ ) {
              digitalWrite(led, HIGH);
              delayMicroseconds(on);
              digitalWrite(led, LOW);
              delayMicroseconds(off);
            }
          }
            /*Fade Down*/
            for(byte i=1; i<50; i++) {
            byte on  = 50-i;
            byte off = i;
            for( byte a=0; a<100; a++ ) {
              digitalWrite(led, HIGH);
              delayMicroseconds(on);
              digitalWrite(led, LOW);
              delayMicroseconds(off);
            }  
           } 
           
    }
    
    else if (voltage < VL_LOW){
      digitalWrite(switchPin, LOW);
      digitalWrite(led, HIGH);
      delay(100);
      digitalWrite(led, LOW);
      delay(100);      
           
    }          
    
  
}

      void averageVoltage() {
        // subtract the last reading:
        total= total - readings[index];        
        // read from the sensor:  
        readings[index] = analogRead(batteryPin);
        // add the reading to the total:
        total= total + readings[index];      
        // advance to the next position in the array:  
        index = index + 1;                    
      
        // if we're at the end of the array...
        if (index >= numReadings)              
          // ...wrap around to the beginning:
          index = 0;                          
      
        // calculate the average:
        average = total / numReadings;          
        delay(1);        // delay in between reads for stability            
      }


//end

Well I figured out all the issues! the analogRead value is a lot closer to whats expected on the actually soldered together version. Bread board must have added odd resistances or bad connections.

All I have to to is calculate the values for 3s and 4s to my taste and then I will make a blog post. I will also make up a PCB in Eagle.

All revisions of the code are on my gists page is anyone is interested: Attiny battery monitor with tiny core. · GitHub

Oh, and heres a video of it working:

Thank you for the follow-up.

Do you still need help with the questions in Reply #15?

I think It's all working as it should now. I thought about making the averageVoltage function loop until it reaches the numReadings value but now I'm thinking that might add a funny pause in my main loop ruining the effect of the fading LED.

Do you have any suggestions or insight into that?

      void averageVoltage() {
        // subtract the last reading:
        total= total - readings[index];        
        // read from the sensor:  
        readings[index] = analogRead(batteryPin);
        // add the reading to the total:
        total= total + readings[index];      
        // advance to the next position in the array:  
        index = index + 1;                    
      
        // if we're at the end of the array...
        if (index >= numReadings)              
          // ...wrap around to the beginning:
          index = 0;                          
      
        // calculate the average:
        average = total / numReadings;          
        delay(1);        // delay in between reads for stability            
      }

dwhacks:
Well I figured out all the issues! the analogRead value is a lot closer to whats expected on the actually soldered together version. Bread board must have added odd resistances or bad connections.

Bear in mind that the internal 1.1V reference is not accurate, it can be anywhere between 1.0 and 1.2V. You need to calibrate it for each chip. Standard practice would be to store the calibration constant in EEPROM so that you don't have to change the program.