LiPo Fuel Gauge (MAX1704X) with Python

I purchased a few of these LiPo fuel gauges and got one hooked up physically using I2C. I’m using a raspberry pi zero however, and am having trouble translating between the examples given for arduino and in circuit python and others on the hookup guide page into “normal” python on the pi. Also, please forgive me, I am also very new to python in general so I’m often making guesses on how to adapt example code and obvious differences to others between circuit python on an ESP32 and python on a pi are not easy for me to spot just yet.

I believe I am close, but because of my lack of understanding with the “registers” where data is stored in the fuel gauge, I’m at a dead end. I found an example in circuitpython that had this code after init and all that:

print(f"Battery voltage: {max17.cell_voltage:.2f} Volts")
print(f"Battery state  : {max17.cell_percent:.1f} %")

After some experimentation, hex was as close as I could get to “working” like this:

cell_voltage = 0x2F
voltage = fg.read_byte_data(address, cell_voltage)
print("volt",voltage)

This “works” in that I get no errors, but I only get a value of 255 for that particular address. So I “walked” the addresses from 0 to 100 and found a few “interesting” addresses.

Below is what I currently have that is “working” in that I am getting values from the fuel gauge, but only one of these values seems to make any sense. The value in reference 5 appears to be the battery percentage as it steadily decreases when I have the pi running on battery. The value starts well over 100 though, and seems to fall quite rapidly at least at the end going from 20-0 in a matter of minutes so if this is a pure percent, I suspect I am missing a calibration step.

In the code below, the “registers” list contains the places I found values other than 255. The “registers2” list contains the places I found values that change basically each time I read them. And it seems to make no difference if I use the hex or decimal number when pulling these values.

In another forum post I found someone’s example code that seems to indicate that the voltage is stored in 1.25mV increments so the value given is modified like this: volt=x1/800. That math does not seem to clear any of the values up that I am getting (i.e. 167, 112, or 164). For that math to make sense I would need a value of around 2000-3000 and I’ve only seen a max of 255 so far. Another part of the example has this formula volt=x5/4096. Again, would need to start with a value in the 2000-3000 range.

Any help you can provide would be much appreciated. I feel like I have the percent value, but I’m missing a calibration step for that one and I’m missing the correct locations to probe for voltages along with how to translate those values to voltages.

I’m also questioning if the fuel gauge itself is causing a drain on the battery while I’m not reading from it and if so, I’ve seen some hibernation references so should I be putting the fuel gauge into hibernation mode between readings?

#!/usr/bin/python3
import smbus
import time

address = 0x36

fg = smbus.SMBus(1)

registers = [2,3,4,5,6,7,8,9,12,13,21,52,53,54,55,60,61,62,63]
registers2 = [2,3,5,21]

for x in range (0,4):
  for x in registers2:
    var = fg.read_byte_data(address,x)
    print(x,var)
    print('---')
  print('--- ---')
  time.sleep(2)

Hookup guide page:

https://learn.sparkfun.com/tutorials/li … okup-guide

Circuitpython guide from Adafruit for a different device with the same chip:

https://learn.adafruit.com/adafruit-max … cuitpython

The library for that similar Adafruit device:

https://github.com/adafruit/Adafruit_Ci … pletest.py

I think, you have to modify your voltage conversion formula. You may have to take the VCELL Register (0x02),SOC Register (0x04) into account. I am not sure if you have already included in your code. I can see two register arrays in your code though. https://www.analog.com/media/en/technic … x17049.pdf

aliarifat794:
I think, you have to modify your voltage conversion formula. You may have to take the VCELL Register (0x02),SOC Register (0x04) into account. I am not sure if you have already included in your code. I can see two register arrays in your code though. https://www.analog.com/media/en/technic … x17049.pdf

Thank you!

This helped me get to the bottom of things for the most part. This document isn’t for the model of chip I have, since I have the 17043, but I was able to find the correct document with this lead and they are pretty similar regardless.

The example code I found used “read_byte_data” instead of “read_word_data” and did not include value conversions.

I now have successful data reading from the fuel gauge so I’m sharing the code below for anyone else who might need it. The SOC value is still a bit curious, but it will probably serve my purpose fine. The do says the register is based on a 0-5v range, but I am using a 3.7v battery that actually reads as 4.0v when fully charged. It is also a multi-cell battery when the fuel gauge mentions being for a single cell.

The result for me currently is that a fully charged battery read as 4.06v and 88.96% SOC. 30 minutes later it read 4.02v and 84.34%. I am going to run a few cycles to see where things end up, but I suspect I will be able to translate the SOC once I have a range of values from actual battery life. Simple math like reading the 88.96% initial value as a percent of 5v would math out to 4.45v, so it’s not that straight forward. As long as I can get at least a pretty good estimate of battery status, that’ll be good enough for my purposes.

The only really troubling part at the moment is that the initial progression of SOC would indicate a 12.5 hour battery life. Previous runs without any battery checking circuitry were 28 hours for this device, load, and battery combo. If the battery lifespan is affected this much by checking it’s SOC, then I’ll get over seeing the SOC real quick.

#!/usr/bin/python3
import smbus2

FG_ADDRESS = 0x36
REGISTER_VCELL = 0x02
REGISTER_SOC = 0x04

bus = smbus2.SMBus(1)

data = bus.read_i2c_block_data(FG_ADDRESS, REGISTER_VCELL, 2)
value = (data[0] << 8) | data[1]
volts=str("%.2f" % round(((value>>4)*0.00125),2) )
print("volts: ",volts)

data = bus.read_i2c_block_data(FG_ADDRESS, REGISTER_SOC, 2)
value = (data[0] << 8) | data[1]
soc=str("%.2f" % round((value/256),2) )
print("soc: ",soc)

bus.close()