program Universal; //=============================================================================================================================================== // PIC pin assignment // // PORTA.0 -> AN0 (battery voltage multiplied by R6/(R5+R6)) (analog IN) // PORTA.1 -> AN1 (current pickup) (analog IN) // PORTA.2 -> spare (OUT) // PORTA.3 -> spare (Analog IN may be temperature reading) // PORTA.4 -> BUZZER (OUT) // PORTA.5 -> spare (OUT) // // PORTB.0 -> FAN cooler (OUT) // PORTB.1 -> D4 data bus for display (OUT) // PORTB.2 -> D5 data bus for display (OUT) // PORTB.3 -> D6 data bus for display (OUT) // PORTB.4 -> D7 data bus for display (OUT) // PORTB.5 -> UP key (IN) // PORTB.6 -> DOWN key (IN) // PORTB.7 -> OK/ENTER key (IN) // // PORTC.0 -> spare (OUT) // PORTC.1 -> PWM2 DISCHARGE (OUT) // PORTC.2 -> PWM1 CHARGE (OUT) // PORTC.3 -> RS register/data for display (OUT) // PORTC.4 -> RW read/write for display (OUT) // PORTC.5 -> E enable for display (OUT) // PORTC.6 -> TX RS232 trasmit (OUT) // PORTC.7 -> RX RS232 receive (IN) //=============================================================================================================================================== //=============================================================================================================================================== // Constants //=============================================================================================================================================== const repeat_period = 16; //repeat key period -> 16*5ms = 80ms (about 12 press/sec) repeat_initial = 200; //initial delay for start autorepeat -> 200*5ms = 1000ms key_pressed = 10; //time to hit a key for a valid action -> 10*5ms = 50ms ctrl_period = 1; //CV charge BW control 1*5ms -> 200Hz BW (max) // EEPROM Memory map // 247..255 //spare locations uni_profile = 246; //Position for the selected profile in EEPROM uni_maxcharge = 245; //Maximum allowable charge current 255 -> 255A uni_maxdischarge = 244; //Maximum allowable discharge current 255 -> 255A uni_beep = 243; //beep tone semiperiod 0..255 uni_r6h = 242; // uni_r6l = 241; //R5=0..65535 in Ohm uni_r5h = 240; // uni_r5l = 239; //R6=0..65535 in Ohm uni_currh = 238; // uni_currl = 237; //current pick up sensitivity 0..65535 -> 65535uV/A uni_mode = 236; //idle, charge or discharge mode, see below constants uni_secondline = 220; //second hello line uni_firstline = 204; //first hello line // 0..203 //space for the 12 profiles // Single profile map profilelen = 17; //single profile length pchemistry = 0; //parameters position in a specific profile: Chemistry pcapacity = 1; //capacity in mAh/100, 255 -> 25500mAh pncells = 2; //Number of cells pcharge = 3; //charge in capacity units 255 -> 25.5*capacity pdischarge = 4; //discharge in capacity units 255 -> 25.5*capacity pinhibit = 5; //Inhibit in minutes for delta peak check: 255 -> 255 min pcutoff_nicd = 6; //Cutoff in discharge for NiMh pcutoff_nimh = 7; //Cutoff in discharge for NiCd pcutoff_lipo = 8; //Cutoff in discharge for LiPo pcutoff_sla = 9; //Cutoff in discharge for SLA pdeltav_nicd = 10; //deltav in charge for NiCd pdeltav_nimh = 11; //deltav in charge for NiMh pmaxv_lipo = 12; //max voltage in charge for LiPo pmaxv_sla = 13; //max voltage in charge for SLA pfinalcurr_lipo = 14; //final current (% of initial) for LiPo pfinalcurr_sla = 15; //final current (% of initial) for SLA ptimeout = 16; //Timeout for charge // Constants for battery identification nicd = 0; //battery codes: Nickel Cadmium nimh = 1; //Nickel Metal Hydride lipo = 2; //Lithium Polimer sla = 3; //Sealed Lead Acid // Constants for charger action identification idle = 0; //idle phase (for interrupt routine) discharge_cc = 1; //discharge at costant current (for interrupt routine) charge_cc = 2; //charge at costant current (for interrupt routine) charge_cv = 3; //charge at costant voltage (for interrupt routine) // Constants for charger last action identification mode_idle = 0; //idle mode for power interrupt mode_charge = 1; //charge phase for power interrupt mode_discharge = 2; //discharge phase for power interrupt //=============================================================================================================================================== // Display messages //=============================================================================================================================================== msg_hello1='.seven-segments.'; msg_hello2='www. .com'; msg_main1='Task select: '; msg_main2='Profile select '; msg_main3='Batt. charge '; msg_main4='Batt. discharge '; msg_main7='Profile change '; msg_main8='PC management '; msg_main9='Volt calibr. '; msg_main10='Ampere calibr. '; msg_change1='Chemistry: '; msg_change2='Battery type '; msg_change3='NiCd'; msg_change4='NiMh'; msg_change5='LiPo'; msg_change6='SLA '; msg_change7='Capacity: '; msg_change8='mAh per cell '; msg_change9='Cells: '; msg_change10='Number of cells '; msg_change11='Charge: '; msg_change12='mult. for capac.'; msg_change13='Discharge: '; msg_change15='Name: '; msg_pcman1='PC serial link..'; msg_discharge='Dsch.'; msg_charge='Chrg.'; msg_end='End!'; msg_display='Pack#'; msg_init='Initializing... '; //=============================================================================================================================================== // Variables //=============================================================================================================================================== var i:byte; //generic interrupt tempw:word; tempi:integer; ii,j,k,l,m,n,act:byte; //generic program tw,tw1,tw2,vrat,pk,maxcap,ilim:word; tlw,tlw1:longint; twi:integer; tmpword:string[5]; tmpbyte:string[3]; repeat_counter:byte; //keys repeat_flag:byte; repeat_k1,repeat_k2,repeat_k3:byte; keys:byte; msec,sec,minu:byte; //time count_slow:byte; //analog values fast_voltage:word; slow_voltage:word; acc_voltage:longint; fast_current:word; slow_current:word; acc_current:longint; action:byte; //task to be performed: charge, discharge... duty_pwm_charge,duty_pwm_discharge:integer; //pwm control (mosfet drive) target_current:word; //target current for costant current phases target_voltage:word; //target voltage for costant voltage phases zero_current:word; mah:longint; //amount of charge menupos:byte; //top menu position selprof:byte; //profile selection index selitem:byte; //item selected within the change profile routine profile_point:byte; //current profile selected eevar,eelim,eeres:byte; //eeprom variables phase,q,ps:byte; //current phase for power interrupt handling pt,pw:word; bdelay:byte; cbeep,dbeep,ebeep,kk:byte; calibra:byte; tempib:integer; slow_voltageb,pwmb:word; mahb:longint; pwm_err:byte; ctrl_co,ctrl_flag:byte; //=============================================================================================================================================== // PWM adjust //=============================================================================================================================================== procedure setpwm; begin CCPR1L:=duty_pwm_charge shr 2; //PWM1 is phisically connected to the charge FET CCP1CON.5:=duty_pwm_charge.1; CCP1CON.4:=duty_pwm_charge.0; CCPR2L:=duty_pwm_discharge shr 2; //PWM2 is phisically connected to the discharge FET CCP2CON.5:=duty_pwm_discharge.1; CCP2CON.4:=duty_pwm_discharge.0; end; //=============================================================================================================================================== // Interrupt, every 5msec -> keyboard, sampling ANx, control loops for voltage and current, TX UART //=============================================================================================================================================== procedure interrupt; begin if INTCON.2 = 1 then begin TMR0 := 0x3D; //an interrupt every 5ms INTCON.2:=0; //=============================================================================================================================================== // Internal clock //=============================================================================================================================================== inc(msec); //clock if (msec=200) then begin msec:=0; inc(sec); //one second is 200 interrupts end; if (sec=60) then begin sec:=0; inc(minu); //one minute is 60 seconds end; //=============================================================================================================================================== // This unit controls the bndwitdh for CV charge //=============================================================================================================================================== inc(ctrl_co); if (ctrl_co=ctrl_period) then begin ctrl_co:=0; ctrl_flag:=1; //When this flag is high the CV control will be peformed end else begin ctrl_flag:=0; end; //=============================================================================================================================================== // Keyboard management //=============================================================================================================================================== inc(repeat_counter); //counter for key repetition if (repeat_counter=repeat_period) then begin repeat_counter:=0; repeat_flag:=1; //when this flag is high is simulated a "key hit" end else begin repeat_flag:=0; end; if PORTB.6 = 0 then //if one key has been pressed, (this is the "DOWN" or "-" key) begin if (repeat_k1<>repeat_initial) then inc(repeat_k1); //increase the "key pressed" counter if (repeat_k1=key_pressed) then begin keys.0:=1; end; if (repeat_k1=repeat_initial) then begin if (repeat_flag=1) then keys.0:=1; end; end else begin repeat_k1:=0; end; if PORTB.5 = 0 then begin if (repeat_k2<>repeat_initial) then inc(repeat_k2); //increase the "key pressed" counter (this is the "UP" or "+" key) if (repeat_k2=key_pressed) then begin keys.1:=1; end; if (repeat_k2=repeat_initial) then begin if (repeat_flag=1) then keys.1:=1; end; end else begin repeat_k2:=0; end; if PORTB.7 = 0 then begin if (repeat_k3<>repeat_initial) then inc(repeat_k3); //increase the "key pressed" counter (this is the "OK" or "NEXT" key) if (repeat_k3=key_pressed) then begin keys.2:=1; end; if (repeat_k3=repeat_initial) then begin if (repeat_flag=1) then keys.2:=1; end; end else begin repeat_k3:=0; end; //=============================================================================================================================================== // sampling the cells voltage and current //=============================================================================================================================================== tempw:=0; ADCON0:=0x81; delay_us(5); for i:=0 to 63 do begin ADCON0.2:=1; repeat until ADCON0.2=0; tempw:=tempw+ADRESL; tempw:=tempw+(ADRESH shl 8); end; fast_voltage:=tempw; //voltage and current @16bits acc_voltage:=acc_voltage+tempw; tempw:=0; ADCON0:=0x89; delay_us(5); for i:=0 to 63 do begin ADCON0.2:=1; repeat until ADCON0.2=0; tempw:=tempw+ADRESL; tempw:=tempw+(ADRESH shl 8); end; fast_current:=tempw; //voltage and current @16bits acc_current:=acc_current+tempw; inc(count_slow); if (count_slow=0) then begin slow_voltage:=acc_voltage shr 8; //voltage and current @16bits "high precision" slow_current:=acc_current shr 8; //voltage and current @16bits "high precision" acc_voltage:=0; acc_current:=0; end; case (action) of //choice for different operating modes //=============================================================================================================================================== // Discharge at constant current (all the batteries) //=============================================================================================================================================== discharge_cc:begin //discharge control loop tempi:=fast_current-zero_current; if (tempi<0) then tempi:=0; mah:=mah+tempi; if (tempi>target_current) then //modulate the PWM following the discharge current changes begin dec(duty_pwm_discharge); if (duty_pwm_discharge<0) then duty_pwm_discharge:=0; end else begin inc(duty_pwm_discharge); if (duty_pwm_discharge>1023) then begin duty_pwm_discharge:=1023; pwm_err:=1; //if the PWM is at the maximum value, there is an error end; end; end; //=============================================================================================================================================== // Charge at constant current (NiMh and NiCd all the time, LiPo and SLA only the first period) //=============================================================================================================================================== charge_cc:begin //charge cc control loop tempi:=zero_current-fast_current; if (tempi>0) then mah:=mah+tempi; if (tempi>target_current) then //modulate the PWM following the charge current changes begin dec(duty_pwm_charge); if (duty_pwm_charge<0) then duty_pwm_charge:=0; end else begin inc(duty_pwm_charge); if (duty_pwm_charge>1023) then begin duty_pwm_charge:=1023; pwm_err:=1; //if the PWM is at the maximum value, there is an error end; end; end; //=============================================================================================================================================== // Charge at costant voltage (LiPo and SLA second period) //=============================================================================================================================================== charge_cv:begin //charge cv control loop tempi:=zero_current-fast_current; if (tempi>0) then mah:=mah+tempi; if (tempi>target_current) then begin dec(duty_pwm_charge); //In any case saturate at the maximum charge current if (duty_pwm_charge<0) then duty_pwm_charge:=0; end else begin if (ctrl_flag=1) then //decrease the bandwidth begin if (fast_voltage>target_voltage) then //modulate the PWM following the cells voltage changes begin dec(duty_pwm_charge); if (duty_pwm_charge<0) then duty_pwm_charge:=0; end else begin inc(duty_pwm_charge); if (duty_pwm_charge>1023) then begin duty_pwm_charge:=1023; pwm_err:=1; //if the PWM is at the maximum value, there is an error end; end; end; end; end //=============================================================================================================================================== // Idle //=============================================================================================================================================== else //otherwise reset all begin duty_pwm_discharge:=0; duty_pwm_charge:=0; end; end; setpwm; //update the PWM hardware with the most recent values //=============================================================================================================================================== // during any charge or discharge TX via serial I/F the values //=============================================================================================================================================== if (action<>0) then begin i:=count_slow and 15; case i of 0:TXREG:=0x55; //4 0x55 values for syncing with PC 1:TXREG:=0x55; 2:TXREG:=0x55; 3:begin TXREG:=0x55; //latches the multibyte values if (action=1) then begin tempib:=slow_current-zero_current; pwmb:=duty_pwm_discharge; end else begin tempib:=zero_current-slow_current; pwmb:=duty_pwm_charge; end; slow_voltageb:=slow_voltage; mahb:=mah; end; 4:TXREG:=hi(pwmb); //output of all the parameters, one for 5ms slot, total 16*5=80ms 5:TXREG:=lo(pwmb); 6:TXREG:=hi(tempib); 7:TXREG:=lo(tempib); 8:TXREG:=hi(slow_voltageb); 9:TXREG:=lo(slow_voltageb); 10:TXREG:=highest(mahb); 11:TXREG:=higher(mahb); 12:TXREG:=hi(mahb); 13:TXREG:=lo(mahb); 14:TXREG:=minu; 15:TXREG:=sec; end; end; end; end; //=============================================================================================================================================== // Procedure delay in milliseconds //=============================================================================================================================================== procedure delayms(a:byte); begin for bdelay:=1 to a do delay_ms(1); end; //=============================================================================================================================================== // procedure click for key feedback //=============================================================================================================================================== procedure click; begin for ebeep:=1 to 5 do begin PORTA.4:=ebeep.0; delayms(1); end; end; //=============================================================================================================================================== // procedure single beep at user defined frequency //=============================================================================================================================================== procedure beep; begin INTCON.GIE:=0; cbeep:=eeprom_read(uni_beep); for dbeep:=1 to 201 do begin PORTA.4:=dbeep.0; for ii:=1 to cbeep do delay_us(10); end; INTCON.GIE:=1; end; //=============================================================================================================================================== // Three beep for end of charge and discharge //=============================================================================================================================================== procedure tribeep; begin for kk:=1 to 3 do begin beep; delayms(200); end; end; //=============================================================================================================================================== // Display management routines //=============================================================================================================================================== procedure lcd4(a,b:byte); //a command code, b=0 command, b=1 data; begin PORTC.3:=b.0; PORTC.4:=0; PORTB.1:=a.4; PORTB.2:=a.5; PORTB.3:=a.6; PORTB.4:=a.7; PORTC.5:=1; //generic 4 bit command or data write PORTC.5:=0; PORTB.1:=a.0; PORTB.2:=a.1; PORTB.3:=a.2; PORTB.4:=a.3; PORTC.5:=1; PORTC.5:=0; delay_us(1000); end; procedure lcd8(a:byte); begin PORTC.3:=0; PORTC.4:=0; PORTB.1:=a.4; PORTB.2:=a.5; //generic 8 bit command write PORTB.3:=a.6; PORTB.4:=a.7; PORTC.5:=1; PORTC.5:=0; end; procedure cls; begin lcd4(1,0); delayms(5); end; procedure lcd_df_config; begin INTCON.GIE:=0; delayms(1); //start with 8 bit interface lcd8(0x30); delayms(10); lcd8(0x30); delayms(10); lcd8(0x30); delayms(10); lcd8(0x20); delayms(10); lcd4(0x28,0); //now we are on 4 bit interface lcd4(0x0C,0); lcd4(6,0); cls; delayms(250); lcd4(0x80,0); lcd4(32,1); //write a space to test cls; //clear all INTCON.GIE:=1; end; procedure lcd_df_out(a:byte;p:word); begin pt:=p; lcd4(a,0); repeat pw:=flash_read(pt); q:=lo(pw); if (q<>0) then lcd4(q,1); inc(pt); until (q=0); end; procedure lcd_df_out_eeprom(a:byte;p:byte); begin ps:=p; lcd4(a,0); for j:=0 to 15 do begin q:=eeprom_read(ps+j); lcd4(q,1); end; end; procedure lcd_df_char(a,c:byte); //a:command code, b:character begin lcd4(a,0); lcd4(c,1); end; //=============================================================================================================================================== // Main menu for functions activation //=============================================================================================================================================== procedure main_menu; begin repeat lcd_df_out(128,@msg_main1); //clear display and write msg_main1 case menupos of 0:lcd_df_out(192,@msg_main2); 1:lcd_df_out(192,@msg_main3); 2:lcd_df_out(192,@msg_main4); 3:lcd_df_out(192,@msg_main7); 4:lcd_df_out(192,@msg_main8); 5:lcd_df_out(192,@msg_main9); 6:lcd_df_out(192,@msg_main10); end; keys:=0; repeat until (keys<>0); click; if (keys.0=1) then begin dec(menupos); if (menupos=255) then menupos:=0; end; if (keys.1=1) then begin inc(menupos); if (menupos=7) then menupos:=6; end; until (keys.2=1); end; //=============================================================================================================================================== // Procedure display profile //=============================================================================================================================================== procedure lcd_profile; begin lcd_df_out(128,@msg_display); l:=selprof; inc(l); bytetostr(l,tmpbyte); lcd_df_char(133,tmpbyte[1]); lcd_df_char(134,tmpbyte[2]); lcd_df_char(135,':'); l:=eeprom_read(profile_point+pchemistry); case l of nicd:lcd_df_out(136,@msg_change3); nimh:lcd_df_out(136,@msg_change4); lipo:lcd_df_out(136,@msg_change5); sla:lcd_df_out(136,@msg_change6); end; lcd_df_char(141,'x'); l:=eeprom_read(profile_point+pncells); bytetostr(l,tmpbyte); lcd_df_char(142,tmpbyte[1]); lcd_df_char(143,tmpbyte[2]); l:=eeprom_read(profile_point+pcapacity); tw:=l*100; wordtostr(tw,tmpword); lcd_df_char(192,'K'); lcd_df_char(193,tmpword[0]); lcd_df_char(194,tmpword[1]); lcd_df_char(195,tmpword[2]); lcd_df_char(196,tmpword[3]); lcd_df_char(197,tmpword[3]); l:=eeprom_read(profile_point+pcharge); bytetostr(l,tmpbyte); lcd_df_char(198,'C'); lcd_df_char(199,tmpbyte[0]); lcd_df_char(200,tmpbyte[1]); lcd_df_char(201,'.'); lcd_df_char(202,tmpbyte[2]); l:=eeprom_read(profile_point+pdischarge); bytetostr(l,tmpbyte); lcd_df_char(203,'D'); lcd_df_char(204,tmpbyte[0]); lcd_df_char(205,tmpbyte[1]); lcd_df_char(206,'.'); lcd_df_char(207,tmpbyte[2]); end; //=============================================================================================================================================== // Battery profile selection //=============================================================================================================================================== procedure selprofile; begin selprof:=eeprom_read(uni_profile); repeat lcd_profile; keys:=0; //reset the key pressed and wait for one key repeat until (keys<>0); click; if (keys.0=1) then begin dec(selprof); //key down -> decrease the profile pointer if (selprof=255) then selprof:=0; profile_point:=selprof*profilelen; end; if (keys.1=1) then begin inc(selprof); //key up -> increase the profile pointer if (selprof=12) then selprof:=11; profile_point:=selprof*profilelen; end; until (keys.2=1); eeprom_write(uni_profile,selprof); //update the profile selector EEPROM location end; //=============================================================================================================================================== // procedure A to ADU and viceversa //=============================================================================================================================================== function atoadu:word; //tw1 argument in A*100,result in ADU begin tw:=eeprom_read(uni_currh)*256+eeprom_read(uni_currl); //tw holds the uV/A for the current to voltage transducer (25000 for LTS25NP) tlw:=tw1*tw; result:=tlw/7629; end; function adutoa:word; //tw1 argument in ADU, result in A*100 begin tw:=eeprom_read(uni_currh)*256+eeprom_read(uni_currl); tlw:=tw1*7629; result:=tlw/tw; end; //=============================================================================================================================================== // procedure mV to ADU and viceversa //=============================================================================================================================================== function mvtoadu:word; //tw1 argument in mV, result in ADU begin vrat:=(eeprom_read(uni_r5h) shl 8)+eeprom_read(uni_r5l); //R5 tw:=(eeprom_read(uni_r6h) shl 8)+eeprom_read(uni_r6l); //R6 tlw:=tw shl 12; //R6*4096 tlw1:=tw+vrat; //R5+R6 tlw:=tlw/tlw1; //R5*4096/(R5+R6) tlw:=(tw1*tlw) shl 1; //mV*(R6/(R5+R6))*65536 result:=tlw/625; //mV*(R6/(R5+R6))*(65536/5000) end; function adutomv:word; //tw1 argument in ADU,result in mV begin vrat:=(eeprom_read(uni_r5h) shl 8)+eeprom_read(uni_r5l); //R5 tw:=(eeprom_read(uni_r6h) shl 8)+eeprom_read(uni_r6l); //R6 tlw:=vrat shl 12; //R5*4096 tlw:=4096+(tlw/tw); //K=(1+R5/R6)*4096 tlw:=tlw*tw1; //ADU*4096*(1+R5/R6)/4096 result:=tlw/53687; //ADU*(1+R5/R6)*(5000/65536) end; //=============================================================================================================================================== // procedure mah ADU to display mah //=============================================================================================================================================== function recallmah:word; //result in mah begin tw:=highest(mah) shl 8; tw:=tw+higher(mah); tlw:=6944*tw; tw:=eeprom_read(uni_currh)*256+eeprom_read(uni_currl); tlw:=tlw/tw; result:=tlw; end; //=============================================================================================================================================== // procedure prepare display for charge and discharge (& other) //=============================================================================================================================================== procedure prepdis(a:byte); begin lcd_df_out(128,@msg_init); for ii:=1 to 30 do delayms(100); zero_current:=slow_current; //sample the "no current" level cls; l:=eeprom_read(profile_point+pcapacity); //l holds the capacity divided by 100 -> 40 = 4000mAh tw:=l; if (a=0) then l:=eeprom_read(profile_point+pdischarge) //l holds the user rate multiplied by 10 -> 20 = 2.0C else l:=eeprom_read(profile_point+pcharge); tw1:=l*tw; //target current in A*100 -> 40*20=800 discharge current (8.00A) if (a=0) then l:=eeprom_read(uni_maxdischarge) else l:=eeprom_read(uni_maxcharge); tw2:=l*100; if tw1>tw2 then tw1:=tw2; //saturate if the current exceed the charger capabilities target_current:=atoadu; //convert the value in ADU lcd_df_char(143,'A'); lcd_df_char(198,'V'); if calibra=0 then begin if (a=0) then lcd_df_out(128,@msg_discharge) else lcd_df_out(128,@msg_charge); lcd_df_char(205,'m'); lcd_df_char(206,'A'); lcd_df_char(207,'h'); end; act:=eeprom_read(profile_point+pchemistry); //read the chemistry end; //=============================================================================================================================================== // display management during charge and discharge //=============================================================================================================================================== procedure displ; begin tw:=adutoa; //current display in format xx.xx wordtostr(tw,tmpword); lcd_df_char(138,tmpword[1]); lcd_df_char(139,tmpword[2]); lcd_df_char(140,'.'); lcd_df_char(141,tmpword[3]); lcd_df_char(142,tmpword[4]); tw1:=slow_voltage; //voltage display in format xx.xxx tw:=adutomv; wordtostr(tw,tmpword); lcd_df_char(192,tmpword[0]); lcd_df_char(193,tmpword[1]); lcd_df_char(194,'.'); lcd_df_char(195,tmpword[2]); lcd_df_char(196,tmpword[3]); lcd_df_char(197,tmpword[4]); if (calibra=0) then //if not in calibration, begin tw:=recallmah; //capacity display in format xxxxx wordtostr(tw,tmpword); lcd_df_char(200,tmpword[0]); lcd_df_char(201,tmpword[1]); lcd_df_char(202,tmpword[2]); lcd_df_char(203,tmpword[3]); lcd_df_char(204,tmpword[4]); end; end; //=============================================================================================================================================== // Charge routine //=============================================================================================================================================== procedure charge; begin cls; //clear display eeprom_write(uni_mode,mode_charge); //set the charge flag in case the action will be interrupted PORTB.0:=1; //fan ON prepdis(1); //prepare the display for charge ii:=eeprom_read(profile_point+pncells); m:=eeprom_read(profile_point+ptimeout); case act of 0:begin //read values for NiCd l:=eeprom_read(profile_point+pdeltav_nicd); n:=eeprom_read(profile_point+pinhibit); lcd_df_out(133,@msg_change3); tw1:=ii*l; end; 1:begin l:=eeprom_read(profile_point+pdeltav_nimh); //read values for NiMh n:=eeprom_read(profile_point+pinhibit); lcd_df_out(133,@msg_change4); tw1:=ii*l; end; 2:begin l:=eeprom_read(profile_point+pmaxv_lipo); //read values for LiPo n:=eeprom_read(profile_point+pfinalcurr_lipo); lcd_df_out(133,@msg_change5); tw:=3500+(l shl 2); tw1:=ii*tw; end; 3:begin l:=eeprom_read(profile_point+pmaxv_sla); //read values for SLA n:=eeprom_read(profile_point+pfinalcurr_sla); lcd_df_out(133,@msg_change6); tw:=2000+(l shl 2); tw1:=ii*tw; end; end; target_voltage:=mvtoadu; //target voltage: deltaV for NiCd e NiMh, MaxV for LiPo e SLA j:=eeprom_read(profile_point+pcapacity); //j holds the capacity divided by 100 -> 40 = 4000mAh maxcap:=j*m; //maxcap holds the timeout-> cap*timeout=capacity for timeout escape tlw:=target_current*n; ilim:=tlw/100; //ilim holds the value of current for end of charge in the CV phase action:=charge_cc; //starts the control mah:=0; //reset the variables sec:=0; minu:=0; pk:=0; keys:=0; pwm_err:=0; repeat twi:=zero_current-slow_current; //calculate the effective current if twi<0 then twi:=0; tw1:=twi; displ; //display the values case act of 0,1:begin if minu>n then //if NiXX charge, wait for end of inhibition begin INTCON.GIE:=0; if slow_voltage>pk then pk:=slow_voltage; //holds the peak twi:=pk-slow_voltage; INTCON.GIE:=1; if twi<0 then twi:=0; if twi>target_voltage then action:=idle; //if under the delta peak, exit end; end; 2,3:begin INTCON.GIE:=0; if (action=charge_cc) then if slow_voltage>target_voltage then action:=charge_cv; //if LiXX charge stops the CC charge at max voltage if (action=charge_cv) then if twimaxcap) then action:=idle; if pwm_err=1 then action:=idle; until (keys.2=1) or (action=idle); action:=idle; eeprom_write(uni_mode,mode_idle); lcd_df_out(133,@msg_end); sec:=0; tribeep; keys:=0; repeat //loop for heatsink cooling, display the cell voltage INTCON.GIE:=0; twi:=zero_current-slow_current; INTCON.GIE:=1; if twi<0 then twi:=0; tw1:=twi; displ; until (keys.2=1); click; PORTB.0:=0; //fan OFF end; //=============================================================================================================================================== // Discharge routine //=============================================================================================================================================== procedure discharge; begin cls; eeprom_write(uni_mode,mode_discharge); PORTB.0:=1; prepdis(0); case act of 0:begin l:=eeprom_read(profile_point+pcutoff_nicd); tw:=l*10; lcd_df_out(133,@msg_change3); end; 1:begin l:=eeprom_read(profile_point+pcutoff_nimh); tw:=l*10; lcd_df_out(133,@msg_change4); end; 2:begin l:=eeprom_read(profile_point+pcutoff_lipo); tw:=2500+(l shl 2); lcd_df_out(133,@msg_change5); end; 3:begin l:=eeprom_read(profile_point+pcutoff_sla); tw:=1500+(l shl 2); lcd_df_out(133,@msg_change6); end; end; l:=eeprom_read(profile_point+pncells); tw1:=l*tw; //ideal total cutoff voltage in mV target_voltage:=mvtoadu; action:=discharge_cc; //start the control sec:=0; minu:=0; mah:=0; keys:=0; pwm_err:=0; repeat INTCON.GIE:=0; twi:=slow_current-zero_current; INTCON.GIE:=1; if twi<0 then twi:=0; tw1:=twi; displ; INTCON.GIE:=0; if pwm_err=1 then action:=idle; if (fast_voltage xx00.AAAA,xx01.BBBB,xx10.CCCC,1x11.DDDD -> write CCCC.DDDD at address AAAA.BBBB // PC management for internal parameters -> xx00.AAAA,xx01.BBBB,xx10.xxxx,0011.xxxx -> read from address AAAA.BBBB //=============================================================================================================================================== procedure pcmanage; begin k:=0; l:=0; m:=0; cls; lcd_df_out(128,@msg_pcman1); //pc management message // lcd_df_out(192,@msg_pcman2); keys:=0; repeat if PIR1.RCIF=1 then //if an UART character has been received begin if (RCSTA and 6)<>0 then //if there is an error begin j:=RCREG; j:=RCREG; RCSTA.CREN:=0; RCSTA.CREN:=1; //clear the error and flush the buffer end else begin eevar:=RCREG; //if no error read the value j:=(eevar and 0x30) shr 4; //select the two bit xxAA.DDDD case j of 0:begin if (m=0) then m:=1 else m:=0; // if AA=00 we have an address, first 4 MSB... if (m=1) then begin k:=0; k:=(eevar and 0x0F) shl 4; end; end; 1:begin if (m=1) then m:=2 else m:=0; if (m=2) then // then AA=01 we have the 4 LSB... begin k:=k or (eevar and 0x0F); end; end; 2:begin if (m=2) then m:=3 else m:=0; if (m=3) then // if AA=10 we have the 4 MSB for data begin l:=0; l:=(eevar and 0x0F) shl 4; end; end; 3:begin if (m=3) then m:=4 else m:=0; if (m=4) then begin l:=l or (eevar and 0x0F); // if AA=11 we have the 4 LSB for data if eevar.7=0 then //the MSB of last byte selects between READ (0) or WRITE(1) begin l:=eeprom_read(k); TXREG:=l; //read from EEPROM and send to UART end else begin eeprom_write(k,l); //write to EEPROM end; m:=0; end; end; end; end; end; until (keys<>0); //repeat until keypressed click; end; //=============================================================================================================================================== // Profile display (display of chemistry, number of cells, capacity ...) //=============================================================================================================================================== procedure display_profile; begin case selitem of 0:begin lcd_df_out(128,@msg_change1); lcd_df_out(192,@msg_change2); l:=eeprom_read(profile_point+pchemistry); case l of nicd:lcd_df_out(139,@msg_change3); nimh:lcd_df_out(139,@msg_change4); lipo:lcd_df_out(139,@msg_change5); sla:lcd_df_out(139,@msg_change6); end; end; 1:begin lcd_df_out(128,@msg_change7); lcd_df_out(192,@msg_change8); l:=eeprom_read(profile_point+pcapacity); tw:=l*100; wordtostr(tw,tmpword); lcd_df_char(138,tmpword[0]); lcd_df_char(139,tmpword[1]); lcd_df_char(140,tmpword[2]); lcd_df_char(141,tmpword[3]); lcd_df_char(142,tmpword[4]); end; 2:begin lcd_df_out(128,@msg_change9); lcd_df_out(192,@msg_change10); l:=eeprom_read(profile_point+pncells); bytetostr(l,tmpbyte); lcd_df_char(135,tmpbyte[1]); lcd_df_char(136,tmpbyte[2]); end; 3:begin lcd_df_out(128,@msg_change11); lcd_df_out(192,@msg_change12); l:=eeprom_read(profile_point+pcharge); bytetostr(l,tmpbyte); lcd_df_char(136,tmpbyte[0]); lcd_df_char(137,tmpbyte[1]); lcd_df_char(138,'.'); lcd_df_char(139,tmpbyte[2]); end; 4:begin lcd_df_out(128,@msg_change13); lcd_df_out(192,@msg_change12); l:=eeprom_read(profile_point+pdischarge); bytetostr(l,tmpbyte); lcd_df_char(139,tmpbyte[0]); lcd_df_char(140,tmpbyte[1]); lcd_df_char(141,'.'); lcd_df_char(142,tmpbyte[2]); end; end; end; //=============================================================================================================================================== // 8 bit Variable modify //=============================================================================================================================================== procedure modvar(a:byte); begin l:=eeprom_read(eevar); //read the variable if (a=0) then dec(l) else inc(l); //modify if l=eelim then l:=eeres; //if below EELIM, saturate to EERES eeprom_write(eevar,l); //write back the value end; //=============================================================================================================================================== // Key mananegement during profile selection //=============================================================================================================================================== procedure key_profile; begin keys:=0; repeat until (keys<>0); click; if (keys.0=1) then begin case selitem of 0:begin eevar:=profile_point+pchemistry; eelim:=255; eeres:=0; modvar(0); end; 1:begin eevar:=profile_point+pcapacity; eelim:=0; eeres:=1; modvar(0); end; 2:begin eevar:=profile_point+pncells; eelim:=0; eeres:=1; modvar(0); end; 3:begin eevar:=profile_point+pcharge; eelim:=0; eeres:=1; modvar(0); end; 4:begin eevar:=profile_point+pdischarge; eelim:=0; eeres:=1; modvar(0); end; end; end; if (keys.1=1) then begin case selitem of 0:begin eevar:=profile_point+pchemistry; eelim:=4; eeres:=3; modvar(1); end; 1:begin eevar:=profile_point+pcapacity; eelim:=0; eeres:=255; modvar(1); end; 2:begin eevar:=profile_point+pncells; eelim:=20; eeres:=19; modvar(1); end; 3:begin eevar:=profile_point+pcharge; eelim:=0; eeres:=255; modvar(1); end; 4:begin eevar:=profile_point+pdischarge; eelim:=0; eeres:=255; modvar(1); end; end; end; if (keys.2=1) then begin inc(selitem); end; end; //=============================================================================================================================================== // Battery profile change //=============================================================================================================================================== procedure changeprofile; begin selitem:=0; repeat display_profile; //display the current item in the profile key_profile; //change it until (keys.2=1) and (selitem=5); end; //=============================================================================================================================================== // Procedure default values //=============================================================================================================================================== procedure default_values_pack; begin j:=k*profilelen; eeprom_write(j+pchemistry,0); //Default NiCd (0) -> 0:NiCd, 1:NiMh, 2:LiPo, 3:SLA eeprom_write(j+pcapacity,30); //Default cells capacity (30) -> 30*100=3000mAh eeprom_write(j+pncells,6); //Deafult number of cells (6) -> 6 eeprom_write(j+pcharge,10); //default charge (10) -> 3000*1.0=3A eeprom_write(j+pdischarge,40); //default discharge (40) -> 3000*4.0=12A eeprom_write(j+pinhibit,5); //default deltapeak check inhibition (5) -> 5 minutes eeprom_write(j+pcutoff_nicd,80); //default NiCd cutoff (80) -> 80*10=800mV eeprom_write(j+pcutoff_nimh,100); //default NiMh cutoff (100) -> 100*10=1000mV eeprom_write(j+pcutoff_lipo,125); //default LiPo cutoff (125) -> 2500+125*4=3000mV eeprom_write(j+pcutoff_sla,125); //default SLA cutoff (125) -> 1500+125*4=2000mV eeprom_write(j+pdeltav_nicd,10); //default NiCd deltapeak (10) -> 10mV eeprom_write(j+pdeltav_nimh,5); //default NiMh deltapeak (5) -> 5mV eeprom_write(j+pmaxv_lipo,175); //default LiPo max voltage (175) -> 3500+175*4=4200mV eeprom_write(j+pmaxv_sla,125); //default SLA max voltage (125) -> 2000+125*4=2500mV eeprom_write(j+pfinalcurr_lipo,5); //default LiPo final current (5) -> 3000*5/100=150mA eeprom_write(j+pfinalcurr_sla,5); //default SLA final current (5) -> 3000*5/100=150mA eeprom_write(j+ptimeout,120); //default cell max charge (120) -> 3000*120/100=3600mAh end; procedure default_values_uni; begin eeprom_write(uni_profile,0); //Default profile selected (0) -> 1st profile eeprom_write(uni_maxcharge,5); //Default max charge current (5) -> 5A eeprom_write(uni_maxdischarge,20); //Default max discharge current (20) -> 20A eeprom_write(uni_beep,25); //default beep tone semiperiod (25) -> 25*10=250us (2000Hz) eeprom_write(uni_r5h,183); //default (183) eeprom_write(uni_r5l,152); //default (152) R5=R5h*256+R5l = 47000 Ohm eeprom_write(uni_r6h,46); //default (46) eeprom_write(uni_r6l,224); //default (224) R6=R6h*256+R6l = 12000 Ohm eeprom_write(uni_currh,97); //default (97) eeprom_write(uni_currl,168); //default (168) Curr=Currh*256+Currl= 25000 -> 25000uv/A eeprom_write(uni_mode,mode_idle); //default mode (0) -> idle for k:=0 to 15 do eeprom_write(uni_firstline+k,msg_hello1[k]); //default hello message 1st line -> ".seven-segments." for k:=0 to 15 do eeprom_write(uni_secondline+k,msg_hello2[k]); //default hello message 2nd line -> "www. .com" end; //=============================================================================================================================================== // 16 bit Variable modify //=============================================================================================================================================== procedure modvar16(a:byte); begin tw:=eeprom_read(eevar); inc(eevar); tw:=tw+(eeprom_read(eevar) shl 8); if (a=0) then tw:=tw+10 else tw:=tw-10; eeprom_write(eevar,hi(tw)); dec(eevar); eeprom_write(eevar,lo(tw)); end; //=============================================================================================================================================== // Calibration procedure (Volt and Ampere) //=============================================================================================================================================== procedure calibration(a:byte); begin cls; calibra:=1; PORTB.0:=1; prepdis(2); tw1:=200; target_current:=atoadu; if (a=0) then begin action:=idle; eevar:=uni_r6l; end else begin action:=charge_cc; eevar:=uni_currl; end; keys:=0; repeat INTCON.GIE:=0; twi:=zero_current-slow_current; INTCON.GIE:=1; if twi<0 then twi:=0; tw1:=twi; displ; if (keys.0=1) then begin modvar16(0); keys:=0; click; end; if (keys.1=1) then begin modvar16(1); keys:=0; click; end; until (keys.2=1); action:=idle; click; calibra:=0; PORTB.0:=0; end; //=============================================================================================================================================== // Main program, neverending loop //=============================================================================================================================================== begin ADCON0:=0x81; //PIC hardware related variables init INTCON:=0x20; RCSTA:=0x90; CCPR1L:=0x0; CCPR2L:=0x0; T2CON:=0x4; CCP1CON:=0xC; CCP2CON:=0xC; OPTION_REG:=0x6; SPBRG:=0x81; //9600 baud @20MHz TXSTA:=0xA4; ADCON1:=0x84; TRISA:=0x0B; TRISB:=0xE0; TRISC:=0x80; PR2:=0xFF; PORTA:=0xFF; PORTB:=0x0; PORTC:=0x0; repeat_k1:=0; repeat_k2:=0; repeat_k3:=0; action:=0; duty_pwm_charge:=0; //all off duty_pwm_discharge:=0; profile_point:=0; calibra:=0; //no calibration at startup lcd_df_config; j:=eeprom_read(uni_profile); if j=255 then //if the EEPROM is unprogrammed, program for defaults begin //writing 255 to the profile selection address the default is reloaded lcd_df_out(128,@msg_init); default_values_uni; for k:=0 to 11 do default_values_pack; end else profile_point:=j*profilelen; //else restore the current profile lcd_df_out_eeprom(128,uni_firstline); lcd_df_out_eeprom(192,uni_secondline); beep; k:=0; keys:=0; repeat inc(k); //wait for user action or timeout for interrupted action restart delayms(10); until (keys<>0) or (k=255); if keys<>0 then click; keys:=0; if (k=255) then //if no key pressed resume the last operation (if any) begin phase:=eeprom_read(uni_mode); //read if there is a suspended action if (phase=mode_charge) then //if a precededent action was suspended by a power loss, begin //complete it charge; //finish the discharge end; if (phase=mode_discharge) then begin discharge; //finish the discharge end; end; eeprom_write(uni_mode,mode_idle); //abort the last action, if necessary menupos:=0; //start display from "profile selection" repeat main_menu; //display the main menu and check the keys case menupos of 0:selprofile; //battery pack profile selection routine 1:charge; //charge the battery 2:discharge; //discharge the battery 3:changeprofile; //Actual battery pack profile change 4:pcmanage; //PC control on parameters 5:calibration(0); //Voltage calibration 6:calibration(1); //Current calibration end; until false; //neverending loop end.