使用Modbus连接上位软件、下位嵌入式系统、现场PLC控制器及其他智能设备

        这几年的工作,从工控转到到嵌入式软件开发,再到目前的上位软件开发,虽然工作形式发生了变化,但实质都是在做各种各样的编程,针对不同行业的应用做编程工作,在这个过程中发现,单独一个领域的编程工作是相对比较简单的,人力市场上也很好招人,但是涉及到不同领域之间的跨领域人才,是相对奇缺的。而如果试图把不同领域之间的编程连接起来,就更加难上加难了。一般的开发过程是,软件工程师做上位开发,嵌入式工程师做下位的驱动,电气工程师负责PLC的编程及调试,如果涉及到这三者之间的相互通信,往往会出现问题。在此,就这三者之间的通信,发表一下个人的经验所得。

主要内容:

1、Modbus简单介绍,主要介绍Modbus的协议规范及在串行链路上的使用(本文只涉及Modbus RTU的使用);

2、Modbus在PLC中的使用,包括PLC与上位HMI及变频器的通信;

3、Modbus在嵌入式系统中的时候,主要介绍基于STM32嵌入式系统移植FreeModbus的实例;

4、Modbus在上位软件中的使用,介绍在C#程序中调用nmodbus.dll库,以及Modbus开源库libmodbus;

5、Modbus在Qt中的使用。

一、Modbus简介

1.1概述

        Modbus是一种串行通信协议,是Modicon公司(现在的施耐德电气Schneider Electric)于1979年为使用可编程逻辑控制器(PLC)通信而发表。Modbus已经成为工业领域通信协议的业界标准(De facto),并且现在是工业电子设备之间常用的连接方式。 

        Modbus允许多个 (大约240个) 设备连接在同一个网络上进行通信,举个例子,一个由测量温度和湿度的装置,并且将结果发送给计算机。在数据采集与监视控制系统(SCADA)中,Modbus通常用来连接监控计算机和远程终端控制系统(RTU)。

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


使用Modbus连接上位软件、下位嵌入式系统、现场PLC控制器及其他智能设备_第1张图片
Modbus通信栈

        自从 1979 年出现工业串行链路的事实标准以来,MODBUS 使成千上万的自动化设备能够通信。目前,继续增加对简单而雅观的 MODBUS 结构支持。互联网组织能够使 TCP/IP 栈上的保留系统端口 502 访问 MODBUS。MODBUS 是一个请求/应答协议,并且提供功能码规定的服务。MODBUS 功能码是 MODBUS请求/应答 PDU 的元素。

        MODBUS 是一项应用层报文传输协议,用于在通过不同类型的总线或网络连接的设备之间的客户机/服务器通信。

使用Modbus连接上位软件、下位嵌入式系统、现场PLC控制器及其他智能设备_第2张图片
Modbus网络体系结构图

更详细的介绍请可以查看Modbus组织的官网:
The Modbus Organization

Modbus比较权威的介绍,可参看《Modbus协议规范》,网上资源特别多,这里就不放链接了。

1.2使用

        这里仅针对Modbus调试软件,介绍一下Modbus的使用。Modbus常用的调试软件,Modbus主站“Modbus Poll”,Modbus从站“Modbus Slave”,在网上很好找的。

1.2.1 Modbus主站

使用Modbus连接上位软件、下位嵌入式系统、现场PLC控制器及其他智能设备_第3张图片
主站调试软件 Modbus Poll

主站支持的功能代码主要有:

01 Read Coils (0x)

02 Read Discrete Inputs (1x)

03 Read Holding Registers (4x)

04 Read Input Registers (3x)

05 Write Single Coil 

06 Write Single Register

15 Write Multiple Coils

16 Write Multiple Registers

1.2.2 Modbus从站

使用Modbus连接上位软件、下位嵌入式系统、现场PLC控制器及其他智能设备_第4张图片
Modbus 从站

主站支持的功能代码主要有:

01 Coil Status (0x)

02 Input Status (1x)

03 Holding Register (4x)

04 Input Registers (3x)


1.2.3 基本功能码的介绍

从调试软件可以看到,Modbus支持的数据模型主要是四种,如下表:

使用Modbus连接上位软件、下位嵌入式系统、现场PLC控制器及其他智能设备_第5张图片
Modbus支持的四种数据模型

除此之外,Modbus还支持其他功能,但是在项目中使用的比较少,这里不做介绍。


使用Modbus连接上位软件、下位嵌入式系统、现场PLC控制器及其他智能设备_第6张图片
Modbus公共功能码

1、 01(0x01)读线圈

        使用该功能从一个远程设备中读取1-2000个连续的线圈状态。请求PDU指定了第一个线圈的地址和线圈的数目。在PDU中,从零开始寻址线圈,因此编号1-16的线圈寻址为0-15。

        响应报文中的线圈按数据字段对每位一个线圈进行打包。状态被表示成1 = ON和0 = OFF。第一个数据字节的LSB(最低有效位)包含询问中所寻址的输出。其他线圈以此类推,一直到这个字节的高位端为止,并在后续字节中按照从低位到高位的顺序排列。

        如果返回的输出数量不是8的倍数,将用零填充最后数据字节中的剩余位。字节计数字段指定了数据的全部字节数。

读取线圈请求

功能码        1 字节        0x01

起始地址    2 字节        0x0000-0Xffff

线圈数量    2 字节        1-2000(0x7D0)

读线圈响应

功能码            1 字节            0x01

字节计数        1 字节            N

线圈状态        N 字节           N或N+1

N = 输出数据/8,如果余数不等于0,则N = N+1

错误

功能码        1 字节            功能码+ 0x80

异常码        1 字节            01或02或03或04

例:请求读取离散量输入20-38(0x14-0x26,19bit 0x13)

请求                                                        响应

字段名              十六进制            字段名        十六进制

功能码                01                    功能码                    01

起始地址Hi         00                    字节数                    03

起始地址Lo        13                    输出状态27-20        CD

输出数量Hi         00                    输出状态35-28        6B

输出数量Lo        13                    输出状态38-36            05

        将输出 27-20 的状态表示为十六进制CD,或二进制 1100 1101。输出 27 是这个字节的MSB,输出 20 是LSB。通常,将一个字节内的比特表示为MSB 位于左侧,LSB 位于右侧。第一字节的输出从左至右为 27 至 20。下一个字节的输出从左到右为 35 至28。当串行发射比特时,从LSB 向 MSB 传输:即20 . . .27、28 . . . 35 等等。在最后的数据字节中,将输出状态38-36 表示为十六进制 05,或二进制 0000 0101。输出38 是左侧第六个比特位置,输出 36 是这个字节的 LSB。用零填充五个剩余高位比特。注:用零填充五个剩余比特(一直到高位端)

2、 02(0x02)读离散输入

读离散输入请求(PDU)

功能码            1 字节        0x02

起始地址        2 字节        0x0000-0Xffff

输入数量        2 字节        1-2000(0x7D0)

读离散输入响应(PDU)

功能码            1 字节            0x02

字节计数        1 字节            N

输入状态        N x 1个字节 

N = 输出数据/8,如果余数不等于0,则N = N+1

错误

功能码            1 字节            0x82

异常码            1 字节            01或02或03或04

例:请求读取离散量输入197-218(0xC5-0xDA,22bit    0x16)

请求                                                            响应

字段名                    十六进制            字段名                十六进制

功能码                        02                    功能码                    02

起始地址Hi                00                     字节数                    03

起始地址Lo                C4                    输出状态201-197      AC

输出数量Hi                 00                    输出状态212-205        DB

输出数量Lo                16                     输出状态218-213        35

3、 03(0x03)读保持寄存器

        使用该功能码读取一个远程设备中保持寄存器连续块的内容。请求PDU 指定了起始寄存器地址和寄存器数量。从零开始寻址寄存器,因此,寻址寄存器 1-16为 0-15。将响应报文中的寄存器数据分成每个寄存器有两字节,在每个字节中直接地调整二进制内容。对于每个寄存器,第一个字节包括高位比特,并且第二个字节包括低位比特。

读保持寄存器请求(PDU)

功能码                    1 字节        0x03

起始地址                 2 字节        0x0000-0Xffff

寄存器数量              2 字节        1-125(0x7D)

读保持寄存器响应(PDU)

功能码                1 字节                0x03

字节计数            1 字节                2 x N

寄存器值            N x 2个字节 

N = 输出数据/8,如果余数不等于0,则N = N+1

错误

功能码            1 字节                0x83

异常码            1 字节            01或02或03或04

例:请求读取寄存器108-110(0x6C-0x6E,3byte)

请求                                                            响应

字段名                    十六进制            字段名                十六进制

功能码                        03                    功能码                    02

起始地址Hi                00                    字节数                     06

起始地址Lo                6B                    寄存器Hi(108)    02

寄存器编号Hi            00                      寄存器Lo(108)    2B

寄存器编号Lo            03                      寄存器Hi(109)    00

  寄存器Lo(109)    00

  寄存器Hi(110)     00

  寄存器Lo(110)    64

        将寄存器 108 的内容表示为两个十六进制字节值 02 2B,或十进制 555。将寄存器109-110 的内容分别表示为十六进制 00 00 和 00 64,或十进制 0 和 100。

4、 04(0x04)读输入寄存器

    使用该功能码读取一个远程设备中1-125的连续输入寄存器。请求PDU 指定了起始寄存器地址和寄存器数量。从零开始寻址寄存器,因此,寻址寄存器1-16 为 0-15。将响应报文中的寄存器数据分成每个寄存器有两字节,在每个字节中直接地调整二进制内容。对于每个寄存器,第一个字节包括高位比特,并且第二个字节包括低位比特。

读输入寄存器请求(PDU)

功能码                        1 字节                0x04

起始地址                    2 字节                 0x0000-0xFFFF

输入寄存器数量          2 字节                1-125(0x7D)

读输入寄存器响应(PDU)

功能码                        1 字节                0x04

字节计数                    1 字节                 2 x N

输入寄存器                    N x 2个字节 

N = 输出数据/8,如果余数不等于0,则N = N+1

错误

功能码            1 字节            0x84

异常码            1 字节            01或02或03或04

例:请求输入寄存器9(0x09,1byte)

请求                                                                响应

字段名                十六进制                        字段名                        十六进制

功能码                    04                                功能码                            02

起始地址Hi            00                                  字节数                            02

起始地址Lo            08                            输入寄存器Hi(9)              00

输入寄存器数量Hi    00                          输入寄存器Lo(9)             0A

输入寄存器数量Lo    01  

将输入寄存器 9 的内容表示为两个十六进制字节值00 0A,或十进制 10。

5、 05(0x05)写单个线圈

        使用该功能码写一个远程设备上的单个输出为ON或者OFF。

        请求 PDU 指定了线圈的地址。从零开始寻址线圈,因此,寻址线圈 1 对应的是0。线圈值域的常量说明请求的ON/OFF 状态。十六进制值 0XFF00 请求线圈为 ON。十六进制值0X0000 请求线圈为OFF。其它所有值均为非法的,并且对线圈不起作用。

        正常响应是请求的应答,在写入线圈状态之后返回这个正常响应

写单个线圈请求(PDU)

功能码                1 字节            0x05

起始地址            2 字节            0x0000-0xFFFF

输入数量            2 字节            0-255(0xFF)

写单个线圈响应(PDU)

功能码            1 字节                0x05

输出地址        2 字节                0x0000-0xFFFF

输出值            2个字节            0x0000 / 0xFF00

错误

功能码            1 字节            0x85

异常码            1 字节            01或02或03或04


例:写线圈173位ON(0xAD)

请求                                    响应

字段名                十六进制                字段名                    十六进制

功能码                    05                        功能码                        05

输出地址Hi            00                        输出地址Hi                    00

输出地址Lo            AC                        输出地址Lo                AC

输出值Hi                FF                        输出值Hi                    FF

输出值Lo                00                        输出值Lo                    00

6、 06(0x06)写单个寄存器

        使用该功能码写一个远程设备上的单个保持寄存器。

        请求 PDU 指定了被写入寄存器的地址。从零开始寻址寄存器,因此,寻址寄存器1 为0。正常响应是请求的应答,在写入寄存器内容之后返回这个正常响应。

写单个寄存器请求(PDU)

功能码            1 字节0x06

起始地址            2 字节0x0000-0xFFFF

输入数量            2 字节0x0000-0xFFFF

写单个寄存器响应(PDU)

功能码1 字节0x06

字节计数2 字节0x0000-0xFFFF

输入状态2个字节0x0000-0xFFFF

错误

功能码1 字节0x86

异常码1 字节01或02或03或04

例:将十六进制00 03写入寄存器2

请求响应

字段名十六进制字段名十六进制

功能码06功能码06

寄存器地址Hi00输出地址Hi00

寄存器地址Lo01输出地址Lo01

寄存器值Hi00输出值Hi00

寄存器值Lo03输出值Lo03

7、 15(0x0F)写多个线圈

         在一个远程设备中,使用该功能码强制线圈序列中的每个线圈为ON 或OFF。请求PDU 指定了写线圈的地址。从零开始寻址线圈,因此,寻址线圈 1 为 0。请求数据域的内容说明了被请求的ON/OFF 状态。数据比特位置中的逻辑“1”请求相应输出为ON。域比特位置中的逻辑“0”请求相应输出为 OFF。正常响应返回功能码、起始地址和强制的线圈数量。

写多个线圈请求(PDU)

功能码            1 字节                0x0F

起始地址        2 字节                0x0000-0xFFFF

输出数量        2 字节                0x0001-0x07B0

字节数            1 字节                 N

输出值            N x 1 字节 

N=输出数量/8,如果余数不等于 0,那么  N = N+1

写多个线圈响应(PDU)

功能码            1 字节        0x0F

起始地址         2 字节        0x0000-0xFFFF

输出数量         2个字节        0x0001-0x07B0

错误

功能码        1 字节            0x8F

异常码        1 字节            01或02或03或04

例:

        这是一个请求从线圈 20 开始写入10 个线圈的实例:请求的数据内容为两个字节:十六进制CD 01 (二进制 1100 1101 0000 0001)。使用下列方法,二进制比特对应输出。

    Bit    1100110100000001

    输出2726252423222120------2928

        传输的第一字节(十六进制CD)寻址为输出27-20,在这种设置中,最低有效比特寻址为最低输出(20)。传输的下一字节(十六进制01)寻址为输出29-28,在这种设置中,最低有效比特寻址为最低输出(28)。应该用零填充最后数据字节中的未使用比特。

请求响应

字段名        十六进制        字段名            十六进制

功能码          0F                功能码                0F

起始地址Hi    00                起始地址Hi        00

起始地址Lo    13                起始地址Lo        13

输出数量Hi    00                输出数量Hi         00

输出数量Lo    0A                输出数量Lo        0A

字节数            02 

输出值Hi        CD

输出值Lo        01

8、 16(0x10)写多个寄存器

        在一个远程设备中,使用该功能码写连续寄存器块(1 至约120 个寄存器)。在请求数据域中说明了请求写入的值。每个寄存器将数据分成两字节。正常响应返回功能码、起始地址和被写入寄存器的数量。

写多个寄存器请求(PDU)

功能码                1 字节                0x10

起始地址            2 字节                0x0000-0xFFFF

寄存器数量        2 字节                0x0001-0x0078

字节数                1 字节                2 x N

寄存器值             N x 2 字节            值

N=寄存器数量

写多个寄存器响应(PDU)

功能码                1 字节                0x10

起始地址            2 字节                0x0000-0xFFFF

输出数量            2个字节            1至123

错误

功能码                1 字节            0x90

异常码                1 字节            01或02或03或04

例:

将十六进制00 0A和01 02写入以2开始的两个寄存器

请求                                                                响应

字段名                十六进制                字段名                    十六进制

功能码                    10                    功能码                          10

起始地址Hi            00                    起始地址Hi                      00

起始地址Lo            01                    起始地址Lo                      01

寄存器数量Hi        00                    寄存器数量Hi                    00

寄存器数量Lo        02                    寄存器数量Lo                    02

字节数                    04 

寄存器值Hi            00

寄存器值Lo            0A

寄存器值Hi            01 

寄存器值Lo            02


其他有关Modbus的介绍及使用的讲解:

Modbus协议深入讲解 - National Instruments


二、Modbus在PLC中的使用


三、Modbus在嵌入式操作系统中的使用


FreeMODBUS - A free MODBUS ASCII/RTU and TCP implementation - SILA

四、Modbus在C#中的使用

五、Modbus在Qt中的使用

六、总结

你可能感兴趣的:(使用Modbus连接上位软件、下位嵌入式系统、现场PLC控制器及其他智能设备)