Modbus是一种串行通信协议,最初由Modicon公司(现为施耐德电气的一部分)在1979年为使用其PLC(可编程逻辑控制器)而开发。Modbus已成为工业领域内广泛使用的一种通信协议,特别是对于监控和控制系统。Modbus协议支持多种通信方式,包括RTU(Remote Terminal Unit,远程终端单元模式)、TCP/IP和ASCII(美国标准信息交换码)等。
本文主要介绍Modbus RTU、Modbus TCP和Modbus ASCII的报文结构。
1.Modbus RTU
1.1简介
Modbus RTU(Remote Terminal Unit)是一种在串行通讯中广泛使用的协议,主要应用于工业领域的设备之间。这个协议是基于主/从(或客户端/服务器)架构,允许主机(通常称为Master)与多个从机(通常称为Slave)进行通信。在Modbus RTU协议中,数据通过二进制形式传输,使得通信更加高效。
1.2报文格式
一个典型的Modbus RTU报文结构如下:
用途 | 设备地址 | 功能码 | 数据 | CRC校验 |
---|---|---|---|---|
长度 | 8bit | 8bit | 可变(0到252个8bit) | 16bit |
描述 | 每个从机都有一个唯一的地址。地址范围从0到247。地址0是广播地址,向所有从机发送消息,但从机不会对广播信息进行应答。 | 用于指定主机要求从机执行的操作类型 | 数据部分的长度可变,包含了命令的具体参数,确切格式和长度取决于功能码。 | 用于检查数据在传输过程中是否有错误。 |
1.3报文详解
1.3.1设备地址
1.3.1.1地址范围
- 在Modbus RTU中,每个从机被分配一个唯一的地址,用于在网络上标识。
- 这个地址是一个8位的数值,范围从0到247。
- 通常情况下,地址0是保留的,用于广播命令,即发送给网络上所有设备的命令。
- 地址248到255通常是保留给特殊功能或未来使用的。
1.3.1.2地址配置
- 设备地址通常需要在设备接入Modbus网络之前就进行配置。
- 不同的设备制造商可能会提供不同的方法来设置这些地址,例如通过拨码开关、软件界面或者直接通过Modbus命令。
1.3.1.3通信过程
- 当主机向从机发送命令时,主机会在消息的开始部分包含从机的地址。从机收到消息后,会先检查消息地址,如果消息是发给自己的,则会执行,否则会抛弃。
- 对广播地址(0),所有设备都会执行该命令。
1.3.1.4地址冲突
- 如果两个或更多的设备被设置成相同的地址,会导致地址冲突,进而导致通信失败。
1.3.2 功能码
在 Modbus 标准协议中,功能码总共分为三类:公用功能码、自定义功能码、保留功能码。
- 公用功能码即经过Modbus协会确认,并提供了公开文档的功能码。在文档中被明确定义,确保唯一。
- 自定义功能码为各厂家(用户)自定义的功能码,不保证唯一性。
- 保留功能码是在报文格式不给范的时候使用的一些功能码,现在已经不作为公共使用了。
(公用功能码和自定义功能码的区别可以近似对比计算机的熟知端口和注册端口)
1.3.2.1 读取功能码
- 01 (0x01): 读线圈状态(Read Coils)- 用于读取一组逻辑线圈的当前状态(ON/OFF)。
- 02 (0x02): 读离散输入状态(Read Discrete Inputs)- 用于读取一组离散输入的状态(ON/OFF)。
- 03 (0x03): 读保持寄存器(Read Holding Registers)- 用于读取一组保持寄存器中的二进制内容。
- 04 (0x04): 读输入寄存器(Read Input Registers)- 用于读取一组输入寄存器中的二进制内容。
1.3.2.2 写入功能码
- 05 (0x05): 写单个线圈(Write Single Coil)- 用于写入单个逻辑线圈的状态(ON/OFF)。
- 06 (0x06): 写单个寄存器(Write Single Register)- 用于写入单个保持寄存器的数据。
- 15 (0x0F): 写多个线圈(Write Multiple Coils)- 用于写入一组逻辑线圈的状态。
- 16 (0x10): 写多个寄存器(Write Multiple Registers)- 用于写入一组保持寄存器的数据。
1.3.2.3 诊断功能码
- 08 (0x08): 诊断(Diagnostic)- 这组功能码用于诊断通信链路的状态,以及测试和诊断Modbus设备。
1.3.2.4 特殊功能码
- 17 (0x11): 报告从机ID(Report Slave ID)- 返回关于设备的信息,如运行状态和识别信息。
- 22 (0x16): 屏蔽写寄存器(Mask Write Register)- 允许用户修改保持寄存器的内容,而不改变未指定位的内容。
- 23 (0x17): 读/写多个寄存器(Read/Write Multiple Registers)- 同时进行读取和写入操作。
1.3.2.5 异常码
- 异常功能码:异常功能码为正常功能码 + 0x80,如写入单个寄存器错误返回异常码为0x86。
1.3.3 数据
数据部分主要包含了命令的具体内容,它的结构和长度依赖于功能码的不同。本文仅列举几个常见的功能码,以及与之对应的数据部分的结构和作用:
1.3.3.1 读取保持寄存器 (功能码03)
主机发送数据部分:
主机发送功能码03的数据,表示要读取从0x0032开始3个寄存器的内容。
用途 | 寄存器起始地址-高 | 寄存器起始地址-低 | 寄存器数量-高 | 寄存器数量-低 |
---|---|---|---|---|
长度 | 8bit | 8bit | 8bit | 8bit |
举例 | 0x00 | 0x32 | 0x00 | 0x03 |
从机回复的数据部分: | ||||
从机发送功能码03的数据,表示读取到的数据总共6个字节,分别为0x00、0x01、0x00、0x02、0x00、0x03,即读取到的3个寄存器的数据为0x0001、0x0002、0x0003。 |
用途 | 返回字节数 | 寄存器1数据-高 | 寄存器1数据-低 | 寄存器2数据-高 | 寄存器2数据-低 | 寄存器3数据-高 | 寄存器3数据-低 |
---|---|---|---|---|---|---|---|
长度 | 8bit | 8bit | 8bit | 8bit | 8bit | 8bit | 8bit |
举例 | 0x06 | 0x00 | 0x01 | 0x00 | 0x02 | 0x00 | 0x03 |
1.3.3.2 写单个寄存器 (功能码06)
功能码06主机发送的数据和从机回复的数据相同。
如主机想给0x0032的寄存器中写入0x0001,则数据为:
用途 | 寄存器地址-高 | 寄存器地址-低 | 写入数据-高 | 写入数据-低 |
---|---|---|---|---|
长度 | 8bit | 8bit | 8bit | 8bit |
举例 | 0x00 | 0x32 | 0x00 | 0x01 |
1.3.3.3 写多个寄存器 (功能码16)
主机发送数据部分:从0x0032寄存器开始,连续3个寄存器,写入数据分别为0x0001、0x0002、0x0003。
用途 | 寄存器起始地址-高 | 寄存器起始地址-低 | 寄存器数量-高 | 寄存器数量-低 | 寄存器1数据-高 | 寄存器1数据-低 | 寄存器2数据-高 | 寄存器2数据-低 | 寄存器3数据-高 | 寄存器3数据-低 |
---|---|---|---|---|---|---|---|---|---|---|
长度 | 8bit | 8bit | 8bit | 8bit | 8bit | 8bit | 8bit | 8bit | 8bit | 8bit |
举例 | 0x00 | 0x32 | 0x00 | 0x03 | 0x00 | 0x01 | 0x00 | 0x02 | 0x00 | 0x03 |
从机回复数据部分:从0x0032寄存器开始,写入数据到连续3个寄存器。
用途 | 寄存器起始地址-高 | 寄存器起始地址-低 | 寄存器数量-高 | 寄存器数量-低 |
---|---|---|---|---|
长度 | 8bit | 8bit | 8bit | 8bit |
举例 | 0x00 | 0x32 | 0x00 | 0x03 |
1.3.3.4 异常数据(异常功能码)
通常Modbus的通讯中可能存在三类异常情况:
- 1.因为通讯故障之类的原因,从机没有收到主机发出的信息,则主机将按照超时进行处理。
- 2.从机收到了报文,但是报文错误(CRC校验不通过),从机将丢弃报文,主机将按照超时进行处理。
- 3.从机接收到了报文,但是报文要求的操作无法实现(如功能码不存在、寄存器范围不对等),从机将会返回包含异常码的响应报文。
用途 | 异常码 |
---|---|
长度 | 8bit |
举例 | 0x04 |
常见异常码如下: |
异常码 | 名称 | 产生原因 |
---|---|---|
01 (0x01) | 非法功能码 | 从机不支持功能码。 |
02 (0x02) | 非法数据地址 | 从机中无对应寄存器。 |
03 (0x03) | 非法数据值 | 数据超出可用范围或数据不可用 |
04 (0x04) | 从机故障 | 从机出现未知错误 |
05 (0x05) | 确认 | 从机已接受命令,正在处理,用于避免发生超时错误 |
06 (0x06) | 从机忙 | 从机设备正在处理长时间命令 |
07 (0x07) | 否定确认 | 从机无法执行主机命令 |
08 (0x08) | 存储奇偶性差错 | 扩展文件区不能通过一致性校验 |
1.3.4 CRC校验
Modbus RTU协议中常用的CRC校验采用的是CRC-16算法,具体的多项式为0x8005
(或其二进制形式1000 0000 0000 0101
),初始值为0xFFFF
。
CRC校验的基本步骤如下:
- 预置:CRC寄存器预置为
0xFFFF
。 - 数据输入:报文中除了CRC校验码以外的所有字节(包括设备地址、功能码和数据)按照顺序进行处理。
- 计算:对每一个字节,从最高位到最低位,将其与CRC寄存器当前的值进行异或运算。如果结果的最高位为1,则将寄存器的值左移一位并与
0x8005
进行异或运算;如果最高位为0,则只需左移一位。重复此过程,直至8位都处理完毕。然后继续处理下一个字节,直到所有字节都计算完毕。 - 结果:最后CRC寄存器中的值就是CRC校验码,通常在传输前转换为低字节在前(Little-Endian)的形式,并附加到报文的末尾。
当接收方收到报文时,会对整个报文(包括CRC校验码)使用相同的CRC计算流程。如果报文未被篡改,计算结果应为0x0000
(考虑到了CRC码的加入和计算规则)。如果结果不是0x0000
,则表明报文在传输过程中可能遭到了篡改或出现了错误。
2.Modbus TCP
2.1 简介
Modbus TCP 是基于Modbus RTU协议的扩展,它是一种在以太网上使用的通讯协议。Modbus TCP 报文格式相较于Modbus RTU,主要是在报文的前面增加了一个MBAP头(Modbus Application Protocol header),用于在TCP/IP网络中传输。
2.2 报文格式
用途 | 事务标识符 | 协议标识符 | 长度字段 | 单元标识符 | *功能码 | 数据 |
---|---|---|---|---|---|---|
长度 | 16bit | 16bit | 16bit | 8bit | 8 | 可变(0到252个8bit) |
描述 | 用于标识请求和响应的对应关系,客户端发起的每个请求都会分配一个唯一的事务标识符,服务器在响应时会使用相同的标识符。 | 用于识别上层协议,固定为0x0000。 | 表示接下来的单元标识符、功能码和数据的总长度,单位为字节。 | 用于在连接到Modbus网关时识别远程服务器上的从 用于指定主机要求从机从机操作类型 类型 | 数据部分的长度可变,包含了命令的具体参数,确切格式和长度取决于功能码。 |
事务标识符、协议标识符、长度字段、单元标识符四部分即为MBAP头。
2.3 报文详解
2.3.1 事务标识符
- 用于标识请求和响应的对应关系,客户端发起的每个请求都会分配一个唯一的事务标识符,服务器在响应时会使用相同的标识符。在并发请求的环境下,事务标识符尤其重要。
- 事务标识符通常由请求发起端生成,通过递增、随机等不同方式进行生成。
2.3.2 协议标识符
- 协议标识符用于识别上层协议。在标准的Modbus TCP应用中,这个值被设置为0x0000,表示使用的是Modbus协议。
- 虽然协议标识符通常被设置为0,但它的存在为Modbus TCP提供了扩展的可能性。这意味着在未来,如果需要,Modbus TCP可以支持除了Modbus之外的其他协议,而无需更改现有的架构。
2.3.3 长度字段
表示接下来的单元标识符、功能码和数据的总长度,单位为字节。如字段长度为0x0008,则后续部分长度为8字节。
2.3.4 单元标识符
- 在纯Modbus TCP网络中,单元标识符通常被设置为0或255。这是因为在这样的环境里,IP地址已经足够用来区分不同的设备,单元标识符并不起到区分设备的作用。
- Modbus TCP到RTU/ASCII网关中,一个Modbus TCP请求通过网络发送到一个网关设备,然后网关设备将这个请求转换为Modbus RTU或ASCII格式,并通过串行通信发送给指定的从机。在这种情况下,单元标识符就是用来告诉网关这个请求应该转发给哪个从机的。
2.3.5 功能码
同Modbus RTU相同
2.3.6 数据
同Modbus RTU相同
3. Modbus ASCII
3.1简介
Modbus ASCII(美国标准信息交换码)报文格式是Modbus协议的一种变体,它允许设备通过文本可读的格式进行通信。这种格式特别适用于速度不是非常关键的应用场景和那些需要通过人眼检查数据的场合。
3.2 报文格式
用途 | 起始符 | 设备地址 | 功能码 | 数据 | 校验和 | 结束符 |
---|---|---|---|---|---|---|
长度 | 1个ASCII字符 | 2个ASCII字符 | 2个ASCII字符 | 可变(0-504个ASCII字符,应为偶数个) | 2个ASCII字符 | 2个ASCII字符 |
描述 | 以冒号(“:”)字符开始,用ASCII码表示为0x3A | 每个从机都有一个唯一的地址。地址范围从0到247。地址0是广播地址,向所有从机发送消息,但从机不会对广播信息进行应答。 | 用于指定主机要求从机执行的操作类型 | 数据部分的长度可变,包含了命令的具体参数,确切格式和长度取决于功能码。 | 用于检查数据在传输过程中是否有错误。 | 每个报文以回车和换行字符(CR LF,ASCII码为0x0D和0x0A)结束 |
ASCII模式下每个字符占用10bit,每个字符格式为: |
用途 | 起始位 | 数据位 | 奇偶校验 | 停止位 |
---|---|---|---|---|
长度 | 1bit | 7bit | 1bit | 1bit |
备注 | 标记数据传输的开始 | 实际的数据内容,即显示的ASCII码 | 用于错误检测 | 标记数据传输的结束 |
3.3 报文详解
3.3.1 起始符
每条Modbus ASCII消息的开头都会有这样一个冒号,用来告诉接收设备一条新的消息正在开始。
3.3.2 设备地址
由于Modbus ASCII中的每个字节都用两个ASCII字符表示,设备地址也不例外。例如,如果设备地址是17(十进制),它将被转换为十六进制11,然后在ASCII消息中表示为两个字符“11”。
3.3.3 功能码
同Modbus RTU相同,即用ASCII字符来展示功能码的十六进制数。
3.3.4 数据
同Modbus RTU相同,即用ASCII字符来展示功能码的十六进制数。
3.3.5 校验和
Modbus ASCII使用一种简单的校验和机制,称为LRC(Longitudinal Redundancy Check,纵向冗余校验)。LRC的目的是确保数据在传输过程中的完整性和准确性。LRC校验和是通过对消息中所有字符的ASCII值进行计算得到的。
LRC计算步骤如下:
- 初始化LRC:LRC的初始值为0x00。
- 计算校验和:将消息中除了起始冒号和结束的回车换行符之外的所有字符(实际上是它们的ASCII值)两两一组(因为Modbus ASCII将每个字节分为两个ASCII字符来表示),转换为字节(即,将ASCII字符对应的十六进制数转换为字节),然后累加到LRC中。这个过程中如果累加结果超过了一个字节的表示范围(即超过了0xFF),则只保留结果的低8位。
- 取反加一:累加完成后,将LRC的值取反(即0xFF - LRC),然后加1。这样得到的最终结果就是发送消息时附加的LRC校验和。
- 发送消息时的处理:计算出的LRC校验和需要被转换为两个ASCII字符附加在消息的末尾,紧接着是结束的回车换行符。这样,接收方在接收到消息后,可以使用同样的方法计算校验和,并与接收到的校验和进行比较,以验证数据的完整性和准确性。
3.3.6 结束符
在Modbus ASCII模式下,每条消息的结束符由两个字符组成:CR (Carriage Return) 和 LF (Line Feed)。在ASCII编码中,CR的十六进制值是0x0D,LF的十六进制值是0x0A。因此,每条Modbus ASCII消息的末尾都会有这样一个字符序列:0x0D0x0A。
- CR(Carriage Return,回车):在打字机时代,这个操作会让打字头回到一行的开头。在计算机文本文件中,它的作用依赖于系统,但通常用于表示一行的结束。
- LF(Line Feed,换行):在打字机时代,这个操作会让纸张向上滚动一行。在计算机文本文件中,它通常用来表示新的一行的开始。
在Modbus ASCII协议中,组合使用CR和LF作为消息结束符,可以确保无论在哪种操作系统上,接收设备都能正确地识别出消息的结束,从而进行相应的处理。