Library to interface to the TI BQ27441, a fuel gauge monitor
Fork of battery-gauge-bq27441 by
Diff: bq27441.cpp
- Revision:
- 1:566163f17cde
- Child:
- 2:93310a83401a
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bq27441.cpp Mon Apr 10 11:18:51 2017 +0100 @@ -0,0 +1,1192 @@ +/* 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 bq27441.cpp + * This file defines the API to the TI BQ27441 battery gauge chip. + */ + +/// Define these to print debug information. +//#define DEBUG_BQ27441 +//#define DEBUG_BQ27441_BLOCK_DATA + +#include <mbed.h> +#include <battery_gauge_bq27441.h> + +#ifdef DEBUG_BQ27441 +# 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 all the ADC readings to be performed. +#define ADC_READ_WAIT_MS 1000 + +/// Delay after the last config update before we can unseal the +// chip again (see section 6.4.5.1.1 of the BQ27441 technical +// reference manual). +#define UNSEAL_DELAY_MS 4000 + +// ---------------------------------------------------------------- +// PRIVATE VARIABLES +// ---------------------------------------------------------------- + +// ---------------------------------------------------------------- +// GENERIC PRIVATE FUNCTIONS +// ---------------------------------------------------------------- + +/// Read two bytes from an address. +// Note: gpI2c should be locked before this is called. +bool BatteryGaugeBq27441::getTwoBytes (uint8_t registerAddress, uint16_t *pBytes) +{ + bool success = false; + char data[3]; + + if (gpI2c != NULL) { + // Send a command to read from registerAddress + data[0] = registerAddress; + data[1] = 0; + data[2] = 0; + + if ((gpI2c->write(gAddress, &(data[0]), 1) == 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 a block of data. +uint8_t BatteryGaugeBq27441::computeChecksum(const char * pData) +{ + uint8_t checkSum = 0; + uint8_t x; + + if (pData != NULL) { +#ifdef DEBUG_BQ27441_BLOCK_DATA + printf ("BatteryGaugeBq27441 (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 <= 32; x++) { + checkSum += *pData; + +#ifdef DEBUG_BQ27441_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_BQ27441_BLOCK_DATA + if (x % 16 != 1) { + printf("\n"); + } + + printf ("BatteryGaugeBq27441 (I2C 0x%02x): check sum is 0x%02x.\n", gAddress >> 1, checkSum); +#endif + + return checkSum; +} + +/// Read data of a given length and class ID. +// Note: gpI2c should be locked before this is called. +bool BatteryGaugeBq27441::readExtendedData(uint8_t subClassId, int32_t offset, int32_t length, char * pData) +{ + bool success = false; + bool wasSealed = false; + char block[32]; + char data[3]; + + if ((gpI2c != NULL) && (length <= 32) && (pData != NULL)) { + + // The offset + length combination must not cross a 32-byte boundary + if (offset / 32 == (offset + length - 1) / 32) { + +#ifdef DEBUG_BQ27441 + printf("BatteryGaugeBq27441 (I2C 0x%02x): preparing to read %d byte(s) from offset %d of sub-class %d.\n", gAddress >> 1, length, offset, subClassId); +#endif + // Handle unsealing + wasSealed = isSealed(); + if (!wasSealed || unseal(gSealCode)) { + + // Enable Block Data Control (0x61) + data[0] = 0x61; + data[1] = 0; + + if (gpI2c->write(gAddress, &(data[0]), 2) == 0) { + // Write sub-class ID using Data Block Class (0x3e) + data[0] = 0x3e; + data[1] = subClassId; + + if (gpI2c->write(gAddress, &(data[0]), 2) == 0) { + // Write offset using Block Offset (0x3f) and then + // read the data block + data[0] = 0x3f; + data[1] = offset / 32; + + if ((gpI2c->write(gAddress, &(data[0]), 2, true) == 0) && + (gpI2c->read(gAddress, &(block[0]), 32) == 0)) { + // Compute the block checksum and then read it + data[2] = computeChecksum(&(block[0])); + data[0] = 0x60; + data[1] = 0; + + if ((gpI2c->write(gAddress, &(data[0]), 1, true) == 0) && + (gpI2c->read(gAddress, &(data[1]), 1) == 0)) { + + // Does the checksum match? + if (data[1] == data[2]) { + // If so read the new data from the block data area at 0x40 plus + // the offset (plus the Block Offset already written) + data[0] = 0x40 + (offset % 32); + + if ((gpI2c->write(gAddress, &(data[0]), 1, true) == 0) && + (gpI2c->read(gAddress, pData, length) == 0)) { + success = true; +#ifdef DEBUG_BQ27441 + printf("BatteryGaugeBq27441 (I2C 0x%02x): read successful.\n", gAddress >> 1); +#endif + } else { +#ifdef DEBUG_BQ27441 + printf("BatteryGaugeBq27441 (I2C 0x%02x): couldn't read all %d bytes of config.\r", gAddress >> 1, length); +#endif + } + } else { +#ifdef DEBUG_BQ27441 + printf("BatteryGaugeBq27441 (I2C 0x%02x): checksum on data block incorrect (expected 0x%02x, received 0x%02x).\n", gAddress >> 1, data[2], data[1]); +#endif + } + } else { +#ifdef DEBUG_BQ27441 + printf("BatteryGaugeBq27441 (I2C 0x%02x): unable to read Block Data Checksum for config.\n", gAddress >> 1); +#endif + } + } else { +#ifdef DEBUG_BQ27441 + printf("BatteryGaugeBq27441 (I2C 0x%02x): unable to write Block Offset (%d), or maybe read the data block, for config.\n", gAddress >> 1, data[1]); +#endif + } + } else { +#ifdef DEBUG_BQ27441 + printf("BatteryGaugeBq27441 (I2C 0x%02x): unable set sub-class ID (0x%02x) for config.\n", gAddress >> 1, subClassId); +#endif + } + } else { +#ifdef DEBUG_BQ27441 + printf("BatteryGaugeBq27441 (I2C 0x%02x): unable to set Block Data Control for config.\n", gAddress >> 1); +#endif + } + } else { +#ifdef DEBUG_BQ27441 + printf("BatteryGaugeBq27441 (I2C 0x%02x): unable to unseal chip.\n", gAddress >> 1); +#endif + } + } else { +#ifdef DEBUG_BQ27441 + printf("BatteryGaugeBq27441 (I2C 0x%02x): offset (%d) is in different 32 byte block to offset + length (%d) [length is %d].\n", gAddress >> 1, offset, offset + length, length); +#endif + } + } + + // Reseal if required, and fail if it fails + if (wasSealed) { + if (!seal()) { + success = false; + } + } + + return success; +} + +/// Write data of a given length and class ID to a given offset. This code taken from +// section 3.1 of the SLUUAC9A application technical reference manual with hints from: +// https://github.com/sparkfun/SparkFun_BQ27441_Arduino_Library/blob/master/src/SparkFunBQ27441.cpp. +// Note: gpI2c should be locked before this is called. +bool BatteryGaugeBq27441::writeExtendedData(uint8_t subClassId, int32_t offset, int32_t length, const char * pData) +{ + bool success = false; + bool wasSealed = false; + char data[3 + 32]; + char block[32]; + uint16_t answer; + uint32_t count = 0; + + if ((gpI2c != NULL) && (length <= 32) && (pData != NULL)) { + + // The offset + length combination must not cross a 32-byte boundary + if (offset / 32 == (offset + length - 1) / 32) { +#ifdef DEBUG_BQ27441 + printf("BatteryGaugeBq27441 (I2C 0x%02x): preparing to write %d byte(s) to offset %d of sub-class %d.\n", gAddress >> 1, length, offset, subClassId); +#endif + // Handle unsealing + wasSealed = isSealed(); + if (!wasSealed || unseal(gSealCode)) { + + // Send Config Update (command 0x13) + data[0] = 0; + data[1] = 0x13; + data[2] = 0; + + if (gpI2c->write(gAddress, &(data[0]), 3) == 0) { + // Wait for CONFIGUPDATE to be set in Flags register (bit 4) + count = 0; + do { + answer = 0; + getTwoBytes(0x06, &answer); + count++; + wait_ms(CONFIG_UPDATE_LOOP_DELAY_MS); + } while (((answer & (1 << 4)) == 0) && (count < CONFIG_UPDATE_LOOPS)); + + if ((answer & (1 << 4)) != 0) { + // Enable Block Data Control (0x61) + data[0] = 0x61; + data[1] = 0; + + if (gpI2c->write(gAddress, &(data[0]), 2) == 0) { + // Write sub-class ID using Data Block Class (0x3e) + data[0] = 0x3e; + data[1] = subClassId; + + if (gpI2c->write(gAddress, &(data[0]), 2) == 0) { + // Write offset using Block Offset (0x3f) and then + // read the data block + data[0] = 0x3f; + data[1] = offset / 32; + + if ((gpI2c->write(gAddress, &(data[0]), 2, true) == 0) && + (gpI2c->read(gAddress, &(block[0]), 32) == 0)) { + // Compute the existing block checksum and then read it + data[2] = computeChecksum(&(block[0])); + data[0] = 0x60; + data[1] = 0; + + if ((gpI2c->write(gAddress, &(data[0]), 1, true) == 0) && + (gpI2c->read(gAddress, &(data[1]), 1) == 0)) { + + // Does the checksum match? + if (data[1] == data[2]) { + // If so write the new data to the block data area at 0x40 plus the offset (plus the Block Offset already written) + // NOTE: I tried doing this as two separate writes, one of the offset and then another of the + // data block (so that I could use pData directly rather than having to extend the local + // data array by 32) but the chip didn't like that, hence we have to copy pData into the + // local array and do a single contiguous write. + data[0] = 0x40 + (offset % 32); + memcpy (&(data[1]), pData, length); + + if (gpI2c->write(gAddress, &(data[0]), length + 1) == 0) { + // Also write the data into our local block variable + // on top of the previously read data and use + // that to compute the new block checksum, then write it + memcpy (&(block[offset % 32]), pData, length); + data[0] = 0x60; + data[1] = computeChecksum(&(block[0])); + + if (gpI2c->write(gAddress, &(data[0]), 2) == 0) { + // Exit config mode with SOFT_RESET command 0x42 + data[0] = 0; + data[1] = 0x42; + data[2] = 0; + + if (gpI2c->write(gAddress, &(data[0]), 3) == 0) { + // Finally, wait for CONFIGUPDATE to be unset in Flags register (bit 4) + count = 0; + do { + answer = 0xFFFF; + getTwoBytes(0x06, &answer); + count++; + wait_ms(CONFIG_UPDATE_LOOP_DELAY_MS); + } while (((answer & (1 << 4)) != 0) && (count < CONFIG_UPDATE_LOOPS)); + + if ((answer & (1 << 4)) == 0) { + success = true; + // Wait around for 4 seconds if there has been a config update + // and there is a danger that we might have to unseal the device + // Note: would be nice if we could note the time and do other + // things but the timer is restarted when certain commands are sent, + // so it is better to be dumb + if (wasSealed) { + wait_ms(UNSEAL_DELAY_MS); + } +#ifdef DEBUG_BQ27441 + printf("BatteryGaugeBq27441 (I2C 0x%02x): write successful.\n", gAddress >> 1); +#endif + } else { +#ifdef DEBUG_BQ27441 + printf("BatteryGaugeBq27441 (I2C 0x%02x): Flags register didn't show config update flag unset in time (0x%04x).\n", gAddress >> 1, answer); +#endif + } + } else { +#ifdef DEBUG_BQ27441 + printf("BatteryGaugeBq27441 (I2C 0x%02x): unable to write config update exit after update.\n", gAddress >> 1); +#endif + } + } else { +#ifdef DEBUG_BQ27441 + printf("BatteryGaugeBq27441 (I2C 0x%02x): checksum on modified data block incorrect (0x%02x) during config update.\n", gAddress >> 1, data[1]); +#endif + } + } else { +#ifdef DEBUG_BQ27441 + printf("BatteryGaugeBq27441 (I2C 0x%02x): couldn't write all %d bytes during config update.\n", gAddress >> 1, length); +#endif + } + } else { +#ifdef DEBUG_BQ27441 + printf("BatteryGaugeBq27441 (I2C 0x%02x): checksum on read data block incorrect (expected 0x%02x, received 0x%02x).\n", gAddress >> 1, data[2], data[1]); +#endif + } + } else { +#ifdef DEBUG_BQ27441 + printf("BatteryGaugeBq27441 (I2C 0x%02x): unable to read Block Data Checksum for config update.\n", gAddress >> 1); +#endif + } + } else { +#ifdef DEBUG_BQ27441 + printf("BatteryGaugeBq27441 (I2C 0x%02x): unable to write Block Offset (%d), or possibly read the data block, for config update.\n", gAddress >> 1, data[1]); +#endif + } + } else { +#ifdef DEBUG_BQ27441 + printf("BatteryGaugeBq27441 (I2C 0x%02x): unable to set sub-class ID (0x%02x) for config update.\r", gAddress >> 1, subClassId); +#endif + } + } else { +#ifdef DEBUG_BQ27441 + printf("BatteryGaugeBq27441 (I2C 0x%02x): unable to set Block Data Control for config update.\n", gAddress >> 1); +#endif + } + } else { +#ifdef DEBUG_BQ27441 + printf("BatteryGaugeBq27441 (I2C 0x%02x): Flags register didn't show config update flag set in time (0x%04x).\n", gAddress >> 1, answer); +#endif + } + } else { +#ifdef DEBUG_BQ27441 + printf("BatteryGaugeBq27441 (I2C 0x%02x): unable to write to control register for config update.\n", gAddress >> 1); +#endif + } + } else { +#ifdef DEBUG_BQ27441 + printf("BatteryGaugeBq27441 (I2C 0x%02x): unable to unseal chip.\n", gAddress >> 1); +#endif + } + } else { +#ifdef DEBUG_BQ27441 + printf("BatteryGaugeBq27441 (I2C 0x%02x): offset (%d) is in different 32 byte block to offset + length (%d) [length is %d].\n", gAddress >> 1, offset, offset + length, length); +#endif + } + } + + // If the write succeeded the exit from config update state will + // re-seal things. If it failed, we need to re-seal the device + // manually if it was previously sealed. + if (!success && wasSealed) { + if (!seal()) { + success = false; + } + } + + return success; +} + + +/// Check if the chip is SEALED or UNSEALED. +// Note: gpI2c should be locked before this is called. +bool BatteryGaugeBq27441::isSealed(void) +{ + bool sealed = false; + char data[3]; + uint16_t controlStatus; + + // Send a control command to read the control status register + data[0] = 0x00; // Set address to first register for control + data[1] = 0x00; // First byte of CONTROL_STATUS sub-command (0x00) + data[2] = 0x00; // Second byte of CONTROL_STATUS sub-command (0x00) (register address will auto-increment) + + if (gpI2c->write(gAddress, &(data[0]), 3) == 0) { + if (getTwoBytes (0, &controlStatus)) { + // Bit 5 of the high byte is set to 1 if the device is sealed + if (controlStatus & (1 << 13)) { + sealed = true; +#ifdef DEBUG_BQ27441 + printf("BatteryGaugeBq27441 (I2C 0x%02x): is sealed (control status 0x%04x).\r\n", gAddress >> 1, controlStatus); + } else { + printf("BatteryGaugeBq27441 (I2C 0x%02x): is unsealed (control status 0x%04x).\r\n", gAddress >> 1, controlStatus); +#endif + } + } + } + + return sealed; +} + +/// Put the chip into SEALED mode. +// Note: gpI2c should be locked before this is called. +bool BatteryGaugeBq27441::seal(void) +{ + bool success = false; + char data[3]; + uint16_t controlStatus; + + // Send a SEALED sub-command + data[0] = 0x00; // Set address to first register for control + 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) + + if (gpI2c->write(gAddress, &(data[0]), 3) == 0) { + // Check for success by reading the control status register + data[0] = 0x00; // Set address to first register for control + data[1] = 0x00; // First byte of CONTROL_STATUS sub-command (0x00) + data[2] = 0x00; // Second byte of CONTROL_STATUS sub-command (0x00) (register address will auto-increment) + + if (gpI2c->write(gAddress, &(data[0]), 3) == 0) { + if (getTwoBytes (0, &controlStatus)) { + // Bit 5 of the high byte is set to 1 if the device is sealed + if (controlStatus & (1 << 13)) { + success = true; +#ifdef DEBUG_BQ27441 + printf("BatteryGaugeBq27441 (I2C 0x%02x): now sealed.\n", gAddress >> 1); + } else { + printf("BatteryGaugeBq27441 (I2C 0x%02x): seal failed (control status 0x%04x).\n", gAddress >> 1, controlStatus); +#endif + } + } + } + } + + return success; +} + +/// Send the seal code to the device to unseal it. +// Note: gpI2c should be locked before this is called. +bool BatteryGaugeBq27441::unseal(uint16_t sealCode) +{ + bool success = false; + char data[3]; + uint16_t controlStatus; + + // Send the unseal code to the control register + data[0] = 0x00; // Set address to first register for control + data[1] = (char) sealCode; // First byte of code + data[2] = (char) (sealCode >> 8); // Second byte of code (register address will auto-increment) + + + if ((gpI2c->write(gAddress, &(data[0]), 3) == 0) && + (gpI2c->write(gAddress, &(data[0]), 3) == 0)) { + // Check for success by reading the control status register + data[0] = 0x00; // Set address to first register for control + data[1] = 0x00; // First byte of CONTROL_STATUS sub-command (0x00) + data[2] = 0x00; // Second byte of CONTROL_STATUS sub-command (0x00) (register address will auto-increment) + + if (gpI2c->write(gAddress, &(data[0]), 3) == 0) { + if (getTwoBytes (0, &controlStatus)) { + // Bit 5 of the high byte is 0 if the device is unsealed + if ((controlStatus & (1 << 13)) == 0) { + success = true; +#ifdef DEBUG_BQ27441 + printf("BatteryGaugeBq27441 (I2C 0x%02x): now unsealed with seal code 0x%04x (control status 0x%04x).\n", gAddress >> 1, sealCode, controlStatus); + } else { + printf("BatteryGaugeBq27441 (I2C 0x%02x): unseal failed (with seal code 0x%04x, control status 0x%04x).\n", gAddress >> 1, sealCode, controlStatus); +#endif + } + } + } + } + + 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 BatteryGaugeBq27441::makeAdcReading(void) +{ + bool success = false; + char data[3]; + + // Send CLEAR_HIBERNATE + data[0] = 0x00; // Set address to first register for control + data[1] = 0x12; // First byte of CLEAR_HIBERNATE sub-command (0x12) + data[2] = 0x00; // Second byte of CLEAR_HIBERNATE sub-command (0x00) (register address will auto-increment) + + gpI2c->lock(); + success = (gpI2c->write(gAddress, &(data[0]), 3) == 0); + gpI2c->unlock(); + wait_ms (ADC_READ_WAIT_MS); + + return success; +} + +/// Set Hibernate mode. +// Note: gpI2c should be locked before this is called. +bool BatteryGaugeBq27441::setHibernate(void) +{ + char data[3]; + + // Send SET_HIBERNATE + data[0] = 0x00; // Set address to first register for control + data[1] = 0x11; // First byte of SET_HIBERNATE sub-command (0x11) + data[2] = 0x00; // Second byte of SET_HIBERNATE sub-command (0x00) (register address will auto-increment) + + return (gpI2c->write(gAddress, &(data[0]), 3) == 0); +} + +//---------------------------------------------------------------- +// PUBLIC FUNCTIONS +// ---------------------------------------------------------------- + +/// Constructor. +BatteryGaugeBq27441::BatteryGaugeBq27441(void) +{ + gpI2c = NULL; + gReady = false; + gGaugeOn = false; + gSealCode = 0; +} + +/// Destructor. +BatteryGaugeBq27441::~BatteryGaugeBq27441(void) +{ +} + +/// Initialise ourselves. +bool BatteryGaugeBq27441::init (I2C * pI2c, uint8_t address, uint16_t sealCode) +{ + uint16_t answer; + char data[4]; + + gpI2c = pI2c; + gAddress = address << 1; + gSealCode = sealCode; + + if (gpI2c != NULL) { + gpI2c->lock(); + + // Send a control command to read the firmware version + data[0] = 0x00; // Set address to first register for control + data[1] = 0x02; // First byte of FW_VERSION sub-command (0x02) + data[2] = 0x00; // Second byte of FW_VERSION sub-command (0x00) (register address will auto-increment) + + if (gpI2c->write(gAddress, &(data[0]), 3) == 0) { + if (getTwoBytes (0, &answer)) { + // The expected response is 0x0109 + if (((answer >> 8) == 0x01) && ((answer & 0xff) == 0x09)) { +#ifdef DEBUG_BQ27441 + if (readExtendedData(112, 0, 4, &(data[0]))) { + printf("BatteryGaugeBq27441 (I2C 0x%02x): seal code stored in chip is 0x%02x%02x%02x%02x.\n", gAddress >> 1, data[0], data[1], data[2], data[3]); + } +#endif + // Set the Sleep Current to the maximum value so that, if + // we tell the chip to go to sleep mode, it will do so + // straight away. Sleep Current is offset 31 in the State + // sub-class (82) and max value is 1000. Since offset 31 + // and a length of 2 crosses a 32 bytes boundary this needs + // two separate writes. + data[0] = (char) (1000 >> 8); + data[1] = (char) 1000; + + if (writeExtendedData(82, 31, 1, &(data[0])) && + writeExtendedData(82, 32, 1, &(data[1]))) { + // Now enter Hibernate mode to minimise power consumption + // Need to set either the Hibernate Current or Hibernate Voltage value + // to max, otherwise we won't hibernate, then set the SET_HIBERNATE + // bit. Here we set Hibernate V element (offset 9) in the Power + // sub-class (68) to its max value (see section 6.4.1.6.2 of the + // BQ27441 technical reference manual). + // Note: the cell must also be "relaxed" for this to occur and so + // the chip may still not enter Hibernate mode for a little while. + data[0] = (char) (5000 >> 8); + data[1] = (char) 5000; + + if (writeExtendedData(68, 9, 2, &(data[0]))) { + // Now send SET_HIBERNATE + gReady = setHibernate(); + } + } + } +#ifdef DEBUG_BQ27441 + printf("BatteryGaugeBq27441 (I2C 0x%02x): read 0x%04x as FW_VERSION, expected 0x0109.\n", gAddress >> 1, answer); +#endif + } + } + gpI2c->unlock(); + } + +#ifdef DEBUG_BQ27441 + if (gReady) { + printf("BatteryGaugeBq27441 (I2C 0x%02x): handler initialised.\r\n", gAddress >> 1); + } else { + printf("BatteryGaugeBq27441 (I2C 0x%02x): init NOT successful.\r\n", gAddress >> 1); + } +#endif + + return gReady; +} + +/// Switch on the battery capacity monitor. +bool BatteryGaugeBq27441::enableGauge (bool isSlow) +{ + bool success = false; + char data[3]; + + if (gReady && (gpI2c != NULL)) { + gpI2c->lock(); + // Make sure that we are not in hibernate + data[0] = 0x00; // Set address to first register for control + data[1] = 0x12; // First byte of CLEAR_HIBERNATE sub-command (0x12) + data[2] = 0x00; // Second byte of CLEAR_HIBERNATE sub-command (0x00) + if (gpI2c->write(gAddress, &(data[0]), 3) == 0) { + gGaugeOn = true; + // Read the OpConfig register which is in the Registers sub-class + // (64) at offset 0. + if (readExtendedData(64, 0, 2, &(data[0]))) { +#ifdef DEBUG_BQ27441 + printf("BatteryGaugeBq27441 (I2C 0x%02x): OpConfig is 0x%02x%02x.\r\n", gAddress >> 1, data[0], data[1]); +#endif + // SLEEP mode is bit 5 of the low byte of OpConfig. In SLEEP + // mode a reading is taken every 20 seconds. + if (isSlow && ((data[1] & (1 << 5)) == 0)) { + // Set the SLEEP bit 'cos it's not set and needs to be + data[1] |= 1 << 5; + // Write the new value back + success = writeExtendedData(64, 0, 2, &(data[0])); +#ifdef DEBUG_BQ27441 + if (success) { + printf("BatteryGaugeBq27441 (I2C 0x%02x): OpConfig becomes 0x%02x%02x.\r\n", gAddress >> 1, data[0], data[1]); + } +#endif + } else if (!isSlow && ((data[1] & (1 << 5)) != 0)) { + // Clear the SLEEP bit 'cos it's set and shouldn't be + data[1] &= ~(1 << 5); + // Write the new value back + success = writeExtendedData(64, 0, 2, &(data[0])); +#ifdef DEBUG_BQ27441 + if (success) { + printf("BatteryGaugeBq27441 (I2C 0x%02x): OpConfig becomes 0x%02x%02x.\r\n", gAddress >> 1, data[0], data[1]); + } +#endif + } else { + success = true; + } + } + } + + gpI2c->unlock(); + } + + return success; +} + +/// Switch off the battery capacity monitor. +bool BatteryGaugeBq27441::disableGauge (void) +{ + bool success = false; + + if (gReady && (gpI2c != NULL)) { + gpI2c->lock(); + // Send SET_HIBERNATE + if (setHibernate()) { + success = true; + gGaugeOn = false; + } + + gpI2c->unlock(); + } + + return success; +} + +/// Check whether a battery has been detected or not. +bool BatteryGaugeBq27441::isBatteryDetected (void) +{ + bool isDetected = false; + uint16_t data; + + if (gReady && (gpI2c != NULL)) { + + // Make sure there's a recent reading + if (gGaugeOn || makeAdcReading()) { + gpI2c->lock(); + // Read from the flags register address + if (getTwoBytes (0x06, &data)) { + + // If bit 3 is set then a battery has been detected + if (data & (1 << 3)) { + isDetected = true; + } + +#ifdef DEBUG_BQ27441 + if (isDetected) { + printf("BatteryGaugeBq27441 (I2C 0x%02x): battery detected (flags 0x%04x).\n", gAddress >> 1, data); + } else { + printf("BatteryGaugeBq27441 (I2C 0x%02x): battery NOT detected (flags 0x%04x).\n", gAddress >> 1, data); + } +#endif + } + + // Set hibernate again if we are not monitoring + if (!gGaugeOn) { + setHibernate(); + } + + gpI2c->unlock(); + } + } + + return isDetected; +} + +/// Get the temperature of the chip. +bool BatteryGaugeBq27441::getTemperature (int32_t *pTemperatureC) +{ + bool success = false; + int32_t temperatureC = 0; + uint16_t data; + + if (gReady && (gpI2c != NULL)) { + // Make sure there's a recent reading + if (gGaugeOn || makeAdcReading()) { + gpI2c->lock(); + // Read from the temperature register address + if (getTwoBytes (0x02, &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_BQ27441 + printf("BatteryGaugeBq27441 (I2C 0x%02x): chip temperature %.1f K, so %d C.\n", gAddress >> 1, ((float) data) / 10, temperatureC); +#endif + } + + // Return to sleep if we are allowed to + if (!gGaugeOn && !setHibernate()) { + success = false; + } + + gpI2c->unlock(); + } + } + + return success; +} + +/// Get the voltage of the battery. +bool BatteryGaugeBq27441::getVoltage (int32_t *pVoltageMV) +{ + bool success = false; + uint16_t data = 0; + + if (gReady && (gpI2c != NULL)) { + // Make sure there's a recent reading + if (gGaugeOn || makeAdcReading()) { + gpI2c->lock(); + // Read from the voltage register address + if (getTwoBytes (0x04, &data)) { + success = true; + + // The answer is in mV + if (pVoltageMV) { + *pVoltageMV = (int32_t) data; + } + +#ifdef DEBUG_BQ27441 + printf("BatteryGaugeBq27441 (I2C 0x%02x): battery voltage %.3f V.\n", gAddress >> 1, ((float) data) / 1000); +#endif + } + + // Return to sleep if we are allowed to + if (!gGaugeOn && !setHibernate()) { + success = false; + } + + gpI2c->unlock(); + } + } + + return success; +} + +/// Get the current flowing from the battery. +bool BatteryGaugeBq27441::getCurrent (int32_t *pCurrentMA) +{ + bool success = false; + int32_t currentMA = 0; + uint16_t data; + + if (gReady && (gpI2c != NULL)) { + // Make sure there's a recent reading + if (gGaugeOn || makeAdcReading()) { + gpI2c->lock(); + // Read from the average current register address + if (getTwoBytes (0x10, &data)) { + success = true; + + if (pCurrentMA) { + *pCurrentMA = currentMA; + } + +#ifdef DEBUG_BQ27441 + printf("BatteryGaugeBq27441 (I2C 0x%02x): current %d mA.\n", gAddress >> 1, currentMA); +#endif + } + + // Return to sleep if we are allowed to + if (!gGaugeOn && !setHibernate()) { + success = false; + } + + gpI2c->unlock(); + } + } + + return success; +} + +/// Get the remaining battery capacity. +bool BatteryGaugeBq27441::getRemainingCapacity (int32_t *pCapacityMAh) +{ + bool success = false; + uint16_t data = 0; + + if (gReady && (gpI2c != NULL)) { + // Make sure there's a recent reading + if (gGaugeOn || makeAdcReading()) { + gpI2c->lock(); + // Read from the RemainingCapacity register address + + if (getTwoBytes (0x0c, &data)) { + success = true; + + // The answer is in mAh + if (pCapacityMAh) { + *pCapacityMAh = (int32_t) data; + } + +#ifdef DEBUG_BQ27441 + printf("BatteryGaugeBq27441 (I2C 0x%02x): remaining battery capacity %u mAh.\n", gAddress >> 1, data); +#endif + } + + // Return to sleep if we are allowed to + if (!gGaugeOn && !setHibernate()) { + success = false; + } + + gpI2c->unlock(); + } + } + + return success; +} + +/// Get the battery percentage remaining +bool BatteryGaugeBq27441::getRemainingPercentage (int32_t *pBatteryPercent) +{ + bool success = false; + uint16_t data = 0; + + if (gReady && (gpI2c != NULL)) { + // Make sure there's a recent reading + if (gGaugeOn || makeAdcReading()) { + gpI2c->lock(); + + // Wake up and take a reading if we have to + if (!gGaugeOn && !setHibernate()) { + success = false; + } + + // Read from the StateOfCharge register address + if (getTwoBytes (0x1c, &data)) { + success = true; + + if (pBatteryPercent) { + *pBatteryPercent = (int32_t) data; + } + +#ifdef DEBUG_BQ27441 + printf("BatteryGaugeBq27441 (I2C 0x%02x): remaining battery percentage %u%%.\n", gAddress >> 1, data); +#endif + } + + // Return to sleep if we are allowed to + if (!gGaugeOn && !setHibernate()) { + success = false; + } + + gpI2c->unlock(); + } + } + + return success; +} + +/// Advanced function to read a configuration data block. +bool BatteryGaugeBq27441::advancedGetConfig(uint8_t subClassId, int32_t offset, int32_t length, char * pData) +{ + bool success = false; + + if (gReady && (gpI2c != NULL) && (pData != NULL)) { + gpI2c->lock(); + // Read the extended configuration data + success = readExtendedData(subClassId, offset, length, pData); +#ifdef DEBUG_BQ27441 + if (success) { + printf("BatteryGaugeBq27441 (I2C 0x%02x): read extended data with subClassId %d from offset %d.\n", gAddress >> 1, subClassId, offset); + } +#endif + + // Return to sleep if we are allowed to + if (!gGaugeOn && !setHibernate()) { + success = false; + } + + gpI2c->unlock(); + } + + return success; +} + +/// Advanced function to write a configuration data block. +bool BatteryGaugeBq27441::advancedSetConfig(uint8_t subClassId, int32_t offset, int32_t length, const char * pData) +{ + bool success = false; + + if (gReady && (gpI2c != NULL) && (pData != NULL)) { + gpI2c->lock(); + // Write the extended configuration data + success = writeExtendedData(subClassId, offset, length, pData); +#ifdef DEBUG_BQ27441 + if (success) { + printf("BatteryGaugeBq27441 (I2C 0x%02x): written %d byte(s) of extended data with subClassId %d from offset %d.\n", gAddress >> 1, length, subClassId, offset); + } +#endif + + // Return to sleep if we are allowed to + if (!gGaugeOn && !setHibernate()) { + success = false; + } + + gpI2c->unlock(); + } + + return success; +} + +/// Send a control word. +bool BatteryGaugeBq27441::advancedSendControlWord (uint16_t controlWord, uint16_t *pDataReturned) +{ + bool success = false; + char data[3]; + + if (gReady && (gpI2c != NULL)) { + gpI2c->lock(); + // Send the control command + data[0] = 0x00; // Set address to first register for control + 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(0, pDataReturned)) { + success = true; +#ifdef DEBUG_BQ27441 + printf("BatteryGaugeBq27441 (I2C 0x%02x): sent control word 0x%04x, read back 0x%04x.\n", gAddress >> 1, controlWord, *pDataReturned); +#endif + } + } else { + success = true; +#ifdef DEBUG_BQ27441 + printf("BatteryGaugeBq27441 (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 BatteryGaugeBq27441::advancedGet (uint8_t address, uint16_t *pDataReturned) +{ + bool success = false; + uint16_t value; + + if (gReady && (gpI2c != NULL)) { + // Make sure there's a recent reading, as most + // of these commands involve the chip having done one + if (gGaugeOn || makeAdcReading()) { + gpI2c->lock(); + // Read the data + if (getTwoBytes(address, &value)) { + success = true; +#ifdef DEBUG_BQ27441 + printf("BatteryGaugeBq27441 (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; +} + +/// Check if the chip is SEALED or UNSEALED. +bool BatteryGaugeBq27441::advancedIsSealed() +{ + bool sealed = false; + + if (gReady && (gpI2c != NULL)) { + gpI2c->lock(); + // Check for sealedness + sealed = isSealed(); + + // Return to sleep if we are allowed to + if (!gGaugeOn) { + setHibernate(); + } + + gpI2c->unlock(); + } + + return sealed; +} + +/// Put the chip into SEALED mode. +bool BatteryGaugeBq27441::advancedSeal() +{ + bool success = false; + + if (gReady && (gpI2c != NULL)) { + gpI2c->lock(); + // Seal + success = seal(); + + // Return to sleep if we are allowed to + if (!gGaugeOn && !setHibernate()) { + success = false; + } + + gpI2c->unlock(); + } + + return success; +} + +/// Unseal the device. +bool BatteryGaugeBq27441::advancedUnseal(uint16_t sealCode) +{ + bool success = false; + char updateStatus; + + if (gReady && (gpI2c != NULL)) { + gpI2c->lock(); + // Unseal and read the Update Status value + if (unseal(sealCode) && readExtendedData(82, 2, 1, &updateStatus)) { + // If the update status value has the top bit set then the chip will + // reseal itself on the next reset, so this bit needs to be cleared to + // unseal it properly + if ((updateStatus & (1 << 7)) != 0) { + updateStatus &= ~(1 << 7); + success = writeExtendedData(82, 2, 1, &updateStatus); + } else { + success = true; + } + } + + // Return to sleep if we are allowed to + if (!gGaugeOn && !setHibernate()) { + success = false; + } + + gpI2c->unlock(); + } + + return success; +} + +/// Do a hard reset of the chip. +bool BatteryGaugeBq27441::advancedReset(void) +{ + bool success = false; + bool wasSealed; + char data[3]; + + if (gReady && (gpI2c != NULL)) { + gpI2c->lock(); + + // Handle unsealing, as this command only works when unsealed + wasSealed = isSealed(); + if (!wasSealed || unseal(gSealCode)) { + // Send a RESET sub-command + data[0] = 0x00; // Set address to first register for control + 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_BQ27441 + printf("BatteryGaugeBq27441 (I2C 0x%02x): chip hard reset.\n", gAddress >> 1); +#endif + } + + // Handle re-sealing and fail if we need to re-seal but can't + if (wasSealed && !seal()) { + success = false; + } + } + + // Return to sleep if we are allowed to + if (!gGaugeOn && !setHibernate()) { + success = false; + } + + gpI2c->unlock(); + } + + return success; +} + +// End Of File