//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