├── BMSModule.cpp ├── BMSModule.h ├── BMSModuleManager.cpp ├── BMSModuleManager.h ├── BMSUtil.h ├── Logger.cpp ├── Logger.h ├── SerialConsole.cpp ├── SerialConsole.h ├── SystemIO.cpp ├── SystemIO.h ├── TeslaBMS-ESP32.ino └── config.h /BMSModule.cpp: -------------------------------------------------------------------------------- 1 | #include "config.h" 2 | #include "BMSModule.h" 3 | #include "BMSUtil.h" 4 | #include "Logger.h" 5 | 6 | extern EEPROMSettings settings; 7 | 8 | BMSModule::BMSModule() 9 | { 10 | for (int i = 0; i < 6; i++) 11 | { 12 | cellVolt[i] = 0.0f; 13 | lowestCellVolt[i] = 5.0f; 14 | highestCellVolt[i] = 0.0f; 15 | balanceState[i] = 0; 16 | } 17 | moduleVolt = 0.0f; 18 | temperatures[0] = 0.0f; 19 | temperatures[1] = 0.0f; 20 | lowestTemperature = 200.0f; 21 | highestTemperature = -100.0f; 22 | lowestModuleVolt = 200.0f; 23 | highestModuleVolt = 0.0f; 24 | exists = false; 25 | moduleAddress = 0; 26 | goodPackets = 0; 27 | badPackets = 0; 28 | } 29 | 30 | /* 31 | Reading the status of the board to identify any flags, will be more useful when implementing a sleep cycle 32 | */ 33 | void BMSModule::readStatus() 34 | { 35 | uint8_t payload[3]; 36 | uint8_t buff[8]; 37 | payload[0] = moduleAddress << 1; //adresss 38 | payload[1] = REG_ALERT_STATUS;//Alert Status start 39 | payload[2] = 0x04; 40 | BMSUtil::sendDataWithReply(payload, 3, false, buff, 7); 41 | alerts = buff[3]; 42 | faults = buff[4]; 43 | COVFaults = buff[5]; 44 | CUVFaults = buff[6]; 45 | } 46 | 47 | uint8_t BMSModule::getFaults() 48 | { 49 | return faults; 50 | } 51 | 52 | uint8_t BMSModule::getAlerts() 53 | { 54 | return alerts; 55 | } 56 | 57 | uint8_t BMSModule::getCOVCells() 58 | { 59 | return COVFaults; 60 | } 61 | 62 | uint8_t BMSModule::getCUVCells() 63 | { 64 | return CUVFaults; 65 | } 66 | 67 | /* 68 | Reading the setpoints, after a reset the default tesla setpoints are loaded 69 | Default response : 0x10, 0x80, 0x31, 0x81, 0x08, 0x81, 0x66, 0xff 70 | */ 71 | /* 72 | void BMSModule::readSetpoint() 73 | { 74 | uint8_t payload[3]; 75 | uint8_t buff[12]; 76 | payload[0] = moduleAddress << 1; //adresss 77 | payload[1] = 0x40;//Alert Status start 78 | payload[2] = 0x08;//two registers 79 | sendData(payload, 3, false); 80 | delay(2); 81 | getReply(buff); 82 | 83 | OVolt = 2.0+ (0.05* buff[5]); 84 | UVolt = 0.7 + (0.1* buff[7]); 85 | Tset = 35 + (5 * (buff[9] >> 4)); 86 | } */ 87 | 88 | bool BMSModule::readModuleValues() 89 | { 90 | uint8_t payload[4]; 91 | uint8_t buff[50]; 92 | uint8_t calcCRC; 93 | bool retVal = false; 94 | int retLen; 95 | float tempCalc; 96 | float tempTemp; 97 | 98 | payload[0] = moduleAddress << 1; 99 | 100 | readStatus(); 101 | Logger::debug("Module %i alerts=%X faults=%X COV=%X CUV=%X", moduleAddress, alerts, faults, COVFaults, CUVFaults); 102 | 103 | payload[1] = REG_ADC_CTRL; 104 | payload[2] = 0b00111101; //ADC Auto mode, read every ADC input we can (Both Temps, Pack, 6 cells) 105 | BMSUtil::sendDataWithReply(payload, 3, true, buff, 3); 106 | 107 | payload[1] = REG_IO_CTRL; 108 | payload[2] = 0b00000011; //enable temperature measurement VSS pins 109 | BMSUtil::sendDataWithReply(payload, 3, true, buff, 3); 110 | 111 | payload[1] = REG_ADC_CONV; //start all ADC conversions 112 | payload[2] = 1; 113 | BMSUtil::sendDataWithReply(payload, 3, true, buff, 3); 114 | 115 | payload[1] = REG_GPAI; //start reading registers at the module voltage registers 116 | payload[2] = 0x12; //read 18 bytes (Each value takes 2 - ModuleV, CellV1-6, Temp1, Temp2) 117 | retLen = BMSUtil::sendDataWithReply(payload, 3, false, buff, 22); 118 | 119 | calcCRC = BMSUtil::genCRC(buff, retLen-1); 120 | Logger::debug("Sent CRC: %x Calculated CRC: %x", buff[21], calcCRC); 121 | 122 | //18 data bytes, address, command, length, and CRC = 22 bytes returned 123 | //Also validate CRC to ensure we didn't get garbage data. 124 | if ( (retLen == 22) && (buff[21] == calcCRC) ) 125 | { 126 | if (buff[0] == (moduleAddress << 1) && buff[1] == REG_GPAI && buff[2] == 0x12) //Also ensure this is actually the reply to our intended query 127 | { 128 | //payload is 2 bytes gpai, 2 bytes for each of 6 cell voltages, 2 bytes for each of two temperatures (18 bytes of data) 129 | moduleVolt = (buff[3] * 256 + buff[4]) * 0.002034609f; 130 | if (moduleVolt > highestModuleVolt) highestModuleVolt = moduleVolt; 131 | if (moduleVolt < lowestModuleVolt) lowestModuleVolt = moduleVolt; 132 | for (int i = 0; i < 6; i++) 133 | { 134 | cellVolt[i] = (buff[5 + (i * 2)] * 256 + buff[6 + (i * 2)]) * 0.000381493f; 135 | if (lowestCellVolt[i] > cellVolt[i]) lowestCellVolt[i] = cellVolt[i]; 136 | if (highestCellVolt[i] < cellVolt[i]) highestCellVolt[i] = cellVolt[i]; 137 | } 138 | 139 | //Now using steinhart/hart equation for temperatures. We'll see if it is better than old code. 140 | tempTemp = (1.78f / ((buff[17] * 256 + buff[18] + 2) / 33046.0f) - 3.57f); 141 | tempTemp *= 1000.0f; 142 | tempCalc = 1.0f / (0.0007610373573f + (0.0002728524832 * logf(tempTemp)) + (powf(logf(tempTemp), 3) * 0.0000001022822735f)); 143 | 144 | temperatures[0] = tempCalc - 273.15f; 145 | 146 | tempTemp = 1.78f / ((buff[19] * 256 + buff[20] + 9) / 33068.0f) - 3.57f; 147 | tempTemp *= 1000.0f; 148 | tempCalc = 1.0f / (0.0007610373573f + (0.0002728524832 * logf(tempTemp)) + (powf(logf(tempTemp), 3) * 0.0000001022822735f)); 149 | temperatures[1] = tempCalc - 273.15f; 150 | 151 | if (getLowTemp() < lowestTemperature) lowestTemperature = getLowTemp(); 152 | if (getHighTemp() > highestTemperature) highestTemperature = getHighTemp(); 153 | 154 | Logger::debug("Got voltage and temperature readings"); 155 | goodPackets++; 156 | retVal = true; 157 | } 158 | } 159 | else 160 | { 161 | Logger::error("Invalid module response received for module %i len: %i crc: %i calc: %i", 162 | moduleAddress, retLen, buff[21], calcCRC); 163 | badPackets++; 164 | } 165 | 166 | Logger::debug("Good RX: %d Bad RX: %d", goodPackets, badPackets); 167 | 168 | //turning the temperature wires off here seems to cause weird temperature glitches 169 | // payload[1] = REG_IO_CTRL; 170 | // payload[2] = 0b00000000; //turn off temperature measurement pins 171 | // BMSUtil::sendData(payload, 3, true); 172 | // delay(3); 173 | // BMSUtil::getReply(buff, 50); //TODO: we're not validating the reply here. Perhaps check to see if a valid reply came back 174 | 175 | return retVal; 176 | } 177 | 178 | float BMSModule::getCellVoltage(int cell) 179 | { 180 | if (cell < 0 || cell > 5) return 0.0f; 181 | return cellVolt[cell]; 182 | } 183 | 184 | float BMSModule::getLowCellV() 185 | { 186 | float lowVal = 10.0f; 187 | for (int i = 0; i < 6; i++) if (cellVolt[i] < lowVal) lowVal = cellVolt[i]; 188 | return lowVal; 189 | } 190 | 191 | float BMSModule::getHighCellV() 192 | { 193 | float hiVal = 0.0f; 194 | for (int i = 0; i < 6; i++) if (cellVolt[i] > hiVal) hiVal = cellVolt[i]; 195 | return hiVal; 196 | } 197 | 198 | float BMSModule::getAverageV() 199 | { 200 | float avgVal = 0.0f; 201 | for (int i = 0; i < 6; i++) avgVal += cellVolt[i]; 202 | avgVal /= 6.0f; 203 | return avgVal; 204 | } 205 | 206 | float BMSModule::getHighestModuleVolt() 207 | { 208 | return highestModuleVolt; 209 | } 210 | 211 | float BMSModule::getLowestModuleVolt() 212 | { 213 | return lowestModuleVolt; 214 | } 215 | 216 | float BMSModule::getHighestCellVolt(int cell) 217 | { 218 | if (cell < 0 || cell > 5) return 0.0f; 219 | return highestCellVolt[cell]; 220 | } 221 | 222 | float BMSModule::getLowestCellVolt(int cell) 223 | { 224 | if (cell < 0 || cell > 5) return 0.0f; 225 | return lowestCellVolt[cell]; 226 | } 227 | 228 | float BMSModule::getHighestTemp() 229 | { 230 | return highestTemperature; 231 | } 232 | 233 | float BMSModule::getLowestTemp() 234 | { 235 | return lowestTemperature; 236 | } 237 | 238 | float BMSModule::getLowTemp() 239 | { 240 | return (temperatures[0] < temperatures[1]) ? temperatures[0] : temperatures[1]; 241 | } 242 | 243 | float BMSModule::getHighTemp() 244 | { 245 | return (temperatures[0] < temperatures[1]) ? temperatures[1] : temperatures[0]; 246 | } 247 | 248 | float BMSModule::getAvgTemp() 249 | { 250 | return (temperatures[0] + temperatures[1]) / 2.0f; 251 | } 252 | 253 | float BMSModule::getModuleVoltage() 254 | { 255 | return moduleVolt; 256 | } 257 | 258 | float BMSModule::getTemperature(int temp) 259 | { 260 | if (temp < 0 || temp > 1) return 0.0f; 261 | return temperatures[temp]; 262 | } 263 | 264 | void BMSModule::setAddress(int newAddr) 265 | { 266 | if (newAddr < 0 || newAddr > MAX_MODULE_ADDR) return; 267 | moduleAddress = newAddr; 268 | } 269 | 270 | int BMSModule::getAddress() 271 | { 272 | return moduleAddress; 273 | } 274 | 275 | bool BMSModule::isExisting() 276 | { 277 | return exists; 278 | } 279 | 280 | 281 | void BMSModule::setExists(bool ex) 282 | { 283 | exists = ex; 284 | } 285 | 286 | void BMSModule::balanceCells() 287 | { 288 | uint8_t payload[4]; 289 | uint8_t buff[30]; 290 | uint8_t balance = 0;//bit 0 - 5 are to activate cell balancing 1-6 291 | 292 | payload[0] = moduleAddress << 1; 293 | payload[1] = REG_BAL_CTRL; 294 | payload[2] = 0; //writing zero to this register resets balance time and must be done before setting balance resistors again. 295 | BMSUtil::sendData(payload, 3, true); 296 | delay(2); 297 | BMSUtil::getReply(buff, 30); 298 | 299 | for (int i = 0; i < 6; i++) 300 | { 301 | if ( (balanceState[i] == 0) && (getCellVoltage(i) > settings.balanceVoltage) ) balanceState[i] = 1; 302 | 303 | if ( /*(balanceState[i] == 1) &&*/ (getCellVoltage(i) < (settings.balanceVoltage - settings.balanceHyst)) ) balanceState[i] = 0; 304 | 305 | if (balanceState[i] == 1) balance |= (1< 5) return 0; 348 | return balanceState[cell]; 349 | } 350 | -------------------------------------------------------------------------------- /BMSModule.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | class BMSModule 4 | { 5 | public: 6 | BMSModule(); 7 | void readStatus(); 8 | bool readModuleValues(); 9 | float getCellVoltage(int cell); 10 | float getLowCellV(); 11 | float getHighCellV(); 12 | float getAverageV(); 13 | float getLowTemp(); 14 | float getHighTemp(); 15 | float getHighestModuleVolt(); 16 | float getLowestModuleVolt(); 17 | float getHighestCellVolt(int cell); 18 | float getLowestCellVolt(int cell); 19 | float getHighestTemp(); 20 | float getLowestTemp(); 21 | float getAvgTemp(); 22 | float getModuleVoltage(); 23 | float getTemperature(int temp); 24 | uint8_t getFaults(); 25 | uint8_t getAlerts(); 26 | uint8_t getCOVCells(); 27 | uint8_t getCUVCells(); 28 | void setAddress(int newAddr); 29 | int getAddress(); 30 | bool isExisting(); 31 | void setExists(bool ex); 32 | void balanceCells(); 33 | uint8_t getBalancingState(int cell); 34 | 35 | private: 36 | float cellVolt[6]; // calculated as 16 bit value * 6.250 / 16383 = volts 37 | float lowestCellVolt[6]; 38 | float highestCellVolt[6]; 39 | float moduleVolt; // calculated as 16 bit value * 33.333 / 16383 = volts 40 | float temperatures[2]; // Don't know the proper scaling at this point 41 | float lowestTemperature; 42 | float highestTemperature; 43 | float lowestModuleVolt; 44 | float highestModuleVolt; 45 | uint8_t balanceState[6]; //0 = balancing off for this cell, 1 = balancing currently on 46 | bool exists; 47 | int alerts; 48 | int faults; 49 | int COVFaults; 50 | int CUVFaults; 51 | int goodPackets; 52 | int badPackets; 53 | 54 | uint8_t moduleAddress; //1 to 0x3E 55 | }; 56 | -------------------------------------------------------------------------------- /BMSModuleManager.cpp: -------------------------------------------------------------------------------- 1 | #include "config.h" 2 | #include "BMSModuleManager.h" 3 | #include "BMSUtil.h" 4 | #include "Logger.h" 5 | 6 | extern EEPROMSettings settings; 7 | 8 | BMSModuleManager::BMSModuleManager() 9 | { 10 | for (int i = 1; i <= MAX_MODULE_ADDR; i++) { 11 | modules[i].setExists(false); 12 | modules[i].setAddress(i); 13 | } 14 | lowestPackVolt = 1000.0f; 15 | highestPackVolt = 0.0f; 16 | lowestPackTemp = 200.0f; 17 | highestPackTemp = -100.0f; 18 | isFaulted = false; 19 | } 20 | 21 | void BMSModuleManager::balanceCells() 22 | { 23 | for (int address = 1; address <= MAX_MODULE_ADDR; address++) 24 | { 25 | if (modules[address].isExisting()) modules[address].balanceCells(); 26 | } 27 | } 28 | 29 | /* 30 | * Try to set up any unitialized boards. Send a command to address 0 and see if there is a response. If there is then there is 31 | * still at least one unitialized board. Go ahead and give it the first ID not registered as already taken. 32 | * If we send a command to address 0 and no one responds then every board is inialized and this routine stops. 33 | * Don't run this routine until after the boards have already been enumerated.\ 34 | * Note: The 0x80 conversion it is looking might in theory block the message from being forwarded so it might be required 35 | * To do all of this differently. Try with multiple boards. The alternative method would be to try to set the next unused 36 | * address and see if any boards respond back saying that they set the address. 37 | */ 38 | void BMSModuleManager::setupBoards() 39 | { 40 | uint8_t payload[3]; 41 | uint8_t buff[10]; 42 | int retLen; 43 | 44 | payload[0] = 0; 45 | payload[1] = 0; 46 | payload[2] = 1; 47 | 48 | while (1 == 1) 49 | { 50 | payload[0] = 0; 51 | payload[1] = 0; 52 | payload[2] = 1; 53 | retLen = BMSUtil::sendDataWithReply(payload, 3, false, buff, 4); 54 | if (retLen == 4) 55 | { 56 | if (buff[0] == 0x80 && buff[1] == 0 && buff[2] == 1) 57 | { 58 | Logger::debug("00 found"); 59 | //look for a free address to use 60 | for (int y = 1; y < 63; y++) 61 | { 62 | if (!modules[y].isExisting()) 63 | { 64 | payload[0] = 0; 65 | payload[1] = REG_ADDR_CTRL; 66 | payload[2] = y | 0x80; 67 | BMSUtil::sendData(payload, 3, true); 68 | delay(3); 69 | if (BMSUtil::getReply(buff, 10) > 2) 70 | { 71 | if (buff[0] == (0x81) && buff[1] == REG_ADDR_CTRL && buff[2] == (y + 0x80)) 72 | { 73 | modules[y].setExists(true); 74 | numFoundModules++; 75 | Logger::debug("Address assigned"); 76 | } 77 | } 78 | break; //quit the for loop 79 | } 80 | } 81 | } 82 | else break; //nobody responded properly to the zero address so our work here is done. 83 | } 84 | else break; 85 | } 86 | } 87 | 88 | /* 89 | * Iterate through all 62 possible board addresses (1-62) to see if they respond 90 | */ 91 | void BMSModuleManager::findBoards() 92 | { 93 | uint8_t payload[3]; 94 | uint8_t buff[8]; 95 | 96 | numFoundModules = 0; 97 | payload[0] = 0; 98 | payload[1] = 0; //read registers starting at 0 99 | payload[2] = 1; //read one byte 100 | for (int x = 1; x <= MAX_MODULE_ADDR; x++) 101 | { 102 | modules[x].setExists(false); 103 | payload[0] = x << 1; 104 | BMSUtil::sendData(payload, 3, false); 105 | delay(20); 106 | if (BMSUtil::getReply(buff, 8) > 4) 107 | { 108 | if (buff[0] == (x << 1) && buff[1] == 0 && buff[2] == 1 && buff[4] > 0) { 109 | modules[x].setExists(true); 110 | numFoundModules++; 111 | Logger::debug("Found module with address: %X", x); 112 | } 113 | } 114 | delay(5); 115 | } 116 | } 117 | 118 | 119 | /* 120 | * Force all modules to reset back to address 0 then set them all up in order so that the first module 121 | * in line from the master board is 1, the second one 2, and so on. 122 | */ 123 | void BMSModuleManager::renumberBoardIDs() 124 | { 125 | uint8_t payload[3]; 126 | uint8_t buff[8]; 127 | int attempts = 1; 128 | 129 | for (int y = 1; y < 63; y++) 130 | { 131 | modules[y].setExists(false); 132 | numFoundModules = 0; 133 | } 134 | 135 | while (attempts < 3) 136 | { 137 | payload[0] = 0x3F << 1; //broadcast the reset command 138 | payload[1] = 0x3C;//reset 139 | payload[2] = 0xA5;//data to cause a reset 140 | BMSUtil::sendData(payload, 3, true); 141 | delay(100); 142 | BMSUtil::getReply(buff, 8); 143 | if (buff[0] == 0x7F && buff[1] == 0x3C && buff[2] == 0xA5 && buff[3] == 0x57) break; 144 | attempts++; 145 | } 146 | 147 | setupBoards(); 148 | } 149 | 150 | /* 151 | After a RESET boards have their faults written due to the hard restart or first time power up, this clears thier faults 152 | */ 153 | void BMSModuleManager::clearFaults() 154 | { 155 | uint8_t payload[3]; 156 | uint8_t buff[8]; 157 | payload[0] = 0x7F; //broadcast 158 | payload[1] = REG_ALERT_STATUS;//Alert Status 159 | payload[2] = 0xFF;//data to cause a reset 160 | BMSUtil::sendDataWithReply(payload, 3, true, buff, 4); 161 | 162 | payload[0] = 0x7F; //broadcast 163 | payload[2] = 0x00;//data to clear 164 | BMSUtil::sendDataWithReply(payload, 3, true, buff, 4); 165 | 166 | payload[0] = 0x7F; //broadcast 167 | payload[1] = REG_FAULT_STATUS;//Fault Status 168 | payload[2] = 0xFF;//data to cause a reset 169 | BMSUtil::sendDataWithReply(payload, 3, true, buff, 4); 170 | 171 | payload[0] = 0x7F; //broadcast 172 | payload[2] = 0x00;//data to clear 173 | BMSUtil::sendDataWithReply(payload, 3, true, buff, 4); 174 | 175 | isFaulted = false; 176 | } 177 | 178 | /* 179 | Puts all boards on the bus into a Sleep state, very good to use when the vehicle is a rest state. 180 | Pulling the boards out of sleep only to check voltage decay and temperature when the contactors are open. 181 | */ 182 | 183 | void BMSModuleManager::sleepBoards() 184 | { 185 | uint8_t payload[3]; 186 | uint8_t buff[8]; 187 | payload[0] = 0x7F; //broadcast 188 | payload[1] = REG_IO_CTRL;//IO ctrl start 189 | payload[2] = 0x04;//write sleep bit 190 | BMSUtil::sendData(payload, 3, true); 191 | delay(2); 192 | BMSUtil::getReply(buff, 8); 193 | } 194 | 195 | /* 196 | Wakes all the boards up and clears thier SLEEP state bit in the Alert Status Registery 197 | */ 198 | 199 | void BMSModuleManager::wakeBoards() 200 | { 201 | uint8_t payload[3]; 202 | uint8_t buff[8]; 203 | payload[0] = 0x7F; //broadcast 204 | payload[1] = REG_IO_CTRL;//IO ctrl start 205 | payload[2] = 0x00;//write sleep bit 206 | BMSUtil::sendData(payload, 3, true); 207 | delay(2); 208 | BMSUtil::getReply(buff, 8); 209 | 210 | payload[0] = 0x7F; //broadcast 211 | payload[1] = REG_ALERT_STATUS;//Fault Status 212 | payload[2] = 0x04;//data to cause a reset 213 | BMSUtil::sendData(payload, 3, true); 214 | delay(2); 215 | BMSUtil::getReply(buff, 8); 216 | payload[0] = 0x7F; //broadcast 217 | payload[2] = 0x00;//data to clear 218 | BMSUtil::sendData(payload, 3, true); 219 | delay(2); 220 | BMSUtil::getReply(buff, 8); 221 | } 222 | 223 | void BMSModuleManager::getAllVoltTemp() 224 | { 225 | packVolt = 0.0f; 226 | for (int x = 1; x <= MAX_MODULE_ADDR; x++) 227 | { 228 | if (modules[x].isExisting()) 229 | { 230 | Logger::debug(""); 231 | Logger::debug("Module %i exists. Reading voltage and temperature values", x); 232 | modules[x].readModuleValues(); 233 | Logger::debug("Module voltage: %f", modules[x].getModuleVoltage()); 234 | Logger::debug("Lowest Cell V: %f Highest Cell V: %f", modules[x].getLowCellV(), modules[x].getHighCellV()); 235 | Logger::debug("Temp1: %f Temp2: %f", modules[x].getTemperature(0), modules[x].getTemperature(1)); 236 | packVolt += modules[x].getModuleVoltage(); 237 | if (modules[x].getLowTemp() < lowestPackTemp) lowestPackTemp = modules[x].getLowTemp(); 238 | if (modules[x].getHighTemp() > highestPackTemp) highestPackTemp = modules[x].getHighTemp(); 239 | } 240 | } 241 | 242 | if (packVolt > highestPackVolt) highestPackVolt = packVolt; 243 | if (packVolt < lowestPackVolt) lowestPackVolt = packVolt; 244 | //You can uncomment this code if you do have the module fault chain attached. Change the pin number to where it is attached 245 | /* 246 | if (digitalRead(13) == LOW) { 247 | if (!isFaulted) Logger::error("One or more BMS modules have entered the fault state!"); 248 | isFaulted = true; 249 | } 250 | else 251 | { 252 | if (isFaulted) Logger::info("All modules have exited a faulted state"); 253 | isFaulted = false; 254 | } 255 | */ 256 | } 257 | 258 | float BMSModuleManager::getPackVoltage() 259 | { 260 | return packVolt; 261 | } 262 | 263 | float BMSModuleManager::getAvgTemperature() 264 | { 265 | float avg = 0.0f; 266 | for (int x = 1; x <= MAX_MODULE_ADDR; x++) 267 | { 268 | if (modules[x].isExisting()) avg += modules[x].getAvgTemp(); 269 | } 270 | avg = avg / (float)numFoundModules; 271 | 272 | return avg; 273 | } 274 | 275 | float BMSModuleManager::getAvgCellVolt() 276 | { 277 | float avg = 0.0f; 278 | for (int x = 1; x <= MAX_MODULE_ADDR; x++) 279 | { 280 | if (modules[x].isExisting()) avg += modules[x].getAverageV(); 281 | } 282 | avg = avg / (float)numFoundModules; 283 | 284 | return avg; 285 | } 286 | 287 | void BMSModuleManager::printPackSummary() 288 | { 289 | uint8_t faults; 290 | uint8_t alerts; 291 | uint8_t COV; 292 | uint8_t CUV; 293 | 294 | Logger::console(""); 295 | Logger::console(""); 296 | Logger::console(""); 297 | Logger::console(" Pack Status:"); 298 | if (isFaulted) Logger::console(" FAULTED!"); 299 | else Logger::console(" All systems go!"); 300 | Logger::console("Modules: %i Voltage: %fV Avg Cell Voltage: %fV Avg Temp: %fC ", numFoundModules, 301 | getPackVoltage(),getAvgCellVolt(), getAvgTemperature()); 302 | Logger::console(""); 303 | for (int y = 1; y < 63; y++) 304 | { 305 | if (modules[y].isExisting()) 306 | { 307 | faults = modules[y].getFaults(); 308 | alerts = modules[y].getAlerts(); 309 | COV = modules[y].getCOVCells(); 310 | CUV = modules[y].getCUVCells(); 311 | 312 | Logger::console(" Module #%i", y); 313 | 314 | Logger::console(" Voltage: %fV (%fV-%fV) Temperatures: (%fC-%fC)", modules[y].getModuleVoltage(), 315 | modules[y].getLowCellV(), modules[y].getHighCellV(), modules[y].getLowTemp(), modules[y].getHighTemp()); 316 | 317 | Serial.print(" Currently balancing cells: "); 318 | for (int i = 0; i < 6; i++) 319 | { 320 | if (modules[y].getBalancingState(i) == 1) 321 | { 322 | Serial.print(i); 323 | Serial.print(" "); 324 | } 325 | } 326 | Serial.println(); 327 | 328 | if (faults > 0) 329 | { 330 | Logger::console(" MODULE IS FAULTED:"); 331 | if (faults & 1) 332 | { 333 | Serial.print(" Overvoltage Cell Numbers (1-6): "); 334 | for (int i = 0; i < 6; i++) 335 | { 336 | if (COV & (1 << i)) 337 | { 338 | Serial.print(i+1); 339 | Serial.print(" "); 340 | } 341 | } 342 | Serial.println(); 343 | } 344 | if (faults & 2) 345 | { 346 | Serial.print(" Undervoltage Cell Numbers (1-6): "); 347 | for (int i = 0; i < 6; i++) 348 | { 349 | if (CUV & (1 << i)) 350 | { 351 | Serial.print(i+1); 352 | Serial.print(" "); 353 | } 354 | } 355 | Serial.println(); 356 | } 357 | if (faults & 4) 358 | { 359 | Logger::console(" CRC error in received packet"); 360 | } 361 | if (faults & 8) 362 | { 363 | Logger::console(" Power on reset has occurred"); 364 | } 365 | if (faults & 0x10) 366 | { 367 | Logger::console(" Test fault active"); 368 | } 369 | if (faults & 0x20) 370 | { 371 | Logger::console(" Internal registers inconsistent"); 372 | } 373 | } 374 | if (alerts > 0) 375 | { 376 | Logger::console(" MODULE HAS ALERTS:"); 377 | if (alerts & 1) 378 | { 379 | Logger::console(" Over temperature on TS1"); 380 | } 381 | if (alerts & 2) 382 | { 383 | Logger::console(" Over temperature on TS2"); 384 | } 385 | if (alerts & 4) 386 | { 387 | Logger::console(" Sleep mode active"); 388 | } 389 | if (alerts & 8) 390 | { 391 | Logger::console(" Thermal shutdown active"); 392 | } 393 | if (alerts & 0x10) 394 | { 395 | Logger::console(" Test Alert"); 396 | } 397 | if (alerts & 0x20) 398 | { 399 | Logger::console(" OTP EPROM Uncorrectable Error"); 400 | } 401 | if (alerts & 0x40) 402 | { 403 | Logger::console(" GROUP3 Regs Invalid"); 404 | } 405 | if (alerts & 0x80) 406 | { 407 | Logger::console(" Address not registered"); 408 | } 409 | } 410 | if (faults > 0 || alerts > 0) Serial.println(); 411 | } 412 | } 413 | } 414 | 415 | void BMSModuleManager::printPackDetails() 416 | { 417 | uint8_t faults; 418 | uint8_t alerts; 419 | uint8_t COV; 420 | uint8_t CUV; 421 | int cellNum = 0; 422 | 423 | Logger::console(""); 424 | Logger::console(""); 425 | Logger::console(""); 426 | Logger::console(" Pack Status:"); 427 | if (isFaulted) Logger::console(" FAULTED!"); 428 | else Logger::console(" All systems go!"); 429 | Logger::console("Modules: %i Voltage: %fV Avg Cell Voltage: %fV Avg Temp: %fC ", numFoundModules, 430 | getPackVoltage(),getAvgCellVolt(), getAvgTemperature()); 431 | Logger::console(""); 432 | for (int y = 1; y < 63; y++) 433 | { 434 | if (modules[y].isExisting()) 435 | { 436 | faults = modules[y].getFaults(); 437 | alerts = modules[y].getAlerts(); 438 | COV = modules[y].getCOVCells(); 439 | CUV = modules[y].getCUVCells(); 440 | 441 | Serial.print("Module #"); 442 | Serial.print(y); 443 | if (y < 10) Serial.print(" "); 444 | Serial.print(" "); 445 | Serial.print(modules[y].getModuleVoltage()); 446 | Serial.print("V"); 447 | for (int i = 0; i < 6; i++) 448 | { 449 | if (cellNum < 10) Serial.print(" "); 450 | Serial.print(" Cell"); 451 | Serial.print(cellNum++); 452 | Serial.print(": "); 453 | Serial.print(modules[y].getCellVoltage(i)); 454 | Serial.print("V"); 455 | if (modules[y].getBalancingState(i) == 1) Serial.print("*"); 456 | else Serial.print(" "); 457 | } 458 | Serial.print(" Neg Term Temp: "); 459 | Serial.print(modules[y].getTemperature(0)); 460 | Serial.print("C Pos Term Temp: "); 461 | Serial.print(modules[y].getTemperature(1)); 462 | Serial.println("C"); 463 | } 464 | } 465 | } 466 | 467 | void BMSModuleManager::processCANMsg(CAN_FRAME &frame) 468 | { 469 | uint8_t battId = (frame.id >> 16) & 0xF; 470 | uint8_t moduleId = (frame.id >> 8) & 0xFF; 471 | uint8_t cellId = (frame.id) & 0xFF; 472 | 473 | if (moduleId = 0xFF) //every module 474 | { 475 | if (cellId == 0xFF) sendBatterySummary(); 476 | else 477 | { 478 | for (int i = 1; i <= MAX_MODULE_ADDR; i++) 479 | { 480 | if (modules[i].isExisting()) 481 | { 482 | sendCellDetails(i, cellId); 483 | delayMicroseconds(500); 484 | } 485 | } 486 | } 487 | } 488 | else //a specific module 489 | { 490 | if (cellId == 0xFF) sendModuleSummary(moduleId); 491 | else sendCellDetails(moduleId, cellId); 492 | } 493 | } 494 | 495 | void BMSModuleManager::sendBatterySummary() 496 | { 497 | CAN_FRAME outgoing; 498 | outgoing.id = (0x1BA00000ul) + ((settings.batteryID & 0xF) << 16) + 0xFFFF; 499 | outgoing.rtr = 0; 500 | outgoing.priority = 1; 501 | outgoing.extended = true; 502 | outgoing.length = 8; 503 | 504 | uint16_t battV = uint16_t(getPackVoltage() * 100.0f); 505 | outgoing.data.byte[0] = battV & 0xFF; 506 | outgoing.data.byte[1] = battV >> 8; 507 | outgoing.data.byte[2] = 0; //instantaneous current. Not measured at this point 508 | outgoing.data.byte[3] = 0; 509 | outgoing.data.byte[4] = 50; //state of charge 510 | int avgTemp = (int)getAvgTemperature() + 40; 511 | if (avgTemp < 0) avgTemp = 0; 512 | outgoing.data.byte[5] = avgTemp; 513 | avgTemp = (int)lowestPackTemp + 40; 514 | if (avgTemp < 0) avgTemp = 0; 515 | outgoing.data.byte[6] = avgTemp; 516 | avgTemp = (int)highestPackTemp + 40; 517 | if (avgTemp < 0) avgTemp = 0; 518 | outgoing.data.byte[7] = avgTemp; 519 | CAN0.sendFrame(outgoing); 520 | } 521 | 522 | void BMSModuleManager::sendModuleSummary(int module) 523 | { 524 | CAN_FRAME outgoing; 525 | outgoing.id = (0x1BA00000ul) + ((settings.batteryID & 0xF) << 16) + ((module & 0xFF) << 8) + 0xFF; 526 | outgoing.rtr = 0; 527 | outgoing.priority = 1; 528 | outgoing.extended = true; 529 | outgoing.length = 8; 530 | 531 | uint16_t battV = uint16_t(modules[module].getModuleVoltage() * 100.0f); 532 | outgoing.data.byte[0] = battV & 0xFF; 533 | outgoing.data.byte[1] = battV >> 8; 534 | outgoing.data.byte[2] = 0; //instantaneous current. Not measured at this point 535 | outgoing.data.byte[3] = 0; 536 | outgoing.data.byte[4] = 50; //state of charge 537 | int avgTemp = (int)modules[module].getAvgTemp() + 40; 538 | if (avgTemp < 0) avgTemp = 0; 539 | outgoing.data.byte[5] = avgTemp; 540 | avgTemp = (int)modules[module].getLowestTemp() + 40; 541 | if (avgTemp < 0) avgTemp = 0; 542 | outgoing.data.byte[6] = avgTemp; 543 | avgTemp = (int)modules[module].getHighestTemp() + 40; 544 | if (avgTemp < 0) avgTemp = 0; 545 | outgoing.data.byte[7] = avgTemp; 546 | 547 | CAN0.sendFrame(outgoing); 548 | } 549 | 550 | void BMSModuleManager::sendCellDetails(int module, int cell) 551 | { 552 | CAN_FRAME outgoing; 553 | outgoing.id = (0x1BA00000ul) + ((settings.batteryID & 0xF) << 16) + ((module & 0xFF) << 8) + (cell & 0xFF); 554 | outgoing.rtr = 0; 555 | outgoing.priority = 1; 556 | outgoing.extended = true; 557 | outgoing.length = 8; 558 | 559 | uint16_t battV = uint16_t(modules[module].getCellVoltage(cell) * 100.0f); 560 | outgoing.data.byte[0] = battV & 0xFF; 561 | outgoing.data.byte[1] = battV >> 8; 562 | battV = uint16_t(modules[module].getHighestCellVolt(cell) * 100.0f); 563 | outgoing.data.byte[2] = battV & 0xFF; 564 | outgoing.data.byte[3] = battV >> 8; 565 | battV = uint16_t(modules[module].getLowestCellVolt(cell) * 100.0f); 566 | outgoing.data.byte[4] = battV & 0xFF; 567 | outgoing.data.byte[5] = battV >> 8; 568 | int instTemp = modules[module].getHighTemp() + 40; 569 | outgoing.data.byte[6] = instTemp; // should be nearest temperature reading not highest but this works too. 570 | outgoing.data.byte[7] = 0; //Bit encoded fault data. No definitions for this yet. 571 | 572 | CAN0.sendFrame(outgoing); 573 | } 574 | 575 | //The SerialConsole actually sets the battery ID to a specific value. We just have to set up the CAN filter here to 576 | //match. 577 | void BMSModuleManager::setBatteryID() 578 | { 579 | //Setup filter for direct access to our registered battery ID 580 | uint32_t canID = (0xBAul << 20) + (((uint32_t)settings.batteryID & 0xF) << 16); 581 | CAN0.setRXFilter(0, canID, 0x1FFF0000ul, true); 582 | } 583 | -------------------------------------------------------------------------------- /BMSModuleManager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "config.h" 3 | #include "BMSModule.h" 4 | #include 5 | 6 | class BMSModuleManager 7 | { 8 | public: 9 | BMSModuleManager(); 10 | void balanceCells(); 11 | void setupBoards(); 12 | void findBoards(); 13 | void renumberBoardIDs(); 14 | void clearFaults(); 15 | void sleepBoards(); 16 | void wakeBoards(); 17 | void getAllVoltTemp(); 18 | void readSetpoints(); 19 | void setBatteryID(); 20 | float getPackVoltage(); 21 | float getAvgTemperature(); 22 | float getAvgCellVolt(); 23 | void processCANMsg(CAN_FRAME &frame); 24 | void printPackSummary(); 25 | void printPackDetails(); 26 | 27 | private: 28 | float packVolt; // All modules added together 29 | float lowestPackVolt; 30 | float highestPackVolt; 31 | float lowestPackTemp; 32 | float highestPackTemp; 33 | BMSModule modules[MAX_MODULE_ADDR + 1]; // store data for as many modules as we've configured for. 34 | int numFoundModules; // The number of modules that seem to exist 35 | bool isFaulted; 36 | 37 | void sendBatterySummary(); 38 | void sendModuleSummary(int module); 39 | void sendCellDetails(int module, int cell); 40 | 41 | }; 42 | -------------------------------------------------------------------------------- /BMSUtil.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include "Logger.h" 3 | 4 | class BMSUtil { 5 | public: 6 | 7 | static uint8_t genCRC(uint8_t *input, int lenInput) 8 | { 9 | uint8_t generator = 0x07; 10 | uint8_t crc = 0; 11 | 12 | for (int x = 0; x < lenInput; x++) 13 | { 14 | crc ^= input[x]; /* XOR-in the next input byte */ 15 | 16 | for (int i = 0; i < 8; i++) 17 | { 18 | if ((crc & 0x80) != 0) 19 | { 20 | crc = (uint8_t)((crc << 1) ^ generator); 21 | } 22 | else 23 | { 24 | crc <<= 1; 25 | } 26 | } 27 | } 28 | 29 | return crc; 30 | } 31 | 32 | static void sendData(uint8_t *data, uint8_t dataLen, bool isWrite) 33 | { 34 | uint8_t orig = data[0]; 35 | uint8_t addrByte = data[0]; 36 | if (isWrite) addrByte |= 1; 37 | SERIAL.write(addrByte); 38 | SERIAL.write(&data[1], dataLen - 1); //assumes that there are at least 2 bytes sent every time. There should be, addr and cmd at the least. 39 | data[0] = addrByte; 40 | if (isWrite) SERIAL.write(genCRC(data, dataLen)); 41 | 42 | if (Logger::isDebug()) 43 | { 44 | SERIALCONSOLE.print("Sending: "); 45 | SERIALCONSOLE.print(addrByte, HEX); 46 | SERIALCONSOLE.print(" "); 47 | for (int x = 1; x < dataLen; x++) { 48 | SERIALCONSOLE.print(data[x], HEX); 49 | SERIALCONSOLE.print(" "); 50 | } 51 | if (isWrite) SERIALCONSOLE.print(genCRC(data, dataLen), HEX); 52 | SERIALCONSOLE.println(); 53 | } 54 | 55 | data[0] = orig; 56 | } 57 | 58 | static int getReply(uint8_t *data, int maxLen) 59 | { 60 | int numBytes = 0; 61 | if (Logger::isDebug()) SERIALCONSOLE.print("Reply: "); 62 | while (SERIAL.available() && numBytes < maxLen) 63 | { 64 | data[numBytes] = SERIAL.read(); 65 | if (Logger::isDebug()) { 66 | SERIALCONSOLE.print(data[numBytes], HEX); 67 | SERIALCONSOLE.print(" "); 68 | } 69 | numBytes++; 70 | } 71 | if (maxLen == numBytes) 72 | { 73 | while (SERIAL.available()) SERIAL.read(); 74 | } 75 | if (Logger::isDebug()) SERIALCONSOLE.println(); 76 | return numBytes; 77 | } 78 | 79 | //Uses above functions to send data then get the response. Will auto retry if response not 80 | //the expected return length. This helps to alleviate any comm issues. The Due cannot exactly 81 | //match the correct comm speed so sometimes there are data glitches. 82 | static int sendDataWithReply(uint8_t *data, uint8_t dataLen, bool isWrite, uint8_t *retData, int retLen) 83 | { 84 | int attempts = 1; 85 | int returnedLength; 86 | while (attempts < 4) 87 | { 88 | sendData(data, dataLen, isWrite); 89 | delay(2 * ((retLen / 8) + 1)); 90 | returnedLength = getReply(retData, retLen); 91 | if (returnedLength == retLen) return returnedLength; 92 | attempts++; 93 | } 94 | return returnedLength; //failed to get a proper response. 95 | } 96 | }; 97 | -------------------------------------------------------------------------------- /Logger.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Logger.cpp 3 | * 4 | Copyright (c) 2013 Collin Kidder, Michael Neuweiler, Charles Galpin 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining 7 | a copy of this software and associated documentation files (the 8 | "Software"), to deal in the Software without restriction, including 9 | without limitation the rights to use, copy, modify, merge, publish, 10 | distribute, sublicense, and/or sell copies of the Software, and to 11 | permit persons to whom the Software is furnished to do so, subject to 12 | the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included 15 | in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 21 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 22 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 23 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | 25 | */ 26 | 27 | #include "Logger.h" 28 | 29 | Logger::LogLevel Logger::logLevel = Logger::Info; 30 | uint32_t Logger::lastLogTime = 0; 31 | 32 | /* 33 | * Output a debug message with a variable amount of parameters. 34 | * printf() style, see Logger::log() 35 | * 36 | */ 37 | void Logger::debug(char *message, ...) { 38 | if (logLevel > Debug) 39 | return; 40 | va_list args; 41 | va_start(args, message); 42 | Logger::log(Debug, message, args); 43 | va_end(args); 44 | } 45 | 46 | /* 47 | * Output a info message with a variable amount of parameters 48 | * printf() style, see Logger::log() 49 | */ 50 | void Logger::info(char *message, ...) { 51 | if (logLevel > Info) 52 | return; 53 | va_list args; 54 | va_start(args, message); 55 | Logger::log(Info, message, args); 56 | va_end(args); 57 | } 58 | 59 | /* 60 | * Output a warning message with a variable amount of parameters 61 | * printf() style, see Logger::log() 62 | */ 63 | void Logger::warn(char *message, ...) { 64 | if (logLevel > Warn) 65 | return; 66 | va_list args; 67 | va_start(args, message); 68 | Logger::log(Warn, message, args); 69 | va_end(args); 70 | } 71 | 72 | /* 73 | * Output a error message with a variable amount of parameters 74 | * printf() style, see Logger::log() 75 | */ 76 | void Logger::error(char *message, ...) { 77 | if (logLevel > Error) 78 | return; 79 | va_list args; 80 | va_start(args, message); 81 | Logger::log(Error, message, args); 82 | va_end(args); 83 | } 84 | 85 | /* 86 | * Output a comnsole message with a variable amount of parameters 87 | * printf() style, see Logger::logMessage() 88 | */ 89 | void Logger::console(char *message, ...) { 90 | va_list args; 91 | va_start(args, message); 92 | Logger::logMessage(message, args); 93 | va_end(args); 94 | } 95 | 96 | /* 97 | * Set the log level. Any output below the specified log level will be omitted. 98 | */ 99 | void Logger::setLoglevel(LogLevel level) { 100 | logLevel = level; 101 | } 102 | 103 | /* 104 | * Retrieve the current log level. 105 | */ 106 | Logger::LogLevel Logger::getLogLevel() { 107 | return logLevel; 108 | } 109 | 110 | /* 111 | * Return a timestamp when the last log entry was made. 112 | */ 113 | uint32_t Logger::getLastLogTime() { 114 | return lastLogTime; 115 | } 116 | 117 | /* 118 | * Returns if debug log level is enabled. This can be used in time critical 119 | * situations to prevent unnecessary string concatenation (if the message won't 120 | * be logged in the end). 121 | * 122 | * Example: 123 | * if (Logger::isDebug()) { 124 | * Logger::debug("current time: %d", millis()); 125 | * } 126 | */ 127 | boolean Logger::isDebug() { 128 | return logLevel == Debug; 129 | } 130 | 131 | /* 132 | * Output a log message (called by debug(), info(), warn(), error(), console()) 133 | * 134 | * Supports printf() like syntax: 135 | * 136 | * %% - outputs a '%' character 137 | * %s - prints the next parameter as string 138 | * %d - prints the next parameter as decimal 139 | * %f - prints the next parameter as double float 140 | * %x - prints the next parameter as hex value 141 | * %X - prints the next parameter as hex value with '0x' added before 142 | * %b - prints the next parameter as binary value 143 | * %B - prints the next parameter as binary value with '0b' added before 144 | * %l - prints the next parameter as long 145 | * %c - prints the next parameter as a character 146 | * %t - prints the next parameter as boolean ('T' or 'F') 147 | * %T - prints the next parameter as boolean ('true' or 'false') 148 | */ 149 | void Logger::log(LogLevel level, char *format, va_list args) { 150 | lastLogTime = millis(); 151 | SERIALCONSOLE.print(lastLogTime); 152 | SERIALCONSOLE.print(" - "); 153 | 154 | switch (level) { 155 | case Debug: 156 | SERIALCONSOLE.print("DEBUG"); 157 | break; 158 | case Info: 159 | SERIALCONSOLE.print("INFO"); 160 | break; 161 | case Warn: 162 | SERIALCONSOLE.print("WARNING"); 163 | break; 164 | case Error: 165 | SERIALCONSOLE.print("ERROR"); 166 | break; 167 | } 168 | SERIALCONSOLE.print(": "); 169 | 170 | logMessage(format, args); 171 | } 172 | 173 | /* 174 | * Output a log message (called by log(), console()) 175 | * 176 | * Supports printf() like syntax: 177 | * 178 | * %% - outputs a '%' character 179 | * %s - prints the next parameter as string 180 | * %d - prints the next parameter as decimal 181 | * %f - prints the next parameter as double float 182 | * %x - prints the next parameter as hex value 183 | * %X - prints the next parameter as hex value with '0x' added before 184 | * %b - prints the next parameter as binary value 185 | * %B - prints the next parameter as binary value with '0b' added before 186 | * %l - prints the next parameter as long 187 | * %c - prints the next parameter as a character 188 | * %t - prints the next parameter as boolean ('T' or 'F') 189 | * %T - prints the next parameter as boolean ('true' or 'false') 190 | */ 191 | void Logger::logMessage(char *format, va_list args) { 192 | for (; *format != 0; ++format) { 193 | if (*format == '%') { 194 | ++format; 195 | if (*format == '\0') 196 | break; 197 | if (*format == '%') { 198 | SERIALCONSOLE.print(*format); 199 | continue; 200 | } 201 | if (*format == 's') { 202 | register char *s = (char *) va_arg( args, int ); 203 | SERIALCONSOLE.print(s); 204 | continue; 205 | } 206 | if (*format == 'd' || *format == 'i') { 207 | SERIALCONSOLE.print(va_arg( args, int ), DEC); 208 | continue; 209 | } 210 | if (*format == 'f') { 211 | SERIALCONSOLE.print(va_arg( args, double ), 3); 212 | continue; 213 | } 214 | if (*format == 'x') { 215 | SERIALCONSOLE.print(va_arg( args, int ), HEX); 216 | continue; 217 | } 218 | if (*format == 'X') { 219 | SERIALCONSOLE.print("0x"); 220 | SERIALCONSOLE.print(va_arg( args, int ), HEX); 221 | continue; 222 | } 223 | if (*format == 'b') { 224 | SERIALCONSOLE.print(va_arg( args, int ), BIN); 225 | continue; 226 | } 227 | if (*format == 'B') { 228 | SERIALCONSOLE.print("0b"); 229 | SERIALCONSOLE.print(va_arg( args, int ), BIN); 230 | continue; 231 | } 232 | if (*format == 'l') { 233 | SERIALCONSOLE.print(va_arg( args, long ), DEC); 234 | continue; 235 | } 236 | 237 | if (*format == 'c') { 238 | SERIALCONSOLE.print(va_arg( args, int )); 239 | continue; 240 | } 241 | if (*format == 't') { 242 | if (va_arg( args, int ) == 1) { 243 | SERIALCONSOLE.print("T"); 244 | } else { 245 | SERIALCONSOLE.print("F"); 246 | } 247 | continue; 248 | } 249 | if (*format == 'T') { 250 | if (va_arg( args, int ) == 1) { 251 | SERIALCONSOLE.print("TRUE"); 252 | } else { 253 | SERIALCONSOLE.print("FALSE"); 254 | } 255 | continue; 256 | } 257 | 258 | } 259 | SERIALCONSOLE.print(*format); 260 | } 261 | SERIALCONSOLE.println(); 262 | } 263 | 264 | -------------------------------------------------------------------------------- /Logger.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Logger.h 3 | * 4 | Copyright (c) 2013 Collin Kidder, Michael Neuweiler, Charles Galpin 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining 7 | a copy of this software and associated documentation files (the 8 | "Software"), to deal in the Software without restriction, including 9 | without limitation the rights to use, copy, modify, merge, publish, 10 | distribute, sublicense, and/or sell copies of the Software, and to 11 | permit persons to whom the Software is furnished to do so, subject to 12 | the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included 15 | in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 21 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 22 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 23 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | 25 | */ 26 | 27 | #ifndef LOGGER_H_ 28 | #define LOGGER_H_ 29 | 30 | #include 31 | #include "config.h" 32 | 33 | class Logger { 34 | public: 35 | enum LogLevel { 36 | Debug = 0, Info = 1, Warn = 2, Error = 3, Off = 4 37 | }; 38 | static void debug(char *, ...); 39 | static void info(char *, ...); 40 | static void warn(char *, ...); 41 | static void error(char *, ...); 42 | static void console(char *, ...); 43 | static void setLoglevel(LogLevel); 44 | static LogLevel getLogLevel(); 45 | static uint32_t getLastLogTime(); 46 | static boolean isDebug(); 47 | private: 48 | static LogLevel logLevel; 49 | static uint32_t lastLogTime; 50 | 51 | static void log(LogLevel, char *format, va_list); 52 | static void logMessage(char *format, va_list args); 53 | }; 54 | 55 | #endif /* LOGGER_H_ */ 56 | 57 | 58 | -------------------------------------------------------------------------------- /SerialConsole.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * SerialConsole.cpp 3 | * 4 | Copyright (c) 2017 EVTV / Collin Kidder 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining 7 | a copy of this software and associated documentation files (the 8 | "Software"), to deal in the Software without restriction, including 9 | without limitation the rights to use, copy, modify, merge, publish, 10 | distribute, sublicense, and/or sell copies of the Software, and to 11 | permit persons to whom the Software is furnished to do so, subject to 12 | the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included 15 | in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 21 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 22 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 23 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | 25 | */ 26 | 27 | #include "SerialConsole.h" 28 | #include "Logger.h" 29 | #include "BMSModuleManager.h" 30 | 31 | template inline Print &operator <<(Print &obj, T arg) { obj.print(arg); return obj; } //Lets us stream SerialUSB 32 | 33 | extern EEPROMSettings settings; 34 | extern BMSModuleManager bms; 35 | 36 | bool printPrettyDisplay; 37 | uint32_t prettyCounter; 38 | int whichDisplay; 39 | 40 | SerialConsole::SerialConsole() { 41 | init(); 42 | } 43 | 44 | void SerialConsole::init() { 45 | //State variables for serial console 46 | ptrBuffer = 0; 47 | state = STATE_ROOT_MENU; 48 | loopcount=0; 49 | cancel=false; 50 | printPrettyDisplay = false; 51 | prettyCounter = 0; 52 | whichDisplay = 0; 53 | } 54 | 55 | void SerialConsole::loop() { 56 | if (SERIALCONSOLE.available()) { 57 | serialEvent(); 58 | } 59 | if (printPrettyDisplay && (millis() > (prettyCounter + 3000))) 60 | { 61 | prettyCounter = millis(); 62 | if (whichDisplay == 0) bms.printPackSummary(); 63 | if (whichDisplay == 1) bms.printPackDetails(); 64 | } 65 | } 66 | 67 | void SerialConsole::printMenu() { 68 | Logger::console("\n*************SYSTEM MENU *****************"); 69 | Logger::console("Enable line endings of some sort (LF, CR, CRLF)"); 70 | Logger::console("Most commands case sensitive\n"); 71 | Logger::console("GENERAL SYSTEM CONFIGURATION\n"); 72 | Logger::console(" E = dump system EEPROM values"); 73 | Logger::console(" h = help (displays this message)"); 74 | Logger::console(" S = Sleep all boards"); 75 | Logger::console(" W = Wake up all boards"); 76 | Logger::console(" C = Clear all board faults"); 77 | Logger::console(" F = Find all connected boards"); 78 | Logger::console(" R = Renumber connected boards in sequence"); 79 | Logger::console(" B = Attempt balancing for 5 seconds"); 80 | Logger::console(" p = Toggle output of pack summary every 3 seconds"); 81 | Logger::console(" d = Toggle output of pack details every 3 seconds"); 82 | 83 | Logger::console(" LOGLEVEL=%i - set log level (0=debug, 1=info, 2=warn, 3=error, 4=off)", Logger::getLogLevel()); 84 | Logger::console(" CANSPEED=%i - set first CAN bus speed", settings.canSpeed); 85 | Logger::console(" BATTERYID=%i - Set battery ID for CAN protocol (1-14)", settings.batteryID); 86 | 87 | Logger::console("\nBATTERY MANAGEMENT CONTROLS\n"); 88 | Logger::console(" VOLTLIMHI=%f - High limit for cells in volts", settings.OverVSetpoint); 89 | Logger::console(" VOLTLIMLO=%f - Low limit for cells in volts", settings.UnderVSetpoint); 90 | Logger::console(" TEMPLIMHI=%f - High limit for cell temperature in degrees C", settings.OverTSetpoint); 91 | Logger::console(" TEMPLIMLO=%f - Low limit for cell temperature in degrees C", settings.UnderTSetpoint); 92 | Logger::console(" BALVOLT=%f - Voltage at which to begin cell balancing", settings.balanceVoltage); 93 | Logger::console(" BALHYST=%f - How far voltage must dip before balancing is turned off", settings.balanceHyst); 94 | 95 | float OverVSetpoint; 96 | float UnderVSetpoint; 97 | float OverTSetpoint; 98 | float UnderTSetpoint; 99 | float balanceVoltage; 100 | float balanceHyst; 101 | } 102 | 103 | /* There is a help menu (press H or h or ?) 104 | 105 | Commands are submitted by sending line ending (LF, CR, or both) 106 | */ 107 | void SerialConsole::serialEvent() { 108 | int incoming; 109 | incoming = SERIALCONSOLE.read(); 110 | if (incoming == -1) { //false alarm.... 111 | return; 112 | } 113 | 114 | if (incoming == 10 || incoming == 13) { //command done. Parse it. 115 | handleConsoleCmd(); 116 | ptrBuffer = 0; //reset line counter once the line has been processed 117 | } else { 118 | cmdBuffer[ptrBuffer++] = (unsigned char) incoming; 119 | if (ptrBuffer > 79) 120 | ptrBuffer = 79; 121 | } 122 | } 123 | 124 | void SerialConsole::handleConsoleCmd() { 125 | 126 | if (state == STATE_ROOT_MENU) { 127 | if (ptrBuffer == 1) { //command is a single ascii character 128 | handleShortCmd(); 129 | } else { //if cmd over 1 char then assume (for now) that it is a config line 130 | handleConfigCmd(); 131 | } 132 | } 133 | } 134 | 135 | /*For simplicity the configuration setting code uses four characters for each configuration choice. This makes things easier for 136 | comparison purposes. 137 | */ 138 | void SerialConsole::handleConfigCmd() { 139 | int i; 140 | int newValue; 141 | float newFloat; 142 | bool needEEPROMWrite = false; 143 | 144 | //Logger::debug("Cmd size: %i", ptrBuffer); 145 | if (ptrBuffer < 6) 146 | return; //4 digit command, =, value is at least 6 characters 147 | cmdBuffer[ptrBuffer] = 0; //make sure to null terminate 148 | String cmdString = String(); 149 | unsigned char whichEntry = '0'; 150 | i = 0; 151 | 152 | while (cmdBuffer[i] != '=' && i < ptrBuffer) { 153 | cmdString.concat(String(cmdBuffer[i++])); 154 | } 155 | i++; //skip the = 156 | if (i >= ptrBuffer) 157 | { 158 | Logger::console("Command needs a value..ie TORQ=3000"); 159 | Logger::console(""); 160 | return; //or, we could use this to display the parameter instead of setting 161 | } 162 | 163 | // strtol() is able to parse also hex values (e.g. a string "0xCAFE"), useful for enable/disable by device id 164 | newValue = strtol((char *) (cmdBuffer + i), NULL, 0); 165 | newFloat = strtof((char *) (cmdBuffer + i), NULL); 166 | 167 | cmdString.toUpperCase(); 168 | 169 | if (cmdString == String("CANSPEED")) { 170 | if (newValue >= 33000 && newValue <= 1000000) { 171 | settings.canSpeed = newValue; 172 | Logger::console("Setting CAN speed to %i", newValue); 173 | needEEPROMWrite = true; 174 | } 175 | else Logger::console("Invalid speed. Enter a value between 33000 and 1000000"); 176 | } else if (cmdString == String("LOGLEVEL")) { 177 | switch (newValue) { 178 | case 0: 179 | Logger::setLoglevel(Logger::Debug); 180 | settings.logLevel = 0; 181 | Logger::console("setting loglevel to 'debug'"); 182 | break; 183 | case 1: 184 | Logger::setLoglevel(Logger::Info); 185 | settings.logLevel = 1; 186 | Logger::console("setting loglevel to 'info'"); 187 | break; 188 | case 2: 189 | Logger::console("setting loglevel to 'warning'"); 190 | settings.logLevel = 2; 191 | Logger::setLoglevel(Logger::Warn); 192 | break; 193 | case 3: 194 | Logger::console("setting loglevel to 'error'"); 195 | settings.logLevel = 3; 196 | Logger::setLoglevel(Logger::Error); 197 | break; 198 | case 4: 199 | Logger::console("setting loglevel to 'off'"); 200 | settings.logLevel = 4; 201 | Logger::setLoglevel(Logger::Off); 202 | break; 203 | } 204 | needEEPROMWrite = true; 205 | } else if (cmdString == String("BATTERYID")) { 206 | if (newValue > 0 && newValue < 15) { 207 | settings.batteryID = newValue; 208 | bms.setBatteryID(); 209 | needEEPROMWrite = true; 210 | Logger::console("Battery ID set to: %i", newValue); 211 | } 212 | else Logger::console("Invalid battery ID. Please enter a value between 1 and 14"); 213 | } else if (cmdString == String("VOLTLIMHI")) { 214 | if (newFloat >= 0.0f && newFloat <= 6.00f) { 215 | settings.OverVSetpoint = newFloat; 216 | needEEPROMWrite = true; 217 | Logger::console("Cell Voltage Upper Limit set to: %f", settings.OverVSetpoint); 218 | } 219 | else Logger::console("Invalid upper cell voltage limit. Please enter a value 0.0 to 6.0"); 220 | } else if (cmdString == String("VOLTLIMLO")) { 221 | if (newFloat >= 0.0f && newFloat <= 6.0f) { 222 | settings.UnderVSetpoint = newFloat; 223 | needEEPROMWrite = true; 224 | Logger::console("Cell Voltage Lower Limit set to %f", settings.UnderVSetpoint); 225 | } 226 | else Logger::console("Invalid lower cell voltage limit. Please enter a value 0.0 to 6.0"); 227 | } else if (cmdString == String("BALVOLT")) { 228 | if (newFloat >= 0.0f && newFloat <= 6.0f) { 229 | settings.balanceVoltage = newFloat; 230 | needEEPROMWrite = true; 231 | Logger::console("Balance voltage set to %f", settings.balanceVoltage); 232 | } 233 | else Logger::console("Invalid balancing voltage. Please enter a value 0.0 to 6.0"); 234 | } else if (cmdString == String("BALHYST")) { 235 | if (newFloat >= 0.0f && newFloat <= 1.0f) { 236 | settings.balanceHyst = newFloat; 237 | needEEPROMWrite = true; 238 | Logger::console("Balance hysteresis set to %f", settings.balanceHyst); 239 | } 240 | else Logger::console("Invalid balance hysteresis. Please enter a value 0.0 to 1.0"); 241 | } else if (cmdString == String("TEMPLIMHI")) { 242 | if (newFloat >= 0.0f && newFloat <= 100.0f) { 243 | settings.OverTSetpoint = newFloat; 244 | needEEPROMWrite=true; 245 | Logger::console("Module Temperature Upper Limit set to: %f", settings.OverTSetpoint); 246 | } 247 | else Logger::console("Invalid temperature upper limit please enter a value 0.0 to 100.0"); 248 | } else if (cmdString == String("TEMPLIMLO")) { 249 | if (newFloat >= -20.00f && newFloat <= 120.0f) { 250 | settings.UnderTSetpoint = newFloat; 251 | needEEPROMWrite = true; 252 | Logger::console("Module Temperature Lower Limit set to: %f", settings.UnderTSetpoint); 253 | } 254 | else Logger::console("Invalid temperature lower limit please enter a value between -20.0 and 120.0"); 255 | } else { 256 | Logger::console("Unknown command"); 257 | } 258 | if (needEEPROMWrite) 259 | { 260 | //EEPROM.write(EEPROM_PAGE, settings); 261 | } 262 | } 263 | 264 | void SerialConsole::handleShortCmd() { 265 | uint8_t val; 266 | 267 | switch (cmdBuffer[0]) { 268 | case 'h': 269 | case '?': 270 | case 'H': 271 | printMenu(); 272 | break; 273 | case 'S': 274 | Logger::console("Sleeping all connected boards"); 275 | bms.sleepBoards(); 276 | break; 277 | case 'W': 278 | Logger::console("Waking up all connected boards"); 279 | bms.wakeBoards(); 280 | break; 281 | case 'C': 282 | Logger::console("Clearing all faults"); 283 | bms.clearFaults(); 284 | break; 285 | case 'F': 286 | bms.findBoards(); 287 | break; 288 | case 'R': 289 | Logger::console("Renumbering all boards."); 290 | bms.renumberBoardIDs(); 291 | break; 292 | case 'B': 293 | bms.balanceCells(); 294 | break; 295 | case 'p': 296 | if (whichDisplay == 1 && printPrettyDisplay) whichDisplay = 0; 297 | else 298 | { 299 | printPrettyDisplay = !printPrettyDisplay; 300 | if (printPrettyDisplay) 301 | { 302 | Logger::console("Enabling pack summary display, 5 second interval"); 303 | } 304 | else 305 | { 306 | Logger::console("No longer displaying pack summary."); 307 | } 308 | } 309 | break; 310 | case 'd': 311 | if (whichDisplay == 0 && printPrettyDisplay) whichDisplay = 1; 312 | else 313 | { 314 | printPrettyDisplay = !printPrettyDisplay; 315 | whichDisplay = 1; 316 | if (printPrettyDisplay) 317 | { 318 | Logger::console("Enabling pack details display, 5 second interval"); 319 | } 320 | else 321 | { 322 | Logger::console("No longer displaying pack details."); 323 | } 324 | } 325 | break; 326 | } 327 | } 328 | 329 | /* 330 | if (SERIALCONSOLE.available()) 331 | { 332 | char y = SERIALCONSOLE.read(); 333 | switch (y) 334 | { 335 | case '1': //ascii 1 336 | renumberBoardIDs(); // force renumber and read out 337 | break; 338 | case '2': //ascii 2 339 | SERIALCONSOLE.println(); 340 | findBoards(); 341 | break; 342 | case '3': //activate cell balance for 5 seconds 343 | SERIALCONSOLE.println(); 344 | SERIALCONSOLE.println("Balancing"); 345 | cellBalance(); 346 | break; 347 | case '4': //clear all faults on all boards, required after Reset or FPO (first power on) 348 | SERIALCONSOLE.println(); 349 | SERIALCONSOLE.println("Clearing Faults"); 350 | clearFaults(); 351 | break; 352 | 353 | case '5': //read out the status of first board 354 | SERIALCONSOLE.println(); 355 | SERIALCONSOLE.println("Reading status"); 356 | readStatus(1); 357 | break; 358 | 359 | case '6': //Read out the limit setpoints of first board 360 | SERIALCONSOLE.println(); 361 | SERIALCONSOLE.println("Reading Setpoints"); 362 | readSetpoint(1); 363 | SERIALCONSOLE.println(OVolt); 364 | SERIALCONSOLE.println(UVolt); 365 | SERIALCONSOLE.println(Tset); 366 | break; 367 | 368 | case '0': //Send all boards into Sleep state 369 | Serial.println(); 370 | Serial.println("Sleep Mode"); 371 | sleepBoards(); 372 | break; 373 | 374 | case '9'://Pull all boards out of Sleep state 375 | Serial.println(); 376 | Serial.println("Wake Boards"); 377 | wakeBoards(); 378 | break; 379 | } 380 | } 381 | */ 382 | -------------------------------------------------------------------------------- /SerialConsole.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SerialConsole.h 3 | * 4 | Copyright (c) 2017 EVTV / Collin Kidder 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining 7 | a copy of this software and associated documentation files (the 8 | "Software"), to deal in the Software without restriction, including 9 | without limitation the rights to use, copy, modify, merge, publish, 10 | distribute, sublicense, and/or sell copies of the Software, and to 11 | permit persons to whom the Software is furnished to do so, subject to 12 | the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included 15 | in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 21 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 22 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 23 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | 25 | */ 26 | 27 | #ifndef SERIALCONSOLE_H_ 28 | #define SERIALCONSOLE_H_ 29 | 30 | #include "config.h" 31 | 32 | class SerialConsole { 33 | public: 34 | SerialConsole(); 35 | void loop(); 36 | void printMenu(); 37 | 38 | protected: 39 | enum CONSOLE_STATE 40 | { 41 | STATE_ROOT_MENU 42 | }; 43 | 44 | private: 45 | char cmdBuffer[80]; 46 | int ptrBuffer; 47 | int state; 48 | int loopcount; 49 | bool cancel; 50 | 51 | 52 | void init(); 53 | void serialEvent(); 54 | void handleConsoleCmd(); 55 | void handleShortCmd(); 56 | void handleConfigCmd(); 57 | }; 58 | 59 | #endif /* SERIALCONSOLE_H_ */ 60 | 61 | 62 | -------------------------------------------------------------------------------- /SystemIO.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "config.h" 3 | #include "Logger.h" 4 | #include "SystemIO.h" 5 | 6 | int DigitalInputs[4] = {DIN1, DIN2, DIN3, DIN4}; 7 | int DigitalOutputs[4][2] = { {DOUT1_L, DOUT1_H}, {DOUT2_L, DOUT2_H}, {DOUT3_L, DOUT3_H}, {DOUT4_L, DOUT4_H} }; 8 | 9 | SystemIO::SystemIO() 10 | { 11 | 12 | } 13 | 14 | void SystemIO::setup() 15 | { 16 | for (int x = 0; x < 4; x++) pinMode(DigitalInputs[x], INPUT); 17 | 18 | for (int y = 0; y < 4; y++) { 19 | pinMode(DigitalOutputs[y][0], OUTPUT); 20 | digitalWrite(DigitalOutputs[y][0], LOW); 21 | pinMode(DigitalOutputs[y][1], OUTPUT); 22 | digitalWrite(DigitalOutputs[y][1], LOW); 23 | } 24 | } 25 | 26 | bool SystemIO::readInput(int pin) 27 | { 28 | if (pin < 0 || pin > 3) return false; 29 | return !digitalRead(DigitalInputs[pin]); 30 | } 31 | 32 | void SystemIO::setOutput(int pin, OUTPUTSTATE state) 33 | { 34 | if (pin < 0 || pin > 3) return; 35 | //first set it floating 36 | digitalWrite(DigitalOutputs[pin][0], LOW); 37 | digitalWrite(DigitalOutputs[pin][1], LOW); 38 | delayMicroseconds(10); //give mosfets some time to turn off 39 | if (state == HIGH_12V) digitalWrite(DigitalOutputs[pin][1], HIGH); 40 | if (state == GND) digitalWrite(DigitalOutputs[pin][0], HIGH); 41 | } 42 | 43 | SystemIO systemIO; 44 | -------------------------------------------------------------------------------- /SystemIO.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | enum OUTPUTSTATE { 4 | FLOATING = 0, 5 | HIGH_12V = 1, 6 | GND = 2 7 | }; 8 | 9 | class SystemIO 10 | { 11 | public: 12 | SystemIO(); 13 | void setup(); 14 | bool readInput(int pin); 15 | void setOutput(int pin, OUTPUTSTATE state); 16 | 17 | private: 18 | 19 | }; 20 | 21 | extern SystemIO systemIO; 22 | -------------------------------------------------------------------------------- /TeslaBMS-ESP32.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include "Logger.h" 3 | #include "SerialConsole.h" 4 | #include "BMSModuleManager.h" 5 | #include "SystemIO.h" 6 | #include 7 | 8 | #define BMS_BAUD 612500 9 | //#define BMS_BAUD 617647 10 | //#define BMS_BAUD 608695 11 | 12 | //HardwareSerial Serial2(1); //define second serial port for Tesla BMS board 13 | 14 | BMSModuleManager bms; 15 | EEPROMSettings settings; 16 | SerialConsole console; 17 | uint32_t lastUpdate; 18 | 19 | void loadSettings() 20 | { 21 | Logger::console("Resetting to factory defaults"); 22 | settings.version = EEPROM_VERSION; 23 | settings.checksum = 0; 24 | settings.canSpeed = 500000; 25 | settings.batteryID = 0x01; //in the future should be 0xFF to force it to ask for an address 26 | settings.OverVSetpoint = 4.1f; 27 | settings.UnderVSetpoint = 2.3f; 28 | settings.OverTSetpoint = 65.0f; 29 | settings.UnderTSetpoint = -10.0f; 30 | settings.balanceVoltage = 3.9f; 31 | settings.balanceHyst = 0.04f; 32 | settings.logLevel = 2; 33 | 34 | Logger::setLoglevel((Logger::LogLevel)settings.logLevel); 35 | } 36 | 37 | void initializeCAN() 38 | { 39 | uint32_t id; 40 | CAN0.begin(settings.canSpeed); 41 | if (settings.batteryID < 0xF) 42 | { 43 | //Setup filter for direct access to our registered battery ID 44 | id = (0xBAul << 20) + (((uint32_t)settings.batteryID & 0xF) << 16); 45 | CAN0.setRXFilter(0, id, 0x1FFF0000ul, true); 46 | //Setup filter for request for all batteries to give summary data 47 | id = (0xBAul << 20) + (0xFul << 16); 48 | CAN0.setRXFilter(1, id, 0x1FFF0000ul, true); 49 | } 50 | } 51 | 52 | void setup() 53 | { 54 | delay(2000); //just for easy debugging. It takes a few seconds for USB to come up properly on most OS's 55 | SERIALCONSOLE.begin(115200); 56 | SERIALCONSOLE.println("Starting up!"); 57 | SERIAL.begin(BMS_BAUD, SERIAL_8N1, 4, 2); //rx pin is 4, tx pin is 2 58 | 59 | SERIALCONSOLE.println("Started serial interface to BMS."); 60 | 61 | pinMode(13, INPUT); 62 | 63 | Serial.println("Load EEPROM"); 64 | loadSettings(); 65 | Serial.println("Initialize CAN"); 66 | initializeCAN(); 67 | Serial.println("System IO setup"); 68 | //systemIO.setup(); 69 | 70 | Serial.println("Init BMS board numbers"); 71 | bms.renumberBoardIDs(); 72 | 73 | //Logger::setLoglevel(Logger::Debug); 74 | 75 | lastUpdate = 0; 76 | 77 | Serial.println("BMS clear faults"); 78 | bms.clearFaults(); 79 | Serial.println("End of setup"); 80 | Serial.println("Send ? line to get help. d to get detailed updates, p to get summary updates."); 81 | delay(1000); 82 | } 83 | 84 | void loop() 85 | { 86 | CAN_FRAME incoming; 87 | 88 | console.loop(); 89 | 90 | if (millis() > (lastUpdate + 1000)) 91 | { 92 | lastUpdate = millis(); 93 | bms.balanceCells(); 94 | bms.getAllVoltTemp(); 95 | } 96 | 97 | if (CAN0.available()) { 98 | CAN0.read(incoming); 99 | bms.processCANMsg(incoming); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /config.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | extern HardwareSerial Serial2; 6 | 7 | //Set to the proper port for your USB connection - SerialUSB on Due (Native) or Serial for Due (Programming) or Teensy 8 | #define SERIALCONSOLE Serial 9 | 10 | //Define this to be the serial port the Tesla BMS modules are connected to. 11 | //On the Due you need to use a USART port (Serial1, Serial2, Serial3) and update the call to serialSpecialInit if not Serial1 12 | #define SERIAL Serial2 13 | 14 | #define REG_DEV_STATUS 0 15 | #define REG_GPAI 1 16 | #define REG_VCELL1 3 17 | #define REG_VCELL2 5 18 | #define REG_VCELL3 7 19 | #define REG_VCELL4 9 20 | #define REG_VCELL5 0xB 21 | #define REG_VCELL6 0xD 22 | #define REG_TEMPERATURE1 0xF 23 | #define REG_TEMPERATURE2 0x11 24 | #define REG_ALERT_STATUS 0x20 25 | #define REG_FAULT_STATUS 0x21 26 | #define REG_COV_FAULT 0x22 27 | #define REG_CUV_FAULT 0x23 28 | #define REG_ADC_CTRL 0x30 29 | #define REG_IO_CTRL 0x31 30 | #define REG_BAL_CTRL 0x32 31 | #define REG_BAL_TIME 0x33 32 | #define REG_ADC_CONV 0x34 33 | #define REG_ADDR_CTRL 0x3B 34 | 35 | #define MAX_MODULE_ADDR 0x3E 36 | 37 | #define EEPROM_VERSION 0x10 //update any time EEPROM struct below is changed. 38 | #define EEPROM_PAGE 0 39 | 40 | #define DIN1 55 41 | #define DIN2 54 42 | #define DIN3 57 43 | #define DIN4 56 44 | #define DOUT4_H 2 45 | #define DOUT4_L 3 46 | #define DOUT3_H 4 47 | #define DOUT3_L 5 48 | #define DOUT2_H 6 49 | #define DOUT2_L 7 50 | #define DOUT1_H 8 51 | #define DOUT1_L 9 52 | 53 | typedef struct { 54 | uint8_t version; 55 | uint8_t checksum; 56 | uint32_t canSpeed; 57 | uint8_t batteryID; //which battery ID should this board associate as on the CAN bus 58 | uint8_t logLevel; 59 | float OverVSetpoint; 60 | float UnderVSetpoint; 61 | float OverTSetpoint; 62 | float UnderTSetpoint; 63 | float balanceVoltage; 64 | float balanceHyst; 65 | } EEPROMSettings; 66 | --------------------------------------------------------------------------------