Modbus协议及基于Python的ModbusTCP客户端实现

这篇具有很好参考价值的文章主要介绍了Modbus协议及基于Python的ModbusTCP客户端实现。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

Modbus 协议是由 Modicon 公司(现在的施耐德电气 Schneider Electric )于1979年为使用可编程逻辑控制器(PLC)通信而推出,主要建立在物理串口、以太网 TCP/IP 层之上,目前已经成为工业领域通信协议的业界标准,广泛应用在工业电子设备之间的互联。

Modbus技术文档

1 网络模型

Modbus 是OSI模型第 7 层上的应用层报文传输协议,它在连接至不同类型总线或网络的设备之间提供客户机/服务器通信。

Modbus 是一个请求/应答协议,并且提供功能码规定的服务。

2 Modbus 协议描述

Modbus 主要有 4 种通信模式:

Modbus 协议类型 描述
RTU 模式(串口) 二进制表示数据,采用循环冗余校验的校验和
ASCII 模式(串口) 采用人类可读的、冗长的表示输入,采用纵向冗余校验的校验和
TCP 模式(网口) 基于TCP通信,与Modbus RTU相似,取消循环冗余校验的校验和
UDP 模式(网口) 基于UDP通信,与Modbus RTU相似,取消循环冗余校验的校验和

协议格式:

Modbus 协议类型 协议格式
Modbus RTU [地址码] [功能码] [数据] [CRC校验码]
Modbus ASCII [起始冒号] [地址码] [功能码] [数据] [LRC校验码] [回车换行]
Modbus TCP [事务处理标识] [协议标识] [长度] [单元标识符] [功能码] [数据]
Modbus UDP [事务处理标识] [协议标识] [长度] [单元标识符] [功能码] [数据]

Modbus 协议定义了一个与基础通信层无关的简单协议数据单元(PDU)。

PDU = [功能码] [数据] ,功能码为1字节,数据长度不定,由具体功能决定。

标准的Modbus协议物理层接口有RS232、RS422、RS485和以太网接口,采用master/slave方式通信。

modbus-RTU (Remote Terminal Unit 远程终端单元)是数据在串口RS485等链路上传输的。

modbus-TCP 是使用以太网TCP网络进行通信的,使用502端口,客户端和服务端模式。

Modbus-RTU 数据格式

modbus-RTU 数据包格式=PDU+CRC

PDU由功能码+数据组成。功能码为1字节,数据长度不定,由具体功能决定。

CRC为校验码,为2个字节。

Modbus-TCP 数据格式

modbus-TCP 数据帧格式=MBAP+PDU

MBAP为报文头,长度为7字节,由事务处理标识+协议标识符+长度+单元标识符组成:

示例内容 长度 描述
事务处理标识 00 00 2字节 可以理解为报文的序列号,一般每次通信之后就要加1
协议标识符 00 00 2字节 00 00表示ModbusTCP协议
长度 00 06 2字节 表示接下来的数据长度,单位为字节
单元标识符 01 1字节 串行链路或其它总线上连接的远程从站的识别码

3 数据模型

Modbus 协议中一个重要的概念是寄存器,所有的数据均存放于寄存器中。最初Modbus协议借鉴了PLC中寄存器的含义,但是随着Modbus协议的广泛应用,寄存器的概念进一步泛化,不再是指具体的物理寄存器,也可能是一块内存区域。Modbus寄存器根据存放的数据类型以及各自读写特性,将寄存器分为4个部分,这4个部分可以连续也可以不连续,由开发者决定。

寄存器种类 数据类型 访问类型 功能码 含义
线圈状态(Coil Status) 读写 0x01 0x05 0x0F PLC的输出位,开关量 0x
离散输入状态(Input Status) 只读 0x02 PLC的输入位,开关量 1x
输入寄存器(Input Register) 只读 0x04 PLC中只能从模拟量输入端改变的寄存器 4x
保持寄存器(Holding Register) 读写 0x03 0x06 0x10 PLC中用于输出模拟量信号的寄存器 3x

4 功能码

Modbus 功能码分三类:公共功能码、用户定义功能码、保留功能码。

Modbus 的常用公共功能码有:

功能码 名称 英文名 位操作/字操作 操作数量
0x01 读线圈状态 READ COIL STATUS 位操作 单个或多个
0x02 读离散输入状态 READ INPUT STATUS 位操作 单个或多个
0x03 读保持寄存器 READ HOLDING REGISTER 字操作 单个或多个
0x04 读输入寄存器 READ INPUT REGISTER 字操作 单个或多个
0x05 写单个线圈状态 WRITE SINGLE COIL 位操作 单个
0x06 写单个保持寄存器 WRITE SINGLE REGISTER 字操作 单个
0x0F 写多个线圈 WRITE MULTIPLE COIL 位操作 多个
0x10 写多个保持寄存器 WRITE MULTIPLE REGISTER 字操作 多个

字节序及数据类型

字节序分类:

存储模式 data_fromat前缀 说明
大端模式 > 数据的高字节保存在寄存器的低地址中,数据的低字节保存在寄存器的高地址中
小端模式 < 数据的高字节保存在寄存器的高地址中,而数据的低字节保存在寄存器的低地址中

Modbus采用大端字节序进行报文传输,字节序不正确则对多字节数据无法解析和组拼。

每个寄存器有两个字节,第一个字节包括高位比特,并且第二个字节包括低位比特。

Modbus 每个寄存器地址是16位的,常用数据类型及长度:

Format C Type Python type 字节数 PLC 寄存器数量 大端模式
h short integer 2 1 AB
H unsigned short integer 2 1 AB
i int integer 2 1 AB
I unsigned int integer 2 1 AB
l long long 4 2 AB CD
L unsigned long integer 4 2 AB CD
f float float 4 2 AB CD
d double float 8 4 AB CD EF GH

1个字节是8个比特,即:1byte = 8bit

Modbus 以16位为一个字进行编址,寄存器是16位的,可以存放两个字节 ,即1寄存器 = 2字节

data_format:对读写数据进行格式化,示例:

>f 中的 > 表示大端模式,f表示1个float,共有4个字节,占用2个寄存器。

>dd 中的 > 表示大端模式,dd表示两个double,共有16个字节,占用8个寄存器。

5 Python示例

Java 的 modbus4j、Python 的 modbus_tk 等第三方库对 modbus 做了很好的封装,开发者通常不需要关注请求、响应、错误的报文解析,第三方库已经根据功能码、数据类型、数据数量等对报文进行了解析。

下面是 Python3 的 modbus 使用示例:文章来源地址https://www.toymoban.com/news/detail-726114.html

import modbus_tk.modbus_tcp as mt
import modbus_tk.defines as cst


if __name__ == '__main__':
    master = mt.TcpMaster('127.0.0.1', 502)
    master.set_timeout(5)
    # 参数说明
    # slave: Modbus从站地址. from 1 to 247. 0为广播所有的slave
    # function_code:功能码
    # starting_address:寄存器起始地址
    # quantity_of_x:寄存器读写的数量,写寄存器时数量可为 0,读寄存器时数量至少为 1; 一个寄存器=2字节, 1字节=8位
    # output_value:输出内容,读操作无效,写操作是一个整数或可迭代的list值:1 / [1,1,1,0,0,1] / xrange(12)
    # data_format:对数据进行格式化 >表示大端模式, <表示小端模式, H表示unsigned short无符号整形(2字节), h表示short有符号整型(2字节), l表示long长整型(4字节), f表示float浮点型(4字节), d表示double双精度浮点型(8字节)
    # expected_length

    try:
        '''
        寄存器类型:线圈状态
        访问类型:读写
        功能码:0x01、0x05、0x0F
        '''

        '''
        0x05功能码:写线圈状态为 开ON(0xff00)/关OFF(0), output_value不为0时都会置为0xff00
        ON的output_value可以设置为 0xff00、True、非0值; OFF的output_value 可以设置为 0x0000、False、0
        返回结果:tuple(地址, 值) ,写成功:如写入开则返回0xff00的十进制格式65280,写入关则返回0x0000的十进制格式0
        '''
        ''' 0x05功能码: 写单个线圈状态 ON '''
        single_coil_value = master.execute(slave=1, function_code=cst.WRITE_SINGLE_COIL, starting_address=0, output_value=1)     # 写单个线圈状态为 ON
        print('0x05 WRITE_SIGNLE_COIL: ', single_coil_value)

        ''' 0x01功能码:读线圈状态 '''
        coils_value = master.execute(slave=1, function_code=cst.READ_COILS, starting_address=0, quantity_of_x=1)    # 读线圈状态
        print('0x01 READ_COILS: ', coils_value)

        ''' 0x05功能码: 写单个线圈状态 OFF '''
        single_coil_value = master.execute(slave=1, function_code=cst.WRITE_SINGLE_COIL, starting_address=1, output_value=0)    # 写单个线圈状态为 OFF
        print('0x05 WRITE_SIGNLE_COIL: ', single_coil_value)
        coils_value = master.execute(slave=1, function_code=cst.READ_COILS, starting_address=1, quantity_of_x=1)    # 读线圈状态
        print('0x01 READ_COILS: ', coils_value)

        ''' 0x0F功能码:写多个线圈状态 '''
        multiple_coils_value = master.execute(slave=1, function_code=cst.WRITE_MULTIPLE_COILS, starting_address=2, quantity_of_x=4, output_value=[1, 1, 1, 1])  # 写多个线圈
        print('0x0F WRITE_COILS_REGISTER: ', multiple_coils_value)
        coils_value = master.execute(slave=1, function_code=cst.READ_COILS, starting_address=2, quantity_of_x=4)    # 读线圈状态
        print('0x01 READ_COILS: ', coils_value)

        '''
        寄存器类型:离散输入状态
        访问类型:只读
        功能码:0x02
        '''
        # 0x02功能码:读离散输入状态
        discrete_value = master.execute(slave=1, function_code=cst.READ_DISCRETE_INPUTS, starting_address=0, quantity_of_x=5)
        print('0x02 READ_DISCRETE_INPUTS: ', discrete_value)

        '''
        寄存器类型:输入寄存器
        访问类型:只读
        功能码:0x04
        '''
        # 0x04功能码:读输入寄存器
        input_value = master.execute(slave=1, function_code=cst.READ_INPUT_REGISTERS, starting_address=0, quantity_of_x=5)
        print('0x04 READ_INPUT_REGISTERS: ', input_value)

        '''
        寄存器类型:保持寄存器
        访问类型:读写
        功能码:0x03、0x06、0x10
        '''
        # 0x06功能码:写单个保持寄存器
        single_register_value = master.execute(slave=1, function_code=cst.WRITE_SINGLE_REGISTER, starting_address=0, output_value=666)
        print('0x06 WRITE_SINGLE_REGISTER: ', single_register_value)

        # 0x03功能码:读保持寄存器
        holding_value = master.execute(slave=1, function_code=cst.READ_HOLDING_REGISTERS, starting_address=0, quantity_of_x=1)
        print('0x03 READ_HOLDING_REGISTERS: ', holding_value)

        # 0x10功能码:写多个保持寄存器
        multiple_registers_value = master.execute(slave=1, function_code=cst.WRITE_MULTIPLE_REGISTERS, starting_address=1, quantity_of_x=3, output_value=[777, 777, 777])
        print('0x10 WRITE_MULTIPLE_REGISTERS: ', multiple_registers_value)
        holding_value = master.execute(slave=1, function_code=cst.READ_HOLDING_REGISTERS, starting_address=1, quantity_of_x=3)
        print('0x03 READ_HOLDING_REGISTERS: ', holding_value)


        # 数据类型
        # 写单个寄存器:无符号整数
        single_register_value = master.execute(slave=1, function_code=cst.WRITE_SINGLE_REGISTER, starting_address=0, output_value=4097)
        print('0x06 WRITE_SINGLE_REGISTER: ', single_register_value)
        # 写单个寄存器:有符号整数
        single_register_value = master.execute(slave=1, function_code=cst.WRITE_SINGLE_REGISTER, starting_address=1, output_value=-1234)
        print('0x06 WRITE_SINGLE_REGISTER: ', single_register_value)
        # 写多个寄存器:有符号整数 (根据列表长度来判读写入个数)
        multiple_registers_value = master.execute(slave=1, function_code=cst.WRITE_MULTIPLE_REGISTERS, starting_address=2, output_value=[1, -2], data_format='>hh')
        print('0x10 WRITE_MULTIPLE_REGISTERS: ', multiple_registers_value)
        # 读寄存器
        holding_value = master.execute(slave=1, function_code=cst.READ_HOLDING_REGISTERS, starting_address=0, quantity_of_x=4, data_format='>hhhh')
        print('0x03 READ_HOLDING_REGISTERS: ', holding_value)

        # 写多个寄存器: 浮点数float(float长度为4个字节,占用2个寄存器)
        # 起始地址为8的保持寄存器,操作寄存器个数为 4 ,一个浮点数float 占两个寄存器;
        # 写浮点数时一定要加 data_format 参数,两个ff 表示要写入两个浮点数,以此类推
        # 我这里模拟的是大端模式,具体可参考 struct 用法。和数据源保持一致即可。 <表示小端,>表示大端
        multiple_registers_value = master.execute(slave=1, function_code=cst.WRITE_MULTIPLE_REGISTERS, starting_address=8, output_value=[1.0, -6.4], data_format='>ff')
        print('0x10 WRITE_MULTIPLE_REGISTERS: ', multiple_registers_value)
        # 读对应的 4个寄存器(2个float),指定数据格式
        holding_value = master.execute(slave=1, function_code=cst.READ_HOLDING_REGISTERS, starting_address=8, quantity_of_x=4, data_format='>ff')
        print('0x03 READ_HOLDING_REGISTERS: ', holding_value)

        # 写多个寄存器:长整型数据long(long长度为4字节,占用2个寄存器)
        multiple_registers_value = master.execute(slave=1, function_code=cst.WRITE_MULTIPLE_REGISTERS, starting_address=12, output_value=[111111, -222222], data_format='>ll')
        print('0x10 WRITE_MULTIPLE_REGISTERS: ', multiple_registers_value)
        # 读对应的 4个寄存器(2个double),指定数据格式
        holding_value = master.execute(slave=1, function_code=cst.READ_HOLDING_REGISTERS, starting_address=12, quantity_of_x=4, data_format='>ll')
        print('0x03 READ_HOLDING_REGISTERS: ', holding_value)

        # 写多个寄存器:双精度浮点数double(double长度为8个字节,占用4个寄存器)
        multiple_registers_value = master.execute(slave=1, function_code=cst.WRITE_MULTIPLE_REGISTERS, starting_address=16, output_value=[1, -6.4], data_format='>dd')
        print('0x10 WRITE_MULTIPLE_REGISTERS: ', multiple_registers_value)
        # 读对应的 4个寄存器(2个double),指定数据格式
        holding_value = master.execute(slave=1, function_code=cst.READ_HOLDING_REGISTERS, starting_address=16, quantity_of_x=8, data_format='>dd')
        print('0x03 READ_HOLDING_REGISTERS: ', holding_value)
    except Exception as e:
        print('error: %s' % e)

到了这里,关于Modbus协议及基于Python的ModbusTCP客户端实现的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【工控通信】ModbusTCP通讯之ModbusPoll客户端工具配置

    1.安装Modbus Poll客户端工具 2.Modbus Poll客户端工具安装好以后的界面 Modbus Poll uses a multiple windows user interface. That means you can open several windows showing different data areas or data from different slave ID’s at the same time. You can write any text in the Alias cells. In any dialog box you can press the F1 key for more he

    2024年02月13日
    浏览(39)
  • QT5.14 实现ModbusTCP客户端 Demo

    本文在QT5.14平台,基于QModbusClientTcp类,实现了客户端对单个寄存器的读写,用ModbusSlave做服务器做测试。 1.界面 (1)更改读按钮的名称为bt_Read (2)更改写按钮的名称为bt_Write 2.修改pro文件的第三行 greaterThan(QT_MAJOR_VERSION, 4): QT += widgets  serialbus   3.修改mainWindow.h #ifndef MAINWINDOW_H

    2024年01月22日
    浏览(61)
  • C#与西门子PLC1500的ModbusTcp服务器通信4--搭建ModbusTcp客户端

    客户端可以是一个程序或一个设备,这里我以C#WINFORM程序来实现客户机与PLC的Modbustcp服务器通信,开发环境是VS2019,.NET Framework版本是4.7.2  创建类库   编写C#各种类的转换库,该库由我提供,不用操心,文章最后提供。 项目引入这个类库  找到项目,找到引用,右键“管理

    2024年02月11日
    浏览(43)
  • S7-1200PLC 作为MODBUSTCP服务器通信(多客户端访问)

    S7-1200PLC作为MODBUSTCP服务器端通信编程应用,详细内容请查看下面文章链接: ModbusTcp通信(S7-1200PLC作为服务器端)-CSDN博客 文章浏览阅读239次。S7-200Smart plc作为ModbusTcp服务器端的通信S7-200SMART PLC ModbusTCP通信(ModbusTcp服务器)_s7-200 modbustcp-CSDN博客文章浏览阅读2.3k次。 https://rxxw-con

    2024年02月01日
    浏览(51)
  • Linux 基于 TCP 协议的简单服务器-客户端应用

    目录 一、相关函数  1、listen() 2、accept() 3、connect()  4、两种IP地址转换方式  5、TCP和UDP数据发送和接收函数对比 5、log.hpp自定义记录日志 二、udp_server.hpp单进程版本 三、tcp_server.cc 四、Telnet客户端(代替tcp_client.cc) 五、多进程实现udp_server.hpp 1、多进程版本一 2、tcp_client.

    2024年04月27日
    浏览(51)
  • Modbus/TCP:主站、从站、客户端和服务端关系

    主站主动找从站读写数据 客户端主动找服务端读写数据 所以当使用Modbus/TCP时,主站一般作为客户端,从站一般作为服务端 当使用Modbus/TCP时,modbus poll一般模拟客户端,modbus slave一般模拟服务端

    2024年02月12日
    浏览(62)
  • 【网络编程】——基于TCP协议实现回显服务器及客户端

    个人主页:兜里有颗棉花糖 欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 兜里有颗棉花糖 原创 收录于专栏【网络编程】【Java系列】 本专栏旨在分享学习网络编程的一点学习心得,欢迎大家在评论区交流讨论💌 TCP提供的API主要有两个类 Socket ( 既会给服务器使用也会给客

    2024年02月03日
    浏览(65)
  • C++ ModBUS TCP客户端工具 qModMaster 介绍及使用

    QModMaster是一个基于Qt的Modbus主站(Master)模拟器,用于模拟和测试Modbus TCP和RTU通信。它提供了一个直观的图形界面,使用户能够轻松设置和发送Modbus请求,并查看和分析响应数据。 以下是QModMaster工具的一些主要特点和功能:  支持Modbus TCP和RTU:QModMaster可以用作Modbus TCP和

    2024年02月13日
    浏览(50)
  • Linux下基于TCP协议的Socket套接字编程(客户端&服务端)入门详解

    写在前面: 本篇博客探讨实践环境如下: 1.操作系统: Linux 2.版本(可以通过命令 cat /etc/os-release 查看版本信息):PRETTY_NAME=“CentOS Linux 7 (Core)” 编程语言:C 常常说socket 、套接字 那么socket 到底指的是什么? socket 本质上是一个抽象的概念,它是一组用于 网络通信的 API , 提供

    2024年02月01日
    浏览(57)
  • Python物联网开发-Python_Socket通信开发-Python与Tcp协议物联网设备通信-Socket客户端

            Python在物联网开发中的重要愈来愈重,因此,掌握Python语言与物联网设备之间的通信就显得尤为重要,可以通过编写Python程序实现获取物联网设备的传感器数值并可以更改物联网设备上的执行器状态。         首先,当使用Python进行Socket通信时,需要导入Python的so

    2024年02月17日
    浏览(70)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包