深入​解读​Modbus​协议

概览

​Modbus​是​工业​领域​常用​的​一种​协议,​于​1979​年​制定​而​成,​旨​在​实现​自动​化​设备​之间​的​通信。​Modbus​最初​是​作为​通过​串​行​层​传输​数据​的​应用​级​协议​实现​的,​如今​扩展​后​也​可​通过​串​行、​TCP/​IP​和​用户​数据​报​协议​(UDP)​实现。​本文​档​对​协议​实现​进行​了​深入​解读。

内容

什么​是​Modbus​协议?

Modbus​是​通过​主从关系​实现​的​请求-​响应​协议。​在​主从关系​中,​通信​总是​成​对​发生​(一个​设备​必须​发起​请求,​然后​等待​响应),​并且​发起​请求​的​设备​(主​设备)​负责​发起​每次​交互。​通常,​主​设备​是​人​机​界面​(HMI)​或者​监​控​和​数据​采集​(SCADA)​系统,​而​从​设备​是​传感器、​可​编​程​逻辑​控制器​(PLC)​或可​编​程​自动​化​控制器​(PAC)。​这些​请求​和​响应​的​内容​以及​发送​这些​消息​的​网络​层​由​协议​的​不同​层​来​定义。

主从网络关系

图1.主​从​网络​关系

Modbus​协议层

在​最初​的​做法​中,​Modbus​是​建立​在​串​行​端​口​之上​的​单一​协议,​因此​它​不能​被​分成​多个​层。​随着​时间​的​推移,​该​协议​引入​了​不同​的​应用​数据​单元,​来​更改​串​行​通信​所用​的​数据​包​格式,​或​允许​使用​TCP/​IP​和​用户​数据​报​协议​(UDP)​网络。​这样​便​实现​了​核心​协议​和​网络​层​的​分离,​前者​用于​定义​协议​数据​单元​(PDU),​后者​用于​定义​应用​数据​单元​(ADU)。

协议​数据​单元

PDU​及其​处理​代码​构成了Modbus​应用​协议​规范的​核心。​该​规范​定义​了​PDU​的​格式、​协议​使用​的​各种​数据​概念、​如何​使用​功能​码​访问​数据,​以及​每​个​功能​码​的​具体​实现​和​限制。

Modbus PDU​格式​可​定义​为​功能​码,​后​跟​一​组​关联​数据。​该​数据​的​大小​和​内容​由​功能​码​定义,​整个​PDU(功能​码​和​数据)​的​大小​不能​超过​253​个​字​节。​每​个​功能​码​都有​一个​特定​的​行为,​从​设备​可以​根据​所需​的​应用​程序​行为​灵活​地​实现​这些​行为。​PDU​规范​定义​了​数据​访问​和​操作​的​核心​概念;​但是,​从​设备​可能​会​以​规范​中​未​明确​定义​的​方式​处理​数据。

访问​Modbus​和​Modbus​数据​模型​中的​数据

一般​来说,​Modbus​可​访问​的​数据​存储​在​以下​4​个​数据​库​或​地址​范围​中的​其中​一个:​线圈、​离散​量​输入、​保持​寄存器​和​输入​寄存器。​与​许多​规范​一样,​这些​名称​可能​因​行业​或​应用​而​异。​例如,​保持​寄存器​可能​称为​输出​寄存器,​线圈​可能​称为​数字​或​离散​量​输出。​这些​数据​库​定义​了​所​包含​数据​的​类型​和​访问​权限。​从​设备​可以​直接​访问​这些​数据,​因为​这些​数据​由​设备​本地​托管。​Modbus​可​访问​的​数据​通常​是​设备​主​存​的​一个​子​集。​相反,​Modbus​主​设备​必须​通过​各种​功能​码​请求​访问​这些​数据。​表​1​中​描述​了​每​个​区块​的​行为。

 

内存​区块 数据​类型 主​设备​访问 从​设备​访问
线圈 布尔 读​取/​写入 读​取/​写入
离散​量​输入 布尔 只读 读​取/​写入
保持​寄存器 无​符号​双​字​节​整型 读​取/​写入 读​取/​写入
输入​寄存器 无​符号​双​字​节​整型 只读 读​取/​写入

表​1.Modbus​数据​模型​区块

通过​这些​区块,​您​可以​限制​或​允许​访问​不同​的​数据​元素,​还​可以​在​应用​层​提供​简化​的​机制​来​访问​不同​的​数据​类型。

这些​区块​是​完全​概念​性的。​它们​可能​作为​独立​的​内存​地址​存在​于​给​定​的​系统​中,​但​也​可能​重叠。​例如,​线圈​1​可能​存在​于​与​保持​寄存器​1​所​代表​的​字​的​第​一位​相同​的​内存​中。​寻​址​方案​完全​由​从​设备​定义,​其​对​每​个​内存​区块​的​解释​是​设备​数据​模型​的​重要​组成​部分。

数据​模型​寻址

该​规范​将​每​个​区块​定义​为​包含​多​达​65,536 (216)​个​元素​的​地址​空间。​在​PDU​的​定义​中,​Modbus​定义​了​每​个​数据​元素​的​地址,​范围​是​从​0​到​65,535。​然而,​每​个​数据​元素​的​编号​从​1​到​n,​其中​n​的​最大值​为​65,536。​也就是说,​线圈​1​位于​地址​0​的​线圈​区块​中,​而​保持​寄存器​54​位于​从​设备​定义​为​保持​寄存器​的​内存​部分​中的​地址​53。

规范​允许​的​全部​范围​不需要​给​定​设备​实现。​例如,​设备​可能​会​选择​不​执行​线圈、​离散​量​输入​或​输入​寄存器,​而​只​使用​保持​寄存器​150​至​175​和​200​至​225。​这​是​完全​可以​接受​的,​而且​可以​通过​例外​来​处理​无效​的​访问​尝试。

数据​寻​址​范围

虽然​规范​将​不同​的​数据​类型​定义​为​存在​于​不同​的​区块​中,​并​为​每​种​类型​分配​一个​本地​地址​范围,​但​这​并不​一定能​转化​为​用于​记录​或​了解​给​定​设备​的​Modbus​可​访问​内存​的​直观​寻​址​方案。​为了​简化​对​内存​区块​位置​的​理解,​我们​引入​了​一种​编号​方案,​即​向​所​讨论​数据​的​地址​中​添加​前​缀。

例如,​在​设备​手册​中,​数据​项​不会​表示​为​位于​地址​13​的​保持​寄存器​14,​而是​表示​为​位于​地址​4,014、​40,014​或​400,014​的​数据​项。​在​这​几​种​情况​中,​第​一个​数字​都是​4,​表示​保持​寄存器,​其余​数字​则​用于​指定​地址。​4XXX、​4XXXX​和​4XXXXX​的​区别​取决​于​设备​所用​的​地址​空间。​如果​65,536​个​寄存器​全部​都在​使用,​应该​使用​4XXXXX​符号,​因为​它​支持​400,001​到​465,536​的​范围。​如果​只有​几个​寄存器​在​使用,​通常​的​做法​是​使用​4,001​到​4,999​的​范围。

在​这种​寻​址​方案​中,​每​种​数据​类型​都​被​分配​了​一个​前​缀,​如​表​2​所​示。

 

数据​区块 前缀
线圈 0
离散​量​输入 1
输入​寄存器 3
保持​寄存器 4

表​2.数据​范围​前缀

线圈​的​前​缀​为​0,​这​意味​着​4001​的​引用​可能是​指​保持​寄存器​1​或​线圈​4001。​出于​这个​原因,​建议​所有​的​新​实现​都​使用​带​前​导​零​的​6​位​数​寻​址,​并​在​记录​时​注明​这​一点。​因此,​保持​寄存器​1​的​地址​为​400,001,​而​线圈​4001​的​地址​则为​004,001。

数据​地址​起始值

内存​地址​和​引用​编号​之间​的​差异​会​因​给​定​应用​程序​选择​的​索引​而​进一步​复杂​化。​如​前​所述,​保持​寄存器​1​位于​地址​0。​通常,​引用​编号​索引​从​1​开始,​这​意味​着​给​定​范围​的​起始​值​为​1。​因此,​400,001​即​表示​位于​地址​0​的​保持​寄存器​00001。​一些​实现​选择​以​0​作为​范围​起始​值,​即​400,000​表示​位于​地址​0​的​保持​寄存器。​表​3​展示​了​这个​概念。

地址 寄存器​编号 编号​(索引​从​1​开始,​标准) 编号​(索引​从​0​开始,​备​选)
0 1 400001 400000
1 2 400002 400001
2 3 400003 400002

表​3.寄存器​索引​方案

 

从​1​开始​的​索引​范围​应用​较​为​广泛,​强烈​建议​采用​这种​方案。​无论​哪​种​情况,​每​个​范围​的​起始​值​都​应​在​记录​时​注明。

大​数据​类型

Modbus​标准​提供​了​一个​相对​简单​的​数据​模型,​该​模型​不​包含​除​无​符号​双​字​节​整型​和​位​值​之外​的​其他​数据​类型。​如果​系统​的​位​值​对应​于​螺线​管​和​继电器,​并且​双​字​节​整型​值​对应​于​未​缩​放​的​ADC​值,​上述​模型​便​已​足够;​但​对于​更​高级​的​系统,​则​无法​满足​需求。​因此,​许多​Modbus​实现​都​包含​跨​寄存器​边界​的​数据​类型。​NI LabVIEW​数据​记录​和​监​控​(DSC)​模块以及KEPServerEX都​定义​了​许多​引用​类型。​例如,​存储​在​保持​寄存器​中的​字符​串​应​遵循​标准​格式​(400,001),​但​后​跟​一个​十进制​数、​长度​和​字符​串​的​字​节序​(400,001.2H​是​指​保持​寄存器​1​中​包含​两​个​字符​的​字符​串,​其中​高位​字​节​对应​到​字符​串​的​第​一个​字符)。​这​是​必需​的,​因为​每​个​请求​的​大小​都是​有限​的,​所以​Modbus​主​设备​必须​知道​字符​串​的​确切​范围,​而不是​搜索​长度​或​分隔​符​(如​NULL)。

位​访问

除了​允许​访问​跨​寄存器​边界​的​数据​之外,​一些​Modbus​主​设备​还​支持​对​寄存器​中​各个​位​的​引用。​由于​允许​设备​将​相同​内存​范围​内的​每​种​类型​的​数据​组合​在一起,​而不​必将​二​进制​数据​分成​线圈​和​离散​量​输入​范围,​因此​该​功能​非常​有益。​通常​使用​小数点​和​位​索引​或​编号​进行​索引,​具体​取决​于​如何​实现。​也就是说,​第​一个​寄存器​的​第​一位​可能是​400,001.00​或​400,001.01。​建议​所有​文​档​均​说明​所​使用​的​索引​方案。

数据​字​节序

通过​将​数据​拆​分​到​两​个​寄存器,​多​寄存器​数据​(如​单​精度​浮点​值)​可以​轻松​地​通过​Modbus​进行​传输。​由于​这​不是​由​标准​定义​的,​因此​此类​拆​分​的​字​节序​未​作​规定。​尽管​每​个​无​符号​双​字​节​整型​必须​以​网络​(大​端)​字​节序​发送​才能​满足​标准,​但​许多​设备​会​颠倒​多​字​节​数据​的​字​节序。​图​2​所​示​的​范​例​虽然​不太​常见,​但​有效​地​展示​了​这​一​观点。

多字数据的字节序交换

图​2.多​字​数据​的​字​节序​交换

主​设备​需要​了解​从​设备​如何​将​信息​存储​在​内存​中,​然后​才能​对​其​进行​正确​解码。​建议​文​档​写​明​系统​所​使用​的​字​序。​如果​在​实现​过程​中​需要​更加​灵活,​也可以​将​字​节序​添加​为​系统​配置​选项,​该​选项​可​提供​基础​的​编码​和​解码​功能。

字符串

字符​串​可以​轻松​地​存储​在​Modbus​寄存器​中。​为了​简单​起​见,​某些​实现​方法​要求​字符​串​长度​为​2​的​倍数,​并​使用​空​值​来​填充​额外​的​空间。​字​节序​也是​字符​串​交互​中的​一个​变量。​字符​串​格式​可能​包含​也​可能​不​包含​NULL(作为​最终​值)。​举​个​例子,​一些​设备​的​数据​存储​方法​可能​如​图​3​所​示。

Modbus字符串中的字节序反转

图​3.Modbus​字符​串​中的​字​节序​反转

了解​功能码

数据​模型​可能​因​设备​而​异,​但​功能​码​则​与​之​不同,​功能​码​及其​数据​由​标准​明确​定义。​每​个​功能​都​遵循​一种​模式。​首先,​从​设备​会​验证​功能​码、​数据​地址​和​数据​范围​等​输入。​然后,​执行​所​请求​的​操作​并​发送​与​代码​相符​的​响应。​如果​此​过程​中的​任何​步骤​失败,​则会​向​请求​程序​返回​异常。​这些​请求​的​数据​传输​就​称为​PDU。

Modbus PDU

PDU​由​一个​单字​节​的​功能​码​组成,​后​跟​多​达​252​字​节​的​功能​特定​数据。

Modbus PDU

图​4.Modbus PDU

功能​码​是​第​一个​需要​验证​的​项。​如果​用于​接收​请求​的​设备​未​识别​功能​码,​则会​返回​异常。​如果​功能​码​被​接受,​则​从​设备​会​根据​功能​定义​开始​分解​数据。

 

由于​数据​包​大小​限制​为​253​字​节,​因此​设备​可​传输​的​数据​量​有限。​最​常见​的​功能​码​可以​在​从​数据​模型​中​传输​240​到​250​字​节​的​实际​数据,​具体​取决​于​代码。

从​设备​功能​执行

正如​由​数据​模型​所​定义,​不同​的​功能​会​访问​不同​的​概念​数据​块。​一种​常见​的​做法​是​让​代码​访问​静态​内存​位置,​但​其他​行为​仍然​可用。​例如,​功能​码​1(读​取​线圈)​和​3(读​取​保持​寄存器)​可以​访问​内存​中​相同​的​物理​位置。​而​功能​码​3(读​取​保持​寄存器)​和​16(写​入​保持​寄存器)​可以​访问​内存​中​完全​不同​的​位置。​因此,​建议​在​定义​从​数据​模型​时​考虑​每​个​功能​码​的​执行​情况。

无论​执行​的是​何​种​实际​行为,​所有​从​设备​都​应​遵循​每​个​请求​的​简单​状态​图。​图​5​是​功能​码​1(读​取​线圈)​的​状态​图​范​例。

读取线圈状态图

图​5.Modbus​协议​规范定义​的​读​取​线圈​状态图

每​个​从​设备​必须​验证​功能​码、​输入​数量、​起始​地址、​总​范围​以及​实际​进行​读​取​行为​的​从属​定义​功能​的​执行。

尽管​上面​的​状态​图​包含​了​静态​地址​范围,​但​真实​系统​的​需求​可能​会​导致​静态​地址​范围​与​所​定义​编号​有所不同。​在​某些​情况​下,​从​设备​无法​传输​协议​所​定义​的​最大​字​节​数。​也就是说,​如果​主​设备​请求​0x07D0​输入,​从​设备​只能​用​0x0400​进行​响应。​同样,​从​数据​模型​能够​将​可​接受​线圈​值​的​范围​定义​为​地址​0​到​500。​如果​主​设备​从​地址​0​开始​请求​125,​则​没有​问题,​但​如果​主​设备​从​地址​400​开始​发出​相同​的​请求,​最后​一个​线圈​将​位于​地址​525,​这​无疑​超出​了​该​设备​的​范围,​因而​会​出现​状态​图​所​定义​的​异常​02。

标准​功能码

规范​中​列出​了​每​个​标准​功能​码​的​定义。​即使​对于​最​常见​的​功能​码,​主​设备​上​启用​的​功能​与​从​设备​可​处理​的​功能​之间​也​会​出现​不​匹配​的​情况,​这种​情况​无法​避免。​为了​解决​这个​问题,​Modbus TCP​规范​的​早期​版本​定义​了三​个​一致性​类。​官方的Modbus​一致性​测试​规范虽​未​引用​这些​类,​而是​在​每​个​功能​的​基础​上​定义​一致性,​但​这些​内容​仍然​便于​理解。​建议​任何​文​档​都​遵循​测试​规范,​并​根据​所​支持​的​代码​(而不是​传统​分类)​来​定义​其​一致性。

0​类​代码

0​类​代码​通常​被​认为​是​有效​Modbus​设备​的​最低​配置,​因为​此类​代码​可​使​主​设备​能够​读​取​或​写​入​数据​模型。

代码 说明
3 读​取​多​寄存器
16 写​入​多​寄存器

表​4.0​类​代码​一致性​规范

1​类​代码

1​类​功能​码​由​访问​所有​类型​的​数据​模型​所需​的​其他​代码​组成。​在​原始​定义​中,​此​列表​包含​功能​码​7(读​取​异常)。​但是,​当前​规范​规定​此​代码​为​仅​限于​串​行​的​代码。

代码 说明
1 读​取​线圈
2 读​取​离散​量​输入
4 读​取​输入​寄存器
5 写​入​单​个​线圈
6 写​入​单​个​寄存器
7 读​取​异常​状态​(仅​限​串​行)

表​5.1​类​代码​一致性​规范

2​类​代码

2​类​功能​码​表示​不太​常用​但​更​为​专业​化​的​功能。​例如,​读​取/​写​入​多个​寄存器​可能有助于减少​请求-​响应​周期​的​总数,​但​该​行为​仍​可用​0​类​代码​实现。

 代码 说明
15 写​入​多个​线圈
20 读​取​文件​记录
21 写​入​文件​记录
22 屏蔽​写​入​寄存器
23 读​取/​写​入​多个​寄存器
24 读​取​FIFO

表​6.2​类​代码​一致性​规范

 
Modbus​封​装​接口

Modbus​封​装​接口​(MEI)​代码​(即​功能​43)​用于​封​装​Modbus​数据​包​内的​其他​数据。​目前,​提供​了​两​个​MEI​编号,​即​13 (CANopen)​和​14(设备​识别)。

功能​43/14(设备​识别)​非常​有用,​因为​它​允许​传输​多​达​256​个​唯一​的​对象。​其中​一些​对象​已​预​定义​并​预​留,​例如​供应​商​名称​和​产品​代码,​但​应用​程序​可以​将​其他​对象​定义​为​作为​通用​数据​集​传输。

此类​代码​并不​常用。

异常

从​设备​使用​异常​来​指示​各种​不良​状况,​比如​错误​请求​或​不​正确​输入。​但是,​异常​也可以​作为​对​无效​请求​的​应用​程序​级​响应。​从​设备​不会​响应​发出​异常​的​请求,​而是​忽略​不​完整​或​损坏​的​请求,​并​开始​等待​新的​消息​传​入。

异常​以​定义​好的​数据​包​格式​报告​给​用户。​首先,​将​功能​码​返回​给​等​同​于​原始​功能​码​的​请求​主​设备,​设置​最高​有效​位​的​情况​除外。​这​等​同​于​为​原始​功能​码​的​值​加上​0x80。​异常​响应​包括​一个​异常​码,​用于​代替​与​给​定​功能​响应​相关​的​正常​数据。

根据​标准,​四​个​最​常见​的​异常​码​是​01、​02、​03​和​04。​表​7​中​显示​了​这些​代码,​并​附有​每​个​功能​的​标准​含义。

异常码 含义
01 不​支持​接收​的​功能​码。​要​确认​原始​功能​码,​请​从​返回​值​中​减去​0x80。
02 请求​尝试​访问​的​地址​无效。​根据​标准,​只有​在​起始​地址​和​所​请求​值​的​编号​超过216时​才​会​发生​这种​情况。​但是,​有些​设备​可能​会​限制​其​数据​模型​中的​地址​空间。
03 请求​包含​不​正确​的​数据。​在​某些​情况​下,​这​意味​着​参数​不​匹配,​例如​所​发送​寄存器​的​数量​与“字​节​总数”字​段​之间​的​参数​不​匹配。​更​常见​的​情况​是,​主​设备​请求​的​数据​高于​从​设备​或​协议​所​允许​的​上限。​例如,​主​设备​一次​只能​读​取​125​个​保持​寄存器,​而​资源​受限​的​设备​可能​会​将​此​值​限制​为​更少​的​寄存器。
04 尝试​处理​请求​时​发生​不可​恢复​的​错误。​这​是​一个​常见​异常​码,​表示​请求​有效,​但从​设备​无法​执行​该​请求。

表​7.常见​的​Modbus​异常码

 

每​个​功能​码​的​状态​图​至少​应​包含​异常​码​01,​通常​包含​异常​码​04、​02、​03,​并且​任何​其他​定义​的​异常​码​都是​可​选​的。

应用​数据​单元

除了​Modbus​协议​的​PDU​核心​所​定义​的​功能​外,​您​还​可以​使用​多种​网络​协议。​最​常见​的​协议​是​串​行​和​TCP/​IP,​但​也可以​使用​UDP​等​其他​协议。​为了​在​这些​层​之间​传输​Modbus​所需​的​数据,​Modbus​包含​一​组​专​为​每​种​网络​协议​量​身​定制​的​ADU。

通用​特征

Modbus​需要​特定​的​功能​来​提供​可靠​的​通信。​每​种​ADU​格式​都​需要​使用​单元​ID​或​地址,​以便​为​应用​层​提供​路​由​信息。​每​个​ADU​都​带有​一个​完整​的​PDU,​其中​包含​给​定​请求​的​功能​码​和​相关​数据。​为了​保证​可靠性,​每​条​消息​都​包含​错误​检查​信息。​最后,​所有​的​ADU​都​提供​了​一种​机制​来​确定​请求​帧​的​开始​和​结束,​但​实现​方式​各不相同。

标准​格式

ADU​的​三​种​标准​格式​分别​是​TCP、​远程​终端​单元​(RTU)​和​ASCII。​RTU​和​ASCII ADU​通常​用于​串​行​线路,​而​TCP​则​用于​现代​TCP/​IP​或​UDP/​IP​网络。

TCP/​IP

TCP ADU​由​Modbus​应用​协议​(MBAP)​报​文​头​和​Modbus PDU​组成。​MBAP​是​一个​通用​的​报​文​头,​依赖​于​可靠​的​网络​层。​此​ADU​的​格式​(包括​报​文​头)​如​图​6​所​示。

图​6.TCP/​IP ADU

报​文​头​的​数据​字​段​代表​其​用途。​首先,​它​包含​一个​事务​处理​标识​符。​这​有助​于​网络​允许​同时​发生​多个​未​处理​的​请求。​也就是说,​主​设备​可以​发送​请求​1、​2​和​3。​在​稍​后​的​时间​点,​从​设备​能​以​2、​1、​3​的​顺序​进行​响应,​并且​主​设备​可以​将​请求​匹配​到​响应​并​准确​解析​数据。​这​对于​以太​网​网络​来说​很有​用。

协议​标识​符​通常​为​零,​但​您​可以​使用​它​来​扩展​协议​的​行为。​协议​使用​长度​字​段​来​描述​数据​包​其余​部分​的​长度。​此​元素​的​位置​也​表明​了​这个​报​文​头​格式​在​可靠​网络​层​上​的​依赖​关系。​由于​TCP​数据​包​具有​内​置​的​错误​检查​功能,​并​可​确保​数据​一致性​和​传递,​因此​数据​包​长度​可​位于​报​文​头​的​任何​位置。​在​可靠性​较​差​的​网络​上​(比如​串​行​网络),​数据​包​可能​会​丢失,​其​影响​是​即使​应用​程序​读​取​的​数据​流​包含​有效​的​事务​处理​和​协议​信息,​长度​信息​的​损坏​也​会​使​报​文​头​无效。​TCP​为​这种​情况​提供​了​适当的​保护。

TCP/​IP​设备​通常​不​使用​单元​ID。​但是,​Modbus​是​一种​常见​的​协议,​因此​通常​会​开发​许多​网​关​来​将​Modbus​协议​转换​为​其他​协议。​在​最初​的​预期​应用​中,​Modbus TCP/​IP​转​串​行​网​关​用于​连接​新的​TCP/​IP​网络​与​旧​的​串​行​网络。​在​这种​环境​中,​单元​ID​用于​确定​PDU​实际​对应​的​从​设备​的​地址。

最后,​ADU​还​包含​一个​PDU。​对于​标准​协议,​PDU​的​长度​仍​限制​为​253​字​节。

RTU

RTU ADU​看起来​要​简单​得​多,​如​图​7​所​示。

RTU ADU

图​7.RTU ADU

与​较​为​复杂​的​TCP/​IP ADU​不同,​除了​核心​PDU​之外,​RTU ADU​仅​包含​两​条​信息。​首先,​地址​用于​定义​PDU​对应​的​从​设备。​在​大​多数​网络​中,​地址​0​定义​的是“广播”地址。​也就是说,​主​设备​可以​将​输出​命令​发送​到​地址​0,​而​所有​从​设备​都​应​处理​该​请求,​但是​不​做出​任何​响应。​除了​此​地址​外,​CRC​还​用于​确保​数据​的​完整性。

然而,​现在​的​实现​机制​远​没有​那么​简单。​数据​包​的​首尾​各有​一段​静默​时间,​即​总​线上​没有​通信​的​时​段。​对于​9,600​的​波特​率,​此​间隔​大约是​4​毫秒。​标准​定义​了​一个​最小​的​静默​时​长,​无论​波特​率​如何,​都​低于​2​毫秒。

 

首先,​这​存在​性能​缺陷,​因为​设备​必须​等待​空闲​时间​结束​后​才能​处理​数据​包。​然而,​更​危险​的是​串​行​传输​引入​了​不同​的​技术,​并且​波特​率​比​标准​更​快。​例如,​使用​USB/​串​口​转换​器​电缆,​您​无法​控制​数据​分​包​和​数据​传输。​测试​表明,​结合​NI-​VISA​驱动​程序​使用​USB​转​串​口​线​缆​时,​会​在​数据​流​中​引入​尺寸​可变​的​大​间隙,​而​这些​间隙​(静默​时​段)​会“诱​骗”符合​规范​的​代码​相信​消息​是​完整​的。​由于​消息​不​完整,​通常​会​导致​CRC​无效,​并​导致​设备​将​ADU​解释​为​损坏。

除了​传输​问题​之外,​现代​驱动​程序​技术​大幅​实现​串​行​通信​抽象​化,​并且​通常​需要​应用​程序​代码​中的​轮​询​机制。​例如,​除非​通过​轮​询​端​口上​的​字​节,.NET Framework 4.5 SerialPort Class和​NI-​VISA​驱动​程序​都不​提供​用于​检测​串​行​线路​上​的​静默​时​段​的​机制。​这​会​导致​性能​降低​(如果​轮​询​执行​过​慢)​或​CPU​使用​率​过​高​(如果​轮​询​执行​过​快)。

解决​这些​问题​的​常用​方法​是​打破​Modbus PDU​和​网络​层​之间​的​抽象​层。​也就是说,​串​行​代码​会​询问​Modbus PDU​数据​包​以​确定​功能​码。​结合​数据​包​中的​其他​数据,​可以​发现​剩余​数据​包​的​长度,​从而​确定​数据​包​的​结尾。​了解​这些​信息​后,​可以​设置​更​长​的​超​时​时间,​即使​出现​传输​间隙​也​可​应对,​并且​应用​程序​级​的​轮​询​速度​也​会​变得​更慢​一些。​建议​针对​新的​开发​使用​这种​机制。​如果​不为​代码​采用​这种​机制,​可能​会​遇到​大于​预期​数量​的“损坏”数据​包。

ASCII

如​图​8​所​示,​ASCII ADU​比​RTU​更​复杂,​但​也​避免​了​RTU​数据​包​的​许多​问题。​然而,​它​自身​也有​一些​缺点。

ASCII ADU

图​8.ASCII ADU

为了​解决​确定​数据​包​大小​的​问题,​ASCII ADU​为​每​个​数据​包​定义​了​一个​明确​且​唯一​的​开始​和​结束。​换​而言​之,​每​个​数据​包​以“:”开始,​并​以​回车​(CR)​和​换​行​符​(LF)​结束。​另外,​像​NI-​VISA​和.NET Framework SerialPort Class​这样​的​串​行​API​可以​轻松​读​取​缓冲​区​中的​数据,​直到​收到​CR/​LF​等​特定​字符​为止。​这些​特性​有助​于​在​现代​应用​程序​代码​中​有效​地​处理​串​行​线路​上​的​数据​流。

ASCII ADU​的​缺点​是​所有​数据​都​以​ASCII​编码​的​十六​进制​字符​进行​传输。​也就是说,​设备​针对​功能​码​3 (0x03)​发送​的​不是​单​个​字​节,​而是​发送​ASCII​字符“0”和“3”或​0x30/0x33。​这​使得​协议​更​具​可读​性,​但​也​意味​着​必须​通过​串​行​网络​传输​两​倍​的​数据,​并且​发送​和​接收​应用​程序​必须​能够​解析​ASCII​值。

扩展​Modbus

Modbus​是​一种​相对​简单​和​开放​的​标准,​可以​进行​修改​以​适应​给​定​应用​的​需求。​这​常用​于​HMI​和​PLC​或​PAC​之间​的​通信,​因为​在​这种​情况​下​组织​可以​控制​协议​的​首尾。​例如,​传感器​的​开发​人员​更​可能​遵守​书面​标准,​因为​他们​通常​只​控制​其​从​设备​的​实现,​互通性​也是​可能​实现​的。

一般​来说,​不​建议​修改​协议。​本​节​仅​作为​对​其他​人​用​来​调整​协议​行为​的​机制​的​确认。

新​功能码

Modbus​标准​定义​了​一些​功能​码,​但​也​允许​您​开发​更多​的​功能​码。​具体​而言,​功能​码​1​至​64、​73​至​99​以及​111​至​127​是​预​留​且​保证​唯一​的​公共​代码。​其余​代码​(即​65​至​72​和​100​至​110)​可​由​用户​自​定义。​使用​这些​用户​定义​的​代码​时,​您​可以​使用​任何​数据​结构。​数据​甚至​可能​超过​Modbus PDU​的​标准​253​字​节​限制,​但​应​验证​整个​应用​程序​以​确保​其他​层​在​PDU​超过​标准​限制​时​按​预期​工作。​高于​127​的​功能​码​预​留​为​异常​响应。

网络层

除了​串​行​和​TCP​之外,​Modbus​还​可以​在​许多​网络​层​上​运行。​一种可能的​实现​是​UDP,​因为​UDP​适合​于​Modbus​通信​风格。​Modbus​本质​上​是​基于​消息​的​协议,​因此​UDP​能够​发送​明确​定义​的​信息​包,​而​不需要​任何​额外​的​应用​程序​级​信息​(如​起始​字符​或​长度),​这​使得​Modbus​非常​易​于​实现。​Modbus PDU​数据​包​可以​使用​标准​的​UDP API​发送,​并​由​另一​端​完全​接收,​而​不需要​额外​的​ADU​或​重新​使用​现有​的​ADU。​虽然​TCP​内​置​确认​系统​且​对​某些​协议​有利,​但​Modbus​是在​应用​层​执行​确认。​因此,​以​这种​方式​使用​UDP​会​消除​TCP ADU​中的​事务​处理​标识​符​字​段,​从而​消除​了​存在​多个​同时​发生​的​未完成​事务​的​可能性。​因而,​主​设备​必须​是​同步​主​设备,​或者​UDP​数据​包​必须​有​一个​标识​符,​用于​帮助​主​设备​组织​请求​和​响应。​建议​的​做法​是在​UDP​网络​层​上​使用​TCP/​IP ADU。

ADU​修改

最后,​应用​程序​可以​选择​修改​ADU,​或​使用​现有​ADU​的​未​使用​部分​(如​TCP)。​例如,​TCP​定义​了​一个​16​位​长度​字​段、​一个​16​位​协议​和​一个​8​位​单元​ID。​鉴于​最大​的​Modbus PDU​是​253​字​节,​长度​字​段​的​高​字​节​始终​为​零。​对于​Modbus/​TCP,​协议​字​段​和​单元​ID​始终​为​零。​有​一种​简单​的​协议​扩展​方式​是,​通过​将​协议​字​段​更改​为非​零​数字​并​使用​两​个​未​使用​的​字​节​(单元​ID​和​长度​字​段​的​高​字​节),​发送​两​个​附加​PDU​的​长度,​从而​同时​发送​三​个​数据​包​(见​图​9)。

TCP ADU修改范例

图​9.TCP ADU​修改​范例

更多​资源

Modbus​应用​程序​开发

Modbus​应用​协议​规范

为什么​OPC UA​非常​重要

Java Modbus库