'------ Li-Ion Battery Management System Master by Peter Perkins Module ------- '------ Picbasic Pro Compiler version PIC16F886 - 191211 - V0.31 Alpha ------- '------ This code requires Master board V2 Please report errors or problems. '------------------------------------------------------------------------------ 'Note this code is for the Pic Basic Pro v3.01+ with MPASM Assembler '**************************** General Information ****************************** 'The BMS modules carry no warranty or guarantee of any kind! They are used at 'your own risk, and I make no claims as to their suitability for a particular 'function. Prospective users must evaluate the system before using it, and no 'liability will be entertained by myself in any shape or form whatsoever. 'The modules and software have been produced at low cost for the benefit of 'the EV & electronic community. The software is available free via the internet. 'Users may modify or adapt the system as they see fit. If you are not fully 'competent to work on potentially lethal battery systems and high voltages, 'then do not experiment with or use this system. Be aware that vehicle 'modifications can lead to invalidated insurance and warranty issues. You the 'end user remain fully liable for any modifications made to your vehicle. '************************** Master PIC 18F2680 Pinout ************************** ' Top ' _____ '(Pic Reset) MCLR -01| ^ |28- RB7 ICSP Data & (433mhz Txd Out) '(Current Sensor Adc In) RA0 -02| |27- RB6 ICSP Clk & (Chg Relay Out) '(Buttons Adc In) RA1 -03| |26- RB5 (Video Display Out) '(Analog Temp1 Adc In) RA2 -04| |25- RB4 (Controller Cutback Out) '(Analog Temp2 Adc In) RA3 -05| 1 |24- RB3 (WatchDog Led Out) '(Spare) RA4 -06| 6 |23- RB2 (Slave Data Bus Out) '(Spare) RA5 -07| F |22- RB1 (Charger Cutback Out) '(- Supply) Gnd -08| 8 |21- RB0 (Audible Alarm Out) '(1SPulse In) RA7 -09| 8 |20- +Ve (+5V Supply) '(Fan) RA6 -10| 6 |19- Gnd (- Supply) '(Interlocks) RC0 -11| |18- RC7 (Master Data Bus In) '(VSS Speed & CCP2 PWM) RC1 -12| |17- RC6 (Dash Led 1 Out) '(I2C Temp & CCP1 PWM) RC2 -13| |16- RC5 (Dash Led 2 Out) '(I2C Eeprom Clock) RC3 -14| |15- RC4 (I2C Eeprom Data) ' ----- '************************ Other IC's On Master Pcb ***************************** '************************* Watchdog PIC 12F683 Pinouts ************************* ' Top ' _____ '(+ Supply) +Ve -1| ^ |8- -Ve (- Supply) '(Not Used) Output 5 -2| 6 |7- Output 0 (1S Pulse Out) '(Watchdog Led Out) Output 4 -3| 8 |6- Output 1 (Audible Alarm Out) '(Pulse Count In) Input 3 -4| 3 |5- Output 2 (Master Reset Out) ' ----- '**************** SV2000 Serial to Composite RCA Video IC ********************** ' Top ' _____ '(Sync Out) Syn -1| ^ |8- +Ve (+5V Supply) '(Video Out) Vid -2| V |7- Nc (No Connection) '(Serial Data In) Rxd 4 -3| I |6- Inv (Serial Data Invert Mode) '(- Supply) Gnd -4| D |5- Txd (No Connection) ' ----- '******************* AT24C512B 64K Byte I2C Serial EEprom IC ******************* ' Top ' _____ '(Address A0) A0 -1| ^ |8- +Ve (+5V Supply) '(Address A1) A1 -2| M |7- WP (Write Protect) '(Address A2) A2 -3| E |6- Scl (I2C Clock) '(- Supply) Gnd -4| M |5- Scd (I2C Data) ' ----- '************************ Master Module Specification ************************** 'Board Supply Voltage 8.00-30.00V DC or as limited by 5.00V 78L05 regulator 'CPU Supply Voltage 5.00V 'Master Pic 16F886 CPU Speed 8mhz with internal resonator 'Watchdog Pic 12F683 CPU Speed 4mhz with internal resonator (Timeout 65 Seconds) 'Average Board Supply Current at 12.00v <100ma 'Serial Master Bus data rate 9600 baud 'Maximum Cell Capacity 650ah but adjustment will be reqd for Soc routines. 'Maximum Pack Voltage 655.35v (65535) (Limit of 16bit Word Variable) 'Maximum Charge/Discharge rate 100A (Allegro Current Sensor limit) (This can be increased to 650A) 'Maximum 95 Slave Modules per Master Module. (95 Cells in series) 'Maximum OdoMeter Reading 65535 Miles 'Maximum TripMeter Reading 6553.5 Miles to 1/10th Mile 'DS18B20 I2C Digital Battery Pack Temp Sensor Range (-55 to +127C) 'LM335 Analog Pack Temp Sensor Range (0 to +100C) 'LM35 Analog Pack Temp Sensor Range (-20 to +100C) 'Composite Video Display data rate 9600 baud (SV2000 Serial to Video Chip) 'Composite RCA Video Monitor Output 1V 75ohm PAL/NTSC 'The Main display is limited to 16x9 characters in a 0-15 & 0-8 format. 'The Remote display is limited to 16x2 characters in a 2 line format. 'Charger relay output is board supply voltage max 30v at 500ma (Normally this will be about 12V) 'Opto Isolated Charger/Controller Cutback outputs max 50ma 50v '***************************** Slave Commands ********************************** ' Command 01 = Send Cell Voltage on Master Bus ' Command 02 = Turn Off Loads ' Command 03 = Turn On Loads as Reqd by Load CutIn Voltage ' Command 04 = Increase Load CutIn Voltage by 50mv ' Command 05 = Decrease Load CutIn Voltage by 50mv ' Command 06 = Set Slave Load CutIn Voltage Default ' Command 07 = Turn On Slave Load for 0.5 seconds (Flash Led) ' Command 08 = Report Slave Software Version Number ' Command 09 = Increase FailLow Cut Off Voltage by 50mv ' Command 10 = Decrease FailLow Cut Off Voltage by 50mv ' Command 11 = Increase FailHigh Cut in Voltage by 50mv ' Command 12 = Decrease FailHigh Cut in Voltage by 50mv ' Command 13 = Set FailHigh/Low Voltages to Default 'Alarms Additional Information (255 possible Alarms) 0 = No Alarms 'If Alarms = 0 then (No Alarms Set) 'If Alarms = 1 then (Cell over AbsMax V) 'If Alarms = 2 then (Cell under AbsMin V) 'If Alarms = 3 then (Cell data serial transfer timeout error) 'If Alarms = 4 then (Battery Pack over AbsMax Temp) 'If Alarms = 5 then (External End Charge OR Battery Fault Condition) (Pack Voltage Drop Detector) 'If Alarms = 6 then (Internal End Charge) (Cells have all reached balancing voltage during this charge cyle) 'If Alarms = 7 then (Pack Voltage > Maximum permitted Pack Voltage) 'If Alarms = 8 then (Pack Voltage < Minimum permitted Pack Voltage) 'If Alarms = 9 then (Watchdog 1S Pulse Missing) 'If Alarms = 10 then (Command MissMatch Error, sent command does not match received) '******************************************************************************* '****************** Program Size 7218 Words out of 8192 Words ****************** '******************************************************************************* '#if _XTAL_FREQ == 8000000 // 8MHz internal '__CONFIG(1, FCMEN_OFF & IESO_OFF & OSC_IRCIO67); '__CONFIG(2, BOREN_BOHW & BORV_3 & PWRT_OFF & WDT_OFF & WDTPS_4096); '__CONFIG(3, PBADEN_OFF & LPT1OSC_OFF & MCLRE_ON); '__CONFIG(4, LVP_OFF & STVREN_OFF & BBSIZ_1024 & XINST_OFF & DEBUG_OFF); '__CONFIG(5, CP0_OFF & CP1_OFF & CP2_OFF & CP3_OFF & CPB_OFF & CPD_OFF); '__CONFIG(6, WRT0_OFF & WRT1_OFF & WRT2_OFF & WRT3_OFF & WRTC_OFF & WRTB_OFF & WRTD_OFF); '__CONFIG(7, EBTR0_OFF & EBTR1_OFF & EBTR2_OFF & EBTR3_OFF & EBTRB_OFF); 'Old Config Lines '__CONFIG _CONFIG1, _LVP_OFF & _FCMEN_OFF & _IESO_OFF & _BOR_OFF & _CPD_OFF & _CP_OFF & _MCLRE_ON & _PWRTE_OFF & _WDT_OFF & _INTRC_OSC_NOCLKOUT '__CONFIG _CONFIG2, _WRT_OFF & _BOR21V #CONFIG CONFIG OSC = IRCIO67 CONFIG FCMEN = OFF CONFIG IESO = OFF CONFIG BOREN = BOHW CONFIG BORV = 3 CONFIG PWRT = OFF CONFIG WDT = OFF CONFIG WDTPS = 4096 CONFIG PBADEN = OFF CONFIG LPT1OSC = OFF CONFIG MCLRE = ON CONFIG LVP = OFF CONFIG STVREN = OFF CONFIG BBSIZ = 1024 CONFIG XINST = OFF CONFIG DEBUG = OFF CONFIG CP0 = OFF CONFIG CP1 = OFF CONFIG CP2 = OFF CONFIG CP3 = OFF CONFIG CPB = OFF CONFIG CPD = OFF CONFIG WRT0 = OFF CONFIG WRT1 = OFF CONFIG WRT2 = OFF CONFIG WRT3 = OFF CONFIG WRTC = OFF CONFIG WRTB = OFF CONFIG WRTD = OFF CONFIG EBTR0 = OFF CONFIG EBTR1 = OFF CONFIG EBTR2 = OFF CONFIG EBTR3 = OFF CONFIG EBTRB = OFF #ENDCONFIG ' EEPROM Storage of Large Numerical Characters in 3 x 3 ascii blocks EEPROM 20, [201,205,187,186,32,186,200,205,188] ;Store Big Digit "0" in eeprom ascii 48 EEPROM 29, [32,32,203,32,32,186,32,32,202] ;Store Big Digit "1" in eeprom ascii 49 EEPROM 38, [201,205,187,201,205,188,200,205,205] ;Store Big Digit "2" in eeprom ascii 50 EEPROM 47, [201,205,187,32,205,185,200,205,188] ;Store Big Digit "3" in eeprom ascii 51 EEPROM 56, [203,32,203,200,205,185,32,32,202] ;Store Big Digit "4" in eeprom ascii 52 EEPROM 65, [201,205,205,200,205,187,200,205,188] ;Store Big Digit "5" in eeprom ascii 53 EEPROM 74, [201,205,32,204,205,187,200,205,188] ;Store Big Digit "6" in eeprom ascii 54 EEPROM 83, [205,205,187,32,32,186,32,32,202] ;Store Big Digit "7" in eeprom ascii 55 EEPROM 92, [201,205,187,204,205,185,200,205,188] ;Store Big Digit "8" in eeprom ascii 56 EEPROM 101, [201,205,187,200,205,185,32,32,202] ;Store Big Digit "9" in eeprom ascii 57 'new config lines as per peters advice Define OSC 8 'Set PicBasic Pro processor speed to 8 Mhz (Must match oscillator value) OSCCON = %01110100 'Internal 8 mhz Osc and stable ' CM1CON0 = 0 'Comparator Off ' CM2CON0 = 0 'Comparator Off INTCON = %00000000 INTCON2 = %10000000 INTCON3 = %00000000 LATB = 0 T1CON = %00110001 'Enables and Starts Timer1 prescale 1:8 internal clock source 262ms at 8mhz TRISA = %10111111 'SET PORTA AS INPUTS EXCEPT PORTS A6 TRISB = %00000000 'SET PORTB AS OUTPUTS TRISC = %10011111 'SET PORTC AS INPUTS EXCEPT PORTS C5,C6 ' ANSEL = %00001111 'SET INPUTS AN0-AN3 AS ANALOG INPUTS ' ANSELH = %00000000 'Disable PortB AD ADCON0 = %01000001 'SETUP ADC CONVERTER MODULE FOSC/8 & ENABLE ADC MODULE on AD0 ADCON1 = %10001011 'SETUP ADC RIGHT JUSTIFY SET REFV to VDD & VSS ADCON2 = %10000001 'Debug is used as it is the fastest and smallest serial routine to drive the video display DEFINE DEBUG_REG PORTB 'Set Debug pin port DEFINE DEBUG_BIT 5 'Set Debug pin bit DEFINE DEBUG_BAUD 9600 'Set Debug baud rate DEFINE DEBUG_MODE 0 'Set Debug mode: 0 = true, 1 = inverted DEFINE PULSEIN_MAX 20000 'Set PULSIN maximum count to 20,000 (100ms) (Range 0-65535 x 5us units) include "modedefs.bas" 'Allows the use of the serin/serout command '************************************************************************************************************* 'Variables Constants and I/O definitions 'Note Global variables must be preserved throughout the program 'Note Local variables may be used at will within subroutines 'Variables 16bit (Word) PowerConsumption var word 'Global variable - inserted by RCC CellV var Word 'Local = Cell Voltage 0-1023 10bit (0-5v) PackV var Word 'Global = Calculated pack voltage (Voltage of all Cells added together) BatCurrent var Word 'Global = Current Sensor ADC value 0-1023 10bit (0-5v) Approx 200ma resolution MaxCurrent var word 'Global = Maximum Recorded Current Offset var Word 'Global = Current Sensor Zero Offset (0 Amp point) Should be around 512 I2CAddr var Word 'Global = I2CAddress byte (0-65535) address of data byte to be written WhMile var Word 'Global = Watt Hours per mile (Power Consumed) WhMile2 var word 'Global = Average Storage WhMile3 var word 'Global = Average Storage WhMileAvg var word 'Global = Moving Average of power consumption Odo var Word 'Global = Vehicle odometer miles travelled (0-65535) Trip var Word 'Global = Vehicle trip meter miles travelled (0-6553.5) Soc var Word 'Global = Calculated pack capacity (Soc State of Charge) Dist var Word 'Global = Distance (feet) travelled (5280ft = 1 mile) HighPackV var Word 'Global = High Pack voltage Seconds var Word 'Global = Time in seconds BMS active Max 65535 (18hrs) Charge var Word 'Global = [Charge] Discharge var Word 'Global = [Discharge] DistRem var Word 'Global = [DistRem] VarA var Word 'Local = General 0-65535 16bit Word Variable VarB var Word 'Local = General 0-65535 16bit Word Variable VarC var Word 'Local = General 0-65535 16bit Word Variable VarD var Word 'Local = General 0-65535 16bit Word Variable VarE var Word 'Local = General 0-65535 16bit Word Variable CurrentOld var word 'Global = General Variable R_Temp var Word 'Local = RAW Temperature readings TempC var Word 'Local = Temp in deg C Float var Word 'Local = Holds remainder for + temp C display 'Variables 8bit (Byte) Alarms var Byte 'Global = Alarm Type Byte (0-255 number of different Alarms) AlarmCount var Byte 'Global = Alarm Counter Byte (0-255 number of times an Alarm has occured) VData var CellV.LowByte 'Local = Voltage data byte (8 bit value) (Rxd from Slave via serial link) Cells var byte 'Global = Number of cells in the battery pack (Max 95) ErrCell var Byte 'Global = [Error Cell] Cell with error HighCell var Byte 'Global = [HighCell] Cell with Highest Voltage LowCell var Byte 'Global = [LowCell] Cell with Lowest Voltage TSeconds var Byte 'Global = [TSeconds] Thirty second elapsed Timer Temp1Before var Byte 'Global = [Temp1] Temperature data before decimal point Temp1After var Byte 'Global = [Temp1] Temperature data after decimal point Temp2Before var Byte 'Global = [Temp2] Temperature data before decimal point Temp2After var Byte 'Global = [Temp2] Temperature data after decimal point Temp1Sign var Byte 'Global = [Temp1] Temperature Sign (+ or -) Temp2Sign var Byte 'Global = [Temp2] Temperature Sign (+ or -) TempInc var byte 'Global = Number of sensor to be interogated on this loop Tenths var Byte 'Global = [Tenths of a mile] VoltsData var Byte 'Global = [PackV] SocData var Byte 'Global = [Soc] AmpSign var Byte 'Global = [AmpSign + or -] Dummy var Byte 'Local = [Dummy for Div32 temp conversion] Control var Byte 'Global = [BMS Control Byte 8 bits used for BMS Configuration] Key var Byte 'Local = [Returns Menu Key/Button pressed] Command var Byte 'Local = Command Value ErrCom var BYTE 'Global returned Command error value BalanceCount Var Byte 'Number of loops before voltage update when charging Hours var Byte 'Number of Hours elapsed Minutes var Byte 'Number of Minutes elapsed 'Seconds Var Byte 'Number of Seconds elapsed LoopCount var byte 'Count the loops BD var BYTE[95] 'Define BD as a byte array (95 Cells) (BD = Battery Data) I2C1 var BYTE[8] 'Define I2C1 as a byte array (8 Bytes) (I2C = I2C Temp sensor) VarA1 var VarA.LowByte 'Local = General 0-255 8bit Byte Variable VarA2 var VarA.HighByte 'Local = General 0-255 8bit Byte Variable VarB1 var VarB.LowByte 'Local = General 0-255 8bit Byte Variable VarB2 var VarB.HighByte 'Local = General 0-255 8bit Byte Variable VarC1 var VarC.LowByte 'Local = General 0-255 8bit Byte Variable VarC2 var VarC.HighByte 'Local = General 0-255 8bit Byte Variable VarD1 var VarD.LowByte 'Local = General 0-255 8bit Byte Variable VarD2 var VarD.HighByte 'Local = General 0-255 8bit Byte Variable CursorX var byte ' X position of display cursor for DisplayBigDigits function Asci var byte ' Ascii code for DisplayBigDigits function Digit var byte ' Digit to print in big numbers Address var byte ' Address for accessing DisplayBigDigit Lookup tables iter var byte 'iteration variable i var byte 'second iteration variable DigitsIn var byte 'input to digit function NumDigits var byte 'output of digit function 'Variables 1bit (Bit) ChargePause var bit 'Global = Charger Paused State ChargerRestart var bit 'Global = Restart the charger Busy var BIT 'Local = I2C Temp conversion Busy Status-Bit Cold_Bit var R_Temp.Bit11 'Local = Sign-Bit for +/- Temp. 1 = Below 0 deg C ChgBit var BIT 'Global = [ChgBit] Charge in progress (0=Not Charging 1=Charging) FullBit var BIT[95] 'Global = FullBit is a bit array (Max 95 Cells) (0 = Cell Not Full) (1 = Cell Full) RetBit var BIT 'Local = Forces return from Buttons Routine if Set ClearBit var bit 'Check for clearing the screen PrevDigit var bit ' 0 = (Previous digit was < 10), 1 = (Previous digit >= 10) 'Control Byte (Bit Assingment) 'TempBit var Control.Bit0 'Global = Temperature Routine (1 = I2C Sensors 0 = Analog Sensors) 'Unallocated var Control.Bit1 'Global = TxBit var Control.Bit2 'Global = Remote Display and EEprom Alarm Logging bit (1 = Disabled 0 = Enabled) PcBit var Control.Bit3 'Global = PCLogger Enable bit (1 = Disabled 0 = Enabled) StartBit var Control.Bit4 'Global = StartBit set when Master first starts, cleared by sending of Command 2 loads off StatBit var Control.Bit5 'Global = Temp sensor status bit 1 = ok 0 = busy 'Unallocated var Control.Bit6 'Global = 'Unallocated var Control.Bit7 'Global = 'Alarms Additional Information (255 possible Alarms) 0 = No Alarms 'If Alarms = 0 then (No Alarms Set) 'If Alarms = 1 then (Cell over AbsMax V) 'If Alarms = 2 then (Cell under AbsMin V) 'If Alarms = 3 then (Cell data serial transfer timeout error) 'If Alarms = 4 then (Battery Pack over AbsMax Temp) 'If Alarms = 5 then (External End Charge OR Battery Fault Condition) (Pack Voltage Drop Detector) 'If Alarms = 6 then (Internal End Charge) (Cells have all reached balancing voltage during this charge cyle) 'If Alarms = 7 then (Pack Voltage > Maximum permitted Pack Voltage) 'If Alarms = 8 then (Pack Voltage < Minimum permitted Pack Voltage) 'If Alarms = 9 then (Watchdog chip 1S pulse missing) 'If Alarms = 10 then (Command Error, Sent & Recd do not match!) 'Constants 'MaxPackV con 22500 'Maximum pack voltage = 225v 60 Cells (22,500 as 16 bit value) Res 10mv (Max 650v) 'MaxPackV con 18500 'Maximum pack voltage = 185v 50 Cells (18,500 as 16 bit value) Res 10mv (Max 650v) 'MaxPackV con 2520 'Maximum pack voltage = 25.2v 6 Cells (2,520 as 16 bit value) Res 10mv (Max 650v)(added by isaac) 'MaxPackV con 20160 'Maximum pack voltage = 201.6 48 Cells (20,160 as 16 bit value) Res 10mv (Max 650v)(added by isaac) MaxPackV con 5920 'Maximum pack voltage =59.20 16 Cells (buspack) MinPackV con 0 'Minimum pack voltage = 0v (0 as 16 bit value) Res 10mv AbsMaxCellV con 370 'Abs Maximum permitted cell voltage = (3.66V) (Alarm & Shutdown point)(buspack changed to 370) AbsMinCellV con 230 'Abs Minimum permitted cell voltage = (2.30V) (Alarm & Shutdown point)(buspack changed to 230) MaxCellV con 360 'Normal Maximum permitted cell voltage = (3.60V) (Charger/Regen cutback point) MinCellV con 240 'Normal Minimum permitted cell voltage = (2.40V) (Controller/Assist cutback point) CutInVD con 365 'Balancing load/bypass default cut in Voltage (Must match value set in Slaves) FanCutOff con 350 'Cutoff of maximum voltage of high pack for fan 'MaxTemp con 40 'Maximum permitted pack temperature = (40C) (Alarm & Shutdown point)(isaac changed back to 40) MaxTemp con 140 ' deg Far MinTemp con 120 ' deg Far SocMax con 1000 'Max cell capacity = 100% (1000 as 16 bit value) SocMin con 100 'Min cell capacity = 10% (100 as 16 bit value) SocUnit con 162 'SocUnit constant for Soc calculations. (For 20ah cells = 72) Test Value 62 DiscardLow con 175 'Cell correction value 175 or 1.75V added to CellV to recreate correct V TimeOut con 100 'Serial Data Receive Timeout value 100ms VoltageDrop con 500 'Voltage Drop = 500 Pack must drop (5V) when charging before charger cuts off FailSafeV con 335 'Fail Safe Minimum Load voltage Cut off (<3.35V) Cell resting voltage AT24C512B con %10100000 'Control byte for AT24C512B eeprom %1010ddd0 (ddd) = device select bits AlarmActivate con 3 'Number of consecutive times Alarm must occur before Alarm activates TempSensors con 2 'Number of I2C temp sensors installed(isaac changed back to 2) BalanceLoop con 4 'Number of 1S loops for each Get Voltage acquisition when charging (Includes Acq) 'Pins used for I/O and designations '*** Digital high/low Outputs on PORT B - Outputs 0-7 *** Alarm var PORTB.0 'Audible Alarm warning on Output 0 Beeper var PORTB.0 'Beeper alias Charger var PORTB.1 'Charger Cutback output on Output 1 (Opto conducts when cell V > 3.62V) SlaveBus var PORTB.2 'Slave Data Bus Output on Output 2 WatchDogLed var PORTB.3 'Watchdog flashing Green Led on Output 3 (Flashes every other program loop) Controller var PORTB.4 'Controller Cutback output on Output 4 (Opto conducts when cell V < 2.40V) Video var PORTB.5 'Dashboard Video Display on Output 5 ChargerOnOff var PORTB.6 'Charger Relay On/Off control on Output 6 (12.00v Max 500ma) DriveInhibit var PORTB.7 'Drive inhibit Opto on Output 7 '*** Digital high/low Inputs & Outputs on PORT C *** Interlocks var PORTC.0 'Interlocks and additional safety/security switch inputs on Input 0 SpeedSensor var PORTC.1 'VSS Speed sensor pulse sensor on Input 1 (Measures length of a single pulse) DigitalTemp var PORTC.2 'I2C DS18B20 Battery Pack Temp Sensor on Input 2 (-55 to +127C) Sckeeprom var PORTC.3 'I2C EEprom Clock (Symbol not used in program added for completeness only) Scdeeprom var PORTC.4 'I2C EEprom Data (Symbol not used in program added for completeness only) Led2 var PORTC.5 'Dashboard Led 2 on Output portc 5 Led1 var PORTC.6 'Dashboard Led 1 on Output portc 6 MasterBus var PORTC.7 'Master Data Bus Input on Input 7 '*** Analogue ADC Inputs on PORT A *** CurrentSensor var PORTA.0 'Battery Current Sensor 0-5V ADC on Input 0 (+100 to -100A) 2.5v = 0 Amps ButtonsADC var PORTA.1 'Menu Buttons ADC Input 0-5V ADC on Input 1 (0-5v = 1 of 6 values) (0,1,2,3,4,5) AnalogTemp1 var PORTA.2 'Analog Temp1 Sensor LM335 ADC Input (-55 to +150C) on Input 2 AnalogTemp2 var PORTA.3 'Analog Temp2 Sensor LM335 ADC Input (-55 to +150C) on Input 3 'Spare var PORTA.4 'Spare Input PCLogger var PORTA.5 'Serial BMS Data Output to Pc Logger Fan var PORTA.6 'Fan Output Pulse1S var PORTA.7 'One Second Pulse Input from Watchdog Chip '************************************ Memory Allocations for 16F886 ****************************************** '************************************************************************************************************* '************************************** 368 bytes of Ram available ******************************************* '************************************** 256 Bytes Eeprom [0 to 255] ****************************************** '************************************************************************************************************* '(Peek & Poke used to access RAM) (Read & Write used for EEprom) '****************** Eeprom Data Storage 0-255 bytes (This does not affect program memory) ******************* '*** Eeprom addresses constants LastErrCell con 250 'EEprom address Byte data [ErrCell] (Dec 251) Stored every Alarm LastAlarm con 251 'EEprom address Byte data [Alarm] (Dec 251) Stored every Alarm OdoStore0 con 252 'EEprom address Word data [Odo] (Dec 252) Stored every 10 seconds OdoStore1 con 253 'EEprom address Word data [Odo] (Dec 253) Stored every 10 seconds TripStore0 con 254 'EEprom address Word data [Trip] (Dec 254) Stored every 10 seconds TripStore1 con 255 'EEprom address Word data [Trip] (Dec 255) Stored every 10 seconds '************************************************************************************************************* Start: 'Initialise Program. Load Variables CLEAR 'Clear all variables to 0 PORTB = %00100000 'Set PortB outputs to off except PortB.5 PORTC.5 = 0 'Set PortC.5 output to off PORTC.6 = 0 'Set PortC.6 output to off StartBit = 1 'Set Start Bit ChgBit = 0 'Clear Charge Bit PcBit = 1 'Set PC Bit disable data logging TxBit = 1 'Set Tx Bit disable data Transmision TempInc = 0 'Set first temp sensor MaxCurrent = 0 'Clear the maximum recorded current ChargePause = 0 ChargerRestart = 0 AlarmCount = 0 Beeper = 0 I2CREAD Scdeeprom,Sckeeprom,AT24C512B,I2CAddr,[Cells,Odo.Byte0,Odo.Byte1,Trip.Byte0,Trip.Byte1,_ Dist.Byte0,Dist.Byte1,Soc.Byte0,Soc.Byte1] 'Read data from I2c eeprom starting at addr 0 I2Caddr = 14 'Set to first available byte of I2C EEprom (First 14 bytes [0-13] reserved for BMS data) 'Manual I2C Temp Sensor initiate routine avoids start up temperature errors 'owout DigitalTemp,1,[$55,$28,$B9,$11,$16,$01,$00,$00,$A4,$44] 'Start 1st temp sensor conversion owout DigitalTemp,1,[$55,$28,$F6,$A8,$BF,$03,$00,$00,$3C,$44] '(isaac temp 1) 'Display Splash Screen, Software Version, CPU Speed & Number of Cells in System. serout PcLogger, N9600, [13,"MSG, BMS Data,",13] 'Send Data to Port Initialise Excel spreadsheet ' gosub CheckCurrent 'Gosub CheckCurrent Routine to calculate sensor zero amps offset ' Offset = BatCurrent 'Set Current sensor Offset to BatCurrent (0 Amps) Around 512 ' Cells = 2 'Added to avoid system forgetting cell number due to missing EEPROm. DEBUG 27,70,27,46,27,67,"BMS Master V0.30www.150mpg.co.uk",10,13,"8mhz (",#Cells,") Cells" debug 27,49 'Use Font 1 ' Debug 10,13,"Offset ",#Offset,10,10,13 'Video Display pause 5000 'Pause for 5 seconds ClearBit = 1 '************************************************************************************************************* '************************************************************************************************************* MainLoop: 'Main program loop toggle WatchDogLed 'Keeps watchdog happy, flashes every other time through loop RetBit = 1 'set RetBit to 1 forces Buttons routine to return gosub Buttons 'Gosub Buttons. Push = 0 Up = 1 Right = 2 Down = 3 Left = 4 No But = 5 if Key = 0 then gosub SubMenu1 'If 'C' Menu button pressed gosub SubMenu1 If Key = 1 Then 'If Up key pressed then Display last stored Alarm ClearBit = 1 Read LastAlarm, Alarms 'Read last Alarm from PIC EEPROM Read LastErrCell, ErrCell 'Read last ErrCell from PIC EEPROM DEBUG 27,94 'Video Display Save Current Screen to SV2000 Flash pause 100 'pause for 100ms at 8mhz DEBUG 27,67,"Last Alarm = ",#Alarms,10,13,"Cell No = ",#ErrCell 'Clear Screen & Display Message Pause 3000 Alarms = 0 'Reset Alarms to 0 (Clears any Alarms) ErrCell = 0 'Reset ErrCell to 0 (Clears Error Cell) DEBUG 27,95 'Video Display Restore Current Screen from SV2000 Flash pause 100 'pause for 100ms at 8mhz endif if Key = 3 or ChargerRestart = 1 then ' Start the charger is needed ClearBit = 1 ' Clear the screen ChargerRestart = 0 ' Don't Restart the charger ChargePause = 0 ' Charger is not paused gosub StartChg ' Start/Toggle Charge Routine endif if PcBit = 0 then serout PcLogger, N9600, [13,"DATA,TIME,"] 'Tx BMS Data bytes to Spreadsheet endif if ChgBit = 1 then 'If charge Bit is set miss out if BalanceLoop <> BalanceCount then 'Jump over get Voltage routine BalanceCount = BalanceCount + 1 'Increment Balance Counter goto Balance 'Jump over get Voltage routine endif endif if ((ChgBit = 0) and (LoopCount = 25)) then Fan = 0 VarC1 = 2 serout SlaveBus,N9600, [VarC1] 'Send Command (VarC1) endif LoopCount = LoopCount + 1 ' Iterate the loop Count if ClearBit = 1 then 'Check to clear screen debug 27, 67 'Clear the screen ClearBit = 0 'Reset the clear bit LoopCount = 0 'Reset the loop count endif gosub CheckVoltage 'Gosub CheckVoltage routine to collect/display Cell V data BalanceCount = 0 'Reset Balance Count counter to 0 Balance: 'Jumps here if charging gosub CheckCurrent 'Gosub CheckCurrent routine to get Current Data from sensor gosub CalculateSoc 'Gosub CalculateSoc routine to calculate/display Current & Soc gosub CheckI2CTemp 'Gosub I2C Temp routine to collect/display battery temperature If ChgBit = 0 and ChargePause = 0 and ChargerRestart = 0 then 'Only check speed if needed gosub CheckSpeed 'Gosub CheckSpeed routine to calculate/display speed/distance else debug 27, 83, 0, 6, " "," "," " 'Clear lower screen if ChargePause = 1 then debug 27, 83, 0, 7, "Charging Paused" 'indicate charging is paused if ChgBit = 1 then debug 27,83, 4, 7, "Charging" ' incate charging endif if TxBit = 0 then serout DriveInhibit, N2400, [85, 85, 85, 85, 85] 'Txd 5 byte preamble to clear Rxd pause 10 'Pause for 10ms to allow serial rxd to clear serout DriveInhibit, N2400, ["bms",Alarms,ErrCell] 'Txd data (2400 baud at 8mhz) endif if ((Alarms > 0) and (AlarmCount > 0)) then gosub RunBeeper if Alarms > 0 then AlarmCount = AlarmCount + 1 'Increment Alarm Counter else AlarmCount = 0 'Reset Alarm Counter endif If Alarms > 0 and ChgBit = 1 then 'If charger is running any alarm cancels charging. gosub RunBeeper AlarmCount = AlarmActivate endif if AlarmCount = AlarmActivate then gosub RunBeeper gosub DisplayAlarms 'If an Alarm is set then Gosub DisplayAlarms endif if TSeconds > 59 then GOSUB EepromWrite 'If Sixty seconds have elapsed then store data TSeconds = TSeconds + 1 'Increment Sixty Second Timer Seconds = Seconds + 1 'Increment One Second Timer Alarms = 0 'Reset Alarms to 0 (Clears any Alarms) VarA = 0 if Pulse1S = 1 then 'Checks if Pulse1S pin is high (Pads out 1 second intervals) while Pulse1S = 1 'If pin is high waits until pin goes low VarA = VarA + 1 'Increment watchdog pusle check if VarA > 60000 then 'Timeout no watchdog 1S pulse Alarms = 9 goto MainLoop endif wend else while Pulse1S = 0 'If pin is low waits until pin goes high VarA = VarA + 1 'Increment watchdog pusle check if VarA > 60000 then 'Timeout no watchdog 1S pulse Alarms = 9 goto MainLoop endif wend endif goto MainLoop '************************************************************************************************************* CheckVoltage: 'Check Voltage subroutine, receives all cell V data VarB1 = 255 'Reset VarB1 to 255 (Used to accumulate lowest Cell V) VarB2 = 0 'Reset VarB2 to 0 (Used to accumulate highest Cell V) PackV = 0 'Reset PackV Total to zero = 0V If ChgBit = 1 or StartBit = 1 then 'If charger is operating or StartBit is 1 then send Command2 Loads Off Command = 2 'Set Command to be sent to 2 Gosub Commands 'Gosub send command routine StartBit = 0 'Clears Start bit endif 'Get Cell Voltage data into BD byte array serout SlaveBus,N9600, [1] 'Send Command 1 (Report Cell Voltages) for VarA1 = 1 to Cells 'Start for/next loop to get cell voltage data into array serin MasterBus, N9600, TimeOut, DataError, BD[VarA1] 'Receive Cell Voltage Data next VarA1 'repeat loop If ChgBit = 1 then 'If charger is operating send Command3 Loads On if required Command = 3 'Set Command to be sent to 3 gosub Commands 'Gosub send command routine endif 'Evaluate returned cell data 'Evaluates and calculates cell voltages for VarA1 = 1 to Cells 'Start for/next loop to receive cell CellV = 0 'Clear CellV Variable if PcBit = 0 then serout PCLogger, N9600, [#BD[VarA1],","] 'Send Cell Voltage Data to PC Logger endif if VarB2 < BD[VarA1] then 'If VData > VarB2 then (Current Cell > Highest Cell) VarB2 = BD[VarA1] 'VarB2 = VData (Store Current High Cell V in VarB2) HighCell = VarA1 'Store High V Cell number in HighCell Variable endif if VarB1 > BD[VarA1] then 'If VData < VarB1 then (Current Cell < Lowest Cell) VarB1 = BD[VarA1] 'VarB1 = VData (Store Current Low Cell V in VarB1) LowCell = VarA1 'Store Low V Cell number in LowCell Variable endif CellV = BD[VarA1] + DiscardLow 'CellV (w1) = CellV (w1) (1-255 0.01-2.55V) + (175 1.75V) if CellV > CutInVD then 'If Cell V > Load cut in point means this cell is charged FullBit [VarA1] = 1 'Set FullBit for this cell to 1 = Charged endif if CellV > AbsMaxCellV then 'If Cell V > AbsMaxCellV then set Alarms to 1 Alarms = 1 'Set Alarms to 1 (Indicates Cell over AbsMax Voltage condition) ErrCell = VarA1 'Store Error Cell number in ErrCell Variable endif if CellV < AbsMinCellV then 'If Cell V < AbsMinCellV then set Alarms to 2 Alarms = 2 'Set Alarms to 2 (Indicates Cell under AbsMin Voltage condition) ErrCell = VarA1 'Store Error Cell number in ErrCell Variable endif PackV = PackV + CellV 'Add Cell voltage to accumulated Pack voltage next VarA1 'Increment for/next loop and move to next cell 'End Charge and/or Battery Faults Detection. 'Detects battery out of range conditions. 'Detects end of charge by keeping record of highest pack voltage, when voltage falls more than (XXX mv Variable) 'means charge has terminated or battery/bms fault so switches off charger relay. 'Detects end of charge and cuts charger when all cells have reached balancing voltage. if PackV > MaxPackV then 'If PackV > Maximum pack voltage then set Alarm Alarms = 7 'set Alarms to 7 (Pack over Maximum Voltage) endif if PackV < MinPackV then 'If PackV < Minimum pack voltage then set Alarm Alarms = 8 'set Alarms to 8 (Pack under Minimum Voltage) endif if ChgBit = 1 then 'If Charger is operating (1) then Chg End FullBit Detection routine VarA2 = 1 'Charge End test Byte all Cells full = 1 for VarA1 = 1 to Cells 'Check FullBit array status (0 = Cell Not Full) (1 = Cell Full) if FullBit [VarA1] = 0 then 'Check if FullBit for Cell is set VarA2 = 0 'if it isnt set, clear Charge End Temporay Byte VarA2 endif next VarA1 'Check Next Cell if VarA2 = 1 then 'If VarA2 is 1 then all cells have reached balancing voltage during cycle Alarms = 6 'Set Alarms to 6 (Indicates End of Charge) endif endif if ChgBit = 1 then 'If Charger is operating (1) then Chg End & Batt Chg Fault Detection if PackV < HighPackV then 'If Pack Voltage is less than stored Pack Voltage then HighPackV = HighPackV - PackV 'Calculate Voltage Drop result stored in HighPackV if HighPackV > VoltageDrop then 'If Pack Voltage Drop > than permitted Voltage Drop then Alarms = 5 'Set Alarms to 5 (Indicates Pack Voltage dropping) endif else HighPackV = PackV 'Load HighPackV from PackV endif endif 'Display Pack Voltage VarA = PackV / 100 'Get first part of decimal value (Before decimal point) VarC = PackV // 100 / 10 'Get second part of decimal value (After decimal point) VoltsData = VarA1 'Store VarA1 in VoltsData Variable (Note 0-255V Max) DEBUG 27,83,0,0,"Vol ",#VarA,".",#VarC,"V ",#Seconds 'Video Display 'Display Highest Cell Voltage VarA = VarB2 + DiscardLow 'Highest Cell V Correction factor if VarA > MaxCellV then 'If VarA > MaxCellV then activate Charger Opto high Charger 'Activate Charger pull down opto high Led1 'Activate dash Over-V Led1 endif if ((VarA > FanCutOff) and (ChgBit = 1) and (Fan = 0)) then Fan = 1 endif VarC = VarA / 100 'Get first part of decimal value (Before decimal point) VarD = VarA // 100 'Get second part of decimal value (After decimal point) VarA1 = HighCell 'Load VarA1 with data from HighCell Variable DEBUG 27,83,0,1,"HiV [" 'Video Display if VarA1 < 10 then 'If Leading zero reqd in display due to Value <10 then DEBUG "0" 'Video Display endif if VarD <10 then 'Check if leading zero reqd in display DEBUG #VarA1,"] ",#VarC,".0",#VarD,"V " 'Video Display else DEBUG #VarA1,"] ",#VarC,".",#VarD,"V " 'Video Display endif 'Display Lowest Cell Voltage VarA = VarB1 + DiscardLow 'Lowest Cell V Correction factor if VarA < MinCellV then 'If VarA < MinCellV then activate Controller Opto high Controller 'Activate Controller pull down opto high Led2 'Activate dash Under-V Led2 else low Controller 'De-Activate Controller pull down opto low Led2 'De-Activate dash Under-V Led2 endif VarC = VarA / 100 'Get first part of decimal value (Before decimal point) VarD = VarA // 100 'Get second part of decimal value (After decimal point) VarA1 = LowCell 'Load VarA1 with data from LowCell Variable DEBUG 27,83,0,2,"LoV [" 'Video Display if VarA1 < 10 then 'If Leading zero reqd in display due to Value <10 then DEBUG "0" 'Video Display endif if VarD <10 then 'Check if leading zero reqd in display DEBUG #VarA1,"] ",#VarC,".0",#VarD,"V " 'Video Display else DEBUG #VarA1,"] ",#VarC,".",#VarD,"V " 'Video Display endif return 'Return to main program loop Commands: 'Sends and acknowledges Commands serout SlaveBus,N9600, [Command] 'Send Command for VarA1 = 1 to Cells 'Start for/next loop to wait until previous command complete serin MasterBus, N9600, TimeOut, DataError, VData 'Check Command If VData <> Command then CommandError 'If Command is not correct then error! next VarA1 'repeat loop return 'Return DataError: 'Serial Slave Data Rxd TimeOut, If no Data within (TimeOut = 100ms) ErrCell = VarA1 'Store Error Cell number in ErrCell Variable Alarms = 3 'Set Alarms to 3 (Indicates Cell Serial Data Timeout Error) return 'Return to main program loop CommandError: 'Returned Command error, (Sent and recd Commands do not match) ErrCell = VarA1 'Store Error Cell number in ErrCell Variable ErrCom = VData 'Store Returned Command Alarms = 10 'Set Alarms to 10 (Indicates Command Error) return 'Return to main program loop '************************************************************************************************************** CheckI2CTemp: 'Check Battery Pack temperature routine (Multiple DS18B20 I2C Sensors) 'The owout lines become ($55,xx,xx,xx,xx,xx,xx,xx,xx,$44) where xx = decimal serial no's for the I2C chips 'AND ($55,xx,xx,xx,xx,xx,xx,xx,xx,$BE) where xx = decimal serial no's for the I2C chips 'Sensor 1 (Front Battery Block) OWIN DigitalTemp, 4, [StatBit] 'Check for still busy converting IF StatBit = 0 THEN 'Still busy return endif if TempInc = 0 then 'Read 1st sensor and start 2nd sensor temp conversion 'owout DigitalTemp,1,[$55,$28,$B9,$11,$16,$01,$00,$00,$A4,$BE] 'Match ROM & Convert Temperature owout DigitalTemp,1,[$55,$28,$F6,$A8,$BF,$03,$00,$00,$3C,$BE] '(isaac temp 1) gosub calculatetemp 'Gosub Calculate Temperature routine Temp1Before = VarD1 'Store Front Pack Temp in Temp1Before Temp1After = VarD2 'Store Front Pack Temp in Temp1After 'Temp1Sign = VarB1 'Store Front Pack Temp Sign 'owout DigitalTemp,1,[$55,$28,$CD,$11,$EA,$00,$00,$00,$28,$44] 'Start 2nd sensor conversion owout DigitalTemp,1,[$55,$28,$DD,$8C,$BF,$03,$00,$00,$95, $44] '(isaac temp 2) endif 'Sensor 2 (Rear Battery Block) if TempInc = 1 then 'Read 2nd sensor and start 1st sensor temp conversion 'owout DigitalTemp,1,[$55,$28,$CD,$11,$EA,$00,$00,$00,$28,$BE] 'Match ROM & Convert Temperature owout DigitalTemp,1,[$55,$28,$DD,$8C,$BF,$03,$00,$00,$95,$BE] '(isaac temp 2) gosub calculatetemp 'Gosub Calculate Temperature routine Temp2Before = VarD1 'Store Rear Pack Temp in Temp2Before Temp2After = VarD2 'Store Rear Pack Temp in Temp2After 'Temp2Sign = VarB1 'Store Rear Pack Temp Sign 'owout DigitalTemp,1,[$55,$28,$B9,$11,$16,$01,$00,$00,$A4,$44] 'Start 1st sensor conversion owout DigitalTemp,1,[$55,$28,$F6,$A8,$BF,$03,$00,$00,$3C,$44] '(isaac temp 1) endif DigitsIn = Temp1Before gosub CountDigits VarD = NumDigits DigitsIn = Temp2Before gosub CountDigits VarD = VarD + NumDigits DEBUG 27,83,0,3,"Tem ",#Temp1Before,".",#Temp1After," ",#Temp2Before,".",#Temp2After for VarA = 1 to (7-VarD) debug " " next VarA if PcBit = 0 then serout PCLogger, N9600, [#Temp1Before,",",#Temp2Before,","] 'Send Pack Temp Data to PC Logger endif TempInc = TempInc + 1 'Increment temp sensors if TempInc >= TempSensors then 'If past last sensor then restart at sensor 0 TempInc = 0 'Reset to first temp sensor endif return 'Return to main program loop CalculateTemp: owin DigitalTemp, 2, [VarA.Lowbyte, VarA.Highbyte] 'Read two bytes / end comms 'Convert 12bit DS18B20 value to a useable Temperature range -55 to +127C 'VarB1 = 43 'Display + (43) IF VarA > 64655 THEN '- 55 degrees = 64656 'VarB1 = 45 'Display - (45) VarA = - VarA 'if - ie w1=100 display 10.0 -c ENDIF VarA = VarA * 5 / 8 '+ ie w1=850 display 85.0c VarA = VarA/10*18 + 373 ' Convert to deg F , experimentally interpulated VarD1 = VarA / 10 'Calculate Temp prior to decimal point VarD2 = VarA // 10 'Calculate Temp after decimal point if VarD1 >= MaxTemp AND VarD1 < 257 then 'If Temp > MaxTemp set Alarms (Also checks if value is within sensor range) Alarms = 4 'Set Alarms to 4 (Indicates Temp over MaxTemp condition) endif if VarD1 <= MinTemp and ChargePause = 1 then ' Check if minimum tempurature is reached and charger is paused ChargerRestart = 1 ' Send the charger restart signal ChargePause = 0 ' Clear the charger pause bit endif return 'Return to CheckTemp '************************************************************************************************************** CheckCurrent: 'Accumulate (Current in Amps) charge/discharge data CurrentOld = BatCurrent 'Store Previous Current BatCurrent = 0 'Reset Current oversampling accumulator to 0 (Zero) ADCON0 = %01000001 'SETUP ADC CONVERTER MODULE FOSC/8 & ENABLE ADC MODULE on AD0 for VarA2 = 0 to 3 '4 x Current Oversampling ADCON0.1 = 1 'Start ADC conversion while ADCON0.1 = 1 'Wait for ADC DONE wend VarB.highbyte = ADRESH 'Move HIGH byte of result to Variable (0-1023 10bit)(-100A to +100A 0-5V) VarB.lowbyte = ADRESL 'Move LOW byte of result to Variable (0-1023 10bit)(-100A to +100A 0-5V) BatCurrent = BatCurrent + VarB 'Add Current to accumulated Current Next VarA2 'Repeat loop until 4 samples obtained return 'Return to main program loop '************************************************************************************************************** CalculateSoc: 'Calculate Soc routine BatCurrent = BatCurrent >> 1 'Divide by 2, BatCurrent is now 0-2048 BatCurrent = BatCurrent ** 64000 'Multiply by ~0.97656, BatCurrent is now 0-2000 BatCurrent = BatCurrent + 25 ' Offset correction (2/20/12) (+67=-27, 40=-5) If BatCurrent < 1001 then 'Means system is Discharging BatCurrent = 1001 - BatCurrent AmpSign = 43 'Set ascii Character 45 (-) to be displayed if Discharging else 'Means system is Charging BatCurrent = BatCurrent - 1001 AmpSign = 45 'Set ascii Character 43 (+) to be displayed if charging endif 'BatCurrent = BatCurrent / 10 'Divide by 10 to get -100 - 0 - +100 A 'BatCurrent = BatCurrent * 3 / 2 ' Convert to get -1500 to +1500 A BatCurrent = BatCurrent * 1600 / 2000 'convert to get -800 to +800A if ((BatCurrent < 4) and (ChgBit != 1)) then BatCurrent = 0 'Remove Stray amp readings if BatCurrent > MaxCurrent then MaxCurrent = BatCurrent endif If PcBit = 0 then serout PCLogger, N9600, [#AmpSign,",",#BatCurrent,","] 'Send Amp Sign & Battery Current Data to PC Logger endif 'Display Battery Amps & Motor Power Kw VarA = (PackV / 100) * BatCurrent VarB = (VarA // 1000) / 100 PowerConsumption = VarA / 1000 DEBUG 27,83,0,4,"I ",AmpSign,#BatCurrent' " KW ",#VarA,".",#VarB," " 'Video Display DigitsIn = BatCurrent if NumDigits > 3 then ClearBit = 1 if NumDigits < 3 then debug " " if NumDigits < 2 then debug " " 'if CurrentOld > BatCurrent then 'if (CurrentOld > 100) then 'if BatCurrent < 10 then debug " " 'if BatCurrent < 100 then debug " " 'endif 'if (CurrentOld > 10) then 'if BatCurrent < 10 then debug " " 'endif 'endif '1ah = 3600 Amp Seconds '10ah = 36000 Amps Seconds (SocUnit 36) '20ah = 72000 Amp Seconds (SocUnit 72) '100ah = 360000 Amp Seconds (SocUnit 360) 'Percentage counter is represented by 1,000 = 100% Soc Each 0.1% for a 20ah battery is represented by 72 Amp Seconds 'Soc New Routine for 20ah Cells 72000 Amp Seconds (SocUnit = 72) Gives resolution to 0.1Ah if AmpSign = 43 then 'If AmpSign = "+" then Charge calculations Charge = Charge + BatCurrent 'Add latest Current data to running Charge total while Charge > SocUnit 'If Charge > SocUnit then execute code in loop Soc = Soc + 1 'Increment Soc % by one unit Maximum 1000 (100%) If Soc > 1000 then 'Limit Soc to 1000 (100%) Soc = 1000 'Set Soc to 100% endif Charge = Charge - SocUnit 'Subtract (SocUnit) from Charge WEND 'Repeat loop until Charge < SocUnit endif if AmpSign = 45 then 'If AmpSign = "-" then Discharge calculations Discharge = Discharge + BatCurrent 'Add latest Current data to running Discharge total while Discharge > SocUnit 'If Discharge > SocUnit then execute code in loop Soc = Soc - 1 'Decrement Soc % by one unit Minimum 1 (0.01%) If Soc < 1 then 'Limit Soc to 1 (00.01%) Soc = 1 endif Discharge = Discharge - SocUnit 'Subtract (SocUnit) from Discharge WEND 'Repeat loop until Discharge < SocUnit endif 'Display Battery State of Charge SOC in % VarC = Soc / 10 'Get first part of value (Before decimal point) if PcBit = 0 then serout PCLogger, N9600, [#VarC1,","] 'Send Soc Data to PC Logger endif SocData = VarC1 'Store Soc % in SocData Ram Byte (Note 0-100%) VarA = Soc // 10 'Get second part of value (After decimal point) DEBUG 27,83,0,5,"Soc ",#VarC,".",#VarA," % Ch",#ChgBit," " 'Video Display return 'Return to main program loop '************************************************************************************************************** '************************************************************************************************************** CheckSpeed: 'Pulsin measures VSS pulse. If no pulse result will be 0 pulsin SpeedSensor,4,VarB 'Measure length of a VSS pulse in 5us (8mhz) units (Timeout 400ms) 'count SpeedSensor,325,VarB 'Measure the number of input pusles in a 325 ms period, works out to speed in mpg if VarB = 0 then JumpSpeed 'If VarB = 0 pulsin has timed out and vehicle is moving at < 0.813 mph VarB = VarB / 8 'Divide VarB by 11 to enable results to fit into 16 bit Integer Maths VarB = (59722 / VarB) 'Returns speed in mph accurate to 1 mph Approx! JumpSpeed: 'Jump here to avoid calculation errors if PcBit = 0 then serout PCLogger, N9600, [#VarB1,","] 'Send vehicle Speed Data to PC Logger endif 'Display Speed if VarB > 0 then WhMile = PowerConsumption*(10000/VarB) 'Calculate the watt hours per mile else WhMile = 0 endif gosub CountDigits WhMile3 = WhMile2 WhMile2 = WhMileAvg WhMileAvg = (WhMile3 + WhMile2 + 2*WhMile)/4 'Weigthed Average debug 27,83,7,4, "Wh/m " , #WhMileAvg 'display the watt hours per mile DigitsIn = WhMileAvg if NumDigits > 3 then ClearBit = 1 if NumDigits < 3 then debug " " if NumDigits < 2 then debug " " VarE = (VarB // 10) ' find the remainder after dividing by ten VarB = VarB/10 ' find the number befor the decimal if VarB < 10 then PrevDigit = 0 ' Update the previous digit CursorX = 5 ' Posistion Cursor Digit = VarB.lowbyte ' Print the first digit gosub DisplayBigDigits CursorX = 8 ' Reposition Cursor Digit = 11 ' Print a decimal gosub DisplayBigDigits CursorX = 9 ' Reposition Digit = VarE ' Print Last digit gosub DisplayBigDigits else If PrevDigit = 0 then 'Check if removal is needed debug 27,83,8,8," " 'Remove the decimal point PrevDigit = 1 'Update the Previous Digit endif CursorX = 5 'Position Cursor Digit = VarB.lowbyte/10 'Print first digit gosub DisplayBigDigits CursorX = 9 'Reposition Digit = (VarB.lowbyte // 10) 'Print last digit gosub DisplayBigDigits endif 'Display the MPH debug 27, 83, 13, 8, "MPH" 'CheckDistance 'Check Distance routine needs to execute once per second. '528ft per 1/10th mile / Tyre circumference 5.8ft = 910 revs per mile '1mph = 1.4667ft per second. 1mile = 5280ft VarB = VarB * 146 'Work out feet travelled in last second (1mph = 1.466ft) Dist = Dist + VarB 'Add Distance travelled to any saved from previous loop while Dist >= 52800 'If distance travelled > 528ft 1/10th mile then execute code in loop Tenths = Tenths + 1 'Increment by 1 /10th Mile Trip = Trip + 1 'Increment TripMeter by 1/10th mile Max 999.9 miles if VarA1 = 10 then 'If VarA1 = 10 one mile has been travelled Odo = Odo + 1 'Increment OdoMeter by 1 mile 'Calculate Miles Remaining ' read PrevOdo0, VarA.Byte0 'Load last saved PrevOdo (Miles) reading from eeprom storage ' read PrevOdo1, VarA.Byte1 'Load last saved PrevOdo (Miles) reading from eeprom storage ' read PrevSoc0, VarC.Byte0 'Load last saved PrevSoc (Miles) reading from eeprom storage ' read PrevSoc1, VarC.Byte1 'Load last saved PrevSoc (Miles) reading from eeprom storage VarA = Odo - VarA 'Subtract Previous Odo from Current Odo (VarA = Dist in whole miles) VarB = VarC - Soc 'Subtract Current Soc from Previous Soc (VarB = Cap Used) VarC = VarB / VarA 'Divide Capacity used by distance travelled (VarC = Cap/Dist) VarD = Soc / VarC 'Divide Soc by Cap/Dist (VarD = Miles Remaining) DistRem = VarD 'Load DistRem from VarD 'Calculate wh/mile WhMile = ((VarC * 4) * 16) / 100 'Calculate wh/mile MAX 999 if WhMile > 999 then 'Limit WhMile to 999 WhMile = 999 endif Tenths = 0 'Reset (Tenths) counter endif Dist = Dist - 52800 'Subtract (528ft 1/10th mile) from Distance WEND 'Repeat loop until Distance < 52800 If Trip > 9999 then 'Limit tripmeter to 999.9 miles Trip = 0 endif If Odo > 9999 then 'Limit Odometer to 9999 miles Odo = 0 endif 'DEBUG 27,83,0,7,"Odo ",#Odo," Rem ",#DistRem," " 'Video Display VarA = Trip / 10 'Get first part of decimal value (Before decimal point) VarB = Trip // 10 'Get second part of decimal value (After decimal point) DEBUG 27,83,0,7,"Trip" debug 27,83,0,8, #VarA,".",#VarB 'Video Display return 'Return to main program loop '************************************************************************************************************** '************************************************************************************************************** DisplayBigDigits: if Digit < 10 then Address = (Digit*9) + 20 ' Get the address for the EEPROM lookup table ' else DEBUG 27,83,CursorX,8,248 ' display "." Character return endif for iter = 6 to 8 ' Exit loop after Three iterations DEBUG 27,83,CursorX,iter 'Set Cursor Position for i = 1 to 3 ' Display current row read Address, Asci ' Read the stored Address debug Asci ' Send the Ascii char Address = Address + 1 ' interate the address next i next iter return '************************************************************************************************************** '************************************************************************************************************** DisplayAlarms: 'Alarms turns on led/audible alarms and turns off charger etc ClearBit = 1 'Clear the screen at main loop DEBUG 27,94 'Video Display Save Current Screen to SV2000 Flash pause 1000 'pause for 100ms at 8mhz DEBUG 27,33,27,67," * BMS Alarms * ",10,13 'Display Negative, Clear Screen & Display Message 'Activate audible alarm high Charger 'Activate Charger pull down opto high Controller 'Activate Controller pull down opto low ChargerOnOff 'Turn off Charger main relay If Alarms = 1 then '(Cell over AbsMax V) DEBUG "Cell ",#ErrCell," >AMaxV" 'Video Display endif If Alarms = 2 then '(Cell under AbsMin V) DEBUG "Cell ",#ErrCell," Max Temp" 'Video Display endif If Alarms = 5 then '(Pack Voltage Dropped) DEBUG "Voltage Drop" 'Video Display endif If Alarms = 6 then '(All Cells Reached Balancing Voltage) DEBUG "Charge End OK" 'Video Display Soc = 1000 'Set Soc to 100% endif If Alarms = 7 then '(Pack over Maximum Voltage) DEBUG "Pack V > Max V" 'Video Display endif If Alarms = 8 then '(Pack under Minimum Voltage) DEBUG "Pack V < Min V" 'Video Display endif If Alarms = 9 Then '(Watchdog chip 1S pulse missing) DEBUG "Watchdog Pulse" 'Video Display endif If Alarms = 10 Then '(Command Error, Sent & Recd do not match!) DEBUG "Cell ",#ErrCell," Cmd ",#ErrCom 'Video Display endif if Alarms != 2 then 'Don't turn off the charger if under voltage ChgBit = 0 'Set ChgBit to 0 indicates Charging Off LoopCount = 0 endif I2CWRITE Scdeeprom,Sckeeprom,AT24C512B,I2CAddr,[Alarms,ErrCell,Seconds,255] 'Write data to I2c eeprom I2CAddress I2CAddr = I2CAddr + 4 'Increment I2CAddress word variable Write LastAlarm, Alarms 'Write last Alarm to PIC EEPROM Write LastErrCell, ErrCell 'Write last ErrCell to PIC EEPROM pause 3000 'Pause for 3 second Command = 2 'Set Command to be sent to 2 Turn Off Loads gosub Commands 'Gosub send command routine Alarms = 0 'Reset Alarms to 0 (Clears any Alarms) ErrCell = 0 'Reset ErrCell to 0 (Clears Error Cell) AlarmCount = 0 'Reset AlarmCount to 0 (Clears Alarm Counter) DEBUG 27,95 'Video Display Restore Current Screen from SV2000 Flash return 'Return to main program loop '************************************ RunBeeper: Beeper = 1 pause 1000 Beeper = 0 return '************************************************************************************************************** '************************************************************************************************************** Buttons250: 'Decodes button Inputs and returns 5 values (0,1,2,3,4) in Key (Wait 250ms) pause 240 'Pause for 240ms Buttons: 'Decodes button Inputs and returns 5 values (0,1,2,3,4) in Key (No wait) Key = 5 'Load Key with 5 no button pressed ADCON0 = %01000101 'SETUP ADC CONVERTER MODULE FOSC/8 & ENABLE ADC MODULE on AD1 ADCON0.1 = 1 'Start ADC conversion while ADCON0.1 = 1 'Wait for ADC DONE wend VarB.highbyte = ADRESH 'Move HIGH byte of result to Variable (0-1023 10bit) VarB.lowbyte = ADRESL 'Move LOW byte of result to Variable (0-1023 10bit) If VarB > 939 then ExitButtons 'If No key pressed then return pause 25 'Wait for 25ms allow button to stablise ADCON0.1 = 1 'Start ADC conversion while ADCON0.1 = 1 'Wait for ADC DONE wend VarD.highbyte = ADRESH 'Move HIGH byte of result to Variable (0-1023 10bit) VarD.lowbyte = ADRESL 'Move LOW byte of result to Variable (0-1023 10bit) if VarB <> VarD then Buttons 'If two readings not the same then try again if varB < 940 then 'Test if button 4 pressed Key = 4 endif if varB < 767 then 'Test if button 3 pressed Key = 3 endif if varB < 597 then 'Test if button 2 pressed Key = 2 endif if varB < 426 then 'Test if button 1 pressed Key = 1 endif if varB < 255 then 'Test if button 0 pressed Key = 0 endif ExitButtons: If RetBit = 1 then 'If RetBit is set then exit Buttons after one loop RetBit = 0 'Clears RetBit return endif if Key > 4 then Buttons250 'If Invalid Key or No Key (5) then repeat return 'Return to main program '************************************************************************************************************** '************************************************************************************************************** StartChg: 'Start Charging Cycle Routine LoopCount = 0 high Alarm 'Activate audible alarm If ChgBit = 0 then 'If Charge Flag is not set then set Charge Flag ChgBit = 1 'Set ChgBit = 1 low Charger 'De-activate Charger pull down opto low Controller 'De-activate Controller pull down opto low Led1 'De-Activate dash Over-V Led1 high ChargerOnOff 'Turn on Charger main relay HighPackV = 0 'Load HighPack Voltage data with 0 for VarA1 = 1 to Cells 'Start for/next loop to clear Charged/Not Charged Bit Array (FullBit) FullBit [VarA1] = 0 'Clear Cell Full Bit Next VarA1 'Repeat Loop until all cleared else 'If Charge Flag is set then clear Charge Flag ChgBit = 0 'Set ChgBit = 0 low ChargerOnOff 'Turn off Charger main relay high Charger 'Activate Charger pull down opto endif pause 250 'Pause for 250ms at 8mhz low Alarm 'Deactivate audible alarm pause 500 'Pause for 500ms at 8mhz goto MainLoop 'Got Main Loop '************************************************************************************************************** SubMenu1: 'Sub Menu 1 routine for extended BMS functions pause 100 ClearBit = 1 ' Set bit to clear the screen in main program loop Debug 27,94 'Video Display Save Current Screen to SV2000 Flash pause 1000 'Pause for 100ms at 8mhz Debug 27,67,"* Sub Menu 1 *",10,13,"Cell Voltage = UReset Soc = LReset Trip = DReset Odo = RSub Menu 2 = C" pause 1000 'Pause for one second gosub Buttons250 'Gosub Buttons250 gets button. Push = 0 Up = 1 Right = 2 Down = 3 Left = 4 if Key = 0 then SubMenu2 'If Key = 0 then goto Sub Menu 2 if Key = 1 then goto Display 'If Key = 1 then goto Display Cell Voltages endif if Key = 2 then 'If Key = 2 then Reset Odometer to 0 miles Odo = 0 'Set Odo to Zero endif if Key = 3 then 'If Key = 3 then Reset Tripmeter to 0 miles Trip = 0 'Set Tripmeter to Zero endif if Key = 4 then 'If Key = 4 then Reset Soc to 100% Soc = 1000 'Set Soc to 1000 (100%) endif ExitMenu: Debug 27,67,27,95 'Video Display Restore Current Screen from SV2000 Flash Key = 0 'Return 0 for Key return 'Return to main program '************************************************************************************************************** SubMenu2: 'Sub Menu 2 routine for extended BMS functions Debug 27,67,"* Sub Menu 2 *",10,13,"Max Current = U","Slave Update = L","Num Of Cells = D","Temp Rom = R","Sub Menu 3 = C" pause 1000 'Pause for one second gosub Buttons250 'Gosub Buttons250 gets button. Push = 0 Up = 1 Right = 2 Down = 3 Left = 4 if Key = 0 then SubMenu3 'If Key = 0 then goto SubMenu3 if Key = 1 then 'If Key = 1 Display Max Current Debug 27,67,"* Max Current *",10," ",#MaxCurrent," Amp" Key = 0 gosub Buttons250 goto ExitMenu endif if Key = 2 then TempRom 'If Key = 2 then goto Discover I2C Temp Sensor Serial Number if Key = 3 then CellNumber 'If Key = 3 then goto Cell Number if Key = 4 then UpdateSlaves 'If Key = 4 then goto Update Slaves '************************************************************************************************************** SubMenu3: 'Sub Menu 3 routine for extended BMS functions Debug 27,67,"* Sub Menu 3 *",10,13,"Toggle PcBit = UToggle ChgOp = LToggle TxBit = DToggle ConOp = RExit Menu = C" debug 10,13,"Charg ",#Charger," Contr ",#Controller pause 2000 'Pause for one second gosub Buttons250 'Gosub Buttons250 gets button. Push = 0 Up = 1 Right = 2 Down = 3 Left = 4 if Key = 0 then ExitMenu 'If Key = 0 then goto ExitMenu if Key = 1 then PcBit = !PcBit 'If Key = 1 then TogglePcBit (Data logging) if Key = 2 then 'If Key = 2 then Toggle Controller Opto Controller = !Controller endif if Key = 3 then TxBit = !TxBit 'If Key = 3 then ToggleTxBit (Remote Display) if Key = 4 then 'If Key = 4 then Toggle Charger Opto Charger = !Charger endif Key = 0 goto SubMenu3 '************************************************************************************************************** CellNumber: 'Set number of cells in pack routine (Max 95) DEBUG 27,83,0,8,"Cells ",#Cells," " 'Video Display gosub Buttons250 'Gosub Buttons gets button. Push = 0 Up = 1 Right = 2 Down = 3 Left = 4 if Key = 1 then 'If Button pressed is up then increment cells Cells = Cells + 1 'Increment Cells Variable if Cells > 95 then 'Prevent Cells > 95 being selected Cells = 1 'Set Cells to 1 endif endif If Key = 3 then 'If Button pressed is down then decrement cells Cells = Cells - 1 'Decrement Cells Variable if Cells < 1 then 'Prevent Cells < 1 being selected Cells = 95 'Set Cells to 95 endif endif If Key <> 0 then CellNumber 'If Centre button not pressed then goto loop1 Gosub EepromWrite 'Gosub I2C EepromWrite to store data goto ExitUpdate 'Goto Exit Update '************************************************************************************************************** Display: 'Display Individual Cell Voltages and Cell Full Bit Data DEBUG 27,67,"Center to Exit",10,13 'Video Display for VarA1 = 1 to Cells 'Start for/next loop to retrieve cell data CellV = BD[VarA1] + DiscardLow 'CellV (w1) = CellV (w1) (1-255 0.01-2.55V) + (175 1.75V) VarB = CellV / 100 'Get first part of decimal value (Before decimal point) VarC = CellV // 100 'Get second part of decimal value (After decimal point) if VarC < 10 then 'If Leading zero reqd in display due to Value <10 then DEBUG #VarA1,") ",#VarB,".0",#VarC,"V FB",#FullBit[VarA1],10,13 'Video Display else DEBUG #VarA1,") ",#VarB,".",#VarC,"V FB",#FullBit[VarA1],10,13 'Video Display endif gosub Buttons250 'Gosub Buttons250 gets button. Push = 0 Up = 1 Right = 2 Down = 3 Left = 4 If Key = 0 then goto ExitUpdate 'If Down Button is pressed then next cell endif next VarA1 'Display Next Cell goto ExitUpdate 'Goto ExitUpdate '************************************************************************************************************** '*********************************************** Slave Commands *********************************************** UpdateSlaves: 'Update Slaves in situ (Non Picaxe Serial command slaves only) VarC1 = 7 'Set variable VarC1 to Command 7 (Turn on Load for 0.5 seconds, flash Led) Loop5: 'Loop here to wait for button press DEBUG 27,83,0,8," Command ",#VarC1," ! " 'Video Display gosub Buttons250 'Gosub Buttons250 gets button. Push = 0 Up = 1 Right = 2 Down = 3 Left = 4 if Key = 1 then 'If Button pressed is up then increment command varC1 = varC1 + 1 'Increment Variable VarC1 if varC1 > 13 then 'If varC1 > 13 then set varC1 to 1 varC1 = 1 'Prevents sending unsupported commands endif endif If Key = 3 then 'If Button pressed is down then decrement command varC1 = varC1 - 1 'Decrement Variable VarC1 if varC1 = 0 then 'If varC1 = 0 then set varC1 to 13 varC1 = 13 'Prevents sending unsupported commands endif endif If Key <> 0 then Loop5 'If Centre button not pressed then goto loop5 DEBUG 10,10,13,"Confirm (Y/N) ? Up=Yes Down=No" 'Video Display Loop6: 'Loop here to wait for button press gosub Buttons250 'Gosub Buttons250 gets button data (0,1,2,3,4,5 = no button) in Key If Key = 1 then ConfirmUpdate 'If Button pressed is up then update slaves If Key = 3 then ExitUpdate 'If Button pressed is down then cancel update Goto Loop6 'Goto Loop6 ConfirmUpdate: DEBUG 27,67,"Command ",#VarC1," Sent" 'Video Display serout SlaveBus,N9600, [VarC1] 'Send Command (VarC1) SlaveCheck: 'Checks Slaves have received and acknowledged commands for VarA1 = 1 to Cells 'Start for/next loop to wait until previous command complete serin MasterBus, N9600, 1000, CommError, BD[VarA1] 'Check Commands next VarA1 'repeat loop for VarA1 = 1 to Cells 'Start for/next loop to receive data if VarC1 = 8 then 'If Command sent was 8 (report slave software version number) then DEBUG 10,13,"Slave ",#VarA1," V",#BD[VarA1]," " 'Video Display pause 250 'Pause for 1/4 second goto CheckLoop 'Goto CheckLoop endif if VarC1 <> BD[VarA1] then 'If bytes don't match then command error DEBUG 10,13,"Slave ",#VarA1," Error!" 'Video Display high Alarm 'Activate audible alarm pause 3000 'Pause for 3 seconds low Alarm 'Deactivate audible alarm goto ExitUpdate 'Goto ExitUpdate endif CheckLoop: 'Slave Commands Check Loop label next VarA1 'Increment for/next loop and move to next cell DEBUG 10,13,"Acknowledged OK" 'Video Display ExitUpdate: 'Jump here to exit updates DEBUG 10,13,"Any Key To Exit" 'Video Display gosub Buttons250 'Gosub Buttons250 gets button data (0,1,2,3,4,5 = no button) in Key DEBUG 27,67,27,95 'Video Display Restore Current Screen from SV2000 Flash return 'Return to main program loop CommError: 'Command serial comms error DEBUG 10,13,"* Comms Error! *",10,13," Cell ",#VarA1," " 'Video Display high Alarm 'Activate audible alarm pause 3000 'Pause for 3 seconds low Alarm 'Deactivate audible alarm goto ExitUpdate 'Goto Exit Update '**************************************************************************** TempRom: 'Read I2C DS18B20 Temp Sensor Rom Code debug 27,67,"Ins Temp Sensor",10,13,"Any key to scan",10,13 'Video Display gosub Buttons250 'Gosub Buttons250 gets button data (0,1,2,3,4,5 = no button) in Key OWOUT DigitalTemp, 1, [$33] 'Issue Read ROM command OWIN DigitalTemp, 0, [STR I2C1\8] 'Read 64-bit device data into the 8-byte array "I2C1" Pause 100 'Pause for 100ms For VarA1 = 0 to 7 'Display Sensor 1 data DEBUG HEX2 I2C1[VarA1] 'Video Display Next VarA1 goto ExitUpdate 'Goto Exit Update '**************************************************************************** EepromWrite: 'Write contents to I2C EEprom VarA = 0 I2CWrite Scdeeprom,Sckeeprom,AT24C512B,VarA,[Cells,Odo.Byte0,Odo.Byte1,Trip.Byte0,Trip.Byte1,_ Dist.Byte0,Dist.Byte1,Soc.Byte0,Soc.Byte1] 'Write data to I2c eeprom starting at addr 0 'Line from above TSeconds = 0 'Reset 60 second counter return 'Return '***************************************************************** CountDigits: NumDigits = 1 if DigitsIn >= 10 then NumDigits = 2 if DigitsIn >= 100 then NumDigits = 3 if DigitsIn >= 1000 then NumDigits = 4 return '**************************************************************************** ' VarA = 14 'Set I2C Address variable to 14 Start of Alarm Data 'AlarmCheck: 'Checks EEprom for stored Alarms from previous BMS cycle ' I2CREAD Scdeeprom,Sckeeprom,AT24C512B,VarA,[VarB1,VarB2,VarC] 'Read data from I2c eeprom starting at VarA ' gosub Buttons 'Gosub Buttons. Push = 0 Up = 1 Right = 2 Down = 3 Left = 4 No But = 5 ' if Key = 0 then 'If Push then skip alarm listing ' VarB1 = 255 'Set End Of data Flag ' endif ' if Key = 1 then 'If Up then clear eeprom alarm data ' VarB1 = 255 'Set End Of data Flag ' I2CWRITE Scdeeprom,Sckeeprom,AT24C512B,VarA,[VarB1] 'Write end of data flag to EEprom data start address ' goto AlarmCheck 'Goto AlarmCheck ' endif ' if VarB1 = 255 then 'Eof (End of file) Detected ' pause 4000 'Pause for 4 seconds ' DEBUG 27,67 'Clear Screen ' goto MainLoop 'Goto Main Loop ' endif ' DEBUG #VarB1," ",#VarB2," ",#VarC," ",#VarA,10,13 'Video Display ' VarA = VarA + 4 'Increment VarA ' goto AlarmCheck