Electronics:Simple circuit for lithium battery charge meter
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
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
- http://web.archive.org/web/20081016212108/http://students.washington.edu/nwk/STK500_sample_program/STK500_sample_program.html
- https://www.avrfreaks.net/forum/attiny26-compatability-stk500
- http://ww1.microchip.com/downloads/en/DeviceDoc/doc1477.pdf
- http://eleccelerator.com/fusecalc/
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