Portál AbcLinuxu, 30. dubna 2025 23:55
Protokol Modbus tunelovaný přes RS-485 (též nazýváno Modbus-RTU) se často používá pro komunikaci s malými průmyslovými zařízeními. Pojďme se podívat, jak vypadá a jak implementovat komunikaci s takovým zařízením.
RS-485 je sběrnice, která má následující parametry:
Diferenciální po dvou drátech označených A a B. Nediferenciální sběrnice (třeba normální sériák) fungují tak, že když chceme poslat jedničku, připojíme na drát nějaké napětí, a když nulu, tak jiné napětí (typicky třeba 5V a 0V pro běžnou TTL logiku). Diferenciální dělají to, že na jeden drát připojí napětí, které chtějí vyslat, a na druhý to druhé (takže třeba jednička: na drátu A je 5V a na drátu B je 0V; nula: na drátu A je 0V a na drátu B je 5V). Výhoda je, že pokud se signál přenáší na větší vzdálenost a dojde k rušení, respektive nějakému plavání referenční úrovně, toto rušení nejspíš ovlivní oba dráty stejně, takže na druhou stranu přijde 8V a 3V, my to odečteme od sebe a zjistíme, že to byla jednička.
Jinými slovy: na A je vyšší napětí než na B → jednička.
Samozřejmě se používá transceiver - například MAX485 - s diferenciálním vstupem a výstupem, který tohle řeší, a s námi komunikuje pomocí TTL.
Na sběrnici může být mnoho zařízení; v každém okamžiku nejvýše jedno vysílá a ostatní přijímají. Transceivery mají typicky drát, kterým se jim říká, jestli teď mají vysílat nebo přijímat. Není nijak zařízeno, jak udělat, aby se více vysílačů navzájem nepotlouklo - až protokol vyšší vrstvy určuje, kdo bude kdy vysílat. V typickém použití pro ovládání několika zařízení z počítače to funguje tak, že zařízení trvale přijímají, počítač odvysílá zprávu nějakému zařízení, pak přejde na příjem, a zařízení za krátký okamžik (milisekundy) odvysílá odpověď a přejde zpět na příjem. Po přijetí odpovědi začne vysílat počítač a pošle zprávu dalšímu zařízení.
Pro systémy bez centrálního řízení (to dělá v našem případě ten počítač, který jednotlivá zařízení oslovuje) existují různá rozšíření, která dělají distribuovanou bus arbitration. Může to vypadat tak, že vysílače rozšíříme o možnost vysílat malým výkonem. Zařízení, které by chtělo vysílat, nejdřív malým výkonem broadcastuje požadavek, že chce vysílat, a detekuje, jestli ho někdo velkým výkonem nepřehlušil. Když ne, vyhodnotí, že je na lince volno, a odvysílá plným výkonem svoji zprávu.
Tohle také mimochodem znamená, že i point-to-point spoj je halfduplexní a nejde skrz to třeba přímočaře tunelovat normální sériák.
Sběrnice má být řetízek bez rozvětvení, kroucená dvojlinka, na které jsou pověšená jednotlivá zařízení, a konce řetízku mají být terminovány odporem 120Ω, aby nedocházelo k odrazům. V praxi se na všechno kašle, rozvětví se to, terminátory se tam nedají, a použije se nekroucená dvojlinka, a pro nízké rychlosti to normálně chodí.
Z praktického provozu dvě pozorování learned the hard way:
Sériák má defaultně na lince trvale jedničku. Když se má poslat bajt, tak se pošle nula = start bit, následně 8 bitů dat, a následně jednička = stop bit (existují i exotické konfigurace, kdy se ještě přenáší parita, bitů je jiný počet než 8, a stop bit je jinak dlouhý, ale nikdy jsem neviděl zařízení, které by je používalo; ta standardní konfigurace se označuje 8N1 = 8 bitů, no parity, 1 stopbit). Takhle to vypadá:
Následující stavy jsou speciální:
Pokud je na drátu A i B stejné napětí, nachází se sběrnice v nedefinovaném stavu. To nemusí nutně vadit, ale pokud tunelujete třeba UART, je potřeba počítat s tím, že z přijímače můžou padat náhodná data, takže to bude přijímat náhodné bajty v náhodnou dobu. Pokud teď chce někdo vysílat, tak musí minimálně po dobu jednoho bajtu vysílat jedničku nebo break condition, aby se to celé dostalo do konzistentního stavu, a až pak může začít posílat data. No a to samozřejmě zařízení, se kterým jsem komunikoval, nedělalo (není to jeho povinnost), a dokonce tam ani nehodilo před prvním startbitem na chvilku jedničku, ale prostě rovnou začalo posílat startbit = nulu a pak data.
Svízelné bylo, že zrovna MAX-485, se kterým jsem to testoval, se choval tak, že v tomto nedefinovaném stavu měl na výstupu jedničku, takže všechno fungovalo. Pak jsme si ale nechali osadit desky v Číně s nějakým klonem SP-485, a ten tohle nedělal, takže se to rozpadalo.
No a tohle se správně řeší klidovým biasováním sběrnice tak, aby když nikdo nevysílá, tam byla jednička (tj. Ačko nahoru, Bčko dolu).
Trochu problém je v tom, že bychom chtěli mít klidový bias tak 1 V, aby to bylo spolehlivé proti náhodnému rušení. Jenže aby byl na sběrnici terminované na obou koncích 120Ω odpory 1 V, musí tam téct 17 mA, a to je u low-power zařízení nepříjemně mnoho. A proto osobně na terminační odpory kašlu .
Mimochodem debugování problému a správnému nastavení biasu vůbec nepomohlo, že existují dva názory na to, co je jednička (jestli je to „A je větší napětí“ jak jsem psal výše, nebo naopak)! To je způsobené tím, že se na RS-485 přecházelo z RS-232, které jedničku signalizuje záporným napětím a nulu kladným, takže je to jakoby opačně, ale moderní TTL transceivery to mají samozřejmě tak jak by člověk čekal. OMFG!
Modbus je protokol, který definuje, že každé zařízení má svoji adresu (jeden bajt) a různé množství (až 4*64K) 16bitových registrů. Registry můžou být buď read-only (tomu říkají input register), například u teploměru to bude naměřená teplota, nebo read-write (holding register), například u motoru to bude nastavení jak rychle se má točit. Pak mají ještě coils a discrete inputs, což jsou jakoby relátka (jednobitové registry), ale efektivně jsou to prostě registry.
Velká nevýhoda je pevná velikost registru 16 bitů, větší 32b hodnoty tak jsou typicky rozděleny do dvou registrů "LO" a "HI". Všechny věci jsou big-endian, a je mrzuté, když výrobce dá LO registr níž než HI, protože to pak nejde concatenovat a dostat big endian číslo, ale musí se to prohodit.
Modbus má definováno, jak zprávy posílat přes různá rozhraní - například přes TCP nebo přes sériák přes RS-485 - a to je to, co potkávám nejčastěji, a proč píšu tento článek.
Modbus přes RS-485 posílá krátké zprávy, které vypadají takto:
0x01 # adresa zařízení 0x04 # příkaz. 4 = přečíst input registry 0x33 0x12 # první registr, který chceme přečíst - 13075. registr 0x00 0x02 # kolik registrů chceme přečíst - 2. Zařízení má limit kolik dat najednou může poslat, bývají to desítky registrů. 0xDE 0x8A # CRC
Jako fakt blbý je, že registry jsou někdy číslované od jedničky, ale adresy jsou číslované od nuly. Takže například registr 6 má adresu 0x0005.
Odpověď pak vypadá:
0x01 # adresa zařízení 0x04 # příkaz; kdyby došlo k chybě, tak to bude zORované s 0x80 0x04 # kolik bajtů bude následovat. 2 16bit registry = 4 bajty 0x01 0x01 # data - 1. čtený registr 0x00 0x00 # data - 2. čtený registr AB B8 # CRC
Zprávy jsou chráněny CRC-16, které se spočítá takto:
def modcrc(buf): crc = 0xFFFF; for pos in range(len(buf)): crc ^= buf[pos] for i in range(7,-1,-1): if ((crc & 0x0001) != 0): crc >>= 1 crc ^= 0xA001 else: crc >>= 1 return (crc>>8) | ((crc&0xff)<<8) assert(modcrc([0x01, 0x04, 0x00, 0x00, 0x00, 0x01]) == 0x31CA)
Standardní rychlost je 19200, ale viděl jsem i zařízení co má 115200 a někdy to jde nastavit.
Pokud chcete komunikovat Modbus-RTU, tak si pořiďte tenhle modul s MAX485. Pokud to dáváte na vlastní desku, tak tam MAX485 snadno dáte (nepotřebuje to žádné externí součástky), případně se podívejte na variantu MAX3485, která má 3.3V logiku. Ten modul z GME si ale pořiďte i pokud děláte vlastní desku, protože ho budete používat pro sniffování sběrnice až vám to nebude fungovat.
Následně se zapojí DI na TX (data input modulu) vašeho UARTu, RO na RX (receiver output) a řídící piny DE (driver enable) a #RE (#receiver enable)křížky u signálů znamenají active low (invertovaná logika) můžete spojit pokud nechcete používat power save (to by se dělalo tak, že dáte 1 na #RE a 0 na DE). Následně se to používá tak, že:
Na Arduinu mi to fungovalo s bitbangovaným SoftSerial (protože menší AtMegy nemívají více sériáků), pouze pro rychlost 115200 jsem musel zakomentovat čekání na stop bit, protože další bajt přišel moc rychle a nestíhalo se to. (nechápu proč tam to čekání na stop bit je, když se stejně nekontroluje)
Tiskni
Sdílej:
Vlákno bylo přesunuto do samostatné diskuse.
Osobně jsem se na tohle vykašlal a delší propoje jsem vyřešil nákupem routříků s OpenWRT a roztaháním ethernetu, který je oddělený trafíčky defaultně, a případný výstup na RS-485 dělám až na druhé straně. Cenově to vyjde podobněUrčitě bude záležet na tom, kolik kusů toho děláte (tj. kdy úspora na těch routerech zaplatí práci), ale když mluvíte o těch trafíčkách, tak mě napadá FSK modulace - digitální signál přes ta trafíčka neproleze, ale sinusovka by měla. Akorát mě teď nenapadá, jak řešit ten bias do klidového stavu
V mnoha zařízeních když zavoláte write na sériák, tak se to jenom vloží do fronty a write hned vrátí a vysílání ještě běží. Potřebujete třeba udělat flush.Nedávno jsem zrovna psal něco, co komunikovalo po sériáku, v Ruby. Dopracoval jsem se k tomu, že při odvysílání zprávy se vypočítá čas, kdy je odeslání hotové, a podle toho se pak posílá dál. Oproti MCU, které udělá interrupt, když má volno v 1B velkém bufferu, docela pakárna. Na druhou stranu je to na straně PC a v Ruby, takže tam si můžu dělat a ladit, co chci. Teda skoro. Narazil jsem na to, že se mezi zařízeními "náhodně" rozpadá časování, takže mi to přecházelo do chybového stavu (timeouty, nesedlo pořadí zpráv a tak) a přestalo se to dít, když jsem vypnul ladící výstupy na terminál. Počítač (nebo Ruby) prostě nestíhal komunikovat po sériáku a zároveň kreslit do konzole hromadu textu.
ISSN 1214-1267, (c) 1999-2007 Stickfish s.r.o.