//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) / BoostC compiler
//Personal and non-commercial use encouraged
//Copyright 2007 - David Theunissen - See www.flyelectric.ukgateway.net


//PREPROCESSOR section ========================================================

#include <system.h>			//Generic device include (actual device set in Settings/Target)

//Core configuration option_regs:
#pragma DATA _CONFIG, _FCMEN_OFF & _IESO_OFF & _BOR_ON & _CPD_OFF & _CP_OFF & _MCLRE_OFF & _PWRTE_ON & _WDT_OFF & _INTOSCIO


//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

typedef unsigned char uchar;	//Defines uchar as an unsigned char type (8bits)
typedef unsigned short ushort;	//Defines ushort as an unsigned short type (16bits)


uchar h;						//Startup LED counter

uchar i;						//Timer in ADC conversion
ushort ADCVALUE;				//Results of each ADC conversion

ushort DIV1;					//Resistor divider ADC values (eg: DIV3 is cells 1, 2 and 3)
ushort DIV2;					
ushort DIV3;					
ushort DIV4;					

ushort CELL1;					//Individual cell values (eg: CELL4 is just cell 4)
ushort CELL2;					
ushort CELL3;					
ushort CELL4;					
uchar k;						//Counter to average multiple readings
	
uchar C1;						//Cell value (multiple readings/averaged)
uchar C2;
uchar C3;
uchar C4;

uchar HIGHCELL;					//Highest individual cell value after comparisons
uchar DIFF1;					//Difference from highest cell value
uchar DIFF2;
uchar DIFF3;
uchar DIFF4;

ushort ACCUM1;					//Accumulated value of many readings during Mode 2
ushort ACCUM2;					
ushort ACCUM3;					
ushort ACCUM4;

uchar l;						//Used for counting writes to memory

uchar j;						//Counts steps cells are out of balance
uchar p;						//Determines how many times to flash LED1...
uchar q;
uchar r;
uchar s;

uchar INT;						//Status of push-button switch (used in interrupt)


//Interrupt section ===========================================================

void interrupt()				//Interrupt function when push-button is pressed
{
	INT = 1;					//Set push-button interrupt flag to stop display loops
	intcon.0 = 0;				//RABIF: Clear interrupt bit to allow re-use
}


//FUNCTIONS section ===========================================================

void write_data()				//Function to write to EEPROM flash memory
{
	intcon.7 = 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 eedata 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)
	
    intcon.7 = 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 ucharge (1 = ~14uS)
		pir1.6 = 0;						//Clear the ADC Complete flag
		adcon0.1 = 1;					//GO: Start ADC acquisition
		while(pir1.6==0);				//Wait for conversion to complete (become 1)
		
	//Store ADC value in ADCVALUE (special BoostC statement to create the 10bit value by merging two registers)
		MAKESHORT(ADCVALUE, adresl, adresh);
}
		
//-----------------------------------------------------------------------------

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 = 0b10010101;			//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 = 0b10010001;			//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 = 0b10001001;			//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 = 0b10000101;			//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
		LOBYTE(C1, CELL1);
		LOBYTE(C2, CELL2);
		LOBYTE(C3, CELL3);
		LOBYTE(C4, CELL4);
}
		
//-----------------------------------------------------------------------------

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_reg.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
    pie1.6 = 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
    intcon.7 = 1;				//Enable interrupts globally
    intcon.3 = 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
				
				eedata = C1;					//Write to memory
				write_data();
				eedata = C2;
				write_data();
				eedata = C3;
				write_data();
				eedata = 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)
			LOBYTE(C1, ACCUM1);
			LOBYTE(C2, ACCUM2);
			LOBYTE(C3, ACCUM3);
			LOBYTE(C4, ACCUM4);
			
		//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
