一篇讲懂Modbus TCP协议,这样理解太简单了


一、发展历史

ModbusTCP是一种基于以太网的通信协议
ModbusTCP协议由施耐德公司在1996年推出,它继承了ModbusRTU协议的核心功能,但主要通过TCP/IP以太网进行数据传输,实现了设备之间的主从式通信。
在实际应用中,针对串口ModbusRTU,我们通过采用主站/从站的说明双方的角色,对于ModbusTCP,我们通过使用服务器/客户端来定义双方的角色。

二、通用格式

我们首先来分析-下ModbusTCP与ModbusRTU的区别:

我们可以看出,ModbusTCP在Modbus串行通信的基础上,去除了差错校验和附加地址(即从站地址),然后加上MBAP报文头(7Bytes)。
1.ModbusTCP协议一般用于TCP或UDP通信,而TCF和UDP本身就具备数据校验,因此不需要再加校验了。
2、ModbusTCP主要用于以太网通信,因此,不再需要通过附加地址(即从站地址)来区分不同的设备,因为以太网设备一般会使用IP地址来区分。


因此,我们只需要了解MBAP报文头即可,因为功能码和数据部分与ModbusRTU协议是一样的,下面主要针对MBAP报文头进行分析说明。

长度描述客户机服务器
事务处理标识符2字节Modbus请求/响
应的识别码
管户机 启动复位响应
协议标识符2字节0=Modbus协议管户机 启动复位响应
长度2字节后续字节的数量管户机 启动服 务器 启动
单元标识符1字节

串行链路或其它

总线上连接的远

程从站的识别码

管户机 启动复位响应

【1】事务处理标识符:事务处理标识符可以理解为报文的编号,服务器会复制客户端 的事务处理标识符进行响应,因此事务处理标识符并没有实际意义,一般情况下我们会使用ID自增的方式,事务处理标识符占用2个字节长度。
【2】协议标识符:协议标识符设计时是为了区分不同的协议,但是在实际应用中,只有一个协议Modbus协议用0来表示,协议标识符占用2个字节长度,所以协议标识符是固定值0x00 0x00。
【3】长度:长度表示它后面有多少个字节,即单元标识符、功能码、数据这三个部分 的字节个数,长度占用2个字节长度。
【4】单元标识符:单元标识符与从站地址是相似的,因为ModbusTCP去除了从站地址,所以在MBAP报文头中加入单元标识符,防止有些场合需要通过一个标识来区别不同的子设备,比如一个串口服务器。下挂了多个串口设备,那么就需要单元标识符来区别不同的串口设备,实际应用中单元标识符的值由服务器决定,
综合来看,ModbusTCP协议的通用报文格式如下所示:

事务处理标识符协议标识符长度单元标识符功能码数据
2 bytes2 bytes2 bytes1 bytes1 bytesN bytes

下面针对常用的几个功能码进行详细讲解。

三、读取输出线圈

读取输出线圈发送报文格式如下:

事务/协议长度单元标识符功能码起始高起始低数量高数量低
0x000000000x00060x010x010x000x130x000x1B

我们来分析一下这段发送报文:
【1】事务/协议:事务处理标识符和协议标识符都是用的固定值0x00 0x00。
【2】长度:单元标识符、功能码、数据部分总共是6个字节,因此长度是0x00 0x06
【3】单元标识符:单元标识符默认是0x01。
【4】功能码:0x01表示读取的是输出线圈存储区
【5】字节计数:0x04表示返回的数据总共有4个字节。
【6】字节1至字节4:返回的具体数据分别为0xCD0x6B、0xB2、0x05,这4个字节对应32个线值,前面的27个线圈值即对应00020-00046的值。
客户端收到响应报文便获取到了自己要的数据,具体对应关系如下:
0xCD=11001101对应00027-00020
0x6B=01101011对应00035-00028
0xB2=10110010对应00043-00036
0x05=0000 0101对应00051-00044

至此,就完成了一次Modbus通信交互。

至于读取输入线圈,与读取输出线圈几乎一致,唯一的区别就是功能码从0x01变成了0x02,这里就不做过多势述了

四、读取保存型寄存器

读取保持型寄存器发送报文格式如下:

事务/协议长度单元标识符功能码起始高起始低数量高数量低
0x000000000x00060x010x030x000x6B0x000x02

这个与读取输出线圈是相似的,只不过这里的起始地址是寄存器地址。
我们来分析一下这段发送报文
【1】事务/协议:事务处理标识符和协议标识符都是用的固定值0x00 0x00。
【2】长度:单元标识符、功能码、数据部分总共是6个字节,因此长度是0x00 0x06,
【3】单元标识符:单元标识符默认是0x01。
【4】功能码:0x03表示读取的是保持型寄存器存储区。
【5】起始地址:十六进制0x00 0x6B对应十进制为107,表示从107号寄存器开始读取,这个107对应的Modbus地址为40108.
【6】寄存器数量:0x00 0x02对应十进制的2,表示读取寄存器数量为2。
客户端发送这段报文是想要读取服务器保持型寄存器存储区,Modbus地址从40108-40109,共2个寄存器的数据值

服务器响应报文格式如下:

事务/协议长度单元标识符功能码字节计数1高1低2高2低
0x000000000x00070x010x030x040x000xC80x010x2C

我们再来分析一下这段响应报文:
【1】事务/协议:事务标识符服务器是复制发送报文的,是固定值0x00 0x00。
【2】长度:单元标识符、功能码、数据部分总共是7个字节,因此长度是0x00 0x07.
【3】单元标识符:单元标识符与发送报文一致:
【4】功能码:0x03表示这帧报文响应的是0x03功能码的报文。
【5】字节计数:0x04表示返回的数据总共有4个字节
【6】字节1至字节4:返回的具体数据分别为0x000xC8、0x01、0x2C,这4个字节对应2个保持型寄存器的值,即对应40108-40109的值。
客户端收到响应报文便获取到了自己要的数据,具体对应关系如下:

0x00 0xC8对应40108的值,16进制的0x000xC8对应十进制的200
0x01 0x2C对应40109的值,16进制的0x010x2C对应十进制的300

读取输入寄存器,与读取保持型寄存器报文格式几乎一致,唯一的区别就是功能码从0x03变成了0x04,这里就不做过多赘述了

五、预置单线圈

我们对0x05功能码预置单线圈进行说明,发送报文格式如下:

事务/协议长度单元标识符功能码线圈高线圈低断通标志断通标志
0x000000000x00060x010x050x000xAC0xFF0x00

我们来分析一下这段发送报文:
【1】事务/协议:事务处理标识符和协议标识符都是用的固定值0x00 0x00。
【2】长度:单元标识符、功能码、数据部分总共是6个字节,因此长度是0x00 0x06。
【3】单元标识符:单元标识符默认是0x01
【4】功能码:0x05表示写入单个输出线圈
【5】线圈地址:十六进制0x00 0x1A对应十进制为26表示写入26号线圈,这个26对应的Modbus地址为00027
【6】断通标志:0xFF 0x00表示置位,就是将该线圈状态设置为True。
这段发送报文表示的含义是客户端想要将服务器输出线存储区,Modbus地址00027这个线置为True:
接收报文格式如下:

事务/协议长度单元标识符功能码线圈高线圈低断通标志断通标志
0x000000000x00060x010x050x000xAC0xFF0x00

预置单线圈接收报文与发送报文一致,原报文返回。

六、预置单寄存器

我们对0x06功能码预置单寄存器进行说明,发送报文格式如下:

事务/协议长度单元标识符功能码寄存器高寄存器低写入值写入值
0x000000000x00060x010x060x000x100x030x00

【1】事务/协议:事务处理标识符和协议标识符都是用的固定值0x00 0x00。
【2】长度:单元标识符、功能码、数据部分总共是6个字节,因此长度是0x00 0x06。
【3】单元标识符:单元标识符默认是0x01
【4】功能码:0x06表示写入单个保持型寄存器
【5】寄存器地址:十六进制0x00 0x10对应十进制为16,表示写入16号寄存器,这个16对应的Modbus地址为40017。                                                                                                              【6】写入值:16进制0x03 0x00对应十进制768,就是将该寄存器的值设置为768。
这段发送报文表示的含义是主站想要将1号从站保持型寄存器存储区,Modbus地址 40017这个寄存器的值修改为768。
接收报文格式如下:

事务/协议长度单元标识符功能码寄存器高寄存器低写入值写入值
0x000000000x00060x010x060x000x100x030x00

预置单寄存器接收报文与发送报文一致,原报文返回。

七、预置多线圈

0x0F功能码预置多线圈发送报文格式如下:

事务/协议长度单元标识符功能码起始数量字节数写入值
0x000000000x00090x01OxOF0x000 x130x00 0x0A0x020x0F03

我们来分析一下这段发送报文:
【1】事务/协议:事务处理标识符和协议标识符都是用的固定值0x00 0x00。
【2】长度:单元标识符、功能码、数据部分总共是6个字节,因此长度是0x00 0x06。
【3】单元标识符:单元标识符默认是0x01
【4】功能码:0x0F表示写入多个输出线圈
【5】起始地址:十六进制0x00 0x13对应十进制为19.表示从19号线圈开始写入,这个19对应的Modbus地址为00020。                                                                                                             【6】数量:十六进制0x000x0A对应一进制为10,表示连续写入10个线圈,Modbus地址从00020到00029。
【7】写入值:0x0F 0x03表示写入的两个字节,第一个字节对应前8个线圈,第二个字节 对应后面的线圈。
这段发送报文表示客户端想要对服务器输出线圈存储区,从Modbus地址00020开始的10个线值进行修改,0x0F对应二进制00001111,对应前8个线,就是将00020-00027写入11110000,0x03对应一进制00000011,对应后面的线圈,也就是将00028-00029写入11。

接收报文格式如下:

事务/协议长度单元标识符功能码起始数量
0x000000000x00060x01OxOF0x000 x130x00 0x0A

预置多输出线圈接收报文是在发送报文基础上除去字节数及写入值。

结束语

加油!


 


### 使用 `modbus_tcp` 库中的 `TcpM` 方法实现 Modbus TCP 通信 为了通过 Python 实现 Modbus TCP 的读写操作,可以利用第三方库如 pymodbus 或者 minimalmodbus。这里假设使用的是一个名为 `modbus_tcp` 的假定库来展示如何调用其内部的 `TcpM` 类来进行基本的数据交互。 #### 安装依赖包 如果该库不是标准库的一部分,则需先安装它: ```bash pip install modbus_tcp ``` #### 初始化连接对象并配置参数 创建一个新的客户端实例用于与服务器建立连接,并设置目标 IP 地址以及端口号等必要属性[^1]。 ```python from modbus_tcp import TcpM client = TcpM(host='192.168.0.1', port=502, unit_id=1) ``` #### 执行简单的寄存器读取命令 下面的例子展示了怎样从远程设备请求保持寄存器 (Holding Registers) 中的内容。此函数会返回所选地址范围内的整数值列表。 ```python registers = client.read_holding_registers(start_address=0, quantity_of_x=10) print(f'Registers values: {registers}') ``` #### 向指定位置写入单个或多个寄存器值 对于某些应用场景可能还需要修改远端装置的状态或是设定新的工作模式,在这种情况下就可以采用如下方式向特定编号的一组连续寄存器里批量赋新值。 ```python write_result = client.write_multiple_registers(starting_address=100, registers=[1, 2, 3]) if write_result.isError(): print('Failed to write data') else: print('Data written successfully.') ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

工控仔

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值