Arduino - Modbus TCP/RTU

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.

Modbus communication

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é.

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:

Modbus master / slave

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:

Modbus Multimaster

 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 RTU

Az RS-485 technikai jellemzői:

 RS-485
Működési módszinkron átvitel
Meghajtók és vevők
száma egy vonalon
32 állomás szegmensenként
Adatátvitel módjafélduplex / duplex
Adatátvitelmultipoint
Max. kábelhosszúság1200 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ézis50 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-232, RS-422, RS-485 compare signal rates

RS-485 jelszintek

RS-485 signal levels

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 half duplex plate

RS-485 full duplex kapcsolás

RS-485 full duplex plate

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):

RS-485 / Modbus RTU kapcsolás

MAX485 modul

Arduino Modbus RTU könyvtárak

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:

MAX485 modul

Vezetékezés

MAX485 modul

PinLeírás
Vcc5V DC
AA+ Modbus
BB- Modbus
GNDGND
ROreceiver output (Rx)
REReceiver enable
DEdriver enable
DIdriver 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:

RS-485 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:

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);
  }
}