Driver for Texas Instruments' battery state-of-charge estimator.

Dependents:   BQ34Z100G1-Utils BQ34Z100G1-ChemIDMeasurer

Embed: (wiki syntax)

« Back to documentation index

Show/hide line numbers BQ34Z100.cpp Source File

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 }