Protokół Modbus/RTU
Czyli komunikacja na łączu szeregowym RS-485
Przesyłane w obydwie strony wiadomości zorganizowane są w postaci ramek o czterech ściśle określonych
polach:
ADDRESS FUNCTION CODE DATA CHECKSUM
W implementacji RTU dla zapytania mamy:
• ADDRESS [1 bajt] - określa do którego urządzenia wpiętego w sieć (tu RS-485) adresowane jest
zapytanie
• FUNCTION CODE [1 bajt] - określa kod funkcji/zapytania
• DATA [różnej długości] - przekazuje parametry wejściowe wywoływanej funkcji
• CHECKSUM [2 bajty] - stanowi sumę kontrolną
Dla odpowiedzi:
• ADDRESS [1 bajt] - określa adres urządzenia slave odpowiadającego na zapytanie
• FUNCTION CODE [1 bajt] - określa kod funkcji/zapytania z zapytania
• DATA [różnej długości] - przekazuje parametry wynikowe funkcji
• CHECKSUM [2 bajty] - stanowi sumę kontrolną
Najczęściej wykorzystywane funkcje (spośród wielu dostępnych ściśle zdefiniowanych w standardzie)
przedstawiono w poniższej tabeli:
Function code
Function description
0x01
Read Coils
0x02
Read Discrete Inputs
0x03
Read Holding Registers
0x04
Read Input Registers
0x05
Write Single Coil
0x06
Write Single Register
0x08
Loopback Diagnostic
0x0F
Write Multiple Coils
0x10
Write Multiple Registers
Oprócz znajomości kodów i struktury poszczególnych funkcji Modbus potrzebne są nam jeszcze adresy
rejestrów. W przypadku modułów pomiarowych serii ADAM możemy je znaleźć w dokumentacji lub
odczytać za pomocą ADAM.NET Utility. Dla najpopularniejszych modułów serii ADAM-4000 - czyli
modułu wejść analogowych ADAM-4017+ i modułu we/wy cyfrowych ADAM-4055 będą to następujące
wartości:
ADAM-4017+
ADAM-4055
Analog Inputs
Digital Inputs
40001
AI-0
R
00001
DI-0
R
40002
AI-1
R
00003
DI-1
R
...
...
...
...
?
...
40008
AI-7
R
00008
DI-7
R
Analog Inputs Types
Digital Outputs
40201
AI-0 typ
R/W
00017
DO-0
R/W
40202
AI-1 typ
R/W
00018
DO-1
R/W
...
...
...
...
?
...
40208
AI-7 typ
R/W
00024
DO-7
R/W
Module Info
Module Info
40211
Name-1
R
40211
Name-1
R
40212
Name-2
R
40212
Name-2
R
40213 Firmware-1
R
40213 Firmware-1
R
40214 Firmware-2
R
40214 Firmware-2
R
Korzystając z tych informacji możemy już budować strukturę naszych zapytań Modbus/RTU. Na początek
skorzystamy z funkcji 0x04 - Read input Registers. Struktura jej zapytań i stosownych odpowiedzi,
zgodnie ze standardem Modbus wygląda następująco:
Read input Registers - struktura zapytania
FUNCTION CODE
1 bajt
0x04
DATA
Starting Address
2 bajty 0x0000-0xFFFF
Quantity of Input Registers (N) 2 bajty 0x0001-0x007D
Read input Registers - struktura odpowiedzi
FUNCTION CODE
1 bajt
0x04
DATA
Byte Count
1 bajt
2xN
Input Registers
Nx2 bajtów -
Przykładowe transakcje będą więc wyglądały następująco:
• Odczyt nazwy urządzenia i wersji firmware dla modułu ADAM-4017+:
01
04
00-D2
00-04
E4-30
-> 01
04
08
40-17-50-00-A2-02-00-00
C9-14
• Odczyt wartości wejścia analogowego AI-0 dla modułu ADAM-4017+:
01
04
00-00
00-01
31-CA
-> 01
04
02
E5-F9
33-E2
Do ustawienia pojedynczego wyjścia cyfrowego skorzystamy z funkcji 0x05 - Write Single Coil:
Write Single Coil - struktura zapytania
FUNCTION CODE
1 bajt
0x05
DATA
Output Address
2 bajty 0x0000-0xFFFF
Output Value
2 bajty 0x0000 / 0xFF00
Write Single Coil - struktura odpowiedzi
FUNCTION CODE
1 bajt
0x05
DATA
Output Address
2 bajty 0x0000-0xFFFF
Output Value
2 bajty 0x0000 / 0xFF00
Przykładowe transakcje - włączenie i wyłączenie wyjścia cyfrowego DO-0 w module ADAM-4055 - będą
więc wyglądały następująco:
02
05
00-10
FF-00
8D-CC
-> 02
05
00-00
FF-00
8C-09
02
05
00-10
00-00
CC-3C
-> 02
05
00-00
00-00
CD-F9
Do ustawienia kilku wyjść cyfrowych (jedną transakcją) skorzystamy funkcji 0x0F - Write Multiple Coils:
Write Multiple Coils - struktura zapytania
FUNCTION CODE
1 bajt
0x0F
DATA
Starting Address
2 bajty
0x0000-0xFFFF
Quantity of Outputs
2 bajty
0x0001-0x07B0
Byte Count
1 bajt
N
Outputs Value
N bajtów -
Write Multiple Coils - struktura odpowiedzi
FUNCTION CODE
1 bajt
0x0F
DATA
Starting Address
2 bajty
0x0000-0xFFFF
Quantity of Outputs
2 bajty
0x0001-0x07B0
Przykładowe transakcje - włączenie i wyłączenie wszystkich wyjść cyfrowych w module ADAM-4055 -
będą więc wyglądały następująco:
02
0F
00-10
00-08
01
FF
3F-03
-> 02
0F
00-00
00-08
54-3E
02
0F
00-10
00-08
01
00
7F-43
-> 02
0F
00-00
00-08
54-3E
Do odczytu stanu wejść cyfrowych skorzystamy z funkcji 0x02 - Read Discrete Inputs:
Read Discrete Inputs - struktura zapytania
FUNCTION CODE
1 bajt
0x02
DATA
Starting Address
2 bajty 0x0000-0xFFFF
Quantity of Inputs
2 bajty 0x0001-0x007D
Read Discrete Inputs - struktura odpowiedzi
FUNCTION CODE
1 bajt
0x02
DATA
Byte Count
1 bajt
N
Inputs Status
N bajtów -
Przykładowa transakcja - odczyt stanu wszystkich wejść cyfrowych w module ADAM-4055 - będzie więc
wyglądała następująco:
02
02
00-00
00-08
79-FF
-> 02
02
01
AA
21-B3
UWAGA: W przedtawionych wyżej przykładach, w każdej z komend (zgodnie ze standardem Modbus) występują dwa kończące bajty reprezentujące sumę
kontrolną CRC. Do jej wygenerowania (algorytm jest bardziej złożony niż np. w przypadku protokołu ADAM-ASCII) skorzystać możemy np. z poniższej funkcji
(Pascal/Delph):
procedure GenerateModbusCRC(var modbus_command:string);
var CRC:word;
bajt,bit:byte;
LSB:byte;
begin
CRC:=$FFFF;
for bajt:=1 to length(modbus_command) do
begin
CRC:=CRC xor ord(modbus_command[bajt]);
for bit:=1 to 8 do
begin
LSB:=CRC and $0001;
if LSB=1 then CRC:=CRC-1;
CRC:=CRC shr 1;
if LSB=1 then CRC:=CRC xor $A001;
end;
end;
modbus_command:=modbus_command+chr(CRC and $00FF)+chr((CRC and $FF00) shr 8);
end;
Na podstawie przedstawionych powyżej danych i przykładów (oraz oczywiście po sięgnięciu do
dokumentacji protokołu Modbus) możemy jak widać we własnym zakresie - od podstaw - zorganizować
komunikację naszej aplikacji z modułami pomiarowymi ADAM - w oparciu o bezpośrednią obsługę łącza
szeregowego. Nie jest to sprawa trywialna - trzeba pamiętać o pewnych dodatkowych, nie opisanych wyżej
właściwościach protokołu Modbus, oraz - co bardzo ważne - odpowiednio zaimplementować obsługę
błędów. Na szczęście nie zawsze jesteśmy do tego zmuszeni - z pomocą przychodzi nam zazwyczaj
implementacja protokołu w systemach SCADA/HMI, plus szeroka dostępność serwerów OPC (pozycja
taka występuje także w ofercie Advantech). Jednakże znajomość struktury tegoż protokołu będzie zapewne
pomocna także i w takim przypadku...
Protokół Modbus/TCP
Czyli komunikacja w sieci Ethernet
W implementacji TCP/IP zrezygnowano z pola adresowego (w odwołaniach Modbus/TCP adres
urządzeń, do których kierowane jest zapytanie jednoznacznie określa już przy samym nawiązywaniu
połączenia docelowy adres IP) i sumy kontrolnej. Wprowadzono natomiast rozszerzony nagłówek MBAP
(Modbus Application Protocol Header) zawierający cztery pola:
• identyfikator transakcji (2 bajty) - wykorzystywany przez urządzenie master do prawidłowego
kojarzenia odpowiedzi uzyskiwanych na jego kolejne zapytania (wartość ta zostaje ustalona i umieszczona
w ramce zapytania przez jednostkę master, a następnie skopiowana i umieszczona w ramce odpowiedzi
przez jednostkę slave)
• identyfikator protokołu (2 bajty) - pole to ma zawsze wartość 0 odpowiadającą oznaczeniu protokołu
Modbus
• rozmiar wiadomości (2 bajty) - liczba pozostałych bajtów wiadomości (poczynając od pola identyfikatora
urządzenia; pole to zostało wprowadzone ze względu na możliwość dzielenia pojedynczej wiadomości na
oddzielne pakiety TCP/IP)
• identyfikator urządzenia (1 bajt) - odgrywający znaczenie np. w przypadku komunikacji z urządzeniami
Modbus wyposażonymi w interfejs szeregowy za pomocą bram (Modbus Data Gateway)
Odnośnie wykorzystywanych funkcji i ich parametrów: obowiązują oczywiście te przedstawione
wcześniej - jak w przypadku implementacji RTU (np. dla modułów serii ADAM-4000). Podobnie jak w
przypadku Modbus/RTU musimy oczywiście także posiadać informacje na temat listy adresów rejestrów
naszego urządzenia. Dla dwóch najpopularniejszych reprezentantów serii ADAM-6000 i wybranych ich
rejestrów będą to:
ADAM-6017
40001-40008 AI0-AI7 Current Value
R
40011-40018 AI0-AI7 Max Value
R
40021-40028 AI0-AI7 Min Value
R
00101-00108 AI0-AI7 Max Value Reset R/W
00111-00118 AI0-AI7 Min Value Reset R/W
00131-00138 AI0-AI7 HI Alarm Flag
R
00141-00148 AI0-AI7 LO Alarm Flag
R
ADAM-6060
00001-00006 DI0-DI5
R
00017-00022 DO0-DO5
R/W
40001
CNT0
R (32bit)
00033
CNT0 Start[1]/Stop[0]
W
00034
CNT0 Clear[1]
W
W przypadku kasety komunikacyjnej ADAM-5000/TCP adresy przypisane są natomiast do poszczególnych
jej slotów:
Slot number Analog I/O
Digital I/O
0
40001÷40008 00001÷00016
1
40009÷40016 00017÷00032
2
40017÷40024 00033÷00048
3
40025÷40032 00049÷00064
4
40033÷40040 00065÷00080
5
40041÷40048 00081÷00096
6
40049÷40056 00097÷00112
7
40057÷40064 00113÷00128
Przykładowe transakcje Modbus/TCP będą więc wyglądały następująco:
• Odczyt wartości wejścia analogowego AI-0 dla modułu ADAM-6017:
45-72-00-00-00-06 01 03 00-00 00-01
-> 45-72-00-00-00-05 01 03 02 B8-FA
• Odczyt stanu DI2-DI5 w module ADAM-6060:
45-72-00-00-00-06 01 02 00-02 00-04
-> 45-72-00-00-00-04 01 02 01 0F
• Odczyt zawartości licznika CNT0 (DI0) w module ADAM-6060:
45-72-00-00-00-06 01 03 00-00 00-02
-> 45-72-00-00-00-07 01 03 04 00-19-00-00
• Skasowanie zawartości licznika CNT0 (na DI0) w module ADAM-6060:
45-72-00-00-00-06 01 05 00-21 FF-00
-> 45-72-00-00-00-06 01 05 00-21 FF-00
• Wyłączenie/włączenie licznika CNT0 (na DI0) w module ADAM-6060:
45-72-00-00-00-06 01 05 00-20 00-00
-> 45-72-00-00-00-06 01 05 00-20 00-00
45-72-00-00-00-06 01 05 00-20 FF-00
-> 45-72-00-00-00-06 01 05 00-20 FF-00
• Ustawienie wartości 7.000V dla wyjścia analogowego A03 w module ADAM-5024 (slot 2 kasety ADAM-
5000/TCP):
45-72-00-00-00-06 01 06 00-13 0B-32
-> 45-72-00-00-00-06 01 06 00-13 0B-32
gdzie:
- adres rejestru pobrany ze schematu adresowania (40020 -> 0x0013)
- wartość docelowa rejestru obliczona jako 7/10*4095=2866=0x0B32
- wykorzystana nie opisana tutaj dotychczas funkcja 0x06 - Write Single Register:
Write Single Register - struktura zapytania
FUNCTION CODE
1 bajt
0x06
DATA
Register Address
2 bajty 0x0000-0xFFFF
Register Value
2 bajty 0x0000-0xFFFF
Write Single Register - struktura odpowiedzi
FUNCTION CODE
1 bajt
0x06
DATA
Register Address
2 bajty 0x0000-0xFFFF
Register Value
2 bajty 0x0000-0xFFFF
Na podstawie powyższych informacji możemy już ponownie próbować zaimplementować ten protokół w
naszej aplikacji. Nasza bardzo prosta procedura ustawiająca wyjście cyfrowe DO-0 w module ADAM-
6060 o adresie IP=192.168.2.130, korzystająca z gotowej kontrolki ClientSocket mógłaby więc wyglądać
następująco:
procedure DO0ON;
var start,aktu:word;
komenda:string;
begin
//KONFIGURACJA GNIAZDA
Clientsocket1.Address:='192.168.2.130';
Clientsocket1.Port:=502;
//NAWI
ĄZANIE POŁĄCZENIA
Clientsocket1.Open;
start:=gettickcount;
repeat
aktu:=gettickcount;
Application.ProcessMessages;
until (aktu-start>500)or(Clientsocket1.Socket.Connected=true);
if aktu-start>500 then
begin
ShowMessage('Po
łączenie nie zostało nawiązane...');
exit;
end;
//KOMENDA=MBAP+ID+FUNCTIONCODE+DATA
komenda:=chr($AA)+chr($BB)+chr($00)+chr($00)+chr($00)+chr($06)+chr($01)+chr($05)+chr($00)+chr($10)+chr($FF)+c
hr($00);
//WYS
ŁANIE KOMENDY (ZAPYTANIA)
Clientsocket1.Socket.SendText(komenda);
//ZERWANIE PO
ŁĄCZENIA
Clientsocket1.Close;
end;