├── CANRelay └── CANRelay.ino ├── CHAdeMO-interface.jpg ├── Capture.PNG ├── Capture2.PNG ├── Chademo flow chart.jpg ├── EVTV JLD505.PNG ├── FinalBMSCode ├── FinalBMSCode.ino ├── LeafBMS.cpp └── LeafBMS.h ├── IJLD505 ├── IJLD505.ino ├── README.md ├── chademo.cpp ├── chademo.h └── globals.h ├── LLTBMS07.ino ├── LLTBMS07 └── LLTBMS07.ino ├── README.md ├── chademo-pins-768x532.jpg └── chademo-sequence-circuit.png /CANRelay/CANRelay.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | struct can_frame canMsgIn; 5 | struct can_frame canMsgOut; 6 | MCP2515 k500(9); 7 | MCP2515 k250(10); 8 | 9 | 10 | void setup() { 11 | //while (!Serial); 12 | Serial.begin(115200); 13 | SPI.begin(); 14 | 15 | k500.reset(); 16 | k500.setBitrate(CAN_500KBPS, MCP_8MHZ); 17 | k500.setNormalMode(); 18 | 19 | k250.reset(); 20 | k250.setBitrate(CAN_250KBPS, MCP_8MHZ); 21 | k250.setNormalMode(); 22 | 23 | Serial.println("CANRelay"); 24 | Serial.println("ID DLC DATA"); 25 | } 26 | 27 | void loop() { 28 | if (k500.readMessage(&canMsgIn) == MCP2515::ERROR_OK) { 29 | if (canMsgIn.can_id == 0x50f) { 30 | //Serial.print(canMsgIn.can_id, HEX); // print ID 31 | // Serial.print(" "); 32 | //Serial.print(canMsgIn.can_dlc, HEX); // print DLC 33 | //Serial.println(" "); 34 | 35 | for (int i = 0; i < canMsgIn.can_dlc; i++) { // print the data 36 | 37 | //Serial.print(canMsgIn.data[i], HEX); 38 | //Serial.print(" "); 39 | canMsgOut.data[i] = canMsgIn.data[i]; 40 | 41 | } 42 | 43 | //Serial.println(); 44 | 45 | 46 | //Serial.println(canMsgOut.can_id, HEX); // print ID 47 | canMsgOut.can_dlc = canMsgIn.can_dlc; 48 | canMsgOut.can_id = 0x01dd0001 + 0x80000000; 49 | k250.sendMessage(&canMsgOut); 50 | } 51 | } 52 | if (k250.readMessage(&canMsgIn) == MCP2515::ERROR_OK) { 53 | if (canMsgIn.can_id == 0x1d4) { 54 | Serial.print(canMsgIn.can_id, HEX); // print ID 55 | Serial.print(" "); 56 | Serial.print(canMsgIn.can_dlc, HEX); // print DLC 57 | Serial.println(" "); 58 | 59 | for (int i = 0; i < canMsgIn.can_dlc; i++) { // print the data 60 | 61 | Serial.print(canMsgIn.data[i], HEX); 62 | Serial.print(" "); 63 | canMsgOut.data[i] = canMsgIn.data[i]; 64 | 65 | } 66 | 67 | Serial.println(); 68 | 69 | 70 | Serial.println(canMsgOut.can_id, HEX); // print ID 71 | canMsgOut.can_dlc = canMsgIn.can_dlc; 72 | canMsgOut.can_id = canMsgIn.can_id; 73 | k500.sendMessage(&canMsgOut); 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /CHAdeMO-interface.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Isaac96/EV-Code/d7546fa45881776557da84c97b0404d377ac14d3/CHAdeMO-interface.jpg -------------------------------------------------------------------------------- /Capture.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Isaac96/EV-Code/d7546fa45881776557da84c97b0404d377ac14d3/Capture.PNG -------------------------------------------------------------------------------- /Capture2.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Isaac96/EV-Code/d7546fa45881776557da84c97b0404d377ac14d3/Capture2.PNG -------------------------------------------------------------------------------- /Chademo flow chart.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Isaac96/EV-Code/d7546fa45881776557da84c97b0404d377ac14d3/Chademo flow chart.jpg -------------------------------------------------------------------------------- /EVTV JLD505.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Isaac96/EV-Code/d7546fa45881776557da84c97b0404d377ac14d3/EVTV JLD505.PNG -------------------------------------------------------------------------------- /FinalBMSCode/FinalBMSCode.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Nissan Leaf BMS data retrieval and interpretation; adapted from 3 | "Object Oriented CAN example for Teensy 3.6 with Dual CAN buses 4 | By Collin Kidder. Based upon the work of Pawelsky and Teachop" 5 | 6 | By Isaac Kelly 7 | 8 | The BnMS object handles all communication and reads frames as they come in 9 | Bus at 500K; alternate pins used with a pretty nice little CAN adapter from Croatia 10 | WARNING: This requires a slightly modified version of the FlexCAN library, unless you can tell me how to skip an input variable to a function 11 | 12 | 13 | This code is mostly interrupt-driven, especially when used in parallel with Leaf Spy 14 | */ 15 | 16 | #include 17 | #include "LeafBMS.h" 18 | unsigned long EVCCTimerMain = 0; 19 | unsigned long timeNow = 0; 20 | LeafBMS BMS;//make a BMS object 21 | 22 | // ------------------------------------------------------------- 23 | void setup(void) 24 | { 25 | delay(1000); 26 | //Serial.println("FinalBMSCode");//just so I know which code is uploaded 27 | 28 | pinMode(28, OUTPUT);//enable pin; this is specific to the CAN adapter I used 29 | digitalWrite(28, LOW);//it needs a strong pull to ground to enable transmissions 30 | Can0.begin(500000, 1, 1);//500K, alternate pins 31 | pinMode(13, OUTPUT); 32 | 33 | Can0.attachObj(&BMS); 34 | 35 | BMS.attachGeneralHandler(); 36 | } 37 | 38 | // ------------------------------------------------------------- 39 | void loop(void) 40 | { 41 | 42 | timeNow = millis(); 43 | if (timeNow - BMS.heartbeat > BMS.heartbeatInterval) {//if there hasn't been data for a while, the BMS isn't connected anymore 44 | BMS.heartbeat = timeNow; 45 | BMS.isConnected = false; 46 | } 47 | if ((timeNow - EVCCTimerMain > BMS.EVCCInterval) && BMS.isConnected) {//Is it time to tell the EVCC what's going on? If the bms is disconnected let the evcc time out 48 | EVCCTimerMain = timeNow; 49 | BMS.sendBMSData(); 50 | 51 | //Serial.print("Sending data to EVCC; min voltage is: "); 52 | //Serial.print(BMS.lowestVolt); 53 | //Serial.print("v; Max voltage: "); 54 | //Serial.println(BMS.highestVolt); 55 | digitalWrite(13, HIGH); 56 | } 57 | if (timeNow - BMS.vTimer > BMS.vInterval) {//group 3 has vmin and vmax 58 | BMS.vTimer = timeNow;//cell data is the main focus of this software 59 | BMS.getGroup(3);//group 3 has what we need 60 | } 61 | if (timeNow - BMS.cellTimer > BMS.cellInterval) {//better go grab the cell data 62 | BMS.cellTimer = timeNow;//cell data is the main focus of this software 63 | BMS.getGroup(2);//group 2 has cell data 64 | digitalWrite(13, LOW); 65 | } 66 | if (timeNow - BMS.SOCTimer > BMS.SOCInterval) {//probably need the SOC data by now 67 | BMS.SOCTimer = timeNow; 68 | BMS.getGroup(1);//group 1 has pack info data 69 | } 70 | if (timeNow - BMS.tempTimer > BMS.tempInterval) {//looks like temps need updating 71 | BMS.tempTimer = timeNow; 72 | BMS.getGroup(4);//group 4 holds temperatures 73 | } 74 | if (timeNow - BMS.debugTimer > 2000) { 75 | BMS.debugTimer = timeNow; 76 | //printCSV(); 77 | //printCellInfo(); 78 | 79 | } 80 | if (Serial.available()) { 81 | while (Serial.available()) { 82 | Serial.read(); 83 | } 84 | printCSV(); 85 | //printCellInfo(); 86 | //BMS.getGroup(Serial.parseInt());//if you send it a number from 1-6 it will retrieve that CAN group 87 | } 88 | if (BMS.isConnected) {//there has been data in the last 10 seconds 89 | if (BMS.highestVolt >= BMS.maxCellVoltage) { 90 | //Serial.println("A cell is too high, stopping charge");//HVC triggered; stop charging 91 | BMS.HVC = true; 92 | } 93 | else if (BMS.highestVolt > BMS.balanceCellVoltage) { 94 | BMS.BVC = true; 95 | } 96 | else if (BMS.smoothLowestVolt <= BMS.minCellVoltage) { 97 | //Serial.println("Cell is too low. Turn off da car");//LVC triggered; stop driving 98 | BMS.LVC = true; 99 | } 100 | else { 101 | BMS.LVC = false; 102 | BMS.HVC = false; 103 | BMS.BVC = false; 104 | } 105 | } 106 | } 107 | void printCellInfo() { 108 | int tempVoltage = 0; 109 | Serial.print("Lowest cell: "); 110 | Serial.print(BMS.lowestVolt); 111 | Serial.print("mV, #"); 112 | Serial.println(BMS.lowestCell); 113 | Serial.print("Highest cell: "); 114 | Serial.print(BMS.highestVolt); 115 | Serial.print("mV, #"); 116 | Serial.println(BMS.highestCell); 117 | Serial.print("Difference: "); 118 | Serial.println(BMS.cellDiff); 119 | for (int i = 0; i < 96; i++) { 120 | #ifdef DEBUG 121 | Serial.print("Cell number: "); 122 | Serial.print(i + 1); 123 | Serial.print(" Voltage: "); 124 | Serial.println(BMS.cellVoltages[i]); 125 | #endif 126 | tempVoltage += BMS.cellVoltages[i]; 127 | } 128 | 129 | BMS.totalVoltage = tempVoltage / 100; 130 | Serial.print("Total voltage: "); 131 | Serial.println(BMS.totalVoltage); 132 | if (!BMS.isConnected) { 133 | Serial.println("We appear to have lost connection"); 134 | } 135 | } 136 | 137 | void printCSV() { 138 | Serial.print("f"); 139 | for (int i = 0; i < 96; i++) { 140 | Serial.print(","); 141 | Serial.print(BMS.cellVoltages[i]); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /FinalBMSCode/LeafBMS.cpp: -------------------------------------------------------------------------------- 1 | #include "LeafBMS.h" 2 | 3 | 4 | void LeafBMS::sendBMSData() { 5 | static CAN_message_t EVCCFrame; 6 | EVCCFrame.ext = 0; 7 | EVCCFrame.id = 0x50f; 8 | EVCCFrame.len = 5; 9 | EVCCFrame.buf[0] = 0x00;//status; 1 = hvc, 2 = lvc, 4 = bvc 10 | EVCCFrame.buf[1] = 0x00;//0 11 | EVCCFrame.buf[2] = 0x00;//fault; 4 = overtemp/undertemp 12 | EVCCFrame.buf[3] = 0x00;//0 13 | EVCCFrame.buf[4] = 0x00;//0 14 | if (HVC) { 15 | bitSet(EVCCFrame.buf[0], 0); 16 | } 17 | if (LVC) { 18 | bitSet(EVCCFrame.buf[0], 1); 19 | } 20 | if (BVC) { 21 | bitSet(EVCCFrame.buf[0], 2); 22 | } 23 | //if (!isConnected) { 24 | //bitSet(EVCCFrame.buf[2], 2);//therm overtemp 25 | //} 26 | //Serial.println(EVCCFrame.buf[1], BIN); 27 | 28 | static CAN_message_t infoFrame; 29 | infoFrame.ext = 0; 30 | infoFrame.id = 0x50e; 31 | infoFrame.len = 8; 32 | infoFrame.buf[0] = 0x00;//status; 1 = hvc, 2 = lvc, 4 = bvc 33 | infoFrame.buf[1] = 0x00;//fault; 1 = overtemp/undertemp 34 | infoFrame.buf[2] = lowByte(totalVoltage);//voltage LSB 35 | infoFrame.buf[3] = highByte(totalVoltage);//voltage MSB 36 | infoFrame.buf[4] = lowByte(highestVolt); 37 | infoFrame.buf[5] = highByte(highestVolt); 38 | infoFrame.buf[6] = lowByte(lowestVolt); 39 | infoFrame.buf[7] = highByte(lowestVolt); 40 | 41 | if (HVC) { 42 | bitSet(infoFrame.buf[0], 0); 43 | } 44 | if (LVC) { 45 | bitSet(infoFrame.buf[0], 1); 46 | } 47 | if (BVC) { 48 | bitSet(infoFrame.buf[0], 2); 49 | } 50 | 51 | Can0.write(EVCCFrame); 52 | delay(20); 53 | Can0.write(infoFrame); 54 | //Serial.println("Sent off data"); 55 | 56 | EVCCTimer = 0; 57 | } 58 | 59 | void LeafBMS::gotFrame(CAN_message_t &frame, int mailbox)//Any frame received by the CAN hardware will be sent to this 60 | { 61 | #ifdef CANDEBUG 62 | printFrame(frame, mailbox); 63 | #endif 64 | if (frame.id == 0x7bb) {//it's a response to something 65 | sortDataFrame(frame, mailbox); 66 | } 67 | } 68 | 69 | void LeafBMS::printFrame(CAN_message_t &frame, int mailbox)//prints received frames for debug purposes; only shows BMS messages 70 | { 71 | if (frame.id == 0x7bb || frame.id == 0x79b) { 72 | Serial.print("ID: "); 73 | Serial.print(frame.id, HEX); 74 | Serial.print(" Data: "); 75 | for (int c = 0; c < frame.len; c++) 76 | { 77 | Serial.print(frame.buf[c], HEX); 78 | Serial.write(' '); 79 | } 80 | Serial.println(); 81 | } 82 | else {} 83 | } 84 | void LeafBMS::sortDataFrame(CAN_message_t &frame, int mailbox)//first layer of sorting 85 | { 86 | if ((frame.buf[0] == 0x10) && (frame.buf[3] == 0x04)) {//first packet, temp packet 87 | parseTempFrame(frame); 88 | } 89 | else if ((frame.buf[0] == 0x10) && (frame.buf[3] == 0x02)) {//first packet, cell packet 90 | parseCellFrame(frame); 91 | } 92 | else if ((frame.buf[0] == 0x10) && (frame.buf[3] == 0x01)) {//first packet, cell packet 93 | parseSOCFrame(frame); 94 | } 95 | else if ((frame.buf[0] == 0x10) && (frame.buf[3] == 0x03)) {//first packet, v packet 96 | parseVFrame(frame); 97 | } 98 | else if (gotTempFrame) {//currently grabbing temperature data 99 | parseTempFrame(frame); 100 | } 101 | else if (gotFirstCellFrame) {//currently grabbing cell data 102 | parseCellFrame(frame); 103 | } 104 | else if (gotSOCFrame) {//currently grabbing SOC data 105 | parseSOCFrame(frame); 106 | } 107 | else if (gotVFrame) { 108 | parseVFrame(frame); 109 | } 110 | else {} 111 | } 112 | void LeafBMS::parseTempFrame(CAN_message_t &frame) {//grabs data from temperature packets 113 | if ((frame.buf[0] == 0x10) && (frame.buf[3] == 0x04)) {//it's both the first packet and a temperature packet 114 | gotTempFrame = 1; 115 | tempData[0] = frame.buf[6]; 116 | } 117 | else if (frame.buf[0] == 0x21) {//second packet and frame 1 of temp has just been received 118 | tempData[1] = frame.buf[2]; 119 | tempData[2] = frame.buf[5]; 120 | } 121 | else if (frame.buf[0] == 0x22) { 122 | gotTempFrame = 0; 123 | 124 | tempData[3] = frame.buf[1]; 125 | for (int i = 0; i < 4; i++) { 126 | } 127 | tempTimer = millis(); 128 | } 129 | else {} 130 | } 131 | //given a frame of cell data from group 2, this will figure out which it is and then store the data 132 | //in the cellData[] array. When the last frame from the group is received, the data is converted to cell voltages 133 | void LeafBMS::parseCellFrame(CAN_message_t &frame) { 134 | int index = 0; 135 | if (frame.buf[0] == 0x10) {//it's the first packet; this function only gets cell packets anyways 136 | gotFirstCellFrame = 1; 137 | for (int i = 0; i < 4; i++) { 138 | cellData[i] = frame.buf[i + 4]; 139 | } 140 | } else { 141 | for (int i = 32; i < 48; i++) {//figures out where we are in the group 142 | if (frame.buf[0] == i && !gotHalfCellFrame) {//we're still in the first half of the data 143 | index = i - 31; 144 | for (int i = 1; i < 8; i++) { 145 | int place = (index * 7 - 11 + i);//do some complicated math that I don't really understand. All I know is, it works. 146 | cellData[place] = frame.buf[i]; 147 | } 148 | } else if (frame.buf[0] == i && gotHalfCellFrame) {//second half is coming in 149 | index = i - 15; 150 | for (int i = 1; i < 8; i++) { 151 | int place = (index * 7 - 11 + i);//The 11 was determined empirically. This code does work 152 | cellData[place] = frame.buf[i]; 153 | } 154 | } else {} 155 | } 156 | } 157 | if (index == 16) {//We got the first half of the group 158 | gotHalfCellFrame = 1; 159 | } else if (index == 29) {//it's the last one! Yay! Reset everything 160 | gotHalfCellFrame = 0; 161 | gotFirstCellFrame = 0; 162 | highestVolt = cellVoltages[0];//reset the max and min voltages 163 | lowestVolt = cellVoltages[0]; 164 | for (int i = 0; i < 96; i++) { 165 | cellVoltages[i] = (cellData[i * 2] * 256) + cellData[(i * 2) + 1]; //convert from 2 bytes of hexadecimal to the actual voltage * 1000; add 15 idk why, some weirdness with this bms 166 | if (cellVoltages[i] > highestVolt) { 167 | highestVolt = cellVoltages[i];// it's the highest yet so store it 168 | highestCell = i + 1; 169 | } 170 | else if (cellVoltages[i] < lowestVolt) { 171 | lowestVolt = cellVoltages[i];//it's the lowest yet so store it 172 | lowestCell = i + 1; 173 | } 174 | else {} 175 | cellDiff = highestVolt - lowestVolt;//figure out the total difference; this is a good measure of pack health 176 | } 177 | if (lowestVolt > 0) { 178 | runs++; 179 | if (runs > 10) { 180 | runs = 10; 181 | } 182 | movingIndex++; 183 | if (movingIndex >= 10) { 184 | movingIndex = 0; 185 | } 186 | lowestVoltArray[movingIndex] = lowestVolt; 187 | for (int i = 0; i < 9; i++) { 188 | smoothLowestVolt += lowestVoltArray[i]; 189 | } 190 | //Serial.println(lowestVolt); 191 | //Serial.println(smoothLowestVolt); 192 | if (runs == 10) { 193 | smoothLowestVolt = smoothLowestVolt / 10; 194 | //Serial.println("Using real smooth values"); 195 | } else { 196 | smoothLowestVolt = lowestVolt; 197 | } 198 | //Serial.println(smoothLowestVolt); 199 | //Serial.println(movingIndex); 200 | } 201 | cellTimer = millis();//we got a full group, reset the counter 202 | heartbeat = millis(); 203 | isConnected = true; 204 | } else {} 205 | } 206 | 207 | void LeafBMS::parseVFrame(CAN_message_t &frame) { 208 | int index = 0; 209 | if (frame.buf[0] == 0x10) { //it's the first packet and thus only contains a few bytes of data 210 | gotVFrame = 1; 211 | for (int i = 0; i < 4; i++) { 212 | vData[i] = frame.buf[i + 4]; 213 | } 214 | } else { 215 | index = frame.buf[0] - 32; 216 | for (int a = 1; a < 8; a++) {//the rest of the packets are mostly data 217 | vData[(index * 7) - 4 + a] = frame.buf[a]; 218 | } 219 | } 220 | 221 | if (index == 4) { 222 | gotVFrame = 0; 223 | vLowestVolt = vData[13] + (vData[12] * 256); 224 | vHighestVolt = vData[11] + (vData[10] * 256); 225 | //Serial.print("Lowest Volt: "); 226 | //Serial.println(vLowestVolt); 227 | //Serial.print("Highest Volt: "); 228 | //Serial.println(vHighestVolt); 229 | vCellDiff = vHighestVolt - vLowestVolt; 230 | vTimer = millis(); 231 | 232 | } 233 | } 234 | void LeafBMS::parseSOCFrame(CAN_message_t &frame) { 235 | int index = 0; 236 | if (frame.buf[0] == 0x10) { //it's the first packet and thus only contains a few bytes of data 237 | gotSOCFrame = 1; 238 | for (int i = 0; i < 4; i++) { 239 | SOCData[i] = frame.buf[i + 4]; 240 | } 241 | } else { 242 | index = frame.buf[0] - 32; 243 | for (int a = 1; a < 8; a++) {//the rest of the packets are mostly data 244 | SOCData[(index * 7) - 4 + a] = frame.buf[a]; 245 | } 246 | } 247 | 248 | if (index == 5) { 249 | gotSOCFrame = 0; 250 | SOCTimer = millis(); 251 | AH = (SOCData[33] * 65536 + SOCData[34] * 256 + SOCData[35]) / 10000.0000; 252 | SOH = (SOCData[26] * 256 + SOCData[27]) / 100.00; 253 | SOC = (SOCData[29] * 65536 + SOCData[30] * 256 + SOCData[31]); 254 | } 255 | } 256 | 257 | //Sends frames to the BMS requesting specific data sets 258 | void LeafBMS::getGroup(int group) { 259 | static CAN_message_t allLineReq; 260 | static CAN_message_t groupReq; 261 | 262 | //this command requests all additional frames in 263 | allLineReq.ext = 0;//add'l lines 264 | allLineReq.id = 0x79b; 265 | allLineReq.len = 8; 266 | allLineReq.buf[0] = 0x30; 267 | allLineReq.buf[1] = 0x00; 268 | allLineReq.buf[2] = 0x00; 269 | allLineReq.buf[3] = 0x00; 270 | allLineReq.buf[4] = 0x00; 271 | allLineReq.buf[5] = 0x00; 272 | allLineReq.buf[6] = 0x00; 273 | allLineReq.buf[7] = 0x00; 274 | 275 | groupReq.ext = 0;//group 1 Request 276 | groupReq.id = 0x79b; 277 | groupReq.len = 8; 278 | groupReq.buf[0] = 0x02; 279 | groupReq.buf[1] = 0x21; 280 | groupReq.buf[2] = group; 281 | groupReq.buf[3] = 0x00; 282 | groupReq.buf[4] = 0x00; 283 | groupReq.buf[5] = 0x00; 284 | groupReq.buf[6] = 0x00; 285 | groupReq.buf[7] = 0x00; 286 | 287 | Can0.write(groupReq); 288 | delay(25); 289 | Can0.write(allLineReq); 290 | delay(25); 291 | } 292 | -------------------------------------------------------------------------------- /FinalBMSCode/LeafBMS.h: -------------------------------------------------------------------------------- 1 | #ifndef LEAFBMS_H_ 2 | #define LEAFBMS_H_ 3 | #include 4 | #include 5 | 6 | //#define DEBUG 7 | //#define CANDEBUG 8 | 9 | 10 | class LeafBMS : public CANListener { 11 | public: 12 | 13 | void getGroup(int group);//requests data from BMS 14 | 15 | void printFrame(CAN_message_t &frame, int mailbox);//debug function which spits out frames to the serial port 16 | void gotFrame(CAN_message_t &frame, int mailbox); //overrides the parent version so we can actually do something 17 | 18 | void sortDataFrame(CAN_message_t &frame, int mailbox);//sorts data frames and passes them to parsing functions 19 | void parseTempFrame(CAN_message_t &frame);//interprets temperature frames 20 | void parseCellFrame(CAN_message_t &frame);//interprets cell frames 21 | void parseSOCFrame(CAN_message_t &frame);//interprets pack info frames 22 | void parseVFrame(CAN_message_t &frame);//interprets pack info frames 23 | 24 | void sendBMSData();//sends data to EVCC w/ID 0x50e 25 | 26 | byte tempData[4];//data from BMS, which is also the actual temperatures 27 | byte cellData[200];//raw cell data from BMS 28 | int cellVoltages[96];//the actual voltages 29 | uint16_t highestVolt;//highest cell voltage 30 | uint16_t vHighestVolt;//highest cell voltage 31 | int highestCell; 32 | uint16_t lowestVolt;//lowest cell voltage 33 | uint32_t smoothLowestVolt;//smoother value 34 | uint16_t lowestVoltArray[10];//array for moving average 35 | int movingIndex = 0; 36 | int runs = 0; 37 | uint16_t vLowestVolt;//lowest cell voltage 38 | int lowestCell; 39 | int cellDiff;//difference between highest and lowest cells 40 | int vCellDiff;//difference between highest and lowest cells 41 | byte SOCData[39];//group 1 info; includes SOC, SOH, Ah remaining etc. 42 | byte vData[32];//group 4 info; has vMin, vMax, etc. 43 | uint16_t totalVoltage; 44 | 45 | float SOC = 0;//state of charge of pack 46 | float SOH = 0;//state of health; probably useless 47 | float AH = 0;//AH remaining; probably also useless 48 | 49 | bool gotTempFrame = 0;//states of data retrieval 50 | bool gotFirstCellFrame = 0; 51 | bool gotHalfCellFrame = 0; 52 | bool gotSOCFrame = 0; 53 | bool gotVFrame = 0; 54 | bool firstRun = false; 55 | 56 | bool isConnected = false;//whether there has been data recently 57 | bool HVC = false; 58 | bool LVC = false; 59 | bool BVC = false; 60 | bool allowCharge = false; 61 | bool allowDischarge = false; 62 | 63 | unsigned long tempTimer;//counters for data retrieval timing 64 | unsigned long vTimer; 65 | unsigned long cellTimer; 66 | unsigned long SOCTimer; 67 | unsigned long debugTimer; 68 | unsigned long EVCCTimer; 69 | unsigned long heartbeat;//the time since the last cell data; makes sure wires stay connected 70 | unsigned long SOCInterval = 3000; 71 | unsigned long cellInterval = 3000; 72 | unsigned long vInterval = 3000; 73 | unsigned long tempInterval = 10000; 74 | unsigned long debugInterval = 1000; 75 | unsigned long heartbeatInterval = 10000; 76 | unsigned long EVCCInterval = 500; 77 | 78 | int maxCellVoltage = 4200;//configure the max and min voltages you wish 79 | int minCellVoltage = 3300; 80 | int balanceCellVoltage = 4150; 81 | }; 82 | 83 | #endif 84 | -------------------------------------------------------------------------------- /IJLD505/IJLD505.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "globals.h" 11 | #include "chademo.h" 12 | /* 13 | Notes on what needs to be done: 14 | - Timing analysis showed that the USB, CANBUS, and BT routines take up entirely too much time. They can delay processing by 15 | almost 100ms! Minor change for test. 16 | 17 | - Investigate what changes are necessary to support the Cortex M0 processor in the Arduino Zero 18 | 19 | - Interrupt driven CAN has a tendency to lock up. It has been disabled for now - It locks up even if the JLD is not sending anything 20 | but it seems to be able to actually send as much as you want. Only interrupt driven reception seems to make things die. 21 | 22 | Note about timing related code: The function millis() returns a 32 bit integer that specifies the # of milliseconds since last start up. 23 | That's all well and good but 4 billion milliseconds is a little less than 50 days. One might ask "so?" well, this could potentially run 24 | indefinitely in some vehicles and so would suffer rollover every 50 days. When this happens improper code will become stupid. So, try 25 | to implement any timing code like this: 26 | if ((millis() - someTimeStamp) >= SomeInterval) do stuff 27 | 28 | Such a code block will do the proper thing so long as all variables used are unsigned long / uint32_t variables. 29 | 30 | */ 31 | 32 | //#define DEBUG_TIMING //if this is defined you'll get time related debugging messages 33 | //#define CHECK_FREE_RAM //if this is defined it does what it says - reports the lowest free RAM found at any point in the sketch 34 | 35 | template inline Print &operator <<(Print &obj, T arg) { 36 | obj.print(arg); //Sets up serial streaming Serial<= 3) 106 | { 107 | timerChademoCounter = 0; 108 | if (chademo.bChademoMode && chademo.bChademoSendRequests) chademo.bChademoRequest = 1; 109 | } 110 | 111 | if (timerFastCounter == 8) 112 | { 113 | debugTick = 1; 114 | timerFastCounter = 0; 115 | timerIntCounter++; 116 | if (timerIntCounter < 10) 117 | { 118 | bGetTemperature = 1; 119 | sensorReadPosition++; 120 | } 121 | if (timerIntCounter == 10) 122 | { 123 | bStartConversion = 1; 124 | sensorReadPosition = 255; 125 | } 126 | if (timerIntCounter == 18) 127 | { 128 | timerIntCounter = 0; 129 | } 130 | } 131 | } 132 | 133 | void setup() 134 | { 135 | //first thing configure the I/O pins and set them to a sane state 136 | pinMode(IN0, INPUT); 137 | pinMode(IN1, INPUT_PULLUP); 138 | pinMode(OUT0, OUTPUT); 139 | pinMode(OUT1, OUTPUT); 140 | digitalWrite(OUT0, LOW); 141 | digitalWrite(OUT1, LOW); 142 | pinMode(A1, OUTPUT); //KEY - Must be HIGH 143 | pinMode(A0, INPUT); //STATE 144 | digitalWrite(A1, HIGH); 145 | pinMode(3, INPUT_PULLUP); //enable weak pull up on MCP2515 int pin connected to INT1 on MCU 146 | 147 | Serial.begin(115200); 148 | 149 | sensors.begin(); 150 | sensors.setWaitForConversion(false); //we're handling the time delay ourselves so no need to wait when asking for temperatures 151 | 152 | CAN.begin(CAN_500KBPS); 153 | attachInterrupt(1, MCP2515_ISR, FALLING); // start interrupt 154 | 155 | ina.begin();//69 156 | ina.configure(INA226_AVERAGES_16, INA226_BUS_CONV_TIME_1100US, INA226_SHUNT_CONV_TIME_1100US, INA226_MODE_SHUNT_BUS_CONT);// ina.configure(INA226_AVERAGES_16, INA226_BUS_CONV_TIME_1100US, INA226_SHUNT_CONV_TIME_1100US, INA226_MODE_SHUNT_BUS_CONT); 157 | ina.calibrate(0.0000833333, 600); 158 | 159 | EEPROM_readAnything(256, settings); 160 | if (settings.valid != EEPROM_VALID) //not proper version so reset to defaults 161 | { 162 | Serial.println(F("Settings invalid, re-reading")); 163 | settings.valid = EEPROM_VALID; 164 | settings.ampHours = 0.0; 165 | settings.kiloWattHours = 0.0; 166 | settings.currentCalibration = 600.0 / 0.050; //600A 50mv shunt 167 | settings.voltageCalibration = (100000.0 * 830000.0 / 930000.0 + 1000000.0) / (100275.0 * 830000.0 / 930000.0); // (Voltage Divider with (100k in parallel with 830k) and 1M ) 168 | settings.packSizeKWH = 24.0; //just a random guess. Maybe it should default to zero though? 169 | settings.maxChargeAmperage = MAX_CHARGE_A; 170 | settings.maxChargeVoltage = MAX_CHARGE_V; 171 | settings.targetChargeVoltage = TARGET_CHARGE_V; 172 | settings.minChargeAmperage = MIN_CHARGE_A; 173 | settings.SOC = INITIAL_SOC; 174 | settings.capacity = CAPACITY; 175 | settings.debuggingLevel = 2; 176 | settings.resetVoltage = RESET_V; 177 | settings.maxChargeTemp = MAX_TEMP; 178 | EEPROM_writeAnything(256, settings); 179 | 180 | } 181 | 182 | settings.debuggingLevel = 2; //locked in to max debugging for now. 183 | 184 | //attachInterrupt(0, Save, FALLING); 185 | FrequencyTimer2::setPeriod(25000); //interrupt every 25ms 186 | FrequencyTimer2::setOnOverflow(timer2Int); 187 | 188 | if (settings.debuggingLevel > 0) 189 | { 190 | Serial.print(F("Found ")); 191 | tempSensorCount = sensors.getDeviceCount(); 192 | Serial.print(tempSensorCount); 193 | Serial.println(F(" temperature sensors.")); 194 | } 195 | 196 | chademo.setTargetAmperage(settings.maxChargeAmperage); 197 | chademo.setTargetVoltage(settings.targetChargeVoltage); 198 | } 199 | 200 | void loop() 201 | { 202 | //Serial.println(millis()); 203 | uint8_t pos; 204 | CurrentMillis = millis(); 205 | uint8_t len; 206 | CAN_FRAME inFrame; 207 | float tempReading; 208 | 209 | #ifdef DEBUG_TIMING 210 | if (debugTick == 1) 211 | { 212 | debugTick = 0; 213 | Serial.println(millis()); 214 | } 215 | #endif 216 | 217 | chademo.loop(); 218 | if (CurrentMillis - PreviousMillis >= Interval) 219 | { 220 | Time = CurrentMillis - PreviousMillis; 221 | PreviousMillis = CurrentMillis; 222 | 223 | Count++; 224 | //Serial.print("Reading "); 225 | Voltage = ina.readBusVoltage() * settings.voltageCalibration; 226 | Current = ina.readShuntCurrent() * 4; 227 | settings.ampHours += Current * (float)Time / 1000.0 / 3600.0; 228 | Power = Voltage * Current / 1000.0; 229 | settings.kiloWattHours += Power * (float)Time / 1000.0 / 3600.0; 230 | settings.SOC = ((settings.capacity - settings.ampHours) / settings.capacity) * 100; 231 | if (Voltage > settings.resetVoltage) { 232 | Serial.print(Voltage); 233 | Serial.print(F("V; Reset = ")); 234 | Serial.println(settings.resetVoltage); 235 | Serial.println(F("Reset Ah & Wh")); 236 | settings.ampHours = 0.0; 237 | settings.kiloWattHours = 0.0; 238 | } 239 | //Serial.println("Read info"); 240 | chademo.doProcessing(); 241 | 242 | if (Count >= 50) 243 | { 244 | Count = 0; 245 | USB(); 246 | 247 | if (!chademo.bChademoMode) //save some processor time by not doing these in chademo mode 248 | { 249 | CANBUS(); 250 | } 251 | else if (settings.debuggingLevel > 0) 252 | { 253 | Serial.print(F("Chademo Mode: ")); 254 | Serial.println(chademo.getState()); 255 | } 256 | Save(); 257 | #ifdef CHECK_FREE_RAM 258 | Serial.print(F("Lowest free RAM: ")); 259 | Serial.println(lowestFreeRAM); 260 | #endif 261 | } 262 | } 263 | //if (CAN.GetRXFrame(inFrame)) { 264 | if (Flag_Recv || (CAN.checkReceive() == CAN_MSGAVAIL)) { 265 | Flag_Recv = 0; 266 | CAN.receiveFrame(inFrame); 267 | //Serial.print("IN CAN: "); 268 | //Serial.println(inFrame.id, HEX); 269 | chademo.handleCANFrame(inFrame); 270 | } 271 | 272 | //digitalWrite(OUT1, HIGH); 273 | 274 | if (bStartConversion == 1) 275 | { 276 | bStartConversion = 0; 277 | sensors.requestTemperatures(); 278 | } 279 | if (bGetTemperature) 280 | { 281 | bGetTemperature = 0; 282 | pos = sensorReadPosition; 283 | if (pos < tempSensorCount) 284 | { 285 | sensors.isConnected(pos); 286 | tempReading = sensors.getTempCByIndex(pos); 287 | if (tempReading > settings.maxChargeTemp) { 288 | tempAlarm = true; 289 | overtempSensor = pos; 290 | Serial.println(F("Sensor overtemp")); 291 | Serial.println(pos); 292 | } else if (pos = overtempSensor) { 293 | tempAlarm = false; 294 | } 295 | if (chademo.bChademoMode && tempAlarm) 296 | { 297 | Serial.println(F("Temperature fault! Aborting charge!")); 298 | chademo.setBattOverTemp(); 299 | chademo.setDelayedState(CEASE_CURRENT, 10); 300 | } 301 | 302 | /* 303 | if (settings.debuggingLevel > 0) 304 | { 305 | Serial.print(F(" Temp sensor:")); 306 | Serial.print(pos); 307 | Serial.print(": "); 308 | Serial.print(tempReading); 309 | Serial.print("/"); 310 | Serial.print(sensors.getMinTempC(pos)); 311 | Serial.print("/"); 312 | Serial.print(sensors.getMaxTempC(pos)); 313 | Serial.print("/"); 314 | Serial.println(sensors.getAvgTempC(pos)); 315 | }*/ 316 | } 317 | } 318 | checkRAM(); 319 | //digitalWrite(OUT1, LOW); 320 | } 321 | 322 | void serialEvent() { 323 | int incoming; 324 | incoming = Serial.read(); 325 | if (incoming == -1) { //false alarm.... 326 | return; 327 | } 328 | 329 | if (incoming == 10 || incoming == 13) { //command done. Parse it. 330 | handleConsoleCmd(); 331 | ptrBuffer = 0; //reset line counter once the line has been processed 332 | } else { 333 | cmdBuffer[ptrBuffer++] = (unsigned char) incoming; 334 | if (ptrBuffer > 79) 335 | ptrBuffer = 79; 336 | } 337 | } 338 | 339 | void handleConsoleCmd() { 340 | handlingEvent = true; 341 | 342 | if (state == STATE_ROOT_MENU) { 343 | if (ptrBuffer == 1) { //command is a single ascii character 344 | handleShortCmd(); 345 | } else { //if cmd over 1 char then assume (for now) that it is a config line 346 | handleConfigCmd(); 347 | } 348 | } 349 | handlingEvent = false; 350 | } 351 | void handleConfigCmd() { 352 | int i; 353 | int newValue; 354 | 355 | cmdBuffer[ptrBuffer] = 0; //make sure to null terminate 356 | String cmdString = String(); 357 | unsigned char whichEntry = '0'; 358 | i = 0; 359 | 360 | while (cmdBuffer[i] != '=' && i < ptrBuffer) { 361 | cmdString.concat(String(cmdBuffer[i++])); 362 | } 363 | i++; //skip the = 364 | if (i >= ptrBuffer) 365 | { 366 | Serial.println(F("Command needs a value..ie AH=30")); 367 | Serial.println(F("")); 368 | return; //or, we could use this to display the parameter instead of setting 369 | } 370 | // strtol() is able to parse also hex values (e.g. a string "0xCAFE"), useful for enable/disable by device id 371 | newValue = strtol((char *) (cmdBuffer + i), NULL, 0); 372 | 373 | cmdString.toUpperCase(); 374 | if (cmdString == String("AH")) { 375 | Serial.print(F("Setting AH to ")); 376 | Serial.println(newValue); 377 | settings.capacity = newValue; 378 | Save(); 379 | //set value 380 | } else if (cmdString == String("RESET")) { 381 | Serial.print(F("Setting Reset Voltage to ")); 382 | Serial.println(newValue); 383 | //set value. save eeprom 384 | settings.resetVoltage = newValue; 385 | Save(); 386 | } else if (cmdString == String("CHADEMOV")) { 387 | Serial.print(F("Setting Charge Voltage to ")); 388 | Serial.println(newValue); 389 | settings.targetChargeVoltage = newValue; 390 | Save(); 391 | //set value, save eeprom 392 | } 393 | else if (cmdString == String("CHADEMOA")) { 394 | Serial.print(F("Setting Charge Current to ")); 395 | Serial.println(newValue); 396 | settings.maxChargeAmperage = newValue; 397 | Save(); 398 | //set value, save eeprom 399 | } 400 | else if (cmdString == String("CHADEMOEND")) { 401 | Serial.print(F("Setting End Current to ")); 402 | Serial.println(newValue); 403 | settings.minChargeAmperage = newValue; 404 | Save(); 405 | //set value, save eeprom 406 | } 407 | else if (cmdString == String("KWH")) { 408 | Serial.print(F("Setting Pack Size to ")); 409 | Serial.println(newValue); 410 | settings.packSizeKWH = newValue; 411 | Save(); 412 | //set value, save eeprom 413 | } 414 | else if (cmdString == String("MAXV")) { 415 | Serial.print(F("Setting Absolute Maximum Voltage to ")); 416 | Serial.println(newValue); 417 | settings.maxChargeVoltage = newValue; 418 | Save(); 419 | //set value, save eeprom 420 | } 421 | else if (cmdString == String("MAXTEMP")) { 422 | Serial.print(F("Setting Maximum Charge Temperature to ")); 423 | Serial.println(newValue); 424 | settings.maxChargeTemp = newValue; 425 | Save(); 426 | //set value, save eeprom 427 | } 428 | } 429 | void handleShortCmd() { 430 | 431 | switch (cmdBuffer[0]) { 432 | case 'h': 433 | case '?': 434 | case 'H': 435 | printMenu(); 436 | break; 437 | case 'z': 438 | Serial.println(F("Reset Ah & Wh")); 439 | settings.ampHours = 0.0; 440 | settings.kiloWattHours = 0.0; 441 | Save(); 442 | break; 443 | case '+': 444 | settings.voltageCalibration += 0.004; 445 | Serial.println (settings.voltageCalibration, 5); 446 | Save(); 447 | break; 448 | case '-': //set all outputs high 449 | settings.voltageCalibration -= 0.004; 450 | Serial.println (settings.voltageCalibration, 5); 451 | Save(); 452 | break; 453 | 454 | } 455 | } 456 | 457 | void printMenu() {} 458 | void Save() 459 | { 460 | EEPROM_writeAnything(256, settings); 461 | } 462 | 463 | void USB() 464 | { 465 | Serial.print (F("JLD505: ")); 466 | Serial.print (Voltage, 3); 467 | Serial.print (F("v ")); 468 | Serial.print (Current, 2); 469 | Serial.print (F("A ")); 470 | Serial.print (settings.ampHours, 1); 471 | Serial.print (F("Ah ")); 472 | Serial.print (Power, 1); 473 | Serial.print (F("kW ")); 474 | Serial.print (settings.kiloWattHours, 1); 475 | Serial.print (F("kWh ")); 476 | Serial.print (settings.capacity, 1); 477 | Serial.print (F("Ah total ")); 478 | Serial.print (settings.SOC, 1); 479 | Serial.println (F("% SOC")); 480 | 481 | if (handlingEvent == false) { 482 | if (Serial.available()) { 483 | serialEvent(); 484 | } 485 | } 486 | 487 | checkRAM(); 488 | } 489 | 490 | 491 | void CANBUS() 492 | { 493 | CAN_FRAME outFrame; 494 | outFrame.id = 0x404; 495 | outFrame.length = 8; 496 | outFrame.priority = 2; 497 | outFrame.rtr = 0; 498 | outFrame.extended = 0; 499 | 500 | uint16_t currINT = abs(Current * 10); 501 | outFrame.data.byte[0] = highByte((int)(Voltage * 10)); // Voltage High Byte 502 | outFrame.data.byte[1] = lowByte((int)(Voltage * 10)); // Voltage Low Byte 503 | outFrame.data.byte[2] = highByte(currINT); // Current High Byte 504 | outFrame.data.byte[3] = lowByte(currINT); // Current Low Byte 505 | outFrame.data.byte[4] = highByte((int)(settings.ampHours * 10)); // AmpHours High Byte 506 | outFrame.data.byte[5] = lowByte((int)(settings.ampHours * 10)); // AmpHours Low Byte 507 | outFrame.data.byte[6] = settings.capacity; // Not Used 508 | outFrame.data.byte[7] = settings.SOC; // Not Used 509 | //CAN.EnqueueTX(outFrame); 510 | CAN.sendFrame(outFrame); 511 | 512 | outFrame.id = 0x505; 513 | outFrame.length = 8; 514 | uint16_t Pwr = abs(Power * 10); 515 | uint16_t KWH = abs(settings.kiloWattHours * 10); 516 | outFrame.data.byte[0] = highByte(Pwr); // Power High Byte 517 | outFrame.data.byte[1] = lowByte(Pwr); // Power Low Byte 518 | outFrame.data.byte[2] = highByte(KWH); // KiloWattHours High Byte 519 | outFrame.data.byte[3] = lowByte(KWH); // KiloWattHours Low Byte 520 | outFrame.data.byte[4] = (sensors.getTempC(0)) + 40; 521 | outFrame.data.byte[5] = (sensors.getTempC(1)) + 40; 522 | outFrame.data.byte[6] = (sensors.getTempC(2)) + 40; 523 | outFrame.data.byte[7] = (sensors.getTempC(3)) + 40; 524 | //CAN.EnqueueTX(outFrame); 525 | CAN.sendFrame(outFrame); 526 | 527 | checkRAM(); 528 | } 529 | 530 | void timestamp() 531 | { 532 | int milliseconds = (int) (millis() / 1) % 1000 ; 533 | int seconds = (int) (millis() / 1000) % 60 ; 534 | int minutes = (int) ((millis() / (1000 * 60)) % 60); 535 | int hours = (int) ((millis() / (1000 * 60 * 60)) % 24); 536 | 537 | Serial.print(F(" Time:")); 538 | Serial.print(hours); 539 | Serial.print(F(":")); 540 | Serial.print(minutes); 541 | Serial.print(F(":")); 542 | Serial.print(seconds); 543 | Serial.print(F(".")); 544 | Serial.println(milliseconds); 545 | } 546 | 547 | int freeRam () { 548 | extern int __heap_start, *__brkval; 549 | int v; 550 | return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval); 551 | } 552 | 553 | void checkRAM() 554 | { 555 | int freeram = freeRam(); 556 | if (freeram < lowestFreeRAM) lowestFreeRAM = freeram; 557 | } 558 | -------------------------------------------------------------------------------- /IJLD505/README.md: -------------------------------------------------------------------------------- 1 | JLD505 2 | ===== 3 | 4 | Battery Monitoring System and CHAdeMO Car-Side 5 | 6 | This project runs on the JLD505 AVR based board and compiles with the Arduino 7 | 1.6.x IDE. 8 | 9 | The following Arduino libraries, which are not distributed with the IDE, are required: 10 | - mcp_can - Canbus library 11 | - INA226 - Library for current / voltage sensing chip 12 | - EEPROMAnything - Provides a user friendly way to store structures to EEPROM 13 | - DS2480B - Interfaces with DS2480B serial to 1-wire chip 14 | - DallasTemperature - Provides support for 1-wire temperature sensors 15 | - FrequencyTimer2 - Provides support for steady timer interrupts on the AVR processor 16 | 17 | All of these libraries are found in the repos for Collin80 on GitHub 18 | 19 | All libraries belong in %USERPROFILE%\Documents\Arduino\libraries (Windows) or ~/Arduino/libraries (Linux/Mac). 20 | You will need to remove -master or any other postfixes. Your library folders should be named as above. 21 | 22 | The canbus is supposed to be terminated on both ends of the bus. The JLD505 board has termination resistors which 23 | can be used by soldering the jumpers near the center of the board. 24 | 25 | 26 | Modified by Isaac Kelly; uses DS18B20 sensors without DS2480b chip since that's SMD only; adds CAN BMS interface, more logging, more configurable settings. 2019 27 | 28 | 29 | 30 | 31 | This software is MIT licensed: 32 | 33 | Copyright (c) 2014 Collin Kidder, Paulo Alemeida, Jack Rickard 34 | 35 | Permission is hereby granted, free of charge, to any person obtaining 36 | a copy of this software and associated documentation files (the 37 | "Software"), to deal in the Software without restriction, including 38 | without limitation the rights to use, copy, modify, merge, publish, 39 | distribute, sublicense, and/or sell copies of the Software, and to 40 | permit persons to whom the Software is furnished to do so, subject to 41 | the following conditions: 42 | 43 | The above copyright notice and this permission notice shall be included 44 | in all copies or substantial portions of the Software. 45 | 46 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 47 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 48 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 49 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 50 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 51 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 52 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 53 | 54 | -------------------------------------------------------------------------------- /IJLD505/chademo.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | chademo.cpp - Houses all chademo related functionality 3 | */ 4 | 5 | #include "chademo.h" 6 | 7 | template inline Print &operator <<(Print &obj, T arg) { 8 | obj.print(arg); //Sets up serial streaming Serial< (uint32_t)(insertionTime + 500)) 77 | { 78 | if (bChademoMode == 0) 79 | { 80 | bChademoMode = 1; 81 | if (chademoState == STOPPED && !bStartedCharge) { 82 | chademoState = STARTUP; 83 | Serial.println(F("Starting Chademo process.")); 84 | carStatus.battOverTemp = 0; 85 | carStatus.battOverVolt = 0; 86 | carStatus.battUnderVolt = 0; 87 | carStatus.chargingFault = 0; 88 | carStatus.chargingEnabled = 0; 89 | carStatus.contactorOpen = 1; 90 | carStatus.currDeviation = 0; 91 | carStatus.notParked = 0; 92 | carStatus.stopRequest = 0; 93 | carStatus.voltDeviation = 0; 94 | bChademo10Protocol = 0; 95 | } 96 | } 97 | } 98 | } 99 | else 100 | { 101 | insertionTime = 0; 102 | if (bChademoMode == 1) 103 | { 104 | Serial.println(F("Stopping chademo process.")); 105 | bChademoMode = 0; 106 | bStartedCharge = 0; 107 | chademoState = STOPPED; 108 | //maybe it would be a good idea to try to see if EVSE is still transmitting to us and providing current 109 | //as it is not a good idea to open the contactors under load. But, IN1 shouldn't trigger 110 | //until the EVSE is ready. Also, the EVSE should have us locked so the only way the plug should come out under 111 | //load is if the idiot driver took off in the car. Bad move moron. 112 | digitalWrite(OUT0, LOW); 113 | digitalWrite(OUT1, LOW); 114 | if (settings.debuggingLevel > 0) 115 | { 116 | Serial << "CAR: Contactor open\n"; 117 | Serial << "CAR: Charge Enable OFF\n"; 118 | } 119 | } 120 | } 121 | 122 | if (bChademoMode) 123 | { 124 | 125 | if (!bDoMismatchChecks && chademoState == RUNNING) 126 | { 127 | if ((CurrentMillis - mismatchStart) >= mismatchDelay) bDoMismatchChecks = 1; 128 | } 129 | 130 | if (chademoState == LIMBO && (CurrentMillis - stateMilli) >= stateDelay) 131 | { 132 | chademoState = stateHolder; 133 | } 134 | 135 | if (bChademoSendRequests && bChademoRequest) 136 | { 137 | bChademoRequest = 0; 138 | frameRotate++; 139 | frameRotate %= 3; 140 | switch (frameRotate) 141 | { 142 | case 0: 143 | sendCANStatus(); 144 | break; 145 | case 1: 146 | sendCANBattSpecs(); 147 | break; 148 | case 2: 149 | sendCANChargingTime(); 150 | break; 151 | } 152 | if (settings.debuggingLevel > 1) Serial.println(F("Tx")); 153 | } 154 | 155 | switch (chademoState) 156 | { 157 | case STARTUP: 158 | bDoMismatchChecks = 0; //reset it for now 159 | setDelayedState(SEND_INITIAL_PARAMS, 50); 160 | break; 161 | case SEND_INITIAL_PARAMS: 162 | //we could do calculations to see how long the charge should take based on SOC and 163 | //also set a more realistic starting amperage. Options for the future. 164 | //One problem with that is that we don't yet know the EVSE parameters so we can't know 165 | //the max allowable amperage just yet. 166 | bChademoSendRequests = 1; //causes chademo frames to be sent out every 100ms 167 | setDelayedState(WAIT_FOR_EVSE_PARAMS, 50); 168 | if (settings.debuggingLevel > 0) Serial.println(F("Sent params to EVSE. Waiting.")); 169 | break; 170 | case WAIT_FOR_EVSE_PARAMS: 171 | //for now do nothing while we wait. Might want to try to resend start up messages periodically if no reply 172 | break; 173 | case SET_CHARGE_BEGIN: 174 | if (settings.debuggingLevel > 0) Serial.println(F("CAR:Charge enable ON")); 175 | digitalWrite(OUT1, HIGH); //signal that we're ready to charge 176 | carStatus.chargingEnabled = 1; //should this be enabled here??? 177 | setDelayedState(WAIT_FOR_BEGIN_CONFIRMATION, 50); 178 | break; 179 | case WAIT_FOR_BEGIN_CONFIRMATION: 180 | if (digitalRead(IN0)) //inverse logic from how IN1 works. Be careful! 181 | { 182 | setDelayedState(CLOSE_CONTACTORS, 100); 183 | } 184 | break; 185 | case CLOSE_CONTACTORS: 186 | if (settings.debuggingLevel > 0) Serial.println(F("CAR:Contactor close.")); 187 | digitalWrite(OUT0, HIGH); 188 | setDelayedState(RUNNING, 50); 189 | carStatus.contactorOpen = 0; //its closed now 190 | carStatus.chargingEnabled = 1; //please sir, I'd like some charge 191 | bStartedCharge = 1; 192 | mismatchStart = millis(); 193 | break; 194 | case RUNNING: 195 | //do processing here by taking our measured voltage, amperage, and SOC to see if we should be commanding something 196 | //different to the EVSE. Also monitor temperatures to make sure we're not incinerating the pack. 197 | break; 198 | case CEASE_CURRENT: 199 | if (settings.debuggingLevel > 0) Serial.println(F("CAR:Current req to 0")); 200 | carStatus.targetCurrent = 0; 201 | chademoState = WAIT_FOR_ZERO_CURRENT; 202 | break; 203 | case WAIT_FOR_ZERO_CURRENT: 204 | if (evse_status.presentCurrent == 0) 205 | { 206 | setDelayedState(OPEN_CONTACTOR, 150); 207 | } 208 | break; 209 | case OPEN_CONTACTOR: 210 | if (settings.debuggingLevel > 0) Serial.println(F("CAR:Contactor OPEN")); 211 | digitalWrite(OUT0, LOW); 212 | carStatus.contactorOpen = 1; 213 | carStatus.chargingEnabled = 0; 214 | sendCANStatus(); //we probably need to force this right now 215 | setDelayedState(STOPPED, 100); 216 | break; 217 | case FAULTED: 218 | Serial.println(F("CAR: fault!")); 219 | chademoState = CEASE_CURRENT; 220 | //digitalWrite(OUT0, LOW); 221 | //digitalWrite(OUT1, LOW); 222 | break; 223 | case STOPPED: 224 | if (bChademoSendRequests == 1) 225 | { 226 | digitalWrite(OUT0, LOW); 227 | digitalWrite(OUT1, LOW); 228 | if (settings.debuggingLevel > 0) 229 | { 230 | Serial.println(F("CAR:Contactor OPEN")); 231 | Serial.println(F("CAR:Charge Enable OFF")); 232 | } 233 | bChademoSendRequests = 0; //don't need to keep sending anymore. 234 | bListenEVSEStatus = 0; //don't want to pay attention to EVSE status when we're stopped 235 | } 236 | break; 237 | } 238 | } 239 | checkRAM(); 240 | } 241 | 242 | //things that are less frequently run - run on a set schedule 243 | void CHADEMO::doProcessing() 244 | { 245 | uint8_t tempCurrVal; 246 | 247 | if (chademoState == RUNNING && ((CurrentMillis - lastCommTime) >= lastCommTimeout)) 248 | { 249 | //this is BAD news. We can't do the normal cease current procedure because the EVSE seems to be unresponsive. 250 | Serial.println(F("EVSE comm fault! Commencing emergency shutdown!")); 251 | //yes, this isn't ideal - this will open the contactor and send the shutdown signal. It's better than letting the EVSE 252 | //potentially run out of control. 253 | chademoState = OPEN_CONTACTOR; 254 | } 255 | if (/*chademoState == RUNNING && */((CurrentMillis - lastBMSTime) >= lastBMSTimeout)){ 256 | Serial.println(F("BMS comm fault! Commencing emergency shutdown!")); 257 | chademoState = OPEN_CONTACTOR; 258 | } 259 | 260 | if (chademoState == RUNNING && bDoMismatchChecks) 261 | { 262 | if (Voltage > settings.maxChargeVoltage && !carStatus.battOverVolt) 263 | { 264 | vOverFault++; 265 | if (vOverFault > 9) 266 | { 267 | Serial.println(F("Over voltage fault!")); 268 | carStatus.battOverVolt = 1; 269 | chademoState = CEASE_CURRENT; 270 | } 271 | } 272 | else vOverFault = 0; 273 | 274 | //Constant Current/Constant Voltage Taper checks. If minimum current is set to zero, we terminate once target voltage is reached. 275 | //If not zero, we will adjust current up or down as needed to maintain voltage until current decreases to the minimum entered 276 | 277 | if (Count == 20) 278 | { 279 | if (evse_status.presentVoltage > settings.targetChargeVoltage - 1) //All initializations complete and we're running.We've reached charging target 280 | { 281 | settings.SOC = 100; 282 | settings.ampHours = 0; 283 | settings.kiloWattHours = 0; 284 | if (settings.minChargeAmperage == 0 || carStatus.targetCurrent < settings.minChargeAmperage) chademoState = CEASE_CURRENT; //Terminate charging 285 | else carStatus.targetCurrent--; //Taper. Actual decrease occurs in sendChademoStatus 286 | } 287 | else //Only adjust upward if we have previous adjusted downward and do not exceed max amps 288 | { 289 | if (carStatus.targetCurrent < settings.maxChargeAmperage) carStatus.targetCurrent++; 290 | } 291 | } 292 | } 293 | checkRAM(); 294 | } 295 | 296 | void CHADEMO::handleCANFrame(CAN_FRAME &frame) 297 | { 298 | uint8_t tempCurrVal; 299 | 300 | if (frame.id == EVSE_PARAMS_ID) 301 | { 302 | lastCommTime = millis(); 303 | if (chademoState == WAIT_FOR_EVSE_PARAMS) setDelayedState(SET_CHARGE_BEGIN, 100); 304 | evse_params.supportWeldCheck = frame.data.byte[0]; 305 | evse_params.availVoltage = frame.data.byte[1] + frame.data.byte[2] * 256; 306 | evse_params.availCurrent = frame.data.byte[3]; 307 | evse_params.thresholdVoltage = frame.data.byte[4] + frame.data.byte[5] * 256; 308 | if (settings.debuggingLevel > 1) 309 | { 310 | Serial.print(F("EVSE: MaxVoltage: ")); 311 | Serial.print(evse_params.availVoltage); 312 | Serial.print(F(" Max Current:")); 313 | Serial.print(evse_params.availCurrent); 314 | Serial.print(F(" Threshold Voltage:")); 315 | Serial.print(evse_params.thresholdVoltage); 316 | timestamp(); 317 | } 318 | 319 | //if charger cannot provide our requested voltage then GTFO 320 | if (evse_params.availVoltage < carStatus.targetVoltage && chademoState <= RUNNING) 321 | { 322 | vCapCount++; 323 | if (vCapCount > 9) 324 | { 325 | Serial.print(F("EVSE can't provide needed voltage. Aborting.")); 326 | Serial.println(evse_params.availVoltage); 327 | chademoState = CEASE_CURRENT; 328 | } 329 | } 330 | else vCapCount = 0; 331 | 332 | //if we want more current then it can provide then revise our request to match max output 333 | if (evse_params.availCurrent < carStatus.targetCurrent) carStatus.targetCurrent = evse_params.availCurrent; 334 | 335 | //If not in running then also change our target current up to the minimum between the 336 | //available current reported and the max charge amperage. This should fix an issue where 337 | //the target current got wacked for some reason and left at zero. 338 | if (chademoState != RUNNING && evse_params.availCurrent > carStatus.targetCurrent) 339 | { 340 | carStatus.targetCurrent = min(evse_params.availCurrent, settings.maxChargeAmperage); 341 | } 342 | } 343 | 344 | if (frame.id == EVSE_STATUS_ID) 345 | { 346 | lastCommTime = millis(); 347 | if (frame.data.byte[0] > 1) bChademo10Protocol = 1; 348 | evse_status.presentVoltage = frame.data.byte[1] + 256 * frame.data.byte[2]; 349 | evse_status.presentCurrent = frame.data.byte[3]; 350 | evse_status.status = frame.data.byte[5]; 351 | if (frame.data.byte[6] < 0xFF) 352 | { 353 | evse_status.remainingChargeSeconds = frame.data.byte[6] * 10; 354 | } 355 | else 356 | { 357 | evse_status.remainingChargeSeconds = frame.data.byte[7] * 60; 358 | } 359 | 360 | if (chademoState == RUNNING && bDoMismatchChecks) 361 | { 362 | if (abs(Voltage - evse_status.presentVoltage) > (evse_status.presentVoltage >> 3) && !carStatus.voltDeviation) 363 | { 364 | vMismatchCount++; 365 | if (vMismatchCount > 4) 366 | { 367 | Serial.print(F("Voltage mismatch! Aborting! Reported: ")); 368 | Serial.print(evse_status.presentVoltage); 369 | Serial.print(F(" Measured: ")); 370 | Serial.println(Voltage); 371 | carStatus.voltDeviation = 1; 372 | chademoState = CEASE_CURRENT; 373 | } 374 | } 375 | else vMismatchCount = 0; 376 | 377 | tempCurrVal = evse_status.presentCurrent >> 3; 378 | if (tempCurrVal < 3) tempCurrVal = 3; 379 | if (abs((Current * -1.0) - evse_status.presentCurrent) > tempCurrVal && !carStatus.currDeviation) 380 | { 381 | cMismatchCount++; 382 | if (cMismatchCount > 4) 383 | { 384 | Serial.print(F("Current mismatch! Aborting! Reported: ")); 385 | Serial.print(evse_status.presentCurrent); 386 | Serial.print(F(" Measured: ")); 387 | Serial.println(Current * -1.0); 388 | carStatus.currDeviation = 1; 389 | chademoState = CEASE_CURRENT; 390 | } 391 | } 392 | else cMismatchCount = 0; 393 | } 394 | 395 | if (settings.debuggingLevel > 1) 396 | { 397 | Serial.print(F("EVSE: Measured Voltage: ")); 398 | Serial.print(evse_status.presentVoltage); 399 | Serial.print(F(" Current: ")); 400 | Serial.print(evse_status.presentCurrent); 401 | Serial.print(F(" Car measured current: ")); 402 | Serial.print(Current); 403 | Serial.print(F(" Time remaining: ")); 404 | Serial.print(evse_status.remainingChargeSeconds); 405 | Serial.print(F(" Status: ")); 406 | Serial.print(evse_status.status, BIN); 407 | timestamp(); 408 | } 409 | 410 | //on fault try to turn off current immediately and cease operation 411 | if ((evse_status.status & 0x1A) != 0) //if bits 1, 3, or 4 are set then we have a problem. 412 | { 413 | faultCount++; 414 | if (faultCount > 3) 415 | { 416 | Serial.print(F("EVSE:fault code ")); 417 | Serial.print(evse_status.status); 418 | Serial.println(F(" Abort.")); 419 | if (chademoState == RUNNING) chademoState = CEASE_CURRENT; 420 | } 421 | } 422 | else faultCount = 0; 423 | 424 | if (chademoState == RUNNING) 425 | { 426 | if (bListenEVSEStatus) 427 | { 428 | if ((evse_status.status & EVSE_STATUS_STOPPED) != 0) 429 | { 430 | Serial.println(F("EVSE:stop charging.")); 431 | chademoState = CEASE_CURRENT; 432 | } 433 | 434 | //if there is no remaining time then gracefully shut down 435 | if (evse_status.remainingChargeSeconds == 0) 436 | { 437 | Serial.println(F("EVSE:time elapsed..Ending")); 438 | chademoState = CEASE_CURRENT; 439 | } 440 | } 441 | else 442 | { 443 | //if charger is not reporting being stopped and is reporting remaining time then enable the checks. 444 | if ((evse_status.status & EVSE_STATUS_STOPPED) == 0 && evse_status.remainingChargeSeconds > 0) bListenEVSEStatus = 1; 445 | } 446 | } 447 | } 448 | if (frame.id == BMS_ID) { 449 | lastBMSTime = millis(); 450 | bms_status.status = frame.data.byte[0]; 451 | if ((bms_status.status & 0x07) != 0) { 452 | if ((bms_status.status & BMS_STATUS_HVC) != 0) { 453 | carStatus.battOverVolt = 1; 454 | chademoState = CEASE_CURRENT; 455 | } 456 | if((bms_status.status & BMS_STATUS_BVC) != 0){ 457 | Serial.println(F("Reached balance threshold")); 458 | } 459 | } 460 | } 461 | checkRAM(); 462 | } 463 | 464 | void CHADEMO::sendCANBattSpecs() 465 | { 466 | CAN_FRAME outFrame; 467 | outFrame.id = CARSIDE_BATT_ID; 468 | outFrame.length = 8; 469 | outFrame.rtr = 0; 470 | outFrame.priority = 2; 471 | outFrame.extended = 0; 472 | 473 | outFrame.data.byte[0] = 0x00; // Not Used 474 | outFrame.data.byte[1] = 0x00; // Not Used 475 | outFrame.data.byte[2] = 0x00; // Not Used 476 | outFrame.data.byte[3] = 0x00; // Not Used 477 | outFrame.data.byte[4] = lowByte(settings.maxChargeVoltage); 478 | outFrame.data.byte[5] = highByte(settings.maxChargeVoltage); 479 | outFrame.data.byte[6] = (uint8_t)settings.packSizeKWH; 480 | outFrame.data.byte[7] = 0; //not used 481 | //CAN.EnqueueTX(outFrame); 482 | CAN.sendFrame(outFrame); 483 | if (settings.debuggingLevel > 1) 484 | { 485 | Serial.print(F("CAR: Absolute MAX Voltage:")); 486 | Serial.print(settings.maxChargeVoltage); 487 | Serial.print(F(" Pack size: ")); 488 | Serial.print(settings.packSizeKWH); 489 | timestamp(); 490 | } 491 | checkRAM(); 492 | } 493 | 494 | void CHADEMO::sendCANChargingTime() 495 | { 496 | CAN_FRAME outFrame; 497 | outFrame.id = CARSIDE_CHARGETIME_ID; 498 | outFrame.rtr = 0; 499 | outFrame.priority = 2; 500 | outFrame.extended = 0; 501 | outFrame.length = 8; 502 | 503 | outFrame.data.byte[0] = 0x00; // Not Used 504 | outFrame.data.byte[1] = 0xFF; //not using 10 second increment mode 505 | outFrame.data.byte[2] = 90; //ask for how long of a charge? It will be forceably stopped if we hit this time 506 | outFrame.data.byte[3] = 60; //how long we think the charge will actually take 507 | outFrame.data.byte[4] = 0; //not used 508 | outFrame.data.byte[5] = 0; //not used 509 | outFrame.data.byte[6] = 0; //not used 510 | outFrame.data.byte[7] = 0; //not used 511 | //CAN.EnqueueTX(outFrame); 512 | CAN.sendFrame(outFrame); 513 | checkRAM(); 514 | } 515 | 516 | void CHADEMO::sendCANStatus() 517 | { 518 | uint8_t faults = 0; 519 | uint8_t status = 0; 520 | CAN_FRAME outFrame; 521 | outFrame.id = CARSIDE_CONTROL_ID; 522 | outFrame.length = 8; 523 | outFrame.rtr = 0; 524 | outFrame.priority = 2; 525 | outFrame.extended = 0; 526 | 527 | if (carStatus.battOverTemp) faults |= CARSIDE_FAULT_OVERT; 528 | if (carStatus.battOverVolt) faults |= CARSIDE_FAULT_OVERV; 529 | if (carStatus.battUnderVolt) faults |= CARSIDE_FAULT_UNDERV; 530 | if (carStatus.currDeviation) faults |= CARSIDE_FAULT_CURR; 531 | if (carStatus.voltDeviation) faults |= CARSIDE_FAULT_VOLTM; 532 | 533 | if (carStatus.chargingEnabled) status |= CARSIDE_STATUS_CHARGE; 534 | if (carStatus.notParked) status |= CARSIDE_STATUS_NOTPARK; 535 | if (carStatus.chargingFault) status |= CARSIDE_STATUS_MALFUN; 536 | if (bChademo10Protocol) 537 | { 538 | if (carStatus.contactorOpen) status |= CARSIDE_STATUS_CONTOP; 539 | if (carStatus.stopRequest) status |= CARSIDE_STATUS_CHSTOP; 540 | } 541 | 542 | if (bChademo10Protocol) outFrame.data.byte[0] = 2; //tell EVSE we are talking 1.0 protocol 543 | else outFrame.data.byte[0] = 1; //talking 0.9 protocol 544 | outFrame.data.byte[1] = lowByte(carStatus.targetVoltage); 545 | outFrame.data.byte[2] = highByte(carStatus.targetVoltage); 546 | outFrame.data.byte[3] = askingAmps; 547 | outFrame.data.byte[4] = faults; 548 | outFrame.data.byte[5] = status; 549 | outFrame.data.byte[6] = (uint8_t)settings.kiloWattHours; 550 | outFrame.data.byte[7] = 0; //not used 551 | //CAN.EnqueueTX(outFrame); 552 | CAN.sendFrame(outFrame); 553 | 554 | if (settings.debuggingLevel > 1) 555 | { 556 | Serial.print(F("CAR: Protocol:")); 557 | Serial.print(outFrame.data.byte[0]); 558 | Serial.print(F(" Target Voltage: ")); 559 | Serial.print(carStatus.targetVoltage); 560 | Serial.print(F(" Current Command: ")); 561 | Serial.print(askingAmps); 562 | Serial.print(F(" Target Amps: ")); 563 | Serial.print(carStatus.targetCurrent); 564 | Serial.print(F(" Faults: ")); 565 | Serial.print(faults, BIN); 566 | Serial.print(F(" Status: ")); 567 | Serial.print(status, BIN); 568 | Serial.print(F(" kWh: ")); 569 | Serial.print(settings.kiloWattHours); 570 | timestamp(); 571 | } 572 | 573 | if (chademoState == RUNNING && askingAmps < carStatus.targetCurrent) 574 | { 575 | if (askingAmps == 0) askingAmps = 3; 576 | int offsetError = askingAmps - evse_status.presentCurrent; 577 | if ((offsetError <= 1) || (evse_status.presentCurrent == 0)) askingAmps++; 578 | } 579 | //not a typo. We're allowed to change requested amps by +/- 20A per second. We send the above frame every 100ms so a single 580 | //increment means we can ramp up 10A per second. But, we want to ramp down quickly if there is a problem so do two which 581 | //gives us -20A per second. 582 | if (chademoState != RUNNING && askingAmps > 0) askingAmps--; 583 | if (askingAmps > carStatus.targetCurrent) askingAmps--; 584 | if (askingAmps > carStatus.targetCurrent) askingAmps--; 585 | checkRAM(); 586 | } 587 | 588 | CHADEMO chademo; 589 | -------------------------------------------------------------------------------- /IJLD505/chademo.h: -------------------------------------------------------------------------------- 1 | #ifndef CHADEMO_H_ 2 | #define CHADEMO_H_ 3 | #include 4 | #include "globals.h" 5 | #include "mcp_can.h" 6 | 7 | enum CHADEMOSTATE 8 | { 9 | STARTUP, 10 | SEND_INITIAL_PARAMS, 11 | WAIT_FOR_EVSE_PARAMS, 12 | SET_CHARGE_BEGIN, 13 | WAIT_FOR_BEGIN_CONFIRMATION, 14 | CLOSE_CONTACTORS, 15 | RUNNING, 16 | CEASE_CURRENT, 17 | WAIT_FOR_ZERO_CURRENT, 18 | OPEN_CONTACTOR, 19 | FAULTED, 20 | STOPPED, 21 | LIMBO 22 | }; 23 | 24 | typedef struct 25 | { 26 | uint8_t supportWeldCheck; 27 | uint16_t availVoltage; 28 | uint8_t availCurrent; 29 | uint16_t thresholdVoltage; //evse calculates this. It is the voltage at which it'll abort charging to save the battery pack in case we asked for something stupid 30 | } EVSE_PARAMS; 31 | 32 | typedef struct 33 | { 34 | uint16_t presentVoltage; 35 | uint8_t presentCurrent; 36 | uint8_t status; 37 | uint16_t remainingChargeSeconds; 38 | } EVSE_STATUS; 39 | 40 | typedef struct 41 | { 42 | uint16_t targetVoltage; //what voltage we want the EVSE to put out 43 | uint8_t targetCurrent; //what current we'd like the EVSE to provide 44 | uint8_t remainingKWH; //report # of KWh in the battery pack (charge level) 45 | uint8_t battOverVolt : 1; //we signal that battery or a cell is too high of a voltage 46 | uint8_t battUnderVolt : 1; //we signal that battery is too low 47 | uint8_t currDeviation : 1; //we signal that measured current is not the same as EVSE is reporting 48 | uint8_t battOverTemp : 1; //we signal that battery is too hot 49 | uint8_t voltDeviation : 1; //we signal that we measure a different voltage than EVSE reports 50 | uint8_t chargingEnabled : 1; //ask EVSE to enable charging 51 | uint8_t notParked : 1; //advise EVSE that we're not in park. 52 | uint8_t chargingFault : 1; //signal EVSE that we found a fault 53 | uint8_t contactorOpen : 1; //tell EVSE whether we've closed the charging contactor 54 | uint8_t stopRequest : 1; //request that the charger cease operation before we really get going 55 | } CARSIDE_STATUS; 56 | typedef struct 57 | { 58 | uint8_t HVC : 1; 59 | uint8_t BVC : 1; 60 | uint8_t status; 61 | } BMS_STATUS; 62 | 63 | //The IDs for chademo comm - both carside and EVSE side so we know what to listen for 64 | //as well. 65 | #define CARSIDE_BATT_ID 0x100 66 | #define CARSIDE_CHARGETIME_ID 0x101 67 | #define CARSIDE_CONTROL_ID 0x102 68 | #define BMS_ID 0x50e 69 | 70 | #define EVSE_PARAMS_ID 0x108 71 | #define EVSE_STATUS_ID 0x109 72 | 73 | #define CARSIDE_FAULT_OVERV 1 //over voltage 74 | #define CARSIDE_FAULT_UNDERV 2 //Under voltage 75 | #define CARSIDE_FAULT_CURR 4 //current mismatch 76 | #define CARSIDE_FAULT_OVERT 8 //over temperature 77 | #define CARSIDE_FAULT_VOLTM 16 //voltage mismatch 78 | 79 | #define CARSIDE_STATUS_CHARGE 1 //charging enabled 80 | #define CARSIDE_STATUS_NOTPARK 2 //shifter not in safe state 81 | #define CARSIDE_STATUS_MALFUN 4 //vehicle did something dumb 82 | #define CARSIDE_STATUS_CONTOP 8 //main contactor open 83 | #define CARSIDE_STATUS_CHSTOP 16 //charger stop before even charging 84 | 85 | #define EVSE_STATUS_CHARGE 1 //charger is active 86 | #define EVSE_STATUS_ERR 2 //something went wrong 87 | #define EVSE_STATUS_CONNLOCK 4 //connector is currently locked 88 | #define EVSE_STATUS_INCOMPAT 8 //parameters between vehicle and charger not compatible 89 | #define EVSE_STATUS_BATTERR 16 //something wrong with battery?! 90 | #define EVSE_STATUS_STOPPED 32 //charger is stopped 91 | 92 | #define BMS_STATUS_HVC 1 93 | #define BMS_STATUS_LVC 2 94 | #define BMS_STATUS_BVC 4 95 | 96 | class CHADEMO 97 | { 98 | public: 99 | CHADEMO(); 100 | void setDelayedState(int newstate, uint16_t delayTime); 101 | CHADEMOSTATE getState(); 102 | void setTargetAmperage(uint8_t t_amp); 103 | void setTargetVoltage(uint16_t t_volt); 104 | void loop(); 105 | void doProcessing(); 106 | void handleCANFrame(CAN_FRAME &frame); 107 | void setChargingFault(); 108 | void setBattOverTemp(); 109 | 110 | //these need to be accessed quickly in tight spots so they're public in an attempt at efficiency 111 | uint8_t bChademoMode; //accessed but not modified in ISR so it should be OK non-volatile 112 | uint8_t bChademoSendRequests; //should we be sending periodic status updates? 113 | volatile uint8_t bChademoRequest; //is it time to send one of those updates? 114 | 115 | protected: 116 | private: 117 | uint8_t bStartedCharge; //we have started a charge since the plug was inserted. Prevents attempts to restart charging if it stopped previously 118 | uint8_t bChademo10Protocol; //can we use 1.0 protocol? 119 | //target values are what we send with periodic frames and can be changed. 120 | uint8_t askingAmps; //how many amps to ask for. Trends toward targetAmperage 121 | uint8_t bListenEVSEStatus; //should we pay attention to stop requests and such yet? 122 | uint8_t bDoMismatchChecks; //should we be checking for voltage and current mismatches? 123 | uint8_t vMismatchCount; //count # of consecutive voltage mismatches. Don't trigger until we get enough 124 | uint8_t cMismatchCount; //same but for current 125 | uint8_t vCapCount; //# of EVSE voltage capacity checks that have failed in a row. 126 | uint8_t vOverFault; //over volt fault counter like above. 127 | uint8_t faultCount; //force faults to count up a bit before we actually fault. 128 | uint32_t mismatchStart; 129 | const uint16_t mismatchDelay = 10000; //don't start mismatch checks for 10 seconds 130 | uint32_t stateMilli; 131 | uint16_t stateDelay; 132 | uint32_t insertionTime; 133 | uint32_t lastCommTime; 134 | uint32_t lastBMSTime; 135 | const uint16_t lastBMSTimeout = 2000; 136 | const uint16_t lastCommTimeout = 1000; //allow up to 1 second of comm fault before getting angry 137 | 138 | CHADEMOSTATE chademoState; 139 | CHADEMOSTATE stateHolder; 140 | EVSE_PARAMS evse_params; 141 | EVSE_STATUS evse_status; 142 | CARSIDE_STATUS carStatus; 143 | BMS_STATUS bms_status; 144 | 145 | void sendCANStatus(); 146 | void sendCANBattSpecs(); 147 | void sendCANChargingTime(); 148 | }; 149 | 150 | extern CHADEMO chademo; 151 | 152 | #endif 153 | -------------------------------------------------------------------------------- /IJLD505/globals.h: -------------------------------------------------------------------------------- 1 | #ifndef GLOBAL_H_ 2 | #define GLOBAL_H_ 3 | 4 | //set the proper digital pins for these 5 | #define IN0 4 6 | #define IN1 7 7 | #define OUT0 5 8 | #define OUT1 6 9 | 10 | #define SENSOR 8 11 | 12 | typedef struct 13 | { 14 | uint8_t valid; //a token to store EEPROM version and validity. If it matches expected value then EEPROM is not reset to defaults //0 15 | float ampHours; //floats are 4 bytes //1 16 | float kiloWattHours; //5 17 | float packSizeKWH; //9 18 | float voltageCalibration; //13 19 | float currentCalibration; //17 20 | uint16_t maxChargeVoltage; //21 21 | uint16_t targetChargeVoltage; //23 22 | uint8_t maxChargeAmperage; //25 23 | uint8_t minChargeAmperage; //26 24 | uint8_t capacity; //27 25 | uint8_t SOC; //28 26 | uint8_t debuggingLevel; //29 27 | uint16_t resetVoltage;//30 28 | uint8_t maxChargeTemp;//31 29 | } EESettings; 30 | 31 | extern EESettings settings; 32 | extern float Voltage; 33 | extern float Current; 34 | extern unsigned long CurrentMillis; 35 | extern int Count; 36 | 37 | #endif 38 | -------------------------------------------------------------------------------- /LLTBMS07.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | uint32_t cellVoltage[16][12]; 4 | uint16_t temp[2][12]; 5 | uint8_t dataIn[39]; 6 | uint8_t BMSCommand[7] = {0xDD, 0xA5, 0x04, 0x00, 0xFF, 0xFC, 0x77}; 7 | uint8_t BMSCommand2[7] = {0xDD, 0xA5, 0x03, 0x00, 0xFF, 0xFD, 0x77}; 8 | uint8_t muxChannel = 0; 9 | uint8_t muxPins[4] = {23, 22, 21, 20}; 10 | 11 | uint8_t packetNum = 0; 12 | uint8_t moduleOutIndex = 0; 13 | 14 | uint8_t moduleStatus[12]; 15 | uint16_t moduleMaxVoltage[12]; 16 | uint16_t moduleMinVoltage[12]; 17 | uint32_t moduleTotalVoltage[12]; 18 | 19 | uint32_t maxVoltage = 0; 20 | uint32_t minVoltage = 0; 21 | 22 | uint32_t aveVoltage = 0; 23 | 24 | uint32_t totalVoltage = 0; 25 | 26 | uint32_t minVoltageLim = 3300; 27 | uint32_t maxVoltageLim = 4200; 28 | uint32_t balVoltageLim = 4150; 29 | uint32_t tempLimit = 50; 30 | 31 | uint32_t interval = 200; 32 | uint32_t prevTime; 33 | 34 | uint32_t printInt = 5700; 35 | uint32_t prevPrint; 36 | 37 | uint32_t CANInt = 500; 38 | uint32_t prevCAN; 39 | 40 | bool bmsConnected[12]; 41 | uint8_t moduleCount; 42 | 43 | bool HVC; 44 | bool LVC; 45 | bool BVC; 46 | bool TMPC; 47 | 48 | /*Module status frames - max and min voltages and total voltage 49 | Address is 0x700+module ID 50 | Bytes 0,1,2,3 are MSB and LSB of max and min voltage 51 | 4,5,6 are MSB, middle and LSB of total voltage 52 | 7 is empty for now 53 | */ 54 | 55 | void setup() { 56 | // put your setup code here, to run once: 57 | Serial2.begin(9600); 58 | Serial.begin(115200); 59 | Serial.println("LLTBMS06-Teensy"); 60 | for (int i = 0; i < 4; i++) { 61 | pinMode(muxPins[i], OUTPUT); 62 | } 63 | pinMode(28, OUTPUT);//enable pin; this is specific to the CAN adapter I used 64 | digitalWrite(28, LOW);//it needs a strong pull to ground to enable transmissions 65 | Can0.begin(500000, 1, 1);//500K, alternate pins 66 | } 67 | 68 | void loop() { 69 | unsigned long timeNow = millis(); 70 | if (timeNow - prevTime > interval) { 71 | for (int i = 0; i < 12; i++) { 72 | setChannel(i); 73 | sendRecvData(); 74 | //getTempData(); 75 | } 76 | prevTime = millis(); 77 | } 78 | timeNow = millis(); 79 | if (timeNow - prevPrint > printInt) { 80 | Serial.println("MIN AVE MAX TOTAL"); 81 | Serial.print(minVoltage); 82 | Serial.print(" "); 83 | Serial.print(aveVoltage); 84 | Serial.print(" "); 85 | Serial.print(maxVoltage); 86 | Serial.print(" "); 87 | Serial.println(totalVoltage); 88 | for (int i = 0; i < 12; i++) { 89 | Serial.print(bmsConnected[i]); 90 | } 91 | Serial.println(); 92 | Serial.print(HVC); 93 | Serial.print(LVC); 94 | Serial.print(BVC); 95 | Serial.println(TMPC); 96 | calcMinMax(); 97 | prevPrint = millis(); 98 | } 99 | timeNow = millis(); 100 | if (timeNow - prevCAN > CANInt) { 101 | sendBMSData(); 102 | prevCAN = millis(); 103 | } 104 | } 105 | 106 | void sendRecvData() { 107 | for (int i = 0; i < 7; i++) { 108 | Serial2.write(BMSCommand[i]); 109 | } 110 | Serial2.flush(); 111 | delay(50); 112 | if (Serial2.peek() == 0xDD) { 113 | for (int i = 0; i < 39; i++) { 114 | dataIn[i] = Serial2.read(); 115 | for (int i = 0; i < 16; i++) { 116 | cellVoltage[i][muxChannel] = (dataIn[(i * 2) + 4] * 256) + (dataIn[(i * 2) + 5]); 117 | } 118 | } 119 | } 120 | while (Serial2.available()) { 121 | Serial2.read(); 122 | } 123 | } 124 | 125 | void getTempData() { 126 | for (int i = 0; i < 7; i++) { 127 | Serial2.write(BMSCommand2[i]); 128 | } 129 | Serial2.flush(); 130 | delay(50); 131 | if (Serial2.peek() == 0xDD) { 132 | for (int i = 0; i < 34; i++) { 133 | dataIn[i] = Serial2.read(); 134 | //Serial.print(" 0x"); 135 | //Serial.print(dataIn[i], HEX); 136 | } 137 | //Serial.println(); 138 | } 139 | temp[0][muxChannel] = ((dataIn[27] * 256) + dataIn[28] - 2731) / 10; 140 | temp[1][muxChannel] = ((dataIn[29] * 256) + dataIn[30] - 2731) / 10; 141 | while (Serial2.available()) { 142 | Serial2.read(); 143 | } 144 | } 145 | 146 | void setChannel(uint8_t chan) { 147 | muxChannel = chan; 148 | for (int i = 0; i < 4; i++) { 149 | digitalWrite(muxPins[i], bitRead(chan, 3 - i)); 150 | } 151 | } 152 | void calcMinMax() { 153 | HVC = 0; 154 | LVC = 0; 155 | BVC = 0; 156 | 157 | minVoltage = 5000;//impossible value 158 | maxVoltage = 1000;//impossible value 159 | totalVoltage = 0; 160 | aveVoltage = 0; 161 | moduleCount = 0; 162 | for (int i = 0; i < 12; i++) { 163 | bmsConnected[i] = 0; 164 | for (int j = 0; j < 16; j++) { 165 | if (cellVoltage[j][i] > 1000) { 166 | bmsConnected[i] = 1; 167 | totalVoltage += cellVoltage[j][i]; 168 | if (cellVoltage[j][i] > maxVoltage) { 169 | maxVoltage = cellVoltage[j][i]; 170 | } 171 | if (cellVoltage[j][i] < minVoltage) { 172 | minVoltage = cellVoltage[j][i]; 173 | } 174 | } 175 | } 176 | if (bmsConnected[i]) { 177 | moduleCount++; 178 | } 179 | calcModuleInfo(i); 180 | } 181 | aveVoltage = totalVoltage / (moduleCount * 16);//16 cells per module, 2 systems in parallel. 182 | totalVoltage = totalVoltage / 2; 183 | if (maxVoltage >= maxVoltageLim) { 184 | HVC = 1; 185 | } 186 | if (minVoltage <= minVoltageLim) { 187 | LVC = 1; 188 | } 189 | if (maxVoltage >= balVoltageLim) { 190 | BVC = 1; 191 | } 192 | TMPC = 0; 193 | for (int i = 1; i < 12; i++) { 194 | if ((temp[1][i] >= tempLimit) || (temp[0][i] >= tempLimit)) { 195 | TMPC = 1; 196 | Serial.println(temp[1][i]); 197 | Serial.println(temp[0][i]); 198 | Serial.println(i); 199 | } 200 | } 201 | for (int i = 0; i < 12; i++) { 202 | for (int j = 0; j < 16; j++) { 203 | cellVoltage[j][i] = 0; 204 | } 205 | } 206 | } 207 | void sendBMSData() { 208 | if (moduleCount < 12) { 209 | TMPC = 1; 210 | } 211 | if (packetNum < 6) { 212 | packetNum++; 213 | TMPC = 0; 214 | BVC = 0; 215 | LVC = 0; 216 | HVC = 0; 217 | } 218 | static CAN_message_t BMSFrame; 219 | BMSFrame.ext = 0; 220 | BMSFrame.id = 0x50f; 221 | BMSFrame.len = 5; 222 | BMSFrame.buf[0] = 0x00;//status; 1 = hvc, 2 = lvc, 4 = bvc 223 | BMSFrame.buf[1] = 0x00;//0 224 | BMSFrame.buf[2] = 0x00;//fault; 4 = overtemp/undertemp 225 | BMSFrame.buf[3] = 0x00;//0 226 | BMSFrame.buf[4] = 0x00;//0 227 | if (HVC) { 228 | bitSet(BMSFrame.buf[0], 0); 229 | } 230 | if (LVC) { 231 | bitSet(BMSFrame.buf[0], 1); 232 | } 233 | if (BVC) { 234 | bitSet(BMSFrame.buf[0], 2); 235 | } 236 | if (TMPC) { 237 | bitSet(BMSFrame.buf[2], 2);//therm overtemp 238 | } 239 | 240 | static CAN_message_t EVCCFrame; 241 | EVCCFrame.ext = 1; 242 | EVCCFrame.id = 0x01dd0001; 243 | EVCCFrame.len = 5; 244 | EVCCFrame.buf[0] = 0x00;//status; 1 = hvc, 2 = lvc, 4 = bvc 245 | EVCCFrame.buf[1] = 0x00;//0 246 | EVCCFrame.buf[2] = 0x00;//fault; 4 = overtemp/undertemp 247 | EVCCFrame.buf[3] = 0x00;//0 248 | EVCCFrame.buf[4] = 0x00;//0 249 | if (HVC) { 250 | bitSet(EVCCFrame.buf[0], 0); 251 | } 252 | if (LVC) { 253 | bitSet(EVCCFrame.buf[0], 1); 254 | } 255 | if (BVC) { 256 | bitSet(EVCCFrame.buf[0], 2); 257 | } 258 | if (TMPC) { 259 | bitSet(EVCCFrame.buf[2], 2);//therm overtemp 260 | } 261 | 262 | uint32_t roundVolts = totalVoltage / 1000; 263 | static CAN_message_t infoFrame; 264 | infoFrame.ext = 0; 265 | infoFrame.id = 0x50e; 266 | infoFrame.len = 8; 267 | infoFrame.buf[0] = 0x00;//status; 1 = hvc, 2 = lvc, 4 = bvc 268 | infoFrame.buf[1] = 0x00;//fault; 1 = overtemp/undertemp 269 | infoFrame.buf[2] = lowByte(roundVolts);//voltage LSB 270 | infoFrame.buf[3] = highByte(roundVolts);//voltage MSB 271 | infoFrame.buf[4] = lowByte(maxVoltage); 272 | infoFrame.buf[5] = highByte(maxVoltage); 273 | infoFrame.buf[6] = lowByte(minVoltage); 274 | infoFrame.buf[7] = highByte(minVoltage); 275 | 276 | if (HVC) { 277 | bitSet(infoFrame.buf[0], 0); 278 | } 279 | if (LVC) { 280 | bitSet(infoFrame.buf[0], 1); 281 | } 282 | if (BVC) { 283 | bitSet(infoFrame.buf[0], 2); 284 | } 285 | 286 | if (TMPC) { 287 | bitSet(infoFrame.buf[0], 3);//therm overtemp 288 | } 289 | Can0.write(BMSFrame); 290 | delay(20); 291 | Can0.write(EVCCFrame); 292 | delay(20); 293 | Can0.write(infoFrame); 294 | //send the module frames 295 | sendModuleInfo(moduleOutIndex); 296 | moduleOutIndex++; 297 | sendModuleInfo(moduleOutIndex); 298 | if (moduleOutIndex == 11) { 299 | moduleOutIndex = 0; 300 | } 301 | else { 302 | moduleOutIndex++; 303 | } 304 | } 305 | 306 | void calcModuleInfo(int sendIndex) { 307 | //first, calculate the module information. 308 | moduleTotalVoltage[sendIndex] = 0; 309 | moduleMinVoltage[sendIndex] = 5000; 310 | moduleMaxVoltage[sendIndex] = 1000; 311 | for (int j = 0; j < 16; j++) { 312 | if (cellVoltage[j][sendIndex] > 1000) { 313 | moduleTotalVoltage[sendIndex] += cellVoltage[j][sendIndex]; 314 | if (cellVoltage[j][sendIndex] > moduleMaxVoltage[sendIndex]) { 315 | moduleMaxVoltage[sendIndex] = cellVoltage[j][sendIndex]; 316 | } 317 | if (cellVoltage[j][sendIndex] < moduleMinVoltage[sendIndex]) { 318 | moduleMinVoltage[sendIndex] = cellVoltage[j][sendIndex]; 319 | } 320 | } 321 | } 322 | Serial.println(sendIndex); 323 | } 324 | void sendModuleInfo(int sendIndex) { 325 | static CAN_message_t moduleFrame; 326 | moduleFrame.ext = 0; 327 | moduleFrame.id = 1792 + sendIndex; 328 | moduleFrame.len = 8; 329 | moduleFrame.buf[0] = lowByte(moduleMinVoltage[sendIndex]); 330 | moduleFrame.buf[1] = highByte(moduleMinVoltage[sendIndex]); 331 | moduleFrame.buf[2] = lowByte(moduleMaxVoltage[sendIndex]); 332 | moduleFrame.buf[3] = highByte(moduleMaxVoltage[sendIndex]); 333 | moduleFrame.buf[4] = moduleTotalVoltage[sendIndex]; 334 | moduleFrame.buf[5] = moduleTotalVoltage[sendIndex] >> 8; 335 | moduleFrame.buf[6] = moduleTotalVoltage[sendIndex] >> 16; 336 | moduleFrame.buf[7] = 0x00; 337 | Can0.write(moduleFrame); 338 | } 339 | -------------------------------------------------------------------------------- /LLTBMS07/LLTBMS07.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | uint32_t cellVoltage[16][12]; 4 | uint16_t temp[2][12]; 5 | uint8_t dataIn[39]; 6 | uint8_t BMSCommand[7] = {0xDD, 0xA5, 0x04, 0x00, 0xFF, 0xFC, 0x77}; 7 | uint8_t BMSCommand2[7] = {0xDD, 0xA5, 0x03, 0x00, 0xFF, 0xFD, 0x77}; 8 | uint8_t muxChannel = 0; 9 | uint8_t muxPins[4] = {23, 22, 21, 20}; 10 | 11 | uint8_t packetNum = 0; 12 | uint8_t moduleOutIndex = 0; 13 | 14 | uint8_t moduleStatus[12]; 15 | uint16_t moduleMaxVoltage[12]; 16 | uint16_t moduleMinVoltage[12]; 17 | uint32_t moduleTotalVoltage[12]; 18 | 19 | uint32_t maxVoltage = 0; 20 | uint32_t minVoltage = 0; 21 | 22 | uint32_t aveVoltage = 0; 23 | 24 | uint32_t totalVoltage = 0; 25 | 26 | uint32_t minVoltageLim = 3300; 27 | uint32_t maxVoltageLim = 4200; 28 | uint32_t balVoltageLim = 4150; 29 | uint32_t tempLimit = 50; 30 | 31 | uint32_t interval = 200; 32 | uint32_t prevTime; 33 | 34 | uint32_t printInt = 5700; 35 | uint32_t prevPrint; 36 | 37 | uint32_t CANInt = 500; 38 | uint32_t prevCAN; 39 | 40 | bool bmsConnected[12]; 41 | uint8_t moduleCount; 42 | 43 | bool HVC; 44 | bool LVC; 45 | bool BVC; 46 | bool TMPC; 47 | 48 | /*Module status frames - max and min voltages and total voltage 49 | Address is 0x700+module ID 50 | Bytes 0,1,2,3 are MSB and LSB of max and min voltage 51 | 4,5,6 are MSB, middle and LSB of total voltage 52 | 7 is empty for now 53 | */ 54 | 55 | void setup() { 56 | // put your setup code here, to run once: 57 | Serial2.begin(9600); 58 | Serial.begin(115200); 59 | Serial.println("LLTBMS06-Teensy"); 60 | for (int i = 0; i < 4; i++) { 61 | pinMode(muxPins[i], OUTPUT); 62 | } 63 | pinMode(28, OUTPUT);//enable pin; this is specific to the CAN adapter I used 64 | digitalWrite(28, LOW);//it needs a strong pull to ground to enable transmissions 65 | Can0.begin(500000, 1, 1);//500K, alternate pins 66 | } 67 | 68 | void loop() { 69 | unsigned long timeNow = millis(); 70 | if (timeNow - prevTime > interval) { 71 | for (int i = 0; i < 12; i++) { 72 | setChannel(i); 73 | sendRecvData(); 74 | //getTempData(); 75 | } 76 | prevTime = millis(); 77 | } 78 | timeNow = millis(); 79 | if (timeNow - prevPrint > printInt) { 80 | Serial.println("MIN AVE MAX TOTAL"); 81 | Serial.print(minVoltage); 82 | Serial.print(" "); 83 | Serial.print(aveVoltage); 84 | Serial.print(" "); 85 | Serial.print(maxVoltage); 86 | Serial.print(" "); 87 | Serial.println(totalVoltage); 88 | for (int i = 0; i < 12; i++) { 89 | Serial.print(bmsConnected[i]); 90 | } 91 | Serial.println(); 92 | Serial.print(HVC); 93 | Serial.print(LVC); 94 | Serial.print(BVC); 95 | Serial.println(TMPC); 96 | calcMinMax(); 97 | prevPrint = millis(); 98 | } 99 | timeNow = millis(); 100 | if (timeNow - prevCAN > CANInt) { 101 | sendBMSData(); 102 | prevCAN = millis(); 103 | } 104 | } 105 | 106 | void sendRecvData() { 107 | for (int i = 0; i < 7; i++) { 108 | Serial2.write(BMSCommand[i]); 109 | } 110 | Serial2.flush(); 111 | delay(50); 112 | if (Serial2.peek() == 0xDD) { 113 | for (int i = 0; i < 39; i++) { 114 | dataIn[i] = Serial2.read(); 115 | for (int i = 0; i < 16; i++) { 116 | cellVoltage[i][muxChannel] = (dataIn[(i * 2) + 4] * 256) + (dataIn[(i * 2) + 5]); 117 | } 118 | } 119 | } 120 | while (Serial2.available()) { 121 | Serial2.read(); 122 | } 123 | } 124 | 125 | void getTempData() { 126 | for (int i = 0; i < 7; i++) { 127 | Serial2.write(BMSCommand2[i]); 128 | } 129 | Serial2.flush(); 130 | delay(50); 131 | if (Serial2.peek() == 0xDD) { 132 | for (int i = 0; i < 34; i++) { 133 | dataIn[i] = Serial2.read(); 134 | //Serial.print(" 0x"); 135 | //Serial.print(dataIn[i], HEX); 136 | } 137 | //Serial.println(); 138 | } 139 | temp[0][muxChannel] = ((dataIn[27] * 256) + dataIn[28] - 2731) / 10; 140 | temp[1][muxChannel] = ((dataIn[29] * 256) + dataIn[30] - 2731) / 10; 141 | while (Serial2.available()) { 142 | Serial2.read(); 143 | } 144 | } 145 | 146 | void setChannel(uint8_t chan) { 147 | muxChannel = chan; 148 | for (int i = 0; i < 4; i++) { 149 | digitalWrite(muxPins[i], bitRead(chan, 3 - i)); 150 | } 151 | } 152 | void calcMinMax() { 153 | HVC = 0; 154 | LVC = 0; 155 | BVC = 0; 156 | 157 | minVoltage = 5000;//impossible value 158 | maxVoltage = 1000;//impossible value 159 | totalVoltage = 0; 160 | aveVoltage = 0; 161 | moduleCount = 0; 162 | for (int i = 0; i < 12; i++) { 163 | bmsConnected[i] = 0; 164 | for (int j = 0; j < 16; j++) { 165 | if (cellVoltage[j][i] > 1000) { 166 | bmsConnected[i] = 1; 167 | totalVoltage += cellVoltage[j][i]; 168 | if (cellVoltage[j][i] > maxVoltage) { 169 | maxVoltage = cellVoltage[j][i]; 170 | } 171 | if (cellVoltage[j][i] < minVoltage) { 172 | minVoltage = cellVoltage[j][i]; 173 | } 174 | } 175 | } 176 | if (bmsConnected[i]) { 177 | moduleCount++; 178 | } 179 | calcModuleInfo(i); 180 | } 181 | aveVoltage = totalVoltage / (moduleCount * 16);//16 cells per module, 2 systems in parallel. 182 | totalVoltage = totalVoltage / 2; 183 | if (maxVoltage >= maxVoltageLim) { 184 | HVC = 1; 185 | } 186 | if (minVoltage <= minVoltageLim) { 187 | LVC = 1; 188 | } 189 | if (maxVoltage >= balVoltageLim) { 190 | BVC = 1; 191 | } 192 | TMPC = 0; 193 | for (int i = 1; i < 12; i++) { 194 | if ((temp[1][i] >= tempLimit) || (temp[0][i] >= tempLimit)) { 195 | TMPC = 1; 196 | Serial.println(temp[1][i]); 197 | Serial.println(temp[0][i]); 198 | Serial.println(i); 199 | } 200 | } 201 | for (int i = 0; i < 12; i++) { 202 | for (int j = 0; j < 16; j++) { 203 | cellVoltage[j][i] = 0; 204 | } 205 | } 206 | } 207 | void sendBMSData() { 208 | if (moduleCount < 12) { 209 | TMPC = 1; 210 | } 211 | if (packetNum < 6) { 212 | packetNum++; 213 | TMPC = 0; 214 | BVC = 0; 215 | LVC = 0; 216 | HVC = 0; 217 | } 218 | static CAN_message_t BMSFrame; 219 | BMSFrame.ext = 0; 220 | BMSFrame.id = 0x50f; 221 | BMSFrame.len = 5; 222 | BMSFrame.buf[0] = 0x00;//status; 1 = hvc, 2 = lvc, 4 = bvc 223 | BMSFrame.buf[1] = 0x00;//0 224 | BMSFrame.buf[2] = 0x00;//fault; 4 = overtemp/undertemp 225 | BMSFrame.buf[3] = 0x00;//0 226 | BMSFrame.buf[4] = 0x00;//0 227 | if (HVC) { 228 | bitSet(BMSFrame.buf[0], 0); 229 | } 230 | if (LVC) { 231 | bitSet(BMSFrame.buf[0], 1); 232 | } 233 | if (BVC) { 234 | bitSet(BMSFrame.buf[0], 2); 235 | } 236 | if (TMPC) { 237 | bitSet(BMSFrame.buf[2], 2);//therm overtemp 238 | } 239 | 240 | static CAN_message_t EVCCFrame; 241 | EVCCFrame.ext = 1; 242 | EVCCFrame.id = 0x01dd0001; 243 | EVCCFrame.len = 5; 244 | EVCCFrame.buf[0] = 0x00;//status; 1 = hvc, 2 = lvc, 4 = bvc 245 | EVCCFrame.buf[1] = 0x00;//0 246 | EVCCFrame.buf[2] = 0x00;//fault; 4 = overtemp/undertemp 247 | EVCCFrame.buf[3] = 0x00;//0 248 | EVCCFrame.buf[4] = 0x00;//0 249 | if (HVC) { 250 | bitSet(EVCCFrame.buf[0], 0); 251 | } 252 | if (LVC) { 253 | bitSet(EVCCFrame.buf[0], 1); 254 | } 255 | if (BVC) { 256 | bitSet(EVCCFrame.buf[0], 2); 257 | } 258 | if (TMPC) { 259 | bitSet(EVCCFrame.buf[2], 2);//therm overtemp 260 | } 261 | 262 | uint32_t roundVolts = totalVoltage / 1000; 263 | static CAN_message_t infoFrame; 264 | infoFrame.ext = 0; 265 | infoFrame.id = 0x50e; 266 | infoFrame.len = 8; 267 | infoFrame.buf[0] = 0x00;//status; 1 = hvc, 2 = lvc, 4 = bvc 268 | infoFrame.buf[1] = 0x00;//fault; 1 = overtemp/undertemp 269 | infoFrame.buf[2] = lowByte(roundVolts);//voltage LSB 270 | infoFrame.buf[3] = highByte(roundVolts);//voltage MSB 271 | infoFrame.buf[4] = lowByte(maxVoltage); 272 | infoFrame.buf[5] = highByte(maxVoltage); 273 | infoFrame.buf[6] = lowByte(minVoltage); 274 | infoFrame.buf[7] = highByte(minVoltage); 275 | 276 | if (HVC) { 277 | bitSet(infoFrame.buf[0], 0); 278 | } 279 | if (LVC) { 280 | bitSet(infoFrame.buf[0], 1); 281 | } 282 | if (BVC) { 283 | bitSet(infoFrame.buf[0], 2); 284 | } 285 | 286 | if (TMPC) { 287 | bitSet(infoFrame.buf[0], 3);//therm overtemp 288 | } 289 | Can0.write(BMSFrame); 290 | delay(20); 291 | Can0.write(EVCCFrame); 292 | delay(20); 293 | Can0.write(infoFrame); 294 | //send the module frames 295 | sendModuleInfo(moduleOutIndex); 296 | moduleOutIndex++; 297 | sendModuleInfo(moduleOutIndex); 298 | if (moduleOutIndex == 11) { 299 | moduleOutIndex = 0; 300 | } 301 | else { 302 | moduleOutIndex++; 303 | } 304 | } 305 | 306 | void calcModuleInfo(int sendIndex) { 307 | //first, calculate the module information. 308 | moduleTotalVoltage[sendIndex] = 0; 309 | moduleMinVoltage[sendIndex] = 5000; 310 | moduleMaxVoltage[sendIndex] = 1000; 311 | for (int j = 0; j < 16; j++) { 312 | if (cellVoltage[j][sendIndex] > 1000) { 313 | moduleTotalVoltage[sendIndex] += cellVoltage[j][sendIndex]; 314 | if (cellVoltage[j][sendIndex] > moduleMaxVoltage[sendIndex]) { 315 | moduleMaxVoltage[sendIndex] = cellVoltage[j][sendIndex]; 316 | } 317 | if (cellVoltage[j][sendIndex] < moduleMinVoltage[sendIndex]) { 318 | moduleMinVoltage[sendIndex] = cellVoltage[j][sendIndex]; 319 | } 320 | } 321 | } 322 | Serial.println(sendIndex); 323 | } 324 | void sendModuleInfo(int sendIndex) { 325 | static CAN_message_t moduleFrame; 326 | moduleFrame.ext = 0; 327 | moduleFrame.id = 1792 + sendIndex; 328 | moduleFrame.len = 8; 329 | moduleFrame.buf[0] = lowByte(moduleMinVoltage[sendIndex]); 330 | moduleFrame.buf[1] = highByte(moduleMinVoltage[sendIndex]); 331 | moduleFrame.buf[2] = lowByte(moduleMaxVoltage[sendIndex]); 332 | moduleFrame.buf[3] = highByte(moduleMaxVoltage[sendIndex]); 333 | moduleFrame.buf[4] = moduleTotalVoltage[sendIndex]; 334 | moduleFrame.buf[5] = moduleTotalVoltage[sendIndex] >> 8; 335 | moduleFrame.buf[6] = moduleTotalVoltage[sendIndex] >> 16; 336 | moduleFrame.buf[7] = 0x00; 337 | Can0.write(moduleFrame); 338 | } 339 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # EV-Code 2 | Arduino projects for the Boxster - http://www.electricboxster.com 3 | 4 | LLTBMS07 runs 12 BMS modules on the Pacifica batteries, using multiplexers and isolators for safety. 5 | 6 | JLD505 is a clone of the EVTV board of the same name; the code is available but in an old version. Handles coulomb counting using INA226 and CHAdeMO with MCP2515 and optoisolators. Includes temperature sensors. Will run ammeter, voltmeter, and temperature gauges eventually. 7 | 8 | CANRelay is a Pro Mini (well a Pro Micro) with 2 CAN boards so it connects the 250kbps EVCC/charger to the 500kbps DC/DC, BMS and BMS controller. Eventually to the JLD505 and CHAdeMO as well, maybe the GEVCU and DMOC too. 9 | 10 | FinalBMSCode is my current use, bleeding edge code running on a Teensy 3.5 reading and interpreting Leaf BMS cell data. Leaf Spy Pro is concurrently running. 11 | -------------------------------------------------------------------------------- /chademo-pins-768x532.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Isaac96/EV-Code/d7546fa45881776557da84c97b0404d377ac14d3/chademo-pins-768x532.jpg -------------------------------------------------------------------------------- /chademo-sequence-circuit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Isaac96/EV-Code/d7546fa45881776557da84c97b0404d377ac14d3/chademo-sequence-circuit.png --------------------------------------------------------------------------------