This class provides APIs to all of the registers of the TI BQ35100 battery gauge, as used on the u-blox C030 primary battery shield.
Dependents: example-battery-gauge-bq35100
Diff: bq35100.cpp
- Revision:
- 2:4c699a813451
- Parent:
- 1:ee7cc8d75283
diff -r ee7cc8d75283 -r 4c699a813451 bq35100.cpp --- a/bq35100.cpp Thu Nov 09 22:55:13 2017 +0000 +++ b/bq35100.cpp Fri Nov 10 17:07:06 2017 +0000 @@ -20,8 +20,8 @@ */ /** Define these to print debug information. */ -#define DEBUG_BQ35100 -#define DEBUG_BQ35100_BLOCK_DATA +//#define DEBUG_BQ35100 +//#define DEBUG_BQ35100_BLOCK_DATA #include <mbed.h> #include <battery_gauge_bq35100.h> @@ -34,33 +34,20 @@ // COMPILE-TIME MACROS // ---------------------------------------------------------------- -/** How many loops to wait for a configuration update to be permitted. - * Experience suggests that the limit really does need to be this large. */ -#define CONFIG_UPDATE_LOOPS 200 - -/** How long to delay when running around the config update loop. */ -#define CONFIG_UPDATE_LOOP_DELAY_MS 100 - -/** How long to wait for each loop while device is initialising. */ -#define INIT_LOOP_WAIT_MS 100 - -/** The maximum number of init loops to wait for. */ -#define INIT_LOOP_COUNT 10 - /** How long to wait for a security mode change to succeed. */ #define SET_SECURITY_MODE_RETRY_SECONDS 5 -// ---------------------------------------------------------------- -// PRIVATE VARIABLES -// ---------------------------------------------------------------- - +/** How long to wait for accumulated capacity data to be written + * to data flash when gauging is disabled */ +#define GAUGE_COMPLETE_WAIT_MS 10000 + // ---------------------------------------------------------------- // GENERIC PRIVATE FUNCTIONS // ---------------------------------------------------------------- // Read two bytes from an address. // Note: gpI2c should be locked before this is called. -bool BatteryGaugeBq35100::getTwoBytes (uint8_t registerAddress, uint16_t *pBytes) +bool BatteryGaugeBq35100::getTwoBytes(uint8_t registerAddress, uint16_t *pBytes) { bool success = false; char data[3]; @@ -84,7 +71,7 @@ } // Compute the checksum over an address plus the block of data. -uint8_t BatteryGaugeBq35100::computeChecksum (const char * pData, int32_t length) +uint8_t BatteryGaugeBq35100::computeChecksum(const char * pData, int32_t length) { uint8_t checkSum = 0; uint8_t x = 0; @@ -125,7 +112,7 @@ // Read data of a given length from a given address. // Note: gpI2c should be locked before this is called. -bool BatteryGaugeBq35100::readExtendedData (int32_t address, int32_t length, char * pData) +bool BatteryGaugeBq35100::readExtendedData(int32_t address, char * pData, int32_t length) { int32_t lengthRead; bool success = false; @@ -212,11 +199,12 @@ // Write data of a given length to a given address. // Note: gpI2c should be locked before this is called. -bool BatteryGaugeBq35100::writeExtendedData(int32_t address, int32_t length, const char * pData) +bool BatteryGaugeBq35100::writeExtendedData(int32_t address, const char * pData, int32_t length) { bool success = false; SecurityMode securityMode = getSecurityMode(); char data[3 + 32]; + uint16_t controlStatus; if ((gpI2c != NULL) && (length <= 32) && (address >= 0x4000) && (address < 0x4400) && (pData != NULL)) { #ifdef DEBUG_BQ35100 @@ -248,8 +236,14 @@ data[0] = 0x61; if (gpI2c->write(gAddress, &(data[0]), 2) == 0) { - // TODO check for successful write - success = true; + // Read the control status register to see if a bad + // flash write has been detected (bit 15) + data[0] = 0; + if ((gpI2c->write(gAddress, &(data[0]), 1) == 0) && + getTwoBytes(0, &controlStatus) && + (((controlStatus >> 15) & 0x01) != 0x01)) { + success = true; + } #ifdef DEBUG_BQ35100 printf("BatteryGaugeBq35100 (I2C 0x%02x): write successful.\n", gAddress >> 1); #endif @@ -296,22 +290,20 @@ char data[1]; uint16_t controlStatus; - data[0] = 0; // Set address to first register for Control - - // Send a control command to read the control status register - if (gpI2c->write(gAddress, &(data[0]), 1) == 0) { - if (getTwoBytes(0, &controlStatus)) { - // Bits 13 and 14 of the high byte represent the security status, - // 01 = full access - // 10 = unsealed access - // 11 = sealed access - securityMode = (SecurityMode) ((controlStatus >> 13) & 0x03); + // Read the control status register + data[0] = 0; + if ((gpI2c->write(gAddress, &(data[0]), 1) == 0) && + getTwoBytes(0, &controlStatus)) { + // Bits 13 and 14 of the high byte represent the security status, + // 01 = full access + // 10 = unsealed access + // 11 = sealed access + securityMode = (SecurityMode) ((controlStatus >> 13) & 0x03); #ifdef DEBUG_BQ35100 - printf("BatteryGaugeBq35100 (I2C 0x%02x): security mode is 0x%02x (control status 0x%04x).\r\n", gAddress >> 1, securityMode, controlStatus); + printf("BatteryGaugeBq35100 (I2C 0x%02x): security mode is 0x%02x (control status 0x%04x).\r\n", gAddress >> 1, securityMode, controlStatus); #endif - } } - + return securityMode; } @@ -329,7 +321,7 @@ // to change security mode if a previous security mode change // happend only a few seconds ago, hence the retry here for (int32_t x = 0; (x < SET_SECURITY_MODE_RETRY_SECONDS) && !success; x++) { - data[0] = 0; // Set address to first register for Control) + data[0] = 0x3e; // Set address to ManufacturerAccessControl switch (securityMode) { case SECURITY_MODE_SEALED: // Just seal the chip @@ -383,36 +375,17 @@ } // Make sure that the device is awake and has taken a reading. -// Note: the function does its own locking of gpI2C so that it isn't -// locked for the entire time we wait for ADC readings to complete. bool BatteryGaugeBq35100::makeAdcReading(void) { bool success = false; - uint16_t controlStatus; - char data[1]; - // Wait for INITCOMP to be set - data[0] = 0; // Set address to first register for Control - - gpI2c->lock(); - // Raise the pin - *pGaugeEnable = 1; - wait_ms(GAUGE_ENABLE_SETTLING_TIME_MS); - for (int x = 0; !success && (x < INIT_LOOP_COUNT); x++) { - if (gpI2c->write(gAddress, &(data[0]), 1) == 0) { - if (getTwoBytes(0, &controlStatus)) { -#ifdef DEBUG_BQ35100 - printf("BatteryGaugeBq35100 (I2C 0x%02x): read 0x%04x as CONTROL_STATUS.\n", gAddress >> 1, controlStatus); -#endif - // Bit 7 is INITCOMP - if (((controlStatus >> 7) & 0x01) == 0x01) { - success = true; - } - } - wait_ms (INIT_LOOP_WAIT_MS); + if (isGaugeEnabled()) { + success = true; + } else { + if (enableGauge() && disableGauge()) { + success = true; } } - gpI2c->unlock(); return success; } @@ -429,16 +402,11 @@ gReady = false; gSealCodes = 0; gFullAccessCodes = 0; - gGaugeOn = false; } // Destructor. BatteryGaugeBq35100::~BatteryGaugeBq35100(void) { - if (pGaugeEnable) { - *pGaugeEnable = 0; - delete pGaugeEnable; - } } // Initialise ourselves. @@ -451,8 +419,10 @@ gAddress = address << 1; gSealCodes = sealCodes; - pGaugeEnable = new DigitalOut(gaugeEnable, 1); - wait_ms(GAUGE_ENABLE_SETTLING_TIME_MS); + if (gaugeEnable != NC) { + pGaugeEnable = new DigitalOut(gaugeEnable, 1); + wait_ms(GAUGE_ENABLE_SETTLING_TIME_MS); + } if (gpI2c != NULL) { gpI2c->lock(); @@ -467,7 +437,7 @@ getTwoBytes(0x40, &answer)) { // Read from MACData address if (answer == 0x00a8) { // Read the full access codes, in case we need them - if (readExtendedData(0x41d0, sizeof (data), &(data[0]))) { + if (readExtendedData(0x41d0, &(data[0]), sizeof (data))) { // The four bytes are the full access codes gFullAccessCodes = ((uint32_t) data[0] << 24) + ((uint32_t) data[1] << 16) + ((uint32_t) data[2] << 8) + data[3]; #ifdef DEBUG_BQ35100 @@ -483,10 +453,7 @@ #endif } - if (!gGaugeOn) { - *pGaugeEnable = 0; - } - + disableGauge(); gpI2c->unlock(); } @@ -502,14 +469,65 @@ } // Switch on the battery capacity monitor. -bool BatteryGaugeBq35100::enableGauge(void) +bool BatteryGaugeBq35100::enableGauge(bool nonVolatile) { + bool accumulatedCapacityOn = false; bool success = false; - + char data[3]; + char opConfig; + uint16_t controlStatus; + if (gReady) { - *pGaugeEnable = 1; - wait_ms(GAUGE_ENABLE_SETTLING_TIME_MS); - success = true; + if (nonVolatile) { + // Read the OpConfig register which is at address 0x41b1 + if (readExtendedData(0x41b1, &opConfig, sizeof (opConfig))) { +#ifdef DEBUG_BQ35100 + printf("BatteryGaugeBq35100 (I2C 0x%02x): OpConfig is 0x%02x.\n", gAddress >> 1, opConfig); +#endif + // AccumulatedCapacity is achieved by setting GMSEL 1:0 (in bits 0 and 1) to 00 + if ((opConfig & 0x03) != 0) { + opConfig &= ~0x03; + // Write the new value back + accumulatedCapacityOn = writeExtendedData(0x41b1, &opConfig, sizeof (opConfig)); +#ifdef DEBUG_BQ35100 + if (accumulatedCapacityOn) { + printf("BatteryGaugeBq35100 (I2C 0x%02x): AccumulatedCapacity enabled, OpConfig becomes 0x%02x.\r\n", gAddress >> 1, opConfig); + } +#endif + } else { + accumulatedCapacityOn = true; + } + } + } + + if (accumulatedCapacityOn || !nonVolatile) { + if (pGaugeEnable) { + *pGaugeEnable = 1; + wait_ms(GAUGE_ENABLE_SETTLING_TIME_MS); + } + gpI2c->lock(); + data[0] = 0x3e; // Set address to ManufacturerAccessControl + data[1] = 0x11; // First byte of GAUGE_START sub-command (0x11) + data[2] = 0x00; // Second byte of GAUGE_START sub-command (0x00) (register address will auto-increment) + if (gpI2c->write(gAddress, &(data[0]), 3) == 0) { + // Wait for GA bit of CONTROL_STATUS (bit 0) to become 1 + data[0] = 0; + if (gpI2c->write(gAddress, &(data[0]), 1) == 0) { + for (int x = 0; (x < GAUGE_COMPLETE_WAIT_MS / 10) && !success; x++) { + if (getTwoBytes(0, &controlStatus)) { + if ((controlStatus & 0x01) == 0x01) { + success = true; + } else { + wait_ms(10); + } + } else { + wait_ms(10); + } + } + } + } + gpI2c->unlock(); + } } return success; @@ -518,11 +536,68 @@ // Switch off the battery capacity monitor. bool BatteryGaugeBq35100::disableGauge(void) { + bool accumulatedCapacityOn = false; bool success = false; + char data[3]; + char opConfig; + uint16_t controlStatus; if (gReady) { - *pGaugeEnable = 0; - success = true; + if (isGaugeEnabled()) { + // Read the OpConfig register which is at address 0x41b1 + if (readExtendedData(0x41b1, &opConfig, sizeof (opConfig))) { +#ifdef DEBUG_BQ35100 + printf("BatteryGaugeBq35100 (I2C 0x%02x): OpConfig is 0x%02x.\n", gAddress >> 1, opConfig); +#endif + // Check if AccumulatedCapacity is on + if ((opConfig & 0x03) == 0) { + accumulatedCapacityOn = true; + } + + // Send GAUGE_STOP + gpI2c->lock(); + data[0] = 0x3e; // Set address to ManufacturerAccessControl + data[1] = 0x12; // First byte of GAUGE_STOP sub-command (0x12) + data[2] = 0x00; // Second byte of GAUGE_STOP sub-command (0x00) (register address will auto-increment) + if (gpI2c->write(gAddress, &(data[0]), 3) == 0) { + // Wait for GA bit of CONTROL_STATUS (bit 0) to become 0 and, + // if AccumulatedCapacity was on, wait for G_DONE + // (bit 6 in CONTROL_STATUS) to be set + data[0] = 0; + if (gpI2c->write(gAddress, &(data[0]), 1) == 0) { + for (int x = 0; (x < GAUGE_COMPLETE_WAIT_MS / 10) && !success; x++) { + if (getTwoBytes(0, &controlStatus)) { + if ((controlStatus & 0x01) == 0) { + if (accumulatedCapacityOn) { + if (((controlStatus >> 6) & 0x01) == 0x01) { + success = true; +#ifdef DEBUG_BQ35100 + printf("BatteryGaugeBq35100 (I2C 0x%02x): AccumulatedCapacity data written to non-volatile memory.\r\n", gAddress >> 1); +#endif + } else { + wait_ms(10); + } + } else { + success = true; + } + } else { + wait_ms(10); + } + } else { + wait_ms(10); + } + } + } + } + gpI2c->unlock(); + } + } else { + success = true; + } + + if (pGaugeEnable) { + *pGaugeEnable = 0; + } } return success; @@ -531,13 +606,25 @@ // Determine whether battery gauging is enabled. bool BatteryGaugeBq35100::isGaugeEnabled(void) { - bool isEnabled = false; - + bool gaugeEnabled = false; + char data[1]; + uint16_t controlStatus; + if (gReady) { - isEnabled = true; + // Check the GA bit (bit 0) in CONTROL_STATUS + data[0] = 0; + if ((gpI2c->write(gAddress, &(data[0]), 1) == 0) && + getTwoBytes(0, &controlStatus)) { +#ifdef DEBUG_BQ35100 + printf("BatteryGaugeBq35100 (I2C 0x%02x): read 0x%04x as CONTROL_STATUS.\n", gAddress >> 1, controlStatus); +#endif + if ((controlStatus & 0x01) == 0x01) { + gaugeEnabled = true; + } + } } - return isEnabled; + return gaugeEnabled; } // Set the designed capacity of the cell. @@ -553,7 +640,7 @@ data[1] = capacityMAh; // Lower byte of design capacity // Write to the "Cell Design Capacity mAh" address in data flash - if (writeExtendedData(0x41fe, sizeof(data), &(data[0]))) { + if (writeExtendedData(0x41fe, &(data[0]), sizeof(data))) { success = true; #ifdef DEBUG_BQ35100 @@ -603,7 +690,7 @@ int32_t temperatureC = 0; uint16_t data; - if (gReady && (gGaugeOn || makeAdcReading())) { + if (gReady && makeAdcReading()) { gpI2c->lock(); // Read from the temperature register address if (getTwoBytes (0x06, &data)) { @@ -621,10 +708,6 @@ #endif } - if (!gGaugeOn) { - *pGaugeEnable = 0; - } - gpI2c->unlock(); } @@ -637,7 +720,7 @@ bool success = false; uint16_t data = 0; - if (gReady && (gGaugeOn || makeAdcReading())) { + if (gReady && makeAdcReading()) { gpI2c->lock(); // Read from the voltage register address if (getTwoBytes (0x08, &data)) { @@ -653,10 +736,6 @@ #endif } - if (!gGaugeOn) { - *pGaugeEnable = 0; - } - gpI2c->unlock(); } @@ -668,9 +747,9 @@ { bool success = false; int32_t currentMA = 0; - uint16_t data; + uint16_t data = 0; - if (gReady && (gGaugeOn || makeAdcReading())) { + if (gReady && makeAdcReading()) { gpI2c->lock(); // Read from the average current register address if (getTwoBytes (0x0c, &data)) { @@ -685,10 +764,6 @@ #endif } - if (!gGaugeOn) { - *pGaugeEnable = 0; - } - gpI2c->unlock(); } @@ -702,7 +777,7 @@ char bytes[5]; uint32_t data; - if (gReady && (gGaugeOn || makeAdcReading())) { + if (gReady && makeAdcReading()) { gpI2c->lock(); // Read four bytes from the AccummulatedCapacity register address @@ -728,10 +803,6 @@ #endif } - if (!gGaugeOn) { - *pGaugeEnable = 0; - } - gpI2c->unlock(); } @@ -779,8 +850,9 @@ uint32_t usedCapacityUAh; int32_t batteryPercentage; - // First, get the designed capacity + // First, get the designed capacity (which is actually in mAh) if (getDesignCapacity(&designCapacityUAh)) { + // Convert to uAh designCapacityUAh *= 1000; // Then get the used capacity if (getUsedCapacity(&usedCapacityUAh)) { @@ -805,6 +877,129 @@ return success; } +// Indicate that a new battery has been inserted. +bool BatteryGaugeBq35100::newBattery(uint32_t capacityMAh) +{ + bool success = false; + char data[3]; + + if (gReady) { + if (setDesignCapacity(capacityMAh)) { + gpI2c->lock(); + // Send a control command to indicate NEW_BATTERY + data[0] = 0x3e; // Set address to ManufacturerAccessControl + data[1] = 0x13; // First byte of NEW_BATTERY sub-command (0x13) + data[2] = 0xA6; // Second byte of NEW_BATTERY sub-command (0xA6) (register address will auto-increment) + if (gpI2c->write(gAddress, &(data[0]), 3) == 0) { + success = true; +#ifdef DEBUG_BQ35100 + printf("BatteryGaugeBq35100 (I2C 0x%02x): new battery set.\n", gAddress >> 1); +#endif + } + + gpI2c->unlock(); + } + } + + return success; +} + +// Read configuration data. +bool BatteryGaugeBq35100::advancedGetConfig(int32_t address, char * pData, int32_t length) +{ + bool success = false; + + if (gReady) { + gpI2c->lock(); + + success = readExtendedData(address, pData, length); + + gpI2c->unlock(); + } + + return success; +} + +// Write configuration data. +bool BatteryGaugeBq35100::advancedSetConfig(int32_t address, const char * pData, int32_t length) +{ + bool success = false; + + if (gReady) { + gpI2c->lock(); + + success = writeExtendedData(address, pData, length); + + gpI2c->unlock(); + } + + return success; +} + +// Send a control word. +bool BatteryGaugeBq35100::advancedSendControlWord(uint16_t controlWord, uint16_t *pDataReturned) +{ + bool success = false; + char data[3]; + + if (gReady) { + gpI2c->lock(); + + // Send the control command + data[0] = 0x3e; // Set address to ManufacturerAccessControl + data[1] = (char) controlWord; // First byte of controlWord + data[2] = (char) (controlWord >> 8); // Second byte of controlWord + if (gpI2c->write(gAddress, &(data[0]), 3) == 0) { + // Read the two bytes returned if requested + if (pDataReturned != NULL) { + if (getTwoBytes(0x40, pDataReturned)) { // Read from MACData + success = true; +#ifdef DEBUG_BQ35100 + printf("BatteryGaugeBq35100 (I2C 0x%02x): sent control word 0x%04x, read back 0x%04x.\n", gAddress >> 1, controlWord, *pDataReturned); +#endif + } + } else { + success = true; +#ifdef DEBUG_BQ35100 + printf("BatteryGaugeBq35100 (I2C 0x%02x): sent control word 0x%04x.\n", gAddress >> 1, controlWord); +#endif + } + } + + gpI2c->unlock(); + } + + return success; +} + +// Read two bytes starting at a given address on the chip. +bool BatteryGaugeBq35100::advancedGet(uint8_t address, uint16_t *pDataReturned) +{ + bool success = false; + uint16_t value = 0; + + if (gReady) { + // Make sure there's a recent reading, as most + // of these commands involve the chip having done one + if (makeAdcReading()) { + gpI2c->lock(); + // Read the data + if (getTwoBytes(address, &value)) { + success = true; +#ifdef DEBUG_BQ35100 + printf("BatteryGaugeBq35100 (I2C 0x%02x): read 0x%04x from addresses 0x%02x and 0x%02x.\n", gAddress >> 1, value, address, address + 1); +#endif + if (pDataReturned != NULL) { + *pDataReturned = value; + } + } + gpI2c->unlock(); + } + } + + return success; +} + // Get the security mode of the chip. BatteryGaugeBq35100::SecurityMode BatteryGaugeBq35100::advancedGetSecurityMode(void) { @@ -813,17 +1008,8 @@ if (gReady) { gpI2c->lock(); - if (!gGaugeOn) { - *pGaugeEnable = 1; - wait_ms(GAUGE_ENABLE_SETTLING_TIME_MS); - } + securityMode = getSecurityMode(); - securityMode = getSecurityMode(); - - if (!gGaugeOn) { - *pGaugeEnable = 0; - } - gpI2c->unlock(); } @@ -838,17 +1024,8 @@ if (gReady) { gpI2c->lock(); - if (!gGaugeOn) { - *pGaugeEnable = 1; - wait_ms(GAUGE_ENABLE_SETTLING_TIME_MS); - } - success = setSecurityMode(securityMode); - if (!gGaugeOn) { - *pGaugeEnable = 0; - } - gpI2c->unlock(); } @@ -862,14 +1039,14 @@ SecurityMode securityMode; char data[3]; - if (gReady && (gpI2c != NULL)) { + if (gReady) { gpI2c->lock(); securityMode = getSecurityMode(); // Must be inside lock() // Handle unsealing, as this command only works when unsealed if (setSecurityMode(SECURITY_MODE_UNSEALED)) { // Send a RESET sub-command - data[0] = 0x3e; // Set address to first register for ManufacturerAccessControl + data[0] = 0x3e; // Set address to ManufacturerAccessControl data[1] = 0x41; // First byte of RESET sub-command (0x41) data[2] = 0x00; // Second byte of RESET sub-command (0x00) (register address will auto-increment) @@ -885,10 +1062,6 @@ success = false; } } - - if (!gGaugeOn) { - *pGaugeEnable = 0; - } gpI2c->unlock(); }