Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Sparkfun Battery Babysitter I2C
#1
I've been trying to talk to Sparkfun's wonderful Battery Babysitter today.

The chip on board is the BQ27441-G1, over I2C.  There is a fairly simple Arduino library I've been using for inspiration, here.


I have the hardware set up correctly, I believe.  I'm able to do this:


Code:
MicroPython ESP32_LoBo_v3.2.24 - 2018-09-06 on ESP32 board with ESP32
Type "help()" for more information.
>>> import machine
>>> m = machine.I2C(0, sda=21, scl=22, speed=100000)
>>> m.scan()
[85]
>>> m.is_ready(85)
True
>>>

85 is what you'd expect, from the Arduino stuff:

Code:
#define BQ72441_I2C_ADDRESS 0x55 // Default I2C address of the BQ27441-G1A

85 == 0x55, so apparently we're talking pretty well.  Everybody seems happy so far.

I'm trying to read something simple to start, the SOH (state of health).  It should be a number around 90 or so, a % of battery health.  Here's the Arduino code that makes that happen:

Code:
#define BQ27441_COMMAND_SOH                0x20 // StateOfHealth()

...

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

...readWord() is a 16 bit int wrapper around i2cReadBytes() ...

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

... i2cReadBytes() does the work...

// 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; i<count; i++)
    {
        dest[i] = Wire.read();
    }
    
    return timeout;
}

To me, this all looks like you should do this:


Code:
m.is_ready(85)
b = bytearray([32])
m.writeto(85,b)
r = m.readfrom(85,2)

That seems like it should do the trick.  But it fails:


Code:
>>> b = bytearray([32])
>>> m.writeto(85,b);r=readfrom(85,2)
Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
OSError: I2C bus error (263)
>>>

Any ideas what I'm getting wrong?  Thanks.   Smile
Reply
#2
(08-28-2019, 07:29 PM)BoredBSEE Wrote: Any ideas what I'm getting wrong?  Thanks.   Smile

Please read the I2C documentation.
Reply
#3
(09-05-2019, 09:28 PM)lobo Wrote:
(08-28-2019, 07:29 PM)BoredBSEE Wrote: Any ideas what I'm getting wrong?  Thanks.   Smile

Please read the I2C documentation.

Did that.  Smile 

Spent some time with my scope and an Arduino that can speak to it.  Turns out the problem was some stray capacitance on the lines.  I have this thing sky-wired all over the place.  Slowing the bus speed from 100k down to 20k fixed everything.  I2C bus errors went away.  BTW anyone else reading this - if you see:

OSError:  I2C bus error (263)

...try slowing down your clock speed.  Worked for me.

As a bonus, here's my code that lets you talk to the Battery Babysitter.  Free for anyone to use, I make no claims as to accuracy or suitability to any purpose, etc etc. 

It reads everything I need it to read - other stuff is easy enough to add in if anyone else wants to.  It's mostly a direct port of the Arduino library they have.  It probably could stand a little cleaning up, but it works.  If you want to add anything else to it just check out the BQ27441-G1 pdf over at Sparkfun's Battery Babysitter page.



Code:
import machine
import utime

#///////////////////////
#// General Constants //
#///////////////////////
BQ72441_I2C_ADDRESS = 0x55
BQ27441_UNSEAL_KEY  =   0x8000 # Secret code to unseal the BQ27441-G1A
BQ27441_DEVICE_ID   =   0x0421 # Default device ID
BQ72441_I2C_TIMEOUT = 2000 # in ms

#///////////////////////
#// 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.
BQ27441_COMMAND_CONTROL         =   0x00 # Control()
BQ27441_COMMAND_TEMP            =   0x02 # Temperature()
BQ27441_COMMAND_VOLTAGE         =   0x04 # Voltage()
BQ27441_COMMAND_FLAGS           =   0x06 # Flags()
BQ27441_COMMAND_NOM_CAPACITY    =   0x08 # NominalAvailableCapacity()
BQ27441_COMMAND_AVAIL_CAPACITY  =   0x0A # FullAvailableCapacity()
BQ27441_COMMAND_REM_CAPACITY    =   0x0C # RemainingCapacity()
BQ27441_COMMAND_FULL_CAPACITY   =   0x0E # FullChargeCapacity()
BQ27441_COMMAND_AVG_CURRENT     =   0x10 # AverageCurrent()
BQ27441_COMMAND_STDBY_CURRENT   =   0x12 # StandbyCurrent()
BQ27441_COMMAND_MAX_CURRENT     =   0x14 # MaxLoadCurrent()
BQ27441_COMMAND_AVG_POWER       =   0x18 # AveragePower()
BQ27441_COMMAND_SOC             =   0x1C # StateOfCharge()
BQ27441_COMMAND_INT_TEMP        =   0x1E # InternalTemperature()
BQ27441_COMMAND_SOH             =   0x20 # StateOfHealth()
BQ27441_COMMAND_REM_CAP_UNFL    =   0x28 # RemainingCapacityUnfiltered()
BQ27441_COMMAND_REM_CAP_FIL     =   0x2A # RemainingCapacityFiltered()
BQ27441_COMMAND_FULL_CAP_UNFL   =   0x2C # FullChargeCapacityUnfiltered()
BQ27441_COMMAND_FULL_CAP_FIL    =   0x2E # FullChargeCapacityFiltered()
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.
BQ27441_CONTROL_STATUS              =   0x00
BQ27441_CONTROL_DEVICE_TYPE         =   0x01
BQ27441_CONTROL_FW_VERSION          =   0x02
BQ27441_CONTROL_DM_CODE             =    0x04
BQ27441_CONTROL_PREV_MACWRITE       =    0x07
BQ27441_CONTROL_CHEM_ID             =    0x08
BQ27441_CONTROL_BAT_INSERT          =   0x0C
BQ27441_CONTROL_BAT_REMOVE          =   0x0D
BQ27441_CONTROL_SET_HIBERNATE       =   0x11
BQ27441_CONTROL_CLEAR_HIBERNATE     =    0x12
BQ27441_CONTROL_SET_CFGUPDATE       =    0x13
BQ27441_CONTROL_SHUTDOWN_ENABLE     =   0x1B
BQ27441_CONTROL_SHUTDOWN            =    0x1C
BQ27441_CONTROL_SEALED              =    0x20
BQ27441_CONTROL_PULSE_SOC_INT       =    0x23
BQ27441_CONTROL_RESET               =    0x41
BQ27441_CONTROL_SOFT_RESET          =    0x42
BQ27441_CONTROL_EXIT_CFGUPDATE      =     0x43
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.
BQ27441_STATUS_SHUTDOWNEN   =   (1<<15)
BQ27441_STATUS_WDRESET      =    (1<<14)
BQ27441_STATUS_SS           =    (1<<13)
BQ27441_STATUS_CALMODE      =    (1<<12)
BQ27441_STATUS_CCA          =    (1<<11)
BQ27441_STATUS_BCA          =    (1<<10)
BQ27441_STATUS_QMAX_UP      =    (1<<9)
BQ27441_STATUS_RES_UP       =    (1<<8)
BQ27441_STATUS_INITCOMP     =    (1<<7)
BQ27441_STATUS_HIBERNATE    =    (1<<6)
BQ27441_STATUS_SLEEP        =    (1<<4)
BQ27441_STATUS_LDMD         =    (1<<3)
BQ27441_STATUS_RUP_DIS      =    (1<<2)
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.
BQ27441_FLAG_OT         =   (1<<15)
BQ27441_FLAG_UT         =   (1<<14)
BQ27441_FLAG_FC         =   (1<<9)
BQ27441_FLAG_CHG        =   (1<<8)
BQ27441_FLAG_OCVTAKEN   =   (1<<7)
BQ27441_FLAG_ITPOR      =   (1<<5)
BQ27441_FLAG_CFGUPMODE  =   (1<<4)
BQ27441_FLAG_BAT_DET    =   (1<<3)
BQ27441_FLAG_SOC1       =   (1<<2)
BQ27441_FLAG_SOCF       =   (1<<1)
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.
BQ27441_EXTENDED_OPCONFIG   =    0x3A # OpConfig()
BQ27441_EXTENDED_CAPACITY   =    0x3C # DesignCapacity()
BQ27441_EXTENDED_DATACLASS  =    0x3E # DataClass()
BQ27441_EXTENDED_DATABLOCK  =    0x3F # DataBlock()
BQ27441_EXTENDED_BLOCKDATA  =    0x40 # BlockData()
BQ27441_EXTENDED_CHECKSUM   =    0x60 # BlockDataCheckSum()
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
BQ27441_ID_SAFETY           =    2   #// Safety
BQ27441_ID_CHG_TERMINATION  =    36  #// Charge Termination
BQ27441_ID_CONFIG_DATA      =    48  #// Data
BQ27441_ID_DISCHARGE        =    49  #// Discharge
BQ27441_ID_REGISTERS        =    64  #// Registers
BQ27441_ID_POWER            =    68  #// Power
#// Gas Gauging Classes
BQ27441_ID_IT_CFG           =    80  #// IT Cfg
BQ27441_ID_CURRENT_THRESH   =    81  #// Current Thresholds
BQ27441_ID_STATE            =    82  #// State
#// Ra Tables Classes
BQ27441_ID_R_A_RAM          =    89  #// R_a RAM
#// Calibration Classes
BQ27441_ID_CALIB_DATA       =    104 #// Data
BQ27441_ID_CC_CAL           =    105 #// CC Cal
BQ27441_ID_CURRENT          =    107 #// Current
#// Security Classes
BQ27441_ID_CODES            =    112 #// Codes

#/////////////////////////////////////////
#// OpConfig Register - Bit Definitions //
#/////////////////////////////////////////
#// Bit positions of the OpConfig Register
BQ27441_OPCONFIG_BIE        =   (1<<13)
BQ27441_OPCONFIG_BI_PU_EN   =   (1<<12)
BQ27441_OPCONFIG_GPIOPOL    =   (1<<11)
BQ27441_OPCONFIG_SLEEP      =   (1<<5)
BQ27441_OPCONFIG_RMFCC      =   (1<<4)
BQ27441_OPCONFIG_BATLOWEN   =   (1<<2)
BQ27441_OPCONFIG_TEMPS      =   (1<<0)

#####  Variables

m = machine.I2C(0, sda=19, scl=18, speed=20000)

#####  User interface functions here

def deviceType():
   return readControlWord(BQ27441_CONTROL_DEVICE_TYPE)

def firmwareVersion():
   return readControlWord(BQ27441_CONTROL_FW_VERSION)

def percentageHealth():
   sohRaw = readWord(BQ27441_COMMAND_SOH)
   percent = sohRaw & 0x00ff
   return percent

def status():
   return readControlWord(BQ27441_CONTROL_STATUS)

def voltage():
   return readWord(BQ27441_COMMAND_VOLTAGE)

def avgCurrent():
   return twos_comp(readWord(BQ27441_COMMAND_AVG_CURRENT),16)

def socFiltered():
   return readWord(BQ27441_COMMAND_SOC)

def capacityRemain():
   return readWord(BQ27441_COMMAND_REM_CAPACITY)

def capacityFull():
   return readWord(BQ27441_COMMAND_FULL_CAPACITY)

def flags():
   return readWord(BQ27441_COMMAND_FLAGS)

def getDesignCapacity():
   msb = readExtendedData(BQ27441_ID_STATE, 10)
   lsb = readExtendedData(BQ27441_ID_STATE, 11)
   return (msb << 8) + lsb

def setDesignCapacity(cap):
   msb = (cap >> 8) & 0xff
   lsb = cap & 0xff
   buf = bytearray([msb,lsb])
   return writeExtendedData(BQ27441_ID_STATE, 10, buf)
               

#####  Utility functions here

_userConfigControl = True
_sealFlag = False

def i2cReadBytes(subaddress, count):
   buf = bytearray([subaddress])
   m.writeto(BQ72441_I2C_ADDRESS, buf)
   x = m.readfrom(BQ72441_I2C_ADDRESS, count)
   return x

def i2cWriteBytes(subaddress, src):
   buf = bytearray([subaddress])
   buf.extend(src)
   m.writeto(BQ72441_I2C_ADDRESS, buf)

def readControlWord(controlWord):
   msb = (controlWord >> 8) & 0xff
   lsb = controlWord & 0xff
   command = bytearray([lsb, msb])
   i2cWriteBytes(0, command)
   x = i2cReadBytes(0, 2)
   return ((x[1]<<8)&0xff00)|(x[0]&0xff)

def readWord(commandWord):
   x = i2cReadBytes(commandWord,2)
   return ((x[1]<<8)&0xff00)|(x[0]&0xff)

def executeControlWord(func):
   msb = (func >> 8) & 0xff
   lsb = func & 0xff
   buf = bytearray([lsb,msb])
   i2cWriteBytes(0,buf)
   return True

def softReset():
   return executeControlWord(BQ27441_CONTROL_SOFT_RESET)

def sealed():
   stat = status()
   retval = False
   if(stat & BQ27441_STATUS_SS) > 0:
       retval = True
   return retval

def seal():
   return readControlWord(BQ27441_CONTROL_SEALED) > 0

def unseal():
   executeControlWord(BQ27441_UNSEAL_KEY)
   executeControlWord(BQ27441_UNSEAL_KEY)
   return True

def enterConfig(userControl):
   if userControl:
       _userConfigControl = True;

   if(sealed()):
       _sealFlag = True;
       unseal()

   if executeControlWord(BQ27441_CONTROL_SET_CFGUPDATE):
       timeout = BQ72441_I2C_TIMEOUT
       while (timeout > 0) and ((flags() & BQ27441_FLAG_CFGUPMODE) == 0):
           timeout -= 1
           utime.sleep_ms(1)          

       if timeout > 0:
           return True
       
   return False

def exitConfig(resim):
   if resim:
       if softReset():
           timeout = BQ72441_I2C_TIMEOUT
           while (timeout > 0) and ((flags() & BQ27441_FLAG_CFGUPMODE) == 0):
               timeout -= 1
               utime.sleep_ms(1)  
           if timeout > 0:
               if _sealFlag:
                   seal()
               return True
       return False
   else:
       return executeControlWord(BQ27441_CONTROL_EXIT_CFGUPDATE)
 

#####  Extended Data functions

def blockDataControl():
   buf = bytearray([0])
   i2cWriteBytes(BQ27441_EXTENDED_CONTROL, buf)
   return True

def blockDataClass(id):
   buf = bytearray([id])
   i2cWriteBytes(BQ27441_EXTENDED_DATACLASS, buf)
   return True

def blockDataChecksum():
   x = i2cReadBytes(BQ27441_EXTENDED_CHECKSUM, 1)
   return x[0]

def computeBlockChecksum():
   x = i2cReadBytes(BQ27441_EXTENDED_BLOCKDATA, 32)
   csum = 0
   for i in range(32):
       csum = csum + x[i]
       csum = csum & 0xff
   csum = 255 - csum
   return csum

def blockDataOffset(offset):
   buf = bytearray([offset])
   i2cWriteBytes(BQ27441_EXTENDED_DATABLOCK, buf)
   return True

def readBlockData(offset):
   address = offset + BQ27441_EXTENDED_BLOCKDATA
   x = i2cReadBytes(address, 1)
   return x[0]

def writeBlockData(offset, data):
   address = offset + BQ27441_EXTENDED_BLOCKDATA
   buf = bytearray([data])
   return i2cWriteBytes(address, buf)

def writeBlockChecksum(csum):
   buf = bytearray([csum])
   return i2cWriteBytes(BQ27441_EXTENDED_CHECKSUM, buf)

def readExtendedData(classID, offset):
   if _userConfigControl:
       enterConfig(False)

   if blockDataControl() == False:
       return False
   if blockDataClass(classID) == False:
       return False

   blockDataOffset(int(offset / 32))
   computeBlockChecksum()
   oldCsum = blockDataChecksum()
   retData = readBlockData(offset % 32)

   if _userConfigControl == False:
       exitConfig(False)

   return retData

def writeExtendedData(classID, offset, data):
   length = len(data)
   
   if length > 32:
       return False

   if _userConfigControl:
       enterConfig(False)

   if blockDataControl() == False:
       return False
   if blockDataClass(classID) == False:
       return False

   blockDataOffset(int(offset / 32))
   computeBlockChecksum()
   oldCsum = blockDataChecksum()

   for i in range(length):
       writeBlockData((offset % 32) + i, data[i])

   newCsum = computeBlockChecksum()
   writeBlockChecksum(newCsum)

   if _userConfigControl == False:
       exitConfig(False)

   return True

# other helpful bits

def twos_comp(val, bits):
   if (val & (1 << (bits - 1))) != 0: # if sign bit is set e.g., 8bit: 128-255
       val = val - (1 << bits)        # compute negative value
   return val                         # return positive value as is

def hexdump(src, length=16):
   result = []
   digits = 2
   for i in range(0, len(src), length):
       s = src[i:i+length]
       hexa = " ".join(map("{0:0>2X}".format,s))
       text = "".join([chr(x) if 0x20 <= x < 0x7F else "." for x in s])
       result.append("%04X   %-*s   %s" % (i, length*(digits + 1), hexa, text) )
   return "\n".join(result)
Reply
#4
Hello,

I've made some improvements, however i don't understand how sleep works.

According to the datasheet it says:
"SLEEP mode is entered automatically if the feature is enabled (OpConfig [SLEEP] = 1)" however to me it seeems that bit i read only? as soon as i write 1 to it and read it back i get 0. Do you have any experience with this?

Also i think that the babysitter needs manual battery insert as it tells me:
Code:
BQ27441:
-Info:

 Firmware: 265

 Device type: 1057

 Chemical id: 296, LiCoO2 (4.2 V maximum charge)

 Sleeping: disabled

 Battery: not detected
-Voltage:

 Voltage: 4104mV
-Current:

 Average current: 0mA

 Standby current: -3mA

 Max current: -200mA
-Capacity:

 Remaining capacity: 424

 Full capacity: 442

 Nominal available capacity: 449

 Available full capacity: 467

 Remaining filtered capacity: 424

 remaining unfiltered capacity: 424

 Full filtered capacity: 442

 Full unfiltered capacity: 442

 Design capacity: 500mAh
-Power:

 Average power: 0mW
-Charge:

 Charge filtered: 96

 Charge unfiltered: 96
-Health:

 Health: 91%

 Health: 2
-Temperature:

 Internal: 27°C

 Battery: 27°C
-Battery detection:

 Manual battery insert/remove

Internal weak pullup disabled on BIN pin


Here is my code:
power.py
from machine import I2C
from micropython import const
import utime

class BQ27441:
    """Implementation of all features of the BQ27441 LiPo Fuel Gauge."""

    #: Default I2C address of the BQ27441-G1A
    BQ72441_I2C_ADDRESS = const(0x55)

    # ///////////////////////
    # // General Constants //
    # ///////////////////////

    BQ27441_UNSEAL_KEY  = const(0x8000) # Secret code to unseal the BQ27441-G1A
    BQ27441_DEVICE_ID   = const(0x0421) # Default device ID
    BQ72441_I2C_TIMEOUT = const(2000) # i2c timeout in ms

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

    BQ27441_COMMAND_CONTROL         = const(0x00) # Control()
    BQ27441_COMMAND_TEMP            = const(0x02) # Temperature()
    BQ27441_COMMAND_VOLTAGE         = const(0x04) # Voltage()
    BQ27441_COMMAND_FLAGS           = const(0x06) # Flags()
    BQ27441_COMMAND_NOM_CAPACITY    = const(0x08) # NominalAvailableCapacity()
    BQ27441_COMMAND_AVAIL_CAPACITY  = const(0x0A) # FullAvailableCapacity()
    BQ27441_COMMAND_REM_CAPACITY    = const(0x0C) # RemainingCapacity()
    BQ27441_COMMAND_FULL_CAPACITY   = const(0x0E) # FullChargeCapacity()
    BQ27441_COMMAND_AVG_CURRENT     = const(0x10) # AverageCurrent()
    BQ27441_COMMAND_STDBY_CURRENT   = const(0x12) # StandbyCurrent()
    BQ27441_COMMAND_MAX_CURRENT     = const(0x14) # MaxLoadCurrent()
    BQ27441_COMMAND_AVG_POWER       = const(0x18) # AveragePower()
    BQ27441_COMMAND_SOC             = const(0x1C) # StateOfCharge()
    BQ27441_COMMAND_INT_TEMP        = const(0x1E) # InternalTemperature()
    BQ27441_COMMAND_SOH             = const(0x20) # StateOfHealth()
    BQ27441_COMMAND_REM_CAP_UNFL    = const(0x28) # RemainingCapacityUnfiltered()
    BQ27441_COMMAND_REM_CAP_FIL     = const(0x2A) # RemainingCapacityFiltered()
    BQ27441_COMMAND_FULL_CAP_UNFL   = const(0x2C) # FullChargeCapacityUnfiltered()
    BQ27441_COMMAND_FULL_CAP_FIL    = const(0x2E) # FullChargeCapacityFiltered()
    BQ27441_COMMAND_SOC_UNFL        = const(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.

    BQ27441_CONTROL_STATUS          = const(0x00)
    BQ27441_CONTROL_DEVICE_TYPE     = const(0x01)
    BQ27441_CONTROL_FW_VERSION      = const(0x02)
    BQ27441_CONTROL_DM_CODE         = const(0x04)
    BQ27441_CONTROL_PREV_MACWRITE   = const(0x07)
    BQ27441_CONTROL_CHEM_ID         = const(0x08)
    BQ27441_CONTROL_BAT_INSERT      = const(0x0C)
    BQ27441_CONTROL_BAT_REMOVE      = const(0x0D)
    BQ27441_CONTROL_SET_HIBERNATE   = const(0x11)
    BQ27441_CONTROL_CLEAR_HIBERNATE = const(0x12)
    BQ27441_CONTROL_SET_CFGUPDATE   = const(0x13)
    BQ27441_CONTROL_SHUTDOWN_ENABLE = const(0x1B)
    BQ27441_CONTROL_SHUTDOWN        = const(0x1C)
    BQ27441_CONTROL_SEALED          = const(0x20)
    BQ27441_CONTROL_PULSE_SOC_INT   = const(0x23)
    BQ27441_CONTROL_RESET           = const(0x41)
    BQ27441_CONTROL_SOFT_RESET      = const(0x42)
    BQ27441_CONTROL_EXIT_CFGUPDATE  = const(0x43)
    BQ27441_CONTROL_EXIT_RESIM      = const(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.

    BQ27441_STATUS_SHUTDOWNEN   = const(1<<15)
    BQ27441_STATUS_WDRESET      = const(1<<14)
    BQ27441_STATUS_SS           = const(1<<13)
    BQ27441_STATUS_CALMODE      = const(1<<12)
    BQ27441_STATUS_CCA          = const(1<<11)
    BQ27441_STATUS_BCA          = const(1<<10)
    BQ27441_STATUS_QMAX_UP      = const(1<<9)
    BQ27441_STATUS_RES_UP       = const(1<<8)
    BQ27441_STATUS_INITCOMP     = const(1<<7)
    BQ27441_STATUS_HIBERNATE    = const(1<<6)
    BQ27441_STATUS_SLEEP        = const(1<<4)
    BQ27441_STATUS_LDMD         = const(1<<3)
    BQ27441_STATUS_RUP_DIS      = const(1<<2)
    BQ27441_STATUS_VOK          = const(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.

    BQ27441_FLAG_OT         = const(1<<15)
    BQ27441_FLAG_UT         = const(1<<14)
    BQ27441_FLAG_FC         = const(1<<9)
    BQ27441_FLAG_CHG        = const(1<<8)
    BQ27441_FLAG_OCVTAKEN   = const(1<<7)
    BQ27441_FLAG_ITPOR      = const(1<<5)
    BQ27441_FLAG_CFGUPMODE  = const(1<<4)
    BQ27441_FLAG_BAT_DET    = const(1<<3)
    BQ27441_FLAG_SOC1       = const(1<<2)
    BQ27441_FLAG_SOCF       = const(1<<1)
    BQ27441_FLAG_DSG        = const(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.

    BQ27441_EXTENDED_OPCONFIG   = const(0x3A) # OpConfig()
    BQ27441_EXTENDED_CAPACITY   = const(0x3C) # DesignCapacity()
    BQ27441_EXTENDED_DATACLASS  = const(0x3E) # DataClass()
    BQ27441_EXTENDED_DATABLOCK  = const(0x3F) # DataBlock()
    BQ27441_EXTENDED_BLOCKDATA  = const(0x40) # BlockData()
    BQ27441_EXTENDED_CHECKSUM   = const(0x60) # BlockDataCheckSum()
    BQ27441_EXTENDED_CONTROL    = const(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
    BQ27441_ID_SAFETY           = const(2)   # Safety
    BQ27441_ID_CHG_TERMINATION  = const(36)  # Charge Termination
    BQ27441_ID_CONFIG_DATA      = const(48)  # Data
    BQ27441_ID_DISCHARGE        = const(49)  # Discharge
    BQ27441_ID_REGISTERS        = const(64)  # Registers
    BQ27441_ID_POWER            = const(68)  # Power

    # Gas Gauging Classes
    BQ27441_ID_IT_CFG           = const(80)  # IT Cfg
    BQ27441_ID_CURRENT_THRESH   = const(81)  # Current Thresholds
    BQ27441_ID_STATE            = const(82)  # State

    # Ra Tables Classes
    BQ27441_ID_R_A_RAM          = const(89)  # R_a RAM

    # Calibration Classes
    BQ27441_ID_CALIB_DATA       = const(104) # Data
    BQ27441_ID_CC_CAL           = const(105) # CC Cal
    BQ27441_ID_CURRENT          = const(107) # Current

    # Security Classes
    BQ27441_ID_CODES            = const(112) # Codes

    # /////////////////////////////////////////
    # // OpConfig Register - Bit Definitions //
    # /////////////////////////////////////////

    # Bit positions of the OpConfig Register
    BQ27441_OPCONFIG_BIE      = const(1<<13)
    BQ27441_OPCONFIG_BI_PU_EN = const(1<<12)
    BQ27441_OPCONFIG_GPIOPOL  = const(1<<11)
    BQ27441_OPCONFIG_SLEEP    = const(1<<5)
    BQ27441_OPCONFIG_RMFCC    = const(1<<4)
    BQ27441_OPCONFIG_BATLOWEN = const(1<<2)
    BQ27441_OPCONFIG_TEMPS    = const(1<<0)

    # /////////////////////////////////////////
    # // Initialization Functions            //
    # /////////////////////////////////////////

    def __init__(self, i2c, addr=BQ72441_I2C_ADDRESS):
        self.i2c = i2c
        self.addr = addr
        self.deviceFound = False

        self._userConfigControl = True
        self._sealFlag = False

        if self.deviceType() == BQ27441_DEVICE_ID:
            self.deviceFound = True
            self.setSleepEnabled(True)

        # SLEEP mode is entered automatically if the feature is enabled (OpConfig [SLEEP] = 1) and
        # AverageCurrent() is below the programmable level Sleep Current (default = 10 mA).

        # Once entry into SLEEP mode has been qualified, but prior to entering it, the fuel gauge performs
        # an ADC autocalibration to minimize the offset.

        # During SLEEP mode, the fuel gauge remains in a very-low-power idle state and automatically
        # wakes up briefly every 20 seconds to take data measurements.

        # ---

        # HIBERNATE mode could be used when the system equipment needs to enter a very-low-power state, and
        # minimal gauge power consumption is required. This mode is ideal when system equipment is set to its
        # own HIBERNATE, SHUTDOWN, or OFF mode.


    def __str__(self):
        print("BQ27441:")

        firmwareVersion = self.firmwareVersion()
        deviceType = self.deviceType()
        chemicalId = self.chemicalId()

        chemStr = "Unknown"
        if chemicalId ==  0x0128:
            chemStr = "LiCoO2 (4.2 V maximum charge)"
        elif chemicalId ==  0x0312:
            chemStr = "LiCoO2 (4.3 to 4.35 V maximum charge)"

        sleepStr = "disabled"
        if self.isSleepEnabled():
            sleepStr = "enabled"

        battStr = "not detected"
        if self.isBatteryDetected():
            battStr = "detected"

        print("-Info:\r\n  Firmware: %d\r\n  Device type: %d\r\n  Chemical id: %d, %s\r\n  Sleeping: %s\r\n  Battery: %s" % (firmwareVersion, deviceType, chemicalId, chemStr, sleepStr, battStr))

        voltage = self.voltage()
        print("-Voltage:\r\n  Voltage: %dmV" % (voltage))

        current = self.current()
        print("-Current:\r\n  Average current: %dmA\r\n  Standby current: %dmA\r\n  Max current: %dmA" % (current["avg"], current["stdby"], current["max"]))

        capacity = self.capacity()
        print("-Capacity:\r\n  Remaining capacity: %d\r\n  Full capacity: %d\r\n  Nominal available capacity: %d\r\n  Available full capacity: %d\r\n  Remaining filtered capacity: %d\r\n  remaining unfiltered capacity: %d\r\n  Full filtered capacity: %d\r\n  Full unfiltered capacity: %d\r\n  Design capacity: %dmAh" % (capacity["rem"], capacity["full"], capacity["avail_nom"], capacity["avail_full"], capacity["remain_f"], capacity["remain_uf"], capacity["full_f"], capacity["full_uf"], capacity["design"]))

        averagePower = self.averagePower()
        print("-Power:\r\n  Average power: %dmW" % (averagePower))

        stateOfCharge = self.stateOfCharge()
        print("-Charge:\r\n  Charge filtered: %d\r\n  Charge unfiltered: %d" % (stateOfCharge["soc_f"], stateOfCharge["soc_uf"]))

        health = self.stateOfHealth()
        print("-Health:\r\n  Health: %d%%\r\n  Health: %d" % (health["soh_percent"], health["soh_status"]))

        temperature = self.temperature()
        print("-Temperature:\r\n  Internal: %d°C\r\n  Battery: %d°C" % (temperature["int"], temperature["batt"]))

        pullup = self.isWeakPullupOnBin()
        autoDet = self.isBatteryAutoDetect()

        if pullup and autoDet:
            print("-Battery detection:\r\n  Auto via pin BIN\r\n Internal weak pullup enabled on BIN pin")
        elif pullup and not autoDet:
            print("-Battery detection:\r\n  Manual battery insert/remove\r\n Internal weak pullup enabled on BIN pin")
        elif not pullup and autoDet:
            print("-Battery detection:\r\n  Auto via pin BIN\r\n Internal weak pullup disabled on BIN pin")
        elif not pullup and not autoDet:
            print("-Battery detection:\r\n  Manual battery insert/remove\r\n Internal weak pullup disabled on BIN pin")

    def setDesignCapacity(self, cap):
        """Configures the design capacity of the connected battery."""
        msb = (cap >> 8) & 0xff
        lsb = cap & 0xff
        buf = bytearray([msb,lsb])
        return self._writeExtendedData(BQ27441_ID_STATE, 10, buf)

    # /////////////////////////////////////////
    # //  Battery Characteristics Functions  //
    # /////////////////////////////////////////

    def voltage(self):
        """ Reads and returns the battery voltage"""
        return self._readWord(BQ27441_COMMAND_VOLTAGE)

    def currentAverage(self):
        """ Reads and returns the avergae current measurement"""
        return self._twosComp(self._readWord(BQ27441_COMMAND_AVG_CURRENT),16)

    def currentStandby(self):
        """ Reads and returns the standby current measurement"""
        return self._twosComp(self._readWord(BQ27441_COMMAND_STDBY_CURRENT),16)

    def currentMax(self):
        """ Reads and returns the max current measurement"""
        return self._twosComp(self._readWord(BQ27441_COMMAND_MAX_CURRENT),16)

    def current(self):
        """Reads and returns all current measurements"""
        return { "avg":self.currentAverage(), "stdby":self.currentStandby(), "max":self.currentMax() }

    def capacityRemaining(self):
        """ Reads and returns the remaining capacity measurement"""
        return self._readWord(BQ27441_COMMAND_REM_CAPACITY)

    def capacityFull(self):
        """ Reads and returns the full capacity measurement"""
        return self._readWord(BQ27441_COMMAND_FULL_CAPACITY)

    def capacityNominalAvailable(self):
        """ Reads and returns the available capacity measurement"""
        return self._readWord(BQ27441_COMMAND_NOM_CAPACITY)

    def capacityAvailableFull(self):
        """ Reads and returns the available full capacity measurement"""
        return self._readWord(BQ27441_COMMAND_AVAIL_CAPACITY)

    def capacityRemainingFiltered(self):
        """ Reads and returns the remaining filtered capacity measurement"""
        return self._readWord(BQ27441_COMMAND_REM_CAP_FIL)

    def capacityRemainingUnfiltered(self):
        """ Reads and returns the remaining unfiltered capacity measurement"""
        return self._readWord(BQ27441_COMMAND_REM_CAP_UNFL)

    def capacityFullFiltered(self):
        """ Reads and returns the full filtered capacity measurement"""
        return self._readWord(BQ27441_COMMAND_FULL_CAP_FIL)

    def capacityFullUnfiltered(self):
        """ Reads and returns the full unfiltered capacity measurement"""
        return self._readWord(BQ27441_COMMAND_FULL_CAP_UNFL)

    def capacityDesign(self):
        """ Reads and returns the design capacity measurement"""

        # msb = readExtendedData(BQ27441_ID_STATE, 10)
        # lsb = readExtendedData(BQ27441_ID_STATE, 11)
        # return (msb << 8) + lsb

        return self._readWord(BQ27441_EXTENDED_CAPACITY)

    def capacity(self):
        """ Reads and returns all capacity measurements"""
        return { "rem":self.capacityRemaining(), "full":self.capacityFull(), "avail_nom":self.capacityNominalAvailable(), "avail_full":self.capacityAvailableFull(), "remain_f":self.capacityRemainingFiltered(), "remain_uf":self.capacityRemainingUnfiltered(), "full_f":self.capacityFullFiltered(), "full_uf":self.capacityFullUnfiltered(), "design":self.capacityDesign() }

    def averagePower(self):
        """Reads and returns measured average power"""
        return self._readWord(BQ27441_COMMAND_AVG_POWER)

    def stateOfChargeFiltered(self):
        """Reads and returns filtered state of charge measurement"""
        return self._readWord(BQ27441_COMMAND_SOC)

    def stateOfChargeUnfiltered(self):
        """Reads and returns unfiltered state of charge measurement"""
        return self._readWord(BQ27441_COMMAND_SOC_UNFL)

    def stateOfCharge(self):
        """Reads and returns all state of charge measurements"""
        return { "soc_f":self.stateOfChargeFiltered(), "soc_uf":self.stateOfChargeUnfiltered() }

    def stateOfHealthPercent(self):
        """Reads and returns state of health percent measurement"""
        return (self._readWord(BQ27441_COMMAND_SOH) & 0x00FF)

    def stateOfHealthStatus(self):
        """Reads and returns state of health status measurement"""
        return (self._readWord(BQ27441_COMMAND_SOH) >> 8)

    def stateOfHealth(self):
        """Reads and returns all state of health measurements"""
        soh = self._readWord(BQ27441_COMMAND_SOH)
        return { "soh_status":(soh >> 8), "soh_percent":(soh & 0x00FF) }

    def temperatureBattery(self, celsius=True):
        """Reads and returns the specified capacity measurement"""
        kelvin = float(self._readWord(BQ27441_COMMAND_TEMP)) / 10
        if celsius:
            return kelvin - 273.15
        else:
            return kelvin

    def temperatureInternal(self, celsius=True):
        """Reads and returns internal temperature measurement"""
        kelvin = float(self._readWord(BQ27441_COMMAND_INT_TEMP)) / 10
        if celsius:
            return kelvin - 273.15
        else:
            return kelvin

    def temperature(self):
        """Reads and returns all temperature measurements"""
        return { "batt":self.temperatureBattery(), "int":self.temperatureInternal() }

    # /////////////////////////////////////////
    # // GPOUT Control Functions             //
    # /////////////////////////////////////////

    def getGPOUTPolarityStr(self):
        """Get GPOUT polarity setting (active-high or active-low)"""
        opConfig = self._opConfig()
        if (opConfig & BQ27441_OPCONFIG_GPIOPOL) == 1:
            return "active-high"
        else:
            return "active-low"

    def getGPOUTPolarity(self):
        """Get GPOUT polarity setting (active-high -> 1 or active-low -> 0)"""
        opConfig = self._opConfig()
        if (opConfig & BQ27441_OPCONFIG_GPIOPOL) == 1:
            return 1
        else:
            return 0

    def setGPOUTPolarity(self, activeHigh):
        """Set GPOUT polarity to active-high or active-low"""
        opConfig = self._opConfig()

        regActiveHigh = ((opConfig & BQ27441_OPCONFIG_GPIOPOL) == 1)

        # Check to see if we need to update opConfig:
        if ((activeHigh and regActiveHigh) or (not activeHigh and not regActiveHigh)):
            return True;

        if activeHigh:
            opConfig |= BQ27441_OPCONFIG_GPIOPOL
        else:
            opConfig &= (~ BQ27441_OPCONFIG_GPIOPOL)

        self._writeOpConfig(opConfig)

    def getGPOUTFunctionStr(void):
        """Get GPOUT function (BAT_LOW or SOC_INT)"""
        opConfig = self._opConfig()
        if (opConfig & BQ27441_OPCONFIG_BATLOWEN) == 1:
            return "batt_low"
        else:
            return "soc_int"

    def getGPOUTFunction(void):
        """Get GPOUT function (BAT_LOW or SOC_INT)"""
        opConfig = self._opConfig()
        if (opConfig & BQ27441_OPCONFIG_BATLOWEN) == 1:
            return 1
        else:
            return 0

    def setGPOUTFunction(self, function):
        """Set GPOUT function to BAT_LOW (1) or SOC_INT (0)"""
        opConfig = self._opConfig()

        regBatLow = ((opConfig & BQ27441_OPCONFIG_BATLOWEN) == 1)

        # Check to see if we need to update opConfig:
        if ((function and regBatLow) or (not function and not regBatLow)):
            return True

        # Modify BATLOWN_EN bit of opConfig:
        if function:
            opConfig |= BQ27441_OPCONFIG_BATLOWEN
        else:
            opConfig &= (~ BQ27441_OPCONFIG_BATLOWEN)

        # Write new opConfig
        return self._writeOpConfig(opConfig)

    def SOC1SetThreshold(void):
        """Get SOC1_Set Threshold - threshold to set the alert flag"""
        return self._readExtendedData(BQ27441_ID_DISCHARGE, 0)

    def SOC1ClearThreshold(void):
        """Get SOC1_Clear Threshold - threshold to clear the alert flag"""
        return self._readExtendedData(BQ27441_ID_DISCHARGE, 1)

    def setSOC1Thresholds(self, setValue, clearValue):
        """Set the SOC1 set and clear thresholds to a percentage"""

        # When StateOfCharge() falls to or below the first capacity threshold, as specified in SOC1 Set Threshold,
        # the Flags() [SOC1] bit is set. This bit is cleared once StateOfCharge() rises to or above SOC1 Clear Threshold.
        # These values are up to the user's preference.

        thresholds = bytearray(2)
        thresholds[0] = self._clamp(setValue, 0, 100)
        thresholds[1] = self._clamp(clearValue, 0, 100)
        return self._writeExtendedData(BQ27441_ID_DISCHARGE, 0, thresholds, 2)

    def SOCFSetThreshold(self):
        """Get SOCF_Set Threshold - threshold to set the alert flag"""
        return self._readExtendedData(BQ27441_ID_DISCHARGE, 2)

    def SOCFClearThreshold(self):
        """Get SOCF_Clear Threshold - threshold to clear the alert flag"""
        return self._readExtendedData(BQ27441_ID_DISCHARGE, 3)

    def setSOCFThresholds(self, set, clear):
        """Set the SOCF set and clear thresholds to a percentage"""

        # When StateOfCharge() falls to or below the final capacity threshold, as specified in SOCF Set Threshold,
        # the Flags() [SOCF] bit is set. This bit is cleared once StateOfCharge() rises to or above SOCF Clear
        # Threshold. The [SOCF] bit serves as the final discharge warning.
        # These values are up to the user's preference.

        thresholds = bytearray(2)
        thresholds[0] = self._clamp(set, 0, 100);
        thresholds[1] = self._clamp(clear, 0, 100);
        return self._writeExtendedData(BQ27441_ID_DISCHARGE, 2, thresholds, 2)

    def socFlag(self):
        """Check if the SOC1 flag is set"""
        flags = self.flags()
        if (flags & BQ27441_FLAG_SOC1) == 1:
            return True
        return False

    def socfFlag(self):
        """Check if the SOCF flag is set"""
        flags = self.flags()
        if (flags & BQ27441_FLAG_SOCF) == 1:
            return True
        return False

    def sociDelta(self):
        """Get the SOC_INT interval delta"""
        return self._readExtendedData(BQ27441_ID_STATE, 26)

    def setSOCIDelta(self, delta):
        """Set the SOC_INT interval delta to a value between 1 and 100"""
        data = bytearray(1)
        data[0] = self._clamp(delta, 0, 100);
        return self._writeExtendedData(BQ27441_ID_STATE, 26, data, 1)

    def pulseGPOUT(self):
        """Pulse the GPOUT pin - must be in SOC_INT mode"""
        return self._executeControlWord(BQ27441_CONTROL_PULSE_SOC_INT)

    # /////////////////////////////////////////
    # // Control Sub-Commands                //
    # /////////////////////////////////////////

    def isSleepEnabled(self):
        """ """
        opConfig = self._opConfig()

        if (opConfig & BQ27441_OPCONFIG_SLEEP) == 1:
            return True
        return False

    def setSleepEnabled(self, sleep):
        """ """
        opConfig = self._opConfig()
        regSleep = ((opConfig & BQ27441_OPCONFIG_SLEEP) == 1)

        # Check to see if we need to update opConfig:
        if ((sleep and regSleep) or (not sleep and not regSleep)):
            return True

        # Modify BIE bit of opConfig:
        if sleep:
            opConfig |= BQ27441_OPCONFIG_SLEEP
        else:
            opConfig &= (~ BQ27441_OPCONFIG_SLEEP)

        # Write new opConfig
        return self._writeOpConfig(opConfig)


    def isBatteryDetected(self):
        flags = self.flags()

        if (flags & BQ27441_FLAG_BAT_DET) == 1:
            return True
        return False

    def hibernate(self):
        """Request for entry into HIBERNATE from SLEEP mode"""
        return self._executeControlWord(BQ27441_CONTROL_SET_HIBERNATE)

    def hibernateClear(self):
        """Request to leave HIBERNATE mode"""
        return self._executeControlWord(BQ27441_CONTROL_CLEAR_HIBERNATE)

    def deviceType(self):
        """Read the device type - should be 0x0421"""
        return self._readControlWord(BQ27441_CONTROL_DEVICE_TYPE)

    def firmwareVersion(self):
        """Read the firmware version"""
        return self._readControlWord(BQ27441_CONTROL_FW_VERSION)

    def chemicalId(self):
        """Reports the chemical identifier of the battery profile used by the fuel gauge"""
        return self._readControlWord(BQ27441_CONTROL_CHEM_ID)

    def isBatteryAutoDetect(self):
        """Returns true if the battery autodetect feature is enabled"""
        opConfig = self._opConfig()

        if (opConfig & BQ27441_OPCONFIG_BIE) == 1:
            return True
        return False

    def setBatteryAutoDetect(self, det):
        """Sets the battery autodetect feature on or off"""

        opConfig = self._opConfig()
        regAutoDet = ((opConfig & BQ27441_OPCONFIG_BIE) == 1)

        # Check to see if we need to update opConfig:
        if ((det and regAutoDet) or (not det and not regAutoDet)):
            return True

        # Modify BIE bit of opConfig:
        if det:
            opConfig |= BQ27441_OPCONFIG_BIE
        else:
            opConfig &= (~ BQ27441_OPCONFIG_BIE)

        # Write new opConfig
        return self._writeOpConfig(opConfig)

    def batteryRemove(self):
        """Tells the fuel gauge that the battery has been disconnected"""

        # Forces the Flags() [BAT_DET] bit to clear when the battery insertion detection is disabled via OpConfig
        # [BIE] = 0. In this case, the gauge does not detect battery removal from the BIN pin logic state, but relies
        # on the BAT_REMOVE host subcommand to indicate battery removal from the system.

        return self._executeControlWord(BQ27441_CONTROL_BAT_REMOVE)

    def batteryInsert(self):
        """Tells the fuel gauge that a battery has been connected"""

        # Forces the Flags() [BAT_DET] bit to set when the battery insertion detection is disabled via OpConfig
        # [BIE] = 0. In this case, the gauge does not detect battery insertion from the BIN pin logic state, but relies
        # on the BAT_INSERT host subcommand to indicate battery presence in the system.

        return self._executeControlWord(BQ27441_CONTROL_BAT_INSERT)

    def isWeakPullupOnBin(self):
        """Internal weak pullup resistor on the BIN pin. If disabled then an external pullup resistor is expected"""
        opConfig = self._opConfig()

        if (opConfig & BQ27441_OPCONFIG_BI_PU_EN) == 1:
            return True
        return False

    def setWeakPullupOnBin(self, pullup):

        opConfig = self._opConfig()
        regPullup = ((opConfig & BQ27441_OPCONFIG_BI_PU_EN) == 1)

        # Check to see if we need to update opConfig:
        if ((pullup and regPullup) or (not pullup and not regPullup)):
            return True

        # Modify BIE bit of opConfig:
        if pullup:
            opConfig |= BQ27441_OPCONFIG_BI_PU_EN
        else:
            opConfig &= (~ BQ27441_OPCONFIG_BI_PU_EN)

        # Write new opConfig
        return self._writeOpConfig(opConfig)

    def enterConfig(self, userControl):
        """Enter configuration mode - set userControl if calling from an Arduino sketch and you want control over when to exitConfig"""
        if userControl:
            self._userConfigControl = True;

        if(self._sealed()):
            self._sealFlag = True;
            self._unseal()

        if self._executeControlWord(BQ27441_CONTROL_SET_CFGUPDATE):
            timeout = BQ72441_I2C_TIMEOUT
            while (timeout > 0) and ((self.flags() & BQ27441_FLAG_CFGUPMODE) == 0):
                timeout -= 1
                utime.sleep_ms(1)

            if timeout > 0:
                return True
           
        return False

    def exitConfig(self, resim):
        """Exit configuration mode with the option to perform a resimulation"""

        # 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 self.softReset():
                timeout = BQ72441_I2C_TIMEOUT
                while (timeout > 0) and ((self.flags() & BQ27441_FLAG_CFGUPMODE) == 0):
                    timeout -= 1
                    utime.sleep_ms(1)
                if timeout > 0:
                    if self._sealFlag:
                        self._seal()
                    return True
            return False
        else:
            return self._executeControlWord(BQ27441_CONTROL_EXIT_CFGUPDATE)
    
    def flags(self):
        """Read the flags() command"""
        return self._readWord(BQ27441_COMMAND_FLAGS)

    def status(self):
        """Read the CONTROL_STATUS subcommand of control()"""
        return self._readControlWord(BQ27441_CONTROL_STATUS)

    # /////////////////////////////////////////
    # // Private Functions                   //
    # /////////////////////////////////////////

    def _sealed(self):
        """Check if the BQ27441-G1A is sealed or not."""
        stat = self.status()
        retval = False
        if(stat & BQ27441_STATUS_SS) > 0:
            retval = True
        return retval

    def _seal(self):
        """Seal the BQ27441-G1A"""
        return self._readControlWord(BQ27441_CONTROL_SEALED) > 0

    def _unseal(self):
        """Unseal the BQ27441-G1A""" 
        # To unseal the BQ27441, write the key to the control
        # command. Then immediately write the same key to control again.
        self._executeControlWord(BQ27441_UNSEAL_KEY)
        self._executeControlWord(BQ27441_UNSEAL_KEY)
        return True

    def _opConfig(self):
        """ Read the 16-bit opConfig register from extended data"""
        return self._readWord(BQ27441_EXTENDED_OPCONFIG)

    def _writeOpConfig(self, value):
        """Write the 16-bit opConfig register in extended data"""
        msb = (value >> 8) & 0xff
        lsb = value & 0xff
        buf = bytearray([msb,lsb])
        return self._writeExtendedData(BQ27441_ID_REGISTERS, 0, buf)

    def _softReset(self):
        """Issue a soft-reset to the BQ27441-G1A"""
        return self._executeControlWord(BQ27441_CONTROL_SOFT_RESET)

    def _reset(self):
        """Issue a hard-reset to the BQ27441-G1A"""
        return self._executeControlWord(BQ27441_CONTROL_RESET)

    def _readWord(self, commandWord):
        """Read a 16-bit command word from the BQ27441-G1A"""
        x = self._i2cReadBytes(commandWord,2)
        return ((x[1]<<8)&0xff00)|(x[0]&0xff)

    def _readControlWord(self, controlWord):
        """Read a 16-bit subcommand() from the BQ27441-G1A's control()"""
        msb = (controlWord >> 8) & 0xff
        lsb = controlWord & 0xff
        command = bytearray([lsb, msb])
        self._i2cWriteBytes(0, command)
        x = self._i2cReadBytes(0, 2)
        return ((x[1]<<8)&0xff00)|(x[0]&0xff)

    def _executeControlWord(self, func):
        """Execute a subcommand() from the BQ27441-G1A's control()"""
        msb = (func >> 8) & 0xff
        lsb = func & 0xff
        buf = bytearray([lsb,msb])
        self._i2cWriteBytes(0,buf)
        return True

    # /////////////////////////////////////////
    # //  Extended Data Commands             //
    # /////////////////////////////////////////

    def _blockDataControl(self):
        """Issue a BlockDataControl() command to enable BlockData access"""
        buf = bytearray([0])
        self._i2cWriteBytes(BQ27441_EXTENDED_CONTROL, buf)
        return True

    def _blockDataClass(self, id):
        """Issue a DataClass() command to set the data class to be accessed"""
        buf = bytearray([id])
        self._i2cWriteBytes(BQ27441_EXTENDED_DATACLASS, buf)
        return True

    def _blockDataOffset(self, offset):
        """Issue a DataBlock() command to set the data block to be accessed"""
        buf = bytearray([offset])
        self._i2cWriteBytes(BQ27441_EXTENDED_DATABLOCK, buf)
        return True

    def _blockDataChecksum(self):
        """Read the current checksum using BlockDataCheckSum()"""
        x = self._i2cReadBytes(BQ27441_EXTENDED_CHECKSUM, 1)
        return x[0]

    def _readBlockData(self, offset):
        """Use BlockData() to read a byte from the loaded extended data"""
        address = offset + BQ27441_EXTENDED_BLOCKDATA
        x = self._i2cReadBytes(address, 1)
        return x[0]

    def _writeBlockData(self, offset, data):
        """Use BlockData() to write a byte to an offset of the loaded data"""
        address = offset + BQ27441_EXTENDED_BLOCKDATA
        buf = bytearray([data])
        return self._i2cWriteBytes(address, buf)

    def _computeBlockChecksum(self):
        """Read all 32 bytes of the loaded extended data and compute a checksum based on the values."""
        x = self._i2cReadBytes(BQ27441_EXTENDED_BLOCKDATA, 32)
        csum = 0
        for i in range(32):
           csum = csum + x[i]
           csum = csum & 0xff
        csum = 255 - csum
        return csum

    def _writeBlockChecksum(self, csum):
        """Use the BlockDataCheckSum() command to write a checksum value"""
        buf = bytearray([csum])
        return self._i2cWriteBytes(BQ27441_EXTENDED_CHECKSUM, buf)

    def _readExtendedData(self, classID, offset):
        if self._userConfigControl:
            self.enterConfig(False)

        if self._blockDataControl() == False:
            return False
        if self._blockDataClass(classID) == False:
            return False

        self._blockDataOffset(int(offset / 32))
        self._computeBlockChecksum()
        oldCsum = self._blockDataChecksum()
        retData = self._readBlockData(offset % 32)

        if self._userConfigControl == False:
            self.exitConfig(False)

        return retData

    def _writeExtendedData(self, classID, offset, data):
        length = len(data)

        if length > 32:
            return False

        if self._userConfigControl:
            self.enterConfig(False)

        if self._blockDataControl() == False:
            return False
        if self._blockDataClass(classID) == False:
            return False

        self._blockDataOffset(int(offset / 32))
        self._computeBlockChecksum()
        oldCsum = self._blockDataChecksum()

        for i in range(length):
            self._writeBlockData((offset % 32) + i, data[i])

        newCsum = self._computeBlockChecksum()
        self._writeBlockChecksum(newCsum)

        if self._userConfigControl == False:
            self.exitConfig(False)

        return True

    # /////////////////////////////////////////
    # //  I2C Read and Write Routines        //
    # /////////////////////////////////////////

    def _i2cReadBytes(self, subaddress, count):
        """Read a specified number of bytes over I2C at a given subAddress"""
        buf = bytearray([subaddress])
        self.i2c.writeto(self.addr, buf)
        x = self.i2c.readfrom(self.addr, count)
        return x

    def _i2cWriteBytes(self, subaddress, src):
        """Write a number of bytes over I2C to a given subAddress"""
        buf = bytearray([subaddress])
        buf.extend(src)
        self.i2c.writeto(self.addr, buf)

    # /////////////////////////////////////////
    # //  Other helpful bits                 //
    # /////////////////////////////////////////

    def _clamp(n, minn, maxn):
        return max(min(maxn, n), minn)

    def _twosComp(self, val, bits):
        if (val & (1 << (bits - 1))) != 0: # if sign bit is set e.g., 8bit: 128-255
            val = val - (1 << bits)        # compute negative value
        return val                         # return positive value as is

    def _hexdump(self, src, length=16):
        result = []
        digits = 2
        for i in range(0, len(src), length):
            s = src[i:i+length]
            hexa = " ".join(map("{0:0>2X}".format,s))
            text = "".join([chr(x) if 0x20 <= x < 0x7F else "." for x in s])
            result.append("%04X   %-*s   %s" % (i, length*(digits + 1), hexa, text) )
        return "\n".join(result)


boot .py:
from machine import I2C
from fpioa_manager import fm
from power import BQ27441

i2c = I2C(I2C.I2C0, freq=400000, scl=40, sda=41)
# print(i2c.scan())

pwr = BQ27441(i2c)

print(pwr)

Reply
#5
Thank you for posting this code. I am using the SparkFun Babysitter battery manager on a Pi Zero W board under Python3. Any suggestions on using your code there to talk with the Babysitter?

Thank you,
Chuck
Reply


Forum Jump:


Users browsing this thread: 1 Guest(s)