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:
19 months ago
Revision:
2:4c699a813451
Parent:
1:ee7cc8d75283

File content as of revision 2:4c699a813451:

/* 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 long to wait for a security mode change to succeed. */
#define SET_SECURITY_MODE_RETRY_SECONDS 5

/** 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 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, char * pData, int32_t length)
{
    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, 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
        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) {
                            // 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
                        } 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;

    // 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);
#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] = 0x3e;  // Set address to ManufacturerAccessControl
                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.
bool BatteryGaugeBq35100::makeAdcReading(void)
{
    bool success = false;
    
    if (isGaugeEnabled()) {
        success = true;
    } else {
        if (enableGauge() && disableGauge()) {
            success = true;
        }
    }
    
    return success;
}

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

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

// Destructor.
BatteryGaugeBq35100::~BatteryGaugeBq35100(void)
{
}

// 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;
    
    if (gaugeEnable != NC) {
        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, &(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
                    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
        }

        disableGauge();
        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(bool nonVolatile)
{
    bool accumulatedCapacityOn = false;
    bool success = false;
    char data[3];
    char opConfig;
    uint16_t controlStatus;

    if (gReady) {
        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;
}

// 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) {
        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;
}

// Determine whether battery gauging is enabled.
bool BatteryGaugeBq35100::isGaugeEnabled(void)
{
    bool gaugeEnabled = false;
    char data[1];
    uint16_t controlStatus;

    if (gReady) {
        // 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 gaugeEnabled;
}

// 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, &(data[0]), sizeof(data))) {
            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 && 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
        }
        
        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 && 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
        }

        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 = 0;

    if (gReady && 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
        }

        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 && 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
        }

        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 (which is actually in mAh)
    if (getDesignCapacity(&designCapacityUAh)) {
        // Convert to uAh
        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;
}

// 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)
{
    SecurityMode securityMode = SECURITY_MODE_UNKNOWN;

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

        securityMode = getSecurityMode();

        gpI2c->unlock();
    }

    return securityMode;
}

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

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

        success = setSecurityMode(securityMode);

        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->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 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;
            }
        }

        gpI2c->unlock();
    }

    return success;
}

/* End Of File */