接入一台 Modbus 设备很简单。但接入十台则不然。
添加第 N 台设备的成本和时间取决于架构设计。问题已从简单的 I/O 连接转变为定义既易于扩展、又便于技术人员和后续工程师阅读的结构。图 1 展示了该 Modbus 解决方案的图形化概览。
这是一种以用户自定义数据类型(UDT)为核心、聚焦于 PLC 内数据流的方法。请参阅本文,了解将 UDT 蓝图作为 FC、FB 与 DB 之间连接纽带的重要性入门知识。
为清晰起见,此处展示的是一个状态机,其中配置了单个 MB_Master 并通过一个数据块(DB)进行设置。
请勿在此寻找多个 MB_Master 的协调方法。然而,这有助于解决您的#8200(接口繁忙)错误。它还有助于缓解总线争用,因为请求之间的时间间隔是配置的一部分。我们无需反复冲击网络,而是可以限制非关键现场设备的通信频率,从而为真正重要的数据腾出空间。
位置 :编程实践 → 网络与协议
难度:
系统集成商 — 难度等级说明
最后更新:2026 年 3 月 6 日
图 1 :PLC 的 Modbus 结构。
理想的 Modbus 至 PLC 接口的特性
理想的 PLC Modbus 接口具备以下特性:
-
标签名称的集中管理位置。这使得程序员无需深入程序代码即可更改符号名称,例如从 xProx1 改为 xPartPresent。
-
使标签感觉具有循环性,并以接近 PROFINET 的简洁性运行,而 Modbus 则作为后台进程。这将注意力从 Modbus 例程转移到 PLC 标签的自然使用及相关编程上。
-
现场设备初始化以及寄存器映射的集中管理位置。
-
具备可扩展性,重点在于轻松添加第 N 台设备。这反映了现实世界中对清晰度和程序简洁性的重视。
-
代码紧凑,最大限度减少程序员常有的复制粘贴习惯所带来的风险。
-
易于遵循,既适用于凌晨 3 点的故障排查,也适用于在未来三十年内修改代码的程序员。
-
Modbus 允许在出现错误检测、错误代码捕获、用户可调整超时以及给定时间范围内的滑动窗口计数时优雅地失败。
拟议解决方案的高级描述
-
所有设备均使用单个 MB_Master 块和单个 MB_COMM_LOAD 块。
-
每个 Modbus 现场设备的数据、状态和配置都保存在一个结构体中。一个结构体数组被传递给 Modbus 通信引擎(一种类似 API 的代码结构)。
-
使用数据块(DB)来存放符号标签名称,例如 xPartPresent。
-
使用双向桥接将数据从符号数据块映射到 Modbus 中介数据块。因此,用户的程序永远不会直接访问中介数据块。与 PROFINET 类似,底层 Modbus 引擎被隐藏起来。
-
该解决方案高度依赖Siemens的风格指南。在此,用户定义类型(UDT)蓝图定义了所有数据块(DB)、函数(FC)和函数块(FB)的接口形状。这在块的输入/输出(InOut)接口处处理:更新一次即可传播到各处,并消除全局变量的标签混乱。
用于每个 Modbus 现场设备的用户定义类型(UDT)
本项目由合同驱动,其原子性在 Modbus 现场设备寄存器级别定义,如清单 1 所示。包含四个部分:
-
控制 :定义 Modbus 设备操作的字段。这些通常被标识为虚拟 Modbus 块的左侧输入,例如地址(Address)、使能(Enabled)和传输元素数量(NumElementsXFR)。
-
状态 :描述虚拟 Modbus 块输出的字段,例如忙(Busy)、完成(Done)和故障代码(FaultCode)。
-
内部状态 :这些数据容器用于计数器和时间戳,例如一小时内三次失败。
-
数据 :数据数组保存从 Modbus 现场设备传递或接收的信息。
这些结构体共同封装了一个单独的 Modbus 现场设备。然后,将此用户定义类型(UDT)汇总为一个结构体数组,用于定义所有 Modbus 现场设备。这是 Modbus 引擎运行所依据的自包含蓝图。该结构体在 MB_Devices 数据块中实例化,如图 1 所示。
与 Modbus 硬件的接口
MB_Engine 功能块在与 TIA Portal MB_Master 通信以连接Siemens CM 1241 接口时,按顺序处理每个 Modbus 现场设备(图 2)。代码已验证可与一组 SELEC Modbus 设备通信,如本文所述(图 3)。
MB_Engine 是一个相对简单的状态机,按顺序处理每个 Modbus 现场设备。该设计可从清单 1 中推断出来。就我个人而言,我发现设计 DB 和 UDT 结构以兼顾添加第 N 个 Modbus 设备的可扩展性要困难得多。这里有一个教训:代码结构的难度与对接单个 Modbus 设备的简易性形成鲜明对比。我能够在一小时内与单个设备通信,而图 1 中标识的结构(契约)所花费的时间比我愿意承认的还要长。
请注意,UDT 包含用于"TimeUpdateSchedule"的字段。这使我们能够降低非关键现场设备的更新频率,从而节省有限的带宽。
UDT 契约化思维
请记住,UDT 类似于电缆规格说明。
我们已定义了用于在 MB_Devices DB 中存储信息的电缆形状(图 1)。这也是 FC_Reg_Map 与 FB_MB_Engine 之间连接“电缆”的形状。在深入探讨 FC_Reg_map 之前,我们将先探索实例化到 MB_Symbolic_Tags 中的 UDT。
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
清单 1 :Modbus 现场设备的 UDT 描述。
图 2 :Siemens Modbus CM 1241 RS422/486 模块夹在 S7-1200 PLC 与以太网交换机之间。
用于符号标签的 UDT
我们如何将 Modbus 线圈、触点和寄存器转换为支持原生操作的、对程序员友好的标签名称?
再次,我们借助基于契约的 UDT 结构。清单 2 展示了将向程序员公开的标签。在功能上,这类似于 TIA Portal 环境中的 PLC 标签。例如,无需引用 SELEC 数字输出卡上的特定 Modbus 寄存器,我们只需调用 PLRed。随后,我们将该标签视为分布式 I/O 系统中的另一个 PROFINET 标签进行处理。图 1 所示的结构有效地隐藏了底层的 Modbus 硬件。
请注意,诸如 PLRed 之类的符号标签名称是在用户自定义类型(UDT)中定义的。这是一种在所有程序中保持完整性的可靠方法。在此,标签名称仅在一个位置设置——就像在 PLC 标签部分中定义的标签一样(强类型)。这保持了完整性,并允许所有程序通过单行修改后编译来接收更新的标签名称。实际上,我们对 Modbus 标签名称施加了结构和规范。
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
清单 2 :暴露给程序员的 Modbus 标签的用户自定义类型(UDT)。
图 3 :安装在 Modbus 载板上的 SELEC SXP Flex 模块集合。
双向寄存器映射
MB_Reg_map 是 Modbus 引擎与 PLC 控制程序之间的关键链接。它执行三项基本任务:
-
端口映射 :Modbus 是一种通用传输协议。然而,每个 Modbus 现场设备都有由 Modbus 机制寻址的特定寄存器。例如,PLRed 连接到 SELECT 数字输出卡上的输出 Q0。按照我工作台上的配置,它位于 Modbus 地址 3、数据地址 1。
-
初始化 :必须使用设置数据配置 MB_Engine,以便其知道如何处理 MB_Devices 数据块中保存的数据。此数据仅在
首次扫描时配置一次。 -
桥接 :执行双向映射,将来自 MB_Symbolic_Tags 数据块的标签名称转换为 MB_Devices 数据块中通用但位置特定的位置。
配置数据存储位置
毫无疑问,这是设计中最具挑战性的方面。并非因为其困难,而是由于相互冲突的用户需求。
-
技术人员 :技术人员需要一个仪表板来检查 Modbus 数据流。
-
程序员 :系统需要一个单一的真实来源位置。分散的方法会增加不必要的时间投入,以确保一致性并防止不可避免的标签混乱。
我们有多种容器选项可用于存放配置数据,包括:
-
数据块(DB) :配置数据作为各个标签的默认值输入。
-
PLC 标签 :全局常量可以声明为特定值。
-
左侧输入 :可构建配置功能块(FB),将左侧输入映射到内存位置。其工作方式类似于专用的移动块,其中符号映射发生在输入接口处。
-
在功能(FC)内部 :可使用结构化文本逐行映射值。
确定解决方案
所选方案如清单 3 所示,其中展示了 SELEC 数字输出卡的完整映射与配置。IF FirstScan 内的代码通过共享的 MB_Devices 数据块(DB)配置 MB_Engine。其余代码用于构建 Modbus 字,进而控制输出卡。我们甚至包含一个交换函数,以校正数据传输的字节序。
请注意此结构中包含和未包含的内容:
-
所有 Modbus 现场设备寄存器均存在,独立模块寻址也存在。
-
两个数据块(DB)之间的映射已存在。
-
波特率、奇偶校验和恢复时间等内容不存在。这些全局 Modbus 设置在 MB_Engine 层面处理,因为它们适用于网络上的所有现场设备。
// 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;
清单 3 :用于控制 SELEC 数字输出模块的 MB_Reg_Map 函数的代表性部分。
添加第 N 个设备有多困难?
最后,我们可以解决设计中最关键的方面。
非常简单。这是一个三步过程:
-
将寄存器名称添加到清单 2 的符号名称用户定义类型(UDT)中。
-
将设备特定的映射追加到清单 3。
-
将新设备物理连接到网络。
故障排查有多困难?
图 4 提供了对所选方案的论证。此处我们看到 MB_Symbolic_Tags 数据块(DB)的调试屏幕。通过这单一位置,我们可以检查整个 Modbus 网络。
图 4 :MB_Symbolic_Tags DB 的调试视图。
结语
请记住,通过 UDT 蓝图进行组织是本项目的关键所在。
继续探索工业控制系统
如果本讨论对您有帮助,您可能还想了解:
DigiKey 导航
- 完整目录 :工业控制与自动化



