// LED_Battery_Meter.c
// Multisegment, multicolor LED voltage meter for 12 & 24 V SLA batteries
// Written by Dale Wheat - 23 July 2008
// 30 jan 2009 - change EIN1D to AIN1D - header file was corrected

// device = ATtiny13
// clock = 9.6 MHz internal RC oscillator
// brown out detect level = 2.7 V
// watchdog timer always on

// input voltage divider = 10 Kohm + 1.8 Kohm resistors:  ratio = 6-5/9:1, or 0.15254x
// 31.24067797 ADC units per volt assuming 5.00 volt reference voltage

#define VOLTS(v) (int)(v * 31.24067797)

///////////////////////////////////////////////////////////////////////////////

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>
#include <avr/wdt.h>

#define nop() asm("nop")

///////////////////////////////////////////////////////////////////////////////
// global variables
///////////////////////////////////////////////////////////////////////////////

volatile union {
	struct {
		unsigned char red1:1;
		unsigned char red2:1;
		unsigned char red3:1;
		unsigned char red4:1;
		unsigned char red5:1;
		unsigned char red6:1;
		unsigned char green1:1;
		unsigned char green2:1;
		unsigned char green3:1;
		unsigned char green4:1;
		unsigned char green5:1;
		unsigned char green6:1;
	};
	unsigned int value;
} LEDs;
		
///////////////////////////////////////////////////////////////////////////////
// initialization functions
///////////////////////////////////////////////////////////////////////////////

// init() - initialize everything

void init(void) __attribute__ ((naked)) __attribute__ ((section(".init3")));
void init(void) {

	// initialize ATtiny13 on-chip watchdog timer

	WDTCR = 1<<WDTIF | 0<<WDTIE | 0<<WDP3 | 1<<WDCE | 1<<WDE | 1<<WDP2 | 1<<WDP1 | 0<<WDP0;

	// initialize ATtiny13 timer/counter

	TCCR0A = 0<<COM0A1 | 0<<COM0A0 | 0<<COM0B1 | 0<<COM0B0 | 0<<WGM01 | 1<<WGM00;
	TCCR0B = 0<<FOC0A | 0<<FOC0B | 0<<WGM02 | 1<<CS02 | 0<<CS01 | 1<<CS00;

	TIMSK0 = 0<<OCIE0B | 0<<OCIE0A | 1<<TOIE0; // interrupts

	// initialize ATtiny13 on-chip analog-to-digital converter

	ADMUX = 0<<REFS0 | 0<<ADLAR | 1<<MUX1 | 0<<MUX0;
	ADCSRA = 1<<ADEN | 1<<ADSC | 0<<ADATE | 1<<ADIF | 0<<ADIE | 1<<ADPS2 | 1<<ADPS1 | 0<<ADPS0;
	DIDR0 = 0<<ADC0D | 1<<ADC2D | 0<<ADC3D | 0<<ADC1D | 0<<AIN1D | 0<<AIN0D;

	sei(); // enable global interrupts
}

///////////////////////////////////////////////////////////////////////////////
// delay functions
///////////////////////////////////////////////////////////////////////////////

void short_delay(void) {

	unsigned char i;

	for(i = 0; i < 16; i++) nop();
}

// blink

void blink(unsigned char on, unsigned char off) {

	if(LEDs.value == on) {
		LEDs.value = off; // blink off
	} else {
		LEDs.value = on; // blink on
	}
}

// the happy dance

void happy_dance(void) {

	static unsigned char charge;

	switch(charge) {

		case 1: charge = 3; break;
		case 3: charge = 7; break;
		case 7: charge = 15; break;
		case 15: charge = 31; break;
		case 31: charge = 63; break;
		default: charge = 1; break;
	}

	LEDs.value = charge << 6;
}

///////////////////////////////////////////////////////////////////////////////
// main() - main program function
///////////////////////////////////////////////////////////////////////////////

void main(void) __attribute__ ((naked, noreturn));
void main(void) {

	// update LEDs continuously in the foreground;  all other tasks execute in the background

	while(1) {

		wdt_reset(); // reset the watchdog timer

		if(LEDs.red1) {
			DDRB = 0<<DDB5 | 0<<DDB4 | 0<<DDB3 | 0<<DDB2 | 1<<DDB1 | 1<<DDB0;
			PORTB = 0<<PORTB5 | 0<<PORTB4 | 0<<PORTB3 | 0<<PORTB2 | 0<<PORTB1 | 1<<PORTB0;
		} else {
			DDRB = 0<<DDB5 | 0<<DDB4 | 0<<DDB3 | 0<<DDB2 | 0<<DDB1 | 0<<DDB0;
			PORTB = 0<<PORTB5 | 0<<PORTB4 | 0<<PORTB3 | 0<<PORTB2 | 0<<PORTB1 | 0<<PORTB0;
		}
		short_delay();

		if(LEDs.red2) {
			DDRB = 0<<DDB5 | 0<<DDB4 | 0<<DDB3 | 1<<DDB2 | 1<<DDB1 | 0<<DDB0;
			PORTB = 0<<PORTB5 | 0<<PORTB4 | 0<<PORTB3 | 0<<PORTB2 | 1<<PORTB1 | 0<<PORTB0;
		} else {
			DDRB = 0<<DDB5 | 0<<DDB4 | 0<<DDB3 | 0<<DDB2 | 0<<DDB1 | 0<<DDB0;
			PORTB = 0<<PORTB5 | 0<<PORTB4 | 0<<PORTB3 | 0<<PORTB2 | 0<<PORTB1 | 0<<PORTB0;
		}
		short_delay();

		if(LEDs.red3) {
			DDRB = 0<<DDB5 | 0<<DDB4 | 1<<DDB3 | 1<<DDB2 | 0<<DDB1 | 0<<DDB0;
			PORTB = 0<<PORTB5 | 0<<PORTB4 | 0<<PORTB3 | 1<<PORTB2 | 0<<PORTB1 | 0<<PORTB0;
		} else {
			DDRB = 0<<DDB5 | 0<<DDB4 | 0<<DDB3 | 0<<DDB2 | 0<<DDB1 | 0<<DDB0;
			PORTB = 0<<PORTB5 | 0<<PORTB4 | 0<<PORTB3 | 0<<PORTB2 | 0<<PORTB1 | 0<<PORTB0;
		}
		short_delay();

		if(LEDs.red4) {
			DDRB = 0<<DDB5 | 0<<DDB4 | 1<<DDB3 | 0<<DDB2 | 0<<DDB1 | 1<<DDB0;
			PORTB = 0<<PORTB5 | 0<<PORTB4 | 1<<PORTB3 | 0<<PORTB2 | 0<<PORTB1 | 0<<PORTB0;
		} else {
			DDRB = 0<<DDB5 | 0<<DDB4 | 0<<DDB3 | 0<<DDB2 | 0<<DDB1 | 0<<DDB0;
			PORTB = 0<<PORTB5 | 0<<PORTB4 | 0<<PORTB3 | 0<<PORTB2 | 0<<PORTB1 | 0<<PORTB0;
		}
		short_delay();

		if(LEDs.red5) {
			DDRB = 0<<DDB5 | 0<<DDB4 | 0<<DDB3 | 1<<DDB2 | 0<<DDB1 | 1<<DDB0;
			PORTB = 0<<PORTB5 | 0<<PORTB4 | 0<<PORTB3 | 0<<PORTB2 | 0<<PORTB1 | 1<<PORTB0;
		} else {
			DDRB = 0<<DDB5 | 0<<DDB4 | 0<<DDB3 | 0<<DDB2 | 0<<DDB1 | 0<<DDB0;
			PORTB = 0<<PORTB5 | 0<<PORTB4 | 0<<PORTB3 | 0<<PORTB2 | 0<<PORTB1 | 0<<PORTB0;
		}
		short_delay();

		if(LEDs.red6) {
			DDRB = 0<<DDB5 | 0<<DDB4 | 1<<DDB3 | 0<<DDB2 | 1<<DDB1 | 0<<DDB0;
			PORTB = 0<<PORTB5 | 0<<PORTB4 | 0<<PORTB3 | 0<<PORTB2 | 1<<PORTB1 | 0<<PORTB0;
		} else {
			DDRB = 0<<DDB5 | 0<<DDB4 | 0<<DDB3 | 0<<DDB2 | 0<<DDB1 | 0<<DDB0;
			PORTB = 0<<PORTB5 | 0<<PORTB4 | 0<<PORTB3 | 0<<PORTB2 | 0<<PORTB1 | 0<<PORTB0;
		}
		short_delay();

		if(LEDs.green1) {
			PORTB = 0<<PORTB5 | 0<<PORTB4 | 0<<PORTB3 | 0<<PORTB2 | 1<<PORTB1 | 0<<PORTB0;
			DDRB = 0<<DDB5 | 0<<DDB4 | 0<<DDB3 | 0<<DDB2 | 1<<DDB1 | 1<<DDB0;
		} else {
			DDRB = 0<<DDB5 | 0<<DDB4 | 0<<DDB3 | 0<<DDB2 | 0<<DDB1 | 0<<DDB0;
			PORTB = 0<<PORTB5 | 0<<PORTB4 | 0<<PORTB3 | 0<<PORTB2 | 0<<PORTB1 | 0<<PORTB0;
		}
		short_delay();

		if(LEDs.green2) {
			PORTB = 0<<PORTB5 | 0<<PORTB4 | 0<<PORTB3 | 1<<PORTB2 | 0<<PORTB1 | 0<<PORTB0;
			DDRB = 0<<DDB5 | 0<<DDB4 | 0<<DDB3 | 1<<DDB2 | 1<<DDB1 | 0<<DDB0;
		} else {
			DDRB = 0<<DDB5 | 0<<DDB4 | 0<<DDB3 | 0<<DDB2 | 0<<DDB1 | 0<<DDB0;
			PORTB = 0<<PORTB5 | 0<<PORTB4 | 0<<PORTB3 | 0<<PORTB2 | 0<<PORTB1 | 0<<PORTB0;
		}
		short_delay();

		if(LEDs.green3) {
			PORTB = 0<<PORTB5 | 0<<PORTB4 | 1<<PORTB3 | 0<<PORTB2 | 0<<PORTB1 | 0<<PORTB0;
			DDRB = 0<<DDB5 | 0<<DDB4 | 1<<DDB3 | 1<<DDB2 | 0<<DDB1 | 0<<DDB0;
		} else {
			DDRB = 0<<DDB5 | 0<<DDB4 | 0<<DDB3 | 0<<DDB2 | 0<<DDB1 | 0<<DDB0;
			PORTB = 0<<PORTB5 | 0<<PORTB4 | 0<<PORTB3 | 0<<PORTB2 | 0<<PORTB1 | 0<<PORTB0;
		}
		short_delay();

		if(LEDs.green4) {
			PORTB = 0<<PORTB5 | 0<<PORTB4 | 0<<PORTB3 | 1<<PORTB2 | 0<<PORTB1 | 1<<PORTB0;
			DDRB = 0<<DDB5 | 0<<DDB4 | 1<<DDB3 | 0<<DDB2 | 0<<DDB1 | 1<<DDB0;
		} else {
			DDRB = 0<<DDB5 | 0<<DDB4 | 0<<DDB3 | 0<<DDB2 | 0<<DDB1 | 0<<DDB0;
			PORTB = 0<<PORTB5 | 0<<PORTB4 | 0<<PORTB3 | 0<<PORTB2 | 0<<PORTB1 | 0<<PORTB0;
		}
		short_delay();

		if(LEDs.green5) {
			PORTB = 0<<PORTB5 | 0<<PORTB4 | 0<<PORTB3 | 1<<PORTB2 | 0<<PORTB1 | 0<<PORTB0;
			DDRB = 0<<DDB5 | 0<<DDB4 | 0<<DDB3 | 1<<DDB2 | 0<<DDB1 | 1<<DDB0;
		} else {
			DDRB = 0<<DDB5 | 0<<DDB4 | 0<<DDB3 | 0<<DDB2 | 0<<DDB1 | 0<<DDB0;
			PORTB = 0<<PORTB5 | 0<<PORTB4 | 0<<PORTB3 | 0<<PORTB2 | 0<<PORTB1 | 0<<PORTB0;
		}
		short_delay();

		if(LEDs.green6) {
			PORTB = 0<<PORTB5 | 0<<PORTB4 | 1<<PORTB3 | 0<<PORTB2 | 0<<PORTB1 | 0<<PORTB0;
			DDRB = 0<<DDB5 | 0<<DDB4 | 1<<DDB3 | 0<<DDB2 | 1<<DDB1 | 0<<DDB0;
		} else {
			DDRB = 0<<DDB5 | 0<<DDB4 | 0<<DDB3 | 0<<DDB2 | 0<<DDB1 | 0<<DDB0;
			PORTB = 0<<PORTB5 | 0<<PORTB4 | 0<<PORTB3 | 0<<PORTB2 | 0<<PORTB1 | 0<<PORTB0;
		}
		short_delay();
	}
}

///////////////////////////////////////////////////////////////////////////////
// timer/counter0 overflow interrupt handler
///////////////////////////////////////////////////////////////////////////////

ISR(TIM0_OVF_vect) {

	static unsigned char i; // sample counter
	static int voltage;
	
	voltage += ADC;
	i++;
	i %= 8;

	ADCSRA = 1<<ADEN | 1<<ADSC | 0<<ADATE | 1<<ADIF | 0<<ADIE | 1<<ADPS2 | 1<<ADPS1 | 0<<ADPS0; // start next conversion

	if(i == 0) {

		voltage >>= 3;

		//  24.00 V  12.00 V
		//   range    range   condition   display
		//  -------  -------  ---------   ------------
		//                                red flash warning
		//  30.00 V  15.00 V  overvoltage
		//                                happy dance
		//  27.00 V  13.50 V  charge
		//                                6 green dots
		//  25.30 V  12.65 V  100% charge
		//                                5 green dots
		//  24.90 V  12.45 V  75%
		//                                4 green dots
		//  24.48 V  12.24 V  50%
		//                                3 yellow dots
		//  24.12 V  12.06 V  25%
		//                                2 red dots
		//  24.00 V  12.00 V  flat
		//                                1 red dot
		//  23.78 V  11.89 V  discharged
		//                                1 flashing red dot
		//  23.00 V  11.50 V  crazy low
		//                                no dots at all

		if(voltage > VOLTS(30.0)) blink(0x02A, 0x015); // overvoltage for this meter // wig wag blink ("uh oh uh oh")
		// 24 V range
		else if(voltage > VOLTS(27.00)) happy_dance();       // charging        // happy dance
		else if(voltage > VOLTS(25.30)) LEDs.value = 0xFC0;  // 100% or more    // 6 green dots
		else if(voltage > VOLTS(24.90)) LEDs.value = 0x7C0;  // 75% or more     // 5 green dots
		else if(voltage > VOLTS(24.48)) LEDs.value = 0x3C0;  // 50% or more     // 4 green dots
		else if(voltage > VOLTS(24.12)) LEDs.value = 0x1C7;  // 25% or more     // 3 yellow dots
		else if(voltage > VOLTS(24.00)) LEDs.value = 0x003;  // flat            // 2 red dots
		else if(voltage > VOLTS(23.78)) LEDs.value = 0x001;  // discharged      // 1 red dot
		else if(voltage > VOLTS(23.00)) blink(0x000, 0x001); // battery failure // blink one red dot
		else if(voltage > VOLTS(15.00)) LEDs.value = 0;      // 23.00 V or less // no dots
		// 12 V range
		else if(voltage > VOLTS(13.50)) happy_dance();       // charging        // happy dance
		else if(voltage > VOLTS(12.65)) LEDs.value = 0xFC0;  // 100% or more    // 6 green dots
		else if(voltage > VOLTS(12.45)) LEDs.value = 0x7C0;  // 75% or more     // 5 green dots
		else if(voltage > VOLTS(12.24)) LEDs.value = 0x3C0;  // 50% or more     // 4 green dots
		else if(voltage > VOLTS(12.06)) LEDs.value = 0x1C7;  // 25% or more     // 3 yellow dots
		else if(voltage > VOLTS(12.00)) LEDs.value = 0x003;  // flat            // 2 red dots
		else if(voltage > VOLTS(11.89)) LEDs.value = 0x001;  // discharged      // 1 red dot
		else if(voltage > VOLTS(11.50)) blink(0x000, 0x001); // battery failure // blink one red dot
		// calibration
		else if(voltage == VOLTS(7.50)) LEDs.value = 0x30C;	 // middle two LEDs red + green
		//else if((voltage <= VOLTS(7.50)) && (voltage >= VOLTS(7.40))) LEDs.value = 0x30C;	 // middle two LEDs red + green
		// or nothing
		else LEDs.value = 0;                                 // 11.50 V or less // no dots for you

		voltage = 0; // reset voltage
	}
}

///////////////////////////////////////////////////////////////////////////////

// [end-of-file]
