Arduino - Modbus TCP/RTU
Modbus kommunikáció
A Modbus protokoll kialakulása szorosan összefonódott az első PLC születésével. Az első PLC, a „Modicon 084” 1969-ben kezdte meg első ciklusait, és rá egy évre, létrehozták a Modbus-t. Nyilván azóta jelentős változásokon esett át a protokoll, de napjainkig megmaradt egy De-facto-standard-nak, azaz, igen széles körben alkalmazott kommunikációs rendszernek.
A Modbus két hordozóközegen bukkan fel a leggyakrabban, az RS485-ön (RTU) és az etherneten (TCP). A Modbus magasabb szintű műveletei mindkét esetben megegyeznek, azaz lehet vegyes hálózatokat képezni a protokollal, ahol az átvitel RTU-bval és TCP-vel is megvalósul.
A Modbus egy monomaster hálózatot vagy pont-pont kapcsolatot feltételez. Mindkét esetben egy master és legalább egy slave szükséges a kommunikációhoz. A Modbus RTU csak egy master egység jelenlétét teszi lehetővé.
Modbus master / slave
Fontos megjegyezni, hogy sok más kommunikációs technológiával szemben az adatokat a slave egységek tárolják (jellemzően regiszteres formában) és a master jogosult ezeket az adattartalmakat lekérdezni. Más közelítésben azt mondhatjuk, hogy a Modbus esetén a slave a szerver. Az Arduino-k ebben a hálózatban mint slave, mint master szerepet is betölthetnek:
Multimaster
A Modbus hálózaton egyszerre több master is jelen lehet (multimaster), de ez esetben ún. multimaster-gateway-t kell beiktatnunk a kummunikációs hálózatba, és a masterek csak Modbus TCP-re lehetnek felfűzve:
A TCP esetén az RTU-t telepítették a TCP-re, így az ethernetes hálózatok összes előnyét sikerült beágyazni a Modbusba. Mivel az etherneten az állomások címe nagyságrendekkel magasabb, mint az RTU/ASCII-nél, így ennek a korlátját a Modbus-ban definiált 1 bájtos címe jelenti (itt is marad az 1..247 korlát).
Modbus funkciókódok
Function Code | Register Type |
---|---|
1 | Read Coil |
2 | Read Discrete Input |
3 | Read Holding Registers |
4 | Read Input Registers |
5 | Write Single Coil |
6 | Write Single Holding Register |
15 | Write Multiple Coils |
16 | Write Multiple Holding Registers |
Modbus RTU
Az RS-485 technikai jellemzői:
RS-485 | |
---|---|
Működési mód | szinkron átvitel |
Meghajtók és vevők száma egy vonalon | 32 állomás szegmensenként |
Adatátvitel módja | félduplex / duplex |
Adatátvitel | multipoint |
Max. kábelhosszúság | 1200 m |
Max. adatátvitel 12 m 1200 m | 35 Mbps 100 kbps |
Max. jelváltozási sebesség (slew rate) | n.a. |
Vevő bemeneti ellenállás | ≧ 12 kΩ |
Meghajtó terhelés- impedancia | 54 Ω |
Vevő „holtsáv” | ±200 mV |
Vevő feszültségszint | –7..12 V |
Meghajtó kimenő feszültség max. | –7..12 V |
Meghajtó kimenő feszültség min. (terheléssel) | ±1.5 V |
Meghajtó kimeneti rövidzárási áram limit | 150 mA tól Test felé 250 mA Vcc felé |
Vevő hiszterézis | 50 mV |
Az RS-485 egy szimmetrikus átviteli mód. Az EIA-485 megnevezés azonos az RS-485 standarddal. A 32 egység / szegmens elvi határon belül az adó és vevő egységek száma szabadon variálható (multipoint).
A maximum 32 egység / szegmens határ az előre definiált meghajtó terhelés (Unit Load [UL]) mellett érvényes, ami az RS-485 esetében 12 kΩ. Az egységek száma emelhető, ha a meghajtó terhelés csökken. Jellemzően ezt - az UL-t - a negyedére (48 kΩ) vagy nyolcadára (96 kΩ) szokás csökkenteni, így az állomások száma rendre 128-ra, vagy 256-ra emelhető. Hálózati erősítővel (repeaterrel) az állomások száma szintén emelhető.
Az RS-485 120 Ω vonalimpedanciát tételez fel a vezetéktől. A szegmens két végét 680 Ω-os 120W-os (10%, 1/2 watt) véglezárókkal szükséges zárni.
RTU/ASCII
A slave-ek száma nem haladhatja meg a 246-ot, címzésük az 1..247-es tartományban történhet. A gyakorlat szerint egy szegmensben 32 állomás lehet, és csak repeater-ekkel bővíthető a hálózat. A 0. címmel broadcast üzeneteket lehet küldeni, amennyiben ez a művelet logikai szinten nincs korlátozva. A master jellemzően az 1. címet szokta megkapni, Modbus RRU egy hálózat-szegmensben csak egy master-t enged meg.
RS-485 jelráta
Az RS-485-nek nincs definiálva maximális hossz, de jellemzően a jeleket 1200 méter távolságig tudja továbbítani, és kb. 50 méterig lehet biztosítani a 10 Mbps átvitelt. Az átviteli ráta / távolság hányadosa jelentősen függ az alkalmazott vezeték minőségétől és a vonali erősítők (repeaterek) számától.
RS-485 jelszintek
A meghajtó kimenő feszültsége +12V..-7V tartományban kell, hogy maradjon. A +0.2V..-0.2V a holtsáv. A +0.2V..+6V tartomány a vevő oldalon a logikai „0” értéknek felel meg, a -0.2V..-6V tartomány pedig a logikai „1”-nek.
RS-485 half duplex kapcsolás
RS-485 full duplex kapcsolás
RS-485 / Modbus RTU kapcsolás
A Modbus RTU-hoz jellemzően a félduplex vezetékezést szokás használni, így három vezetéket tartalmaz az átvitel D+, D- és GND. A vezetékek feszültségszintjeit ellenállásokkal szokás stabilizálni (a puulup, pulldown esetén a lenti ellenállás a minimális érték, ez akár pár kΩ is lehet):
Arduino Modbus RTU könyvtárak
Modbus RTU slave: https://www.arduino.cc/reference/en/libraries/modbusrtu_slave/
Modbus RTU slave RS-485: https://www.arduino.cc/reference/en/libraries/modbusrtu_slave_rs485/
Modbus RTU master: https://www.arduino.cc/reference/en/libraries/modbusmaster/
Modbus RTU ESP8266: https://www.arduino.cc/reference/en/libraries/modbus-esp8266/
ModbusConfig ESP8266/ESP32/Arduino: https://www.arduino.cc/reference/en/libraries/modbusconfig/
MAX485 modul
A MAX 485 modul egy egyszerű csatlakozási lehetőséget kínál az Arduino oldalról a Modbus RTU hálózathoz. A modul viszonylag egyszerűen csatolható az Arduinokhoz:
Vezetékezés
Pin | Leírás |
Vcc | 5V DC |
A | A+ Modbus |
B | B- Modbus |
GND | GND |
RO | receiver output (Rx) |
RE | Receiver enable |
DE | driver enable |
DI | driver input (Tx) |
RS-485 shield
Az RS485-Shield kibővítheti az Arduinót egy RS-485 interfésszel. Az Arduinók többsége csak egy UART soros porttal van ellátva, ennek a funkcióit bővíti ki (cseréli le) ez a shield RS485-re.
A bővítmény az Arduino D0-D7 közötti portjait tudja az RS-485-re „átirányítani”, így mér az Arduino Mega boardon is csak a normál Serial portot tudja felhasználni. Ez azért probléma, mert az Arduinókra a program letöltésére is a soros, normál „serial” kerül felhasználásra. A shield esetén a megoldás erre a problémára, hogy a program letöltése idejére mindkét jumpert el kell távolítani a címkijelölő portról. Kényelmetlen, de ez van. A jumpereket egyébként a serial-hoz kell igazítani, azaz a D0 az RX, a D1 a TX.
A modbus shield részei:
- 1: címkijelölő port, a serialhoz D0 az RX, a D1 a TX
- 2: kommunikációs jellemző, érdemes TX_CTRL-en hagyni
- 3: a kommunikáció jelzése: ha felváltva néha felvillan, nyertünk
- 4: a Modbus csatlakozók, elég csak egyet bekötni
- 5: tápfeszültség kiválasztása, Uno esetén pl. 5V
A board mind Modbus Slave mind Master funkcióval is felruházható. Ebben az esetben javaslom ezeknek a libeknek az alkalmazását:
- ArduinoRS485.h : https://www.arduino.cc/en/Reference/ArduinoRS485
- ArduinoModbus.h : https://www.arduino.cc/en/ArduinoModbus/ArduinoModbus
Modbus slave példaprogram
- a D8 portra kötöttem egy LEDet a 2.regiszter állapotjelzésére (ha nem nulla, világít a LED)
- a D4 portra egy DS18B20 hőmérséklet szenzor onewire kommunikációval, ennek eredménye az első regiszterben található.
/* OB121 Modbus slave example, 2021. Vamos Sandor * reg 0: life register reg 1: temperature (*100) reg 2: led on/off reg 3: copy to reg 4 reg 4: copy from reg 3 */ #include <OneWire.h> #include <DallasTemperature.h> #include <ArduinoRS485.h> // ArduinoModbus depends on the ArduinoRS485 library #include <ArduinoModbus.h> // Data wire is plugged into port 4 #define ONE_WIRE_BUS 4 // Setup a oneWire instance to communicate with any OneWire devices OneWire oneWire(ONE_WIRE_BUS); // Pass our oneWire reference to Dallas Temperature. DallasTemperature sensors(&oneWire); uint8_t sensor1[8] = { 0x28, 0x20, 0x54, 0x29, 0x05, 0x00, 0x00, 0xF4 }; //copied from the address scanner float tempSensor1; uint16_t reg0, reg1, reg2, reg3, reg4; bool ledsta; const int ledPin = LED_BUILTIN; void setup() { sensors.begin(); Serial.begin(9600); // start the Modbus RTU server, with (slave) id 1 if (!ModbusRTUServer.begin(1, 9600)) { while (1); } // configure the LED pinMode(8, OUTPUT); digitalWrite(8, LOW); // configure Holding Registers from address 0x00 ModbusRTUServer.configureHoldingRegisters(0x00, 20); } void loop() { sensors.requestTemperatures(); tempSensor1 = sensors.getTempC(sensor1); // Gets the values of the temperature // poll for Modbus RTU requests ModbusRTUServer.poll(); reg1 = (word)(tempSensor1*100); reg0++; ModbusRTUServer.holdingRegisterWrite(0, reg0); ModbusRTUServer.holdingRegisterWrite(1, reg1); reg2 = ModbusRTUServer.holdingRegisterRead(2); reg3 = ModbusRTUServer.holdingRegisterRead(3); ModbusRTUServer.holdingRegisterWrite(4, reg3); // read the current value of the coil //int coilValue = ModbusRTUServer.coilRead(0x00); if (reg2 == 0) { digitalWrite(8, LOW); } else { digitalWrite(8, HIGH); } }