Fork me on GitHub

Modbus协议

一、初识Modbus

Modbus 是应用层的协议,需要借助其他协议,如物理层协议UART、RS485等协议传输,或者传输层TCPIP协议传输。

协议分支有:

Modbus RTU (最根本的协议)Modbus ASCII 、Modbus TCP/IP 、Modbus over TCP/IP 、Modbus UDP

常用接口(物理层):

  • RS232通讯

    全双工,这个基本用在点对点通信场景下,不适合多点拓扑连接,共模电平编码,一般需要Rxd/Txd/Gnd三根线连接。
    三/四线
    
  • RS485通讯

    半双工,这是最为常用的modbus物理层,差分电平编码,一对双绞线,抗干扰性能也不错
    两线
    
  • RS422通讯

    全双工,这种物理层也有比较多的应用,差分电平编码,两对双绞线,抗干扰性能也不错。与RS-485相比,其优势在于可以实现全双工,通信的效率高些,所需要的代价就是现场布线需要两对双绞线,增加了一定的成本。
    四线
    
  • 以太网通讯(越来越多)

    通用、便携、兼容
    
  • 其他:串口、无线、蓝牙

二、初识寄存器

简单介绍:Modbus的寄存器存储区概念来自于PLC。线圈代表单片机的可位寻址操作,寄存器则不可位寻址。

四种寄存器:线圈状态寄存器(0 Coil Status)、离散线圈输入状态寄存器(1 Input Status)、保持寄存器(4 Holding Register)、输入寄存器(3 Input Register)。

其中数字(0、1、4、3)是代表存储区编号,注意没 2,与协议没关系,但是能由编号得出该寄存器的十进制基地址。操作不同的存储区需要用不同的功能码。

1、线圈状态寄存器

2、离散线圈输入状态寄存器

3、保持寄存器

4、输入寄存器

三、PLC的地址

寄存器 访问长度 R/W 寄存器编号 十进制地址范围 协议地址范围 可操作功能码 说明
线圈状态寄存器(Coil Status) 位bit RW 0 00001-09999 0-FFFF 01H、05H、0FH
离散线圈输入状态寄存器(Input Status) 位bit R 1 10001-19999 0-FFFF 02H
保持寄存器(Holding Register) 字word(2Byte) RW 4 40001-49999 0-FFFF 03H、06H、10H
输入寄存器(Input Register) 字word(2Byte) R 3 30001-39999 0-FFFF 04H

四、Modbus协议

一个完整的数据帧包括:地址 + 功能码 + 数据 + CRC校验

一个数据包最大256个字节。读最多个操作和写最多个操作都可能满

读操作规律:

读操作都发送 2Byte 地址 + 2Byte 数量(数量代表:线圈个数或寄存器个数)。

读操作都返回 1Byte 字节计数 + 计数数量的N个字节(N字节代表:线圈返回字节数 = 读线圈数/8 向上取整寄存器返回字节数 = 读取寄存器数*2

写操作规律:

所有的写单个数据包,返回数据包与请求数据包相同。

写多个需要

1、地址

十进制范围 十六进制范围 作用
0 0 广播地址,所有的从设备必须处理广播报文,但是不用回应
1-247 0x01 - 0xF7 从设备地址,主设备是没有地址的,这一点需要注意
248-255 0xF8 - 0xFF 预留地址

2、功能码

范围:0x00 - 0x7F (1-127)

地址范围 作用
111-127 公共功能码
100-110 用户定义功能码
73-99 公共功能码
64-72 用户定义功能码
1-64 公共功能码

请求数据包发送功能码,回应数据包发送响应码。正常回应的回应码等于功能码,异常回应的异常功能码为功能码+0x80

异常响应时,在功能码后加异常码。

常见功能码 Modbus功能码列表(全)

功能码 解释 作用
0x01 Read Coils 读线圈状态 读取远程设备中1到2000个连续的线圈的状态
0x02 Read Discrete Inputs 读离散输入状态 读取远程设备中1到2000个连续的离散输入的状态
0x03 Read Holding Registers 读保持寄存器内容 读取远程设备中1到125个连续的保持寄存器的内容
0x04 Read Input Registers 读输入寄存器内容 读取远程设备中1到125个连续的输入寄存器的内容
0x05 Write Single Coil 写单个线圈 在远程设备中把单个线圈状态改变为打开或关闭的状态
0x06 Write Single Register 写单个保持寄存器 在远程设备中写入单个保持寄存器
0x07 Read Exception Status (Serial Line only) 读取异常状态(仅限串行线路) 读取远程设备中八个异常状态输出的内容
0x08 Diagnostics (Serial Line only) 通信系统诊断(仅限串行线路)
0x0B Get Comm Event Counter (Serial Line only) 获取通讯事件计数器(仅限串行线路) 从远程设备的通信事件计数器获取状态字和事件计数
0x0C Get Comm Event Log (Serial Line only) 获取通讯事件日志(仅限串行线路) 从远程设备获取状态字、事件计数、消息计数和事件字节字段
0x0F Write Multiple Coils 写多个线圈 强制远程设备中线圈序列中的每个线圈接通或断开 1-1968 个
0x10 Write Multiple registers 写多个保持寄存器 在远程设备中写入连续寄存器块 1-123个
0x11 Report Slave ID (Serial Line only) 报导从机信息(仅限串行线路) 读取远程设备特有的类型、当前状态和其他信息的说明。数据内容特定于每种类型的设备
0x14 Read File Record 读取文件记录
0x15 Write File Record 写文件记录
0x16 Mask Write Register 带屏蔽字写入寄存器
0x17 Read/Write Multiple registers 读、写多个寄存器 执行一次连续写和连续读,写入操作在读取之前执行
0x18 Read FIFO Queue 读取先进先出队列
0x2B Encapsulated Interface Transport 封装接口传输

异常功能码

异常功能码 = 普通功能码 + 0x80

如:读保持寄存器异常,从机返回主机数据帧 Rx: 01 83 01 80 F0

3、数据

数据可能的几种格式

1、线圈状态寄存器

2、离散线圈输入状态寄存器

3、保持寄存器

4、输入寄存器

异常码

代码 HEX 名称 含义
01 非法功能 对于服务器(或从站)来说,询问中接收到的功能码是不可允许的操作。这也许是因为功能码仅仅适用于新设备而在被选单元中是不可实现的。同时,还指出服务器(或从站)在错误状态中处理这种请求,例如:因为它是未配置的,并且要求返回寄存器值。
02 非法数据地址 对于服务器(或从站)来说,询问中接收到的数据地址是不可允许的地址。特别是,参考号和传输长度的组合是无效的。对于带有 100 个寄存器的控制器来说,带有偏移量 96 和长度 4 的请求会成功,带有偏移量 96 和长度 5 的请求将产生异常码 02。
03 非法数据值 对于服务器(或从站)来说,询问中包括的值是不可允许的值。这个值指示了组合请求剩余结构中的故障,例如:隐含长度是不正确的。并不意味着,因为MODBUS 协议不知道任何特殊寄存器的任何特殊值的重要意义,寄存器中被提交存储的数据项有一个应用程序期望之外的值。
04 从站设备故障 当服务器(或从站)正在设法执行请求的操作时,产生不可重新获得的差错。
05 确认 与编程命令一起使用。服务器(或从站)已经接受请求,并且正在处理这个请求,但是需要长的持续时间进行这些操作。返回这个响应防止在客户机(或主站)中发生超时错误。客户机(或主站)可以继续发送轮询程序完成报文来确定是否完成处理。
06 从属设备忙 与编程命令一起使用。服务器(或从站)正在处理长持续时间的程序命令。当服务器(或从站)空闲时,用户(或主站)应该稍后重新传输报文。
0A 不可用网关路径 与网关一起使用,指示网关不能为处理请求分配输入端口至输出端口的内部通信路径。通常意味着网关是错误配置的或过载的。
0B 网关目标设备响应失败 与网关一起使用,指示没有从目标设备中获得响应。通常意味着设备未在网络中。

4、CRC

CRC 计算是包括字节:1Byte 地址、有效载荷(1Byte 功能码+ NByte 数据)的所有字节,即CRC前的所有数据。

1、多项式 0X8005
2、初始异或值 0xFFFF
3、数据反转:输入反转 要,输出反转 要
4、Modbus 是低地址字节先发,比如单片机计算结果是uint_16 0xCDC5,打包发送时是C5 CD
例子:01 03 00 00 00 0A C5 CD

拓展:
按照以上的计算,网页输出是 CDC5,因为网页输出的是uint_16 是大端在前,小端在后。
若是YModem协议,数据包结尾就是直接 MSB的CRC16 格式,但是MODEMBUS 是 LSB 的格式,所以是C5CD,YModem 的CRC只包括有效数据段,不包括前面1B功能码2B数据包计数。

CRC 计算代码

//modbus 可直接使用的
uint16_t calculate_crc(uint8_t *data, uint16_t len)
{
    uint16_t crc = 0xFFFF; // CRC 初始值
    for (int i = 0; i < len; i++) {
        crc ^= (uint16_t)data[i]; // 与数据异或
        for (int j = 0; j < 8; j++) {
            if (crc & 0x0001) {
                crc >>= 1;
                crc ^= 0xA001; // 生成多项式 0xA001
            } else {
                crc >>= 1;
            }
        }
    }
    return crc;
}

5、 数据包实例

最常用的8种,其中 4个读取四种寄存器 + 2个写单个寄存器 + 2个写多个寄存器。未添加异常数据包。

1、读线圈状态 01

1、主机发送 Tx: 01 01 00 00 00 06 BC 08

地址 功能码 线圈地址 数量(线圈) CRC
数据包实例 01 01 00 00 00 06 BC 08
可选范围HEX 01-F7 0-9999/65535 1 - 07D0(1-2000)
所占字节 1Byte 1Byte 2Byte 2Byte 2Byte
解释 从机地址01 读取单个线圈 从地址0x0000 读取6个线圈

2、从机回应 Rx: 01 01 01 32 D0 5D

地址 功能码 字节数 数据(字节中一位代表一个线圈) CRC
数据包实例 01 01 01 32 D0 5D
读取范围HEX 1-FA(1-250)
所占字节 1Byte 1Byte 1Byte 1-250 2Byte
解释 从机地址01 读取单个线圈 数据字节数 高地址-低地址:0011 0010

读取的六个地址是从低地址0x0000 开始读取的,读到0x0006。没读的高位补 0 凑一个字节返回。

2、读离散(输入)线圈输入状态 02

1、主机发送 Tx: 01 02 00 00 00 06 F8 08

地址 功能码 地址 数量(线圈) CRC
数据包实例 01 02 00 00 00 06 F8 08
可选范围HEX 同线圈状态
所占字节 1Byte 1Byte 2Byte 2Byte 2Byte
解释 从机地址01 读取离散(输入)线圈 从地址0x0000 读取6个线圈

2、从机回应 Rx: 01 02 01 24 A1 93

地址 功能码 字节数 数据(字节中一位代表一个线圈) CRC
数据包实例 01 02 01 24 A1 93
可选范围HEX 1-FA(1-250)
所占字节 1Byte 1Byte 1Byte 1-250(读取线圈数/8向上取整) 2Byte
解释 从机地址01 正常读取 数据字节数 高地址-低地址:0010 0100

3、读保持寄存器 03

1、主机发送 Tx: 01 03 00 00 00 06 C5 C8

地址 功能码 地址 数量(取寄存器) CRC
数据包实例 01 03 00 00 00 06 C5 08
可选范围HEX 1-007D(1-125)
所占字节 1Byte 1Byte 2Byte 2Byte 2Byte
解释 从机地址01 读保持寄存器 从地址0x0000 读取6个寄存器

2、从机回应 Rx: 01 03 0C 00 46 00 50 00 5A 00 00 00 00 00 00 DE 3F

地址 功能码 数据字节计数 数据 CRC
数据包实例 01 03 0C 00 46 00 50 00 5A 00 00 00 00 00 00 BC 08
可选范围HEX 1-FA(1-250)
所占字节 1Byte 1Byte 1Byte 1-250(?6*2 = 12Byte) 2Byte
解释 从机地址01 正常读取 后面有12个Byte的数据 十进制:70 80 90 00 00 00

4、读输出寄存器 04

1、主机发送 Tx: 01 04 00 00 00 05 30 09

地址 功能码 地址 读取数量 CRC
数据包实例 01 04 00 00 00 05 30 09
可选范围HEX 1-007D(1-125)
所占字节 1Byte 1Byte 2Byte 2Byte 2Byte
解释 从机地址01 读取输出寄存器 从地址0x0000 读取5个寄存器

2、从机回应Rx: 01 04 0A AB CD 12 34 23 45 00 00 00 02 10 C4

地址 功能码 字节计数 数据 CRC
数据包实例 01 04 0A AB CD 12 34 23 45 00 00 00 02 10 C4
可选范围HEX 1-FA(1-250)
所占字节 1Byte 1Byte 2Byte 1-250 ?5*2 = 10Byte 2Byte
解释 从机地址01 正常读取 后面有10个Byte的数据 0xABCD、0x1234、0x2345、
0x0000、0x0002

5、写单个线圈状态 05

1、主机发送 Tx: 01 05 00 00 FF 00 8C 3A

地址 功能码 地址 CRC
数据包实例 01 05 00 00 FF 00 8C 3A
可选范围HEX 真FF00 假0000
所占字节 1Byte 1Byte 2Byte 2Byte 2Byte
解释 从机地址01 写取单个线圈 从地址0x0000 对某一位置一

2、从机回应 Rx: 01 05 00 00 FF 00 8C 3A 同请求

地址 功能码 地址 CRC
数据包实例 01 05 00 00 FF 00 8C 3A
可选范围HEX 真FF00 假0000
所占字节 1Byte 1Byte 2Byte 2Byte 2Byte
解释 从机地址01 正常写入 从地址0x0000 置一成功

6、写单个保持寄存器 06

1、主机发送 Tx: 01 06 00 00 04 D2 0B 57

地址 功能码 地址 CRC
数据包实例 01 06 00 00 04 D2 0B 57
可选范围HEX
所占字节 1Byte 1Byte 2Byte 2Byte 2Byte
解释 从机地址01 写单个保持寄存器 从地址0x0000 写1个线圈

2、从机回应 Rx: 01 06 00 00 04 D2 0B 57 同请求

地址 功能码 地址 CRC
数据包实例 01 06 00 00 04 D2 0B 57
可选范围HEX
所占字节 1Byte 1Byte 2Byte 2Byte 2Byte
解释 从机地址01 正常写入 从地址0x0000 读得十进制1234

7、写多个线圈 15

1、主机发送 Tx: 01 0F 00 00 00 0A 02 07 00 E7 08

地址 功能码 线圈地址 写数量(线圈) 数据计数 数据 CRC
数据包实例 01 0F 00 00 00 0A 02 07 00 E7 08
可选范围HEX 1-07B0(1-1968) 1-F6
所占字节 1Byte 1Byte 2Byte 2Byte 1Byte (1-246) 2Byte
解释 从机地址01 写多个线圈 从地址0x0000 请求写10个线圈 数据计数 0000000111

2、从机回应 Rx: 01 0F 00 00 00 0A D5 CC

地址 功能码 线圈地址 写数量(线圈) CRC
数据包实例 01 0F 00 00 00 0A D5 CC
可选范围HEX 1-07B0(1-1968)
所占字节 1Byte 1Byte 2Byte 2Byte 2Byte
解释 从机地址01 正常写入 从地址0x0000 写10个线圈成功

8、写多个保持寄存器 16

1、主机发送 Tx: 01 10 00 00 00 0A 14 12 34 09 29 00 00 00 00 00 00 00 00 00 00 00 00 00 00 1A 85 A3 7E

地址 功能码 线圈地址 写数量(寄存器) 数据计数 数据 CRC
数据包实例 01 10 00 00 00 0A 14 -- A3 7E
可选范围HEX 1-007B(1-123) 1-F6
所占字节 1Byte 1Byte 2Byte 2Byte 1Byte 1-246 2Byte
解释 从机地址01 写多个保持寄存器 从地址0x0000 --

2、从机回应 Rx: 01 10 00 00 00 0A 40 0E

地址 功能码 线圈地址 写数量(寄存器) CRC
数据包实例 01 10 00 00 00 0A 40 0E
可选范围HEX
所占字节 1Byte 1Byte 2Byte 2Byte 2Byte
解释 从机地址01 正常写入 从地址0x0000

REF:

Modbus通讯协议从一窍不通到原来如此_modbus从站主动上报-CSDN博客ModbusRTU的几种常用功能码介绍及使用_modbus功能码-CSDN博客

posted @ 2024-07-15 13:16  赤诚Xie  阅读(4)  评论(0编辑  收藏  举报