文章目录
基础认识
- Modbus协议包括ASCII、RTU(Remote Terminal Unit)、TCP/IP等,此协议定义了控制器能够认识和使用的消息结构,而不管它们是经过何种网络进行通信的。
- 可以理解ModBus是工作在应用层的,对应到OSI七层模型,其实也只是用到了应用层和物理层,并没有规定物理层,所以对于物理层既可以使用RS232又可以使用RS485实现。
- Modbus的ASCII、RTU协议规定了消息、数据的结构、命令和就答的方式,数据通讯采用Maser/Slave方式,Master端发出数据请求消息,Slave端接收到正确消息后就可以发送数据到Master端以响应请求;Master端也可以直接发消息修改Slave端的数据,实现双向读写。
功能码对照表
功能码 | 英文名 | 中文名 |
---|---|---|
01 | Read Coils | 读线圈 |
02 | Read Discrete Inputs | 读离散寄存器 |
03 | Read Holding Registers | 读保持寄存器 |
04 | Read Input Registers | 读取输入寄存器 |
05 | Write Singled Coil | 写线圈 |
06 | Write Singled Register | 写单个寄存器 |
15 | Write Multiple Coils | 写多个线圈 |
16 | Write Multiple Registers | 写多个寄存器 |
相关名词与概念
输入寄存器与保持寄存器
- 保持寄存器的值可以读取也可以修改,而输入寄存器的值对于master来说就只能读取。
- 保持寄存器(Holding Register),指的是可以通过通信命令读写的寄存器;通常是一些功能控制寄存器或者输出寄存器等。不同的设计中,有些保持寄存器是掉电保持;有些则不。
- 输入寄存器(Input Register),指的是只能读不能写的寄存器,通常是状态寄存器或者是输入结果寄存器等。(输入是模拟量的输入,保持寄存器就是设备内部的寄存器了)
线圈寄存器与离散输入寄存器
- 这两个寄存器每一个位都表示一个开关状态,不同的是离散输入寄存器是只读的。
- 线圈寄存器(Coil):实际上就可以类比为开关量(继电器状态),每一个bit对应一个信号的开关状态。所以一个byte就可以同时控制8路的信号。比如控制外部8路io的高低。 线圈寄存器支持读写,写在功能码里面又分为写单个线圈寄存器和写多个线圈寄存器。对应功能码也就是:0x01 0x05 0x0f
- 离散输入寄存器(Discrete) :如果线圈寄存器理解了这个自然也明白了。离散输入寄存器就相当于线圈寄存器的只读模式,他也是每个bit表示一个开关量,而他的开关量只能读取输入的开关信号,是不能够写的。比如我读取外部按键的按下还是松开。所以功能码也简单就一个读的 0x02
如下为Modbus的概述,粗略的读过去便可, 跳至最后两部分内容实践即可
使用Modbus poll&Modbus slave模拟Modbus主从通信
Modbus帧分析,虚拟串口使用Modbus-RTU通信
Modbus定义
-
Modbus是使用主从关系实现的请求-响应协议。
-
Modbus协议引入了不同的应用程序数据单元来更改串行通信使用的数据包格式,或允许使用TCP/IP和用户数据报协议(UDP)网络。 其通信协议理解也比较简单,其帧结构如下:
协议数据单元PDU(功能码和数据)
- PDU(Packet Data Unit)及其处理代码构成了Modbus应用协议规范的核心。 该规范定义了PDU的格式、协议使用的各种数据概念、如何使用功能代码访问数据,以及每个功能代码的具体实现和限制。
- Modbus PDU格式被定义为一个功能代码,后面跟着一组关联的数据。 该数据的大小和内容由功能代码定义,整个PDU(功能代码和数据)的大小不能超过253个字节。 每个功能代码都有一个特定的行为,从设备可以根据所需的应用程序行为灵活地实现这些行为。
- 与数据模型可能因设备而异不同,功能代码及其数据由标准明确定义。 每个功能都遵循一种模式。 首先,从设备会验证功能代码、数据地址和数据范围等输入。 然后执行所请求的操作并发送与代码相符的响应。 如果此过程中的任何步骤失败,则会向请求程序返回异常。 这些请求的数据传输就称为PDU。
Modbus PDU结构
- PDU由一个单字节的功能代码组成,后面跟着多达252字节的针对特定函数的数据。
- 功能代码是第一个需要验证的项。 如果功能代码没有被接收到请求的设备识别,则会回应一个异常。 如果功能代码被接受,则从设备根据功能定义开始分解数据。
- 由于数据包大小限制为253字节,设备可传输的数据量有限。 最常见的功能代码可以240到250字节的从设备数据模型数据,具体取决于代码。
访问Modbus和Modbus数据模型中的数据
通常,Modbus可访问的数据存储在四个数据库或地址范围的其中一个: 线圈状态、离散量输入、保持寄存器和输入寄存器。 与许多规范一样,名称可能因行业或应用而异。 例如,保持寄存器也可以称为输出寄存器,线圈状态可能称为数字或离散量输出。 这些数据库定义了所包含数据的类型和访问权限。 从设备可以直接访问这些数据,因为这些数据由设备本地托管。 Modbus可访问的数据通常是设备主存的一个子集。 相反,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。
在这种寻址方案中,每种数据类型都被分配了一个前缀:
线圈状态存在前缀为0的情况。这意味着4001的引用可能指的是保持寄存器1或线圈4001。因此,建议所有新寻址方案都采用带前导零的6位寻址,并在文档中进行标注。 因此,保持寄存器1的地址为400,001,而线圈4001的地址则为004,001。
数据地址起始值
内存地址和参考数字之间的差异会由给定应用程序选择的索引进一步复杂化。 如前所述,保存寄存器1位于地址零。 通常,参考号码是1索引,这意味着给定范围的起始值为1。 因此,400,001就表示为地址0的保持寄存器00001。一些做法选择以零开始其范围,这意味着400,000转换为地址零的保持寄存器。
如下为寄存器索引方案,1索引范围应用较为广泛,强烈建议采用。
大数据类型
Modbus标准提供了一个相对简单的数据模型,它不包含无符号字和位值之外的其他数据类型。 如果系统的位值对应于螺线管和继电器,并且字值对应于未缩放的ADC值,这是足够的,但对于更高级的系统则可能不足。 因此,许多Modbus实现都包含跨寄存器边界的数据类型。 NI LabVIEW数据记录和监控(DSC)模块和KEPServerEX都定义了许多参考类型。 例如,存储在保持寄存器中的字符串遵循标准格式(400,001),但后跟一个十进制数、长度和字符串的字节顺序(400,001.2H是指保持寄存器1中的两个字符串,其中高位字节对应到字符串的第一个字符)。 这是必需的,因为每个请求的大小都是有限的,所以Modbus主机必须知道字符串的确切范围,而不是像NULL那样搜索长度或分隔符。
位访问
除了允许访问跨寄存器边界的数据之外,一些Modbus主设备还支持对寄存器中各个位的引用。 这是有好处的,因为它允许设备将相同内存范围内的每种类型的数据组合在一起,而不必将二进制数据分成线圈整体和离散量输入范围。 这通常使用小数点和位索引或数字进行索引,具体取决于如何实现。 也就是说,第一个寄存器的第一位可能是400,001.00或400,001.01。 建议任何文档都要说明所使用的索引方案。
数据字节顺序
多寄存器数据(单精度浮点值),可以通过将数据拆分到两个寄存器,轻松地在Modbus中传输。 由于这不是由标准定义的,因此分割的字节顺序没有规定。 尽管每个无符号字必须以网络(big-endian)字节顺序发送以满足标准,但许多设备会颠倒多字节数据的字节顺序。 下图所示的是一个不常见但有效的例子。
字符串
字符串可以很容易地存储在Modbus寄存器中。 为了简单起见,一些方法要求字符串长度为2的倍数,并使用控制来填充额外的空间。 字节顺序也是字符串交互中的一个变量。 字符串格式可能包含也可能不包含NULL作为最终值。 举个例子,一些设备的数据存储方法可能如图3所示。
应用数据单元(ADU(ADU包含PDU))
除了Modbus协议的PDU核心定义的功能外,您还可以使用多种网络协议。 最常见的协议是串行和TCP/IP,但也可以使用其他协议,如UDP。为了在这些层之间传输Modbus所需的数据,Modbus包含一组适用于每种网络协议的ADU。
通用特征:Modbus需要某些功能来提供可靠的通信。 单元ID或地址用在每个ADU格式中,为应用层提供路由信息。 每个ADU都带有一个完整的PDU,其中包含给定请求的功能代码和相关数据。 为了可靠性,每条消息都包含错误检查信息。 最后,所有的ADU都提供了一种机制来确定请求帧的开始和结束,但实现这些机制的方式各不相同。
标准格式:ADU的三种标准格式是TCP、远程终端单元(RTU)和ASCII。 RTU和ASCII ADU通常用于串行线路,而TCP则用于现代TCP/IP或UDP/IP网络。
TCP/IP&ADU
TCP/IP TCP ADU由Modbus应用协议(MBAP)报文头和Modbus PDU组成。 MBAP是一个通用的报文头,依赖于可靠的网络层。 此ADU的格式(包括报文头)如图所示。
报文头的数据字段代表其用途。 首先,它包含一个事务处理标识符。 这有助于网络允许同时发生多个未处理的请求。 也就是说,主设备可以发送请求1、2和3。在稍后的时间点,从设备可以以2、1、3的顺序进行响应,并且主设备可以将请求匹配到响应并准确解析数据。 这对以太网网络很有用。
协议标识符通常为零,但您可以使用它来扩展协议的行为。 协议使用长度字段来描述数据包其余部分的长度。 这个元素的位置也表明了这个报文头格式在可靠的网络层上的依赖关系。 由于TCP数据包具有内置的错误检查功能,可确保数据一致性和传送,因此数据包长度可位于报文头的任何位置。 在可靠性较差的网络上(比如串行网络),数据包可能会丢失,其影响是即使应用程序读取的数据流包含有效的事务处理和协议信息,长度信息的损坏也会使报文头无效。 TCP为这种情况提供了适当的保护。
TCP/IP设备通常不适用单元ID。 但是,Modbus是一种常见的协议,因此通常会开发一些网关来将Modbus协议转换为另一种协议。 在最初的预期应用中, Modbus TCP/IP转串行网关用于连接新的TCP/IP网络和旧的串行网络。 这时,单元ID用于确定PDU对应的从设备的地址。
TCP/IP设备通常不适用单元ID。 但是,Modbus是一种常见的协议,因此通常会开发一些网关来将Modbus协议转换为另一种协议。 在最初的预期应用中, Modbus TCP/IP转串行网关用于连接新的TCP/IP网络和旧的串行网络。 这时,单元ID用于确定PDU对应的从设备的地址。
最后,ADU包含一个PDU。 对于标准协议,PDU的长度仍限制为253字节。
RTU&ADU
RTU ADU看起来要简单得多,如图所示。
与较为复杂的TCP/IP ADU不同的是,除了核心PDU之外,该ADU仅包含两条信息。 首先,地址用于定义PDU对应的从设备。 在大多数网络中,地址0定义的是“广播”地址。 也就是说,主设备可以发送输出命令到地址0,而所有从设备应处理该请求,但是不做出任何响应。 除了这个地址外,CRC还用于确保数据的完整性。
然而,现在的实现机制远没有那么简单。 数据包的首尾一对沉默时间(silent time),即总线上没有通信的时段。对于9,600的波特率,这个速率大约是4ms。该标准定义了一个最小沉默长度,不论波特率如何,都低于2 ms。
这存在性能缺陷,因为在处理数据包之前设备必须等待空闲时间结束。 然而,更危险的是串行传输引入了不同技术,并且波特率比标准更快。 例如,使用USB/串口转换器电缆,您无法控制数据的数据包和数据传输。 测试表明,结合NI-VISA驱动程序使用USB转串口电缆会在数据流中引入了尺寸可变的大间隙,而这些间隙 – 沉默期 – 会“诱骗”符合规范的代码相信消息是完整的。 由于消息不完整,通常会导致CRC无效,并导致设备将ADU解释为损坏。
除了传输问题之外,现代驱动程序技术还大量提取串行通信,并且通常需要应用程序代码中的轮询机制。 例如,除非通过轮询端口上的字节,.NET Framework 4.5 SerialPort Class和NI-VISA驱动程序都不提供检测串行线路上的沉默的机制。 这会导致性能降低(如果轮询执行过慢)或CPU使用率过高(如果轮询执行过快)。
解决这些问题的常用方法是打破Modbus PDU和网络层之间的抽象层。 也就是说,串行代码询问Modbus PDU数据包以确定功能代码。 结合数据包中的其他数据,可以发现剩余数据包的长度,从而确定数据包的结尾。 利用这些信息,可以使用更长的超时时间,以允许传输间隙,并且应用程序级的轮询速度可能更慢。 这种机制推荐用于新的开发。 不采用此方法可能会遇到大于预期数量的“损坏”数据包。
ASCII&ADU
ASCII ADU比RTU更复杂,但也避免了RTU数据包的许多问题。 然而,它自身也有一些缺点。
为了解决确定数据包大小的问题,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 poll&Modbus slave模拟Modbus主从通信
下载和破解搜一下都很方便,使用的时候Modbus Poll作为Master,Modbus slave作为从机,设置相同的Setup和Connection就可以进行通信了。
详细教程见博客园的一篇不错的的文章。
Modbus帧分析,虚拟串口使用Modbus-RTU通信
由于篇幅比较长,另起文章https://blog.csdn.net/qq_42069216/article/details/118519822