Reading a Sinowealth SH367601x battery protection IC (SH3676010 / 3676014 / 3676016) over its UART: the protocol, the CRC, an Arduino Uno bridge, a command-line reader and a live web dashboard.
If you have a cheap 6S to 16S "smart" protection board with an unmarked three-pad header and an
SH36760xx chip on it, this is for you. There was no working public code for this chip when we started.
| Interface | UART only, 9600 baud, 8N1, 5 V logic. The chip has no I2C. |
| Address | fixed 0x1C |
| Read RAM | 1C 03 <reg> <n> <crc> -> 1C 03 5A <reg> <n> <data...> <crc> |
| Read EEPROM | 1C 02 <reg> <n> <crc> |
| CRC8 | poly 0x07, init 0x00, over every byte incl. the address |
| Wake-up | send a dummy 0x00 first; the UART block sleeps after 1 s of silence |
| The three pads | GND, TXD (chip output, driven hard high), RXD (chip input, weak pull-up) |
| Gotcha | EEPROM is rated for 100 write cycles total |
Full register maps and formulas: docs/protocol.md.
Hardware: an Arduino Uno (5 V logic matches the chip) and three wires.
| Uno | BMS pad |
|---|---|
| GND | GND |
| A5 | chip TXD |
| A4 | chip RXD |
If you do not know which pad is which, wire them either way. The bridge can swap the roles in software.
# flash the bridge (or open the .ino in the Arduino IDE)
arduino-cli compile --fqbn arduino:avr:uno arduino_uart_bridge
arduino-cli upload --fqbn arduino:avr:uno -p /dev/ttyACM0 arduino_uart_bridge
# one-shot decoded dump: cells, current, temperatures, status, EEPROM thresholds
python3 arduino_uart_bridge/sh367601_host.py /dev/ttyACM0 dump
# live dashboard on http://localhost:8080/
python3 arduino_uart_bridge/bms_web.pyNo Python packages are needed; the host side talks to the Uno with plain termios.
If dump prints no reply, run python3 arduino_uart_bridge/sh367601_host.py /dev/ttyACM0 orient 0
once to swap TXD/RXD and try again.
arduino_uart_bridge/arduino_uart_bridge.ino- software-serial bridge on A4/A5, sends the wake byte, forwards raw frames, returns raw replies. Orientation switchable at runtime.arduino_uart_bridge/sh367601.py- protocol, CRC, register decoding, conversions.arduino_uart_bridge/sh367601_host.py- CLI:dump,ram <reg> <n>,eeprom <reg> <n>,orient 0|1.arduino_uart_bridge/bms_web.py+static/index.html- dashboard: live tiles, history charts with hover, per-cell plot against the chip's own thresholds, decoded protection settings, session Ah/Wh, a voltage/coulomb-count state-of-charge estimate, CSV logging, dark mode. Standard library only.
This took a whole day, and the detours are the useful part.
- A look-alike board said SDA/SCL. A similar board from the same family of products has its
three pads silk-screened
GND SCL SDA, so we assumed I2C. Wrong: that board uses a different chip. The SH367601x has no I2C at all. Always find the datasheet for your chip. - An FT232R could not pull one line low. Bit-banging I2C on the FTDI adapter (open-drain emulated by
toggling pin direction, see
dead-ends/ftdi/) showed one adapter pin stuck high whatever we did. In hindsight that pin was on the chip's TXD, a push-pull output driven high. A UART output cannot be pulled low by an open-drain master. This was the first real clue and we misread it. - Random ACKs from an I2C scan mean a floating wire. An Arduino
Wirescan returned different "devices" on every pass. That is not a bus, that is noise. A bad solder joint on one wire caused it; after reflowing, the scan went clean and empty, as it should for a chip with no I2C. - Rise times told the pins apart. Driving each line low from the Uno and timing the recovery: one line rose in 4 us (a strong driver or stiff pull-up), the other in 44 us (a weak pull-up). The fast one is TXD, the slow one is RXD. Cheap to measure, very informative.
- The only public driver had the wrong opcode. A GitHub snippet for this chip used
0x01for reads and admitted its address was a guess. The datasheet says0x02(EEPROM) and0x03(RAM) with a length byte, and0x01is write. The first correct frame answered immediately. - The wake byte matters. In power-down the UART block is off until it sees a falling edge on RXD. Without a dummy byte first, a perfectly formed frame is ignored.
- Shunt: four
R001in parallel = 0.25 mOhm. With that, one step of the current register is 13.7 mA, but the register only moves in steps of 8, so expect ~0.11 A resolution. - Current sign: positive = charging, negative = discharging (verified with a charger and a load).
RSW1two-hole footprint = wake/reset switch input. Bridging it for a second wakes the chip.- EEPROM on this board: OV 4.250 V / 0.2 s, OV release 4.15 V, UV 2.70 V / 0.5 s, UV release 2.90 V, balancing from 4.18 V, 7 cells.
It is a protection IC, not a controller. The MOSFETs are on/off switches: no current limiting, no PWM,
no output regulation. Adjustable via EEPROM: cell count, over/undervoltage thresholds and delays,
balance start voltage, overcurrent and short-circuit thresholds, temperature limits, release behaviour.
There is no "switch output off" command; the CTLD pin does that in hardware, and the EEPROM
write-enable sequence turns the MOSFETs off as a side effect. Writing tools are deliberately not
included here because of the 100-cycle limit.
dead-ends/ holds the FTDI bit-bang I2C master and the Arduino I2C bridge. They work as I2C tools;
they just cannot find a device that does not exist. They are kept because the diagnostics inside them
(rise-time test, both-orientation scan, analog line levels) are what eventually solved this.
SH367601X CV1.0A, Sinowealth. It is copyrighted and not redistributed here. Section 11 covers the UART, 10.2 the conversions, 13 the register maps.
MIT, see LICENSE.



