Wiznet makers

Alan

Published March 03, 2023 ©

51 UCC

36 WCC

91 VAR

0 Contests

0 Followers

0 Following

Original Link

MODBUS RTU <–> MODBUS TCP / UDP GATEWAY POSTAVENÁ NA ARDUINU

MODBUS RTU <–> MODBUS TCP / UDP GATEWAY POSTAVENÁ NA ARDUINU

COMPONENTS
PROJECT DESCRIPTION

Co to je:

  • Transparentní gateway, která umí překládat mezi Modbus RTU na jedné straně a Modbus TCP a Modbus UDP na straně druhé.
  • Slave (server odpovídající na requesty) je na straně RTU, master (klient odesílající requesty) je na straně TCP či UDP. Na TCP straně může být najednou připojeno 8 klientů.
  • Gateway je transparentní, to znamená, že samotná Modbus data (PDU) se nijak nemění. Mění se pouze hlavičky, které se u Modbus RTU a Modbus TCP (resp. Modbus UDP) liší. To znamená, že:
    • gateway je poměrně rychlá a jednoduchá, v pohodě ji zvládne Arduino Nano
    • gateway podporuje všechny funkce Modbus protokolu (vč. proprietárních)
    • na RTU stranu můžete připojit maximum slave zařízení, které lze přes Modbus protokol připojit (tj. 247)
    • gateway umí přeposílat i Modbus broadcasty a chybové kódy
  • Modbus RTU je pomalý a synchronní, Modbus TCP / UDP je rychlý a asynchronní. Z tohoto důvodu má gateway vyrovnávací paměť (frontu) na požadavky přicházející přes TCP resp. UDP kanál.

K čemu to je:

  • Protokol Modbus TCP slouží jako náhrada za “Modbus Extension”. Gateway se k Loxone připojí jako “Modbusserver”.
  • Protokol Modbus UDP můžete použít k obejití nejrůznějších “Modbus kurvítek”, které Loxone implementoval ve svém Modbus RTU protokolu (tj. “Modbus Extension”) i v Modbus TCP protokolu (tj. “Modbusserver”). Gateway můžete k Loxone připojit i přes kombinaci UDP výstupů (odesílání požadavku) a UDP vstupů (přijetí a parsování odpovědi).

Nákupní seznam:

Místo Nano můžete samozřejmě použít i Uno a příslušný ethernet shield. MAX485 je nejpoužívanější převodník mezi RS485 a TTL signálem – možná se na to dají použít i jiné čipy ale to nemám vyzkoušeno.

Zapojení:

Zapojení mezi Arduinem a MAX485 je jednoduché:

Arduino <-> MAX485
Tx1 <-> DI
Rx0 <-> RO
Pin 6 <-> DE,RE

Jak vidíte, MAX485 připojíme na jediný sériový port, který má Nano k dispozici, takže na případný debugging nemůžeme použít USB a sériový monitor v Arduino IDE. Ale nemusíte z toho být smutní, pokud by bylo potřeba ladit a řešit problémy, doporučuji toto:

  • sledovat na Arduinu Rx a Tx diody, jestli přes sériový port odchází z gateway požadavky (Tx) a jestli na ně vaše Modbus RTU zařízení odpovídají (Rx).
  • na ladění se dají docela dobře použít UDP zprávy. Tj. v inkriminovaném místě kódu můžete místo obvyklého Serial.println(…); použít:
    • Udp.beginPacket(vzdalenaIp, vzdalenyDebugPort);
    • Udp.print(…);
    • Udp.endPacket;
  • … a pakety pak na inkriminovaném portu chytat přes Wireshark

Programování Arduina:

Skeč do Arduina je tady:

https://github.com/budulinek/Arduino-Modbus-RTU—Modbus-TCP-UDP-Gateway

Do Arduina IDE si doinstalujte potřebné knihovny (viz začátek skeče). Všechny jsou dostupné přes manažer knihoven (“Spravovat knihovny…”).

Stručně co skeč dělá:

  • přijme Modbus požadavek přes UDP nebo TCP
  • zkontroluje hlavičky a uloží požadavek do fronty (vč. metadat o odesílateli požadavku)
  • pokud je ve frontě nějaký požadavek, upraví jeho formát (přidá CRC) a odešle ho do sériového portu (tj. na RS485 linku)
  • až přijde odpověď, tak ji pošle zpátky tam, odkud přišel požadavek
  • teprve až se vrátí odpověď (nebo vyprší timeout), může se na sériový port poslat další požadavek

Samotný sériový port / RS485 linka je samozřejmě úzkým hrdlem celé gateway (RS485 je halfduplex, navíc Modbus RTU standardně běží na rychlosti jenom 9600 baud). Co s tím:

  • Na čekání na odpověď ze sériové linky je samozřejmě timeout. Udělá se pár opakovaných požadavků a pokud se stále nic neděje, pošle se zpátky chybový kód.
  • Všechny funkce Arduina (čekání na odpověď, čtení ze sériového portu, ale i odesílání na sériový port) jsou psány jako non-blocking.
  • Arduino si pamatuje, které Modbus RTU slave zařízení odpovídá na zaslané požadavky a které nikoliv a podle toho řídí provoz ve frontě:
    • ve frontě může být pouze jeden požadavek směřovaný na zařízení, které neodpovídá
    • z fronty jsou přednostně odbavovány požadavky směřující na zařízení, která reagují na požadavky

Těmihle opatřeními jsem se snažil zabránit tomu, aby požadavky směřované na nereagující (vadné či odpojené) Modbus zařízení zahlcovaly frontu. V praxi to třeba znamená, že třeba při nastavení 200ms timeout a 5 retries nebude požadavek směřující na vadné slave zařízení nikdy blokovat frontu na celou 1 sekundu. Už před prvním požadavkem nebo mezi (neúspěšnými) opakovanými požadavky ho může předběhnout požadavek na jiné zařízení, které v pořádku funguje.

Detaily viz poznámky v kódu. Nastavení je klasicky na začátku skeče. Upravte si zejména ip adresu gateway (a mac adresu na něco víc random). IP adresu Loxone zadávat nemusíte. Gateway odpovědi automaticky posílá zpět na IP adresy (a porty), ze kterých přišel dotaz.

Nastavení v Loxone Config: Modbus TCP

Miniserver > Komunikace Miniserveru a pak v panelu nástrojů kliknete na ikonu “Modbusserver” (viz https://www.loxone.com/cscz/kb/komunikace-pres-modbus-tcp/ ).

Nastavení “Modbusserveru”:

  • adresa: viz ip nastavená ve skeči
  • timeout: neměl by asi být kratší než SERIAL_TIMEOUT ve skeči

Pak přidáte “Modbus zařízení”, kde nastavíte:

  • Modbus adresa: Modbus RTU adresa zařízení za gateway

A pak už si přidáváte senzory a aktory daného zařízení.

Nastavení v Loxone Config: Modbus UDP

Modbus UDP je “nestandardní” protokol. Je to vlastně Modbus TCP zpráva, ovšem poslaná UDP paketem (pro úplnost: není to Modbus RTU zpráva poslaná UDP paketem). V praxi to znamená, že oproti klasické Modbus RTU zprávě (adresa+funkce+data+CRC) je na začátku pár bytů navíc a na konci naopak chybí CRC.

Pořád je to Modbus, to znamená, že:

  • Na vzdálené Modbus zařízení musíte nejdřív odeslat požadavek (UDP paketem – Virtuální UDP výstup) a to vám teprve pak pošle odpověď (Virtuální UDP vstup). Teoreticky si můžete naprogramovat nějaké periodické dotazování v samotném Arduinu (a na Loxone posílat hotové odpovědi), ale je to práce navíc a porušení protokolu 🙂
  • V jednom Modbus požadavku (UDP paketu) se můžete dotázat na vícero registrů (“IO adres” jednotlivých senzorů a aktorů) najednou. V odpovědi pak dostanete (v pevně daném pořadí) hodnoty těchto registrů (jednotlivých senzorů a aktorů). Jak udělat takový vícenásobný dotaz (vícenásobný požadavek) se asi bude lišit podle zařízení – to už si musíte naštudovat sami.

… ale je to Modbus, který není limitovaný Loxone kurvítky. To znamená, že:

  • Přes UDP můžete na Modbus posílat dotazy častěji než jednou za 5 sekund. Při testech se mi dařilo docela obstojně komunikovat na 100ms.
  • Nejste omezeni výběrem funkcí (“Příkazů”), které vám Loxone nabízí. Pokud máte zařízení s nějakými nestandardními / proprietárními funkcemi (u mě třeba příkaz na vynulování elektroměru nebo na jeho kalibraci), tak není problém.
  • Prostě pokud zjistíte, že v Loxone implementaci Modbus RTU nebo Modbus TCP něco nefunguje, máte možnost si přes UDP sáhnout na “čistou” Modbus zprávu.

Tak teď tedy implementace Modbus UDP v Loxone.

Modbus UDP požadavek

Klasika. V Loxone Configu přidáme nový “Virtuální výstup”, kde adresa bude v tomto tvaru:

/dev/udp/192.168.1.10/502

kde máte ip adresu gatewaye a UDP port (UDP port můžete ve skeči změnit,, default je standardní Modbus TCP port 502 – na jednom portu může gateway přijímat TCP i UDP požadavky). Pak přidáte “Virtuální výstup příkazu”, kde musíte nadefinovat instrukci při zapnutí. Tady musíte dodržet pravidla pro sestavování Modbus TCP hlavičky (tzv. MBAP hlavičky). Doporučuju projít si https://en.wikipedia.org/wiki/Modbus . Pokud tahle pravidla nedodržíte, tak se na vás moje gateway vykašle,  požadavek zahodí a ani vám nepošle žádný error:

  • bajty č. 1 a 2 (transaction ID) mohou být cokoliv
  • bajty č. 3 a 4 (protocol ID) jsou vždy 0x00
  • bajty č. 5 a 6 jsou délka zbytku zprávy. Brána akceptuje max délku zbytku zprávy 255 bajtů, takže bajt 5 musí být 0x00 a bajt 6 je délka zbytku zprávy v HEX
  • bajt č. 7 je unit ID neboli Modbus adresa zařízení připojeného k bráně. Může být cokoliv, brána akceptuje i Modbus broadcasty tj. adresa 0x00

Zbytek zprávy je funkce (příkaz), který voláte a samotná data (adresa registru atd.). V mém případě třeba:

\xOO\xOO\xOO\xOO\xOO\xO6\xO1\xO4\xOO\xOO\xOO\xO8

(musíte nahradit O za nuly 0 protože s nulami se mi to sem nedaří vkládat)

Na 7. bajtu je modbus adresa zařízení (0x01), následuje funkce (0x04), následují dva bajty adresy prvního registru, na jehož hodnoty se dotazujeme (0x0000) a dva bajty, které říkají, kolik registrů má být přečteno. Je to vícenásobný dotaz, takže hodnota (0x0008) říká, že chceme hodnoty osmi navazujících registrů (senzorů) na adresách 0x0000 až 0x0007. Toť vše. Jak už jsem psal, CRC nepotřebujeme.

Takto nadefinovaný “Virtuální výstup příkazu” šoupneme do programu, přidáme před něj “Zdroj impulsů” s požadovanou frekvencí a je to.

Modbus UDP odpověď

Jako první si otevřete UDP monitor, přepněte ho do HEX a sledujte, jaké odpovědi chodí. Může vám přijít:

  • platná odpověď Modbus zařízení
  • chyba vygenerovaná samotným Modbus zařízením
  • chyba vygenerovaná gateway

Chybu poznáte podle toho, že má pevnou délku (9 bajtů), předposlední bajt chyby většinou začíná na 0x8 (je to hodnota vámi volané funkce, ke které je přičteno 0x80) a poslední bajt je kód chyby (exception code – viz wiki). Brána sama posílá kód chyby 0x06 pokud byl požadavek odmítnut z důvodu zaplnění fronty a 0x0B (tj. 11) pokud Modbus zařízení nereaguje na požadavky (vypršel timeout na sériovém portu).

Pak klasika: do Loxone Configu přidáte nový “Virtuální UDP vstup” (jako adresu dáte ip gateway, jako port zadáte UDP port gateway) a nový “Virtuální UDP příkaz”, kde vyplníte nastavení “rozeznání příkazu”.

Na parsování odpovědí budete potřebovat manuál vašeho zařízení, kde se dozvíte, na kolikátém bajtu (bajtech) odpovědi se nachází hodnota požadované proměnné, jak jsou bajty seřazené a jestli je hodnota signed („hodnota s předznaménkem“) nebi unsigned. Přehledný souhrn Loxone syntaxe na parsování UDP zpráv najdete tady: http://sarnau.info/loxone-udp-http-command-parser-syntax/ . Nejdůležitější je \. na přeskakování bajtů. V mém případě mi na vícenásobný dotaz přišla vícenásobná odpověď, parsování první proměnné (prvního senzoru) vypadá takto:

|.|.|.|.|.|.|.|.|.|2|1

(prosím nahraďte | za \ , špatně se to tu zobrazuje)

což znamená, že data senzoru jsou na bajtech č. 10 a 11, přičemž bajt 10 je high byte. Na další senzor si zřídíte další “Virtuální UDP příkaz”, jeho parsování může vypadat třeba takto:

|.|.|.|.|.|.|.|.|.|.|.|2|1

ale třeba taky

|.|.|.|.|.|.|.|.|.|.|.|.|.|2|1|4|3

(prosím nahraďte | za \ , špatně se to tu zobrazuje)

No a to je vše. Na validaci můžete použít nastavení “Validace”, kde si dáte hodnotu timeoutu (na straně Loxone), ale klidně můžete zachytávat i chybové kódy generované gateway a samotným zařízením.

Disklejmr

Nejsem programátor. Celý projekt jsem bral jako demonstraci toho, co zvládne obyčejné Arduino Nano z Aliexpresu a co zvládne naprogramovat amatér bez formálního IT vzdělání (když má volný čas díky karanténě). Tj. kopírováním a upravováním kódu z internetu, využíváním hotových knihoven atd. Budu rád za jakékoliv nápady na zlepšení a zjednodušení kódu (což je i jeden z důvodů, proč ho zveřejňuju :-)).

Pomohl Vám náš blog? Chcete nás podpořit? I málo udělá radost
Documents
Comments Write