Driver for Texas Instruments' battery state-of-charge estimator.
Dependents: BQ34Z100G1-Utils BQ34Z100G1-ChemIDMeasurer
BQ34Z100.cpp
00001 #include "BQ34Z100.h" 00002 00003 #include <cinttypes> 00004 00005 BQ34Z100::BQ34Z100(PinName sda, PinName scl, int hz) : _i2c(sda,scl) 00006 { 00007 //Set the I2C bus frequency 00008 _i2c.frequency(hz); 00009 } 00010 00011 //For example, sending Control(0x0414) 00012 //Translates into WRITE 0x00 0x14 0x04 00013 //Command1 = 0x04 and Command2 = 0x14 in this case 00014 void BQ34Z100::sendControlCommand(Control control) 00015 { 00016 //First write two bytes, LSB first 00017 uint16_t controlBytes = static_cast<uint16_t>(control); 00018 write(Command::Control, controlBytes & 0xFF, (controlBytes >> 8) & 0xFF); 00019 } 00020 00021 //For example, sending Control(0x0414) 00022 //Translates into WRITE 0x00 0x14 0x04 00023 //Command1 = 0x04 and Command2 = 0x14 in this case 00024 uint16_t BQ34Z100::readControlCommand(Control control) 00025 { 00026 //First write two bytes, LSB first 00027 uint16_t controlBytes = static_cast<uint16_t>(control); 00028 write(Command::Control, controlBytes & 0xFF, (controlBytes >> 8) & 0xFF); 00029 //Read two bytes from the register 0x00 00030 return read(Command::Control, 2); 00031 } 00032 00033 //Writes over I2c to the device 00034 //For example the start subcommand WRITE 01 00 to Register 00 requires 00035 //write(0x00, 0x01, 0x00) 00036 void BQ34Z100::write(Command command, const uint8_t cmd1, const uint8_t cmd2) 00037 { 00038 //TODO, add i2c error checking like in read() 00039 _i2c.start(); 00040 _i2c.write(GAUGE_ADDRESS | 0x0); 00041 _i2c.write(static_cast<uint8_t>(command)); 00042 _i2c.write(cmd1); 00043 _i2c.write(cmd2); 00044 _i2c.stop(); 00045 } 00046 00047 //Similar to the previous function but only writes 1 byte 00048 void BQ34Z100::write(Command command, const uint8_t cmd) 00049 { 00050 _i2c.start(); 00051 _i2c.write(GAUGE_ADDRESS | 0x0); 00052 _i2c.write(static_cast<uint8_t>(command)); 00053 _i2c.write(cmd); 00054 _i2c.stop(); 00055 } 00056 00057 //Reads over i2c to the device 00058 //For example, to finish the subcommand sequence (step 2), READ 2 bytes from register 00 00059 //read(0x00, 2) 00060 uint32_t BQ34Z100::read(Command command, const uint8_t length) 00061 { 00062 uint32_t val = 0; //Contains next byte of data that will be read 00063 00064 for (int i = 0; i < length; i++) 00065 { 00066 _i2c.start(); 00067 00068 int writeResult = _i2c.write(GAUGE_ADDRESS | 0x0); 00069 00070 if(writeResult != 1) 00071 { 00072 printf("A write error has occurred when transmitting address.\r\n"); 00073 return 0; 00074 } 00075 00076 _i2c.write(static_cast<uint8_t>(command) + i); 00077 _i2c.stop(); 00078 00079 _i2c.start(); 00080 _i2c.write(GAUGE_ADDRESS | 0x1); 00081 00082 //Swap bytes around (convert to Little Endian) 00083 val |= (_i2c.read(false) << (8 * i)); 00084 00085 _i2c.stop(); 00086 00087 } 00088 00089 return val; 00090 } 00091 00092 void BQ34Z100::enableCal() 00093 { 00094 sendControlCommand(Control::CAL_ENABLE); 00095 } 00096 00097 void BQ34Z100::enterCal() 00098 { 00099 sendControlCommand(Control::ENTER_CAL); 00100 } 00101 00102 void BQ34Z100::exitCal() 00103 { 00104 sendControlCommand(Control::EXIT_CAL); 00105 } 00106 00107 //Warning: Enabling IT should only be done when configuration is complete 00108 void BQ34Z100::ITEnable() 00109 { 00110 sendControlCommand(Control::IT_ENABLE); 00111 } 00112 00113 uint16_t BQ34Z100::getStatus() 00114 { 00115 return readControlCommand(Control::CONTROL_STATUS); 00116 } 00117 00118 uint16_t BQ34Z100::getChemID() 00119 { 00120 return readControlCommand(Control::CHEM_ID); 00121 } 00122 00123 uint16_t BQ34Z100::getStateOfHealth() 00124 { 00125 return read(Command::StateOfHealth, 2); 00126 } 00127 00128 uint8_t BQ34Z100::getSOC() 00129 { 00130 return read(Command::StateOfCharge, 1); 00131 } 00132 00133 uint16_t BQ34Z100::getError() 00134 { 00135 return read(Command::MaxError, 1); 00136 } 00137 00138 uint16_t BQ34Z100::getRemaining() 00139 { 00140 return read(Command::RemainingCapacity, 2); 00141 } 00142 00143 uint16_t BQ34Z100::getVoltage() 00144 { 00145 return read(Command::Voltage, 2); 00146 } 00147 00148 int16_t BQ34Z100::getCurrent() 00149 { 00150 int16_t result = read(Command::Current, 2); 00151 if (result < 0) result = -result; 00152 return result; 00153 } 00154 00155 double BQ34Z100::getTemperature() 00156 { 00157 //The device returns an internal temperature in units of 0.1K 00158 //Convert to celsius by subtracting dividing by 10 then subtracting 273.15K 00159 return (read(Command::Temperature, 2)/10.0) - 273.15; 00160 } 00161 00162 int BQ34Z100::getSerial() 00163 { 00164 return read(Command::SerialNumber, 2); 00165 } 00166 00167 void BQ34Z100::reset() 00168 { 00169 sendControlCommand(Control::RESET); 00170 ThisThread::sleep_for(175ms); // experimentally determined boot time 00171 } 00172 00173 void BQ34Z100::unseal() 00174 { 00175 //Follows the UNSEAL commands listed on page 21 of the datasheet 00176 //First sequence required to read DATA FLASH 00177 sendControlCommand(Control::UNSEAL_KEY1); 00178 sendControlCommand(Control::UNSEAL_KEY2); 00179 } 00180 00181 void BQ34Z100::seal() 00182 { 00183 //Reseals the sensor, blocking access to certain flash registers 00184 //Would not recommend using this 00185 //See page 22 of the datasheet 00186 sendControlCommand(Control::SEALED); 00187 } 00188 00189 //TODO: see if we can get away with taking the ThisThread::sleep_for away 00190 //as they will slow down the main Hamster loop 00191 void BQ34Z100::changePage(char subclass, uint16_t offset) 00192 { 00193 ThisThread::sleep_for(10ms); 00194 //Enable block data flash control (single byte write) 00195 write(Command::BlockDataControl, 0x00); 00196 ThisThread::sleep_for(10ms); 00197 00198 //Use DataFlashClass() command to access the subclass 00199 write(Command::DataFlashClass, subclass); 00200 currFlashPage = subclass; 00201 ThisThread::sleep_for(10ms); 00202 00203 //Select the block offset location 00204 //Blocks are 32 in size, so the offset is which block the data sits in 00205 //Ex: 16 is block 0x00, 52 is block 0x01 (called "index" variable) 00206 currFlashBlockIndex =(uint8_t)(offset/32); 00207 write(Command::DataFlashBlock, currFlashBlockIndex); 00208 ThisThread::sleep_for(20ms); 00209 } 00210 00211 void BQ34Z100::updateChecksum() 00212 { 00213 00214 uint8_t newChecksum = calcChecksum(); 00215 00216 //Send new checksum thru I2C 00217 write(Command::BlockDataCheckSum, newChecksum); 00218 printf("Writing new checksum for page %" PRIu8 " block %" PRIu8 ": 0x%" PRIx8 "\r\n", currFlashPage, currFlashBlockIndex, newChecksum); 00219 00220 ThisThread::sleep_for(50ms); //Wait for BQ34Z100 to process, may be totally overkill 00221 } 00222 00223 00224 //Warning: change page first before reading data 00225 //Copies a page in flash into the "flashbytes" array 00226 void BQ34Z100::readFlash() 00227 { 00228 //Request read flash 00229 _i2c.write(GAUGE_ADDRESS | 0); 00230 _i2c.write(static_cast<uint8_t>(Command::BlockData)); //Command to read Data Flash 00231 _i2c.stop(); 00232 00233 _i2c.start(); 00234 _i2c.write(GAUGE_ADDRESS | 1); 00235 //Read all bytes from page (which is 32 bytes long) 00236 //_i2c.read(GAUGE_ADDRESS, reinterpret_cast<char*>(flashbytes), 32); 00237 for (int i = 0; i<32; i++) 00238 { 00239 //Store in flashbytes (memory) 00240 flashbytes[i] = _i2c.read(i < 31); 00241 } 00242 00243 _i2c.stop(); 00244 00245 uint8_t expectedChecksum = read(Command::BlockDataCheckSum, 1); 00246 00247 if(expectedChecksum != calcChecksum()) 00248 { 00249 printf("ERROR: Checksum of flash memory block does not match. I2C read was likely corrupted."); 00250 } 00251 00252 printf("Page %" PRIu8 " block %" PRIu8 " contents:", currFlashPage, currFlashBlockIndex); 00253 for(size_t byteIndex = 0; byteIndex < 32; ++byteIndex) 00254 { 00255 printf(" %" PRIx8, flashbytes[byteIndex]); 00256 } 00257 printf("\r\n"); 00258 printf("Checksum: 0x%" PRIx8 "\r\n", expectedChecksum); 00259 00260 00261 ThisThread::sleep_for(10ms); //Is this necessary? 00262 } 00263 00264 //Writes new data, first to flashbytes array, then updates on the sensor as well 00265 //Note: Requires update of the checksum after changes are made 00266 //See pg. 25 of datasheet for reference 00267 //Input: subclass ID (index) 00268 //Input: offset 00269 //Input: length of write 00270 void BQ34Z100::writeFlash(uint8_t index, uint32_t value, int len) 00271 { 00272 //Has to be a number between 0 to 31 00273 //Use changePage to set the offset correctly beforehand 00274 if (index > 31) index = index % 32; 00275 00276 //Write to I2C bus and change flashbytes at the same time 00277 //Necessary for checksum calculation at the end 00278 if (len == 1) 00279 { 00280 flashbytes[index] = value; 00281 write(static_cast<Command>(static_cast<uint8_t>(Command::BlockData) + index), (unsigned char)value); 00282 printf("Flash[%d] <- 0x%" PRIx8 "\n", index, flashbytes[index]); 00283 00284 } else if (len > 1) 00285 { 00286 //Process every byte except the last 00287 for (int i = 0; i<len-1; i++) 00288 { 00289 flashbytes[index+i] = value >> 8*((len-1)-i); 00290 printf("Flash[%d] <- 0x%" PRIx8 "\n", index + i, flashbytes[index+i]); 00291 write(static_cast<Command>(static_cast<uint8_t>(Command::BlockData) + index + i), flashbytes[index+i]); 00292 } 00293 00294 //Last byte (lower byte) 00295 flashbytes[index+len-1] = value & 0xFF; 00296 printf("Flash[%d] <- 0x%" PRIx8 "\n", index + (len - 1), flashbytes[index+len-1]); 00297 write(static_cast<Command>(static_cast<uint8_t>(Command::BlockData) + index + len - 1), value & 0xFF); 00298 } 00299 } 00300 00301 uint8_t* BQ34Z100::getFlashBytes() 00302 { 00303 return flashbytes; 00304 } 00305 00306 //Edits the design capacity and design energy of the battery 00307 void BQ34Z100::changePage48() 00308 { 00309 changePage(48, 0); //Block 48, offset 0 00310 readFlash(); 00311 writeFlash(11, DESIGNCAP, 2); 00312 writeFlash(13, DESIGNENERGY, 2); 00313 updateChecksum(); 00314 ThisThread::sleep_for(300ms); 00315 } 00316 00317 //This function sets certain settings, such as enabling the external voltage divider, 00318 //enablibg calibration, enabling internal temp sensor, and setting the number of cells in the bat. 00319 //and selecting the correct LED config mode 00320 void BQ34Z100::changePage64() 00321 { 00322 changePage(64, 0); //Block 64, offset 0 00323 readFlash(); 00324 00325 //Edit pack configuration register (has an upper and lower byte) 00326 //Enables external voltage divider use 00327 uint8_t packConfig_high = flashbytes[0]; 00328 uint8_t packConfig_low = flashbytes[1]; 00329 if (VOLTSEL) 00330 { 00331 packConfig_high |= 0x08; //00001000 00332 } 00333 00334 //Also allow calibration by switching the CAL_EN bit to 1 00335 packConfig_high |= 0x40; //01000000 00336 00337 //Set temps bit. 00338 packConfig_low &= ~(1); 00339 packConfig_low |= USE_EXTERNAL_THERMISTOR; 00340 00341 writeFlash(0, packConfig_high, 1); 00342 writeFlash(1, packConfig_low, 1); 00343 00344 // Disable fast convergence as recommended in the calibration procedure 00345 uint8_t packConfigB = flashbytes[2]; 00346 packConfigB &= ~1; 00347 writeFlash(2, packConfigB, 1); 00348 00349 //Update LED config and number of cells in battery 00350 writeFlash(4, LEDCONFIG, 1); 00351 writeFlash(7, 0x04, 1); 00352 updateChecksum(); 00353 ThisThread::sleep_for(300ms); 00354 } 00355 00356 void BQ34Z100::changePage80() 00357 { 00358 changePage(80, 0); 00359 readFlash(); 00360 00361 writeFlash(0, LOADSELECT, 1); 00362 writeFlash(1, LOADMODE, 1); 00363 writeFlash(10, 10, 2); 00364 updateChecksum(); 00365 ThisThread::sleep_for(300ms); 00366 00367 changePage(80, 53); 00368 readFlash(); 00369 //Update cell terminate voltage 00370 writeFlash(53, ZEROCHARGEVOLT, 2); 00371 updateChecksum(); 00372 ThisThread::sleep_for(300ms); 00373 } 00374 00375 void BQ34Z100::changePage82() 00376 { 00377 changePage(82, 0); 00378 readFlash(); 00379 //Update QMax cell 0 00380 writeFlash(0, QMAX0, 2); 00381 updateChecksum(); 00382 ThisThread::sleep_for(300ms); 00383 } 00384 00385 //Input: The actual battery voltage, measure with multimeter (mV) 00386 //Output: The new voltage divider ratio written 00387 uint16_t BQ34Z100::calibrateVoltage(uint16_t currentVoltage) 00388 { 00389 changePage(104, 0); 00390 readFlash(); 00391 //Gets the voltage divider value stored in flash 00392 uint16_t flashVoltage = (uint16_t)(flashbytes[14] << 8); 00393 flashVoltage |= (uint16_t)(flashbytes[15]); 00394 00395 float readVoltage = (float)getVoltage(); 00396 float newSetting = ((float)(currentVoltage)/readVoltage)*(float)(flashVoltage); 00397 uint16_t writeSetting; //This 2 byte integer will be written to chip 00398 if (newSetting>65535.0f) writeSetting=65535; 00399 else if (newSetting < 0.0f) writeSetting=0; 00400 else writeSetting=(uint16_t)(newSetting); 00401 writeFlash(14, writeSetting, 2); 00402 updateChecksum(); 00403 ThisThread::sleep_for(10ms); 00404 00405 // also change the "Flash Update OK Cell Volt" as the datasheet says to: 00406 changePage(68, 0); 00407 readFlash(); 00408 00409 int16_t oldUpdateOK = 0; 00410 oldUpdateOK |= (static_cast<uint16_t>(flashbytes[0]) << 8); 00411 oldUpdateOK |= flashbytes[1]; 00412 00413 int16_t newUpdateOK = static_cast<int16_t>(round(FLASH_UPDATE_OK_VOLT * CELLCOUNT * (5000.0f/writeSetting))); 00414 printf("Changing Flash Update OK Voltage from %" PRIi16 " to %" PRIi16 "\r\n", oldUpdateOK, newUpdateOK); 00415 00416 writeFlash(0, newUpdateOK, 2); 00417 updateChecksum(); 00418 00419 //Test output 00420 printf("Register (voltage divider): %d\r\n", flashVoltage); 00421 printf("New Ratio: %f\r\n", ((float)(currentVoltage)/readVoltage)); 00422 printf("READ VOLTAGE (mv): %f\r\n", readVoltage); 00423 return (uint16_t)newSetting; 00424 } 00425 00426 //If too much calibration is done, you may want to reset the Voltage divider 00427 //Sets voltage divider register to custom value (in mV) 00428 //Recommended: 1.5x the current battery voltage 00429 void BQ34Z100::setVoltageDivider(uint16_t newVoltage) 00430 { 00431 changePage(104, 0); 00432 readFlash(); 00433 writeFlash(14, newVoltage, 2); 00434 updateChecksum(); 00435 ThisThread::sleep_for(300ms); 00436 } 00437 00438 //Calibrates the current output to match the calibration current input (calCurrent) 00439 void BQ34Z100::calibrateShunt(int16_t calCurrent) 00440 { 00441 if (calCurrent < 0) calCurrent = -calCurrent; 00442 00443 int16_t currentReading = getCurrent(); 00444 if (currentReading < 0) currentReading = -currentReading; 00445 00446 changePage(104, 0); 00447 readFlash(); 00448 ThisThread::sleep_for(30ms); 00449 00450 // read and convert CC Gain 00451 uint32_t currentGainDF = ((uint32_t)flashbytes[0]) << 24 | ((uint32_t)flashbytes[1]) << 16 | ((uint32_t)flashbytes[2]) << 8 | (uint32_t)flashbytes[3]; 00452 float currentGainResistance = (4.768f/xemicsToFloat(currentGainDF)); 00453 00454 uint32_t currentDeltaDF = ((uint32_t)flashbytes[4]) << 24 | ((uint32_t)flashbytes[5]) << 16 | ((uint32_t)flashbytes[6]) << 8 | (uint32_t)flashbytes[7]; 00455 float currentDeltaResistance = (5677445/xemicsToFloat(currentGainDF)); 00456 00457 float newGain = (((float)currentReading)/((float)calCurrent)) * currentGainResistance; 00458 00459 uint32_t newGainDF = floatToXemics(4.768 / newGain); 00460 float DeltaDF = floatToXemics(5677445/newGain); 00461 00462 printf("currentGainDF = 0x%" PRIx32 ", currentGainResistance = %f, currentDeltaDF = %" PRIx32 ", currentDeltaResistance = %f, newGain = %f, newGainDF = 0x%" PRIx32 "\n", 00463 currentGainDF, currentGainResistance, currentDeltaDF, currentDeltaResistance, newGain, newGainDF); 00464 00465 writeFlash(0, newGainDF, 4); 00466 writeFlash(4, DeltaDF, 4); 00467 00468 updateChecksum(); 00469 ThisThread::sleep_for(300ms); 00470 } 00471 00472 void BQ34Z100::setSenseResistor() { 00473 changePage(104, 0); 00474 ThisThread::sleep_for(30ms); 00475 00476 //Constants use to convert mOhm to the BQ34Z100 data flash units 00477 uint32_t GainDF = floatToXemics(/*4.768/SENSE_RES*/0.4768); 00478 uint32_t DeltaDF = floatToXemics(5677445/SENSE_RES); 00479 writeFlash(0, GainDF, 4); 00480 writeFlash(4, DeltaDF, 4); 00481 00482 printf("newGain = %f, GainDF = 0x%" PRIx32 "\n", 00483 SENSE_RES, GainDF); 00484 00485 updateChecksum(); 00486 ThisThread::sleep_for(300ms); 00487 } 00488 00489 uint16_t BQ34Z100::readDeviceType() 00490 { 00491 return readControlCommand(Control::DEVICE_TYPE); 00492 } 00493 00494 uint16_t BQ34Z100::readFWVersion() { 00495 return readControlCommand(Control::FW_VERSION); 00496 } 00497 00498 uint16_t BQ34Z100::readHWVersion() { 00499 return readControlCommand(Control::HW_VERSION); 00500 } 00501 00502 uint8_t BQ34Z100::calcChecksum() { 00503 uint8_t chkSum = 0; 00504 //Perform 8 bit summation of the entire BlockData() on byte-per-byte 00505 //basis. Checksum = (255-sum) 00506 //For every byte in flashbytes, add to sum 00507 for (int i = 0; i<32; i++) 00508 { 00509 chkSum += flashbytes[i]; 00510 } 00511 00512 chkSum = 255 - chkSum; 00513 00514 return chkSum; 00515 } 00516 00517 uint32_t BQ34Z100::floatToXemics(float value) { 00518 int iByte1, iByte2, iByte3, iByte4, iExp; 00519 bool bNegative = false; 00520 float fMantissa; 00521 // Don't blow up with logs of zero 00522 if (value == 0) value = 0.00001F; 00523 if (value < 0) 00524 { 00525 bNegative = true; 00526 value = -value; 00527 } 00528 // find the correct exponent 00529 iExp = (int)((log(value) / log(2)) + 1);// remember - log of any base is ln(x)/ln(base) 00530 00531 // MS byte is the exponent + 0x80 00532 iByte1 = iExp + 128; 00533 00534 // Divide input by this exponent to get mantissa 00535 fMantissa = value / (pow(2, iExp)); 00536 00537 // Scale it up 00538 fMantissa = fMantissa / (pow(2, -24)); 00539 00540 // Split the mantissa into 3 bytes 00541 iByte2 = (int)(fMantissa / (pow(2, 16))); 00542 00543 iByte3 = (int)((fMantissa - (iByte2 * (pow(2, 16)))) / (pow(2, 8))); 00544 00545 iByte4 = (int)(fMantissa - (iByte2 * (pow(2, 16))) - (iByte3 * (pow(2, 8)))); 00546 00547 // subtract the sign bit if number is positive 00548 if (bNegative == false) 00549 { 00550 iByte2 = iByte2 & 0x7F; 00551 } 00552 return (uint32_t)((uint32_t)iByte1 << 24 | (uint32_t)iByte2 << 16 | (uint32_t)iByte3 << 8 | (uint32_t)iByte4); 00553 } 00554 00555 float BQ34Z100::xemicsToFloat(uint32_t xemics) 00556 { 00557 bool bIsPositive = false; 00558 float fExponent, fResult; 00559 uint8_t vMSByte = (uint8_t)(xemics >> 24); 00560 uint8_t vMidHiByte = (uint8_t)(xemics >> 16); 00561 uint8_t vMidLoByte = (uint8_t)(xemics >> 8); 00562 uint8_t vLSByte = (uint8_t)xemics; 00563 // Get the sign, its in the 0x00 80 00 00 bit 00564 if ((vMidHiByte & 128) == 0) 00565 { bIsPositive = true; } 00566 00567 // Get the exponent, it's 2^(MSbyte - 0x80) 00568 fExponent = pow(2, (vMSByte - 128)); 00569 // Or in 0x80 to the MidHiByte 00570 vMidHiByte = (uint8_t)(vMidHiByte | 128); 00571 // get value out of midhi byte 00572 fResult = (vMidHiByte) * 65536; 00573 // add in midlow byte 00574 fResult = fResult + (vMidLoByte * 256); 00575 // add in LS byte 00576 fResult = fResult + vLSByte; 00577 // multiply by 2^-24 to get the actual fraction 00578 fResult = fResult * pow(2, -24); 00579 // multiply fraction by the ‘exponent’ part 00580 fResult = fResult * fExponent; 00581 // Make negative if necessary 00582 if (bIsPositive) 00583 return fResult; 00584 else 00585 return -fResult; 00586 } 00587 00588 uint8_t BQ34Z100::getUpdateStatus() { 00589 changePage(82, 0); 00590 readFlash(); 00591 return flashbytes[4]; 00592 } 00593 00594 std::pair<uint16_t, uint16_t> BQ34Z100::getFlags() { 00595 uint16_t flags = read(Command::Flags, 2); 00596 uint16_t flagsB = read(Command::FlagsB, 2); 00597 return std::make_pair(flags, flagsB); 00598 }
Generated on Wed Jul 13 2022 00:31:00 by
![doxygen](doxygen.png)