Robert Fletcher
Published

A BQ9003 Smart Battery Monitor with USB-SMBus and Python

Turn a TI BQ9003-based intelligent battery into a useful engineering data source. This project uses a CP2112 USB-to-SMBus bridge and Python.

IntermediateWork in progress36
A BQ9003 Smart Battery Monitor with USB-SMBus and Python

Things used in this project

Hardware components

TI BQ9003
Battery/BMS under test
×1
Silicon Labs CP2112
USB-to-SMBus/IΒ²C bridge
×1
USB cable
PC-to-CP2112 connection
×1
Logic-level pull-up resistors
SMBus SDA/SCL pull-ups if not already present
×1
10 kΞ© resistors
Example SMBus pull-ups
×2
100 nF ceramic capacitor
CP2112 local bypass
×1
Battery connector
Non-invasive access to SDA, SCL and GND
×1
Fuse or current-limited laboratory supply
Additional protection when using an accessible battery fixture
×1
Oscilloscope
Optional SMBus waveform verification
×1

Software apps and online services

Python 3
Python HID library
A CP2112-compatible HID driver
VS Code
Saleae Logic
GitHub

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)
Fine-tip soldering station
Digital multimeter
Wire stripper
Flush cutter
Tweezers
Heat-shrink tubing
Small screwdriver set
3D Printer (generic)
3D Printer (generic)
PCB assembly service

Story

Read more

Schematics

functional connection diagram

SMBus pull-ups

Recommended connector arrangement

Code

Python code

Python
import time
import csv
from datetime import datetime

# ---------------------------------------------------------
# Smart Battery System command codes
# ---------------------------------------------------------

CMD_TEMPERATURE       = 0x08
CMD_VOLTAGE           = 0x09
CMD_CURRENT           = 0x0A
CMD_RELATIVE_SOC      = 0x0D
CMD_REMAINING_CAP     = 0x0F
CMD_FULL_CHARGE_CAP   = 0x10
CMD_BATTERY_STATUS    = 0x16
CMD_CYCLE_COUNT       = 0x17
CMD_DESIGN_CAPACITY   = 0x18
CMD_DESIGN_VOLTAGE    = 0x19

# Typical Smart Battery 7-bit address.
# Verify the actual address used by your battery.
BATTERY_ADDRESS = 0x0B


def read_word(bus, address, command):
    """
    Read one SMBus word.

    Replace this function with the API provided by the
    CP2112 Python library being used.
    """
    data = bus.read_word_data(address, command)

    # SMBus data is commonly returned LSB first.
    return data


def decode_temperature(raw):
    """
    SBS Temperature() is normally reported in 0.1 K.
    """
    return (raw * 0.1) - 273.15


def decode_voltage(raw):
    """
    Voltage() is normally reported in mV.
    """
    return raw / 1000.0


def decode_capacity(raw):
    """
    RemainingCapacity() and FullChargeCapacity()
    are normally reported in mAh.
    """
    return raw


def read_battery(bus):

    data = {}

    data["temperature_C"] = decode_temperature(
        read_word(bus, BATTERY_ADDRESS, CMD_TEMPERATURE)
    )

    data["voltage_V"] = decode_voltage(
        read_word(bus, BATTERY_ADDRESS, CMD_VOLTAGE)
    )

    data["soc_percent"] = read_word(
        bus, BATTERY_ADDRESS, CMD_RELATIVE_SOC
    )

    data["remaining_mAh"] = decode_capacity(
        read_word(bus, BATTERY_ADDRESS, CMD_REMAINING_CAP)
    )

    data["full_charge_mAh"] = decode_capacity(
        read_word(bus, BATTERY_ADDRESS, CMD_FULL_CHARGE_CAP)
    )

    data["battery_status"] = read_word(
        bus, BATTERY_ADDRESS, CMD_BATTERY_STATUS
    )

    data["cycle_count"] = read_word(
        bus, BATTERY_ADDRESS, CMD_CYCLE_COUNT
    )

    data["design_capacity_mAh"] = read_word(
        bus, BATTERY_ADDRESS, CMD_DESIGN_CAPACITY
    )

    data["design_voltage_V"] = (
        read_word(bus, BATTERY_ADDRESS, CMD_DESIGN_VOLTAGE)
        / 1000.0
    )

    return data


def save_log(filename, data):

    with open(filename, "a", newline="") as f:

        writer = csv.writer(f)

        writer.writerow([
            datetime.now().isoformat(),
            data["temperature_C"],
            data["voltage_V"],
            data["soc_percent"],
            data["remaining_mAh"],
            data["full_charge_mAh"],
            data["battery_status"],
            data["cycle_count"]
        ])


# ---------------------------------------------------------
# Main loop
# ---------------------------------------------------------

def main(bus):

    print("BQ9003 Smart Battery Monitor")
    print("Read-only SMBus telemetry")
    print("--------------------------------")

    while True:

        try:

            battery = read_battery(bus)

            print(
                f"Voltage:     {battery['voltage_V']:.3f} V"
            )

            print(
                f"Temperature: {battery['temperature_C']:.1f} Β°C"
            )

            print(
                f"State:       {battery['soc_percent']} %"
            )

            print(
                f"Remaining:   {battery['remaining_mAh']} mAh"
            )

            print(
                f"Full charge: {battery['full_charge_mAh']} mAh"
            )

            print(
                f"Cycles:      {battery['cycle_count']}"
            )

            print(
                f"Status:      0x{battery['battery_status']:04X}"
            )

            print("--------------------------------")

            save_log("battery_log.csv", battery)

        except Exception as e:

            print("SMBus read error:", e)

        time.sleep(2)


if __name__ == "__main__":

    # Initialize the CP2112-specific SMBus interface here.
    #
    # Example:
    #
    # bus = CP2112(...)
    # main(bus)
    #
    # The exact initialization depends on the CP2112
    # Python library used by the project.
    pass

Credits

Robert Fletcher
19 projects β€’ 1 follower
Senior semiconductor engineer at AVAQ Semiconductor. With extensive experience in electronic components, supply chain solutions & sourcing.

Comments