Scalable Modbus Architecture in TIA Portal: The Nth Device Strategy

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.

:link: 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:

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.

1 Like