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