'------ Li-Ion Battery Management System Master by Peter Perkins Module ------- '------ Picbasic Pro Compiler version PIC16F886 - 210710 - V0.13 Alpha ------- '------ This code requires Master board V2. Please report errors or problems. '------------------------------------------------------------------------------ 'Note this code is for the Pic Basic Pro v2.60A+ internal PM compiler not MPASM! '**************************** 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 16F886 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) '(Spare) RA6 -09| 8 |20- +Ve (+5V Supply) '(Spare) RA7 -10| 6 |19- Gnd (- Supply) '(Interlocks) RC0 -11| |18- RC7 (Master Data Bus In) '(VSS Speed Sensor In) RC1 -12| |17- RC6 (Dash Led 1 Out) '(I2C Temp Sensor In) 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 (Not Used) '(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 'Master Command pulses are 0.1ms units in length '***************************** Slave Commands ********************************** ' Command 01 = Send Cell Voltage on Master Bus (0.1ms pulse) ' Command 02 = Turn Off Loads (Multi Slave Mode Only) (0.2ms pulse) ' Command 03 = Turn On Loads as Reqd (Multi Slave Mode Only) (0.3ms pulse) ' Command 04 = Increase Load CutIn Voltage by 10mv (0.4ms pulse) ' Command 05 = Decrease Load CutIn Voltage by 10mv (0.5ms pulse) ' Command 06 = Increase Load CutOut Voltage by 10mv (0.6ms pulse) ' Command 07 = Decrease Load CutOut Voltage by 10mv (0.7ms pulse) ' Command 08 = Set Slave Load CutIn/CutOut Voltage Defaults (0.8ms pulse) ' Command 09 = Turn On Slave Load for 0.5 seconds (Flash Led) (0.9ms pulse) ' Command 10 = Set Baud to 9600bps (Default) (1.0ms pulse) ' Command 11 = Set Baud to 2400bps (No support in PBP Master) (1.1ms pulse) ' Command 12 = Report Slave Software Version Number (1.2ms pulse) '************* Below are experimental Commands not fully tested **************** ' Command 13 = Set to Multi Slave (Multi Slave Mode) Default (1.3ms pulse) ' Command 14 = Set to Single Slave (Single Slave Mode) (1.4ms pulse) ' Command 15 = Increase Command Delay Time by 1ms (Max 10ms) (1.5ms pulse) ' Command 16 = Decrease Command Delay Time by 1ms (Min 0ms) (1.6ms pulse) ' Command 17 = Set Default Command Delay Time (3ms) (1.7ms pulse) ' Command 18 = Increase FailLow Cut Off Voltage by 5mv (1.8ms pulse) ' Command 19 = Decrease FailLow Cut Off Voltage by 5mv (1.9ms pulse) ' Command 20 = Set FailLow Cut Off Voltage to Default 3.35V (2.0ms pulse) ' Command 21 = Set ADC Output Voltage to 3.70V (Test Mode) (2.1ms pulse) ' Command 22 = Set ADC Output Voltage to Cell Voltage (Default) (2.2ms pulse) ' Command 23 = Increase FailHigh Cut in Voltage by 5mv (2.3ms pulse) ' Command 24 = Decrease FailHigh Cut in Voltage by 5mv (2.4ms pulse) ' Command 25 = Set FailHigh Cut in Voltage to Default 3.85V (2.5ms pulse) ' Command 26 = Set Slave to Super (No command echo) (2.6ms pulse) ' Command 27 = Set Slave to Normal (command echo) Default (2.7ms pulse) '******************************************************************************* '****************** Program Size 8023 Words out of 8192 Words ****************** '******************************************************************************* '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) 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 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) 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) Distance var Word 'Global = Distance (feet) travelled (5280ft = 1 mile) HighPackV var Word 'Global = High Pack voltage 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 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 TMR1RunOn var Word 'Local = Variable holding TMR1 Run-On value 'Variables 8bit (Byte) Alarms var Byte 'Global = Alarm Type Byte (0-255 number of different Alarms) 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 QSeconds var Byte 'Global = [QSeconds] 250ms second elapsed Timer TSeconds var Byte 'Global = [TSeconds] Thirty second elapsed Timer Temp1Data var Byte 'Global = [Temp1] Temperature data Temp2Data var Byte 'Global = [Temp2] Temperature data Tenths var Byte 'Global = [Tenths of a mile] VoltsData var Byte 'Global = [PackV] SocData var Byte 'Global = [Soc] AmpSign var Byte 'Global = [AmpSign + or -] AlarmData var Byte 'Global = [AlarmData] 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] 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 sensors) I2C2 var BYTE[8] 'Define I2C2 as a byte array (8 Bytes) (I2C = I2C Temp sensors) 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 'Variables 1bit (Bit) TMR1IF var PIR1.0 'Global = Overflow flag bit of Timer1 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 = Define FullBit as a bit array (Max 95 Cells) (0 = Cell Not Full) (1 = Cell Full) 'Control Byte (Bit Assingment) TempBit var Control.Bit0 'Global = Temperature Routine (1 = I2C Sensors 0 = Analog Sensors) SuperBit var Control.Bit1 'Global = Super Slaves Bit (1 = Normal Slaves 0 = Super Slaves) TxdBit var Control.Bit2 'Global = Remote Display and Eeprom Logging Enable bit (1 = Bypass 0 = Transmit data) PcBit var Control.Bit3 'Global = PCLogger Enable bit (1 = Bypass 0 = Transmit data) 'Unallocated var Control.Bit4 'Global = 'Unallocated var Control.Bit5 'Global = '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) 'If Alarms = 7 then (Pack Voltage > Maximum permitted Pack Voltage) 'If Alarms = 8 then (Pack Voltage < Minimum permitted Pack Voltage) 'Constants MaxPackV con 18500 'Maximum pack voltage = 185v (18,500 as 16 bit value) Res 10mv (Max 650v) MinPackV con 11500 'Minimum pack voltage = 115v (11,500 as 16 bit value) Res 10mv AbsMaxCellV con 375 'Abs Maximum permitted cell voltage = (3.75V) (Alarm & Shutdown point) AbsMinCellV con 230 'Abs Minimum permitted cell voltage = (2.30V) (Alarm & Shutdown point) MaxCellV con 370 'Normal Maximum permitted cell voltage = (3.70V) (Charger/Regen cutback point) MinCellV con 250 'Normal Minimum permitted cell voltage = (2.50V) (Controller/Assist cutback point) CutInVD con 365 'Balancing load/bypass default cut in Voltage (Must match value set in Slaves) CutOutVD con 360 'Balancing load/bypass default cut out Voltage (Must match value set in Slaves) MaxTemp con 45 'Maximum permitted pack temperature = (45C) (Alarm & Shutdown point) SocMax con 10000 'Max cell capacity = 100% (10000 as 16 bit value) SocMin con 1000 'Min cell capacity = 10% (1000 as 16 bit value) SocUnit con 20 'SocUnit constant for Soc calculations. for 20ah cells (1 Unit = 20) Delay con 2 'Interrupt and data delay (Pause 2 = 2ms) 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 50 'Voltage Drop = 50 Pack must drop (500mv) 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 PreLoad con 3035 'Preload Constant Loads Timer with 3035 to get 250ms ticks '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 Charger var PORTB.1 'Charger Cutback output on Output 1 (Opto conducts when cell V > 3.75V) 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 (5.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 'Unallocated4 var PORTA.4 'Spare Output PCLogger var PORTA.5 'Serial BMS Data Output to Pc Logger 'Unallocated6 var PORTA.6 'Spare Output 'Unallocated7 var PORTA.7 'Spare Output '************************************ 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 Cells0 con 242 'EEprom address Byte data [Cells0] (Dec 242) Control0 con 243 'EEprom address Byte data [Control] (Dec 243) PrevOdo0 con 244 'EEprom address Word data [PrevOdo] (Dec 244) PrevOdo1 con 245 'EEprom address Word data [PrevOdo] (Dec 245) PrevSoc0 con 246 'EEprom address Word data [PrevSoc] (Dec 246) PrevSoc1 con 247 'EEprom address Word data [PrevSoc] (Dec 247) SocStore0 con 248 'EEprom address Word data [SOC] (Dec 248) Stored every 10 seconds SocStore1 con 249 'EEprom address Word data [SOC] (Dec 249) Stored every 10 seconds DistStore0 con 250 'EEprom address Word data [Distance] (Dec 250) Stored every 10 seconds DistStore1 con 251 'EEprom address Word data [Distance] (Dec 251) Stored every 10 seconds 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 '************************************************************************************************************* @ DEVICE PIC16F886,LVP_OFF 'Turn off low voltage programming @ DEVICE PIC16F886,FCMEN_OFF @ DEVICE PIC16F886,IESO_OFF @ DEVICE PIC16F886,CPD_OFF @ DEVICE PIC16F886,MCLR_ON 'Turn on Master Clear @ DEVICE PIC16F886,WDT_OFF 'Turn off internal Watchdog timer @ DEVICE PIC16F886,INTRC_OSC_NOCLKOUT 'Set internal oscillator with no clock out pins @ DEVICE PIC16F886,PROTECT_OFF 'Turn off code protect Define OSC 8 'Set PicBasic Pro processor speed to 8 Mhz (Must match oscillator value) OSCCON = %01110101 'Internal 8 mhz Osc and stable CM1CON0 = 0 'Comparator Off CM2CON0 = 0 'Comparator Off T1CON = %00111001 'Enables and Starts Timer1 prescale 1:8 internal clock source 262ms at 8mhz TRISA = %11111111 'SET PORTA AS INPUTS 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 = %10000000 'SETUP ADC RIGHT JUSTIFY SET REFV to VDD & VSS '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 '************************************************************************************************************* Start: 'Initialise Program. Start Timer, 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 read Cells0, Cells 'Load Cells Byte from eeprom storage (Number of Cells in pack) if Cells = 255 then 'Check if new master version uploaded (Cells = 255 is set if eeprom cleared) Cells = 50 'Set Cells to 50 endif read Control0, Control 'Load BMS Control Byte from eeprom storage read OdoStore0, Odo.Byte0 'Load last saved OdoMeter (Miles) reading from eeprom storage read OdoStore1, Odo.Byte1 'Load last saved OdoMeter (Miles) reading from eeprom storage read TripStore0, Trip.Byte0 'Load last saved TripMeter (10ths Mile) reading from eeprom storage read TripStore1, Trip.Byte1 'Load last saved TripMeter (10ths Mile) reading from eeprom storage read DistStore0, Distance.Byte0 'Load last saved Distance (Feet) reading from eeprom storage read DistStore1, Distance.Byte1 'Load last saved Distance (Feet) reading from eeprom storage read SocStore0, Soc.Byte0 'Load last saved SOC (0-100% = 10,000) reading from eeprom storage read SocStore1, Soc.Byte1 'Load last saved SOC (0-100% = 10,000) reading from eeprom storage write PrevOdo0, Odo.Byte0 'Write Initial Odometer reading to eeprom storage (Used for wh/m Calc) write PrevOdo1, Odo.Byte1 'Write Initial Odometer reading to eeprom storage (Used for wh/m Calc) write PrevSoc0, Soc.Byte0 'Write Initial Soc reading to eeprom storage (Used for wh/m Calc) write PrevSoc1, Soc.Byte1 'Write Initial Soc reading to eeprom storage (Used for wh/m Calc) 'Load 2 x I2C Temp sensor data from first 16 bytes of AT24C512B External EEPROM ' Pause 20 ' VarA = 0 ' I2CREAD Scdeeprom,Sckeeprom,AT24C512B,VarA,[STR I2C1\8,STR I2C2\8] 'Read data from I2c eeprom ' Pause 20 'Start initial I2C temperature conversion to avoid start up over temp alarm condition ' owout DigitalTemp,1,[$55,STR I2C1\8,$44] 'Match ROM & Convert Temperature ' owout DigitalTemp,1,[$55,STR I2C2\8,$44] 'Match ROM & Convert Temperature 'Manual I2C Temp Sensor initiate routine avoids start up temperature error owout DigitalTemp,1,[$55,$28,$B9,$11,$16,$01,$00,$00,$A4,$44] 'Match ROM & Convert Temperature owout DigitalTemp,1,[$55,$28,$CD,$11,$EA,$00,$00,$00,$28,$44] 'Match ROM & Convert Temperature high Alarm 'Activate audible alarm pause 250 'Pause for 250ms (Wait for display screen to be ready) low Alarm 'Deactivate audible alarm 'Display Splash Screen, Software Version, CPU Speed & Number of Cells in System. DEBUG 27,70,27,46,27,67,"BMS Master V0.13By Peter Perkinswww.150mpg.co.uk",10,13,"8mhz (",#Cells,") Cells" pause 4000 'Pause for 4 seconds DEBUG 27,67 'Clear Screen I2CAddr = 16 'Set start address to avoid overwriting I2C temp sensor data PCBit = 0 'Enable/Disable Data Logging output (0 = enabled) (1= disabled) 'Initialise Timer ' PIE1.0=1 'Enable TMR1 Interrupts ' INTCON.6=1 'Enable all unmasked Interrupts ' INTCON.7=1 'Enable Global Interrupts ' TMR1L = PreLoad.LowByte 'Load the Timer with preload value ' TMR1H = PreLoad.HighByte 'Load the Timer with preload value ' TMR1IF = 0 'Clear TMR1 int flag '************************************************************************************************************* '************************************************************************************************************* MainLoop: 'Main program loop toggle WatchDogLed 'Keeps watchdog happy, flashes every other time through loop gosub CheckVoltage 'Gosub CheckVoltage routine to collect/display Cell V data gosub CheckCurrent 'Gosub CheckCurrent routine to calculate/display charge/discharge data gosub Timer 'Gosub Quarter Second Timer If TempBit = 1 then gosub CheckI2CTemp 'Gosub I2C Temp routine to collect/display battery temperature else gosub CheckAnalogTemp 'Gosub Analog Temp routine to collect/display battery temperature endif gosub CheckSpeed 'Gosub CheckSpeed routine to calculate/display speed/distance gosub Timer 'Gosub Quarter Second Timer if Alarms > 0 then gosub DisplayAlarms 'If an Alarm is set then Gosub DisplayAlarms gosub MenuButtons 'Gosub MenuButtons gets button data (0,1,2,3,4,5 = no button) in Key if Key = 4 then gosub SubMenu1 'If Menu button pressed gosub SubMenu1 if TxdBit = 0 then gosub TxdData 'If TxdData routine is enabled then Txd data to remote display & Eeprom log if TSeconds > 29 then 'If Thirty seconds have elapsed then store data write Control0, Control 'Write BMS Control Byte to eeprom storage write OdoStore0, Odo.Byte0 'Write OdoMeter reading to eeprom storage write OdoStore1, Odo.Byte1 'Write OdoMeter reading to eeprom storage write TripStore0, Trip.Byte0 'Write TripMeter reading to eeprom storage write TripStore1, Trip.Byte1 'Write TripMeter reading to eeprom storage write DistStore0, Distance.Byte0 'Write Distance reading to eeprom storage write DistStore1, Distance.Byte1 'Write Distance reading to eeprom storage write SocStore0, Soc.Byte0 'Write SOC reading to eeprom storage write SocStore1, Soc.Byte1 'Write SOC reading to eeprom storage TSeconds = 0 'Reset 30 second counter endif lOOP0: gosub Timer 'Gosub Quarter Second Timer IF QSeconds < 4 THEN lOOP0 'If One Second has not elapsed then goto Loop0 QSeconds = 0 'Reset One Second counter TSeconds = TSeconds + 1 'Increment Thirty Second Timer goto mainloop 'Goto main program loop '************************************************************************************************************* Timer: 'Quarter Second Timer IF TMR1IF = 0 THEN return 'If 250ms has not elapsed then return T1CON.0=0 'Stop the Clock TMR1RunOn.Highbyte = TMR1H 'Load the Run-On (Over-Run) value (if any) TMR1RunOn.Lowbyte = TMR1L 'Load the Run-On (Over-Run) value (if any) TMR1RunOn = PreLoad + TMR1RunOn 'Calculate new Preload setting to include any timer overrun TMR1H = TMR1RunOn.HighByte 'Load the timer with new value TMR1L = TMR1RunOn.LowByte 'Load the timer with new value T1CON.0=1 'Restart the Clock TMR1IF = 0 'Clear TMR1 int flag 'PIR1.0=0 'Reset TMR1's Interupt Flag QSeconds = QSeconds + 1 'Increment 1/4 Seconds return 'Return to main program '************************************************************************************************************* '************************************************************************************************************* CheckVoltage: 'Check Voltage subroutine, receive data to calculate pack voltage 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 high SlaveBus 'Turn on Message waiting signal (SlaveBus) pause Delay 'Hold Message waiting signal high until Slave has time to respond low SlaveBus 'Turn off Message waiting signal (SlaveBus) pause 1 'Pause for 1ms pulsout SlaveBus, 40 'Send Command 2 (40 x 5us units at 8mhz = 0.2ms) (Turn off all Loads) 'Slave Command check delay. Stops data collision error on Master Data Bus. if SuperBit = 0 then SuperSlaves 'Jump over standard slaves collision test for VarA1 = 1 to Cells 'Start for/next loop to wait until previous command complete serin MasterBus, N9600, TimeOut, DataError, VData 'Check Commands If VData <> 2 then DataError 'If Command is not 2 then error! next VarA1 'repeat loop SuperSlaves: 'Jump here if using super slaves (No Command 2 & 3 acknowledge) high SlaveBus 'Turn on Message waiting signal (SlaveBus) pause Delay 'Hold Message waiting signal high until Slave has time to respond low SlaveBus 'Turn off Message waiting signal (SlaveBus) pause 1 'Pause for 1ms pulsout SlaveBus, 20 'Send Command 1 (20 x 5us units at 8mhz = 0.1ms) (Report Cell Voltages) gosub Timer 'Gosub Quarter Second Timer 'Get cell data into BD byte array 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 'Turn on Slave loads as reqd 'Sends Slave Command 3 to turn on loads as required high SlaveBus 'Turn on Message waiting signal (SlaveBus) pause Delay 'Hold Message waiting signal high until Slave has time to respond low SlaveBus 'Turn off Message waiting signal (SlaveBus) pause 1 'Pause for 1ms pulsout SlaveBus, 60 'Send Command 3 (60 x 5us units at 8mhz = 0.3ms) (Turn on Loads if Reqd) gosub Timer 'Gosub Quarter Second Timer 'Evaluate returned cell data 'Evaluates and calculates cell voltages for VarA1 = 1 to Cells 'Start for/next loop to receive cell if PCBit = 0 then 'If PCBit is 0 then Transmit data to PCLogger serout PCLogger, N9600, [BD[VarA1]] 'Send Cell Voltage Data to PC Logger endif CellV = 0 'Clear CellV Variable 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 (500mv 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 'Start for next loop to 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 Charge end test Byte is 1 then all cells have reached balancing voltage during this charge 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 " '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 else low Charger 'De-Activate Charger pull down opto (Note may need to remove this) low Led1 'De-Activate dash Over-V Led1 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 DataError: 'Serial Slave Data Rxd TimeOut, If no Data within (TimeOut = 50ms) 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 '************************************************************************************************************** CheckAnalogTemp: 'Check Analog Temperature Sensors Routine (LM335 10mv/K Sensors) VarB = 0 'Set VarB to 0 VarA = 0 'Set VarA to 0 ADCON0 = %01001001 'SETUP ADC CONVERTER MODULE FOSC/8 & ENABLE ADC MODULE on AD2 for VarC2 = 1 to 10 '10 x Temperature Oversampling 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) VarA = VarA + VarD 'Accumulate 10 temp readings in VarA next VarC2 'Next VarC2 ADCON0 = %01001101 'SETUP ADC CONVERTER MODULE FOSC/8 & ENABLE ADC MODULE on AD3 for VarC2 = 1 to 10 '10 x Temperature Oversampling ADCON0.1 = 1 'Start ADC conversion while ADCON0.1 = 1 'Wait for ADC DONE wend VarE.highbyte = ADRESH 'Move HIGH byte of result to Variable (0-1023 10bit) VarE.lowbyte = ADRESL 'Move LOW byte of result to Variable (0-1023 10bit) VarB = VarB + VarE 'Accumulate 10 temp readings in VarB next VarC2 'Next VarC2 VarB = VarB / 10 'Get average temp for last 10 readings VarA = VarA / 10 'Get average temp for last 10 readings Temp1Data = (VarA - 560) / 2 'Calculate Temp1 in C from average Adc reading Temp2Data = (VarB - 560) / 2 'Calculate Temp2 in C from average Adc reading if Temp1Data > MaxTemp or Temp2Data > MaxTemp then 'If Temp > MaxTemp set Alarms Alarms = 4 'Set Alarms to 4 (Indicates Temp over MaxTemp condition) endif 'Display Temperatures DEBUG 27,83,0,3,"Tem F",#VarA1," R",#VarB1," ",248,"C " 'Display Temps return 'Return to main program loop '************************************************************************************************************** '************************************************************************************************************** CheckI2CTemp: 'Check Battery Pack temperature routine (Multiple DS18B20 I2C Sensors) ' debug 27,67 ' For VarA1 = 0 to 7 'Load I2C1 data from eeprom ' DEBUG HEX2 I2C1[VarA1], HEX2 I2C2[VarA1] ' Next VarA1 ' debug 10,10,13 ' For VarA = 0 to 15 'Load I2C1 data from eeprom ' I2CREAD Scdeeprom,Sckeeprom,AT24C512B,VarA,[VarB1] 'Read data from I2c eeprom ' DEBUG HEX2 VarB1 ' Next VarA ' Pause 5000 'The owout lines become ($55,xx,xx,xx,xx,xx,xx,xx,xx,$44) where xx = decimal serial no's for the I2C chips DEBUG 27,83,0,3,"Tem " 'Setup Display for Temp Sensors 'Sensor 1 (Front Battery Block) owout DigitalTemp,1,[$55,$28,$B9,$11,$16,$01,$00,$00,$A4,$BE] 'Match ROM & Convert Temperature ' owout DigitalTemp,1,[$55,STR I2C1\8,$BE] 'Match ROM & Send lsb,msb gosub calculatetemp 'Gosub Calculate Temperature routine DEBUG 27,83,0,3,"Tem ",VarB1,#VarD1,".",#VarD2," " 'Display Temp Sensor 1 owout DigitalTemp,1,[$55,$28,$B9,$11,$16,$01,$00,$00,$A4,$44] 'Match ROM & Convert Temperature ' owout DigitalTemp,1,[$55,STR I2C1\8,$44] 'Match ROM & Convert Temperature Temp1Data = VarD1 'Store Front Pack Temp in Temp1Data (Note 0-100C Max) 'Sensor 2 (Rear Battery Block) owout DigitalTemp,1,[$55,$28,$CD,$11,$EA,$00,$00,$00,$28,$BE] 'Match ROM & Convert Temperature ' owout DigitalTemp,1,[$55,STR I2C2\8,$BE] 'Match ROM & Send lsb,msb gosub calculatetemp 'Gosub Calculate Temperature routine DEBUG VarB1,#VarD1,".",#VarD2," " 'Display Temp Sensor 2 owout DigitalTemp,1,[$55,$28,$CD,$11,$EA,$00,$00,$00,$28,$44] 'Match ROM & Convert Temperature ' owout DigitalTemp,1,[$55,STR I2C2\8,$44] 'Match ROM & Convert Temperature Temp2Data = VarD1 'Store Rear Pack Temp in Temp2Data (Note 0-100C Max) 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 VarD1 = VarA / 10 'Calculate Temp prior to decimal point VarD2 = VarA // 10 'Calculate Temp after decimal point if VarD1 > MaxTemp AND VarD1 < 127 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 PCBit = 0 then 'If PCBit is 0 then Transmit data to PCLogger serout PCLogger, N9600, [VarD1] 'Send Pack Temp Data to PC Logger endif return 'Return to CheckTemp '************************************************************************************************************** '************************************************************************************************************** CheckCurrent: 'Accumulate (Current in Amps) charge/discharge data VarB = 0 'Reset Current oversampling accumulator to 0 (Zero) ADCON0 = %01000001 'SETUP ADC CONVERTER MODULE FOSC/8 & ENABLE ADC MODULE on AD0 for VarA2 = 1 to 10 '10 x Current Oversampling ADCON0.1 = 1 'Start ADC conversion while ADCON0.1 = 1 'Wait for ADC DONE wend BatCurrent.highbyte = ADRESH 'Move HIGH byte of result to Variable (0-1023 10bit)(-100A to +100A 0-5V) BatCurrent.lowbyte = ADRESL 'Move LOW byte of result to Variable (0-1023 10bit)(-100A to +100A 0-5V) VarB = VarB + BatCurrent 'Add Current to accumulated Current Next VarA2 'Repeat loop until 10 samples obtained BatCurrent = VarB / 10 'Divide VarB by 10 to get average current from last 10 samples 'The below comment lines can be swapped for the existing lines below them depending on orientation of current sensor ' if BatCurrent <507 then 'If BatCurrent is <507 means system is Charging ' BatCurrent = ((510 - BatCurrent) / 3) 'Subtract sensor offset to get a positive number (0-510 = 0-100A+) Maximum 99 ' AmpSign = 43 'Set ascii Character 43 (+) to be displayed if charging ' goto ExitCurrent ' endif ' if BatCurrent >512 then 'If BatCurrent is >512 means system is Discharging ' BatCurrent = ((BatCurrent - 512) / 3) 'Subtract sensor offset to get a positive number (0-512 = 0-100A-) Maximum 99 ' AmpSign = 45 'Set ascii Character 45 (-) to be displayed if discharging ' goto ExitCurrent ' endif if BatCurrent <507 then 'If BatCurrent is <507 means system is Discharging BatCurrent = ((510 - BatCurrent) / 3) 'Subtract sensor offset to get a positive number (0-510 = 0-100A+) Maximum 99 AmpSign = 45 'Set ascii Character 45 (-) to be displayed if Discharging goto ExitCurrent endif if BatCurrent >512 then 'If BatCurrent is >512 means system is Charging BatCurrent = ((BatCurrent - 512) / 3) 'Subtract sensor offset to get a positive number (0-512 = 0-100A-) Maximum 99 AmpSign = 43 'Set ascii Character 43 (+) to be displayed if charging goto ExitCurrent endif BatCurrent = 0 'If BatCurrent = (508 to 512) then set BatCurrent to 0 (Zero) AmpSign = 61 'Set ascii Character (=) to be displayed if no charge/discharge ExitCurrent: if BatCurrent > 99 then 'Limits BatCurrent to 99 Amps BatCurrent = 99 endif if PCBit = 0 then 'If PCBit is 0 then Transmit data to PCLogger 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 VarA = VarA / 1000 DEBUG 27,83,0,4,"Amp ",AmpSign,#BatCurrent," K ",#VarA,".",#VarB," " 'Video Display CalculateSoc: 'Calculate Soc Routine '1A charge/discharge = 2.7 per second when 1A = 100 '20A charge/discharge = 55.55 per second when 20A = 2000 '50A charge/discharge = 138.8 per second when 50A = 5,000 '100A charge/discharge = 277.7 per second when 100A = 10,000 '500A charge/discharge = 1388.8 per second when 500A = 50,000 '650A charge/discharge = 1805.5 per second when 650A = 65,000 'For a 8ah Cell 100% Soc is represented by 10,000 which equals 0.8mah per unit. (8 = 1 unit = 0.8mah) 'For a 20ah Cell 100% Soc is represented by 10,000 which equals 2mah per unit. (20 = 1 unit = 2mah) 'For a 40ah Cell 100% Soc is represented by 10,000 which equals 4mah per unit. (40 = 1 unit = 4mah) 'For a 100ah Cell 100% Soc is represented by 10,000 which equals 10mah per unit. (100 = 1 unit = 10mah) 'For a 200ah Cell 100% Soc is represented by 10,000 which equals 20mah per unit. (200 = 1 unit = 20mah) 'SocUnit is a Constant (8 for 8ah cells) (20 for 20ah cells) (40 for 40ah cells) (100 for 100ah cells) (200 for 200ah cells) BatCurrent = BatCurrent * 100 / 36 'Calculate Ah to be added/subtracted in last second 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 (2ma/ah) Maximum 10,000 (100%) 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 (2ma/ah) 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 If Soc > 10000 then 'Limit Soc to 10,000 (100%) Soc = 10000 endif 'Display Battery State of Charge SOC in % VarC = Soc / 100 'Get first part of value (Before decimal point) if PCBit = 0 then 'If PCBit is 0 then Transmit data to PCLogger serout PCLogger, N9600, [VarC] 'Send Soc Data to PC Logger endif SocData = VarC1 'Store Soc % in SocData Ram Byte (Note 0-100%) VarA = Soc // 100 'Get second part of value (After decimal point) DEBUG 27,83,0,5,"Soc ",#VarC,"." 'Video Display if VarA < 10 then 'If Leading zero reqd in display due to Value <10 then DEBUG "0",#VarA," % " 'Video Display else DEBUG #VarA," % " 'Video Display endif return 'Return to main program loop '************************************************************************************************************** '************************************************************************************************************** CheckSpeed: 'Pulsin measures VSS pulse. If no pulse result will be 0 pulsin SpeedSensor,1,VarB 'Measure length of a VSS pulse in 5us (8mhz) units (Timeout 100ms) if VarB = 0 then JumpSpeed 'If VarB = 0 pulsin has timed out and vehicle is moving at < 1.5mph VarB = VarB / 11 'Divide VarB by 11 to enable results to fit into 16 bit Integer Maths VarB = (7908 / VarB) + 1 'Returns speed in mph accurate to 1 mph Approx! JumpSpeed: 'Jump here to avoid calculation errors if PCBit = 0 then 'If PCBit is 0 then Transmit data to PCLogger serout PCLogger, N9600, [VarB] 'Send vehicle Speed Data to PC Logger endif 'Display Speed DEBUG 27,83,0,6,"Mph ",#VarB," wh/m ",#WhMile," " 'Video Display '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) Distance = Distance + VarB 'Add Distance travelled to any saved from previous loop while Distance >= 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 Distance = Distance - 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 'Display Odometer, Tripmeter and Charge Flag Status 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,8,"Tri ",#VarA,".",#VarB," Chg ",#ChgBit," " 'Video Display return 'Return to main program loop '************************************************************************************************************** '************************************************************************************************************** DisplayAlarms: 'Alarms turns on led/audible alarms and turns off charger etc 'Action taken depends on Alarms (255 different Alarms max!) DEBUG 27,94 'Video Display Save Current Screen to SV2000 Flash pause 100 'pause for 100ms at 8mhz DEBUG 27,33,27,67," * BMS Alarms * ",10,13 'Display Negative, Clear Screen & Display Message high Alarm 'Activate audible alarm high Charger 'Activate Charger pull down opto high Controller 'Activate Controller pull down opto low ChargerOnOff 'Turn off Charger main relay ChgBit = 0 'Set ChgBit to 0 indicates Charging Off 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 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 pause 3000 'Pause for 3 seconds low Alarm 'Deactivate audible alarm DEBUG 27,95 'Video Display Restore Current Screen from SV2000 Flash Alarms = 0 'Reset Alarms to 0 (Clears any Alarm Flags) return 'Return to main program loop '************************************************************************************************************** '************************************************************************************************************** MenuButtons: 'Decodes button Inputs and returns 6 values (0,1,2,3,4,5) in Key ADCON0 = %01000101 'SETUP ADC CONVERTER MODULE FOSC/8 & ENABLE ADC MODULE on AD1 pause 10 'Pause 10ms until ADC Module ready 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) VarD = VarD / 4 'Convert 10 bit to 8 bit result VarD = VarD + 6 VarD = VarD / 42 VarD = VarD - 1 Key = VarD1 'Loads Returned button value into Key return 'Return to main program '************************************************************************************************************** '************************************************************************************************************** SetTrip: 'Set Trip Meter to Zero Trip = 0 'Set Tripmeter to Zero write TripStore0, Trip.Byte0 'Write TripMeter reading to eeprom for storage when Master Off write TripStore1, Trip.Byte1 'Write TripMeter reading to eeprom for storage when Master Off goto ExitMenu 'Return to main program via restore screen routine '************************************************************************************************************** SetOdo: 'Set Odo Meter to Zero Odo = 0 'Set Odo to Zero write OdoStore0, Odo.Byte0 'Write Odo reading to eeprom for storage when Master Off write OdoStore1, Odo.Byte1 'Write Odo reading to eeprom for storage when Master Off goto ExitMenu 'Return to main program via restore screen routine '************************************************************************************************************** SetSoc: 'Set Soc to 100% Soc = 10000 'Set Soc to 10,000 (100.00%) write SocStore0, Soc.Byte0 'Write Soc reading to eeprom for storage when Master Off write SocStore1, Soc.Byte1 'Write Soc reading to eeprom for storage when Master Off goto ExitMenu 'Return to main program via restore screen routine '************************************************************************************************************** '************************************************************************************************************** StartChg: 'Start Charging Cycle Routine If Alarms > 0 then 'If any Alarms are set then charge start is aborted return 'Return to main loop, charging aborted due to error set endif 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 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 endif pause 250 'Pause for 250ms at 8mhz low Alarm 'Deactivate audible alarm goto ExitMenu 'Return to main program via restore screen routine '************************************************************************************************************** SubMenu1: 'Sub Menu 1 routine for extended BMS functions Debug 27,94 'Video Display Save Current Screen to SV2000 Flash pause 100 'Pause for 100ms at 8mhz Debug 27,67,"* Sub Menu 1 *",10,13,"Start Charge = UReset Soc = LReset Trip = DReset Odo = RSub Menu 2 = C" pause 2000 'Pause for 2 seconds MenuLoop1: 'Loop here to wait for button press gosub MenuButtons 'Gosub MenuButtons gets button data (0,1,2,3,4,5 = no button) in Key if Key = 0 then StartChg 'If Key = 0 then goto Start Charge Routine if Key = 1 then SetSoc 'If Key = 1 then goto Reset Soc to 100% if Key = 2 then SetTrip 'If Key = 2 then goto Reset Tripmeter to 0 miles if Key = 3 then SetOdo 'If Key = 3 then goto Reset Odometer to 0 miles if Key = 4 then SubMenu2 'If Key = 4 then goto Sub Menu 2 goto MenuLoop1 'Goto Menu Loop 1 ExitMenu: Debug 27,67,27,95 'Video Display Restore Current Screen from SV2000 Flash return 'Return to main program '************************************************************************************************************** SubMenu2: 'Sub Menu 2 routine for extended BMS functions Debug 27,67,"* Sub Menu 2 *",10,13,"Cell Voltage = USlave Update = LTx BMS Data = DTemp Rom = RSub Menu 3 = C" pause 2000 'Pause for 2 seconds MenuLoop2: 'Loop here to wait for button press gosub MenuButtons 'Gosub MenuButtons gets button data (0,1,2,3,4,5 = no button) in Key if Key = 0 then Display 'If Key = 0 then goto Display Cell Voltages if Key = 1 then UpdateSlaves 'If Key = 1 then goto Update Slaves if Key = 2 then DumpData 'If Key = 2 then goto Dump BMS Data if Key = 3 then TempRom 'If Key = 3 then goto Discover I2C Sensor Serial Number if Key = 4 then SubMenu3 'If Key = 4 then goto Sub Menu 3 goto MenuLoop2 'Goto Menu Loop 2 '************************************************************************************************************** SubMenu3: 'Sub Menu 3 routine for extended BMS functions Debug 27,67,"* Sub Menu 3 *",10,13,"Analog Temp = UDigital Temp = LNum of Cells = DSuper Slaves = RExit = C" pause 2000 'Pause for 2 seconds MenuLoop3: 'Loop here to wait for button press gosub MenuButtons 'Gosub MenuButtons gets button data (0,1,2,3,4,5 = no button) in Key if Key = 0 then 'If Key = 0 then set AnalogTemp mode TempBit = 0 'Set Analog temperature sensor mode Debug 10,13,"Analog Temp Set!" pause 2000 'Pause for 2 seconds goto ExitMenu 'Goto Exit Sub menu endif if Key = 1 then 'If Key = 1 then goto DigitalTemp TempBit = 1 'Set Digital temperature sensor mode Debug 10,13,"Digital Temp Set" pause 2000 'Pause for 2 seconds goto ExitMenu 'Goto Exit Sub menu endif if Key = 2 then CellNumber 'If Key = 2 then goto Cell Number if Key = 3 then ToggleSuper 'If Key = 3 then goto ToggleSuper if Key = 4 then ExitMenu 'If Key = 4 then Exit Sub Menu goto MenuLoop3 'Goto Menu Loop 3 '************************************************************************************************************** ToggleSuper: 'Toggles Super slave mode on/off If SuperBit = 0 then 'If Superbit is 0 toggle Superbit SuperBit = 1 else 'If Superbit is 1 toggle Superbit SuperBit = 0 endif goto ExitMenu 'Goto Exit Menu '************************************************************************************************************** CellNumber: 'Set number of cells in pack routine (Max 95) pause 500 'Pause for 500ms second Loop1: 'Loop here to wait for button press gosub MenuButtons 'Gosub MenuButtons gets button data (0,1,2,3,4,5 = no button) in Key DEBUG 27,83,0,8,"Cells ",#Cells," " 'Video Display pause 200 'Pause for 200ms if Key = 5 then Loop1 'If no button pressed goto Loop1 if Key = 0 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 = 2 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 <> 4 then Loop1 'If Centre button not pressed then goto loop1 WRITE Cells0, Cells 'Load Cells Byte into eeprom storage (Number of cells in pack) goto ExitUpdate 'Goto Exit Update '************************************************************************************************************** TxdData: 'Sends 17 bytes of data to remote display via 433mhz rf link 'Txd sends 5 byte preamble to lock with Rxd, then qualifier "bms" 'then Control code (0) followed by 8 bytes of data I2CWRITE Scdeeprom,Sckeeprom,AT24C512B,I2CAddr,[Alarms] 'Write data to I2c eeprom starting at I2CAddress I2CAddr = I2CAddr + 1 'Increment I2CAddress word variable 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",0,Alarms,ErrCell] 'Txd Control code (0) and data (2400 baud at 8mhz) I2CWRITE Scdeeprom,Sckeeprom,AT24C512B,I2CAddr,[ErrCell] 'Write data to I2c eeprom starting at I2CAddress I2CAddr = I2CAddr + 1 'Increment I2CAddress word variable if I2CAddr > 65532 then 'Check if AT24C512B external eeprom full I2CAddr = 16 'Set start address to avoid overwriting I2C temp sensor data endif return 'Return to main program '************************************************************************************************************** DumpData: 'Sends Stored EEPROM BMS data to remote display and/or PC Logger 'Txd sends 5 byte preamble to lock with Rxd, then qualifier "bms" 'followed by control code and then data. Data rate is limited to '2400 baud and 64kbyte may take upto 4 minutes to transmit DEBUG 27,67,"Txd ",#I2CAddr," Bytes" 'Video Display serout DriveInhibit, N2400, [85, 85, 85, 85, 85] 'Txd 5 byte preamble to clear Rxd pause 5 'Pause for 5ms to allow serial rxd to clear serout DriveInhibit, N2400, ["bms",1] 'Txd Qualifier and control code (1) (2400 baud at 8mhz) pause 5 'Pause for 5ms to allow serial rxd to clear for VarA = 0 to I2CAddr 'Start loop to transmit stored data I2CREAD Scdeeprom,Sckeeprom,AT24C512B,I2CAddr,[VarB1] 'Read data from I2c eeprom starting at I2CAddress serout DriveInhibit, N2400, [VarB1] 'Txd BMS Data byte via 443mhz Tx 2400 baud toggle WatchDogLed 'Keeps watchdog happy, Prevents alarm during data transmision next VarA 'Repeat loop until all eeprom data sent serout DriveInhibit, N2400, [255] 'Txd BMS Data end flag (255) via 443mhz Tx 2400 baud goto ExitUpdate 'Goto Exit Update '************************************************************************************************************** '************************************************************************************************************** Display: 'Display Extended Cell BMS Data DEBUG 27,67,"Scroll Cells = D",10,13 'Video Display VarA2 = 1 'Set Cell counter to first Cell (1) ReadCellV: 'Read Cell Voltages high SlaveBus 'Turn on Message waiting signal (SlaveBus) pause Delay 'Hold Message waiting signal high until Slave has time to respond low SlaveBus 'Turn off Message waiting signal (SlaveBus) pause 1 'Pause for 1ms pulsout SlaveBus, 20 'Send Command 1 (20 x 5us units at 8mhz = 0.1ms) (Report Cell Voltages) for VarA1 = 1 to Cells 'Start for/next loop to receive cell data CellV = 0 'Clear CellV Variable 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) 'Display Cell Voltage 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 Loop4: gosub MenuButtons 'MenuButtons gets current button data (0,1,2,3,4,5) value in Key If Key <> 2 then Loop4 'If Down Button is not pressed goto Loop4 pause 150 'Pause for 150ms next VarA1 'Display Next Cell goto ExitUpdate 'Goto ExitUpdate '************************************************************************************************************** '*********************************************** Slave Commands *********************************************** UpdateSlaves: 'Update Slaves in situ (Non Picaxe Serial command slaves only) VarC1 = 9 'Set variable VarC1 to Command 9 (Turn on Load for 0.5 seconds, flash Led) DEBUG 27,67,"* Slave Update * Use Up/Down to Select Command " 'Video Display DEBUG 10,13," Centre Push to Send Command!!" 'Video Display pause 1000 'Pause for 1 second Loop5: 'Loop here to wait for button press gosub MenuButtons 'Gosub MenuButtons gets button data (0,1,2,3,4,5 = no button) in Key DEBUG 27,83,0,7," Command ",#VarC1," ! " 'Video Display pause 500 'Pause for 500ms if Key = 5 then Loop5 'If no button pressed goto Loop5 if Key = 0 then 'If Button pressed is up then increment command varC1 = varC1 + 1 'Increment Variable VarC1 if varC1 > 27 then 'If varC1 > 27 then set varC1 to 1 varC1 = 1 'Prevents sending unsupported commands endif endif If Key = 2 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 27 varC1 = 27 'Prevents sending unsupported commands endif endif If Key <> 4 then Loop5 'If Centre button not pressed then goto loop5 DEBUG 10,10,13,"Confirm (Y/N) ? Up=Yes Down=No" 'Video Display pause 1000 'Pause for 1 second Loop6: 'Loop here to wait for button press gosub MenuButtons 'Gosub MenuButtons gets button data (0,1,2,3,4,5 = no button) in Key If Key = 0 then ConfirmUpdate 'If Button pressed is up then update slaves If Key = 2 then ExitUpdate 'If Button pressed is down then cancel update Goto Loop6 'Goto Loop6 ConfirmUpdate: DEBUG 27,67,"Command ",#VarC1," Sent" 'Video Display VarD = VarC1 * 20 'Calculate pulse length value (20 x 5us units at 8mhz = 0.1ms) high SlaveBus 'Turn on Message waiting signal (SlaveBus) pause Delay 'Hold Message waiting signal high until Slave has time to respond low SlaveBus 'Turn off Message waiting signal (SlaveBus) pause 1 'Pause for 1ms at 8mhz pulsout SlaveBus, VarD 'Send Command (VarD x 5us units at 8mhz) SlaveCheck: 'Checks Slaves have received and acknowledged commands 'Get cell data into BD byte array 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 = 12 then 'If Command sent was 12 (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 pause 500 'Pause for 500ms DEBUG 10,13,"Any Key To Exit" 'Video Display Loop7: 'Loop here to wait for button press gosub MenuButtons 'Gosub MenuButtons gets button data (0,1,2,3,4,5 = no button) in Key If Key = 5 then Loop7 'If no button pressed goto loop7 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 & Store 2 x I2C DS18B20 Temp Sensor Rom Codes debug 27,67,"Ins 1st sensor",10,13,"Any key to scan" 'Video Display pause 1000 'Pause for 1 second LoopA: 'Loop here to wait for button press gosub MenuButtons 'Gosub MenuButtons gets button data (0,1,2,3,4,5 = no button) in Key If Key = 5 then LoopA 'If no button pressed goto loopA 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" debug 27,67,"Ins 2nd sensor",10,13,"Any key to scan",10,10,13 'Video Display pause 1000 'Pause for 1 second LoopB: 'Loop here to wait for button press gosub MenuButtons 'Gosub MenuButtons gets button data (0,1,2,3,4,5 = no button) in Key If Key = 5 then LoopB 'If no button pressed goto loopB OWOUT DigitalTemp, 1, [$33] 'Issue Read ROM command OWIN DigitalTemp, 0, [STR I2C2\8] 'Read 64-bit device data into the 8-byte array "I2C2" Pause 20 'Pause for 20ms VarA = 0 I2CWRITE Scdeeprom,Sckeeprom,AT24C512B,VarA,[STR I2C1\8,STR I2C2\8] 'Load data into I2c eeprom pause 20 'Pause for 20ms For VarA = 0 to 15 'Load I2C1 data from eeprom I2CREAD Scdeeprom,Sckeeprom,AT24C512B,VarA,[VarB1] 'Read data from I2c eeprom DEBUG HEX2 VarB1 'Video Display Next VarA goto ExitUpdate 'Goto Exit Update