Interfacing to one Modbus device is easy. Ten is not.
The cost and time to add the Nth device is determined by structure. The problem shifts from simple I/O connections to defining structures that are easily expandable while retaining readability for technicians and future engineers. Figure 1 presents a graphical overview of the Modbus solution.
This is a UDT-centric focus on data flow in the PLC. Please refer to this article for a primer on the importance of using the UDT blueprint as the connective tissue between FC, FB, and DB.
For clarity this is a state machine with single MB_Master configured via a DB.
Do not look here for coordination of multiple MB_Masters. However, this can help with your #8200 (Interface Busy) errors. It can also help with bus contention as the time between requests is part of the configuration. Instead of hammering the network, we can throttle back non-essential field devices making room for the truly important data.
See Siemens Resource Hub.
Figure 1: Modbus Structure for a PLC.
Attributes of the Ideal Modbus to PLC Interface
The ideal PLC Modbus interface has the following attributes:
-
Central location for tag names. This allows the programmer to change symbolic names without digging through the programs e.g., xProx1 to xPartPresent.
-
Makes the tags feel cyclic with near-PROFINET simplicity with Modbus as a background process. This shifts attention from the Modbus routines to the natural use of PLC tags and associated programming.
-
Central location for the field devices’ initialization as well as the register mapping.
-
Expandable with an emphasis on adding the Nth device. This reflects real-world emphasis on clarity with program simplicity.
-
Compact code with minimal exposure to the copy-and-paste habits that haunt programmers.
-
Easy to follow for the 3 AM troubleshooting as well as the programmer who modifies the code over the next three decades.
-
Modbus is allowed to fail gracefully with error detection, error code capture, user adjustable timeout, and a moving window for strikes with a given timeframe.
High Level Description of the Proposed Solution
-
A single MB_Master and a single MB_COMM_LOAD block are used for all devices.
-
Data, state, and configuration for each Modbus field device are held in a struct. An array of structs is passed to the Modbus communication engine (an API-like code structure).
-
A DB is used to house the symbolic tag names such as xPartPresent.
-
A bidirectional bridge is used to map the data from the symbolic DB to the Modbus mediator DB. Consequently, the user’s program never touches the mediator DB. Like PROFINET, the low level Modbus engine is hidden.
-
The solution leans heavily on the Siemens style guide. Here, the UDT blueprint defines the shape of the interface for all DBs, FCs, and FBs. This is handled at at the blocks InOut interface: update once, propagate everywhere, and eliminate tag-spaghetti of global variables.
UDT for Each Modbus Field Device
This project is contract driven with atomicity defined at the Modbus field device register level as shown in Listing 1. There are four sections including:
-
Controls: Fields that define the operation of the Modbus device. These are generally identified as the left-hand input to the virtual Modbus block e.g., Address, Enabled, and NumElementsXFR.
-
Status: Fields that describe the outputs of the virtual Modbus block e.g., Busy, Done, and FaultCode.
-
Internal State: These data containers are used for counters and time stamps e.g., three strikes in a single hour.
-
Data: The data array holds information passed or received from the Modbus field device.
Together, these structs encapsulate a single Modbus field device. This UDT is then rolled up into an array of structs that define all Modbus field devices. This is the self-contained blueprint upon which the Modbus engine operates. The struct is instantiated in the MB_Devices DB as shown in Figure 1.
Interface with the Modbus Hardware
The MB_Engine function block sequentially handles each Modbus field device as it communicates with the TIA Portal MB_Master to talk to the Siemens CM 1241 interface (Figure 2). The code was verified talking to a group of SELEC Modbus devices as described here (Figure 3).
The MB_Engine is a relatively straightforward state machine that sequentially handles each Modbus field device. The design can be inferred from Listing 1. Personally, I found it much harder to design the DB and UDT structure with an eye toward adding the Nth Modbus device. There is a lesson here about the difficulty of code structure vs the ease of interfacing to a single Modbus device. I was able to talk to a single device within an hour while the structure (contract) identified in Figure 1 took more time than I like to admit.
Note that the UDT contains fields for `TimeUpdateSchedule’. This allows us to throttle down non-essential field devices thereby preserving the limited bandwidth.
UDT Contractual Thinking
Remember that a UDT is like a cable specification.
We have defined the shape of the cable used to store information in the MB_Devices DB (Figure 1). This is also the shape of the connective “cable” between the FC_Reg_Map and FB_MB_Engine. Before diving into the FC_Reg_map we will explore the UDT instantiated into MB_Symbolic_Tags.
TYPE "UDT_MB_Device"
VERSION : 0.1
STRUCT
Controls : Struct
SymbolicName : String[50] := 'FIXME';
Enable : Bool := true; // Field device enabled
MB_ADDR : UInt; // Default address range: 0 to 247
DATA_ADDR : UDInt;
Mode : USInt; // 0 = read, 1 = write
NumElementsXfr : UInt;
TimeUpdateSchedule : Time := T#1s; // T#0s = poll whenever eligible
ResponseTimeout : Time := T#500ms; // User controlled timeout
MaxNumStrikes : UInt := 3; // Default to three strikes in an hour
TimeStrikeWindow : Time := T#1h;
PolicyDisableOnFault : Bool; // 0 = continue, 1 = remove from queue
END_STRUCT;
Status : Struct
Busy : Bool; // Module is on the bus
Done : Bool; // High for one scan cycle
DTLLastSuccessComms {InstructionName := 'DTL'; LibVersion := '1.0'} : DTL;
Fault : Bool; // Sticky
FaultCode : Word; // Siemens specific
END_STRUCT;
InternalState : Struct // Items for the error handler
NumStrikes : UInt;
DTLStartComms {InstructionName := 'DTL'; LibVersion := '1.0'} : DTL;
DTLStrikeTimeBegin {InstructionName := 'DTL'; LibVersion := '1.0'} : DTL;
END_STRUCT;
Data : Struct
Data : Array[0..31] of Word; // Increase if needed
END_STRUCT;
END_STRUCT;
END_TYPE
Listing 1: UDT description of a Modbus field device.
Figure 2: The Siemens Modbus CM 1241 RS422/486 module is sandwiched between a S7-1200 PLC and an Ethernet switch.
UDT for the Symbolic Tags
How do we convert Modbus coils, contacts, and registers to programmer-friendly tag names with native operations.
Once again, we turn to the contract-based structure of the UDT. Listing 2 presents the tags that will be exposed to the programmer. Functionally, this is similar to a PLC tag in the TIA Portal environment. For example, instead of referring to a specific Modbus register on the SELEC Digital output card we simply call PLRed. We then handle the tag as if it were just another PROFINET tag in a distributed I/O system. The structure shown in Figure 1 has effectively hidden the underlying Modbus hardware.
Observe that symbolic tag names such as PLRed are defined within the UDT. This is a robust way to maintain integrity across all programs. Here, the tag name is set in one and only one location — just like a tag defined in the PLC tag section (strong typing). This preserves integrity and allows all programs to receive an updated tag name using a single-line modification followed by a compilation. In effect we have imposed structure and discipline on the Modbus tag names.
TYPE "UDT_MB_REG_MAP"
VERSION : 0.1
STRUCT
SELEC_Digital_Out : Struct
Coils : Struct
PLRed : Bool;
PLGreen : Bool;
CR1 : Bool;
CR2 : Bool;
Q4 : Bool;
Q5 : Bool;
Q6 : Bool;
Q7 : Bool;
END_STRUCT;
Input_Reg : Struct
MOD_ID : Word;
HW_VerNo : Word;
SW_VerNo : Word;
SlotStatus : Word;
SlotErrorCntr : Word;
END_STRUCT;
END_STRUCT;
SELEC_Digital_In : Struct
Contacts : Struct
SW1 : Bool;
PBGreen : Bool;
PBRedNormallyClosed : Bool;
I3 : Bool;
I4 : Bool;
I5 : Bool;
I6 : Bool;
I7 : Bool;
I8 : Bool;
I9 : Bool;
I10 : Bool;
I11 : Bool;
I12 : Bool;
I13 : Bool;
END_STRUCT;
Input_Reg : Struct
MOD_ID : Word;
HW_VerNo : Word;
SW_VerNo : Word;
SlotStatus : Word;
SlotErrorCntr : Word;
END_STRUCT;
END_STRUCT;
SELEC_ANALOG : Struct
Input_Reg : Struct
MOD_ID : Word;
HW_VerNo : Word;
SW_VerNo : Word;
SlotStatus : Word;
SlotErrorCntr : Word;
A_In_0 : Word; // AI3_U_PV0
A_In_1 : Word; // AI3_U_PV1
A_In_2 : Word; // AI3_U_PV2
AI3_U_PVS0 : Word;
AI3_U_PVS1 : Word;
AI3_U_PVS2 : Word;
END_STRUCT;
Holding_Reg : Struct
A_IN_CH_0_CFG : Word; // AI3_U_SEN0, Set to 15 to measure voltage
A_IN_CH_1_CFG : Word; // AI3_U_SEN0
"A__IN_CH_2_CFG" : Word; // AI3_U_SEN0
A_Out_0 : Word; // AO2_U_OPT0
A_Out_1 : Word; // AO2_U_OPT1
AO2_U_TYP0 : Word;
AO2_U_TYP1 : Word;
END_STRUCT;
END_STRUCT;
END_STRUCT;
END_TYPE
Listing 2: UDT for the Modbus tags exposed to the programmer.
Figure 3: Collection of SELEC SXP Flex modules each installed in a Modbus carrier.
Bidirectional Register Mapping
The MB_Reg_map is the critical link between the Modbus engine and the PLC’s control program. It performs three essential tasks:
-
Port mapping: Modbus is a generic transport protocol. However, each Modbus field device has specific registers that are addressed by the Modbus machinery. For example, the PLRed is connected to output Q0 in the SELECT Digital output card. As configured on my workbench, this exists at Modbus address 3, data address 1.
-
Initialization: The MB_Engine must be configured with setup data so that it knows what to do with the data held in the MB_Devices DB. This data is configured once upon
FirstScan. -
Bridge: A bidirectional mapping is performed converting tag names from the MB_Symbolic_Tags DB to the generic, but position specific location, of the MB_Devices DB.
Configuration Data Storage Location
Without question, this is the most challenging aspect of the design. Not because it was difficult but because of the conflicting user requirements.
-
Technician: The technician needs a dashboard to examine the Modbus data flow.
-
Programmer: The system needs a single location for truth. A scattered approach would add an unwelcome time commitment to ensure consistency and prevent the inevitable tag-spaghetti.
We have several container options to house the configuration data including:
-
DB: Configuration data are entered as the default value for individual tags.
-
PLC Tag: Global constants may be declared with specific values.
-
Left-hand Side inputs: The configuration FB could be constructed to map the left-hand-inputs to memory locations. This would operate like a dedicated move block where the symbolic mapping occurs at the input interface.
-
Inside the FC: Values may be mapped line by line using structured text.
Settling on a Solution
The chosen solution is shown in Listing 3 where we see the complete mapping and configuration for the SELEC digital output card. The code within the IF FirstScan configures the MB_Engine via shared MB_Devices DB. The remainder of the code is used to build the Modbus word which then controls the output card. We even include a swap function to correct for the endianness of the data transfer.
Note what is and what is not contained within this structure:
-
All Modbus Field device registers are present as is the individual module addressing.
-
Mapping between the two DB is present.
-
Things like baud rate, parity, and recover time are absent. These global Modbus settings are handled at the MB_Engine level as they are applicable to all field devices on the network.
// Mapping for the SELEC FL-SC-TO08-CE Digital Output Card.
//
// * 8 digital inputs
// * Write starting at MB address 1
IF "FirstScan" THEN
#MB.Device[#SELEC_D_COILS].Controls.SymbolicName := 'SELEC Digital Output FL-SC-TO08-CE';
#MB.Device[#SELEC_D_COILS].Controls.MB_ADDR := 3;
#MB.Device[#SELEC_D_COILS].Controls.DATA_ADDR := 1;
#MB.Device[#SELEC_D_COILS].Controls.Mode := #WRITE;
#MB.Device[#SELEC_D_COILS].Controls.NumElementsXFR := 8;
// All other controls set via defaults established in UDT_MB_Device
END_IF;
#wTemp.%X0 := #REG.SELEC_Digital_Out.Coils.PLRed;
#wTemp.%X1 := #REG.SELEC_Digital_Out.Coils.PLGreen;
#wTemp.%X2 := #REG.SELEC_Digital_Out.Coils.CR1;
#wTemp.%X3 := #REG.SELEC_Digital_Out.Coils.CR2;
#wTemp.%X4 := #REG.SELEC_Digital_Out.Coils.Q4;
#wTemp.%X5 := #REG.SELEC_Digital_Out.Coils.Q5;
#wTemp.%X6 := #REG.SELEC_Digital_Out.Coils.Q6;
#wTemp.%X7 := #REG.SELEC_Digital_Out.Coils.Q7;
#wTemp := SWAP(#wTemp);
#MB.Device[#SELEC_D_COILS].Data.Data[0] := #wTemp;
Listing 3: Representative part of the MB_Reg_Map function used to control the SELEC digital output module.
How hard is it to add the Nth device?
Finally we can address the most important aspect of the design.
It’s very easy. It’s a three-step process:
-
Add the register names to the Listing 2 symbolic names UDT
-
Append the device-specific mapping to Listing 3.
-
Physically connect the new device to the network.
How hard is it to troubleshoot?
Figure 4 provides the justification for the chosen solution. Here we see the debug screen for the MB_Symbolic_Tags DB. From this single location we can examine the entire Modbus network.
Figure 4: Debug view of the MB_Symbolic_Tags DB.
Parting Thoughts
Remember that organization via the UDT blueprint is the essential piece of this project.
Let me know in the comments if you would like to explore the MB engine and the error handler.
Sincerely,
Aaron
Related Articles by this Author
If you enjoyed this article, you may also find these related articles helpful:
- Getting Started with the Siemens ET 200SP Distributed I/O
- Make Your PLC Play a Song: Mastering Siemens PLC Memory Arrays
- Use of an Interposing Relay for Increased Contactor Speed
About this Author
Aaron Dahlen, LCDR USCG (Ret.), serves as an application engineer at DigiKey. He has a unique electronics and automation foundation built over a 27-year military career as a technician and engineer which was further enhanced by 12 years of teaching (interwoven). With an MSEE degree from Minnesota State University, Mankato, Dahlen has taught in an ABET-accredited EE program, served as the program coordinator for an EET program, and taught component-level repair to military electronics technicians.
Dahlen has returned to his Northern Minnesota home, completing a decades-long journey that began as a search for capacitors. Read his story here.



