Stránka 1 z 1

BMS s komunikací RS485/232/Modbus

PříspěvekNapsal: úte úno 23, 2021 2:55 pm
od luky
Přečetl jsem celé forum i spoustu dalšího, ale nejsem z toho moudrej:
Potřebuji ochránit baterii LiFePO 48V/110Ah (16s) a rád bych naměřené SOC, napětí článků, aktuální proud apod. četl pomocí PLC (Domat markMX). Mám k dispozici RS485(+ModbusRTU), RS232, Ethernet (+ModbusTCP).

Existuje nějaká BMS, ke které existuje nějaký rozumný popis komunikace? Jestli to bude balancování + hlídání napětí článků + počítání SOC v jedné nebo 3 krabicích už je celkem jedno.

Re: BMS s komunikací RS485/232/Modbus

PříspěvekNapsal: úte úno 23, 2021 3:33 pm
od xmasin
Pro některé čínské BMS (třeba tyto https://www.aliexpress.com/item/32876909159.html?spm=a2g0s.9042311.0.0.27424c4dyd8kGQ) existuje dokumentace komunikačního protokolu https://github.com/simat/BatteryMonitor/wiki
Kolega koupil BMSChargery http://chargery.com/balancer.asp a kněmu našel také komunikační protokol.

Re: BMS s komunikací RS485/232/Modbus

PříspěvekNapsal: stř úno 24, 2021 9:23 am
od luky
Ano, to první pochází z [url]lithiumbatterypcb.com[/url]. K tomu existuje knihovna pro Arduino. Tedy dalo by se to z toho vyčíst nebo spíš vložit Arduino jako převodník na ModbusRTU. viz. https://github.com/rahmaevao/JbdBms a https://hackaday.io/project/162806-jbd-bms-protocol
Bohužel se tohle pro 16s dělá maximálně na 60A, což je pro domácí elektrárnu málo (uvažuju 100A, špičky <2s 200A).

Ještě jsem našel DALY BMS s UARTem nebo RS485 https://www.aliexpress.com/item/4001321708781.html?spm=2114.12010612.8148356.11.519c700dKSfzra
...ale k tomu se mi nepodařilo najít popis komunikace nebo zmínku, že by se tím někdo zabýval.

Jedna varianta je i DIYBMS v4. Jestli to dobře chápu, je to napsané v Arduinu, takže přidat tam Modbus komunikaci by neměl být úplně velký problém.

Re: BMS s komunikací RS485/232/Modbus

PříspěvekNapsal: stř úno 24, 2021 10:56 am
od xmasin
luky píše:Ano, to první pochází z [url]lithiumbatterypcb.com[/url]. K tomu existuje knihovna pro Arduino. Tedy dalo by se to z toho vyčíst nebo spíš vložit Arduino jako převodník na ModbusRTU. viz. https://github.com/rahmaevao/JbdBms a https://hackaday.io/project/162806-jbd-bms-protocol
Bohužel se tohle pro 16s dělá maximálně na 60A, což je pro domácí elektrárnu málo (uvažuju 100A, špičky <2s 200A).

Dělají se i varianty s proudem až 250A, stačí hledat na Aliexpresu. V tomhle obchodě mají 100A verzi pro LiFe https://www.aliexpress.com/item/1005001831386283.html?spm=2114.12010612.8148356.58.340a3de20g6g5v

luky píše:Ještě jsem našel DALY BMS s UARTem nebo RS485 https://www.aliexpress.com/item/4001321708781.html?spm=2114.12010612.8148356.11.519c700dKSfzra
...ale k tomu se mi nepodařilo najít popis komunikace nebo zmínku, že by se tím někdo zabýval.

K tomuhle popis komunikace mám, jestli jsem to dobře pochopil, tak se jedná o CANbus nad různými fyzickými rozhraními. Jestli chceš, tak můžu poslat, je to ve wordu anglicko-čínsky.

Re: BMS s komunikací RS485/232/Modbus

PříspěvekNapsal: stř úno 24, 2021 12:25 pm
od JiTr
>>> xmasin: Mohl by jsi mi prisimte poslat popis komunikacniho protokolu pres RS232 pro BMS Chargery?

Re: BMS s komunikací RS485/232/Modbus

PříspěvekNapsal: stř úno 24, 2021 2:17 pm
od xmasin
JiTr píše:>>> xmasin: Mohl by jsi mi prisimte poslat popis komunikacniho protokolu pres RS232 pro BMS Chargery?

Popis protokolu je k mání tady:
http://www.chargery.com/uploadFiles/BMS24T,16T,8T%20Additional%20Protocol%20Info%20V1.25.pdf
A našel jsem nějaký projekt, který integruje Chargery BMS do VenusOS od Victronu:
https://github.com/Tobi177/venus-chargerybms

Re: BMS s komunikací RS485/232/Modbus

PříspěvekNapsal: stř úno 24, 2021 4:07 pm
od JiTr
>>> xmasin: Diky
Bonus Chargery BMS vidim v tom, ze umi balancovat proudem 1A ...

Re: BMS s komunikací RS485/232/Modbus

PříspěvekNapsal: čtv úno 25, 2021 8:34 pm
od xmasin
JiTr píše:>>> xmasin: Diky
Bonus Chargery BMS vidim v tom, ze umi balancovat proudem 1A ...

Ten Chargery se mně taky líbí, už ho mám na cestě ve variantě se 100A bočníkem. A pokud bude vyhovvoat, tak objednám ještě dva, aby měl každý ze tří bateriových packů svůj.

Re: BMS s komunikací RS485/232/Modbus

PříspěvekNapsal: ned úno 28, 2021 9:16 am
od luky
xmasin píše:
luky píše:Ještě jsem našel DALY BMS s UARTem nebo RS485 https://www.aliexpress.com/item/4001321708781.html?spm=2114.12010612.8148356.11.519c700dKSfzra
...ale k tomu se mi nepodařilo najít popis komunikace nebo zmínku, že by se tím někdo zabýval.

K tomuhle popis komunikace mám, jestli jsem to dobře pochopil, tak se jedná o CANbus nad různými fyzickými rozhraními. Jestli chceš, tak můžu poslat, je to ve wordu anglicko-čínsky.

Ahoj, prosím o zaslání popisu komunikace pro RS485 na DALY BMS.
Předem díky
L.

Re: BMS s komunikací RS485/232/Modbus

PříspěvekNapsal: pon bře 08, 2021 3:32 pm
od kodl69
Dnes mi přišla reklama z TI na BQ76952PFBR . 1-16s BMS/balance, dají se k tomu připojit snadno mosfety s externími odpory pro balancování větším proudem, má to komunikaci přes I2C, SPI, tj někdo trochu zručný k tomu dopíše komunikaci přes nějakej procesor s čímkoliv, případně nějaký rozhraní s displejem a enkodérem pro nastavení, možností mnoho. Cenovka švába celkem přijatelná, cca 150Kč s DPH, ostatně tohle asi dává číňan do těch "smart BMS" .

Re: BMS s komunikací RS485/232/Modbus

PříspěvekNapsal: pon bře 08, 2021 5:06 pm
od marsal64
luky píše:Ahoj, prosím o zaslání popisu komunikace pro RS485 na DALY BMS.

Nabízím svůj "překlad" z čínštiny ;-/
Pokud má někdo něco lepšího, můžete to prosím sdílet?

Re: BMS s komunikací RS485/232/Modbus

PříspěvekNapsal: pát črc 16, 2021 1:19 pm
od ViktorEX
ahojte chlapi, nasel jsem toto vlakno ... rozchodil jste to nekdo? trochu programovani, dam, ale nektere veci uz mimo moje chpani resp. bych potreboval poradne nakopnout.
BMS DALY, mam tam vsechna rozhrani (UART/RS482 ... celkem 4 konektory), a rad bych cetl hodnoty arduinem. tj stav napeti, stav jednotlivych clancich atd

dik

Re: BMS s komunikací RS485/232/Modbus

PříspěvekNapsal: pát črc 16, 2021 1:57 pm
od rottenkiwi

Re: BMS s komunikací RS485/232/Modbus

PříspěvekNapsal: sob črc 17, 2021 7:58 am
od marsal64
ViktorEX píše:rozchodil jste to nekdo?

Jasně :-), v mém případě jak jinak než na Teco Foxtrotu.

Re: BMS s komunikací RS485/232/Modbus

PříspěvekNapsal: sob črc 17, 2021 10:17 pm
od ViktorEX
hmm, hezky ... ale dal mi to nepomohlo... nemate prosim nekdo nejaky vzorovy programek?

Re: BMS s komunikací RS485/232/Modbus

PříspěvekNapsal: ned črc 18, 2021 6:49 am
od rottenkiwi

Re: BMS s komunikací RS485/232/Modbus

PříspěvekNapsal: ned črc 18, 2021 7:23 am
od marsal64
ViktorEX píše:hmm, hezky ... ale dal mi to nepomohlo... nemate prosim nekdo nejaky vzorovy programek?

Tak je to ten Foxtrot, třeba trochu pomůže pro inspiraci.

Kód: Vybrat vše
VAR_GLOBAL
  NUM_DALYS : USINT := 2;  // number of Dalys present
  CH_DALYS : UINT := CH3_UNI;  // UNI channel for the communication
  DALYADDR : ARRAY[1..3] of BYTE := [16#40, 16#41, 0];  // Daly addresses
  Timeout_recv_Daly : TIME := T#100MS;        // How long wait for Daly response
  CurrentBatSum : Real;   // sum of battery current from all BMS
  CurrentBatSum_cl : CL;   // color of battery current from all BMS: > 0.5 green; < -0.5 orange; else green

END_VAR


// Structure for receiving and storing raw Daly data
TYPE
  DALYBMSDATA : STRUCT

    LastError_text : STRING;
    LastError_datetime : DT;
    ErrorCount : USINT;
    LastReceive_datetime : DT;
    ReceiveOKCount : USINT;  // counter of correct communications
    maxvoltage : REAL; // maximum cell voltage
    minvoltage : REAL; // minimum cell voltage
    totvoltage : REAL; // total battery voltage "pressure"
    current : REAL; // proud
    SOC : REAL; // SOC
    minVcellNo: USINT;  // cell no with minimum voltage
    maxVcellNo: USINT;  // cell no with maximum voltage
    temperature : INT; // temperatureplota
    state : USINT; // 0 - static  1 - charge   2 - discharge
    statea : CL; // rgb
    balancing : string[4];   // 16 battery cells in pack expected here
    MOScharging : USINT; // 1 - MOS for charging on
    MOSLoad : USINT;   // 1 - MOS for discharging on
    faultstatus1 : string[8];  // bits indicating error
    faultstatus2 : string[8];  // bits indicating error 2
  END_STRUCT
END_TYPE

// Daly values store
VAR_GLOBAL RETAIN
  dalys : ARRAY[1..3] OF DALYBMSDATA;
  daly_diffvoltage_1 : real; // difference of voltage between min and max for battery 1
  daly_diffvoltage_2 : real; // difference of voltage between min and max for battery 2
END_VAR



Kód: Vybrat vše
// Communicate with Daly BMS

// Expected RS-485, all Dalys to use a single communication channel


// function to calculate checksum
FUNCTION bchksm : USINT
  VAR_INPUT
    cfrom : PTR_TO USINT; // USINT pointer to start from
    numbytes : UINT; // number of bytes
  END_VAR
  VAR
    a : USINT;
  END_VAR

  REPEAT
    a := a + cfrom^;
    cfrom := cfrom + 1;
    numbytes := numbytes - 1;
  UNTIL numbytes = 0 END_REPEAT;
  bchksm := a;
END_FUNCTION

// Structure for received message
TYPE
  Dalyrecv : STRUCT
    error : STRING[250]; // error found, '' if none
    status : USINT; // processing status: 0: ready, 1: sending, 2: receiving, 3: result ready, 4: error
    lenmesrec : UINT; // length of the raw received message
    datarec : STRING[13]; // raw content of the received message
  END_STRUCT
END_TYPE


// send and receive one message from/to Daly
FUNCTION_BLOCK fbDalySendRec1
  VAR_INPUT
    init : BOOL; // trigger to start processing
    dst_addr : BYTE;    // address of Daly to send data to
    data_id : BYTE;   // command id
  END_VAR
  VAR_OUTPUT
    rm : Dalyrecv; // Daly status and data structure
  END_VAR
  VAR
    m : STRING[13]; // message data to send
    r : STRING[13]; // message data received
    pb : PTR_TO BYTE;
    pd : PTR_TO DWORD;
    // helpers
    chs : USINT;
    SendToDaly : fbSendTo;
    RecvFromDaly : fbRecvFrom;
    recvTimer : TON; // receiving timer
  END_VAR

  // init and send the message?
  IF init THEN
    recvTimer(IN := False, PT := Timeout_Recv_Daly);

    // construct the message to be sent
    rm.error := ''; // expect no error
    pb := adr(m); // to initial byte
    pb^ := 16#A5;
    pb := pb + 1; // to communication module address
    pb^ := dst_addr;
    //pb^ := SHL(dst_addr, 4);
    pb := pb + 1; // to data id
    pb^ := data_id;
    pb := pb + 1; // to data legth
    pb^ := 8;
    // may process data to send here, if any
    pd := pb + 1; // to first data DWORD
    pd^ := 0;
    pd := pd + 4; // to second data DWORD
    pd^ := 0;
    pb := pd + 4; // to checksum
    pb^ := USINT_TO_BYTE(bchksm(adr(m), 12));

    // send
    SendToDaly(rq :=True, chanCode := CH_DALYS, lenTx := 13, data := void(m));

    // initiate receiving timer
    recvTimer(IN := True);
   
    rm.status := 2; // receiving
  END_IF;

  // exit if the main program did not processed the response (3 = response data, 4 = error), then return without further processing
  IF rm.status > 2 THEN
    RETURN;
  END_IF;

  // initiate message receive if status = 2
  RecvFromDaly(rq :=True, chanCode := CH_DALYS, lenRx := 13, data := void(r));

  // message not received?
  IF not RecvFromDaly.mesRec THEN
    // timeout?
    IF recvTimer.Q THEN
      rm.error := 'General receive error: response timeout';
      rm.status := 4; // present the error
      return;
    ELSE
      return; // message not received and no timeout - continue waiting for response
    END_IF;
  ELSE // message received

    // message received - check general error
    IF RecvFromDaly.error <> 0 THEN
      rm.error := concat('General receive error: ', GetLastComErrTxt(RecvFromDaly.error));
      rm.status := 4; // indicate the error
      return;
    END_IF;
    // no error, start parsing
    rm.lenmesrec := RecvFromDaly.lenData;
    // despite potenital error, copy the received data to the output variable, if not too long
    IF rm.lenmesrec <= 13 THEN
      Memcpy(length := rm.lenmesrec, source := void(r), dest := void(rm.datarec));
    END_IF;
    pb := adr(rm.datarec); // to start_byte
    // check start byte
    IF not pb^ = 16#a5 THEN
      // first byte is not A5
      rm.error := 'Daly receive error: the first byte is not A5';
      rm.status := 4; // indicate the error
      return;
    END_IF;
    // verify checksum
    pb := pb + 12;
    chs := BYTE_TO_USINT(pb^);
    IF chs <> bchksm(adr(rm.datarec), 12) THEN
      rm.error := 'Daly receive error: checksum verification failed';
      rm.status := 4; // indicate the error
      return;
    ELSE
      // indicate correctly received data
      rm.status := 3;
    END_IF;
  END_IF;
END_FUNCTION_BLOCK


PROGRAM DalyBMS
  VAR
    a : fbDalySendRec1; // function block to send one message to Studer and receive response
    s : Dalyrecv; // structure with receive data
    tinit : BOOL := True; // for the first run, lanuch
    pw : PTR_TO WORD;
    pb : PTR_TO BYTE;
    pd : PTR_TO DWORD;
    daly_id : USINT := 1;           //current Daly to process
    daly_addr : BYTE ;               // current Daly address
    COMMAND_MIN : USINT := 16#90;    // minimum no of command to be sent
    COMMAND_MAX : USINT := 16#98;   // maximum no of command to be sent
    command_id : USINT := 16#90;  // work variable - current command_id
    i : USINT;
  END_VAR

  // if tinit, go to next command or next Daly
  if tinit THEN
    command_id := command_id + 1;
    if command_id > COMMAND_MAX THEN
       // first command id
       command_id := COMMAND_MIN;
       // next daly
       daly_id := daly_id + 1;
       if daly_id > NUM_DALYS THEN daly_id := 1; END_IF;
    end_if;
  END_IF;

  // take daly address
  daly_addr := DALYADDR[daly_id];

  // process the current command for the current Daly
  a(init := tinit, dst_addr := daly_addr, data_id := USINT_TO_BYTE(command_id), rm => s);
  tinit := False; // synchronous - wait for response

    // verify if response or came and process
  CASE s.status OF
    2:
       RETURN;

    4 :
      // mark error to the Daly data
      dalys[daly_id].LastError_text  := s.error;
      dalys[daly_id].LastError_datetime  := GetDateTime();
      dalys[daly_id].ErrorCount  := dalys[daly_id].ErrorCount + 1;
      tinit := True;

    3 :
      // mark last received data to Daly
      dalys[daly_id].LastReceive_datetime := GetDateTime();
      dalys[daly_id].ReceiveOKCount := dalys[daly_id].ReceiveOKCount + 1;
      tinit := True;
     
      // parse - go to the first data byte
      pw := adr(s.datarec) + 4;
      pb := pw;
      pd := pw;

      case command_id of

      16#90:
         dalys[daly_id].totvoltage := int_to_real(int_to_int(word_to_int(pw^))) * 0.1;
         pw := pw + 4;
         dalys[daly_id].current := - (int_to_real(int_to_int(word_to_int(pw^))) - 30000) * 0.1; // A
         pw := pw + 2;
         dalys[daly_id].SOC := int_to_real(int_to_int(word_to_int(pw^))) * 0.1; // perc.

      16#91:
         dalys[daly_id].maxvoltage := int_to_real(int_to_int(word_to_int(pw^))) / 1000; // mV
         pb := pw + 2;
         dalys[daly_id].maxVcellNo := byte_to_usint(pb^);
         pw := pw + 3;
         dalys[daly_id].minvoltage := int_to_real(int_to_int(word_to_int(pw^))) / 1000;
         pb := pb + 3;
         dalys[daly_id].minVcellNo := byte_to_usint(pb^);
      16#92:
         dalys[daly_id].temperature := byte_to_int(pb^) - 40;  // only one thermometer expected here
      16#93:
         dalys[daly_id].state := byte_to_usint(pb^);  // 0 - static  1 - charge   2 - discharge

         case dalys[daly_id].state of
         // static - grey
         0: dalys[daly_id].statea.r := 212;
            dalys[daly_id].statea.g := 212;
            dalys[daly_id].statea.b := 212;

         // charge - green
         1: dalys[daly_id].statea.r := 0;
            dalys[daly_id].statea.g := 200;
            dalys[daly_id].statea.b := 0;

         // discharge - orange
         2: dalys[daly_id].statea.r := 255;
            dalys[daly_id].statea.g := 128;
            dalys[daly_id].statea.b := 0;
         end_case;

         pb := pb + 1;
         dalys[daly_id].MosCharging := byte_to_usint(pb^);
         pb := pb + 1;
         dalys[daly_id].MosLoad := byte_to_usint(pb^);
         
         // force switch to nearest processed command
         // switches also 95 and 96 which could not be processes in this program
         
         command_id := 16#96;  // will be incremented
         
      16#97:
         dalys[daly_id].balancing := word_to_stringf(pw^, '%04X');         // bit = the cell is balancing
      16#98:
         dalys[daly_id].faultstatus1 := dword_to_stringf(pd^,'%08X');
         pd := pd + 4;
         dalys[daly_id].faultstatus2 := dword_to_stringf(pd^,'%08X');
      END_CASE;

  END_CASE;


  // calculate total current from batteries
  CurrentBatSum := 0;
 
  for i := 1 to NUM_DALYS DO
    CurrentBatSum := CurrentBatSum + dalys[i].current;
  end_for;

  if CurrentBatSum > 0.5 THEN // charge - green
    CurrentBatSum_cl.r := 0;CurrentBatSum_cl.g := 200;CurrentBatSum_cl.b := 0;
  elsif CurrentBatSum < -0.5 THEN // discharge - orange
     CurrentBatSum_cl.r := 255;CurrentBatSum_cl.g := 128;CurrentBatSum_cl.b := 0;
  else // static - grey
     CurrentBatSum_cl.r := 212;CurrentBatSum_cl.g := 212;CurrentBatSum_cl.b := 212;
  end_if;

  // actualization of the global variables
  daly_diffvoltage_1 := dalys[1].maxvoltage - dalys[1].minvoltage;
  daly_diffvoltage_2 := dalys[2].maxvoltage - dalys[2].minvoltage;
END_PROGRAM