Library to interface to the TI BQ27441, a fuel gauge monitor
Fork of battery-gauge-bq27441 by
bq27441.cpp
- Committer:
- breadboardbasics
- Date:
- 2017-12-13
- Revision:
- 6:998cc334f8f2
- Parent:
- 5:63b325f2c21a
File content as of revision 6:998cc334f8f2:
/* 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; } bool BatteryGaugeBq27441::getTwoBytesSigned (uint8_t registerAddress, int16_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 = (((int16_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, (int) length, (int) 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, (int) 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, (int) offset, (int) (offset + length), (int) 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, (int) length, (int) 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, (int) 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, (int) offset, (int) (offset + length), (int) 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; } // Determine whether battery gauging is enabled. bool BatteryGaugeBq27441::isGaugeEnabled(void) { return gGaugeOn; } // Disable the battery detect pin. bool BatteryGaugeBq27441::disableBatteryDetect (void) { bool success = false; char data[3]; if (gReady && (gpI2c != NULL)) { gpI2c->lock(); // 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 // Battery Insertion Enabled is bit 5 of the high byte of OpConfig. // 1 means that the battery input pin is enabled if (((data[0] & (1 << 5)) != 0)) { // Clear the BIE bit 'cos it's set and is shouldn't be data[0] &= ~(1 << 5); // Write the new value back if (writeExtendedData(64, 0, 2, &(data[0]))) { // Send the Battery Inserted message as we can't do gauging otherwise data[0] = 0x00; // Set address to first register for control data[1] = 0x0C; // First byte of BAT_INSERT sub-command (0x0C) data[2] = 0x00; // Second byte of BAT_INSERT sub-command (0x00) success = gpI2c->write(gAddress, &(data[0]), 3) == 0; #ifdef DEBUG_BQ27441 if (success) { printf("BatteryGaugeBq27441 (I2C 0x%02x): BIE disabled, BAT_INSERT sent, OpConfig becomes 0x%02x%02x.\r\n", gAddress >> 1, data[0], data[1]); } #endif } } else { success = true; } } // Set hibernate again if we are not monitoring if (!gGaugeOn) { setHibernate(); } gpI2c->unlock(); } return success; } // Enable the battery detect pin. bool BatteryGaugeBq27441::enableBatteryDetect (void) { bool success = false; char data[3]; if (gReady && (gpI2c != NULL)) { gpI2c->lock(); // 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 // Battery Insertion Enabled is bit 5 of the high byte of OpConfig. // 1 means that the battery input pin is enabled if (((data[0] & (1 << 5)) == 0)) { // Set the BIE bit 'cos it's not set and needs to be data[0] |= 1 << 5; // Write the new value back success = writeExtendedData(64, 0, 2, &(data[0])); #ifdef DEBUG_BQ27441 if (success) { printf("BatteryGaugeBq27441 (I2C 0x%02x): BIE enabled, OpConfig becomes 0x%02x%02x.\r\n", gAddress >> 1, data[0], data[1]); } #endif } else { success = true; } } // Set hibernate again if we are not monitoring if (!gGaugeOn) { setHibernate(); } 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, (int) 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 power output of the battery bool BatteryGaugeBq27441::getPower (int32_t *pPowerMW) { bool success = false; int16_t data = 0; if (gReady && (gpI2c != NULL)) { // Make sure there's a recent reading if (gGaugeOn || makeAdcReading()) { gpI2c->lock(); // Read from the power register address if (getTwoBytesSigned (0x18, &data)) { success = true; // The answer is in mW if (pPowerMW) { *pPowerMW = (int32_t) data; } } // 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; int16_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 (getTwoBytesSigned (0x10, &data)) { success = true; if (pCurrentMA) { *pCurrentMA = (int32_t) data; } } // 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, (int) 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, (int) length, subClassId, (int) 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 */