I have done this for a project I was working on, although I haven't touched the project since 2021. I was surprised at the time that no one else had done this before as I too was frustrated by the complexity and was hoping like you to build off from someone else's code. But even emailing Dacian, there really wasn't much he could provide, as it didn't sound like he or anyone he knew of had written any actual functional code.
Took me a while of staring cross-eyed at the manual (my only real source of a cryptic answer) until I had a few assorted "aha!" moments of revelation where something clicked in my head, then a bunch of trial and error trying to make functional code. I can supply relevant Arduino code snippets here... (I don't think I ever posted this publicly before... sorry, folks).
First, some constants:
C:
char Months[12][9] = { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" };
char AMPM[2] = "xm";
// set up defines for easily testing bits in "ERR" data from SBMS40 serial UART datastream
#define OV 1 // Bit 0 over-voltage. Battery full, charging stopped (not an error)
#define OVLK 2 // Bit 1 charge MOSFET has probably failed
#define UV 4 // Bit 2 under-voltage. Battery empty (below min voltage). Not an error (but not good)
#define UVLK 8 // Bit 3 discharge MOSTFET has probably failed
#define IOT 0x10 // Bit 4 Internal over temperature. SBMS40 has gotten too hot.
#define COC 0x20 // Bit 5 charge over-current. Error, can be caused by several things
#define DOC 0x40 // Bit 6 discharge over-current. Error, can be caused by several things
#define DSC 0x80 // Bit 7 (discharge?) short-circuit. Error, can be caused by several things
#define CELF 0x100 // Bit 8 cell fail. A cell has a voltage below what is acceptable. Bad cell or bad connection between cells
#define OPEN 0x200 // Bit 9 open cell wire. Bad connection in one of the cell monitoring wires
#define LVC 0x400 // Bit 10 low-voltage cell. A cell has a voltage so low that normal charging is not allowed. Charging the cell independently outside of the BMS might recover it
#define ECCF 0x800 // Bit 11 EEPROM fail.
#define CFET 0x1000 // Bit 12 charge FET active. Not an error. When fully charged, will clear and OV will light
#define EOC 0x2000 // Bit 13 end of charge. Battery full, not an error.
#define DFET 0x4000 // Bit 14 discharge FET active. Will go out when battery is exhausted and UV will light. Not an error.
unsigned int Flags = 0;
char SBMSdata[61] = " "; // each data packet from the SBMS40 is a 59-character bunch of data encoded in base-91 using printable characters from 35 "#" to 125 "}"
char nullbuffer[64];
Then, the main read function:
C:
void readSBMSdata() {
//code to read serial stream off from SBMS40 goes here
// for now just populate the global variable with a static sample value of working data
// strcpy(SBMSdata, "3';2LD$,I)I*I+I+H}I%I+I**h##+#)P####->##################%N(");
// Serial.print("Buffer size: ");
//Serial.println(Serial.available());
// data packet is 60 bytes but there's a newline char so a complete buffer is actually 61 bytes. For sanity we
// keep at it until we see a complete 61 byte buffer, waiting if it's too small or flushing if it's too big.
// First few loops can be off until the SBMS sends its first data packet after Arduino boot. Depends on log refresh
// rate on SBMS. Don't set too small... weird stuff happens and more sanity checking would be necessary.
// 5 seconds seems to work pretty well but depends on how big the loop() routine becomes.
if (Serial.available() < 61 ) {
// Serial.println("Waiting for full buffer...");
}
if (Serial.available() > 61) {
// Serial.println("Buffer too big, flushing...");
Serial.readBytes(nullbuffer,Serial.available());
}
if (Serial.available() == 61) {
Serial.readBytes(SBMSdata,61);
}
return;
}
Set up the serial port within the setup() section:
C:
void setup() {
Serial.begin(9600);
while (!Serial) {
; // wait for serial port to connect. Needed for native USB
}
Then, in the main loop() somewhere, you poll the serial line for data:
C:
readSBMSdata(); // pull a datapacket string from the SBMS40 UART serial line
This populates the previously-defined SBMSdata string, which you can then decode now. This code will dump decoded data to the serial console:
C:
// now we'll decode the data and populate some standard variables that can be used later
unsigned int Year = SBMSdata[0] - 35 + 2000;
unsigned int Month = SBMSdata[1] - 36;
unsigned int Day = SBMSdata[2] - 35;
unsigned int Hour = SBMSdata[3] - 35;
unsigned int Minute = SBMSdata[4] - 35;
unsigned int Second = SBMSdata[5] - 35;
unsigned int SOC = ((SBMSdata[6] - 35) * 91) + (SBMSdata[7] - 35); // battery state-of-charge percentage
float intTemp = (((SBMSdata[24] - 35) * 91) + (SBMSdata[25] - 35) - 450) / 10; // internal temperature in celcius
float extTemp = (((SBMSdata[26] - 35) * 91) + (SBMSdata[27] - 35) - 450) / 10; // external temperature in celcius
// (C*(9/5))+32=F
// deg symbol is ASCII 223 ?
byte Charge = SBMSdata[28]; // + means charging, - means discharging. This is a simple char and doesn't need decoding
float Battery = ((SBMSdata[29] - 35) * (91 * 91)) + ((SBMSdata[30] - 35) * 91) + (SBMSdata[31]); // battery milliamps
float PV1 = ((SBMSdata[32] - 35) * (91 * 91)) + ((SBMSdata[33] - 35) * 91) + (SBMSdata[34]); // solar panel milliamps
// we skip bytes 35-37 as the SMBS40 model doesn't have a PV2
float extLoad = ((SBMSdata[38] - 35) * (91 * 91)) + ((SBMSdata[38] - 35) * 91) + (SBMSdata[40]); // external load milliamps
if ( Charge == '-') {
Battery = -Battery;
}
float intLoad = PV1 - Battery;
Flags = ((SBMSdata[56] - 35) * (91 * 91)) + ((SBMSdata[57] - 35) * 91) + (SBMSdata[58]); // 15 flag bits
Serial.println(SBMSdata);
Serial.print(Months[Month]);
Serial.print(" ");
Serial.print(Day);
Serial.print(", ");
Serial.println(Year);
if ( Hour > 11 ) {
AMPM[0] = 'p';
} else {
AMPM[0] = 'a';
}
AMPM[2] = 0;
if ( Hour > 11 ) {
Hour = Hour - 12;
}
Serial.print(Hour);
Serial.print(":");
Serial.print(Minute);
Serial.print(":");
Serial.print(Second);
Serial.print(" ");
Serial.println(AMPM);
Serial.print("Battery: ");
Serial.print(SOC);
Serial.print("% ");
Serial.print(Battery/1000);
Serial.println("A");
Serial.print("Internal: ");
Serial.println(intTemp);
Serial.print("External: ");
Serial.println(extTemp);
Serial.print("Ext load: ");
Serial.print(extLoad/1000);
Serial.println(" A");
Serial.print("Int load: ");
Serial.print(intLoad/1000);
Serial.println(" A");
// Flags override for testing
// Flags = 32767;
Serial.print("Flags: ");
char FlagsB[10];
itoa( Flags, FlagsB, 2);
Serial.println(FlagsB);
Serial.println(Flags);
if ( Flags & OV) { Serial.print("OV ");}
if ( Flags & OVLK) { Serial.print("OVLK ");}
if ( Flags & UV) { Serial.print("UV ");}
if ( Flags & UVLK) { Serial.print("UVLK ");}
if ( Flags & IOT) { Serial.print("IOT ");}
if ( Flags & COC) { Serial.print("COC ");}
if ( Flags & DOC) { Serial.print("DOC ");}
if ( Flags & DSC) { Serial.print("DSC ");}
if ( Flags & CELF) { Serial.print("CELF ");}
if ( Flags & OPEN) { Serial.print("OPEN ");}
if ( Flags & LVC) { Serial.print("LVC ");}
if ( Flags & ECCF) { Serial.print("ECCF ");}
if ( Flags & CFET) { Serial.print("CFET ");}
if ( Flags & EOC) { Serial.print("EOC ");}
if ( Flags & DFET) { Serial.print("DFET ");}
I can't promise this code will run exactly as-is as I pulled the relevant sections out of a much-bigger project. But hopefully it gets you most of the way there.