EV2300 — USB-to-I2C Adapter¶
Controls Texas Instruments EV2300A USB-to-SMBus/I2C adapters for communicating with battery fuel gauges, charger ICs, and other I2C peripherals.
- The EV2300 is auto-detected via USB HID — no VISA or NI-VISA required.
- Addresses and registers accept hex (
0x08) or decimal (8) notation. - 16-bit word reads/writes use big-endian wire order (high byte at the lower register address). This matches the BQ76920/BQ76940 register-pair convention (e.g.
VC1_HIat0x0C,VC1_LOat0x0D). To update a single 8-bit register, usewrite_byte-- awrite_wordalways writes TWO consecutive registers.
USB HID adapter for SMBus/I2C communication. VID 0x0451, PID 0x0036. Pure-Python driver — no TI drivers or bqStudio required for basic register access.
ev2300 info¶
Show device identification: VID, PID, serial number, product name, manufacturer, and firmware version.
vid : 0x0451
pid : 0x0036
serial : HPA02 FW:2.0a/TUSB3210:0
product : EV2300A
manufacturer : Texas Inst
version : 0x0002
ev2300 read_word¶
Read a 16-bit little-endian word from an I2C device register.
| Parameter | Required | Values | Description |
|---|---|---|---|
i2c_addr |
required | 0x00 – 0x7F | 7-bit I2C slave address. |
register |
required | 0x00 – 0xFF | Register (command code) to read. |
ev2300 read_word 0x08 0x0C # cell 1 voltage on a BQ76920 (VC1_HI/LO)
ev2300 read_word 0x08 0x2A # pack voltage (BAT_HI/LO)
ev2300 read_word 0x08 0x32 # coulomb counter (CC_HI/LO)
ev2300 read_word 8 12 # same as 0x08 0x0C in decimal
Returns the value in both hex and decimal: 0x24BB (9403) — raw 14-bit ADC for ~3.51 V cell at GAIN=369 uV/LSB.
ev2300 write_word¶
Write a 16-bit little-endian word to an I2C device register.
| Parameter | Required | Values | Description |
|---|---|---|---|
i2c_addr |
required | 0x00 – 0x7F | 7-bit I2C slave address. |
register |
required | 0x00 – 0xFF | Register (command code) to write. |
value |
required | 0x0000 – 0xFFFF | 16-bit value to write. |
ev2300 write_word 0x08 0x09 0xAC97 # atomic write: OV_TRIP=0xAC, UV_TRIP=0x97
# (high byte 0xAC -> reg 0x09, low byte 0x97 -> reg 0x0A)
write_word always touches TWO consecutive registers
write_word 0x08 0x0A 0x0097 looks like "write 0x97 to UV_TRIP", but it actually writes the high byte 0x00 to UV_TRIP and the low byte 0x97 to CC_CFG (0x0B) -- corrupting the CC_CFG=0x19 init value that BQ76920 datasheet SLUSBK2I Section 8.5 Register Maps requires ("CC_CFG ... Must be programmed to 0x19"). To set a single 8-bit threshold register, always use write_byte:
ev2300 read_byte¶
Read a single byte from an I2C device register.
| Parameter | Required | Values | Description |
|---|---|---|---|
i2c_addr |
required | 0x00 – 0x7F | 7-bit I2C slave address. |
register |
required | 0x00 – 0xFF | Register to read. |
ev2300 read_byte 0x08 0x00 # SYS_STAT (alert flags)
ev2300 read_byte 0x08 0x04 # SYS_CTRL1 (ADC_EN bit 4)
ev2300 read_byte 0x08 0x0B # CC_CFG (must be 0x19 after init)
ev2300 read_byte 0x08 0x50 # ADCGAIN1 (factory ADC gain trim)
Returns the value in hex and decimal: 0x19 (25) — confirms BQ76920 init wrote CC_CFG correctly.
ev2300 write_byte¶
Write a single byte to an I2C device register.
| Parameter | Required | Values | Description |
|---|---|---|---|
i2c_addr |
required | 0x00 – 0x7F | 7-bit I2C slave address. |
register |
required | 0x00 – 0xFF | Register to write. |
value |
required | 0x00 – 0xFF | Byte value to write. |
ev2300 write_byte 0x08 0x0B 0x19 # CC_CFG := 0x19 (datasheet-required init)
ev2300 write_byte 0x08 0x05 0x40 # SYS_CTRL2 := CC_EN (bit 6)
ev2300 write_byte 0x08 0x00 0xFF # clear all SYS_STAT fault bits (write-1-to-clear)
SYS_STAT (register 0x00) is write-1-to-clear, NOT normal R/W
write_byte 0x08 0x00 0x80 does NOT replace SYS_STAT with 0x80. Per
the BQ76920 datasheet (SLUSBK2I, section 8.5.1): "Bits in SYS_STAT may
be cleared by writing a '1' to the corresponding bit. Writing a '0'
does not change the state of the corresponding bit."
So writing 0x80 clears only bit 7 (CC_READY) and leaves every other
bit untouched. Any latched fault (OV at bit 2, UV at bit 3,
DEVICE_XREADY at bit 5, SCD at bit 1, OCD at bit 0) stays latched.
Prefer the dedicated helpers ev2300 sys_stat and
ev2300 clear_faults instead of raw write_byte for this register
-- see below.
ev2300 sys_stat¶
Read SYS_STAT (register 0x00) and decode each bit so you can see exactly which faults are latched instead of trying to interpret an opaque hex value yourself.
| Parameter | Required | Default | Description |
|---|---|---|---|
i2c_addr |
optional | 0x08 |
BQ76920 7-bit I2C address. |
Example output after triggering overvoltage:
SYS_STAT (0x00) = 0x84 [10000100]
bits set: CC_READY + OV
latched faults: OV
-> run 'ev2300 clear_faults' once the underlying condition is gone
Bit map (datasheet Table 8-3, p. 30):
| Bit | Name | Latched? | Meaning |
|---|---|---|---|
| 7 | CC_READY |
no (auto-re-asserts) | Coulomb counter reading ready |
| 6 | RSVD | - | Reserved |
| 5 | DEVICE_XREADY |
yes | Internal chip fault detected |
| 4 | OVRD_ALERT |
yes | External ALERT pin override active |
| 3 | UV |
yes | Undervoltage fault detected |
| 2 | OV |
yes | Overvoltage fault detected |
| 1 | SCD |
yes | Short-circuit in discharge fault |
| 0 | OCD |
yes | Overcurrent in discharge fault |
ev2300 clear_faults¶
Clear latched faults in SYS_STAT via write-1-to-clear semantics.
| Parameter | Required | Default | Description |
|---|---|---|---|
i2c_addr |
optional | 0x08 |
BQ76920 7-bit I2C address. |
mask |
optional | 0xFF |
Bits to clear; default clears all. |
The command writes mask to register 0x00 (W1C clears every bit set to 1
in the mask) and then re-reads SYS_STAT so you immediately see whether
any fault re-latched. A fault bit will re-latch if the underlying
protection condition is still true -- e.g. OV stays set while the cell
voltage is above OV_TRIP (register 0x09).
ev2300 clear_faults # clear everything (mask 0xFF)
ev2300 clear_faults 0x08 0x04 # clear only OV (bit 2)
ev2300 clear_faults 0x18 # clear all on alternate BQ address
Example output after an overvoltage was cleared by lowering the supply:
Wrote 0xFF to SYS_STAT (write-1-to-clear; bits set in mask are cleared)
SYS_STAT before: 0x84 -> after: 0x00
cleared: OV
If the underlying overvoltage condition is still active when you clear, the command tells you explicitly so you don't think the write failed:
SYS_STAT before: 0x84 -> after: 0x04
STILL active: OV (underlying condition still true -- check cell
voltages / OV_TRIP / UV_TRIP)
Issue #128 -- hardware-verified reproduction and fix
A student wrote 0x80 to SYS_STAT after triggering OV with a 25 V
supply, expecting that to "overwrite" the fault bit. The fault stayed
latched. Live bench trace (HP E3631A P25V channel + BQ76920 EVM +
EV2300):
baseline (PSU=18 V) SYS_STAT=0x00
after psu_set_voltage 25 V SYS_STAT=0x80 bits=CC_READY
after psu_set_voltage 18 V (OV latched) SYS_STAT=0x84 bits=CC_READY+OV faults=OV
after wrong write_byte 0x80 SYS_STAT=0x04 bits=OV faults=OV
after ev2300_clear_bq_faults SYS_STAT=0x00 bits=(none) faults=(none)
The write_byte 0x80 step is the one that confused the student: SYS_STAT
dropped from 0x84 to 0x04, not because the OV bit changed, but
because the CC_READY bit (bit 7) cleared and OV (bit 2) stayed exactly
where it was. The fix is ev2300 clear_faults, which writes 0xFF
so every set bit gets cleared by the W1C semantics in one shot.
ev2300 read_block¶
Read a variable-length block of bytes from an I2C device register (SMBus Block Read).
| Parameter | Required | Values | Description |
|---|---|---|---|
i2c_addr |
required | 0x00 – 0x7F | 7-bit I2C slave address. |
register |
required | 0x00 – 0xFF | Register to read. |
Returns the block as hex bytes: [2 bytes] 00 00
read_block returns 2 bytes by default on this driver
The toolkit's read_block does not include an explicit byte-count in the request, so the bridge defaults to a 2-byte transfer. To read more bytes (e.g. 6 consecutive registers PROTECT1..CC_CFG), issue six read_byte calls -- the BQ76920 auto-increments the register pointer per byte (datasheet SLUSBK2I Section 8.3.1.4 Communications Subsystem).
Tip
Block reads are commonly used to retrieve string registers. The returned bytes are raw — interpret them as ASCII if reading a text field.
ev2300 write_block¶
Write a variable-length block of bytes to an I2C device register (SMBus Block Write).
| Parameter | Required | Values | Description |
|---|---|---|---|
i2c_addr |
required | 0x00 – 0x7F | 7-bit I2C slave address. |
register |
required | 0x00 – 0xFF | Register to write. |
hex_bytes |
required | hex string | Data bytes as a hex string (no 0x prefix, no spaces). |
ev2300 write_block 0x55 0x44 0203 # write two bytes [0x02, 0x03] to register 0x44
ev2300 write_block 0x55 0x00 AABB # write two bytes [0xAA, 0xBB] to register 0x00
Note
The hex string must be an even number of characters. Each pair of characters is one byte: AABB = [0xAA, 0xBB].
ev2300 send_byte¶
Send a single command byte to an I2C address with no register or data (SMBus Send Byte protocol).
| Parameter | Required | Values | Description |
|---|---|---|---|
i2c_addr |
required | 0x00 – 0x7F | 7-bit I2C slave address. |
command |
required | 0x00 – 0xFF | Command byte to send. |
ev2300 scan¶
Probe all 256 registers (0x00 – 0xFF) on a given I2C address using word reads, and list every register that responds successfully.
| Parameter | Required | Values | Description |
|---|---|---|---|
i2c_addr |
required | 0x00 – 0x7F | 7-bit I2C slave address to scan. |
Prints every readable register with its value:
Found 38 readable registers:
0x00: 0x0080 (128) # SYS_STAT (CC_READY high)
0x04: 0x0010 (16) # SYS_CTRL1 (ADC_EN)
0x05: 0x0040 (64) # SYS_CTRL2 (CC_EN)
0x09: 0x00AC (172) # OV_TRIP
0x0A: 0x0097 (151) # UV_TRIP
0x0B: 0x0019 (25) # CC_CFG
0x0C: 0x24BB (9403) # VC1_HI/LO (cell 1 ~3.51 V)
0x2A: 0x2DEC (11756) # BAT_HI/LO (pack ~17.59 V)
...
Warning
This sends 256 read transactions, which takes several seconds. Some devices may respond poorly to reads on undefined registers — use with caution on unfamiliar hardware.
ev2300 probe¶
Send an arbitrary HID command code for low-level debugging and reverse engineering. This bypasses the standard read/write helpers and sends a raw EV2300 protocol packet.
| Parameter | Required | Values | Description |
|---|---|---|---|
cmd_code |
required | 0x00 – 0xFF | EV2300 protocol command code. |
i2c_addr |
optional | 0x00 – 0x7F | I2C address (default: 0x00). |
register |
optional | 0x00 – 0xFF | Register (default: 0x00). |
ev2300 probe 0x01 0x08 0x0B # manual read_word of CC_CFG
ev2300 probe 0x31 # firmware version (BQ76920_Bridge extension cmd)
Shows the raw response: 0x01: resp=0x41 [AA 41 06 2C 00 E4 0C ...]
Note
cmd 0x31 (BRIDGE_CMD_GET_VERSION) returns ASCII MAJOR.MINOR.PATCH+githash — only on the open-source BQ76920_Bridge replacement, not real EV2300 hardware.
Note
Standard command codes: 0x01 = read word, 0x02 = read block, 0x03 = read byte, 0x04 = write word, 0x05 = write block, 0x06 = send byte, 0x07 = write byte.
ev2300 state¶
| Value | Effect |
|---|---|
on |
No-op (EV2300 has no output to enable) |
off |
No-op |
safe |
No-op — EV2300 is a passive adapter |
reset |
Disconnect and reconnect the USB HID session |
BQ76920 quickstart¶
End-to-end flow for ESET 453 lab 5 with the BQ76920 EVM (HPA002, default I2C address 0x08):
# 1. Make sure the EVM is powered (PSU at 18 V on BAT+/BAT-) and the
# cell-sim DIP switches are CLOSED, otherwise UV trips immediately.
psu set 2 18.0 0.5
psu chan 2 on
# 2. Wait for the BQ to ACK -- press the BOOT button on the EVM during this
# window. The BQ76920 stays in SHIP mode after every POR event until the
# TS1 pin is pulsed above V_BOOT (datasheet SLUSBK2I sec 8.4.2 SHIP Mode).
ev2300 wait_for_bq 30
# 3. Verify init -- CC_CFG MUST read 0x19 (datasheet SLUSBK2I sec 8.5
# Register Maps: "CC_CFG ... Must be programmed to 0x19"). If it
# reads 0x00, see the troubleshooting page (sticky-init bug workaround).
ev2300 read_byte 0x08 0x0B
# 4. Read live measurements
ev2300 read_word 0x08 0x0C # cell 1 voltage
ev2300 read_word 0x08 0x2A # pack voltage
ev2300 read_byte 0x08 0x00 # SYS_STAT (any latched faults)
Expected values with the cell-sim divider closed and 18 V on BAT+:
| Register | Value | Meaning |
|---|---|---|
0x0B CC_CFG |
0x19 |
datasheet-required init value (host or bridge must write) |
0x04 SYS_CTRL1 |
0x10 |
ADC_EN set |
0x05 SYS_CTRL2 |
0x40 |
CC_EN set |
0x00 SYS_STAT |
0x80 |
only CC_READY (no faults) |
0x0C VC1 (word) |
~0x24BB |
~3.51 V per cell with GAIN=369 uV/LSB |
0x2A BAT (word) |
~0x2DEC |
~17.59 V pack (PSU 18 V minus cable + ADC offset) |
Setup¶
The EV2300 is detected automatically when plugged in via USB. No VISA drivers are needed.
Requirements:
- EV2300A must have firmware loaded (not in bootloader mode). If the REPL reports "bootloader mode", flash firmware using TI bqStudio.
- On Windows: no extra drivers needed — the built-in HID driver is used.
- On Linux: the user must have permission to access
/dev/hidrawN. Create a udev rule:
# /etc/udev/rules.d/99-ev2300.rules
SUBSYSTEM=="hidraw", ATTRS{idVendor}=="0451", ATTRS{idProduct}=="0036", MODE="0666"
Then reload: sudo udevadm control --reload-rules && sudo udevadm trigger
- On macOS: install
hidapi:pip install hidapi
Troubleshooting¶
"Device error (0x46)" or I2C communication failures¶
This usually means the EV2300 USB-to-I2C bridge is in a bad state. Common causes:
- The fuel gauge / BQ EVM was powered on after the EV2300 was connected
- A previous session left the I2C bus in a stuck state
- Another program (e.g. BQ Studio) had the EV2300 open
Recovery steps (also available via ev2300 fix in the REPL):
- Make sure the BQ EVM board is powered (e.g. PSU set to 18V) — do not turn the PSU off, the BQ chip needs to stay powered
- Press the BOOT button on the BQ EVM board — this resets both the EV2300 bridge and the fuel gauge IC
- In the REPL, disconnect the adapter:
disconnect ev2300 - Re-scan to pick it back up:
scan - Retry your command
Shortcut: ev2300 cycle bundles steps 3–4 with an interactive BOOT-press prompt and an explicit reminder that the PSU should stay on. Use it when you hit Write failed or wedged I2C state during a session:
eset> ev2300 cycle
[INFO] Disconnecting EV2300 (PSU untouched -- BQ EVM stays powered)...
[WARNING] ACTION REQUIRED -- press the BOOT button on the BQ76920 EVM now.
Press Enter to reconnect the EV2300...
[INFO] Reconnecting EV2300...
[SUCCESS] EV2300 reconnected. Try your read/write commands again.
If it still doesn't work, unplug the EV2300 USB cable, plug it back in, then run disconnect ev2300, scan, and retry.
Why does disconnect-and-reconnect fix it?¶
The EV2300's firmware has an internal USB-to-I2C state machine that can get stuck after a NACK, bus contention, or timing glitch from the BQ slave. Once stuck, every subsequent HID write returns "Write failed" because the firmware refuses to start a new transaction on top of the wedged one. Host-side flush_input() only drains the OS-side HID receive buffer — it does not reach into the EV2300 firmware to clear its stuck state.
Closing the OS-side HID handle (disconnect) and opening it fresh (scan or ev2300 cycle) signals the EV2300 firmware to clear its internal state machine. Combined with a BOOT-button press on the BQ EVM (which hard-resets the slave so it ACKs cleanly on the next transaction), this clears almost every wedged-bus condition without touching the PSU.
"connect failed" during scan¶
If the EV2300 is detected but cannot be opened, another program likely has it locked. Close BQ Studio, bqStudio, or any other TI tool that may be using the EV2300, then run scan again.
Best practices¶
- Power on the BQ EVM board before launching the REPL
- If using a BQ EVM with a BOOT button, press it once after power-on to ensure a clean I2C bus
- Close BQ Studio before starting the REPL — only one program can hold the EV2300 HID handle at a time
Tips & Gotchas¶
Things that trip students up most often on the EV2300.
TI EV2300¶
- HID interface. Communicates over USB HID, not VISA. No NI-VISA required.
- I2C address format. Addresses are 7-bit. Use hex:
ev2300 read_word 0x08 0x09. - Bus recovery. If I2C communication hangs, use
ev2300 fixto attempt bus recovery. - Slow for bulk transfers. HID has limited bandwidth. Block reads of large registers take time.