diff --git a/lib/bq27441_sparkfun/BQ27441_Definitions.h b/lib/bq27441_sparkfun/BQ27441_Definitions.h new file mode 100644 index 000000000..f64d1a28f --- /dev/null +++ b/lib/bq27441_sparkfun/BQ27441_Definitions.h @@ -0,0 +1,172 @@ +/****************************************************************************** +BQ27441_Definitions.h +BQ27441 LiPo Fuel Gauge Definitions +Jim Lindblom @ SparkFun Electronics +May 9, 2016 +https://github.com/sparkfun/SparkFun_BQ27441_Arduino_Library + +BQ27441 hardware constants, register addresses, and bit positions. + +Hardware Resources: +- Arduino Development Board +- SparkFun Battery Babysitter + +Development environment specifics: +Arduino 1.6.7 +SparkFun Battery Babysitter v1.0 +Arduino Uno (any 'duino should do) +******************************************************************************/ +#define BQ72441_I2C_ADDRESS 0x55 // Default I2C address of the BQ27441-G1A + +/////////////////////// +// General Constants // +/////////////////////// +#define BQ27441_UNSEAL_KEY 0x8000 // Secret code to unseal the BQ27441-G1A +#define BQ27441_DEVICE_ID 0x0421 // Default device ID + +/////////////////////// +// Standard Commands // +/////////////////////// +// The fuel gauge uses a series of 2-byte standard commands to enable system +// reading and writing of battery information. Each command has an associated +// sequential command-code pair. +#define BQ27441_COMMAND_CONTROL 0x00 // Control() +#define BQ27441_COMMAND_TEMP 0x02 // Temperature() +#define BQ27441_COMMAND_VOLTAGE 0x04 // Voltage() +#define BQ27441_COMMAND_FLAGS 0x06 // Flags() +#define BQ27441_COMMAND_NOM_CAPACITY 0x08 // NominalAvailableCapacity() +#define BQ27441_COMMAND_AVAIL_CAPACITY 0x0A // FullAvailableCapacity() +#define BQ27441_COMMAND_REM_CAPACITY 0x0C // RemainingCapacity() +#define BQ27441_COMMAND_FULL_CAPACITY 0x0E // FullChargeCapacity() +#define BQ27441_COMMAND_AVG_CURRENT 0x10 // AverageCurrent() +#define BQ27441_COMMAND_STDBY_CURRENT 0x12 // StandbyCurrent() +#define BQ27441_COMMAND_MAX_CURRENT 0x14 // MaxLoadCurrent() +#define BQ27441_COMMAND_AVG_POWER 0x18 // AveragePower() +#define BQ27441_COMMAND_SOC 0x1C // StateOfCharge() +#define BQ27441_COMMAND_INT_TEMP 0x1E // InternalTemperature() +#define BQ27441_COMMAND_SOH 0x20 // StateOfHealth() +#define BQ27441_COMMAND_REM_CAP_UNFL 0x28 // RemainingCapacityUnfiltered() +#define BQ27441_COMMAND_REM_CAP_FIL 0x2A // RemainingCapacityFiltered() +#define BQ27441_COMMAND_FULL_CAP_UNFL 0x2C // FullChargeCapacityUnfiltered() +#define BQ27441_COMMAND_FULL_CAP_FIL 0x2E // FullChargeCapacityFiltered() +#define BQ27441_COMMAND_SOC_UNFL 0x30 // StateOfChargeUnfiltered() + +////////////////////////// +// Control Sub-commands // +////////////////////////// +// Issuing a Control() command requires a subsequent 2-byte subcommand. These +// additional bytes specify the particular control function desired. The +// Control() command allows the system to control specific features of the fuel +// gauge during normal operation and additional features when the device is in +// different access modes. +#define BQ27441_CONTROL_STATUS 0x00 +#define BQ27441_CONTROL_DEVICE_TYPE 0x01 +#define BQ27441_CONTROL_FW_VERSION 0x02 +#define BQ27441_CONTROL_DM_CODE 0x04 +#define BQ27441_CONTROL_PREV_MACWRITE 0x07 +#define BQ27441_CONTROL_CHEM_ID 0x08 +#define BQ27441_CONTROL_BAT_INSERT 0x0C +#define BQ27441_CONTROL_BAT_REMOVE 0x0D +#define BQ27441_CONTROL_SET_HIBERNATE 0x11 +#define BQ27441_CONTROL_CLEAR_HIBERNATE 0x12 +#define BQ27441_CONTROL_SET_CFGUPDATE 0x13 +#define BQ27441_CONTROL_SHUTDOWN_ENABLE 0x1B +#define BQ27441_CONTROL_SHUTDOWN 0x1C +#define BQ27441_CONTROL_SEALED 0x20 +#define BQ27441_CONTROL_PULSE_SOC_INT 0x23 +#define BQ27441_CONTROL_RESET 0x41 +#define BQ27441_CONTROL_SOFT_RESET 0x42 +#define BQ27441_CONTROL_EXIT_CFGUPDATE 0x43 +#define BQ27441_CONTROL_EXIT_RESIM 0x44 + +/////////////////////////////////////////// +// Control Status Word - Bit Definitions // +/////////////////////////////////////////// +// Bit positions for the 16-bit data of CONTROL_STATUS. +// CONTROL_STATUS instructs the fuel gauge to return status information to +// Control() addresses 0x00 and 0x01. The read-only status word contains status +// bits that are set or cleared either automatically as conditions warrant or +// through using specified subcommands. +#define BQ27441_STATUS_SHUTDOWNEN (1<<15) +#define BQ27441_STATUS_WDRESET (1<<14) +#define BQ27441_STATUS_SS (1<<13) +#define BQ27441_STATUS_CALMODE (1<<12) +#define BQ27441_STATUS_CCA (1<<11) +#define BQ27441_STATUS_BCA (1<<10) +#define BQ27441_STATUS_QMAX_UP (1<<9) +#define BQ27441_STATUS_RES_UP (1<<8) +#define BQ27441_STATUS_INITCOMP (1<<7) +#define BQ27441_STATUS_HIBERNATE (1<<6) +#define BQ27441_STATUS_SLEEP (1<<4) +#define BQ27441_STATUS_LDMD (1<<3) +#define BQ27441_STATUS_RUP_DIS (1<<2) +#define BQ27441_STATUS_VOK (1<<1) + +//////////////////////////////////// +// Flag Command - Bit Definitions // +//////////////////////////////////// +// Bit positions for the 16-bit data of Flags() +// This read-word function returns the contents of the fuel gauging status +// register, depicting the current operating status. +#define BQ27441_FLAG_OT (1<<15) +#define BQ27441_FLAG_UT (1<<14) +#define BQ27441_FLAG_FC (1<<9) +#define BQ27441_FLAG_CHG (1<<8) +#define BQ27441_FLAG_OCVTAKEN (1<<7) +#define BQ27441_FLAG_ITPOR (1<<5) +#define BQ27441_FLAG_CFGUPMODE (1<<4) +#define BQ27441_FLAG_BAT_DET (1<<3) +#define BQ27441_FLAG_SOC1 (1<<2) +#define BQ27441_FLAG_SOCF (1<<1) +#define BQ27441_FLAG_DSG (1<<0) + +//////////////////////////// +// Extended Data Commands // +//////////////////////////// +// Extended data commands offer additional functionality beyond the standard +// set of commands. They are used in the same manner; however, unlike standard +// commands, extended commands are not limited to 2-byte words. +#define BQ27441_EXTENDED_OPCONFIG 0x3A // OpConfig() +#define BQ27441_EXTENDED_CAPACITY 0x3C // DesignCapacity() +#define BQ27441_EXTENDED_DATACLASS 0x3E // DataClass() +#define BQ27441_EXTENDED_DATABLOCK 0x3F // DataBlock() +#define BQ27441_EXTENDED_BLOCKDATA 0x40 // BlockData() +#define BQ27441_EXTENDED_CHECKSUM 0x60 // BlockDataCheckSum() +#define BQ27441_EXTENDED_CONTROL 0x61 // BlockDataControl() + +//////////////////////////////////////// +// Configuration Class, Subclass ID's // +//////////////////////////////////////// +// To access a subclass of the extended data, set the DataClass() function +// with one of these values. +// Configuration Classes +#define BQ27441_ID_SAFETY 2 // Safety +#define BQ27441_ID_CHG_TERMINATION 36 // Charge Termination +#define BQ27441_ID_CONFIG_DATA 48 // Data +#define BQ27441_ID_DISCHARGE 49 // Discharge +#define BQ27441_ID_REGISTERS 64 // Registers +#define BQ27441_ID_POWER 68 // Power +// Gas Gauging Classes +#define BQ27441_ID_IT_CFG 80 // IT Cfg +#define BQ27441_ID_CURRENT_THRESH 81 // Current Thresholds +#define BQ27441_ID_STATE 82 // State +// Ra Tables Classes +#define BQ27441_ID_R_A_RAM 89 // R_a RAM +// Calibration Classes +#define BQ27441_ID_CALIB_DATA 104 // Data +#define BQ27441_ID_CC_CAL 105 // CC Cal +#define BQ27441_ID_CURRENT 107 // Current +// Security Classes +#define BQ27441_ID_CODES 112 // Codes + +///////////////////////////////////////// +// OpConfig Register - Bit Definitions // +///////////////////////////////////////// +// Bit positions of the OpConfig Register +#define BQ27441_OPCONFIG_BIE (1<<13) +#define BQ27441_OPCONFIG_BI_PU_EN (1<<12) +#define BQ27441_OPCONFIG_GPIOPOL (1<<11) +#define BQ27441_OPCONFIG_SLEEP (1<<5) +#define BQ27441_OPCONFIG_RMFCC (1<<4) +#define BQ27441_OPCONFIG_BATLOWEN (1<<2) +#define BQ27441_OPCONFIG_TEMPS (1<<0) \ No newline at end of file diff --git a/lib/bq27441_sparkfun/SparkFunBQ27441.cpp b/lib/bq27441_sparkfun/SparkFunBQ27441.cpp new file mode 100644 index 000000000..5c99b8d86 --- /dev/null +++ b/lib/bq27441_sparkfun/SparkFunBQ27441.cpp @@ -0,0 +1,729 @@ +/****************************************************************************** +SparkFunBQ27441.cpp +BQ27441 Arduino Library Main Source File +Jim Lindblom @ SparkFun Electronics +May 9, 2016 +https://github.com/sparkfun/SparkFun_BQ27441_Arduino_Library + +Implementation of all features of the BQ27441 LiPo Fuel Gauge. + +Hardware Resources: +- Arduino Development Board +- SparkFun Battery Babysitter + +Development environment specifics: +Arduino 1.6.7 +SparkFun Battery Babysitter v1.0 +Arduino Uno (any 'duino should do) +******************************************************************************/ + +#include "Arduino.h" +#include +#include "SparkFunBQ27441.h" +#include "BQ27441_Definitions.h" + +/***************************************************************************** + ************************** Initialization Functions ************************* + *****************************************************************************/ +// Initializes class variables +BQ27441::BQ27441() : _deviceAddress(BQ72441_I2C_ADDRESS), _sealFlag(false), _userConfigControl(false) +{ +} + +// Initializes I2C and verifies communication with the BQ27441. +bool BQ27441::begin(void) +{ + uint16_t deviceID = 0; + + //Wire.begin(); // Initialize I2C master + + deviceID = deviceType(); // Read deviceType from BQ27441 + + if (deviceID == BQ27441_DEVICE_ID) + { + return true; // If device ID is valid, return true + } + + return false; // Otherwise return false +} + +// Configures the design capacity of the connected battery. +bool BQ27441::setCapacity(uint16_t capacity) +{ + // Write to STATE subclass (82) of BQ27441 extended memory. + // Offset 0x0A (10) + // Design capacity is a 2-byte piece of data - MSB first + // Unit: mAh + uint8_t capMSB = capacity >> 8; + uint8_t capLSB = capacity & 0x00FF; + uint8_t capacityData[2] = {capMSB, capLSB}; + return writeExtendedData(BQ27441_ID_STATE, 10, capacityData, 2); +} + +// Configures the design energy of the connected battery. +bool BQ27441::setDesignEnergy(uint16_t energy) +{ + // Write to STATE subclass (82) of BQ27441 extended memory. + // Offset 0x0C (12) + // Design energy is a 2-byte piece of data - MSB first + // Unit: mWh + uint8_t enMSB = energy >> 8; + uint8_t enLSB = energy & 0x00FF; + uint8_t energyData[2] = {enMSB, enLSB}; + return writeExtendedData(BQ27441_ID_STATE, 12, energyData, 2); +} + +// Configures the terminate voltage. +bool BQ27441::setTerminateVoltage(uint16_t voltage) +{ + // Write to STATE subclass (82) of BQ27441 extended memory. + // Offset 0x0F (16) + // Termiante voltage is a 2-byte piece of data - MSB first + // Unit: mV + // Min 2500, Max 3700 + if(voltage<2500) voltage=2500; + if(voltage>3700) voltage=3700; + + uint8_t tvMSB = voltage >> 8; + uint8_t tvLSB = voltage & 0x00FF; + uint8_t tvData[2] = {tvMSB, tvLSB}; + return writeExtendedData(BQ27441_ID_STATE, 16, tvData, 2); +} + +// Configures taper rate of connected battery. +bool BQ27441::setTaperRate(uint16_t rate) +{ + // Write to STATE subclass (82) of BQ27441 extended memory. + // Offset 0x1B (27) + // Termiante voltage is a 2-byte piece of data - MSB first + // Unit: 0.1h + // Max 2000 + if(rate>2000) rate=2000; + uint8_t trMSB = rate >> 8; + uint8_t trLSB = rate & 0x00FF; + uint8_t trData[2] = {trMSB, trLSB}; + return writeExtendedData(BQ27441_ID_STATE, 27, trData, 2); +} + +/***************************************************************************** + ********************** Battery Characteristics Functions ******************** + *****************************************************************************/ + +// Reads and returns the battery voltage +uint16_t BQ27441::voltage(void) +{ + return readWord(BQ27441_COMMAND_VOLTAGE); +} + +// Reads and returns the specified current measurement +int16_t BQ27441::current(current_measure type) +{ + int16_t current = 0; + switch (type) + { + case AVG: + current = (int16_t) readWord(BQ27441_COMMAND_AVG_CURRENT); + break; + case STBY: + current = (int16_t) readWord(BQ27441_COMMAND_STDBY_CURRENT); + break; + case MAX: + current = (int16_t) readWord(BQ27441_COMMAND_MAX_CURRENT); + break; + } + + return current; +} + +// Reads and returns the specified capacity measurement +uint16_t BQ27441::capacity(capacity_measure type) +{ + uint16_t capacity = 0; + switch (type) + { + case REMAIN: + return readWord(BQ27441_COMMAND_REM_CAPACITY); + break; + case FULL: + return readWord(BQ27441_COMMAND_FULL_CAPACITY); + break; + case AVAIL: + capacity = readWord(BQ27441_COMMAND_NOM_CAPACITY); + break; + case AVAIL_FULL: + capacity = readWord(BQ27441_COMMAND_AVAIL_CAPACITY); + break; + case REMAIN_F: + capacity = readWord(BQ27441_COMMAND_REM_CAP_FIL); + break; + case REMAIN_UF: + capacity = readWord(BQ27441_COMMAND_REM_CAP_UNFL); + break; + case FULL_F: + capacity = readWord(BQ27441_COMMAND_FULL_CAP_FIL); + break; + case FULL_UF: + capacity = readWord(BQ27441_COMMAND_FULL_CAP_UNFL); + break; + case DESIGN: + capacity = readWord(BQ27441_EXTENDED_CAPACITY); + } + + return capacity; +} + +// Reads and returns measured average power +int16_t BQ27441::power(void) +{ + return (int16_t) readWord(BQ27441_COMMAND_AVG_POWER); +} + +// Reads and returns specified state of charge measurement +uint16_t BQ27441::soc(soc_measure type) +{ + uint16_t socRet = 0; + switch (type) + { + case FILTERED: + socRet = readWord(BQ27441_COMMAND_SOC); + break; + case UNFILTERED: + socRet = readWord(BQ27441_COMMAND_SOC_UNFL); + break; + } + + return socRet; +} + +// Reads and returns specified state of health measurement +uint8_t BQ27441::soh(soh_measure type) +{ + uint16_t sohRaw = readWord(BQ27441_COMMAND_SOH); + uint8_t sohStatus = sohRaw >> 8; + uint8_t sohPercent = sohRaw & 0x00FF; + + if (type == PERCENT) + return sohPercent; + else + return sohStatus; +} + +// Reads and returns specified temperature measurement +uint16_t BQ27441::temperature(temp_measure type) +{ + uint16_t temp = 0; + switch (type) + { + case BATTERY: + temp = readWord(BQ27441_COMMAND_TEMP); + break; + case INTERNAL_TEMP: + temp = readWord(BQ27441_COMMAND_INT_TEMP); + break; + } + return temp; +} + +/***************************************************************************** + ************************** GPOUT Control Functions ************************** + *****************************************************************************/ +// Get GPOUT polarity setting (active-high or active-low) +bool BQ27441::GPOUTPolarity(void) +{ + uint16_t opConfigRegister = opConfig(); + + return (opConfigRegister & BQ27441_OPCONFIG_GPIOPOL); +} + +// Set GPOUT polarity to active-high or active-low +bool BQ27441::setGPOUTPolarity(bool activeHigh) +{ + uint16_t oldOpConfig = opConfig(); + + // Check to see if we need to update opConfig: + if ((activeHigh && (oldOpConfig & BQ27441_OPCONFIG_GPIOPOL)) || + (!activeHigh && !(oldOpConfig & BQ27441_OPCONFIG_GPIOPOL))) + return true; + + uint16_t newOpConfig = oldOpConfig; + if (activeHigh) + newOpConfig |= BQ27441_OPCONFIG_GPIOPOL; + else + newOpConfig &= ~(BQ27441_OPCONFIG_GPIOPOL); + + return writeOpConfig(newOpConfig); +} + +// Get GPOUT function (BAT_LOW or SOC_INT) +bool BQ27441::GPOUTFunction(void) +{ + uint16_t opConfigRegister = opConfig(); + + return (opConfigRegister & BQ27441_OPCONFIG_BATLOWEN); +} + +// Set GPOUT function to BAT_LOW or SOC_INT +bool BQ27441::setGPOUTFunction(gpout_function function) +{ + uint16_t oldOpConfig = opConfig(); + + // Check to see if we need to update opConfig: + if ((function && (oldOpConfig & BQ27441_OPCONFIG_BATLOWEN)) || + (!function && !(oldOpConfig & BQ27441_OPCONFIG_BATLOWEN))) + return true; + + // Modify BATLOWN_EN bit of opConfig: + uint16_t newOpConfig = oldOpConfig; + if (function) + newOpConfig |= BQ27441_OPCONFIG_BATLOWEN; + else + newOpConfig &= ~(BQ27441_OPCONFIG_BATLOWEN); + + // Write new opConfig + return writeOpConfig(newOpConfig); +} + +// Get SOC1_Set Threshold - threshold to set the alert flag +uint8_t BQ27441::SOC1SetThreshold(void) +{ + return readExtendedData(BQ27441_ID_DISCHARGE, 0); +} + +// Get SOC1_Clear Threshold - threshold to clear the alert flag +uint8_t BQ27441::SOC1ClearThreshold(void) +{ + return readExtendedData(BQ27441_ID_DISCHARGE, 1); +} + +// Set the SOC1 set and clear thresholds to a percentage +bool BQ27441::setSOC1Thresholds(uint8_t set, uint8_t clear) +{ + uint8_t thresholds[2]; + thresholds[0] = constrain(set, 0, 100); + thresholds[1] = constrain(clear, 0, 100); + return writeExtendedData(BQ27441_ID_DISCHARGE, 0, thresholds, 2); +} + +// Get SOCF_Set Threshold - threshold to set the alert flag +uint8_t BQ27441::SOCFSetThreshold(void) +{ + return readExtendedData(BQ27441_ID_DISCHARGE, 2); +} + +// Get SOCF_Clear Threshold - threshold to clear the alert flag +uint8_t BQ27441::SOCFClearThreshold(void) +{ + return readExtendedData(BQ27441_ID_DISCHARGE, 3); +} + +// Set the SOCF set and clear thresholds to a percentage +bool BQ27441::setSOCFThresholds(uint8_t set, uint8_t clear) +{ + uint8_t thresholds[2]; + thresholds[0] = constrain(set, 0, 100); + thresholds[1] = constrain(clear, 0, 100); + return writeExtendedData(BQ27441_ID_DISCHARGE, 2, thresholds, 2); +} + +// Check if the SOC1 flag is set +bool BQ27441::socFlag(void) +{ + uint16_t flagState = flags(); + + return flagState & BQ27441_FLAG_SOC1; +} + +// Check if the SOCF flag is set +bool BQ27441::socfFlag(void) +{ + uint16_t flagState = flags(); + + return flagState & BQ27441_FLAG_SOCF; + +} + +// Check if the ITPOR flag is set +bool BQ27441::itporFlag(void) +{ + uint16_t flagState = flags(); + + return flagState & BQ27441_FLAG_ITPOR; +} + +// Check if the FC flag is set +bool BQ27441::fcFlag(void) +{ + uint16_t flagState = flags(); + + return flagState & BQ27441_FLAG_FC; +} + +// Check if the CHG flag is set +bool BQ27441::chgFlag(void) +{ + uint16_t flagState = flags(); + + return flagState & BQ27441_FLAG_CHG; +} + +// Check if the DSG flag is set +bool BQ27441::dsgFlag(void) +{ + uint16_t flagState = flags(); + + return flagState & BQ27441_FLAG_DSG; +} + +// Get the SOC_INT interval delta +uint8_t BQ27441::sociDelta(void) +{ + return readExtendedData(BQ27441_ID_STATE, 26); +} + +// Set the SOC_INT interval delta to a value between 1 and 100 +bool BQ27441::setSOCIDelta(uint8_t delta) +{ + uint8_t soci = constrain(delta, 0, 100); + return writeExtendedData(BQ27441_ID_STATE, 26, &soci, 1); +} + +// Pulse the GPOUT pin - must be in SOC_INT mode +bool BQ27441::pulseGPOUT(void) +{ + return executeControlWord(BQ27441_CONTROL_PULSE_SOC_INT); +} + +/***************************************************************************** + *************************** Control Sub-Commands **************************** + *****************************************************************************/ + +// Read the device type - should be 0x0421 +uint16_t BQ27441::deviceType(void) +{ + return readControlWord(BQ27441_CONTROL_DEVICE_TYPE); +} + +// Enter configuration mode - set userControl if calling from an Arduino sketch +// and you want control over when to exitConfig +bool BQ27441::enterConfig(bool userControl) +{ + if (userControl) _userConfigControl = true; + + if (sealed()) + { + _sealFlag = true; + unseal(); // Must be unsealed before making changes + } + + if (executeControlWord(BQ27441_CONTROL_SET_CFGUPDATE)) + { + int16_t timeout = BQ72441_I2C_TIMEOUT; + while ((timeout--) && (!(flags() & BQ27441_FLAG_CFGUPMODE))) + delay(1); + + if (timeout > 0) + return true; + } + + return false; +} + +// Exit configuration mode with the option to perform a resimulation +bool BQ27441::exitConfig(bool resim) +{ + // There are two methods for exiting config mode: + // 1. Execute the EXIT_CFGUPDATE command + // 2. Execute the SOFT_RESET command + // EXIT_CFGUPDATE exits config mode _without_ an OCV (open-circuit voltage) + // measurement, and without resimulating to update unfiltered-SoC and SoC. + // If a new OCV measurement or resimulation is desired, SOFT_RESET or + // EXIT_RESIM should be used to exit config mode. + if (resim) + { + if (softReset()) + { + int16_t timeout = BQ72441_I2C_TIMEOUT; + while ((timeout--) && ((flags() & BQ27441_FLAG_CFGUPMODE))) + delay(1); + if (timeout > 0) + { + if (_sealFlag) seal(); // Seal back up if we IC was sealed coming in + return true; + } + } + return false; + } + else + { + return executeControlWord(BQ27441_CONTROL_EXIT_CFGUPDATE); + } +} + +// Read the flags() command +uint16_t BQ27441::flags(void) +{ + return readWord(BQ27441_COMMAND_FLAGS); +} + +// Read the CONTROL_STATUS subcommand of control() +uint16_t BQ27441::status(void) +{ + return readControlWord(BQ27441_CONTROL_STATUS); +} + +/***************************** Private Functions *****************************/ + +// Check if the BQ27441-G1A is sealed or not. +bool BQ27441::sealed(void) +{ + uint16_t stat = status(); + return stat & BQ27441_STATUS_SS; +} + +// Seal the BQ27441-G1A +bool BQ27441::seal(void) +{ + return readControlWord(BQ27441_CONTROL_SEALED); +} + +// UNseal the BQ27441-G1A +bool BQ27441::unseal(void) +{ + // To unseal the BQ27441, write the key to the control + // command. Then immediately write the same key to control again. + if (readControlWord(BQ27441_UNSEAL_KEY)) + { + return readControlWord(BQ27441_UNSEAL_KEY); + } + return false; +} + +// Read the 16-bit opConfig register from extended data +uint16_t BQ27441::opConfig(void) +{ + return readWord(BQ27441_EXTENDED_OPCONFIG); +} + +// Write the 16-bit opConfig register in extended data +bool BQ27441::writeOpConfig(uint16_t value) +{ + uint8_t opConfigMSB = value >> 8; + uint8_t opConfigLSB = value & 0x00FF; + uint8_t opConfigData[2] = {opConfigMSB, opConfigLSB}; + + // OpConfig register location: BQ27441_ID_REGISTERS id, offset 0 + return writeExtendedData(BQ27441_ID_REGISTERS, 0, opConfigData, 2); +} + +// Issue a soft-reset to the BQ27441-G1A +bool BQ27441::softReset(void) +{ + return executeControlWord(BQ27441_CONTROL_SOFT_RESET); +} + +// Read a 16-bit command word from the BQ27441-G1A +uint16_t BQ27441::readWord(uint16_t subAddress) +{ + uint8_t data[2]; + i2cReadBytes(subAddress, data, 2); + return ((uint16_t) data[1] << 8) | data[0]; +} + +// Read a 16-bit subcommand() from the BQ27441-G1A's control() +uint16_t BQ27441::readControlWord(uint16_t function) +{ + uint8_t subCommandMSB = (function >> 8); + uint8_t subCommandLSB = (function & 0x00FF); + uint8_t command[2] = {subCommandLSB, subCommandMSB}; + uint8_t data[2] = {0, 0}; + + i2cWriteBytes((uint8_t) 0, command, 2); + + if (i2cReadBytes((uint8_t) 0, data, 2)) + { + return ((uint16_t)data[1] << 8) | data[0]; + } + + return false; +} + +// Execute a subcommand() from the BQ27441-G1A's control() +bool BQ27441::executeControlWord(uint16_t function) +{ + uint8_t subCommandMSB = (function >> 8); + uint8_t subCommandLSB = (function & 0x00FF); + uint8_t command[2] = {subCommandLSB, subCommandMSB}; + uint8_t data[2] = {0, 0}; + + if (i2cWriteBytes((uint8_t) 0, command, 2)) + return true; + + return false; +} + +/***************************************************************************** + ************************** Extended Data Commands *************************** + *****************************************************************************/ + +// Issue a BlockDataControl() command to enable BlockData access +bool BQ27441::blockDataControl(void) +{ + uint8_t enableByte = 0x00; + return i2cWriteBytes(BQ27441_EXTENDED_CONTROL, &enableByte, 1); +} + +// Issue a DataClass() command to set the data class to be accessed +bool BQ27441::blockDataClass(uint8_t id) +{ + return i2cWriteBytes(BQ27441_EXTENDED_DATACLASS, &id, 1); +} + +// Issue a DataBlock() command to set the data block to be accessed +bool BQ27441::blockDataOffset(uint8_t offset) +{ + return i2cWriteBytes(BQ27441_EXTENDED_DATABLOCK, &offset, 1); +} + +// Read the current checksum using BlockDataCheckSum() +uint8_t BQ27441::blockDataChecksum(void) +{ + uint8_t csum; + i2cReadBytes(BQ27441_EXTENDED_CHECKSUM, &csum, 1); + return csum; +} + +// Use BlockData() to read a byte from the loaded extended data +uint8_t BQ27441::readBlockData(uint8_t offset) +{ + uint8_t ret; + uint8_t address = offset + BQ27441_EXTENDED_BLOCKDATA; + i2cReadBytes(address, &ret, 1); + return ret; +} + +// Use BlockData() to write a byte to an offset of the loaded data +bool BQ27441::writeBlockData(uint8_t offset, uint8_t data) +{ + uint8_t address = offset + BQ27441_EXTENDED_BLOCKDATA; + return i2cWriteBytes(address, &data, 1); +} + +// Read all 32 bytes of the loaded extended data and compute a +// checksum based on the values. +uint8_t BQ27441::computeBlockChecksum(void) +{ + uint8_t data[32]; + i2cReadBytes(BQ27441_EXTENDED_BLOCKDATA, data, 32); + + uint8_t csum = 0; + for (int i=0; i<32; i++) + { + csum += data[i]; + } + csum = 255 - csum; + + return csum; +} + +// Use the BlockDataCheckSum() command to write a checksum value +bool BQ27441::writeBlockChecksum(uint8_t csum) +{ + return i2cWriteBytes(BQ27441_EXTENDED_CHECKSUM, &csum, 1); +} + +// Read a byte from extended data specifying a class ID and position offset +uint8_t BQ27441::readExtendedData(uint8_t classID, uint8_t offset) +{ + uint8_t retData = 0; + if (!_userConfigControl) enterConfig(false); + + if (!blockDataControl()) // // enable block data memory control + return false; // Return false if enable fails + if (!blockDataClass(classID)) // Write class ID using DataBlockClass() + return false; + + blockDataOffset(offset / 32); // Write 32-bit block offset (usually 0) + + computeBlockChecksum(); // Compute checksum going in + uint8_t oldCsum = blockDataChecksum(); + /*for (int i=0; i<32; i++) + Serial.print(String(readBlockData(i)) + " ");*/ + retData = readBlockData(offset % 32); // Read from offset (limit to 0-31) + + if (!_userConfigControl) exitConfig(); + + return retData; +} + +// Write a specified number of bytes to extended data specifying a +// class ID, position offset. +bool BQ27441::writeExtendedData(uint8_t classID, uint8_t offset, uint8_t * data, uint8_t len) +{ + if (len > 32) + return false; + + if (!_userConfigControl) enterConfig(false); + + if (!blockDataControl()) // // enable block data memory control + return false; // Return false if enable fails + if (!blockDataClass(classID)) // Write class ID using DataBlockClass() + return false; + + blockDataOffset(offset / 32); // Write 32-bit block offset (usually 0) + computeBlockChecksum(); // Compute checksum going in + uint8_t oldCsum = blockDataChecksum(); + + // Write data bytes: + for (int i = 0; i < len; i++) + { + // Write to offset, mod 32 if offset is greater than 32 + // The blockDataOffset above sets the 32-bit block + writeBlockData((offset % 32) + i, data[i]); + } + + // Write new checksum using BlockDataChecksum (0x60) + uint8_t newCsum = computeBlockChecksum(); // Compute the new checksum + writeBlockChecksum(newCsum); + + if (!_userConfigControl) exitConfig(); + + return true; +} + +/***************************************************************************** + ************************ I2C Read and Write Routines ************************ + *****************************************************************************/ + +// Read a specified number of bytes over I2C at a given subAddress +int16_t BQ27441::i2cReadBytes(uint8_t subAddress, uint8_t * dest, uint8_t count) +{ + int16_t timeout = BQ72441_I2C_TIMEOUT; + Wire.beginTransmission(_deviceAddress); + Wire.write(subAddress); + Wire.endTransmission(true); + + Wire.requestFrom(_deviceAddress, count); + + for (int i=0; i0 indicates charging. + */ + int16_t current(current_measure type = AVG); + + /** + Reads and returns the specified capacity measurement + + @param capacity_measure enum specifying capacity value to be read + @return specified capacity measurement in mAh. + */ + uint16_t capacity(capacity_measure type = REMAIN); + + /** + Reads and returns measured average power + + @return average power in mAh. >0 indicates charging. + */ + int16_t power(void); + + /** + Reads and returns specified state of charge measurement + + @param soc_measure enum specifying filtered or unfiltered measurement + @return specified state of charge measurement in % + */ + uint16_t soc(soc_measure type = FILTERED); + + /** + Reads and returns specified state of health measurement + + @param soh_measure enum specifying filtered or unfiltered measurement + @return specified state of health measurement in %, or status bits + */ + uint8_t soh(soh_measure type = PERCENT); + + /** + Reads and returns specified temperature measurement + + @param temp_measure enum specifying internal or battery measurement + @return specified temperature measurement in degrees C + */ + uint16_t temperature(temp_measure type = BATTERY); + + //////////////////////////// + // GPOUT Control Commands // + //////////////////////////// + /** + Get GPOUT polarity setting (active-high or active-low) + + @return true if active-high, false if active-low + */ + bool GPOUTPolarity(void); + + /** + Set GPOUT polarity to active-high or active-low + + @param activeHigh is true if active-high, false if active-low + @return true on success + */ + bool setGPOUTPolarity(bool activeHigh); + + /** + Get GPOUT function (BAT_LOW or SOC_INT) + + @return true if BAT_LOW or false if SOC_INT + */ + bool GPOUTFunction(void); + + /** + Set GPOUT function to BAT_LOW or SOC_INT + + @param function should be either BAT_LOW or SOC_INT + @return true on success + */ + bool setGPOUTFunction(gpout_function function); + + /** + Get SOC1_Set Threshold - threshold to set the alert flag + + @return state of charge value between 0 and 100% + */ + uint8_t SOC1SetThreshold(void); + + /** + Get SOC1_Clear Threshold - threshold to clear the alert flag + + @return state of charge value between 0 and 100% + */ + uint8_t SOC1ClearThreshold(void); + + /** + Set the SOC1 set and clear thresholds to a percentage + + @param set and clear percentages between 0 and 100. clear > set. + @return true on success + */ + bool setSOC1Thresholds(uint8_t set, uint8_t clear); + + /** + Get SOCF_Set Threshold - threshold to set the alert flag + + @return state of charge value between 0 and 100% + */ + uint8_t SOCFSetThreshold(void); + + /** + Get SOCF_Clear Threshold - threshold to clear the alert flag + + @return state of charge value between 0 and 100% + */ + uint8_t SOCFClearThreshold(void); + + /** + Set the SOCF set and clear thresholds to a percentage + + @param set and clear percentages between 0 and 100. clear > set. + @return true on success + */ + bool setSOCFThresholds(uint8_t set, uint8_t clear); + + /** + Check if the SOC1 flag is set in flags() + + @return true if flag is set + */ + bool socFlag(void); + + /** + Check if the SOCF flag is set in flags() + + @return true if flag is set + */ + bool socfFlag(void); + + /** + Check if the ITPOR flag is set in flags() + + @return true if flag is set + */ + bool itporFlag(void); + + /** + Check if the FC flag is set in flags() + + @return true if flag is set + */ + bool fcFlag(void); + + /** + Check if the CHG flag is set in flags() + + @return true if flag is set + */ + bool chgFlag(void); + + /** + Check if the DSG flag is set in flags() + + @return true if flag is set + */ + bool dsgFlag(void); + + + /** + Get the SOC_INT interval delta + + @return interval percentage value between 1 and 100 + */ + uint8_t sociDelta(void); + + /** + Set the SOC_INT interval delta to a value between 1 and 100 + + @param interval percentage value between 1 and 100 + @return true on success + */ + bool setSOCIDelta(uint8_t delta); + + /** + Pulse the GPOUT pin - must be in SOC_INT mode + + @return true on success + */ + bool pulseGPOUT(void); + + ////////////////////////// + // Control Sub-commands // + ////////////////////////// + + /** + Read the device type - should be 0x0421 + + @return 16-bit value read from DEVICE_TYPE subcommand + */ + uint16_t deviceType(void); + + /** + Enter configuration mode - set userControl if calling from an Arduino + sketch and you want control over when to exitConfig. + + @param userControl is true if the Arduino sketch is handling entering + and exiting config mode (should be false in library calls). + @return true on success + */ + bool enterConfig(bool userControl = true); + + /** + Exit configuration mode with the option to perform a resimulation + + @param resim is true if resimulation should be performed after exiting + @return true on success + */ + bool exitConfig(bool resim = true); + + /** + Read the flags() command + + @return 16-bit representation of flags() command register + */ + uint16_t flags(void); + + /** + Read the CONTROL_STATUS subcommand of control() + + @return 16-bit representation of CONTROL_STATUS subcommand + */ + uint16_t status(void); + +private: + uint8_t _deviceAddress; // Stores the BQ27441-G1A's I2C address + bool _sealFlag; // Global to identify that IC was previously sealed + bool _userConfigControl; // Global to identify that user has control over + // entering/exiting config + + /** + Check if the BQ27441-G1A is sealed or not. + + @return true if the chip is sealed + */ + bool sealed(void); + + /** + Seal the BQ27441-G1A + + @return true on success + */ + bool seal(void); + + /** + UNseal the BQ27441-G1A + + @return true on success + */ + bool unseal(void); + + /** + Read the 16-bit opConfig register from extended data + + @return opConfig register contents + */ + uint16_t opConfig(void); + + /** + Write the 16-bit opConfig register in extended data + + @param New 16-bit value for opConfig + @return true on success + */ + bool writeOpConfig(uint16_t value); + + /** + Issue a soft-reset to the BQ27441-G1A + + @return true on success + */ + bool softReset(void); + + /** + Read a 16-bit command word from the BQ27441-G1A + + @param subAddress is the command to be read from + @return 16-bit value of the command's contents + */ + uint16_t readWord(uint16_t subAddress); + + /** + Read a 16-bit subcommand() from the BQ27441-G1A's control() + + @param function is the subcommand of control() to be read + @return 16-bit value of the subcommand's contents + */ + uint16_t readControlWord(uint16_t function); + + /** + Execute a subcommand() from the BQ27441-G1A's control() + + @param function is the subcommand of control() to be executed + @return true on success + */ + bool executeControlWord(uint16_t function); + + //////////////////////////// + // Extended Data Commands // + //////////////////////////// + /** + Issue a BlockDataControl() command to enable BlockData access + + @return true on success + */ + bool blockDataControl(void); + + /** + Issue a DataClass() command to set the data class to be accessed + + @param id is the id number of the class + @return true on success + */ + bool blockDataClass(uint8_t id); + + /** + Issue a DataBlock() command to set the data block to be accessed + + @param offset of the data block + @return true on success + */ + bool blockDataOffset(uint8_t offset); + + /** + Read the current checksum using BlockDataCheckSum() + + @return true on success + */ + uint8_t blockDataChecksum(void); + + /** + Use BlockData() to read a byte from the loaded extended data + + @param offset of data block byte to be read + @return true on success + */ + uint8_t readBlockData(uint8_t offset); + + /** + Use BlockData() to write a byte to an offset of the loaded data + + @param offset is the position of the byte to be written + data is the value to be written + @return true on success + */ + bool writeBlockData(uint8_t offset, uint8_t data); + + /** + Read all 32 bytes of the loaded extended data and compute a + checksum based on the values. + + @return 8-bit checksum value calculated based on loaded data + */ + uint8_t computeBlockChecksum(void); + + /** + Use the BlockDataCheckSum() command to write a checksum value + + @param csum is the 8-bit checksum to be written + @return true on success + */ + bool writeBlockChecksum(uint8_t csum); + + /** + Read a byte from extended data specifying a class ID and position offset + + @param classID is the id of the class to be read from + offset is the byte position of the byte to be read + @return 8-bit value of specified data + */ + uint8_t readExtendedData(uint8_t classID, uint8_t offset); + + /** + Write a specified number of bytes to extended data specifying a + class ID, position offset. + + @param classID is the id of the class to be read from + offset is the byte position of the byte to be read + data is the data buffer to be written + len is the number of bytes to be written + @return true on success + */ + bool writeExtendedData(uint8_t classID, uint8_t offset, uint8_t * data, uint8_t len); + + ///////////////////////////////// + // I2C Read and Write Routines // + ///////////////////////////////// + + /** + Read a specified number of bytes over I2C at a given subAddress + + @param subAddress is the 8-bit address of the data to be read + dest is the data buffer to be written to + count is the number of bytes to be read + @return true on success + */ + int16_t i2cReadBytes(uint8_t subAddress, uint8_t * dest, uint8_t count); + + /** + Write a specified number of bytes over I2C to a given subAddress + + @param subAddress is the 8-bit address of the data to be written to + src is the data buffer to be written + count is the number of bytes to be written + @return true on success + */ + uint16_t i2cWriteBytes(uint8_t subAddress, uint8_t * src, uint8_t count); +}; + +extern BQ27441 lipo; // Use lipo.[] to interact with the library in an Arduino sketch +// Thanks for reading! + +#endif diff --git a/src/batterymonitor.cpp b/src/batterymonitor.cpp index 45066a2cc..f9fee9906 100644 --- a/src/batterymonitor.cpp +++ b/src/batterymonitor.cpp @@ -27,8 +27,30 @@ ADC_MODE(ADC_VCC); #endif +#if BATTERY_MONITOR == BAT_BQ27441 +#include +#endif + void BatteryMonitor::Setup() { +#if BATTERY_MONITOR == BAT_BQ27441 + if (!lipo.begin()){ + m_Logger.error("BQ27741 not found on I2C bus"); + } + else{ + m_Logger.info("BQ27741 found"); + if(lipo.itporFlag()){ // Check if need to set configs + m_Logger.info("BQ27741 paremeters not set, setting them"); + lipo.enterConfig(); + lipo.setCapacity(BATTERY_DESIGN_CAPACITY); + lipo.setDesignEnergy(3.7f*BATTERY_DESIGN_CAPACITY); + lipo.setTerminateVoltage(BATTERY_LOWEST_OP_VOLTAGE); + lipo.setTaperRate(10 * BATTERY_DESIGN_CAPACITY / BATTERY_TAPER_CURRENT); + lipo.exitConfig(); + } + } + +#endif #if BATTERY_MONITOR == BAT_MCP3021 || BATTERY_MONITOR == BAT_INTERNAL_MCP3021 for (uint8_t i = 0x48; i < 0x4F; i++) { @@ -47,7 +69,7 @@ void BatteryMonitor::Setup() void BatteryMonitor::Loop() { - #if BATTERY_MONITOR == BAT_EXTERNAL || BATTERY_MONITOR == BAT_INTERNAL || BATTERY_MONITOR == BAT_MCP3021 || BATTERY_MONITOR == BAT_INTERNAL_MCP3021 + #if BATTERY_MONITOR == BAT_EXTERNAL || BATTERY_MONITOR == BAT_INTERNAL || BATTERY_MONITOR == BAT_MCP3021 || BATTERY_MONITOR == BAT_INTERNAL_MCP3021 || BATTERY_MONITOR == BAT_BQ27441 auto now_ms = millis(); if (now_ms - last_battery_sample >= batterySampleRate) { @@ -99,26 +121,49 @@ void BatteryMonitor::Loop() } } #endif + #if BATTERY_MONITOR == BAT_BQ27441 + voltage = lipo.voltage()/1000.f; + level = lipo.soc()/100.0f; + + int current = lipo.current(AVG); + unsigned int fullCapacity = lipo.capacity(FULL); + unsigned int capacity = lipo.capacity(REMAIN); + int power = lipo.power(); + int health = lipo.soh(); + + String bat_status = "Battery:"; + bat_status += " Level : " + String(level); + bat_status += ", Voltage [V]: " + String(voltage); + bat_status += ", Current [mA]: " + String(current); + bat_status += ", Capacity [mAh]: " + String (capacity) + "/" + String(fullCapacity); + bat_status += ", Power draw [mW]: " + String(power); + bat_status += ", Health [perc]: " + String(health); + m_Logger.info(bat_status.c_str()); + + #endif if (voltage > 0) //valid measurement { - // Estimate battery level, 3.2V is 0%, 4.17V is 100% (1.0) - if (voltage > 3.975f) - level = (voltage - 2.920f) * 0.8f; - else if (voltage > 3.678f) - level = (voltage - 3.300f) * 1.25f; - else if (voltage > 3.489f) - level = (voltage - 3.400f) * 1.7f; - else if (voltage > 3.360f) - level = (voltage - 3.300f) * 0.8f; - else - level = (voltage - 3.200f) * 0.3f; + #if BATTERY_MONITOR == BAT_EXTERNAL || BATTERY_MONITOR == BAT_INTERNAL || BATTERY_MONITOR == BAT_MCP3021 || BATTERY_MONITOR == BAT_INTERNAL_MCP3021 + // Estimate battery level based on voltage, 3.2V is 0%, 4.17V is 100% (1.0) + if (voltage > 3.975f) + level = (voltage - 2.920f) * 0.8f; + else if (voltage > 3.678f) + level = (voltage - 3.300f) * 1.25f; + else if (voltage > 3.489f) + level = (voltage - 3.400f) * 1.7f; + else if (voltage > 3.360f) + level = (voltage - 3.300f) * 0.8f; + else + level = (voltage - 3.200f) * 0.3f; + + level = (level - 0.05f) / 0.95f; // Cut off the last 5% (3.36V) - level = (level - 0.05f) / 0.95f; // Cut off the last 5% (3.36V) + if (level > 1) + level = 1; + else if (level < 0) + level = 0; + #endif - if (level > 1) - level = 1; - else if (level < 0) - level = 0; networkConnection.sendBatteryLevel(voltage, level); #ifdef BATTERY_LOW_POWER_VOLTAGE if (voltage < BATTERY_LOW_POWER_VOLTAGE) diff --git a/src/consts.h b/src/consts.h index e8dac2f1c..66750ebd6 100644 --- a/src/consts.h +++ b/src/consts.h @@ -61,6 +61,7 @@ #define BAT_INTERNAL 2 #define BAT_MCP3021 3 #define BAT_INTERNAL_MCP3021 4 +#define BAT_BQ27441 5 #define LED_OFF 255 diff --git a/src/debug.h b/src/debug.h index b2c4968d5..96036342c 100644 --- a/src/debug.h +++ b/src/debug.h @@ -67,11 +67,6 @@ // Setup for the Magnetometer #define useFullCalibrationMatrix true -// Battery configuration -#define batterySampleRate 10000 -#define BATTERY_LOW_VOLTAGE_DEEP_SLEEP false -#define BATTERY_LOW_POWER_VOLTAGE 3.3f // Voltage to raise error - // Send updates over network only when changes are substantial // If "false" updates are sent at the sensor update rate (usually 100 TPS) // If "true" updates will be less frequent in the time of little motion diff --git a/src/defines.h b/src/defines.h index 33296454a..618f61e92 100644 --- a/src/defines.h +++ b/src/defines.h @@ -52,11 +52,24 @@ IMU_DESC_ENTRY(IMU_BMP160, PRIMARY_IMU_ADDRESS_ONE, IMU_ROTATION, PIN_IMU_SCL, P IMU_DESC_ENTRY(SECOND_IMU, SECONDARY_IMU_ADDRESS_TWO, SECOND_IMU_ROTATION, PIN_IMU_SCL, PIN_IMU_SDA, SECONDARY_IMU_OPTIONAL, PIN_IMU_INT_2) #endif +// Battery configuration // Battery monitoring options (comment to disable): // BAT_EXTERNAL for ADC pin, // BAT_INTERNAL for internal - can detect only low battery, // BAT_MCP3021 for external ADC connected over I2C +// BAT_BQ27441 for external BQ27441 battery gauge IC #define BATTERY_MONITOR BAT_EXTERNAL +#define batterySampleRate 10000 +#define BATTERY_LOW_VOLTAGE_DEEP_SLEEP false +#define BATTERY_LOW_POWER_VOLTAGE 3.3f // Voltage to raise error +// Only needed for BQ27441: +#define BATTERY_DESIGN_CAPACITY 1500 // Battery design capacity in mAh +#define BATTERY_LOWEST_OP_VOLTAGE 3.0f // Lowest battery voltage in which board can still operate +#define BATTERY_TAPER_CURRENT 75 // Current at which charger stops charging in mA + +#if BATTERY_MONITOR == BAT_BQ27441 && I2C_SPEED > 100000 +#error BQ27441 library supports only 100 kHz I2C speed +#endif // BAT_EXTERNAL definition override // D1 Mini boards with ESP8266 have internal resistors. For these boards you only have to adjust BATTERY_SHIELD_RESISTANCE.