├── README.md ├── ReadBatmon ├── Batmon.cpp ├── Batmon.h ├── Batmon_struct.h ├── FastCRC.zip └── ReadBatmon.ino └── Tutorials ├── programming_tutorial └── README.md └── running_multiple_batmons └── README.md /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Rotoye Batmon 3 | 4 | ## _The Dumb-Battery Era is Over_ 5 | ![](https://rotoye.com/wp-content/uploads/2021/04/Battery-Pack-v5.png) 6 | 7 | ## Getting Started 8 | 9 | When you receive a battery with integrated BATMON, the OLED display (if applicable) and red/green LED's should be flashing periodically, displaying the battery state of charge, the min and max cell voltages, and current in Amps. The button is used to cycle through screens. 10 | 11 | _**If you see the < ! > symbol on the OLED display, this means that one or more safety warning conditions have been met. Press the button to see the warning(s) present.**_ 12 | 13 | ### Buzzer/OLED board 14 | ![](https://rotoye.com/wp-content/uploads/2021/04/Batmon-v5-programming-1-1536x1239.jpg) 15 | The optional OLED display/buzzer attachment extends BATMON functionality by providing a user-friendly interface for displaying state of charge, voltages, warnings, and more. The small board is attached to the main BATMON board via a flex connector. This connector is rated for a limited number of plug-unplug cycles. It is recommended to attach/detach this accessory as little, and as carefully as possible. 16 | 17 | ## Interfacing with an Autopilot 18 | ### Wiring to autopilot 19 | 20 | ### Wiring to cells 21 | Ensure that you have received BATMON with the right cell count as your batteries. You may connect the JST balance leads of a battery to BATMON to power it up. Ensure that the negative of the battery is connected to the negative terminal on the power board(the top board with the Prolanv connector). You'd need this connection to power up BATMON even if you are just testing the board. 22 | 23 | ### PX4 24 | #### Stable 25 | 26 | BATMON is supported on the stock firmware of PX4 since the [v1.12.2](https://github.com/PX4/PX4-Autopilot/releases/tag/v1.12.2) version. 27 | 28 | Connect SMBUS/I2C of BATMON to the appropriate bus on the autopilot. 29 | 30 | In ground control software of choice, set 31 | 32 | - BAT_SOURCE parameter to "External" 33 | - BATMON_DRIVER_EN to 1 34 | 35 | Start BATMON on nsh using the script: 36 | 37 | `batmon start -X` * Search all the external bus and connect to the first BATMON detected 38 | 39 | `batmon start -X -b 1 -a 11` * External bus 1, address 0x0b 40 | 41 | `batmon start -X -b 1 -a 12` * External bus 1, address 0x0c 42 | 43 | ![batmon start](https://rotoye.com/wp-content/uploads/BATMON/batmon-start.png) 44 | 45 | I2C devices connected on the different bus can be identified by running nsh script: 46 | `i2cdetect -b ` 47 | 48 | ![i2cdetect](https://rotoye.com/wp-content/uploads/BATMON/i2cdetect-screenshot.png) 49 | 50 | *battery_status* uorb messages may be inspected using the command 51 | `listener battery_status 1` 52 | ![listening to batmon uorb](https://rotoye.com/wp-content/uploads/BATMON/listener.png) 53 | ### Ardupilot 54 | 55 | Clone or download [Ardupilot firmware](https://github.com/ArduPilot/ardupilot) 56 | 57 | Check out the master branch, [build from source](https://ardupilot.org/dev/docs/building-the-code.html), and flash the firmware. **BATMON support is not yet in a stable release of Ardupilot.** 58 | 59 | In ground control software of choice, manually set the BATTx_MONITOR parameter to 19 (or select Rotoye by name, if enumerated), and select the appropriate BATTx_BUS (0-3), where *x* is the Batt number (multiple Smart Batteries can be used at once!) 60 | 61 | See the [full list](https://ardupilot.org/copter/docs/parameters.html#batt2-parameters) of parameters for more detail [![](https://camo.githubusercontent.com/4e0d13de8634b0ae88226aba1f015ae81a342f7f/68747470733a2f2f6c68362e676f6f676c6575736572636f6e74656e742e636f6d2f6a477257786b4d4b6f384e495f49764f6d6d665a6a334f6c644b4537477051666c5253756c6f45514b3652456b30797a47325a6e717244506f6d48565479574d68386e7447594838476c533139774d5f736d6c4438495732717a6e324f544d4346756d772d7243674e4c2d46496936596b7032785f717853724a506a7337316d747573564d336454)](https://camo.githubusercontent.com/4e0d13de8634b0ae88226aba1f015ae81a342f7f/68747470733a2f2f6c68362e676f6f676c6575736572636f6e74656e742e636f6d2f6a477257786b4d4b6f384e495f49764f6d6d665a6a334f6c644b4537477051666c5253756c6f45514b3652456b30797a47325a6e717244506f6d48565479574d68386e7447594838476c533139774d5f736d6c4438495732717a6e324f544d4346756d772d7243674e4c2d46496936596b7032785f717853724a506a7337316d747573564d336454) 62 | 63 | ### Ground Control: 64 | 65 | You should now be able to verify through a tool such as MAVLink Inspector in QGroundControl that Batmon is sending data to the autopilot: [![](https://camo.githubusercontent.com/b2bfaff53eeb6a400d0f91813a3dc36f3d63df9a/68747470733a2f2f6c68352e676f6f676c6575736572636f6e74656e742e636f6d2f56304a737455317947524632544435665f6a4730725464314730414f64796c6e4261334d786b454257444472425549396d687563714430794c5936506b4c38614d49694e67734f4a77345576334a5054476753562d336e4d647045595f796f4d6a6c692d79725044547165346c315268534d697044694b6478314c643964465171676a3061334c7a)](https://camo.githubusercontent.com/b2bfaff53eeb6a400d0f91813a3dc36f3d63df9a/68747470733a2f2f6c68352e676f6f676c6575736572636f6e74656e742e636f6d2f56304a737455317947524632544435665f6a4730725464314730414f64796c6e4261334d786b454257444472425549396d687563714430794c5936506b4c38614d49694e67734f4a77345576334a5054476753562d336e4d647045595f796f4d6a6c692d79725044547165346c315268534d697044694b6478314c643964465171676a3061334c7a) 66 | 67 | ## Arduino Library: 68 | 69 | ### ReadBatmon: Arduino code to access the Batmon battery monitor. 70 | 71 | Instruction to run ReadBatmon 72 | 73 | - Copy the ReadBatmon folder to local directory. 74 | - Open the sketch using Arduino IDE. 75 | - Open Sketch->Include Library-> Add .ZIP library 76 | - Give location of FastCRC.zip file. The FastCRC library would now be added. 77 | - Connect the Power, GND, SDA, SCL lines from Arduino to Batmon 78 | - Connect the cells to BATMON 79 | - Upload code to Arduino 80 | - Run serial monitor to see output 81 | ![batmon_reader output](https://rotoye.com/wp-content/uploads/BATMON/batmon_reader-serial-output.png) 82 | -------------------------------------------------------------------------------- /ReadBatmon/Batmon.cpp: -------------------------------------------------------------------------------- 1 | #include "Batmon.h" 2 | #include 3 | 4 | Batmon::Batmon(byte _i2cAddress, byte _numTherms) 5 | { 6 | i2cAddress = _i2cAddress; 7 | numTherms = _numTherms; 8 | } 9 | 10 | //byte Batmon::shutdown() 11 | //{ 12 | // Wire.beginTransmission(i2cAddress); 13 | // Wire.write(SHUTDOWN); 14 | // return Wire.endTransmission(); 15 | //} 16 | //byte Batmon::powerup() 17 | //{ 18 | // Wire.beginTransmission(i2cAddress); 19 | // Wire.write(POWERUP); 20 | // return Wire.endTransmission(); 21 | //} 22 | 23 | //////////////////////////////// 24 | // Return 0: No error 25 | // 1: CRC error 26 | // 2: i2c error 27 | // 3: status error; read status 28 | byte Batmon::readCellVoltages(CVolts &cv) 29 | { 30 | unsigned short *ptr; 31 | ptr = (unsigned short *)&cv.VCell1; 32 | uint8_t i ; 33 | int cellCount; 34 | cellCount = getCellCount(); 35 | for(i = 0; i < cellCount; i++) 36 | { 37 | Wire.beginTransmission(i2cAddress); 38 | Wire.write(SMBUS_VCELL1 - i); 39 | 40 | // endTransmission return 41 | // Output 0 .. success 42 | // 1 .. length to long for buffer 43 | // 2 .. address send, NACK received 44 | // 3 .. data send, NACK received 45 | // 4 .. other twi error (lost bus arbitration, bus error, ..) 46 | unsigned char ret = Wire.endTransmission(false); 47 | switch(ret) 48 | { 49 | case 0: 50 | break; 51 | case 1: 52 | case 2: 53 | case 4: 54 | Wire.endTransmission(true); 55 | return 2; 56 | case 3: // Its upto the user on whether to read the data in this case. Sometimes, you might want to read it, sometime not 57 | Wire.endTransmission(true); 58 | return 3; 59 | } 60 | 61 | unsigned char readNum = 3; // CRC + size 62 | if(Wire.requestFrom(i2cAddress, readNum) == readNum) 63 | { 64 | ptr[i ] = Wire.read(); 65 | ptr[i ] += Wire.read() << 8; 66 | Wire.read(); 67 | } 68 | else 69 | return 2; 70 | } 71 | //if(CRC8.smbus(ptr,10) == cv.CRC) 72 | //{ 73 | // return 0; 74 | //} 75 | //else 76 | // return 1; 77 | return 0; 78 | } 79 | 80 | //////////////////////////////// 81 | // Return 0: No error 82 | // 1: CRC error 83 | // 2: i2c error 84 | // 3: status error without CRC error 85 | // 4: status error with CRC error 86 | // Check Batmon_struct.h for &st values 87 | byte Batmon::readStatus(byte &st) 88 | { 89 | Wire.beginTransmission(i2cAddress); 90 | Wire.write(SMBUS_SAFETY_STATUS); 91 | 92 | // endTransmission return 93 | // Output 0 .. success 94 | // 1 .. length to long for buffer 95 | // 2 .. address send, NACK received 96 | // 3 .. data send, NACK received 97 | // 4 .. other twi error (lost bus arbitration, bus error, ..) 98 | unsigned char ret = Wire.endTransmission(false); 99 | switch(ret) 100 | { 101 | case 0: 102 | case 3: 103 | break; 104 | case 1: 105 | case 2: 106 | case 4: 107 | Wire.endTransmission(true); 108 | return 2; 109 | } 110 | 111 | unsigned char readNum = 1 + 1; // CRC + size 112 | if(Wire.requestFrom(i2cAddress, readNum) == readNum) 113 | { 114 | st = Wire.read(); 115 | if(CRC8.smbus(&st,1) == Wire.read()) 116 | { 117 | if(ret == 3) 118 | return 3; 119 | else 120 | return 0; 121 | } 122 | else 123 | { 124 | if(ret == 3) 125 | return 4; 126 | else 127 | return 1; 128 | } 129 | } 130 | else 131 | return 2; 132 | } 133 | 134 | //////////////////////////////// 135 | // Return 0: No error 136 | // 1: CRC error 137 | // 2: i2c error 138 | // 3: status error; read status 139 | byte Batmon::readTotalVoltage(TotVolt &tv) 140 | { 141 | Wire.beginTransmission(i2cAddress); 142 | Wire.write(SMBUS_VOLTAGE); 143 | 144 | // endTransmission return 145 | // Output 0 .. success 146 | // 1 .. length to long for buffer 147 | // 2 .. address send, NACK received 148 | // 3 .. data send, NACK received 149 | // 4 .. other twi error (lost bus arbitration, bus error, ..) 150 | unsigned char ret = Wire.endTransmission(false); 151 | switch(ret) 152 | { 153 | case 0: 154 | break; 155 | case 1: 156 | case 2: 157 | case 4: 158 | Wire.endTransmission(true); 159 | return 2; 160 | case 3: // Its upto the user on whether to read the data in this case. Sometimes, you might want to read it, sometime not 161 | Wire.endTransmission(true); 162 | return 3; 163 | } 164 | 165 | unsigned char readNum = sizeof(tv); // CRC + size 166 | if(Wire.requestFrom(i2cAddress, readNum) == readNum) 167 | { 168 | tv.TV.VTotByte.VTot_HI = Wire.read(); 169 | tv.TV.VTotByte.VTot_LO = Wire.read(); 170 | tv.CRC = Wire.read(); 171 | unsigned char *ptr = (unsigned char *)&tv.TV.VTotWord; 172 | if(CRC8.smbus(ptr, sizeof(tv.TV.VTotWord)) == tv.CRC) 173 | { 174 | return 0; 175 | } 176 | else 177 | return 1; 178 | } 179 | else 180 | return 2; 181 | } 182 | 183 | byte Batmon::readTherms(Therms &ts) 184 | { 185 | return readTherms(ts, numTherms); 186 | } 187 | 188 | //////////////////////////////// 189 | // Return 0: No error 190 | // 1: CRC error 191 | // 2: i2c error 192 | // 3: status error; read status 193 | byte Batmon::readTherms(Therms &ts, byte num) 194 | { 195 | if(num > 2) 196 | return 3; 197 | 198 | Wire.beginTransmission(i2cAddress); 199 | if(num == 0) 200 | { 201 | Wire.write(SMBUS_TEMP_INT); 202 | } 203 | else if(num == 1) 204 | { 205 | Wire.write(SMBUS_TEMP_EXTERNAL_1); 206 | } 207 | else if(num == 2) 208 | { 209 | Wire.write(SMBUS_TEMP_EXTERNAL_2); 210 | } 211 | 212 | // endTransmission return 213 | // Output 0 .. success 214 | // 1 .. length to long for buffer 215 | // 2 .. address send, NACK received 216 | // 3 .. data send, NACK received 217 | // 4 .. other twi error (lost bus arbitration, bus error, ..) 218 | unsigned char ret = Wire.endTransmission(false); 219 | switch(ret) 220 | { 221 | case 0: 222 | break; 223 | case 1: 224 | case 2: 225 | case 4: 226 | Wire.endTransmission(true); 227 | return 2; 228 | case 3: // Its upto the user on whether to read the data in this case. Sometimes, you might want to read it, sometime not 229 | Wire.endTransmission(true); 230 | return 3; 231 | } 232 | 233 | unsigned char readNum = sizeof(ts.T_int) * (num + 1) + 1; // CRC + size 234 | if(Wire.requestFrom(i2cAddress, readNum) == readNum) 235 | { 236 | unsigned char *ptr; 237 | if(num == 0) 238 | { 239 | ptr = (unsigned char *)&ts.T_int; 240 | } 241 | else if(num == 1) 242 | { 243 | ptr = (unsigned char *)&ts.T1; 244 | } 245 | else if(num == 2) 246 | { 247 | ptr = (unsigned char *)&ts.T2; 248 | } 249 | 250 | for(int i = 0; i < readNum; i++) 251 | { 252 | ptr[i] = Wire.read(); 253 | } 254 | 255 | if (CRC8.smbus(ptr, readNum-1) == ts.CRC) 256 | { 257 | return 0; 258 | } 259 | else 260 | return 1; 261 | } 262 | else 263 | return 2; 264 | } 265 | 266 | unsigned char* Batmon::getMan(unsigned char *buf) 267 | { 268 | Wire.beginTransmission(i2cAddress); 269 | Wire.write(0x20); 270 | Wire.endTransmission(); 271 | int i = 0; 272 | if(Wire.requestFrom(i2cAddress, 8)) 273 | { 274 | while(Wire.available()) 275 | { 276 | buf[i] = Wire.read(); 277 | i++; 278 | } 279 | } 280 | return buf; 281 | } 282 | 283 | int Batmon::getCur() 284 | { 285 | int current; 286 | Wire.beginTransmission(i2cAddress); 287 | Wire.write(SMBUS_CURRENT); 288 | Wire.endTransmission(); 289 | if(Wire.requestFrom(i2cAddress, 3)) 290 | { 291 | current = (int)Wire.read(); 292 | current |= (int)Wire.read() << 8; 293 | Wire.read(); 294 | } 295 | return current; 296 | } 297 | 298 | int Batmon::getDeciCur() 299 | { 300 | int current; 301 | Wire.beginTransmission(i2cAddress); 302 | Wire.write(SMBUS_DECI_CURRENT); 303 | Wire.endTransmission(); 304 | if(Wire.requestFrom(i2cAddress, 3)) 305 | { 306 | current = (int)Wire.read(); 307 | current |= (int)Wire.read() << 8; 308 | Wire.read(); 309 | } 310 | return current; 311 | } 312 | 313 | // deciCelcius output 314 | int Batmon::getTInt() 315 | { 316 | int t; 317 | Wire.beginTransmission(i2cAddress); 318 | Wire.write(SMBUS_TEMP_INT); 319 | Wire.endTransmission(); 320 | if(Wire.requestFrom(i2cAddress, 3)) 321 | { 322 | t = (int)Wire.read(); 323 | t |= (int)Wire.read() << 8; 324 | Wire.read(); 325 | t = t - 2731; 326 | } 327 | return t; 328 | } 329 | // deciCelcius output 330 | int Batmon::getTExt(byte extThermNum) 331 | { 332 | int t; 333 | Wire.beginTransmission(i2cAddress); 334 | switch(extThermNum) 335 | { 336 | case 0: 337 | Wire.write(SMBUS_TEMP_EXTERNAL_1); 338 | break; 339 | case 1: 340 | Wire.write(SMBUS_TEMP_EXTERNAL_2); 341 | break; 342 | } 343 | Wire.endTransmission(); 344 | if(Wire.requestFrom(i2cAddress, 3)) 345 | { 346 | t = (int)Wire.read(); 347 | t |= (int)Wire.read() << 8; 348 | Wire.read(); 349 | t = t - 2731; 350 | } 351 | return t; 352 | } 353 | 354 | int16_t Batmon::read_mAh_discharged() 355 | { 356 | uint8_t num = 2; 357 | char discharged[num]; 358 | Wire.beginTransmission(i2cAddress); 359 | Wire.write(SMBUS_MAH_DISCHARGED); 360 | Wire.endTransmission(); 361 | if(Wire.requestFrom(i2cAddress, num + 1)) 362 | { 363 | discharged[0] = Wire.read(); 364 | discharged[1] = Wire.read(); 365 | //discharged[2] = Wire.read(); 366 | //discharged[3] = Wire.read(); 367 | Wire.read(); //throw out crc 368 | } 369 | return *((int16_t *)discharged); 370 | } 371 | 372 | unsigned int Batmon::getSOC() 373 | { 374 | unsigned int soc; 375 | Wire.beginTransmission(i2cAddress); 376 | Wire.write(SMBUS_RELATIVE_SOC); 377 | Wire.endTransmission(); 378 | if(Wire.requestFrom(i2cAddress, 3)) 379 | { 380 | soc = (unsigned int)Wire.read(); 381 | soc |= (unsigned int)Wire.read() << 8; 382 | Wire.read();// throw out crc 383 | } 384 | return soc; 385 | } 386 | 387 | unsigned int Batmon::readRemainCap() 388 | { 389 | unsigned int cap; 390 | Wire.beginTransmission(i2cAddress); 391 | Wire.write(SMBUS_REMAIN_CAP); 392 | Wire.endTransmission(); 393 | if(Wire.requestFrom(i2cAddress, 3)) 394 | { 395 | cap = (unsigned int)Wire.read(); 396 | cap |= (unsigned int)Wire.read() << 8; 397 | Wire.read();// throw out crc 398 | } 399 | return cap; 400 | } 401 | 402 | uint8_t Batmon::getCellCount() 403 | { 404 | unsigned int cellCount; 405 | Wire.beginTransmission(i2cAddress); 406 | Wire.write(SMBUS_CELL_COUNT); 407 | Wire.endTransmission(); 408 | if(Wire.requestFrom(i2cAddress, 3)) 409 | { 410 | cellCount = (unsigned int)Wire.read(); 411 | cellCount |= (unsigned int)Wire.read() << 8; 412 | Wire.read();// throw out crc 413 | } 414 | return cellCount; 415 | } 416 | 417 | uint16_t Batmon::getHash() 418 | { 419 | uint16_t hash; 420 | Wire.beginTransmission(i2cAddress); 421 | Wire.write(SMBUS_SERIAL_NUM); 422 | Wire.endTransmission(); 423 | if(Wire.requestFrom(i2cAddress, 3)) 424 | { 425 | hash = (uint16_t)Wire.read(); 426 | hash |= (uint16_t)Wire.read() << 8; 427 | Wire.read();// throw out crc 428 | } 429 | return hash; 430 | } 431 | // Return true if successfully read the serial number 432 | bool Batmon::getSN(uint16_t sn[8]) 433 | { 434 | Wire.beginTransmission(i2cAddress); 435 | Wire.write(SMBUS_MANUFACTURER_DATA); 436 | Wire.endTransmission(); 437 | // TODO: should BATMON return less than 18 for firmware without full serial number? 438 | if(Wire.requestFrom(i2cAddress, 18) == 18) 439 | { 440 | if(Wire.read() != 16) 441 | return false; 442 | uint16_t sn1, sn2, sn3, sn4, sn5, sn6, sn7, sn8; 443 | sn[0] = Wire.read() | Wire.read() << 8; 444 | sn[1] = Wire.read() | Wire.read() << 8; 445 | sn[2] = Wire.read() | Wire.read() << 8; 446 | sn[3] = Wire.read() | Wire.read() << 8; 447 | sn[4] = Wire.read() | Wire.read() << 8; 448 | sn[5] = Wire.read() | Wire.read() << 8; 449 | sn[6] = Wire.read() | Wire.read() << 8; 450 | sn[7] = Wire.read() | Wire.read() << 8; 451 | Wire.read(); 452 | return true; 453 | } 454 | return false; 455 | } 456 | -------------------------------------------------------------------------------- /ReadBatmon/Batmon.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Batmon_struct.h" 3 | #include 4 | #include 5 | 6 | typedef Batmon_cellVoltages CVolts; 7 | typedef Batmon_thermistors Therms; 8 | typedef Batmon_totalVoltage TotVolt; 9 | class Batmon 10 | { 11 | FastCRC8 CRC8; 12 | public: 13 | Batmon(byte _i2cAddress, byte _numTherms); 14 | byte readCellVoltages(CVolts &cv); 15 | byte readTotalVoltage(TotVolt &tv); 16 | uint8_t getCellCount(); 17 | byte readTherms(Therms &ts); 18 | byte readStatus(byte &st); 19 | int16_t read_mAh_discharged(); 20 | unsigned int readRemainCap(); 21 | byte shutdown(); 22 | byte powerup(); 23 | unsigned char *getMan(unsigned char *buf); 24 | int getCur(); 25 | int getDeciCur(); 26 | unsigned int getSOC(); 27 | int getTInt(); 28 | int getTExt(byte extThermNum); 29 | uint16_t getHash(); 30 | bool getSN(uint16_t sn[8]); 31 | private: 32 | byte i2cAddress; 33 | byte numTherms; 34 | byte readTherms(Therms &ts, byte num); 35 | }; 36 | -------------------------------------------------------------------------------- /ReadBatmon/Batmon_struct.h: -------------------------------------------------------------------------------- 1 | /************************************************************************************************** 2 | * 3 | * Copyright (c) 2020-2022, SkyMul Inc. 4 | * All Rights Reserved. 5 | * 6 | * BATMON and its associated brands and logos published in the website and source code 7 | * are Trademarks of SkyMul Inc. 8 | * 9 | * Redistribution and use in source and binary forms, with or without 10 | * modification, are permitted provided that the following conditions are met: 11 | * 12 | * * Adheres to the rules of GNU GENERAL PUBLIC LICENSE, Version 3 13 | * 14 | * * Redistributions of source code and the binary form must retain this copyright notice, 15 | * this list of conditions and disclaimer in the documentation and literature provided with 16 | * the distribution. 17 | * 18 | * * Neither the name of the copyright holder nor the names of its contributors may be used to 19 | * endorse or promote products derived from this software without specific prior written 20 | * permission. 21 | * 22 | **************************************************************************************************/ 23 | 24 | /** 25 | * @file Batmon_struct.h 26 | * 27 | * @author Eohan George 28 | */ 29 | 30 | #pragma once 31 | 32 | typedef unsigned char uint8_t; 33 | typedef long unsigned int uint32_t; 34 | 35 | #define BATMON_SMBUS_TOTAL_ADDRESS 10 36 | const uint8_t BATMON_SMBUS_ADDRESS_ARRAY[BATMON_SMBUS_TOTAL_ADDRESS] = {0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14}; 37 | // The default SMBUS address is first one. Other values can be selected based on resistance connected to CAN_ID pin 38 | // {Inf, 59k, 30k, 20k, 15k, 8.45k, 6.49k, 4.42k, 1.8k, 0} 39 | #define ADC_TOTAL_THRESHOLD (BATMON_SMBUS_TOTAL_ADDRESS-1) 40 | const uint8_t ADC_READING_THRESHOLD_ARRAY[ADC_TOTAL_THRESHOLD] = {13, 54, 84, 110, 132, 153, 172, 201, 230}; 41 | #define NUM_THERM_TO_READ 2 // Number of external thermistor to read 42 | //SMBUS Register enumeration 43 | enum smbus_reg : unsigned char 44 | { 45 | SMBUS_VOLTAGE = 0x09, // 46 | SMBUS_CURRENT = 0x0a, // 47 | SMBUS_AVG_CURRENT = 0x0b, // Not implemented 48 | SMBUS_TEMP_INT = 0x08, // 49 | SMBUS_MAN_NAME = 0x20, // 50 | SMBUS_MAN_DATE = 0x1b, 51 | SMBUS_SERIAL_NUM = 0x1c, // Send a hashed serial number based on SAM device's UID 52 | SMBUS_MANUFACTURER_DATA = 0x23, // Send the full 128 bit SAM device UID, SBS:ManufacturerData <128bit serial number> 53 | SMBUS_RUN_TIME_TO_EMPTY = 0x11, // Not implemented 54 | SMBUS_AVG_TIME_TO_EMPTY = 0x12, // Not implemented 55 | SMBUS_CHG_CURRENT = 0x14, // 56 | SMBUS_CHG_VOLTAGE = 0x15, // 57 | SMBUS_BATT_STATUS = 0x16, // Only over-temp alarm is implemented 58 | SMBUS_RELATIVE_SOC = 0x0d, // 59 | SMBUS_REMAIN_CAP = 0x0f, // 60 | SMBUS_FULL_CAP = 0x10, // 61 | SMBUS_CYCLE_COUNT = 0x17, // 62 | SMBUS_VCELL1 = 0x3f, // 63 | SMBUS_VCELL2 = 0x3e, // Same as above 64 | SMBUS_VCELL3 = 0x3d, // Same as above 65 | SMBUS_VCELL4 = 0x3c, // Same as above 66 | SMBUS_VCELL5 = 0x3b, // Same as above 67 | SMBUS_VCELL6 = 0x3a, // Same as above 68 | SMBUS_VCELL7 = 0x39, // Same as above 69 | SMBUS_VCELL8 = 0x38, // Same as above 70 | SMBUS_VCELL9 = 0x37, // Same as above 71 | SMBUS_VCELL10 = 0x36, // Same as above 72 | SMBUS_VCELL11 = 0x35, // Same as above 73 | SMBUS_VCELL12 = 0x34, // Same as above 74 | SMBUS_CELL_COUNT = 0x40, // 75 | SMBUS_DECI_CURRENT = 0x41, // 76 | SMBUS_TEMP_EXTERNAL_1 = 0x48, // 77 | SMBUS_TEMP_EXTERNAL_2 = 0X49, // 78 | SMBUS_SAFETY_STATUS = 0x51, // 79 | SMBUS_ALERT_STATUS = 0x50, // Not implemented 80 | SMBUS_MAH_DISCHARGED = 0x4f, 81 | 82 | //EEPROM parameter addresses 83 | EEPROM_SHUNT_VAL_SET = 0, 84 | EEPROM_CAPACITY_SET = 2, 85 | EEPROM_EST_SET = 4, 86 | 87 | //Defines for BATMON specific I2C functionality : Not using this now. 88 | BATMON_MAIN_RESET_ADDRESS = 0x90, 89 | BATMON_BQ_RESET_ADDRESS = 0x91, 90 | BATMON_SHUNT_VAL_SET_ADDRESS = 0x92, 91 | BATMON_CAPACITY_SET_ADDRESS = 0x93 92 | }; 93 | 94 | //For reference: pixhawk must have 8,9,a,b,f,10,11,12,17,1b,1c,20,3c to 45 reserved 95 | //#define READTHERM0 0x10 96 | //#define READTHERM1 0x11 97 | //#define READTHERM2 0x12 98 | //#define READTOTVOLTAGE 0x13 99 | //#define READCELLVOLTAGES 0x14 100 | //#define READSTATUS 0x15 101 | //#define SHUTDOWN 0x16 102 | //#define POWERUP 0x17 103 | //Can't use these because conflicts 104 | 105 | 106 | // STATUS messages 107 | #define BATMON_NOT_BOOTED 0x43 108 | #define ADC_CHIP_CONNECTION_ERROR 0x44 109 | #define ADC_CANT_WRITE_CHIP 0x45 110 | #define ADC_CHIP_NOT_FOUND 0x46 111 | #define ADC_I2C_ERROR 0x47 112 | #define BATMON_READY 0x48 113 | #define DEF_ERROR 0x49 114 | #define BATMON_SLEEPING 0x40 115 | 116 | //typedef struct _Batmon_struct 117 | struct Batmon_thermistors 118 | { 119 | union 120 | { 121 | struct 122 | { 123 | unsigned char T_HI; 124 | unsigned char T_LO; 125 | }TByte; 126 | unsigned short TWord; // temperature *10 (deg C) 127 | }T2,T1,T_int; 128 | unsigned char CRC; 129 | }; 130 | 131 | struct Batmon_totalVoltage 132 | { 133 | union 134 | { 135 | struct 136 | { 137 | unsigned char VTot_HI; 138 | unsigned char VTot_LO; 139 | }VTotByte; 140 | unsigned short VTotWord; //mV 141 | }TV; 142 | unsigned char CRC; 143 | }; 144 | 145 | struct Batmon_cellVoltages 146 | { 147 | union 148 | { 149 | struct 150 | { 151 | unsigned char VC1_HI; 152 | unsigned char VC1_LO; 153 | }VCell1Byte; 154 | unsigned short VCell1Word; //mV 155 | }VCell1; 156 | union 157 | { 158 | struct 159 | { 160 | unsigned char VC2_HI; 161 | unsigned char VC2_LO; 162 | }VCell2Byte; 163 | unsigned short VCell2Word; //mV 164 | }VCell2; 165 | union 166 | { 167 | struct 168 | { 169 | unsigned char VC3_HI; 170 | unsigned char VC3_LO; 171 | }VCell3Byte; 172 | unsigned short VCell3Word; //mV 173 | }VCell3; 174 | union 175 | { 176 | struct 177 | { 178 | unsigned char VC4_HI; 179 | unsigned char VC4_LO; 180 | }VCell4Byte; 181 | unsigned short VCell4Word; //mV 182 | }VCell4; 183 | union 184 | { 185 | struct 186 | { 187 | unsigned char VC5_HI; 188 | unsigned char VC5_LO; 189 | }VCell5Byte; 190 | unsigned short VCell5Word; 191 | }VCell5; 192 | union 193 | { 194 | struct 195 | { 196 | unsigned char VC6_HI; 197 | unsigned char VC6_LO; 198 | }VCell6Byte; 199 | unsigned short VCell6Word; 200 | }VCell6; 201 | union 202 | { 203 | struct 204 | { 205 | unsigned char VC7_HI; 206 | unsigned char VC7_LO; 207 | }VCell7Byte; 208 | unsigned short VCell7Word; 209 | }VCell7; 210 | union 211 | { 212 | struct 213 | { 214 | unsigned char VC8_HI; 215 | unsigned char VC8_LO; 216 | }VCell8Byte; 217 | unsigned short VCell8Word; 218 | }VCell8; 219 | union 220 | { 221 | struct 222 | { 223 | unsigned char VC9_HI; 224 | unsigned char VC9_LO; 225 | }VCell9Byte; 226 | unsigned short VCell9Word; 227 | }VCell9; 228 | union 229 | { 230 | struct 231 | { 232 | unsigned char VC10_HI; 233 | unsigned char VC10_LO; 234 | }VCell10Byte; 235 | unsigned short VCell10Word; 236 | }VCell10; 237 | union 238 | { 239 | struct 240 | { 241 | unsigned char VC11_HI; 242 | unsigned char VC11_LO; 243 | }VCell11Byte; 244 | unsigned short VCell11Word; 245 | }VCell11; 246 | union 247 | { 248 | struct 249 | { 250 | unsigned char VC12_HI; 251 | unsigned char VC12_LO; 252 | }VCell12Byte; 253 | unsigned short VCell12Word; 254 | }VCell12; 255 | unsigned char CRC; 256 | }; 257 | //}Batmon_struct; 258 | struct BatteryStatus 259 | { 260 | union { 261 | struct { 262 | uint8_t reserved_0123:4; 263 | uint8_t FULLY_DISCHARGED:1; 264 | uint8_t FULLY_CHARGED:1; 265 | uint8_t DISCHARGING:1; 266 | uint8_t INITIALIZED:1; 267 | uint8_t REMAINING_TIME_ALARM:1; 268 | uint8_t REMAINING_CAPACITY_ALARM:1; 269 | uint8_t reserved_10:1; 270 | uint8_t TERMINAT_DISCHARGE_ALARM:1; 271 | uint8_t OVER_TEMP_ALARM:1; 272 | uint8_t reserved_13:1; 273 | uint8_t TERMINATE_CHARGE_ALARM:1; 274 | uint8_t OVER_CHARGED_ALARM:1; 275 | }bits; 276 | unsigned short status; 277 | }; 278 | }; 279 | 280 | struct SafetyStatus 281 | { 282 | uint8_t len = 4; 283 | union 284 | { 285 | struct 286 | { 287 | uint8_t _rsvd_31:1; 288 | uint8_t _rsvd_30:1; 289 | uint8_t FLAG_DISCHARGE_OVERCURRENT:1; 290 | uint8_t FLAG_CELL_OVERVOLTAGE_LATCH:1; 291 | uint8_t FLAG_DISCHARGE_UNDERTEMP:1; 292 | uint8_t FLAG_CHARGE_UNDERTEMP:1; 293 | uint8_t FLAG_OVERPRECHARGE_CURRENT:1; 294 | uint8_t FLAG_OVERCHARGE_VOLTAGE:1; 295 | uint8_t FLAG_OVERCHARGE_CURRENT:1; 296 | uint8_t FLAG_OVERCHARGE:1; 297 | uint8_t _rsvd_21:1; 298 | uint8_t FLAG_CHARGE_TIMEOUT:1; 299 | uint8_t _rsvd_19:1; 300 | uint8_t FLAG_PRECHARGE_TIMEOUT:1; 301 | uint8_t _rsvd_17:1; 302 | uint8_t FLAG_FET_OVERTEMP:1; 303 | uint8_t _rsvd_15:1; 304 | uint8_t FLAG_CELL_UNDERVOLTAGE_COMPENSATED:1; 305 | uint8_t FLAG_DISCHARGE_OVERTEMP:1; 306 | uint8_t FLAG_CHARGE_OVERTEMP:1; 307 | uint8_t FLAG_DISHCARGE_LATCH_SHORT_CIRCUIT:1; 308 | uint8_t FLAG_DISCHARGE_SHORT_CIRCUIT:1; 309 | uint8_t FLAG_CHARGE_LATCH_SHORT_CIRCUIT:1; 310 | uint8_t FLAG_CHARGE_SHORT_CIRCUIT:1; 311 | uint8_t FLAG_DISCHARGE_LATCH_OVERLOAD:1; 312 | uint8_t FLAG_DISCHARGE_OVERLOAD:1; 313 | uint8_t FLAG_DISCHARGE_OVERCURRENT_2:1; 314 | uint8_t FLAG_DISCHARGE_OVERCURRENT_1:1; 315 | uint8_t FLAG_CHARGE_OVERCURRENT_2:1; 316 | uint8_t FLAG_CHARGE_OVERCURRENT_1:1; 317 | uint8_t FLAG_CELL_OVERVOLTAGE:1; 318 | uint8_t FLAG_CELL_UNDERVOLTAGE:1; 319 | }flags; 320 | uint32_t data; 321 | }flag; 322 | uint8_t crc; 323 | }; 324 | -------------------------------------------------------------------------------- /ReadBatmon/FastCRC.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rotoye/batmon_reader/40ed0c9baeb81e2ddc187d8252caa6a763575654/ReadBatmon/FastCRC.zip -------------------------------------------------------------------------------- /ReadBatmon/ReadBatmon.ino: -------------------------------------------------------------------------------- 1 | /************************************************************************************************** 2 | * 3 | * Copyright (c) 2020-2022, SkyMul Inc. 4 | * All Rights Reserved. 5 | * 6 | * BatMon and its associated brands and logos published in the website and source code 7 | * are Trademarks of SkyMul Inc. 8 | * 9 | * Redistribution and use in source and binary forms, with or without 10 | * modification, are permitted provided that the following conditions are met: 11 | * 12 | * * Adheres to the rules of GNU GENERAL PUBLIC LICENSE, Version 3 13 | * 14 | * * Redistributions of source code and the binary form must retain this copyright notice, 15 | * this list of conditions and disclaimer in the documentation and literature provided with 16 | * the distribution. 17 | * 18 | * * Neither the name of the copyright holder nor the names of its contributors may be used to 19 | * endorse or promote products derived from this software without specific prior written 20 | * permission. 21 | * 22 | **************************************************************************************************/ 23 | 24 | /** 25 | * @file ReadBatmon.ino 26 | * 27 | * @author Eohan George 28 | */ 29 | 30 | #include 31 | #include "Batmon.h" 32 | 33 | class Batt{ 34 | private: 35 | uint8_t i2cAddress, numTherms, cellCount, isDetected; 36 | public: 37 | Batmon bm; 38 | CVolts cv; 39 | TotVolt tv; 40 | Therms ts; 41 | uint8_t MAXCELLCOUNT = 12; 42 | Batt(uint8_t _i2cAddress, uint8_t _numTherms):bm(_i2cAddress,_numTherms){ 43 | i2cAddress = _i2cAddress; 44 | numTherms = _numTherms; 45 | } 46 | // Returns true for succeed 47 | bool checkConnection() 48 | { 49 | Wire.beginTransmission(i2cAddress); 50 | uint8_t error = Wire.endTransmission(); 51 | if (error == 0) 52 | return true; 53 | else 54 | return false; 55 | } 56 | bool init() 57 | { 58 | if(checkConnection()) 59 | { 60 | cellCount = bm.getCellCount(); 61 | if(cellCount == 0 || cellCount == 0xFF) 62 | isDetected = false; 63 | else 64 | isDetected = true; 65 | return isDetected; 66 | } 67 | return false; 68 | } 69 | void readVoltages() 70 | { 71 | bm.readCellVoltages(cv); 72 | bm.readTotalVoltage(tv); 73 | } 74 | void printBatteryInfo(bool heading =false) 75 | { 76 | char str[50]; 77 | if (heading) 78 | { 79 | Serial.print(" I2C "); 80 | Serial.print(" TV "); 81 | for(int i = 0; i=0;i--) 148 | { 149 | sprintf(str,"%s%02X",str,sn_byte[i]); 150 | if(i%2==0 && i!=0) 151 | sprintf(str,"%s-",str); 152 | } 153 | Serial.print(str); Serial.print(" "); 154 | } 155 | else 156 | Serial.print("ERR"); 157 | } 158 | else 159 | init(); 160 | } 161 | }; 162 | 163 | Batt batt[]= {Batt(BATMON_SMBUS_ADDRESS_ARRAY[0], NUM_THERM_TO_READ), 164 | Batt(BATMON_SMBUS_ADDRESS_ARRAY[1], NUM_THERM_TO_READ), 165 | Batt(BATMON_SMBUS_ADDRESS_ARRAY[2], NUM_THERM_TO_READ), 166 | Batt(BATMON_SMBUS_ADDRESS_ARRAY[3], NUM_THERM_TO_READ), 167 | Batt(BATMON_SMBUS_ADDRESS_ARRAY[4], NUM_THERM_TO_READ), 168 | Batt(BATMON_SMBUS_ADDRESS_ARRAY[5], NUM_THERM_TO_READ), 169 | Batt(BATMON_SMBUS_ADDRESS_ARRAY[6], NUM_THERM_TO_READ), 170 | Batt(BATMON_SMBUS_ADDRESS_ARRAY[7], NUM_THERM_TO_READ), 171 | Batt(BATMON_SMBUS_ADDRESS_ARRAY[8], NUM_THERM_TO_READ), 172 | Batt(BATMON_SMBUS_ADDRESS_ARRAY[9], NUM_THERM_TO_READ)}; 173 | 174 | void setup() 175 | { 176 | // Start the I2C Bus as Master 177 | Serial.begin(115200); 178 | //Serial.println("Read Batmon"); 179 | Wire.setClock(100000); 180 | for (uint8_t i = 0; i < BATMON_SMBUS_TOTAL_ADDRESS; i++) { 181 | batt[i].init(); 182 | } 183 | 184 | //Serial.println("Total Voltage, Cell 10, Cell 9, Cell 8, Cell 7, Cell 6, Cell 5, Cell 4, Cell 3, Cell 2, Cell 1, Current, Discharged Current"); 185 | Serial.println("Starting BATMON Reader"); 186 | } 187 | 188 | 189 | 190 | void loop() 191 | { 192 | Serial.write(0x0C); // Command to clear screen for non-Arduino terminals like putty 193 | 194 | for (uint8_t i = 0; i < BATMON_SMBUS_TOTAL_ADDRESS; i++) { 195 | if (i==0) 196 | batt[i].printBatteryInfo(true); 197 | else 198 | batt[i].printBatteryInfo(false); 199 | Serial.println(); 200 | } 201 | /* 202 | //Serial.print("\tManufacturer's Name: "); 203 | //unsigned char man_name [20]; 204 | //Serial.print((char *)bm.getMan(man_name)); 205 | 206 | */ 207 | Serial.println(); 208 | 209 | //bm.shutdown(); 210 | delay(200); 211 | } 212 | -------------------------------------------------------------------------------- /Tutorials/programming_tutorial/README.md: -------------------------------------------------------------------------------- 1 | # BatMon Programming Guide 2 | 3 | ## Required Materials 4 | 5 | * Microchip ICE Programmer 6 | 7 | ![](http://batmonfiles.rotoye.com/userguide/atmel_ice.jpg) 8 | 9 | * Programmer cable adapter 10 | 11 | ![](http://batmonfiles.rotoye.com/userguide/adapter_2.png) 12 | 13 | * Programmer cable 14 | 15 | ![](http://batmonfiles.rotoye.com/userguide/programming_cable.jpg | width=4.903673447069116in height=1.3153083989501313in) 16 | 17 | * BatMon board 18 | 19 | ![](http://batmonfiles.rotoye.com/userguide/batmon.jpg) 20 | 21 | * Battery 22 | 23 | * Computer with Atmel Studio and Git Bash installed 24 | 25 | ## Procedure 26 | 27 | 1. Remove case 28 | 29 | ![](http://batmonfiles.rotoye.com/userguide/batmon_case.jpg) 30 | 31 | 1. Locate ICE programmer 32 | 33 | 2. Plug cable into SAM port 34 | 35 | ![](http://batmonfiles.rotoye.com/userguide/program_connect.jpg) 36 | 37 | 1. Connect to Adapter 38 | 39 | ![](http://batmonfiles.rotoye.com/userguide/cable_in_adapter.jpg) 40 | 41 | 1. Connect to BatMon 42 | 43 | ![](http://batmonfiles.rotoye.com/userguide/batmon_program.jpg) 44 | 45 | 1. Turn on the BatMon by pressing on the button. 46 | 47 | 2. Connect to computer. 48 | 49 | 3. Open Atmel Studio. 50 | 51 | 4. Click on the *Device Programming* icon in the upper-right side of the window or press Ctrl+Shift+P on the keyboard. 52 | 53 | ![](http://batmonfiles.rotoye.com/userguide/programming_icon.png) 54 | 55 | 1. Select the Atmel ICE as the tool. Make sure *ATSAMC21E18A* is set as the device, and make sure the Interface is set as *SWD*. 56 | 57 | 1. Click *Apply*. 58 | 59 | 2. Click on the *Read* button under Device Signature. The signature of this device should appear in the text box. The *3.3V* should be read as the Target Voltage. 60 | 61 | ![](http://batmonfiles.rotoye.com/userguide/start_debug.png) 62 | 63 | 1. Contact Rotoye for access to the Github repository [https://github.com/rotoye/batmon](https://github.com/rotoye/batmon) 64 | 65 | 2. Open Git Bash. 66 | 67 | 3. Navigate the the folder of your choice and enter the command: `git clone https://github.com/rotoye/batmon.git` 68 | 69 | 4. Go to File\>Open\>Project/Solution. 70 | 71 | ![](http://batmonfiles.rotoye.com/userguide/open_project.png) 72 | 73 | 1. Browse for the location of the *batmon* repository and open the Batmon_firm.atsln* file found in the folder *batmon/Batmon_firm/*. 74 | 75 | ![](http://batmonfiles.rotoye.com/userguide/project_file.png) 76 | 77 | 1. Click on the *Start Without Debugging* icon on the toolbar or press Ctrl+Alt+F5 on the keyboard. 78 | 79 | ![](http://batmonfiles.rotoye.com/userguide/start_debug.png) 80 | 81 | 1. Wait for the programming to finish (can take up to a minute). 82 | 83 | 2. Disconnect the programmer and enjoy! 84 | 85 | ## Troubleshooting 86 | 87 | ### Programming Errors 88 | 89 | There might be errors like the following when trying to read the device 90 | signature. 91 | 92 | ![](http://batmonfiles.rotoye.com/userguide/error_msg.png) 93 | 94 | If this happens, it means the programmer isn't detecting 3.3V at the 95 | programming connector. Try the following: 96 | 97 | 1. Try pressing the power button and holding for two seconds before releasing. 98 | 99 | 2. Check that the programming cable is connected from the Atmel ICE programmer to the BatMon. 100 | 101 | 3. Check that the battery is connected properly to the BatMon using the correct cables. -------------------------------------------------------------------------------- /Tutorials/running_multiple_batmons/README.md: -------------------------------------------------------------------------------- 1 | # Running Multiple Batmons on One Bus 2 | ## Physical Layer 3 | The Opto-Isolation of the signals is **always** recommended to reduce noise and improve reliability on the bus, and is **absolutely necessary on the higher battery** when the batteries are in series. 4 | 5 | ![Opto-iso I2C block diagram](http://batmonfiles.rotoye.com/userguide/Opto-iso%20connection.jpg) 6 | 7 | When using two Batmons on the same bus, an I2C splitter, like the one shown here, is useful: 8 | ![I2C signal splitter](http://batmonfiles.rotoye.com/userguide/splitter.png) 9 | 10 | ## Batmon Firmware 11 | When using two Batmons on the same I2C bus, they cannot have the same I2C slave adress. This needs to be changed in the Batmon Firmware, in: 12 | 13 | Batmon_firm/batmon_v3/battery_config.h 14 | 15 | `BATMON_SMBUS_ADDRESS` Must be set to a different value for each Batmon that is to be used on the same bus. 16 | 17 | If Batmons are being used on two seperate bus interfaces, no changes are required in firmware. 18 | 19 | ## Interfacing 20 | 21 | #### PX4 22 | 23 | As of PX4 v1.11, multiple smart batteries with different SMBus addresses can be used on the same bus. 24 | 25 | Clone or download [Rotoye's fork of PX4:](https://github.com/rotoye/px4_firmware_batmon) 26 | 27 | Checkout the batmon_v2.02_px4_v1.11 branch 28 | 29 | Build and upload the firmware according to [PX4 documentation instructions](https://dev.px4.io/master/en/setup/building_px4.html) 30 | 31 | In ground control software of choice, set the BATx_SOURCE parameters to "External", and set SENS_EN_BAT to true 32 | 33 | Start the batt_smbus [script](https://dev.px4.io/master/en/middleware/modules_driver.html) on NuttShell 34 | 35 | For example, running two BatMons on the same bus: 36 | 37 | `batt_smbus start -X -b 1 -a 11` * External bus 1, address 0x0b 38 | `batt_smbus start -X -b 1 -a 12` * External bus 1, address 0x0c 39 | 40 | #### Ardupilot 41 | Ardupilot does not currently support using multiple smart batteries on the same bus 42 | 43 | If you want to use multiple Batmon-equipt batteries with Ardupilot, you'll need to run them on different busses 44 | 45 | For example, using params 46 | ``BATT_BUS = 2`` 47 | ``BATT_MONITOR = 19: Rotoye`` 48 | 49 | ``BATT2_BUS = 3`` 50 | ``BATT2_MONITOR = 19: Rotoye`` 51 | 52 | Would allow for using two Batmons on seperate external I2C busses 53 | 54 | --------------------------------------------------------------------------------