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

Function CodeRegister Type
1Read Coil
2Read Discrete Input
3Read Holding Registers
4Read Input Registers
5Write Single Coil
6Write Single Holding Register
15Write Multiple Coils
16Write Multiple Holding Registers

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