使用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通信栈

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

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

Modbus网络体系结构图

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

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

1.2使用

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

1.2.1 Modbus主站

主站调试软件 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 从站

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

01 Coil Status (0x)

02 Input Status (1x)

03 Holding Register (4x)

04 Input Registers (3x)


1.2.3 基本功能码的介绍

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

Modbus支持的四种数据模型

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


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中的使用

六、总结

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,340评论 5 467
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,762评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,329评论 0 329
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,678评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,583评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,995评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,493评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,145评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,293评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,250评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,267评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,973评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,556评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,648评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,873评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,257评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,809评论 2 339

推荐阅读更多精彩内容

  • 一、Modbus 协议简介 ModBus网络是一个工业通信系统,由带智能终端的可编程序控制器和计算机通过公用线路或...
    漠漠彡阅读 91,139评论 2 21
  • 1 简述 Modbus寄存器分为四种,如表1.1所示: 表1.1 寄存器分类 2 常用功能码 Modbus中常用的...
    lakerszhy阅读 44,824评论 0 16
  • 概述 Modbus协议最初由Modicon公司开发出来,在1979年末该公司成为施耐德自动化部门的一部分,现在Mo...
    得奕阅读 1,919评论 1 0
  • 读线圈状态 读取从设备的线圈或离散量输出状态,即DO(Discrete Output,离散输出)请求码:01地址范...
    中_中_阅读 17,078评论 0 1
  • 点击链接加入群聊【Node-RED与IoT开发交流】https://jq.qq.com/?_wv=1027&k=5...
    梅干菜烧饼不加肉阅读 12,147评论 5 7