Modbus client#
Starting from version 7.03 Fx2020 has generic modbus interface which can be used for communication with other modbus devices than Fidelix I/O modules.
You can define at most 100 groups (300 starting from version 8.40, 1000 starting from version 9.87) with following features:
address (1..255)
start register (0..65535)
register count (1..16)
register type (1..6)
Several groups may be defined into one device.
Versions 7.03 .. 8.34 support two register types
INPUT read function code 4
HOLDING read function code 3, write 16
Starting from version 8.35 also followings types are supported
COILS read function code 1, write 15
SINGLE COIL read function code 1, write 5
SINGLE HOLDING read function code 3, write 6
DISCRETE INPUTS read function code 2
Detailed description in sample program.
Modbus device must use same baud rate with Fx2020. Setup is done using method defined by device manufacturer. For example Golden Gate is configured using PM Luft made configuration software running in desktop PC.
Note
If baud rate 38400 is used with Golden Gate every register group must have at least 10 registers. With slower speeds there is no limitation.
Changing values in devices and using values that has been read from them is done in IEC program of Fx2020.
Note
Fx2020 needs 2ms delay after sending before it is ready to receive data. If modbus device sends response faster communication does not work especially when high baud rate is used.
Since versiosta 12.20.25 it has been possible to define bus address for communication.
If bus address is zero device works as before and communication uses value of field “Modbus address”
If bus address is nonzero then it is used for communication.
This functionality is needeed eg. when multiple devices are connected to Fx so that each of them has unique IP address and you cannot choose modbus address (always 1 for example)
Now you can create multiple devices with same modbus address into same port.
Samples#
Sample 1#
Sample IEC program has following features - Read values from PM Luft Golden Gate and set values to AI points - Synchronize Golden Gate and Fx2020 clocks - Synchronize Ouman EH203 controller and Fx2020 clocks
1PROGRAM GenericModbus
2
3VAR_EXTERNAL
4END_VAR
5
6VAR_GLOBAL
7END_VAR
8
9VAR
10 InputRegisters:GenericModbusFB;
11 HoldingRegs:GenericModbusFB;
12 Ouman205:GenericModbusFB;
13 rReg : REAL;
14 Result : INT;
15 SystemTime:SystemTimeFB;
16 GG_FailCount : INT;
17 Ouman_FailCount, Ouman_Delay : INT;
18 ModuleErrors : REAL;
19 OumanTime : SystemTimeFB;
20END_VAR
21
22(*
23
24Sample program demonstrating use of generic Modbus interface with PM Luft Golden
25Gate and Ouman EH205
26
27Requires Windows CE version 4.2, Fx2020 version 7.03 and OpenPCS.500 folder for
287.03
29
30NOTE! Sending holding registers to device sets DataValid flag of group to 0.
31
32It will be set back to 1 after succesfull read from module.
33
34GenericModbusFB sends (reads) values to (from) buffer in Fx2020.
35
36It does not do actual modbus communication (it is done by Modbus.exe
37asynchronously)
38
39In case of permanent communication failure DataValid flag stays in value 0 for
40ever.
41
42NOTE! Sending values to input registers is illegal
43
44For this sample to work you must define three generic modbus groups in Fx2020
45 - Module 1 Startregister 0 RegisterCount 16 RegisterType INPUT (PM Luft Golden
46 Gate)
47 - Module 1 Startregister 0 RegisterCount 10 RegisterType HOLDING (PM Luft
48 Golden Gate)
49 - Module 33 Startregister 0 RegisterCount 4 RegisterType HOLDING (Ouman EH205)
50*)
51
52(* Get communication error count of all groups in module 1 *)
53ModuleErrors := GetSystemStatusF( Mode:=1, iParameter:=1, rParameter:=0.0 );
54
55(***
56**** Read measurements from Golden Gate and set values to AI points
57***)
58
59(* Read input register group starting at address 0 from module 1 *)
60InputRegisters(Module:=1,StartRegister:=0, RegisterType:=3);
61
62(* If data has been read from module *)
63IF InputRegisters.Datavalid = 1 THEN
64 (* Convert WORD parameter to REAL without scaling *)
65 rReg := WORD_TO_REAL(InputRegisters.Reg5);
66
67 (* Set value to point *)
68 Result := SetAnalogPointF( Value:=rReg, LockState:=1, Name:='GG_05_SUPPLY_AIR_FLOW' );
69
70 (* Convert WORD parameter to REAL without scaling *)
71 rReg := WORD_TO_REAL(InputRegisters.Reg6);
72
73 (* Set value to point *)
74 Result := SetAnalogPointF( Value:=rReg, LockState:=1, Name:='GG_06_EXHAUST_AIR_FLOW' );
75
76 (* Convert WORD parameter to REAL witht scaling *)
77 rReg := WORD_TO_REAL(InputRegisters.Reg12) / 100.0;
78
79 (* Set value to point *)
80 Result := SetAnalogPointF( Value:=rReg, LockState:=1, Name:='GG_12_SUPPLY_AIR_TEMP' );
81
82 (* Convert WORD parameter to REAL witht scaling *)
83 rReg := WORD_TO_REAL(InputRegisters.Reg13) / 100.0;
84
85 (* Set value to point *)
86 Result := SetAnalogPointF( Value:=rReg, LockState:=1, Name:='GG_13_OUT_AIR_TEMP' );
87
88 (* Convert WORD parameter to REAL witht scaling *)
89 rReg := WORD_TO_REAL(InputRegisters.Reg14) / 100.0;
90
91 (* Set value to point *)
92 Result := SetAnalogPointF( Value:=rReg, LockState:=1, Name:='GG_14_EXHAUST_AIR_TEMP' );
93
94 (* Convert WORD parameter to REAL witht scaling *)
95 rReg := WORD_TO_REAL(InputRegisters.Reg15) / 100.0;
96
97 (* Set value to point *)
98 Result := SetAnalogPointF( Value:=rReg, LockState:=1, Name:='GG_15_SUPPLY_AIR_TEMP_SET' );
99END_IF;
100
101(* Get systen time of Fx2020 *)
102SystemTime();
103
104(***
105**** Synchronize Golden Gate time with Fx2020 time
106***)
107
108(* Read holding register group starting at 0 from module 1 *)
109HoldingRegs(Send:=0, Module:=1, StartRegister:=0, RegisterType:=4);
110
111(* If data has been read from module after last send*)
112IF HoldingRegs.Datavalid = 1 THEN
113 (* If time in Golden Gate is different from time in Fx2020 *)
114 IF SystemTime.Year <> WORD_TO_INT(HoldingRegs.Reg6) OR
115 SystemTime.Month <> WORD_TO_INT(HoldingRegs.Reg5) OR
116 SystemTime.DayOfWeek <> WORD_TO_INT(HoldingRegs.Reg4) OR
117 SystemTime.DayOfMonth <> WORD_TO_INT(HoldingRegs.Reg3) OR
118 SystemTime.Hour <> WORD_TO_INT(HoldingRegs.Reg2) OR
119 SystemTime.Minute <> WORD_TO_INT(HoldingRegs.Reg1) then
120 (* Update fail counter *)
121 GG_FailCount:=GG_FailCount+1;
122 ELSE
123 (* Time Ok, Clear fail counter *)
124 GG_FailCount:=0;
125 END_IF;
126
127 (* If time check has 10 fails in sequence *)
128 IF GG_FailCount > 10 THEN
129 (* Set Fx2020 time into registers *)
130 HoldingRegs.Reg0 := INT_TO_WORD(SystemTime.Second);
131 HoldingRegs.Reg1 := INT_TO_WORD(SystemTime.Minute);
132 HoldingRegs.Reg2 := INT_TO_WORD(SystemTime.Hour);
133 HoldingRegs.Reg3 := INT_TO_WORD(SystemTime.DayOfMonth);
134 HoldingRegs.Reg4 := INT_TO_WORD(SystemTime.DayOfWeek);
135 HoldingRegs.Reg5 := INT_TO_WORD(SystemTime.Month);
136 HoldingRegs.Reg6 := INT_TO_WORD(SystemTime.Year);
137
138 (* Send time to device *)
139 HoldingRegs(Send:=1, Module:=1,StartRegister:=0, RegisterType:=4);
140
141 (* Send done, clear fail counter *)
142 GG_FailCount := 0 ;
143 END_IF;
144END_IF;
145
146(***
147**** Synchronize Ouman EH205 time with Fx2020 time
148***)
149
150(* Check time once / 30 seconds *)
151Ouman_Delay := Ouman_Delay + 1 ;
152
153IF Ouman_Delay > 30 THEN
154 Ouman_Delay := 0 ;
155
156 (* Read time from Ouman EH205 controller *)
157 Ouman205(Send:=0, Module:=33, StartRegister:=0, RegisterType:=4);
158
159 (* If data has been read from module after last send*)
160 IF Ouman205.Datavalid=1 THEN
161 (* Reg 0 = Year *)
162 OumanTime.Year := WORD_TO_INT(Ouman205.Reg0);
163
164 (* Reg 1 high byte = Month *)
165 OumanTime.Month := WORD_TO_INT( SHR(Ouman205.Reg1,8) );
166
167 (* Reg 1 low byte = Day *)
168 OumanTime.DayOfMonth := WORD_TO_INT( 16#00FF AND Ouman205.Reg1 );
169
170 (* Reg 2 high byte = Hour *)
171 OumanTime.Hour := WORD_TO_INT( SHR(Ouman205.Reg2,8) );
172
173 (* Reg 2 low byte = Minute *)
174 OumanTime.Minute := WORD_TO_INT( 16#00FF AND Ouman205.Reg2 );
175
176 (* Reg 3 high byte = Second *)
177 OumanTime.Second := WORD_TO_INT( SHR(Ouman205.Reg3,8) );
178
179 (* Reg 3 low byte = Day Of Week *)
180 OumanTime.DayOfWeek := WORD_TO_INT( 16#00FF AND Ouman205.Reg3 );
181
182 (* If time in SH203 is different from time in Fx2020 *)
183 IF SystemTime.Year <> OumanTime.Year OR
184 SystemTime.Month <> OumanTime.Month OR
185 SystemTime.DayOfWeek <> OumanTime.DayOfWeek OR
186 SystemTime.DayOfMonth <> OumanTime.DayOfMonth OR
187 SystemTime.Hour <> OumanTime.Hour OR
188 SystemTime.Minute <> OumanTime.Minute THEN
189 (* Update fail counter *)
190 Ouman_FailCount:=Ouman_FailCount+1;
191 ELSE
192 (* Time Ok, Clear fail counter *)
193 Ouman_FailCount:=0;
194 END_IF;
195
196 (* If time check has 3 fails in sequence *)
197 IF Ouman_FailCount >= 3 THEN
198 Ouman205.Reg0 := INT_TO_WORD( SystemTime.Year);
199 Ouman205.Reg1 := INT_TO_WORD( SystemTime.Month *256 + SystemTime.DayOfMonth );
200 Ouman205.Reg2 := INT_TO_WORD( SystemTime.Hour *256 + SystemTime.Minute );
201 Ouman205.Reg3 := INT_TO_WORD( SystemTime.Second*256 + SystemTime.DayOfWeek );
202
203 (* Send time to device *)
204 Ouman205(Send:=1, Module:=33,StartRegister:=0, RegisterType:=4);
205
206 Ouman_FailCount := 0;
207 END_IF;
208 END_IF;
209END_IF;
210
211END_PROGRAM
Sample 2#
Example program contains code about using new features in version 8.35.
1PROGRAM GenericModbus2
2
3VAR_EXTERNAL
4END_VAR
5
6VAR_GLOBAL
7END_VAR
8
9VAR
10 (*
11 ** NOTE! These examples are using same registers numbers for different data types
12 ** Modbus device may have both INPUT REGISTER number 2 and HOLDING REGISTER number 2
13 ** they may hold same or different data, it is manufacturer decision.
14 **
15 ** To make it easier to read this example has own FB variable for each group.
16 ** Unless you want to save values in FB over execution loops of program you could
17 ** as well use same FB variable for all groups.
18 *)
19
20 (* This group has DISCRETE INPUTS at address=1, type=2, count=4 *)
21 DiscreteInputs : GenericModbusFB;
22
23 (* Variables for discrete input values *)
24 DI1, DI2 : INT;
25
26 (* This group has INPUT REGISTERS at address=1, type=4, count=12 *)
27 InputRegs : GenericModbusFB;
28
29 (* Variable for input register value *)
30 IR3 : INT;
31
32 (* This group has COILS at address=1, type=1, count=2 *)
33 Coils : GenericModbusFB;
34
35 (* Variables for DO point values *)
36 DO1, DO2 : INT;
37
38 (* This group has SINGLE COIL at address 1, type=5, count=1 *)
39 SingleCoil : GenericModbusFB;
40
41 (* This group has HOLDING REGISTERS at address=2, type=3, count=5 *)
42 HoldingRegs : GenericModbusFB;
43
44 (* Variable for AO point value *)
45 AO2 : INT;
46
47 (* This group has SINGLE HOLDING REGISTER at address=2, type=6, count=1 *)
48 SingleHoldingReg : GenericModbusFB;
49
50 (* Modbus address of device *)
51 Module : INT;
52
53 (* Couple of WORD variables needed for type conversion *)
54 w1, w2, w3 : WORD;
55END_VAR
56
57(* Set modbus address of device *)
58Module := 40;
59
60(*********************************************************)
61(******************** DISCRETE INPUTS ********************)
62(*********************************************************)
63
64(*
65** DISCRETE INPUT (later DI) is single bit read only data type in modbus.
66** It may have values OFF/ON = 0/1.
67** 8 DI values are packed in one 8 bit number
68** Fx2020 is using 16 bit registers which means that each register holds values for 16 DIs
69** If you have 27 DI values they are found from registers of function block as follows:
70** Reg0 holds values for DI01-DI16
71** Reg1 holds values for DI17-DI27
72** Bits in Reg0
73** 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
74** DI8 DI7 DI6 DI5 DI4 DI3 DI2 DI1 DI16 DI15 DI14 DI13 DI12 DI11 DI10 DI9
75** Bits in Reg1
76** 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
77** DI23 DI22 DI21 DI20 DI19 DI18 DI17 --- --- --- --- DI27 DI26 DI25 D24
78*)
79
80(* Get values of 4 inputs using modbus function code 2 *)
81DiscreteInputs( Send:=0 , Module:=Module , StartRegister:=1 , RegisterType:=2 );
82
83(* If values has been read from device *)
84IF DiscreteInputs.DataValid = 1 THEN
85 (* Shift right, bit 8 --> bit 1 *)
86 w1 := SHR( DiscreteInputs.Reg0, 8 );
87
88 (* INT variable DI1 gets value 0/1 *)
89 DI1 := WORD_TO_INT(w1 AND 16#0001);
90
91 (* Shift right, bit 9 --> bit 1 *)
92 w1 := SHR( DiscreteInputs.Reg0, 8 );
93
94 (* INT variable DI2 gets value 0/1 *)
95 DI2 := WORD_TO_INT(w1 AND 16#0001);
96END_IF;
97
98(*********************************************************)
99(******************** INPUT REGISTERS ********************)
100(*********************************************************)
101
102(*
103** INPUT REGISTER is 16 bit read only data type in modbus
104** It may hold any kind of data (bits, integers, decimal values)
105** Actual data type depends on device type
106*)
107
108(* This example reads value of input register 3 into variable IR3 *)
109(* Get values of 12 register using modbus function code 4 *)
110InputRegs( Send:=0 , Module:=Module , StartRegister:=1 , RegisterType:=4 );
111
112(* If values has been read from device *)
113IF InputRegs.DataValid=1 THEN
114 (* Reg2 = input register 3 because start register of device is 1 *)
115 IR3 := WORD_TO_INT(InputRegs.Reg2);
116END_IF;
117
118(*********************************************************)
119(******************** COILS ******************************)
120(*********************************************************)
121
122(*
123** COIL is single bit read/write data type in modbus.
124** It is packed in registers same way as DISCRETE INPUTS
125*)
126
127(* This example sets values of two DO points into corresponding coils in modbus device *)
128
129(* Get values of 2 coils using modbus function code 1 *)
130Coils( Send:=0 , Module:=Module , StartRegister:=1 , RegisterType:=1 );
131
132(* If values has been read from device *)
133IF Coils.DataValid=1 THEN
134 (* Value 0/1 of DO1 as INT *)
135 DO1 := GetDigitalPointF( 'POINT1_DO' );
136
137 (* Value 0/1 of DO2 as INT *)
138 DO2 := GetDigitalPointF( 'POINT2_DO' );
139
140 (* Value 0/1 of DO1 as WORD *)
141 w1 := INT_TO_WORD(DO1);
142
143 (* Value 0/1 of DO2 as WORD *)
144 w2 := INT_TO_WORD(DO2);
145
146 (* Set both bits into w3, NOTE! coil 1 is in bit 8 *)
147 w3 := SHL(w2,9) OR SHL(w1,8);
148
149 (* If coil values in device do not match values of DO points *)
150 IF Coils.Reg0 <> w3 THEN
151 (* Set new value to register *)
152 Coils.Reg0 := w3;
153 (* Send coils using modbus function code 15 *)
154 Coils( Send:=1 );
155 END_IF;
156END_IF;
157
158(* This example inverts value of coil 1 *)
159(* Get value of 1 coil using modbus function code 1 *)
160SingleCoil( Send:=0 , Module:=Module , StartRegister:=1 , RegisterType:=5 );
161
162(* If value has been read from device *)
163IF SingleCoil.DataValid=1 THEN
164 (* Just like in previous example coil 1 is in bit 8 of register *)
165 IF SingleCoil.Reg0 = 16#0100 THEN
166 (* Value 0 in WRITE SINGLE COIL message is 16#0000 *)
167 SingleCoil.Reg0 := 16#0000 ;
168 ELSE
169 (* Value 1 in WRITE SINGLE COIL message is 16#FF00 *)
170 SingleCoil.Reg0 := 16#FF00 ;
171 END_IF;
172
173 (* Send one coil using modbus function code 5 *)
174 SingleCoil( Send:=1 );
175END_IF;
176
177(*********************************************************)
178(******************** HOLDING REGISTERS ******************)
179(*********************************************************)
180
181(*
182** HOLDING REGISTER is 16 bit read/write data type in modbus
183** It may hold any kind of data (bits, integers, decimal values)
184** Actual data type depends on device type
185*)
186
187(* This example sets value of AO point to holding register 2 in device *)
188(* Get values of 5 registers using modbus function code 3 *)
189HoldingRegs( Send:=0 , Module:=Module , StartRegister:=2 , RegisterType:=3 );
190
191(* If value has been read from device *)
192IF HoldingRegs.DataValid=1 THEN
193 (* Value of AO1 as INT *)
194 AO2 := GetDigitalPointF( 'POINT1_AO' );
195
196 (* If value in device do not match value of AO point *)
197 IF HoldingRegs.Reg0 <> INT_TO_WORD(AO2) THEN
198 (* Set new value to register *)
199 HoldingRegs.Reg0 := INT_TO_WORD(AO2);
200 (* Send 5 registers using modbus function code 16 *)
201 HoldingRegs( Send:=1 );
202 END_IF;
203END_IF;
204
205(* This example sets value of AO point to holding register 2 in device *)
206(* Get value of one register using modbus function code 3 *)
207SingleHoldingReg( Send:=0 , Module:=Module , StartRegister:=2 , RegisterType:=6 );
208
209(* If value has been read from device *)
210IF SingleHoldingReg.DataValid=1 THEN
211 (* Value of AO1 as INT *)
212 AO2 := GetDigitalPointF( 'POINT1_AO' );
213
214 (* If value in device do not match value of AO point *)
215 IF SingleHoldingReg.Reg0 <> INT_TO_WORD(AO2) THEN
216 (* Set new value to register *)
217 SingleHoldingReg.Reg0 := INT_TO_WORD(AO2);
218 (* Send one register using modbus function code 6 *)
219 SingleHoldingReg( Send:=1 );
220 END_IF;
221END_IF;
222
223END_PROGRAM