RS485 polling returns erroneous data

I trying to read data from a Daly BMS, which has been a pain in the backside to say the least.
I tried their CAN bus, which eventually shows data corruption, and gave up.

Now I am trying the RS485 port... without any luck.

The BMS needs to be queried in order to provide a reply. This is done by sending a start byte, host address, message id and frame length, and empty or rather 0x00 data bytes, plus a simple checksum; all up 13 bytes; like so

SB HA MI FL Data................... CS
A5 40 91 08 00 00 00 00 00 00 00 00 7E

It is supposed to return a response like this:

A5 01 91 08 0C F4 05 0C C9 08 00 00 CS

Here what I am getting back:

--> 14604912 | 91 | A5 | 40 | 91 | 08 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 7E |
<-- 14604942 | 91 | 00 | 00 | 00 | A5 | 01 | 91 | 08 | 0D | 0B | 0F | 0C | F2 |
<UART>[MsgID: 0x91][CRC Rec: 64][CRC Calc:  A]
--> 14607955 | 91 | A5 | 40 | 91 | 08 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 7E |
<-- 14607985 | 91 | 00 | 00 | 00 | A5 | 01 | 91 | 08 | 0D | 06 | 0E | 0C | F5 |
<UART>[MsgID: 0x91][CRC Rec: 61][CRC Calc:  1]
--> 14610997 | 91 | A5 | 40 | 91 | 08 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 7E |
<-- 14611027 | 91 | 00 | 00 | 00 | A5 | 01 | 91 | 08 | 0D | 0C | 0F | 0C | F3 |
<UART>[MsgID: 0x91][CRC Rec: 66][CRC Calc:  A]
--> 14614040 | 91 | A5 | 40 | 91 | 08 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 7E |
<-- 14614072 | 91 | 00 | 00 | 00 | 00 | A5 | 01 | 91 | 08 | 0D | 0A | 0F | 0C |
<UART>[MsgID: 0x91][CRC Rec: 71][CRC Calc: F0]
--> 14617086 | 91 | A5 | 40 | 91 | 08 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 7E |
<-- 14617112 | 91 | A8 | 5D | B0 | E4 | 84 | 0D | 08 | 0E | 0C | F5 | 01 | 00 |
<UART>[MsgID: 0x91][CRC Rec: 42][CRC Calc:  0]
--> 14620123 | 91 | A5 | 40 | 91 | 08 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 7E |
<-- 14620153 | 91 | 00 | 00 | 00 | A5 | 01 | 91 | 08 | 0D | 08 | 0E | 0C | F2 |
<UART>[MsgID: 0x91][CRC Rec: 60][CRC Calc:  1]
--> 14623165 | 91 | A5 | 40 | 91 | 08 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 7E |
<-- 14623199 | 91 | 02 | 00 | 00 | 00 | 00 | A5 | 01 | 91 | 08 | 0D | 09 | 0F |
<UART>[MsgID: 0x91][CRC Rec: 66][CRC Calc:  C]
--> 14626212 | 91 | A5 | 40 | 91 | 08 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 7E |
<-- 14626247 | 91 | 00 | 01 | 00 | 00 | A5 | 01 | 91 | 08 | 0D | 0A | 0F | 0C |
<UART>[MsgID: 0x91][CRC Rec: 72][CRC Calc: F3]
--> 14629260 | 91 | A5 | 40 | 91 | 08 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 7E |
<-- 14629284 | 91 | 00 | 00 | 08 | 00 | 12 | 81 | 49 | 40 | E9 | 80 | 91 | 08 |
<UART>[MsgID: 0x91][CRC Rec: 26][CRC Calc:  D]
--> 14632301 | 91 | A5 | 40 | 91 | 08 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 7E |
<-- 14632328 | 91 | 00 | 00 | 95 | 0B | 16 | 0E | 58 | 18 | B0 | 18 | D8 | 3E |
<UART>[MsgID: 0x91][CRC Rec: 12][CRC Calc: 61]

More often than not, the start byte appears in the middle or not at all. However, what follows the start byte seems to represent valid data. But, because I am reading 13 bytes, the remainder is cut off.

Any ideas why this is not working as intended?

The code I use:

#include <Arduino.h>
#include <SoftwareSerial.h>

#define BAUD_RATE_HW       115200
#define BAUD_RATE_485        9600

#define MAX485_DI               2   // TX
#define MAX485_DE               3
#define MAX485_RE_NEG           4
#define MAX485_R0               5   // RX

#define START_BYTE           0xA5
#define HOST_ADRESS          0x40
#define FRAME_LENGTH         0x08
#define XFER_BUFFER_LENGTH     13

enum MSG_ID
{
    MSG_ID_BATTERY_PACK_DATA       = 0x90,
    MSG_ID_MIN_MAX_CELLS           = 0x91,
    MSG_ID_MIN_MAX_TEMPERATURE     = 0x92,
    MSG_ID_MOSFET_CHARGE_STATUS    = 0x93,
    MSG_ID_STATUS_INFO             = 0x94,
    MSG_ID_CELL_VOLTAGES           = 0x95,
    MSG_ID_CELL_TEMPERATURES       = 0x96,
    MSG_ID_CELL_BALANCE_STATUS     = 0x97,
    MSG_ID_BATTERY_FAILURE_STATUS  = 0x98
};

uint8_t rx_frame_buffer[XFER_BUFFER_LENGTH * 12];
uint8_t frame_buffer[12][XFER_BUFFER_LENGTH];
uint8_t frame_Counter;
uint8_t rx_buffer[XFER_BUFFER_LENGTH] = {0};
uint8_t tx_buffer[XFER_BUFFER_LENGTH] = {0};

SoftwareSerial rs485(MAX485_R0, MAX485_DI);     // RX, TX


void pre_transmission()
{
    digitalWrite(MAX485_RE_NEG, 1);
    digitalWrite(MAX485_DE, 1);
}


void post_transmission()
{
    digitalWrite(MAX485_RE_NEG, 0);
    digitalWrite(MAX485_DE, 0);
}


void clear_rs485_input_buffer()
{
    while (rs485.available() > 0)
    {
        rs485.read();
    }
}


/*
    //      1  2  3  4  5  6  7  8  9 10 11 12 13
    // --> A5 40 90 08 00 00 00 00 00 00 00 00 7D
    // <-- A5 01 90 08 02 7B 00 00 75 30 03 C1 24

*/
bool request_data(uint8_t message_id, uint8_t frame_amount)
{
    uint8_t tx_checksum = 0x00;                 // transmit checksum buffer
    uint8_t byte_counter = 0;

    // Clear out the buffers
    memset(tx_buffer, 0x00, XFER_BUFFER_LENGTH);
    memset(rx_frame_buffer, 0x00, sizeof(rx_frame_buffer));
    memset(frame_buffer, 0x00, sizeof(frame_buffer));

    // Populate the frame with static data and message ID
    tx_buffer[0] = START_BYTE;
    tx_buffer[1] = HOST_ADRESS;
    tx_buffer[2] = message_id;
    tx_buffer[3] = FRAME_LENGTH;

    // Calculate the checksum
    for (uint8_t i = 0; i < XFER_BUFFER_LENGTH - 1; i++)
    {
        tx_checksum += tx_buffer[i];
    }

    // add check sum to the frame buffer
    tx_buffer[12] = tx_checksum;

    Serial.print("--> ");
    Serial.print(millis());
    Serial.print(" (");
    Serial.print(message_id, HEX);
    Serial.print(") ");

    for (uint8_t i = 0; i < sizeof(tx_buffer); i++)
    {
        if (tx_buffer[i] < 0x10)
        {
            Serial.print("0");
        }

        Serial.print(tx_buffer[i], HEX);
        Serial.print(" ");

    }
    Serial.println();

    pre_transmission();
    rs485.write(tx_buffer, XFER_BUFFER_LENGTH);
    post_transmission();

    // wait for transmission end
    rs485.flush();

    // Receive data
    rs485.readBytes(rx_frame_buffer, XFER_BUFFER_LENGTH * frame_amount);

    uint8_t rx_checksum = 0x00;

    for (size_t i = 0; i < frame_amount; i++)
    {
        for (size_t j = 0; j < XFER_BUFFER_LENGTH; j++)
        {
            frame_buffer[i][j] = rx_frame_buffer[byte_counter];
            byte_counter++;
        }

        Serial.print("<-- ");
        Serial.print(millis());
        Serial.print(" (");
        Serial.print(message_id, HEX);
        Serial.print(") ");

        for (int k = 0; k < XFER_BUFFER_LENGTH - 1; k++)
        {
            rx_checksum += frame_buffer[i][k];

            if (frame_buffer[i][k] < 0x10)
            {
                Serial.print("0");
            }

            Serial.print(frame_buffer[i][k], HEX);
            Serial.print(" ");
        }
        Serial.println();

        // Output debugging information
        char debugBuff[128];

        sprintf
        (
            debugBuff,
            "<UART>[MsgID: 0x%2X][CRC Rec: %2X][CRC Calc: %2X]",
            message_id, rx_checksum, frame_buffer[i][XFER_BUFFER_LENGTH - 1]
        );

        Serial.println(debugBuff);

        if (rx_checksum != frame_buffer[i][XFER_BUFFER_LENGTH - 1])
        {
            Serial.println("<BMS.> CRC fail");
            return false;
        }

        if (rx_checksum == 0)
        {
            Serial.println("<BMS.> No data");
            return false;
        }

        if (frame_buffer[i][1] >= 0x20)
        {
            Serial.println("<BMS.> BMS sleeping");
            return false;
        }
    }

    return true;
}


void setup()
{
    pinMode(MAX485_RE_NEG, OUTPUT);
    pinMode(MAX485_DE, OUTPUT);

    digitalWrite(MAX485_RE_NEG, 0);
    digitalWrite(MAX485_DE, 0);

    Serial.begin(BAUD_RATE_HW);
    delay(300);

    rs485.begin(BAUD_RATE_485);

    Serial.println("Ready");
}


void loop()
{
    //request_data(MSG_ID_BATTERY_PACK_DATA, 1);
    request_data(MSG_ID_MIN_MAX_CELLS, 1);
    //request_data(MSG_ID_MIN_MAX_TEMPERATURE, 1);
    //request_data(MSG_ID_MOSFET_CHARGE_STATUS, 1);
    //request_data(MSG_ID_STATUS_INFO, 1);
    //request_data(MSG_ID_CELL_VOLTAGES, 1);
    //request_data(MSG_ID_CELL_TEMPERATURES, 1);
    //request_data(MSG_ID_CELL_BALANCE_STATUS, 1);
    //request_data(MSG_ID_BATTERY_FAILURE_STATUS, 1);
    delay(3000);
}

I assume you are using an Arduino UNO
SoftwareSerial is very limited in particular in cannot simultaneously transmit and receive which maybe your problem.
try AltSoftSerial or better use a microcontroller with a hardware serial port such as a Mega or an ESP32

1 Like

Can you post an annotated schematic showing how everything is connected. Be sure to show all connections, components, power sources, etc. Links to the hardware items would be a big plus.

You enable the line for receiving before transmission is ended?

Wouldn't know how to, other than doing it on paper...
I am using a run-of-the-mill MAX485-based TTL to RS485 board.

    UNO             MAX485
    Rx 1 ------------ 1 RO
    Tx 3 enable ----- 2 /RE
    Tx 4 enable ----- 3 DE
    Tx 2 ------------ 4 DI

#define MAX485_DI               2   // TX
#define MAX485_DE               3
#define MAX485_RE_NEG           4
#define MAX485_R0               5   // RX

5V via USB port, out on the UNO to the MAX485, same with ground.
A and B are connected properly between BMS and UNO.

I wondered about this too; but changing it around, or not using flush() at all, does not change the outcome. The result snippet below is based on:

    pre_transmission();
    Serial1.write(tx_buffer, XFER_BUFFER_LENGTH);
    // ... wait for transmission to complete
    // = Block the Arduino until all outgoing data has been sent
    Serial1.flush();
    post_transmission();

A and B are connected properly, otherwise I would not get a sensible response; the bytes are just positionally out. (Switching A/B results in all bytes received to be zero.)

--> 151037 (91) A5 40 91 08 00 00 00 00 00 00 00 00 7E 
Serial1.available(): 50
<-- 151055 (91) 35 A5 01 91 08 0D 41 0B 0D 07 02 A5 
<UART>[MsgID: 0x91][CRC Rec: 88][CRC Calc:  1]
<BMS.> CRC fail

I have even used another MAX485 module; same outcome.
Have used the R4 Minima; same result.

Which means, either code or BMS is faulty.
As for the latter, different host addresses are available.
I have the Bluetooth module, which works. Since the data formats are the same, BT, CAN, RS232, and RS485, I assume the BMS to send signals correctly.

image

If you were using a hardware serial port, then the use of the flush function would be required (and placed just before post_transmission). When using a software serial port, I think flush has no effect.

Your code assumes that a response from the remote device is instantly available the moment transmission completes. This is incorrect as it takes time for the remote device to respond.

I suspect that when you run your code, the very first response message that your code extracts starts with a few 0xFF bytes - i.e. -1 which is the software serial port saying that there is no data available. You then get the first few bytes of the actual response.

On your second query, you read out the remainder of the previous response and the first few bytes of the actual response to the second query. That would explain why you see the expected response in the position you do rather than at the start.

Is the response always a fixed length?

You should wait for bytes to be received, reading them out until you have the expected number. That bit of code would usually include a timeout so your code could continue if a response wasn't received within a certain time period.

1 Like

Yes, always 13 bytes...

I moved rs485.available before rs485.readBytes and now get constanly a value of 63 bytes. Meaning the 63 bytes are in the buffer before rs485.readBytes is called.

What bothers me are the garbage bytes, which should not be there.

If it has no effect, can I leave it there?

My latest code looks like this. what may be confusing is that I renamed the rs485 object to Serial1; which is not available on the R3 but on the R4. I also allow flush only on R3 (which uses SoftwareSerial; the R4 has a hardware UART on D0 and D1, independent of Serial = USB).

/*
    Read Daly BMS via RS485 interface


    UNO R3            LTC1480 or MAX485
    Rx 2 ------------ 1 RO
    Tx 3 enable ----- 2 /RE
    Tx 4 enable ----- 3 DE
    Tx 5 ------------ 4 DI

    UNO R4            LTC1480 or MAX485
    Rx 0 ------------ 1 RO
    Tx 3 enable ----- 2 /RE
    Tx 4 enable ----- 3 DE
    Tx 1 ------------ 4 DI

*/
#include <Arduino.h>

#define R3

#ifdef R3
#include <SoftwareSerial.h>
#define MAX485_DI               2   // TX
#define MAX485_DE               3
#define MAX485_RE_NEG           4
#define MAX485_R0               5   // RX
SoftwareSerial Serial1(MAX485_R0, MAX485_DI);        // RX, TX
#else
#define MAX485_DI               1   // TX
#define MAX485_DE               3
#define MAX485_RE_NEG           4
#define MAX485_R0               0   // RX
#endif

#define BAUD_RATE_HW       115200
#define BAUD_RATE_485        9600

#define START_BYTE           0xA5
#define HOST_ADRESS          0x40
#define FRAME_LENGTH         0x08
#define XFER_BUFFER_LENGTH     13

enum MSG_ID
{
    MSG_ID_BATTERY_PACK_DATA       = 0x90,
    MSG_ID_MIN_MAX_CELLS           = 0x91,
    MSG_ID_MIN_MAX_TEMPERATURE     = 0x92,
    MSG_ID_MOSFET_CHARGE_STATUS    = 0x93,
    MSG_ID_STATUS_INFO             = 0x94,
    MSG_ID_CELL_VOLTAGES           = 0x95,
    MSG_ID_CELL_TEMPERATURES       = 0x96,
    MSG_ID_CELL_BALANCE_STATUS     = 0x97,
    MSG_ID_BATTERY_FAILURE_STATUS  = 0x98
};

uint8_t rx_frame_buffer[XFER_BUFFER_LENGTH * 12];
uint8_t frame_buffer[12][XFER_BUFFER_LENGTH];
uint8_t frame_Counter;
uint8_t rx_buffer[XFER_BUFFER_LENGTH] = {0};
uint8_t tx_buffer[XFER_BUFFER_LENGTH] = {0};


void pre_transmission()
{
    digitalWrite(MAX485_RE_NEG, 1);
    digitalWrite(MAX485_DE, 1);
}


void post_transmission()
{
    digitalWrite(MAX485_RE_NEG, 0);
    digitalWrite(MAX485_DE, 0);
}


void clear_rs485_input_buffer()
{
    while (Serial1.available() > 0)
    {
        Serial1.read();
    }
}


/*
    //      1  2  3  4  5  6  7  8  9 10 11 12 13
    // --> A5 40 90 08 00 00 00 00 00 00 00 00 7D
    // <-- A5 01 90 08 02 7B 00 00 75 30 03 C1 24

*/
bool request_data(uint8_t message_id, uint8_t frame_amount)
{
    uint8_t tx_checksum = 0x00;                 // transmit checksum buffer
    uint8_t byte_counter = 0;

    // Clear out the buffers
    memset(tx_buffer, 0x00, XFER_BUFFER_LENGTH);
    memset(rx_frame_buffer, 0x00, sizeof(rx_frame_buffer));
    memset(frame_buffer, 0x00, sizeof(frame_buffer));

    // Populate the frame with static data and message ID
    tx_buffer[0] = START_BYTE;
    tx_buffer[1] = HOST_ADRESS;
    tx_buffer[2] = message_id;
    tx_buffer[3] = FRAME_LENGTH;

    // Calculate the checksum
    for (uint8_t i = 0; i < XFER_BUFFER_LENGTH - 1; i++)
    {
        tx_checksum += tx_buffer[i];
    }

    // add check sum to the frame buffer
    tx_buffer[12] = tx_checksum;

    Serial.print("--> ");
    Serial.print(millis());
    Serial.print(" (");
    Serial.print(message_id, HEX);
    Serial.print(") ");

    for (uint8_t i = 0; i < sizeof(tx_buffer); i++)
    {
        if (tx_buffer[i] < 0x10)
        {
            Serial.print("0");
        }

        Serial.print(tx_buffer[i], HEX);
        Serial.print(" ");

    }
    Serial.println();

    pre_transmission();

    Serial1.write(tx_buffer, XFER_BUFFER_LENGTH);

    #ifdef R4
    // ... wait for transmission to complete
    // = Block the Arduino until all outgoing data has been sent
    Serial1.flush();
    #endif

    post_transmission();

    // Receive data
    Serial1.readBytes(rx_frame_buffer, XFER_BUFFER_LENGTH * frame_amount);

    // for debug only
    Serial.print("Serial1.available(): ");
    Serial.println(Serial1.available());

    for (size_t i = 0; i < frame_amount; i++)
    {
        for (size_t j = 0; j < XFER_BUFFER_LENGTH; j++)
        {
            frame_buffer[i][j] = rx_frame_buffer[byte_counter];
            byte_counter++;
        }

        Serial.print("<-- ");
        Serial.print(millis());
        Serial.print(" (");
        Serial.print(message_id, HEX);
        Serial.print(") ");

        uint8_t rx_checksum = 0x00;

        for (int k = 0; k < XFER_BUFFER_LENGTH - 1; k++)
        {
            rx_checksum += frame_buffer[i][k];

            if (frame_buffer[i][k] < 0x10)
            {
                Serial.print("0");
            }

            Serial.print(frame_buffer[i][k], HEX);
            Serial.print(" ");
        }
        Serial.println();

        // Output debugging information
        char debugBuff[128];

        sprintf
        (
            debugBuff,
            "<UART>[MsgID: 0x%2X][CRC Rec: %2X][CRC Calc: %2X]",
            message_id, rx_checksum, frame_buffer[i][XFER_BUFFER_LENGTH - 1]
        );

        Serial.println(debugBuff);

        if (rx_checksum != frame_buffer[i][XFER_BUFFER_LENGTH - 1])
        {
            Serial.println("<BMS.> CRC fail");
            return false;
        }

        if (rx_checksum == 0)
        {
            Serial.println("<BMS.> No data");
            return false;
        }

        if (frame_buffer[i][1] >= 0x20)
        {
            Serial.println("<BMS.> BMS sleeping");
            return false;
        }
    }

    return true;
}


void setup()
{
    pinMode(MAX485_RE_NEG, OUTPUT);
    pinMode(MAX485_DE, OUTPUT);

    digitalWrite(MAX485_RE_NEG, 0);
    digitalWrite(MAX485_DE, 0);

    Serial.begin(BAUD_RATE_HW);
    delay(100);

    Serial1.begin(BAUD_RATE_485);
    delay(100);

    Serial.println("BMS via RS485 ready...");

    #ifdef R4
        Serial.print("baud: ");
        Serial.print(Serial.baud());
        Serial.print(" stop: ");
        Serial.print(Serial.stopbits());
        Serial.print(" parity: ");
        Serial.print(Serial.paritytype());
        Serial.print(" bits: ");
        Serial.print(Serial.numbits());
        Serial.println();
    #endif

}


void loop()
{
    request_data(MSG_ID_BATTERY_PACK_DATA, 1);
    //request_data(MSG_ID_MIN_MAX_CELLS, 1);
    //request_data(MSG_ID_MIN_MAX_TEMPERATURE, 1);
    //request_data(MSG_ID_MOSFET_CHARGE_STATUS, 1);
    //request_data(MSG_ID_STATUS_INFO, 1);
    //request_data(MSG_ID_CELL_VOLTAGES, 1);
    //request_data(MSG_ID_CELL_TEMPERATURES, 1);
    //request_data(MSG_ID_CELL_BALANCE_STATUS, 1);
    //request_data(MSG_ID_BATTERY_FAILURE_STATUS, 1);
    delay(3000);
}

:slight_smile: lright...

Lots of Serial.print for testing... and I have a solution. It's most likely not the most elegant, but what my skill level could muster :slight_smile:

Here the annotation to make sense of the serial output:

--> is the query
then buffer count
buffer contents
counter for serial.read() and A5 (start byte) found
which position the start byte was found (could be a data byte, didn't care)
k = position in the buffer|pos = position in the extracted msg array
the proper message frame :)
--> 428760 (90) A5 40 90 08 00 00 00 00 00 00 00 00 7D
Serial1.available()...: 63
FF E7 FD FF A5 01 90 08 02 11 00 00 74 C9 03 AE 3F FF F7 FF FF DE DF 16 00 F6 FF FF 00 DF DF 3E 00 F7 F7 01 FF DB 33 00 FB 9F FB 00 DE FB 2B 00 7F FF FF FF FE DF DB 3B 00 BE DF 01 DF DF 13
count_serial_readings: 160 | count_a5: 98
Found start sequence at j: 4
k|pos: 4|0 5|1 6|2 7|3 8|4 9|5 10|6 11|7 12|8 13|9 14|10 15|11 16|12
A5 01 90 08 02 11 00 00 74 C9 03 AE 3F
--> 431828 (90) A5 40 90 08 00 00 00 00 00 00 00 00 7D
Serial1.available()...: 63
A5 01 90 08 02 11 00 00 75 43 03 AE BA DF DF 13 00 DF FF DF 00 DF FB 13 00 FF FE FF 00 FF DF 0B 00 FE FF 00 FF DF 17 00 FB FF FE DF 17 00 FF FF FF DF 17 00 F6 FF FF 00 F7 DF 3B 00 FF BF FE
count_serial_readings: 161 | count_a5: 99
Found start sequence at j: 0
k|pos: 0|0 1|1 2|2 3|3 4|4 5|5 6|6 7|7 8|8 9|9 10|10 11|11 12|12
A5 01 90 08 02 11 00 00 75 43 03 AE BA
--> 434899 (90) A5 40 90 08 00 00 00 00 00 00 00 00 7D
Serial1.available()...: 63
FF A5 01 90 08 02 11 00 00 74 CD 03 AE 43 BB FF FF 00 FF DE 1F 00 6F FF FC FE FE DE 1F 00 FF FF FF 01 F7 DF 3F 00 DF FF FE 01 FE DF 2F 00 7E FF FF FE DF 3F 00 FE FF FF F7 F7 DF 2F 00 FB EF
count_serial_readings: 162 | count_a5: 100
Found start sequence at j: 1
k|pos: 1|0 2|1 3|2 4|3 5|4 6|5 7|6 8|7 9|8 10|9 11|10 12|11 13|12
A5 01 90 08 02 11 00 00 74 CD 03 AE 43
--> 437965 (90) A5 40 90 08 00 00 00 00 00 00 00 00 7D
Serial1.available()...: 63
90 08 02 11 00 00 75 47 03 AE BE DF DB 1B 00 FF DF FF DF DB 13 00 FF DF FF FF DF DF 13 00 FF F3 FF DF DB 13 00 FF FF FE DF DF 33 00 FF FF DF DB 33 00 FB DB 00 DF DB 13 00 FE DF FD FE DF DB
count_serial_readings: 163 | count_a5: 100
not enough chars left --> j: 51 num_chars - XFER buf: 50

--> 441021 (90) A5 40 90 08 00 00 00 00 00 00 00 00 7D
Serial1.available()...: 63
93 7F A5 01 90 08 02 11 00 00 74 CB 03 AE 41 FB FF FF FF FB F6 17 00 FE FE 7E 00 FE DF 1E 00 FE FF FF 00 F6 DE 2F 00 FE DF 00 FF DF 37 00 FF FD FF DF DB 13 00 FF DF FF FC DF DB 33 00 FF AF
count_serial_readings: 164 | count_a5: 101
Found start sequence at j: 2
k|pos: 2|0 3|1 4|2 5|3 6|4 7|5 8|6 9|7 10|8 11|9 12|10 13|11 14|12
A5 01 90 08 02 11 00 00 74 CB 03 AE 41
--> 444090 (90) A5 40 90 08 00 00 00 00 00 00 00 00 7D
Serial1.available()...: 63
DF DF 33 17 90 08 02 11 00 00 75 2B 03 AE A2 F6 DA 13 00 FF 7F 01 DB DF 33 00 FE BD 01 DF FB 13 00 FF FF FF FF F6 DF 13 00 FE FB 01 F6 DF 37 00 FF BF 00 DF DB 13 00 FD FD DF DB 13 00 FE FF
count_serial_readings: 165 | count_a5: 101
not enough chars left --> j: 51 num_chars - XFER buf: 50

--> 447148 (90) A5 40 90 08 00 00 00 00 00 00 00 00 7D
Serial1.available()...: 63
DF B3 A5 01 90 08 02 11 00 00 75 0D 03 AE 84 FB FB FB 01 DE DF F3 FA DF FF FF DF DB 13 00 FF FF FF DE FF 3B 00 FF 00 FF DF 13 00 FB FE DB 1B 00 FF FF 00 DF DB 3B 00 FF FF FF 00 DF DB 17 00
count_serial_readings: 166 | count_a5: 102
Found start sequence at j: 2
k|pos: 2|0 3|1 4|2 5|3 6|4 7|5 8|6 9|7 10|8 11|9 12|10 13|11 14|12
A5 01 90 08 02 11 00 00 75 0D 03 AE 84
--> 450215 (90) A5 40 90 08 00 00 00 00 00 00 00 00 7D
Serial1.available()...: 63
A5 01 90 08 02 11 00 00 74 D3 03 AE 49 DF BF FF DF DE FB FF FF 03 FE DF 37 00 EE FF FF 00 D6 FF 1F 00 FF FF FE FF DE 2F 00 FF BE 7F FE DF 37 00 FA FF F7 FE DF 13 00 FE 7E FF 00 FE DF 2F 00
count_serial_readings: 167 | count_a5: 103
Found start sequence at j: 0
k|pos: 0|0 1|1 2|2 3|3 4|4 5|5 6|6 7|7 8|8 9|9 10|10 11|11 12|12
A5 01 90 08 02 11 00 00 74 D3 03 AE 49
--> 453286 (90) A5 40 90 08 00 00 00 00 00 00 00 00 7D
Serial1.available()...: 63
DF FF FF A5 01 90 08 02 11 00 00 75 33 03 AE AA FE FE FF FE FE 26 00 FE FF 00 F7 FF 17 FC FE DE DF 17 00 FF FF FF 00 FF DB 37 F7 FF F6 DB FB 33 00 FF FF FF DF DF 13 00 FF 7F FE DB FB 33 00
count_serial_readings: 168 | count_a5: 104
Found start sequence at j: 3
k|pos: 3|0 4|1 5|2 6|3 7|4 8|5 9|6 10|7 11|8 12|9 13|10 14|11 15|12
A5 01 90 08 02 11 00 00 75 33 03 AE AA
--> 456353 (90) A5 40 90 08 00 00 00 00 00 00 00 00 7D
Serial1.available()...: 63
FB BE FF 00 A5 01 90 08 02 11 00 00 74 EB 03 AE 61 FF 00 FF DF 2E 00 FF FF FF FE DE 1F 00 FF FE FF 00 FE DB 1B 00 FE FF 00 FF FB 17 00 FF FF FF DE DF 13 00 7E FF FF DB 37 00 7E FF FF DF FF
count_serial_readings: 169 | count_a5: 105
Found start sequence at j: 4
k|pos: 4|0 5|1 6|2 7|3 8|4 9|5 10|6 11|7 12|8 13|9 14|10 15|11 16|12
A5 01 90 08 02 11 00 00 74 EB 03 AE 61
--> 459421 (90) A5 40 90 08 00 00 00 00 00 00 00 00 7D
Serial1.available()...: 63
FF A5 01 90 08 02 11 00 00 75 1A 03 AE 91 7F FF FE DE 16 FF FF FF 00 FE DF 1E FE FF FF F6 DF 2F 00 F7 EF FF FF BF FF 37 00 FE FF FF 00 FE DE 3F 00 FC FF FF FF B7 FF 3B 00 7E FF FF 00 DE DF
count_serial_readings: 170 | count_a5: 106
Found start sequence at j: 1
k|pos: 1|0 2|1 3|2 4|3 5|4 6|5 7|6 8|7 9|8 10|9 11|10 12|11 13|12
A5 01 90 08 02 11 00 00 75 1A 03 AE 91
--> 462488 (90) A5 40 90 08 00 00 00 00 00 00 00 00 7D
Serial1.available()...: 63
DE DF 16 00 A5 01 90 08 02 11 00 00 74 D3 03 AE 49 F6 DF 1E F6 FF 00 FF DF 3E 00 FE FF FE FF DF 17 00 FE DB FF F7 FE 3F 00 FE FF FF F7 FE 3F 00 FF FF 00 FF DF 1B 00 FE F7 FF 00 FF DF 13 00
count_serial_readings: 171 | count_a5: 107
Found start sequence at j: 4
k|pos: 4|0 5|1 6|2 7|3 8|4 9|5 10|6 11|7 12|8 13|9 14|10 15|11 16|12
A5 01 90 08 02 11 00 00 74 D3 03 AE 49
--> 465557 (90) A5 40 90 08 00 00 00 00 00 00 00 00 7D
Serial1.available()...: 63
DF DF 12 4A 01 90 08 02 11 00 00 75 3A 03 AE B1 DF DF 16 FE FF 00 DF DF 2E 00 FF FE FF DF 7B 13 00 FF FF 00 DE FB 1B 00 FF DF FF DF 5B 2B 00 DF F7 FF F7 FE 3F 00 FE FF FF 00 FF DE 1F 00 DF
count_serial_readings: 172 | count_a5: 107
not enough chars left --> j: 51 num_chars - XFER buf: 50

Here the code:

/*
    Read Daly BMS via RS485 interface

    @author Max Grenkowitz 2024

    giving up; pursuiing CAN bus cellVoltages only via MQTT

    20240902-1234 v0.0.2 -` PoC; reads 63 bytes into buffer, but does not catch
                            the expected msg.
    20240901-2344 v0.0.1 -` PoC; reads n bytes into buffer, but does not catch
                            the expected msg; start byte can be anywhere in the
                            return msg.

    UNO R3            LTC1480 or MAX485
    Rx 2 ------------ 1 RO
    Tx 3 enable ----- 2 /RE
    Tx 4 enable ----- 3 DE
    Tx 5 ------------ 4 DI

    UNO R4            LTC1480 or MAX485
    Rx 0 ------------ 1 RO
    Tx 3 enable ----- 2 /RE
    Tx 4 enable ----- 3 DE
    Tx 1 ------------ 4 DI

*/
#include <Arduino.h>

#define R3                          // either R3 or R4 for UNO or Minima

#ifdef R3
#include <SoftwareSerial.h>
#define MAX485_DI               2   // TX
#define MAX485_DE               3
#define MAX485_RE_NEG           4
#define MAX485_R0               5   // RX
SoftwareSerial Serial1(MAX485_R0, MAX485_DI);        // RX, TX
#else
#define MAX485_DI               1   // TX
#define MAX485_DE               3
#define MAX485_RE_NEG           4
#define MAX485_R0               0   // RX
#endif

#define BAUD_RATE_HW       115200
#define BAUD_RATE_485        9600

const unsigned char START_BYTE          = 0xA5;
const unsigned char HOST_ADRESS         = 0x40;
const unsigned char FRAME_LENGTH        = 0x08;
const unsigned char XFER_BUFFER_LENGTH  =   13;

enum MSG_ID
{
    MSG_ID_BATTERY_PACK_DATA       = 0x90,
    MSG_ID_MIN_MAX_CELLS           = 0x91,
    MSG_ID_MIN_MAX_TEMPERATURE     = 0x92,
    MSG_ID_MOSFET_CHARGE_STATUS    = 0x93,
    MSG_ID_STATUS_INFO             = 0x94,
    MSG_ID_CELL_VOLTAGES           = 0x95,
    MSG_ID_CELL_TEMPERATURES       = 0x96,
    MSG_ID_CELL_BALANCE_STATUS     = 0x97,
    MSG_ID_BATTERY_FAILURE_STATUS  = 0x98
};

uint8_t rx_frame_buffer[XFER_BUFFER_LENGTH * 12];
uint8_t frame_buffer[12][XFER_BUFFER_LENGTH];
uint8_t frame_Counter;
uint8_t rx_buffer[XFER_BUFFER_LENGTH] = {0};
uint8_t tx_buffer[XFER_BUFFER_LENGTH] = {0};


void pre_transmission()
{
    digitalWrite(MAX485_RE_NEG, 1);
    digitalWrite(MAX485_DE, 1);
}


void post_transmission()
{
    digitalWrite(MAX485_RE_NEG, 0);
    digitalWrite(MAX485_DE, 0);
}


void extract_msg_from_serial_buffer(uint8_t message_id)
{
    uint8_t num_chars_in_buffer = Serial1.available();

    if (num_chars_in_buffer == 0)
    {
        return;
    }

    Serial.print("Serial1.available()...: ");
    Serial.println(num_chars_in_buffer);

    uint8_t receive_buffer[num_chars_in_buffer + 1] = {0};

    static uint16_t count_a5 = 0;
    static uint16_t count_serial_readings = 0;

    // put chars read into buffer array
    for (uint8_t i = 0; i < num_chars_in_buffer; i++)
    {
        receive_buffer[i] = Serial1.read();

        // print what's in the buffer
        if (receive_buffer[i] < 0x10) Serial.print("0");
        if (receive_buffer[i] == (uint8_t)START_BYTE) count_a5++;
        Serial.print(receive_buffer[i], HEX);
        Serial.print(" ");
    }

    count_serial_readings++;

    Serial.print("\ncount_serial_readings: ");
    Serial.print(count_serial_readings);
    Serial.print(" | count_a5: ");
    Serial.print(count_a5);
    Serial.println();

    for (uint8_t j = 0; j < num_chars_in_buffer; j++)
    {
        // print what's in the array
        //if (receive_buffer[j] < 0x10) Serial.print("0");
        //Serial.print(receive_buffer[j], HEX);
        //Serial.print(" ");

        // exit out if j = num_chars_in_buffer - XFER_BUFFER_LENGTH
        //                 63 - 13 = 50
        if (j > num_chars_in_buffer - XFER_BUFFER_LENGTH)
        {
            Serial.print("not enough chars left --> j: ");
            Serial.print(j);
            Serial.print(" num_chars - XFER buf: ");
            Serial.print(num_chars_in_buffer - XFER_BUFFER_LENGTH);
            Serial.println();
            return;
        }
        const uint8_t SLAVE_ADDRESS = 0x01;
        //if (receive_buffer[j] == START_BYTE) Serial.println("found start byte");
        //if (receive_buffer[j + 1] == SLAVE_ADDRESS) Serial.println("found slave address");
        //if (receive_buffer[j + 2] == message_id) Serial.println("found msg id");

        if ((receive_buffer[j] == START_BYTE)
            && (receive_buffer[j + 1] == SLAVE_ADDRESS)
            && (receive_buffer[j + 2] == message_id))
        {
            Serial.print("Found start sequence at j: ");
            Serial.println(j);

            uint8_t rx_message[XFER_BUFFER_LENGTH + 1] = {0};
            uint8_t rx_msg_byte_counter = 0;

            Serial.print("k|pos: ");

            // build rx_record, as we have anough bytes left to make it
            for (uint8_t k = j; k < (j + XFER_BUFFER_LENGTH); k++)
            {
                Serial.print(k);
                Serial.print("|");
                Serial.print(rx_msg_byte_counter);
                Serial.print(" ");

                rx_message[rx_msg_byte_counter] = receive_buffer[k];
                rx_msg_byte_counter++;
            }

            rx_message[rx_msg_byte_counter] = '\0';
            
            Serial.println();

            // print what's in the rx_message
            for (uint8_t m = 0; m < XFER_BUFFER_LENGTH; m++)
            {
                if (rx_message[m] < 0x10) Serial.print("0");
                Serial.print(rx_message[m], HEX);
                Serial.print(" ");
            }

            return;
        }
    }
}


/*
    //      1  2  3  4  5  6  7  8  9 10 11 12 13
    // --> A5 40 90 08 00 00 00 00 00 00 00 00 7D
    // <-- A5 01 90 08 02 7B 00 00 75 30 03 C1 24

*/
bool request_data(uint8_t message_id, uint8_t frame_amount)
{
    uint8_t tx_checksum = 0x00;                 // transmit checksum buffer
    uint8_t byte_counter = 0;

    // Clear out the buffers
    memset(tx_buffer, 0x00, XFER_BUFFER_LENGTH);
    memset(rx_frame_buffer, 0x00, sizeof(rx_frame_buffer));
    memset(frame_buffer, 0x00, sizeof(frame_buffer));

    // Populate the frame with static data and message ID
    tx_buffer[0] = START_BYTE;
    tx_buffer[1] = HOST_ADRESS;
    tx_buffer[2] = message_id;
    tx_buffer[3] = FRAME_LENGTH;

    // Calculate the checksum
    for (uint8_t i = 0; i < XFER_BUFFER_LENGTH - 1; i++)
    {
        tx_checksum += tx_buffer[i];
    }

    // add check sum to the frame buffer
    tx_buffer[12] = tx_checksum;

    Serial.println();
    Serial.print("--> ");
    Serial.print(millis());
    Serial.print(" (");
    Serial.print(message_id, HEX);
    Serial.print(") ");

    for (uint8_t i = 0; i < sizeof(tx_buffer); i++)
    {
        if (tx_buffer[i] < 0x10)
        {
            Serial.print("0");
        }

        Serial.print(tx_buffer[i], HEX);
        Serial.print(" ");

    }
    Serial.println();

    pre_transmission();

    Serial1.write(tx_buffer, XFER_BUFFER_LENGTH);

    #ifdef R4
    // ... wait for transmission to complete
    // = Block the Arduino until all outgoing data has been sent
    Serial1.flush();
    #endif

    post_transmission();

    // Receive data
    //Serial1.readBytes(rx_frame_buffer, XFER_BUFFER_LENGTH * frame_amount);

    extract_msg_from_serial_buffer(message_id);


  if (1 == 0)
  {

    for (uint8_t i = 0; i < frame_amount; i++)
    {
        for (uint8_t j = 0; j < XFER_BUFFER_LENGTH; j++)
        {
            frame_buffer[i][j] = rx_frame_buffer[byte_counter];
            byte_counter++;
        }

        Serial.print("<-- ");
        Serial.print(millis());
        Serial.print(" (");
        Serial.print(message_id, HEX);
        Serial.print(") ");

        uint8_t rx_checksum = 0x00;

        for (int k = 0; k < XFER_BUFFER_LENGTH - 1; k++)
        {
            rx_checksum += frame_buffer[i][k];

            if (frame_buffer[i][k] < 0x10)
            {
                Serial.print("0");
            }

            Serial.print(frame_buffer[i][k], HEX);
            Serial.print(" ");
        }
        Serial.println();

        // Output debugging information
        char debugBuff[128];

        sprintf
        (
            debugBuff,
            "<UART>[MsgID: 0x%2X][CRC Rec: %2X][CRC Calc: %2X]",
            message_id, rx_checksum, frame_buffer[i][XFER_BUFFER_LENGTH - 1]
        );

        Serial.println(debugBuff);

        if (rx_checksum != frame_buffer[i][XFER_BUFFER_LENGTH - 1])
        {
            Serial.println("<BMS.> CRC fail");
            return false;
        }

        if (rx_checksum == 0)
        {
            Serial.println("<BMS.> No data");
            return false;
        }

        if (frame_buffer[i][1] >= 0x20)
        {
            Serial.println("<BMS.> BMS sleeping");
            return false;
        }
    }
  }
    return true;
}


void setup()
{
    pinMode(MAX485_RE_NEG, OUTPUT);
    pinMode(MAX485_DE, OUTPUT);

    digitalWrite(MAX485_RE_NEG, 0);
    digitalWrite(MAX485_DE, 0);

    Serial.begin(BAUD_RATE_HW);
    delay(100);

    Serial1.begin(BAUD_RATE_485);
    delay(100);

    Serial.println("\nBMS via RS485 ready...");
}

uint8_t delay_between_msgs = 250;

void loop()
{
    request_data(MSG_ID_BATTERY_PACK_DATA, 1);
    /*
    delay(delay_between_msgs);
    request_data(MSG_ID_MIN_MAX_CELLS, 1);
    delay(delay_between_msgs);
    request_data(MSG_ID_MIN_MAX_TEMPERATURE, 1);
    delay(delay_between_msgs);
    request_data(MSG_ID_MOSFET_CHARGE_STATUS, 1);
    delay(delay_between_msgs);
    request_data(MSG_ID_STATUS_INFO, 1);
    delay(delay_between_msgs);
    request_data(MSG_ID_CELL_VOLTAGES, 1);
    delay(delay_between_msgs);
    request_data(MSG_ID_CELL_TEMPERATURES, 1);
    delay(delay_between_msgs);
    request_data(MSG_ID_CELL_BALANCE_STATUS, 1);
    delay(delay_between_msgs);
    request_data(MSG_ID_BATTERY_FAILURE_STATUS, 1);
    */
    delay(3000);
}

In case anyone is keen, feel free to comment on the function void extract_msg_from_serial_buffer(uint8_t message_id).

Thanks for all your input.

Sorry for the delay in responding - life got in the way!

Here's a bit of code I had from a while ago that handled an RS485 message transmission & reception:

const uint32_t TIMEOUT = 500UL;

const byte moist[] = {0x01, 0x03, 0x00, 0x00, 0x00, 0x01, 0x84, 0x0A};

byte values[11];
SoftwareSerial mod(8, 9); // Rx pin, Tx pin

void moisture() {
  uint32_t startTime = 0;
  uint8_t  byteCount = 0;
  
  digitalWrite(DE, HIGH);
  digitalWrite(RE, HIGH);
  delay(10);
  mod.write(moist, sizeof(moist));
  mod.flush();
  digitalWrite(DE, LOW);
  digitalWrite(RE, LOW);

  startTime = millis();
  while ( millis() - startTime <= TIMEOUT ) {
    if (mod.available() && byteCount < sizeof(values) ) {
      values[byteCount++] = mod.read();
    }
  }
}

This should work for you too. Change the "mod" serial object for yours (Serial1). Change the moist array for your array. The pre-transmission and post-transmission are taken care of within the function. Set the size of the values array to the number of bytes you expect to receive - 13 bytes.

You can modify the code so that it exits the while loop before the timeout period expires if all 13 bytes have been received.

1 Like

Thanks for the 'short and sweet' code to fill an array; also, no need to apologise, as there is no expectation of anyone to do anything... :slight_smile:
However, I am always grateful getting a reply to these weird problems I seem to encounter.

Maybe the actual problem went under with all my code and result posting?!

The issue is, I have a BMS, which only replies when queried. However, it seems to return lots of extra bytes, hence, the actual message (of 13 bytes) is somewhere located within the (mostly) 63 bytes of the read buffer.
The result below, is the same as before but simplified:

--> [send query] A5 40 90 08 00 00 00 00 00 00 00 00 7D
Serial1.available() [read] 63 [bytes]
[Serial buffer bytes] FD FB A5 01 90 08 02 0C 00 00 75 37 03 14 0F 7B DB 1D 00 FB EF FF DF DB 1B 00 FF 7F 00 DF DB FB FE DF FF FF DB DB 1B 00 FF FF FF DE FF 1F 00 FF FF DF DF FF FF FF FF DF DF 3B 00 FF FF FF 7B 
[serial read counter] 13319 --- A5 [start byte] count: 9095
Found start [byte] sequence at position 2 in the serial buffer
...
[desired message] A5 01 90 08 02 0C 00 00 75 37 03 14 0F

This line [serial read counter] 13319 --- A5 [start byte] count: 9095 tells us that I have sent the query 13,319 times, and it came back 9095 times with the required start byte.

The 9,095 may not represent the number of completed messages extracted; e.g., the start byte was closer to the end of the buffer, and had not enough byes left to complete the message.

While it works, I still believe there is something fundamentally wrong. The protocol is not optimal. In principle, all three protocols (CAN, RS232 and RS485) provide the identical data structure of 13 bytes, despite data frames that would need to be longer, like the cell voltages, where there can be up to 48 battery cells. Instead of increasing the frame, they split it; and instead of giving the split frames individual IDs, they use the same, and in addition introduce a frame number. Simply bad design.

What I still have to figure out is how to capture the multi-frame message, given the mess of the return message I am getting back.
I have actually managed to get these multi-frame messages reliably on the CAN bus, but not the single frame msgs, which will eventually show corrupted data.

:slight_smile: Maybe I use CAN bus for the multi-frames, and RS485 for the single frames... :slight_smile:

Can you provide a link to the BMS manual that details the serial protocol as there may be some more clues in it.

I appreciate the thought; have attached file protocol for anyone interested... but I bluntly say: don't bother. I am done with this junk, and will get a different BMS. I wasted a week of my life on this .

There are solutions for RS-232, but this port is already used by the Bluetooth module.

DALY-CAN_CommunicationsProtocol_v1.0.pdf (158.2 KB)

Daly UART_485 Communications Protocol V1.2 (1).pdf (164.7 KB)

This 16 x 3.2V 400 Ah LiFePO4 battery, has a GigaVAC 300A relay; I also have a separate 5A balancer on it, as well as individual cell (over/under voltage) modules; hence, I can just take the BMS out altogether.

In any case, I am out... thanks for all the support.