Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

BmsHub

Reads a JBD (Jiabaida) BMS over UART. Pack voltage, current, state of charge, cell voltages, temperatures, and energy used.

Tested with SP17S005P17S40A which differs from the bms-tools' JBD_REGISTER_MAP.md

The protocol

9600 8N1 UART

Request/response with the host polling.

to BMS:    0xDD  cmd  reg  len  data[len]  crc_hi crc_lo  0x77
from BMS:  0xDD  reg  status  len  data[len]  crc_hi crc_lo  0x77

cmd      0xA5 read, 0x5A write
status   0x00 ok, anything else is a refusal
crc      0x10000 - sum(bytes covered), big-endian

The checksum covers different bytes in each direction. Requests cover reg + len, excluding the command byte. Responses cover status + len + data, excluding the register byte.

Register map

offset field type unit map said
0x00 pack voltage U16 10 mV same
0x02 pack current S16 10 mA, negative = discharge same
0x04 remaining capacity U16 10 mAh same
0x06 nominal capacity U16 10 mAh same
0x08 cycles U16 same
0x0A manufacture date U16 packed y/m/d same
0x10 protection flags U16 bitfield same
0x12 software version U8 state of charge
0x13 state of charge U8 percent FET status
0x14 FET status U8 bit0 charge, bit1 discharge cell count
0x15 cell count U8 NTC count
0x16 NTC count U8 (temperatures)
0x17… NTC values U16 each 0.1 K

Also note there are 9 trailing bytes past the temperatures that are undocumented. Make sure to use the length value when decoding.

Reading a pack

#include <jbd/jbd.h>
#include <jbd/serial.h>

int fd = jbd_serial_open("/dev/ttyUSB1");   /* NULL for the default */
jbd_frame_t frame;
jbd_basic_t basic;

if (jbd_serial_read_register(fd, JBD_REG_BASIC, 1000, &frame, NULL) == JBD_OK
    && jbd_decode_basic(&frame, &basic) == JBD_OK) {
    printf("%.2f V  %.2f A  %u%%\n",
           (double) basic.volts, (double) basic.amps, basic.soc_percent);
}

jbd_serial_close(fd);

jbd_serial_read_register blocks up to its timeout.

Parsing

jbd_feed() can parse one byte at a time.

jbd_status_t jbd_feed(jbd_parser_t *parser, uint8_t byte, jbd_frame_t *out);
returns meaning
JBD_OK *out holds a frame, checksum and status good
JBD_INCOMPLETE the usual answer
JBD_E_CRC checksum rejected it
JBD_E_STATUS the BMS refused; out->status says why
JBD_E_TRAILER end byte was not 0x77, so framing was lost

Energy

jbd_energy_update(&energy, &basic, dt_ms);

energy.joules          /* trapezoidal integral of volts x amps */
energy.coulomb_joules  /* from the capacity the BMS reports    */
jbd_energy_disagreement(&energy)

Integrating is responsive and finely grained but it tends to drift: it depends on polling often enough to catch current swings. The BMS also coulomb counts, which will not drift but is coarser — on a 45 V pack one 10 mAh step is about 1.6 kJ.

Building

cmake -S . -B build && cmake --build build -j
ctest --test-dir build
./build/bms-monitor /dev/ttyUSB1 200

As a subproject:

add_subdirectory(BmsHub)
target_link_libraries(your_app PRIVATE jbd)

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages