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

bq35100.cpp

Committer:
RobMeades
Date:
2017-11-09
Revision:
1:ee7cc8d75283
Parent:
0:cec745c014b7
Child:
2:4c699a813451

File content as of revision 1:ee7cc8d75283:

/* mbed Microcontroller Library
 * Copyright (c) 2017 u-blox
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * @file bq35100.cpp
 * This file defines the API to the TI BQ35100 battery gauge chip.
 */

/** Define these to print debug information. */
#define DEBUG_BQ35100
#define DEBUG_BQ35100_BLOCK_DATA

#include <mbed.h>
#include <battery_gauge_bq35100.h>

#ifdef DEBUG_BQ35100
# include <stdio.h>
#endif

// ----------------------------------------------------------------
// 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
// ----------------------------------------------------------------

// ----------------------------------------------------------------
// 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 success = false;
    char data[3];

    if (gpI2c != NULL) {
        data[0] = registerAddress;
        data[1] = 0;
        data[2] = 0;

        // Send a command to read from registerAddress
        if ((gpI2c->write(gAddress, &(data[0]), 1, true) == 0) &&
            (gpI2c->read(gAddress, &(data[1]), 2) == 0)) {
            success = true;
            if (pBytes) {
                *pBytes = (((uint16_t) data[2]) << 8) + data[1];
            }
        }
    }

    return success;
}

// Compute the checksum over an address plus the block of data.
uint8_t BatteryGaugeBq35100::computeChecksum (const char * pData, int32_t length)
{
    uint8_t checkSum = 0;
    uint8_t x = 0;

    if (pData != NULL) {
#ifdef DEBUG_BQ35100_BLOCK_DATA
        printf ("BatteryGaugeBq35100 (I2C 0x%02x): computing check sum on data block.\n", gAddress >> 1);
        printf (" 0  1  2  3  4  5  6  7   8  9  A  B  C  D  E  F\n");
#endif
        for (x = 1; x <= length; x++) {
            checkSum += *pData;
            
#ifdef DEBUG_BQ35100_BLOCK_DATA
            if (x % 16 == 8) {
                printf ("%02x  ", *pData);
            } else if (x % 16 == 0) {
                printf ("%02x\n", *pData);
            } else {
                printf ("%02x-", *pData);
            }
#endif
            pData++;
        }

        checkSum = 0xff - checkSum;
    }

#ifdef DEBUG_BQ35100_BLOCK_DATA
    if (x % 16 !=  1) {
        printf("\n");
    }
    
    printf ("BatteryGaugeBq35100 (I2C 0x%02x): check sum is 0x%02x.\n", gAddress >> 1, checkSum);
#endif    
    
    return checkSum;
}

// 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)
{
    int32_t lengthRead;
    bool success = false;
    SecurityMode securityMode = getSecurityMode();
    char block[32 + 2 + 2]; // 32 bytes of data, 2 bytes of address,
                            // 1 byte of MACDataSum and 1 byte of MACDataLen
    char data[3];

    // Handle security mode
    if (setSecurityMode(SECURITY_MODE_UNSEALED)) {
        if ((gpI2c != NULL) && (length <= 32) && (address >= 0x4000) && (address < 0x4400) && (pData != NULL)) {
#ifdef DEBUG_BQ35100
            printf("BatteryGaugeBq35100 (I2C 0x%02x): preparing to read %d byte(s) from address 0x%04x.\n", gAddress >> 1, (int) length, (unsigned int) address);
#endif
            // Enable Block Data Control (0x61)
            data[0] = 0x61;
            data[1] = 0;

            if (gpI2c->write(gAddress, &(data[0]), 2) == 0) {
                // Write address to ManufacturerAccessControl (0x3e)
                data[0] = 0x3e;
                data[1] = (char) address;
                data[2] = (char) (address >> 8);

                if (gpI2c->write(gAddress, &(data[0]), 3) == 0) {
                    // Read the address from ManufacturerAccessControl (0x3e then 0x3f),
                    // data from MACData (0x40 to 0x5f), checksum from MACDataSum (0x60) 
                    // and length from MACDataLen (0x61)
                    if ((gpI2c->write(gAddress, &(data[0]), 1, true) == 0) &&
                        (gpI2c->read(gAddress, &(block[0]), sizeof (block)) == 0)) {
                        // Check that the address matches
                        if ((block[0] == (char) address) && (block[1] == (char) (address >> 8))) {
                            // Check that the checksum matches (-2 on MACDataLen as it includes MACDataSum and itself)
                            if (block[34] == computeChecksum (&(block[0]), block[35] - 2)) {
                                // All is good, copy the data to the user
                                lengthRead = block[35] - 4; // -4 rather than -2 to remove the two bytes of address as well
                                 if (lengthRead > length) {
                                    lengthRead = length;
                                }
                                memcpy(pData, &(block[2]), lengthRead);
                                success = true;
#ifdef DEBUG_BQ35100
                                printf("BatteryGaugeBq35100 (I2C 0x%02x): %d byte(s) read successfully.\n", gAddress >> 1, (int) lengthRead);
#endif
                            } else {
#ifdef DEBUG_BQ35100
                                printf("BatteryGaugeBq35100 (I2C 0x%02x): checksum didn't match (0x%02x expected).\n", gAddress >> 1, block[34]);
#endif
                            }
                        } else {
#ifdef DEBUG_BQ35100
                            printf("BatteryGaugeBq35100 (I2C 0x%02x): address didn't match (expected 0x%04x, received 0x%02x%02x).\n", gAddress >> 1, (unsigned int) address, block[1], block[0]);
#endif
                        }
                    } else {
#ifdef DEBUG_BQ35100
                        printf("BatteryGaugeBq35100 (I2C 0x%02x): unable to read %d bytes from ManufacturerAccessControl.\n", gAddress >> 1, sizeof (block));
#endif
                    }
                } else {
#ifdef DEBUG_BQ35100
                    printf("BatteryGaugeBq35100 (I2C 0x%02x): unable to write %d bytes to ManufacturerAccessControl.\r", gAddress >> 1, 3);
#endif
                }
            } else {
#ifdef DEBUG_BQ35100
                printf("BatteryGaugeBq35100 (I2C 0x%02x): unable to set Block Data Control.\n", gAddress >> 1);
#endif
            }
        } else {
#ifdef DEBUG_BQ35100
            printf("BatteryGaugeBq35100 (I2C 0x%02x): unable to set the security mode of the chip.\n", gAddress >> 1);
#endif
        }
    }

    // Put the security mode back to what it was
    if (!setSecurityMode(securityMode)) {
        success = false;
    }

    return success;
}

// 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 success = false;
    SecurityMode securityMode = getSecurityMode();
    char data[3 + 32];

    if ((gpI2c != NULL) && (length <= 32) && (address >= 0x4000) && (address < 0x4400) && (pData != NULL)) {
#ifdef DEBUG_BQ35100
        printf("BatteryGaugeBq35100 (I2C 0x%02x): preparing to write %d byte(s) to address 0x%04x.\n", gAddress >> 1, (int) length, (unsigned int) address);
#endif
        // Handle security mode
        if (setSecurityMode(SECURITY_MODE_UNSEALED)) {
            // Enable Block Data Control (0x61)
            data[0] = 0x61;
            data[1] = 0;

            if (gpI2c->write(gAddress, &(data[0]), 2) == 0) {
                // Start write at ManufacturerAccessControl (0x3e)
                data[0] = 0x3e;
                // Next two bytes are the address we will write to
                data[1] = (char) address;
                data[2] = (char) (address >> 8);
                // Remaining bytes are the data bytes we wish to write
                memcpy (&(data[3]), pData, length);

                if (gpI2c->write(gAddress, &(data[0]), 3 + length) == 0) {
                    // Compute the checksum and write it to MACDataSum (0x60)
                    data[1] = computeChecksum (&(data[1]), length + 2);
                    data[0] = 0x60;
                    
                    if (gpI2c->write(gAddress, &(data[0]), 2) == 0) {
                        // Write 4 + length to MACDataLen (0x61)
                        data[1] = length + 4;
                        data[0] = 0x61;

                        if (gpI2c->write(gAddress, &(data[0]), 2) == 0) {
                            // TODO check for successful write
                            success = true;
#ifdef DEBUG_BQ35100
                            printf("BatteryGaugeBq35100 (I2C 0x%02x): write successful.\n", gAddress >> 1);
#endif
                        } else {
#ifdef DEBUG_BQ35100
                            printf("BatteryGaugeBq35100 (I2C 0x%02x): unable to read write to MACDataLen.\n", gAddress >> 1);
#endif
                        }
                    } else {
#ifdef DEBUG_BQ35100
                        printf("BatteryGaugeBq35100 (I2C 0x%02x): unable to write to MACDataSum.\n", gAddress >> 1);
#endif
                    }
                } else {
#ifdef DEBUG_BQ35100
                    printf("BatteryGaugeBq35100 (I2C 0x%02x): unable to write %d bytes to ManufacturerAccessControl.\r", gAddress >> 1, (int) length + 2);
#endif
                }
            } else {
#ifdef DEBUG_BQ35100
                printf("BatteryGaugeBq35100 (I2C 0x%02x): unable to set Block Data Control.\n", gAddress >> 1);
#endif
            }
        } else {
#ifdef DEBUG_BQ35100
            printf("BatteryGaugeBq35100 (I2C 0x%02x): unable to set the security mode of the chip.\n", gAddress >> 1);
#endif
        }
    }
    
    // Put the security mode back to what it was
    if (!setSecurityMode(securityMode)) {
        success = false;
    }

    return success;
}

// Get the security mode of the chip.
// Note: gpI2c should be locked before this is called.
BatteryGaugeBq35100::SecurityMode BatteryGaugeBq35100::getSecurityMode(void)
{
    SecurityMode securityMode = SECURITY_MODE_UNKNOWN;
    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);
#ifdef DEBUG_BQ35100
            printf("BatteryGaugeBq35100 (I2C 0x%02x): security mode is 0x%02x (control status 0x%04x).\r\n", gAddress >> 1, securityMode, controlStatus);
#endif
        }
    }
    
    return securityMode;
}

// Set the security mode of the chip.
// Note: gpI2c should be locked before this is called.
bool BatteryGaugeBq35100::setSecurityMode(SecurityMode securityMode)
{
    bool success = false;
    char data[3];
    SecurityMode currentSecurityMode = getSecurityMode();
    
    if (securityMode != SECURITY_MODE_UNKNOWN) {
        if (securityMode != currentSecurityMode) {
            // For reasons that aren't clear, the BQ35100 sometimes refuses
            // 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)
                switch (securityMode) {
                    case SECURITY_MODE_SEALED:
                        // Just seal the chip
                        data[1] = 0x20;  // First byte of SEALED sub-command (0x20)
                        data[2] = 0x00;  // Second byte of SEALED sub-command (0x00) (register address will auto-increment)
                        gpI2c->write(gAddress, &(data[0]), 3);
                    break;
                    case SECURITY_MODE_FULL_ACCESS:
                        // Send the full access code with endianness conversion
                        // in TWO writes
                        data[2] = (char) (gFullAccessCodes >> 24);
                        data[1] = (char) (gFullAccessCodes >> 16);
                        gpI2c->write(gAddress, &(data[0]), 3);
                        data[2] = (char) (gFullAccessCodes >> 8);
                        data[1] = (char) gFullAccessCodes;
                        gpI2c->write(gAddress, &(data[0]), 3);
                    break;
                    case SECURITY_MODE_UNSEALED:
                        data[2] = (char) (gSealCodes >> 24);
                        data[1] = (char) (gSealCodes >> 16);
                        gpI2c->write(gAddress, &(data[0]), 3);
                        data[2] = (char) (gSealCodes >> 8);
                        data[1] = (char) gSealCodes;
                        gpI2c->write(gAddress, &(data[0]), 3);
                    break;
                    case SECURITY_MODE_UNKNOWN:
                    default:
                        MBED_ASSERT(false);
                    break;
                }

                currentSecurityMode = getSecurityMode();
                if (currentSecurityMode == securityMode) {
                    success = true;
#ifdef DEBUG_BQ35100
                    printf("BatteryGaugeBq35100 (I2C 0x%02x): security mode is now 0x%02x.\n", gAddress >> 1, currentSecurityMode);
#endif
                } else {
                    wait_ms(1000);
#ifdef DEBUG_BQ35100
                    printf("BatteryGaugeBq35100 (I2C 0x%02x): security mode set failed (wanted 0x%02x, got 0x%02x), will retry.\n", gAddress >> 1, securityMode, currentSecurityMode);
#endif
                }
            }
        } else {
            success = true;
        }
    }
    
    return success;
}

// 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);
        }
    }
    gpI2c->unlock();
    
    return success;
}

//----------------------------------------------------------------
// PUBLIC FUNCTIONS
// ----------------------------------------------------------------

// Constructor.
BatteryGaugeBq35100::BatteryGaugeBq35100(void)
{
    gpI2c = NULL;
    pGaugeEnable = NULL;
    gReady = false;
    gSealCodes = 0;
    gFullAccessCodes = 0;
    gGaugeOn = false;
}

// Destructor.
BatteryGaugeBq35100::~BatteryGaugeBq35100(void)
{
    if (pGaugeEnable) {
        *pGaugeEnable = 0;
        delete pGaugeEnable;
    }
}

// Initialise ourselves.
bool BatteryGaugeBq35100::init(I2C * pI2c, PinName gaugeEnable, uint8_t address, uint32_t sealCodes)
{
    uint16_t answer;
    char data[4];

    gpI2c = pI2c;
    gAddress = address << 1;
    gSealCodes = sealCodes;
    
    pGaugeEnable = new DigitalOut(gaugeEnable, 1);
    wait_ms(GAUGE_ENABLE_SETTLING_TIME_MS);

    if (gpI2c != NULL) {
        gpI2c->lock();
        gpI2c->frequency(I2C_CLOCK_FREQUENCY);
        
        // Send a control command to read the device type
        data[0] = 0x3e;  // Set address to ManufacturerAccessControl
        data[1] = 0x03;  // First byte of HW_VERSION sub-command (0x03)
        data[2] = 0x00;  // Second byte of HW_VERSION sub-command (0x00) (register address will auto-increment)

        if ((gpI2c->write(gAddress, &(data[0]), 3) == 0) &&
            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]))) {
                    // 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
                    printf("BatteryGaugeBq35100 (I2C 0x%02x): full access code is 0x%08x.\n", gAddress >> 1,
                           (unsigned int) gFullAccessCodes);
#endif
                    gReady = true;
                }
            }

#ifdef DEBUG_BQ35100
            printf("BatteryGaugeBq35100 (I2C 0x%02x): read 0x%04x as HW_VERSION, expected 0x00a8.\n", gAddress >> 1, answer);
#endif
        }

        if (!gGaugeOn) {
            *pGaugeEnable = 0;
        }
                
        gpI2c->unlock();
    }

#ifdef DEBUG_BQ35100
    if (gReady) {
        printf("BatteryGaugeBq35100 (I2C 0x%02x): handler initialised.\n", gAddress >> 1);
    } else {
        printf("BatteryGaugeBq35100 (I2C 0x%02x): init NOT successful.\n", gAddress >> 1);
    }
#endif

    return gReady;
}

// Switch on the battery capacity monitor.
bool BatteryGaugeBq35100::enableGauge(void)
{
    bool success = false;
    
    if (gReady) {
        *pGaugeEnable = 1;
        wait_ms(GAUGE_ENABLE_SETTLING_TIME_MS);
        success = true;
    }
    
    return success;
}

// Switch off the battery capacity monitor.
bool BatteryGaugeBq35100::disableGauge(void)
{
    bool success = false;
    
    if (gReady) {
        *pGaugeEnable = 0;
        success = true;
    }
    
    return success;
}

// Determine whether battery gauging is enabled.
bool BatteryGaugeBq35100::isGaugeEnabled(void)
{
    bool isEnabled = false;
    
    if (gReady) {
        isEnabled = true;
    }
    
    return isEnabled;
}

// Set the designed capacity of the cell.
bool BatteryGaugeBq35100::setDesignCapacity(uint32_t capacityMAh)
{
    bool success = false;
    char data[2];

    if (gReady) {
        gpI2c->lock();
        
        data[0] = capacityMAh >> 8;  // Upper byte of design capacity 
        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]))) {
            success = true;
 
#ifdef DEBUG_BQ35100
            printf("BatteryGaugeBq35100 (I2C 0x%02x): designed cell capacity set to %d mAh.\n", gAddress >> 1,
                   (unsigned int) capacityMAh);
#endif
        }

        gpI2c->unlock();
    }

    return success;
}

// Get the designed capacity of the cell.
bool BatteryGaugeBq35100::getDesignCapacity(uint32_t *pCapacityMAh)
{
    bool success = false;
    uint16_t data;
    
    if (gReady) {
        gpI2c->lock();

        // Read from the DesignCapacity address
        if (getTwoBytes (0x3c, &data)) {
            success = true;

            // The answer is in mAh
            if (pCapacityMAh) {
                *pCapacityMAh = data;
            }

#ifdef DEBUG_BQ35100
            printf("BatteryGaugeBq35100 (I2C 0x%02x): designed cell capacity is %d mAh.\n", gAddress >> 1, data);
#endif
        }

    }
    
    return success;
}

// Get the temperature of the chip.
bool BatteryGaugeBq35100::getTemperature(int32_t *pTemperatureC)
{
    bool success = false;
    int32_t temperatureC = 0;
    uint16_t data;

    if (gReady && (gGaugeOn || makeAdcReading())) {
        gpI2c->lock();
        // Read from the temperature register address
        if (getTwoBytes (0x06, &data)) {
            success = true;

            // The answer is in units of 0.1 K, so convert to C
            temperatureC = ((int32_t) data / 10) - 273;

            if (pTemperatureC) {
                *pTemperatureC = temperatureC;
            }

#ifdef DEBUG_BQ35100
            printf("BatteryGaugeBq35100 (I2C 0x%02x): chip temperature %.1f K, so %d C.\n", gAddress >> 1, ((float) data) / 10, (int) temperatureC);
#endif
        }
        
        if (!gGaugeOn) {
            *pGaugeEnable = 0;
        }
        
        gpI2c->unlock();
    }

    return success;
}

// Get the voltage of the battery.
bool BatteryGaugeBq35100::getVoltage(int32_t *pVoltageMV)
{
    bool success = false;
    uint16_t data = 0;

    if (gReady && (gGaugeOn || makeAdcReading())) {
        gpI2c->lock();
        // Read from the voltage register address
        if (getTwoBytes (0x08, &data)) {
            success = true;

            // The answer is in mV
            if (pVoltageMV) {
                *pVoltageMV = (int32_t) data;
            }

#ifdef DEBUG_BQ35100
            printf("BatteryGaugeBq35100 (I2C 0x%02x): battery voltage %.3f V.\n", gAddress >> 1, ((float) data) / 1000);
#endif
        }

        if (!gGaugeOn) {
            *pGaugeEnable = 0;
        }
        
        gpI2c->unlock();
    }
    
    return success;
}

// Get the current flowing from the battery.
bool BatteryGaugeBq35100::getCurrent(int32_t *pCurrentMA)
{
    bool success = false;
    int32_t currentMA = 0;
    uint16_t data;

    if (gReady && (gGaugeOn || makeAdcReading())) {
        gpI2c->lock();            
        // Read from the average current register address
        if (getTwoBytes (0x0c, &data)) {
            success = true;

            if (pCurrentMA) {
                *pCurrentMA = currentMA;
            }

#ifdef DEBUG_BQ35100
            printf("BatteryGaugeBq35100 (I2C 0x%02x): current %d mA.\n", gAddress >> 1, (int) currentMA);
#endif
        }

        if (!gGaugeOn) {
            *pGaugeEnable = 0;
        }
        
        gpI2c->unlock();
    }
    
    return success;
}

// Get the battery capacity used.
bool BatteryGaugeBq35100::getUsedCapacity(uint32_t *pCapacityUAh)
{
    bool success = false;
    char bytes[5];
    uint32_t data;

    if (gReady && (gGaugeOn || makeAdcReading())) {
        gpI2c->lock();
        // Read four bytes from the AccummulatedCapacity register address
        
        // Send a command to read from registerAddress
        bytes[0] = 0x02;
        bytes[1] = 0;
        bytes[2] = 0;
        bytes[3] = 0;
        bytes[4] = 0;

        if ((gpI2c->write(gAddress, &(bytes[0]), 1) == 0) &&
            (gpI2c->read(gAddress, &(bytes[1]), 4) == 0)) {
            success = true;
            data = (((uint32_t) bytes[4]) << 24) + (((uint32_t) bytes[3]) << 16) + (((uint32_t) bytes[2]) << 8) + bytes[1];
 
            // The answer is in uAh
            if (pCapacityUAh) {
                *pCapacityUAh = data;
            }

#ifdef DEBUG_BQ35100
            printf("BatteryGaugeBq35100 (I2C 0x%02x): battery capacity used %u uAh.\n", gAddress >> 1, (unsigned int) data);
#endif
        }

        if (!gGaugeOn) {
            *pGaugeEnable = 0;
        }
        
        gpI2c->unlock();
    }
    
    return success;
}

// Get the battery capacity remaining.
bool BatteryGaugeBq35100::getRemainingCapacity(uint32_t *pCapacityUAh)
{
    bool success = false;
    uint32_t designCapacityUAh;
    uint32_t usedCapacityUAh;

    // First, get the designed capacity
    if (getDesignCapacity(&designCapacityUAh)) {
        designCapacityUAh *= 1000;
        // Then get the used capacity
        if (getUsedCapacity(&usedCapacityUAh)) {
            success = true;
            // Limit the result
            if (usedCapacityUAh > designCapacityUAh) {
                usedCapacityUAh = designCapacityUAh;
            }

            // The answer is in uAh
            if (pCapacityUAh) {
                *pCapacityUAh = designCapacityUAh - usedCapacityUAh;
            }

#ifdef DEBUG_BQ35100
            printf("BatteryGaugeBq35100 (I2C 0x%02x): battery capacity remaining %u uAh (from a designed capacity of %d uAh).\n", gAddress >> 1,
                   (unsigned int) (designCapacityUAh - usedCapacityUAh), (unsigned int) designCapacityUAh);
#endif
        }
    }
    
    return success;
}

// Get the percentage capacity remaining.
bool BatteryGaugeBq35100::getRemainingPercentage(int32_t *pBatteryPercentage)
{
    bool success = false;
    uint32_t designCapacityUAh;
    uint32_t usedCapacityUAh;
    int32_t batteryPercentage;

    // First, get the designed capacity
    if (getDesignCapacity(&designCapacityUAh)) {
        designCapacityUAh *= 1000;
        // Then get the used capacity
        if (getUsedCapacity(&usedCapacityUAh)) {
            success = true;
            // Limit the result
            if (usedCapacityUAh > designCapacityUAh) {
                usedCapacityUAh = designCapacityUAh;
            }
            batteryPercentage = (uint64_t) (designCapacityUAh - usedCapacityUAh) * 100 / designCapacityUAh;

            if (pBatteryPercentage) {
                *pBatteryPercentage = batteryPercentage;
            }

#ifdef DEBUG_BQ35100
            printf("BatteryGaugeBq35100 (I2C 0x%02x): battery capacity remaining %d%%.\n", gAddress >> 1,
                   (unsigned int) batteryPercentage);
#endif
        }
    }
    
    return success;
}

// Get the security mode of the chip.
BatteryGaugeBq35100::SecurityMode BatteryGaugeBq35100::advancedGetSecurityMode(void)
{
    SecurityMode securityMode = SECURITY_MODE_UNKNOWN;

    if (gReady) {
        gpI2c->lock();

        if (!gGaugeOn) {
            *pGaugeEnable = 1;
            wait_ms(GAUGE_ENABLE_SETTLING_TIME_MS);
        }

        securityMode = getSecurityMode();
        
        if (!gGaugeOn) {
            *pGaugeEnable = 0;
        }
        
        gpI2c->unlock();
    }

    return securityMode;
}

// Set the security mode of the chip.
bool BatteryGaugeBq35100::advancedSetSecurityMode(SecurityMode securityMode)
{
    bool success = false;

    if (gReady) {
        gpI2c->lock();

        if (!gGaugeOn) {
            *pGaugeEnable = 1;
            wait_ms(GAUGE_ENABLE_SETTLING_TIME_MS);
        }

        success = setSecurityMode(securityMode);

        if (!gGaugeOn) {
            *pGaugeEnable = 0;
        }
        
        gpI2c->unlock();
    }

    return success;
}

// Do a hard reset of the chip.
bool BatteryGaugeBq35100::advancedReset(void)
{
    bool success = false;
    SecurityMode securityMode;
    char data[3];

    if (gReady && (gpI2c != NULL)) {
        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[1] = 0x41;  // First byte of RESET sub-command (0x41)
            data[2] = 0x00;  // Second byte of RESET sub-command (0x00) (register address will auto-increment)

            if (gpI2c->write(gAddress, &(data[0]), 3) == 0) {
                success = true;
#ifdef DEBUG_BQ35100
                printf("BatteryGaugeBq35100 (I2C 0x%02x): chip hard reset.\n", gAddress >> 1);
#endif
            }
            
            // Set the security mode back to what it was
            if (!setSecurityMode(securityMode)) {
                success = false;
            }
        }
        
        if (!gGaugeOn) {
            *pGaugeEnable = 0;
        }

        gpI2c->unlock();
    }

    return success;
}

/* End Of File */