Connect DIY BMS via CAN to Cerbo GX - Eigenes BMS via CAN mit Cerbo GX verbinden

German below.

Hello everyone,

I have a custom-built BMS that can send CAN telegrams. I would like to transmit battery information to the Cerbo GX. I am using 500 kbit/s, 11-bit identifiers, and I’m sending the messages 0x351, 0x355, 0x356, and 0x35A with the appropriate content/scaling, based on a document from Victron from the year 2021. However, no external battery is being recognized.

When I send messages 0x35E (heartbeat) and 0x35C (serial number/manufacturer info), the Remote Console displays an “LG RESU”. However, voltage, current, and SOC are not being displayed.

I would prefer not to reverse-engineer everything from Pylontech or other manufacturers. Is there a way to obtain an up-to-date version of the protocol documentation?

Thank you!

Hallo zusammen,

ich habe ein Eigenbau BMS welches CAN Telegramme senden kann. Ich möchte die Infos über meine Batterie an das Cerbo GX übergeben. Ich sende mit 500kbit/s, 11bit Identifier und sende die Botschaften 0x351, 0x355, 0x356 and 0x35A mit passendem Inhalt/Skalierung, entnommen aus einem Dokument von victron aus dem Jahr 2021. Es wird keine externe Batterie erkannt.

Sende ich hingegen Botschaften 0x35E (Heartbeat), und 0x35C (Seriennummer/Herstellerinfo), wird mir in der Remote Console eine “LG resu” angezeigt. Spannung, Strom und SOC werden hingegen nicht angezeigt.

Ich würde jetzt ungern alles von Pylontech oder anderen Herstellern im Reengineering zusammensuchen müssen, gibt es die Möglichkeit eine aktuelle Version der Protokollbeschreibung zu bekommen?

Vielen Dank

Can you please tell us what are you sending (the data bytes) for each CAN ID?
BTW:

  • 35E is manufacturer name, not heartbeat, so only ASCII chars in all 8 bytes.
  • 35C is a battery request to inverter, generally only first byte is defined, bit encoding.

Hey,

All values are in decimal unless prefixed with 0x.
0x521–0x524 are from my current shunt.
0x351 = 500 (uint16), 100 (int16), -100 (int16), 360 (uint16) // I believe this is correct, or should -100 be sent as 100?
0x355 = 80 (uint16), 95 (uint16), 8034 (uint16)
0x356 = 5223 (int16), 50 (int16), 298 (int16)
0x35A = warnings for testing and system online status

Is this correct?

Best regards,
Paul

Zusammenfassung

Dieser Text wird ausgeblendet

This is the remote console. Hey get the LG resu with funny values

after an redetect, “CAN-bus BMS battery” is shown in the remote console. The values are still wrong like in the last picture :frowning:

At a first glance, you stored the values in big endian order. The values must be stored in little endian order.
For example, for 0x356:

  • bytes 0-1, in your order, but interpreted in little-endian are 0x6714 = 26388 decimal, which is displayed 263.88V
  • bytes 2-3, in your order, but interpreted in little-endian are 0x3200 = 12800 decimal, which is displayed 1280.0A

Correct the endianness for correct display.

Also, the charge and discharge currents are positive values, so 100A even for discharge, but stored as 1000, because the value, according with documentation, is in 0.1A units, so 0x3E8, in 0xE8, 0x03 order.

Please, pay attention to the correct fields, their unit denomination (scaling) and their data size.

Also take a look at the Pylontech CAN protocol, found on the web.

1 Like

@Paul88

ich würde gerne mit Dir Erfahrung austauschen. Da PN nicht funktioniert, sende mir bitte eine Mail an bk-ve (at) GMX (Punkt) de

VG Björn

Please, also share it with us, maybe somebody could benefit from the info… :folded_hands:
Thanks!

Hello everyone,

I’m checking back in.

First of all: Thanks to Alex – the hint with the Little Endian format was the key!

Now all values (voltage, current, SOC, cell_min, cell_max, Tmin, Tmax) are arriving correctly at the Cerbo.

It’s sufficient to send the 4 messages to keep the system “happy.”

void Send_BMS_0x351_DynamicCVL(uint16_t ccl_01A, uint16_t dcl_01A)
{
    CAN_TxHeaderTypeDef TxHeader = { .StdId = 0x351, .IDE = CAN_ID_STD, .RTR = CAN_RTR_DATA, .DLC = 8 };
    uint8_t TxData[8] = { 0 };
    uint32_t TxMailbox;

    extern int32_t u2;       // Systemspannung in mV
    extern int32_t current;  // Strom in mA (positiv = laden, negativ = entladen)
    extern bool cvl_reduced;
    extern uint32_t cvl_reduce_start;

    uint32_t now = HAL_GetTick();
    uint16_t cvl_01V;

    if (ccl == 0)
    {
        // Ladeverbot → Ladegrenze absenken
        if (!cvl_reduced)
        {
            cvl_reduce_start = now;
            cvl_reduced = true;
        }

        if ((int32_t)(now - cvl_reduce_start) < 10000)
        {
            // Für 10 s → ganz sperren
            cvl_01V = 0;
        }
        else
        {
            // Danach → Entladung weiterhin erlauben
            cvl_01V = (uint16_t)(u2 / 100); // Systemspannung in 0.1V
        }
    }
    else
    {
        // Laden erlaubt → hohe CVL für MPPT
        cvl_01V = 520;  // 52.0 V
        cvl_reduced = false;
    }

    // CVL
    TxData[0] = cvl_01V & 0xFF;
    TxData[1] = (cvl_01V >> 8) & 0xFF;

    // CCL
    TxData[2] = ccl_01A & 0xFF;
    TxData[3] = (ccl_01A >> 8) & 0xFF;

    // DCL
    TxData[4] = dcl_01A & 0xFF;
    TxData[5] = (dcl_01A >> 8) & 0xFF;

    // Discharge voltage (nicht genutzt)
    TxData[6] = 0;
    TxData[7] = 0;

    HAL_CAN_AddTxMessage(&hcan, &TxHeader, TxData, &TxMailbox);
}




void Send_BMS_0x355(void)
{
	CAN_TxHeaderTypeDef TxHeader = { .StdId = 0x355, .IDE = CAN_ID_STD, .RTR = CAN_RTR_DATA, .DLC = 8 };
	uint8_t TxData[8];
	uint32_t TxMailbox;

	uint16_t soh = 95;          // 95 % → ×1
	uint16_t soc_hr = 8044;     // 80.44 % → ×0.01

	TxData[0] = SOC & 0xFF;
	TxData[1] = SOC >> 8;
	TxData[2] = soh & 0xFF;
	TxData[3] = soh >> 8;
	TxData[4] = soc_hr & 0xFF;
	TxData[5] = soc_hr >> 8;
	TxData[6] = 0;
	TxData[7] = 0;

	HAL_CAN_AddTxMessage(&hcan, &TxHeader, TxData, &TxMailbox);
}

void Send_BMS_0x356(void)
{
	CAN_TxHeaderTypeDef TxHeader = { .StdId = 0x356, .IDE = CAN_ID_STD, .RTR = CAN_RTR_DATA, .DLC = 8 };
	uint8_t TxData[8];
	uint32_t TxMailbox;

	int16_t voltage = (int16_t) (u2 / 10);         // mV → 0.01 V
	int16_t current1 = (int16_t) (current / 100);  // mA → 0.1 A
	int16_t temp = (int16_t) (Tmax * 10.0f);       // °C → 0.1 °C

	TxData[0] = voltage & 0xFF;
	TxData[1] = voltage >> 8;
	TxData[2] = current1 & 0xFF;
	TxData[3] = current1 >> 8;
	TxData[4] = temp & 0xFF;
	TxData[5] = temp >> 8;
	TxData[6] = 0;
	TxData[7] = 0;

	HAL_CAN_AddTxMessage(&hcan, &TxHeader, TxData, &TxMailbox);
}

void Send_BMS_0x35A(void)
{
    CAN_TxHeaderTypeDef TxHeader = {
        .StdId = 0x35A,
        .IDE = CAN_ID_STD,
        .RTR = CAN_RTR_DATA,
        .DLC = 8
    };
    uint32_t TxMailbox;
    HAL_CAN_AddTxMessage(&hcan, &TxHeader, alarm_flags, &TxMailbox);
}


void Send_BMS_0x373(void)
{
	CAN_TxHeaderTypeDef TxHeader = { .StdId = 0x373, .IDE = CAN_ID_STD, .RTR = CAN_RTR_DATA, .DLC = 8 };
	uint8_t TxData[8];
	uint32_t TxMailbox;

	// Umrechnung Spannungen in mV
	uint16_t Zell_min_mV = (uint16_t) (Zell_min * 1000.0f);
	uint16_t Zell_max_mV = (uint16_t) (Zell_max * 1000.0f);

	// Umrechnung Temperaturen in Kelvin (ganzzahlig)
	uint16_t Tmin_K = (uint16_t) (Tmin + 273.15f);
	uint16_t Tmax_K = (uint16_t) (Tmax + 273.15f);

	// Zellspannung min
	TxData[0] = Zell_min_mV & 0xFF;
	TxData[1] = Zell_min_mV >> 8;

	// Zellspannung max
	TxData[2] = Zell_max_mV & 0xFF;
	TxData[3] = Zell_max_mV >> 8;

	// Temperatur min
	TxData[4] = Tmin_K & 0xFF;
	TxData[5] = Tmin_K >> 8;

	// Temperatur max
	TxData[6] = Tmax_K & 0xFF;
	TxData[7] = Tmax_K >> 8;

	HAL_CAN_AddTxMessage(&hcan, &TxHeader, TxData, &TxMailbox);
}

Message 0x351 is a bit confusing, and that’s exactly where I’m stuck right now.

In addition to the Cerbo GX, the Multi, and the DIY battery, I also have an MPPT 150/45 in the system.
I’m using a Li-ion battery (12s). This means I have a voltage range of 36–50 V, and here’s the issue:

If I want to discharge at full power, I have to set the CVL value (which is actually supposed to be the charge voltage limit…) to the current battery voltage or slightly below. The MultiPlus uses this value to regulate discharge current and increasingly limits it as battery voltage drops.

If I set CVL to 52 V, I can only discharge at max 7 A at 42 V → not good.
Solution: I lower the CVL value — but then the MPPT “thinks” that the target charge voltage has already been reached and stops charging → also not good.

Its written down in the “Victron CAN-bus BMS Protocol” application note, point 1.5.3.

Either I’ve misunderstood something, or the Victron ecosystem is more tailored to LiFePO4 cells, which have a much narrower voltage range.
DVCC is enabled for voltage and current. All values are provided by the BMS.

Is this normal? Is it supposed to work like this? Or is it possible to use Byte 6&7 of 0x351 for discharge voltage?
Is there any meaningful setting or workaround?
Is there a way to export the whole setup so someone with more experience can have a look?

Paul

0x351 must be implemented purely from the battery point of view and needs.
Based on those values, the system tries to play with the voltages in order to keep everybody happy, battery, feed, consumption, etc.
Although not always you can make everybody happy… :wink:
So playing with the CVL, could interfere with the system way of thinking.
You can study a little how the system works, looking at the sources of DVCC here:

Hey,

thanks for the link. CVL is set constantly at 52V. Charging works perfectly — I reduce the current to 10A at 4.05V, and shut it off at 4.15V. It only turns back on with a hysteresis and timeout.

Discharging also works fine until the voltage drops to 41.2V (not 42V as I initially thought). At that point, it suddenly cuts the current down to 14–15A and then continues to reduce it further.

I believe this is due to the settings in the MP2, specifically the ESS Assistant. There, I can only set a minimum of 36V for the dynamic cut-off, but the restart offset has to be 41.2V (VE.Configure won’t allow anything lower).
Is there any way to bypass this and control the MP2 entirely via BMS data? I know my 12S Li-ion setup isn’t ideal, but is there still a way to make it work?

Best regards,
Paul

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.