Electronics:Simple circuit for lithium battery charge meter

From happytrees.org
Jump to: navigation, search

I wanted a simple meter using 4 LEDs to display the charge of a lithium battery. My project used a single battery(1S), so it needed to display voltages between 3 - 4.2 Volts. If you need to measure multiple batteries in series you should be able to modify the circuit by changing the voltage divider resistors, and regulate the voltage to the MCU. I used an AVR ATTINY26L because I had them, but you can use any MCU that can handle a supply voltage from a little less than 3 volts to 4.5V.

Components

  • 1x Atmel ATTINY26L
  • 4x LEDs or LED array
  • 2x 1k resistors (for resistive divider to ADC)
  • 4x resistors for LEDs. See LED resistor calculator
  • 1x 0.1uF to 1uF larger capacitor (for bypass capacitor)
  • ( 1x push button switch (optional)

Circuit Schematic

Battery-charge-schematic schem.jpg

Programming

STK-500 configuration for programing

    ISP6PIN to SPROG1
    PORTE RST to PORTB PB7
    PORTE XT1 to PORTB PB4
    ATTINY26L in socket SCKT3700A1
    PORTB PB0 to LED0
    PORTB PB1 to LED1
    PORTB PB2 to LED2
    PORTB PB3 to LED3
    PORTA PA0 to Vin

Fuses

Oddly internal oscillator was enabled already on my MCU, but you may need to enable it.

    default for avrdude is "safemode: Fuses OK (E:FF, H:F7, L:E1)"
    Set low fuse bits to 0xE1 for internal oscillator
    avrdude commandline flags "-U lfuse:w:0xE1:m"
    See http://eleccelerator.com/fusecalc/

Problems

As the battery voltage decreases the brightness decreases. To fix this you could enable Vref on the MCU. Attach the LED + Resistors between Vref and the PORTB pins, then invert variables in the code. I probable would have done this.


Download Files

Download code, Makefile, schematic, etc


References


Code

/*
 * Lithium battery level meter
 *
 * Created: ZyMOS 04/2020
 *
 * Microcontroller
 *     ATTiny26
 *
 * Resistor divider to ADC Channel 0
 *    R1/R2 = 1/2
 *
 * Internal RC Oscilator 1MHz
 *    Fuse bits
 *         CLKSEL[3:0] = 0001
 *        PLLCK = 1 ?
 *        Note: For all fuses “1” means unprogrammed while “0” means programmed.
 *
 * Leds output
 *     PB0-PB3
 *
 */


#include <stdlib.h>
#include <avr/io.h>
#include <avr/pgmspace.h>
#include <util/delay.h>

// Global
uint16_t battery_level;

#define std_delay 35
#define F_CPU 12000000UL

// ADC
void adc_init(void){

    ADMUX |=     (0 << ADLAR)| // Set left shift result
                (1 << REFS1)| // Set Vref to internal (2.56V), pin out not connected
                (0 << REFS0); // Set Vref to internal (2.56V), pin out not connected
        // Int Ref, pin not connected

    ADCSR |=     (1 << ADEN)| // Enable ADC
                (1 << ADPS2)| // Set prescaler to 128 (111)
                (1 << ADPS1)| // Set prescaler to  128 (111)
                (1 << ADPS0);  // Set prescaler to  128 (111)
}


// ADC read
uint16_t adc_read(uint8_t ch)
{
   // ADC = Vin/Vref * 1024

    //Select ADC Channel ch must be 0-7
   /* ch=0b00001001; */
       ADMUX &= 0xF0;
    ADMUX|=ch;

   //Start Single conversion
   ADCSR |= (1<<ADSC);

   //Wait for conversion to complete
   while(!(ADCSR & (1<<ADIF)));

   //Clear ADIF by writing one to it
   ADCSR |= (1<<ADIF);


   return(ADC);
}


// ADC average
uint16_t adc_average(uint8_t adc_channel) {
    uint16_t result=0;
    uint16_t result2=0;

        uint8_t adc_samples=50;    //times 2
        uint8_t adc_delay=50; //us
        uint8_t z;    

        // average first half
        for(z=0;z<adc_samples;z++){
             result = result + adc_read(adc_channel); // Read Analog value from channel-0
            _delay_us(adc_delay);
        }
        result = result / adc_samples;

        // average second half
        for(z=0;z<adc_samples;z++){
             result2 = result2 + adc_read(adc_channel); // Read Analog value from channel-0
            _delay_us(adc_delay);
        }
        result2 = result2 / adc_samples;

        // average 2 halfs
        result = result2 + result;
        result =  result / 2;

        return(result);

}

/* void set_battery_leds(uint16_t battery_level){ */
    /* PORTB &= 0b11110000; */
    /* if(battery_level > 100){ */
        /* PORTB |= 0b00010000; */
    /* } */
/* } */


void led_test(void){
    PORTB = 0b00000000;
    _delay_ms(std_delay);
    PORTB = 0b00001000;
    _delay_ms(std_delay);
    PORTB = 0b00000100;
    _delay_ms(std_delay);
    PORTB = 0b00000010;
    _delay_ms(std_delay);
    PORTB = 0b00000001;
    _delay_ms(std_delay);
    PORTB = 0b00000010;
    _delay_ms(std_delay);
    PORTB = 0b00000100;
    _delay_ms(std_delay);
    PORTB = 0b00001000;
    _delay_ms(std_delay);
    PORTB = 0b00000000;
    _delay_ms(std_delay);


}

void display_led_value(uint16_t value){
    
    /* Value = Rgain * Vbatt/Vref + 1024 */

    // Full = 4.2
    // Empty = 3
    // Resistor gain = 1/2
    // LEDs = 0 : <3.1V      :         value <= 620
    // LEDs = 1 : 3.1 - 3.3V : 620 < value <= 660
    // LEDs = 2 : 3.3 - 3.6V : 660 < value <= 720
    // LEDs = 3 : 3.6 - 3.9V : 720 < value <= 780
    // LEDs = 4 : >3.9V      : 780 < value

    // 2.19V = 780 (876)
    // 2.012 = 720 (804)
    // 1.855 = 660 (742)
    // 1.741 = 620 (696)
    
    /* Theoretical */
    /* uint16_t value0=620; */
    /* uint16_t value1=660; */
    /* uint16_t value2=720; */
    /* uint16_t value3=780; */

    /* Actual */
    uint16_t value0=560;
    uint16_t value1=594;
    uint16_t value2=630;
    uint16_t value3=702;

    _delay_ms(std_delay*10);
    
    if(value <= value0){
        PORTB = 0b00000000;
        _delay_ms(std_delay);

    }else if(value > value0 && value <= value1){
        PORTB = 0b00000000;
        _delay_ms(std_delay);
        PORTB = 0b00001000;
        _delay_ms(std_delay);

    }else if(value > value1 && value <= value2){
        PORTB = 0b00000000;
        _delay_ms(std_delay);
        PORTB = 0b00001000;
        _delay_ms(std_delay);
        PORTB = 0b00001100;
        _delay_ms(std_delay);

    }else if(value > value2 && value <= value3 ){
        PORTB = 0b00000000;
        _delay_ms(std_delay);
        PORTB = 0b00001000;
        _delay_ms(std_delay);
        PORTB = 0b00001100;
        _delay_ms(std_delay);
        PORTB = 0b00001110;
        _delay_ms(std_delay);

    }else if(value > value3){
        /* PORTB = 0b00000000; */
        /* _delay_ms(std_delay); */
        /* PORTB = 0b00001000; */
        /* _delay_ms(std_delay); */
        /* PORTB = 0b00001100; */
        /* _delay_ms(std_delay); */
        /* PORTB = 0b00001110; */
        /* _delay_ms(std_delay); */
        PORTB = 0b00001111;
        _delay_ms(std_delay);

    }

    /* _delay_ms(std_delay); */


}


int main(void)
{

    /* DDRA &= ~(1 << DDA7);  // PD0 is now an input */
    /* PORTA |= (1 << PORTA7);   // turn On the Pull-up enabled */
    // port A7, input with pullup resistor

    /* std_delay=50 */

    /* uint8_t stabilize_count=0; */
    /* uint8_t stabilize_delay=5; */
    
    uint8_t adc_channel = 0;

    /* initialize display, cursor off */
    adc_init();


    // Set led outputs
    DDRB |= (1 << DDB0) | (1 << DDB1) | (1 << DDB2)| (1 << DDB3);
    
    // startup delay
    _delay_ms(std_delay);
    
    // Startup led display
    led_test();

    while(1) {

        /* uint16_t x;         */
        /* for ( x = 0; x < 600; x++ ) { //loop 60 sec */

            // loop delay
            /* _delay_ms(std_delay); */
        
            // get the battery level from adc
            battery_level = adc_average(adc_channel);            
            
            display_led_value(battery_level);
            /* set_battery_leds(battery_level); */

        /* } //loop once a min */


    } // infinite loop
} //main