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