Modbus RTU <–> Modbus TCP / UDP Gateway postavená na Arduinu

Modbus RTU <–> Modbus TCP / UDP Gateway postavená na Arduinu

Categories
You are here:
  • KB Home
  • Arduino
  • Modbus RTU <--> Modbus TCP / UDP Gateway postavená na Arduinu
< Zpět

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 😉
Table of Contents

9 thoughts on “Modbus RTU <–> Modbus TCP / UDP Gateway postavená na Arduinu

  1. Hodně zajímavé řešení… Jestli to dobře chápu, tak se tvým řešením nemusí použít zde hojně doporučovaný převodník “USR-TCP232-410s ModBus RTU” a pomocí Nano posílat zprávy do Loxone přímo přes Modbus TCP (nebo UDP) protokol…

  2. Jojo, je to plně funkční náhrada za převodník “USR-TCP232-410s ModBus RTU”. Ten převodník nemám, ale když koukám na návod, tak rozdíly jsou minimální:
    – Nano funguje pouze v režimu kdy Modbus zařízení (slaves) připojuješ přes RTU a klienty (masters) připojuješ na straně TCP či UDP”. Převodník USR umí i opačný režim, který je pro naše potřeby ale zbytečný.
    – Nano nemá web server na konfiguraci a neumí websockets.
    – Nano nemá keepalive na TCP rozhraní ani heartbeet. Jak už jsem psal v návodu, neimplementoval jsem ani periodický polling Modbus RTU slavů samotnou bránou (periodické odesílání požadavků řeší Loxone).

    Jinak je to to v zásadě to samé. Základem je Modbus RTUModbus TCP protocol conversion. Nano taky zvládá až 8 TCP klientů. Hmm, koukám, že USR umí UDP broadcasty. Tuhle možnost jsem tam taky měl (je to trochu rychlejší, páč v broadcastu ethernet shield neověřije existenci cílové IP adresy v podsíti), nakonec jsem ale nechal singlecast a “vytunil” jsem jej tím, že jsem snížil timeouty a retries.

    Když na to koukám, tak možná jsem neměl psát “transparentní gateway”. V návodu od USR “Modbus RTU transparent transmission” znamená Modbus RTU Modbus RTU over TCP. Moje Nano nic takového jako “Modbus RTU over TCP” ani “Modbus RTU over UDP” nedělá. Dělá Modbus TCP a Modbus UDP (tj. dělá konverzi hlaviček). Tou transparentností v nadpise jsem měl na mysli to, že Nano nijak nesahá na samotná data, která se přes Modbus přenáší.

    1. Moc díky za komentář. Já právě ten “USR-TCP232-410s ModBus RTU” používám na Meteostanici dle návodu od jirin.sv, která je tady taky ve Wiki.
      Takže mi to připadá jako zajímavá věc v tom smyslu, že kdybych to použil, tak odpadne “jedna krabička” USR v komunikační cestě. Co se týče nákladů, tak je to 10 dolarů za Ethernet Shield navíc oproti 25 dolarům za USR…

  3. Moment, moment….
    Nebylo by jednodušší upravit skeč od jirin.sv tak, aby meteostanice posílala data rovnou přes UDP pakety a ne přes Modbus? Ostatně jirin to ve svém návodu taky píše, že by bylo určitě reálné udělat i jiné varianty komunikace.

  4. Jestli se dá DMX brána a Modbus brána udělat na jednom Arduinu? Asi jo, jenom si hlídej:

    1) DMX a Modbus RTU musí být na samostatných drátech. Takže potřebuješ 2 ks MAX485.

    2) Modbus je připojený přes HW serial. Standardní Arduino má k dispozici jenom jeden HW serial (piny 0 a 1). Vypadá to, že DMX nevyžaduje připojení přes HW serial (v tom návodu je MAX485 někde na pinu 3), takže potud je vše OK.

    3) Pokud se ti program vejde do flash paměti Arduina a jestli bude dost RAM na jeho běh. Modbus gateway využívá skoro všehnu RAM Arduina, ale není problém zmenšit frontu (REQUESTS_QUEUE a REQUESTS_QUEUE_BYTES) aby zbyla RAM i na jiné věci.

    4) Arduino neumí multithreading. Celý program běží v jednom procesu (donekonečna se opakující void loop()), takže pokud chceš obsluhovat víc věcí současně (Modbus a DMX) je vhodné, aby kód neobsahoval žádné funkce, které by ho blokovaly a zastavovaly. Pokud budeš mít ve funkci, která obsluhuje DMX, příkaz delay(1000), tak to znamená, že se celé Arduino na 1s zastaví a po tu dobu nebude obsluhovaná ani Modbus brána. Kód pro Modbus Gaeway je psaný jako nonblocking, kód na DMX si budeš muset ověřit.

Leave a Reply

Your email address will not be published. Required fields are marked *