//LITHIUM BATTERY TESTER v1

//Measures voltage of individual cells and provides visual indication of relative and absolute differences
//Two modes: (1) Continuous refresh and (2) Three-second recording (eg: while under load in model)
//Mode 2 logs 64 readings for each cell (256 in total) to memory (can be extracted to PC for analysis)

//With 4k7/15k resistor divider has 0.02v 'steps' and 0.02v resolution/error

//'HIGH' LEDs illuminate when cells are within 0.02v (= in balance)
//'MIDDLE' LEDs flash once for every 0.02v below highest values (up to 4 steps)
//'LOW' LEDs flash once for every 0.2v below highest values (up to 4 steps; ignores larger differences)

//Designed for 3 and 4 cell packs (requires 3 to power circuit)

//Mode 1 starts by default
//A single push-button press starts Mode 2 (LEDs show average of full 3 second INTing)
//Another push-button press ends the Mode 2 display and returns to Mode 1

//Written for PIC16F687 (20pin) / CC5X compiler
//Personal and non-commercial use encouraged
//Copyright 2007 - David Theunissen - See www.flyelectric.ukgateway.net


//PREPROCESSOR section ========================================================

#include "16F687.H"				//PIC header file from CC5X
#include "INT16CXX.H"			//Interrupt header file from CC5X
#pragma origin 4				//Locates interrupt location at address 4 as is required
#pragma config = 0b.11.1100.0100//Core device configuration


//VARIABLES section ===========================================================

#define LED4L 	PORTA.0			//RA0 (Pin19)	AN0		LED for cell 4 'low' indicator...
#define LED2M 	PORTA.4			//RA4 (Pin3) 	AN3
#define LED2L 	PORTA.5			//RA5 (Pin2)

#define PUSH 	PORTB.4			//RB4 (Pin13) 	AN10	Push-button switch (push goes low)
#define LED3L 	PORTB.5			//RB5 (Pin12) 	AN11
#define LED4H 	PORTB.6			//RB6 (Pin11)
#define LED1H 	PORTB.7			//RB7 (Pin10)

#define LED4M 	PORTC.2			//RC2 (Pin14) 	AN6
#define LED3M 	PORTC.3			//RC3 (Pin7) 	AN7
#define LED3H 	PORTC.4			//RC4 (Pin6)
#define LED2H 	PORTC.5			//RC5 (Pin5)
#define LED1L 	PORTC.6			//RC6 (Pin8) 	AN8
#define LED1M 	PORTC.7			//RC7 (Pin9) 	AN9

char h;							//Startup LED counter

char i;							//Timer in ADC conversion
uns16 ADCVALUE;					//Results of each ADC conversion

uns16 DIV1;						//Resistor divider ADC values (eg: DIV3 is cells 1, 2 and 3)
uns16 DIV2;					
uns16 DIV3;					
uns16 DIV4;					

uns16 CELL1;					//Individual cell values (eg: CELL4 is just cell 4)
uns16 CELL2;					
uns16 CELL3;					
uns16 CELL4;					
char k;							//Counter to average multiple readings
	
char C1;						//Cell value (multiple readings/averaged)
char C2;
char C3;
char C4;

char HIGHCELL;					//Highest individual cell value after comparisons
char DIFF1;						//Difference from highest cell value
char DIFF2;
char DIFF3;
char DIFF4;

uns16 ACCUM1;					//Accumulated value of many readings during Mode 2
uns16 ACCUM2;					
uns16 ACCUM3;					
uns16 ACCUM4;

char l;							//Used for counting writes to memory

char j;							//Counts steps cells are out of balance
char p;							//Determines how many times to flash LED1...
char q;
char r;
char s;

char INT;						//Status of push-button switch (used in interrupt)


//Interrupt section ===========================================================

interrupt push_button()			//Interrupt function when push-button is pressed
{
	int_save_registers    		//Standard registers saved
	INT = 1;					//Set push-button interrupt flag to stop display loops
	INTCON.0 = 0;				//RABIF: Clear interrupt bit to allow re-use
    int_restore_registers 		//Pre-interrupt register settings restored
}


//2nd PREPROCESSOR section ====================================================

#include "delay_ms.h"			//Delay function used below
								//Placed here to avoid compile errors (due to interrupts)


//FUNCTIONS section ===========================================================

void write_data()				//Function to write to EEPROM flash memory
{
	GIE = 0;					//Disable all interrupts if not done elsewhere
    PIR1.7 = 0;					//EEIF: Clear this bit which gets set after each write
	
	EECON2 = 0x55;				//Required before writing to memory
	EECON2 = 0xAA;				//Required before writing to memory
	EECON1.1 = 1;				//WR: Writes data EEDAT to address EEADR

	while(EECON1.1==1);			//Wait for write to complete (becomes 0 when complete)

	EEADR++;					//Increment the address by 1 for next write (8 bit 0-255)
	
    GIE = 1;					//Enable interrupts if appropriate
}

//-----------------------------------------------------------------------------

void run_adc()							//Function to run ADC conversions (result is in ADRESL)
{
    //Run ADC
		for(i=0; i<1; i++);				//Delay for acquisition capacitor to charge (1 = ~14uS)
		ADIF = 0;						//Clear the ADC Complete flag
		ADCON0.1 = 1;					//GO: Start ADC acquisition
		while(ADIF==0);					//Wait for conversion to complete (become 1)
		
	//Determine ADC value (this is a 10bit value stored in two registers that need merging)
		ADCVALUE = ADRESH;				//Copies upper 2 bits from 8bit register to 16bit
		ADCVALUE = ADCVALUE<<8;			//Moves all bits 8 places left
		ADCVALUE = ADCVALUE|ADRESL;		//| merges lower 8 bits with upper 2
		
}
		
//-----------------------------------------------------------------------------

void cell_values()					//Function to trigger ADC conversions and determine cell values
{
    //ADCON0 structure...			//1  	Right justify ADC values
    								//0  	Use Vdd as reference
       								//xxxx 	Input channel (THIS PART CHANGES BELOW)
    								//0  	ADC capture (GO) not started
    								//1  	ADC enabled

//Determine 1st resistor divider value (= cell 1)
    ADCON0 = 0b.10.0101.01;			//Makes AN5 (pin15) channel active
    run_adc();						//Run ADC conversion
    DIV1 = ADCVALUE;				//Copy result to appropriate register
    
//Determine 2nd resistor divider value (= cells 1 and 2)
    ADCON0 = 0b.10.0100.01;			//Makes AN4 (pin16) channel active
    run_adc();						//Run ADC conversion
    DIV2 = ADCVALUE;				//Copy result to appropriate register
    
//Determine 3rd resistor divider value (= cells 1, 2 and 3)
    ADCON0 = 0b.10.0010.01;			//Makes AN2 (pin17) channel active
    run_adc();						//Run ADC conversion
    DIV3 = ADCVALUE;				//Copy result to appropriate register
    
//Determine 4th resistor divider value (= cells 1, 2, 3 and 4)
	ADCON0 = 0b.10.0001.01;			//Makes AN1 (pin18) channel active
	run_adc();						//Run ADC conversion
    DIV4 = ADCVALUE;				//Copy result to appropriate register
    
}

//-----------------------------------------------------------------------------

void measure_cells()				//Function to measure all cells
{
	CELL1 = 0;						//Initialise cell value stores
	CELL2 = 0;
	CELL3 = 0;
	CELL4 = 0;

  	for(k=0; k<32; k++)				//Measure cell values 'n' times to smooth results (don't go over 64)
	{
		cell_values();				//Determine cell values (resistor dividers actually)
		CELL1 += DIV1;				//Sum each iteration for each cell
		CELL2 += DIV2;
		CELL3 += DIV3;
		CELL4 += DIV4;
	}
		
  	//Determine individual cell values (eg: deduct cells 1-3 from cells 1-4 = Cell 4 alone)
		if (CELL4>CELL3) CELL4 -= CELL3;	//This logic tries to avoids problems with no 4th cell attached
		else CELL4=0;
		
		CELL3 -= CELL2;
		CELL2 -= CELL1;
	
	//Divide by 32 to average results and use less space when written to EEPROM memory
		CELL1 = CELL1>>5;
		CELL2 = CELL2>>5;
		CELL3 = CELL3>>5;
		CELL4 = CELL4>>5;
		
	//Copy into a common variable used to display results
		C1 = CELL1.low8;
		C2 = CELL2.low8;
		C3 = CELL3.low8;
		C4 = CELL4.low8;
}
		
//-----------------------------------------------------------------------------

void led_init()								//Function to initialise all LEDs
{
	LED1H=0; LED1M=0; LED1L=0;
	LED2H=0; LED2M=0; LED2L=0;
	LED3H=0; LED3M=0; LED3L=0;
	LED4H=0; LED4M=0; LED4L=0;
}

//-----------------------------------------------------------------------------

void display_results()						//Function to display results via LEDs
{
	//Find highest cell (start with Cell 1 and then compare against others)
	//CELL1 etc is 16 bit for the 32 measurements; only want lower 8 after these been averaged in measure_cells()
		HIGHCELL = C1;
	   	if (C2>HIGHCELL) HIGHCELL = C2;
	   	if (C3>HIGHCELL) HIGHCELL = C3;
		if (C4>HIGHCELL) HIGHCELL = C4;

	//Determine differences from highest
		DIFF1=0; DIFF2=0; DIFF3=0; DIFF4=0; 	//Initialise
		
		DIFF1 = HIGHCELL - C1;
		DIFF2 = HIGHCELL - C2;
		DIFF3 = HIGHCELL - C3;
		if (C4>0) DIFF4 = HIGHCELL - C4;	//Only if Cell 4 exists
				
	//Create a two-tiered scale (<0.1v differences ('M' LEDs) and >0.1v ('L' LEDs))
		if (DIFF1<=4) p = DIFF1;				//Used for 'M' LEDs (0.02v per flash)
		if (DIFF1>=5 && DIFF1<=9) p = 5;		//Used for 'L' LEDs (0.2v per flash)
		if (DIFF1>=10 && DIFF1<=14) p = 6;
		if (DIFF1>=15 && DIFF1<=19) p = 7;
		if (DIFF1>=20) p=8;
		
		if (DIFF2<=4) q = DIFF2;
		if (DIFF2>=5 && DIFF2<=9) q = 5;
		if (DIFF2>=10 && DIFF2<=14) q = 6;
		if (DIFF2>=15 && DIFF2<=19) q = 7;
		if (DIFF2>=20) q=8;
		
		if (DIFF3<=4) r = DIFF3;
		if (DIFF3>=5 && DIFF3<=9) r = 5;
		if (DIFF3>=10 && DIFF3<=14) r = 6;
		if (DIFF3>=15 && DIFF3<=19) r = 7;
		if (DIFF3>=20) r=8;
		
		if (DIFF4<=4) s = DIFF4;
		if (DIFF4>=5 && DIFF4<=9) s = 5;
		if (DIFF4>=10 && DIFF4<=14) s = 6;
		if (DIFF4>=15 && DIFF4<=19) s = 7;
		if (DIFF4>=20) s=8;
		
	
	//Illuminate 'High' LEDs (no flashing) for all cells equal to highest (ie: all within 1 ADC step = 0.02v)
		led_init();
		if (DIFF1==0) LED1H=1;
		if (DIFF2==0) LED2H=1;
		if (DIFF3==0) LED3H=1;
		if (C4>0 && DIFF4==0) LED4H=1;	//Only if cell 4 exists

	//Flash 'M' LEDs for <0.1v (once per 0.02v) and 'L' for >0.1v (once per 0.2v)
		
		//Cell 1
			j = DIFF1;					//Flash up to the number of times cell is 'different' from highest
			if (j>4) j=4;				//Limit LED flashes to 4
			while (j>0)					//Count down (& flash) appropriate number of times
			{
				if (DIFF1>=1 && DIFF1<=4 && p>0) LED1M=1;	//0.02, 0.04, 0.06 or 0.08v different
				if (DIFF1>=5 && p>4) LED1L=1;				//0.2, 0.4, 0.6 or >0.8v
				delay_ms(300);
				LED1M=0; LED1L=0;
				delay_ms(125);
				p--;
				j--;
			}
			
		//Cell 2
			j = DIFF2;
			if (j>4) j=4;
			while (j>0)
			{
				if (DIFF2>=1 && DIFF2<=4 && q>0) LED2M=1;
				if (DIFF2>=5 && q>4) LED2L=1;
				delay_ms(300);
				LED2M=0; LED2L=0;
				delay_ms(125);
				q--;
				j--;
			}
			
		//Cell 3
			j = DIFF3;
			if (j>4) j=4;
			while (j>0)
			{
				if (DIFF3>=1 && DIFF3<=4 && r>0) LED3M=1;
				if (DIFF3>=5 && r>4) LED3L=1;
				delay_ms(300);
				LED3M=0; LED3L=0;
				delay_ms(125);
				r--;
				j--;
			}
				
		//Cell 4
			if (C4>0)			//If Cell 4 exists
			{
				j = DIFF4;
				if (j>4) j=4;
				while (j>0)
				{
					if (DIFF4>=1 && DIFF4<=4 && s>0) LED4M=1;
					if (DIFF4>=5 && s>4) LED4L=1;
					delay_ms(300);
					LED4M=0; LED4L=0;
					delay_ms(125);
					s--;
					j--;
				}
			}
		
		delay_ms(750);
		
}
		
//===========================================================================

void main()						//main program
{
//Initialise all ports
	PORTA=0; PORTB=0; PORTC=0;
	
//Main port settings (1=Input 0=Output)
	TRISA.0 = 0;				//RA0 (Pin19)	AN0		LED for cell 4 'low' indicator...
	TRISA.1 = 1;				//RA1 (Pin18) 	AN1		Cell 4 input
	TRISA.2 = 1;				//RA2 (Pin17) 	AN2		Cell 3 input
//	TRISA.3 = 1;				//RA3 (Pin4)			Not used
	TRISA.4 = 0;				//RA4 (Pin3) 	AN3
	TRISA.5 = 0;				//RA5 (Pin2)
	
	TRISB.4 = 1;				//RB4 (Pin13) 	AN10	Push-button switch (push goes low)
	TRISB.5 = 0;				//RB5 (Pin12) 	AN11
	TRISB.6 = 0;				//RB6 (Pin11)
	TRISB.7 = 0;				//RB7 (Pin10)

	TRISC.0 = 1;				//RC0 (Pin16) 	AN4		Cell 2 input
	TRISC.1 = 1;				//RC1 (Pin15) 	AN5		Cell 1 input
	TRISC.2 = 0;				//RC2 (Pin14) 	AN6
	TRISC.3 = 0;				//RC3 (Pin7) 	AN7
	TRISC.4 = 0;				//RC4 (Pin6)
	TRISC.5 = 0;				//RC5 (Pin5)
	TRISC.6 = 0;				//RC6 (Pin8) 	AN8
	TRISC.7 = 0;				//RC7 (Pin9) 	AN9

//ADC port settings (1=Analog 0=Digital)
	ANSEL.0 = 0;				//RA0 (Pin19)	AN0		LED for cell 4 'low' indicator...
	ANSEL.1 = 1;				//RA1 (Pin18) 	AN1		Cell 4 input
	ANSEL.2 = 1;				//RA2 (Pin17) 	AN2		Cell 3 input
	ANSEL.3 = 0;				//RA4 (Pin3) 	AN3
	ANSEL.4 = 1;				//RC0 (Pin16) 	AN4		Cell 2 input
	ANSEL.5 = 1;				//RC1 (Pin15) 	AN5		Cell 1 input
	ANSEL.6 = 0;				//RC2 (Pin14) 	AN6
	ANSEL.7 = 0;				//RC3 (Pin7) 	AN7
	ANSELH.0 = 0;				//RC6 (Pin8) 	AN8
	ANSELH.1 = 0;				//RC7 (Pin9) 	AN9
	ANSELH.2 = 0;				//RB4 (Pin13) 	AN10	Push-button switch (push goes low)
	ANSELH.3 = 0;				//RB5 (Pin12) 	AN11

//Weak pullups
	OPTION.7 = 0;				//Weak pullups enabled globally
	WPUB.4 = 1;					//For RB4 push-button (holds pin high normally; goes to ground when pressed)
	
//Static ADC settings
    ADCON1 = 101;				//AD Conversion Clock Fosc/16
    ADIE = 0;					//ADC interrupts disabled
  
//Static EEPROM data logging settings
    EECON1.7 = 0;				//EEPGD: Access data memory
    EECON1.3 = 0;				//WRERR: clear on startup in case previously set
	EECON1.2 = 1;				//WREN: enable writing to memory

//Flash 3 LED times on startup
	led_init();
	for (h=0; h<3; h++)
	{
		LED1H = 1;
		delay_ms(250);
		LED1H = 0;
		delay_ms(250);
	}

//Static Interrupt settings
    GIE = 1;					//Enable interrupts globally
    RABIE = 1;					//Enable Port A and B interrupts
    IOCB.4 = 1;					//Enable RB4 (pin13) only


//-----------------------------------------------------------------------------
	
    while(1)
    {
	    
	//Mode 1 (keep measuring and displaying results - does not write to memory)
	    INT = 0;								//Set the interrupt flag
		while (INT==0)							//The push-button interrupt will break this loop
		{
			measure_cells();					//Measure all cells
		    display_results();					//Display results via LEDs
		}

		
	//Mode 2 (measure each cell 64 times (to maximise use of EEPROM memory), average and display results)
	
	    //Initialise ADC ports and variables
			ACCUM1=0; ACCUM2=0; ACCUM3=0; ACCUM4=0;
			led_init();							//Initialise all LEDs
		    EEADR = 0;							//Address to be used for first write to memory

		//Measure 64 times and accumulate these for display purposes
			for (l=0; l<64; l++)				//Iterate 64 times to fill memory but not over-write
			{
				measure_cells();				//Measure all cells
				
				EEDAT = C1;						//Write to memory
				write_data();
				EEDAT = C2;
				write_data();
				EEDAT = C3;
				write_data();
				EEDAT = C4;
				write_data();
				
				ACCUM1 += C1;					//Add all 64 measurements together (used in display)
				ACCUM2 += C2;
				ACCUM3 += C3;
				ACCUM4 += C4;
			}

		//Average the 64 accumulated readings
			ACCUM1 /= 64;
			ACCUM2 /= 64;
			ACCUM3 /= 64;
			ACCUM4 /= 64;
		
		//Copy results into same variables as used in Mode 1 above (to make it easier to display)
			C1 = ACCUM1.low8;
			C2 = ACCUM2.low8;
			C3 = ACCUM3.low8;
			C4 = ACCUM4.low8;
		
		//Display results until interrupted
			INT = 0;								//Set the interrupt flag
	    	while (INT==0)							//The push-button interrupt will break theis loop
			{
				display_results();					//Display results via LEDs
			}
				
	}									//end while
}										//end main
