一文详解Modbus协议原理、技术细节及软件辅助调试
【摘要】本文详细叙述了Modbus协议原理及技术细节,对初次接触该协议的同学有较好的参考借鉴意义。
【参考】「知识分享」Modbus通信协议详解,「知识分享」知识链路-Modbus通信知识链路
【正文】
概述
Modbus 协议是一种已广泛应用于当今工业控制领域的通用通讯协议。通过此协议,控制器相互之间、或控制器经由网络(如以太网)可以和其它设备之间进行通信。 Modbus 协议使用的是主从通讯技术,即由主设备主动查询和操作从设备。一般将主控设备方所使用的协议称为 Modbus Master,从设备方使用的协议称为 Modbus Slave。典型的主设备包括工控机和工业控制器等;典型的从设备如 PLC 可编程控制器等。有了它,不同厂商生产的控制设备就可以连接成工业网络,进行集中监控。 Modbus 协议定义了一个控制器能够认识和使用的消息结构,而不管它们是经过何种网络进行通信的;而且描述了控制器请求访问其他设备的过程,如何应答来自其他设备的请求,以及怎样侦测错误并记录,并制定了统一的消息域的结构和内容。 当在 Modbus 网络上通信时, Modbus 协议决定了每个控制器必须要知道它们的设备地址,识别按地址发来的消息决定要产生何种行为。如果需要回应,则控制器将生成反馈信息并通过 Modbus 协议发送。
Modbus是一种“一主一从”的点对点通信方式(主机发一帧,从机回一帧的形式),当然在连接上也可以一主多从(宏观看),但实际也是一对一通信,同一时刻只能有一个从机进行响应。
如果需要和多个从机同时通信,这里也支持使用广播,即主机发送指令,所有从机接收指令并执行,但不进行应答。当进行一主多从通信时,主机通过从机ID号来区分要通信的从机设备。从机ID范围为1 - 247,0为广播地址,248~255为用户自定义地址。
Modbus 通讯物理接口可以选用串口(包括 RS232、 RS485 和 RS422 等),也可以选择以太网口。其通信遵循以下的过程:
① 主设备向从设备发送请求
② 从设备分析并处理主设备的请求,然后向主设备发送结果
③ 如果出现任何差错,从设备将返回一个异常功能码
此协议定义了一个控制器能认识使用的消息结构,而不管它们是经过何种网络进行通信的。它描述了一控制器请求访问其它设备的过程,如何回应来自其它设备的请求,以及怎样侦测错误并记录。它制定了消息域格局和内容的公共格式。
当在 Modbus 网络上通信时,此协议决定了每个控制器须要知道它们的设备地址,识别按地址发来的消息,决定要产生何种行动。如果需要回应,控制器将生成反馈信息并用Modbus 协议发出。在其它网络上,包含了 Modbus 协议的消息转换为在此网络上使用的帧或包结构。这种转换也扩展了根据具体的网络解决节地址、路由路径及错误检测的方法。
Modbus 的工作方式是请求/应答,每次通讯都是主站先发送指令,可以是广播,或是向特定从站的单播;从站响应指令,并按要求应答,或者报告异常。当主站不发送请求时,从站不会自己发出数据,从站和从站之间不能直接通讯。
MODBUS 是一种应用层消息传递协议,位于 OSI 模型的第 7 层。它提供连接在不同类型总线或网络上的设备之间的客户端/服务器通信。Modbus 通信栈如下:
-
Modbus常用术语
名词 意义 Master 主(站) 设备 Slave 从 (站) 设备 Client 客户端 Server 服务器端 ADU 应用数据单元(Application Data Unit) PDU 协议数据单元 (Protocol Data Unit) MSB 最高有效位(Most Significant Bit) LSB 最低有效位 (Least Significant Bit) MBAP Modbus 应用协议(Modbus Application Protocol) PLC 可 编 程 逻 辑 控 制 器 (Programmable Logic Controller)
通信链路
MODBUS 协议允许在各种网络体系结构内进行简单通信,每种设备(PLC、HMI、控制面板、驱动程序、动作控制、输入/输出设备)都能使用 MODBUS协议来启动远程操作。在基于串行链路和以太 TCP/IP 网络的 MODBUS 上可以进行相同通信。一些网关允许在几种使用 MODBUS 协议的总线或网络之间进行通信。
- 整体知识链路一览
- 软件数据链路
首先从软件层面来看,如果现在有一对设备需要进行Modbus通信,那么至少需要有一个主机,一个从机,所以在应用层面上,会使用到Modbus主机和Modbus从机协议栈。
如果使用的是Modbus RTU/ASCII的通信形式,可能会使用到串口。如果使用的是Modbus TCP,则可能会使用到TCP/IP协议栈。
- 硬件链路
从硬件层面上来看,无论是Modbus,还是串口,都是以MCU为载体的。所以对于硬件来讲,作为Modbus主机或从机并没有什么区别。
图
然而MCU跟MCU之间的通信,还需要借助一些外围电路来实现,以匹配不同的应用环境。最简单的是TTL,两个MCU直连。如果需要长距离传输,则需要使用232、485、422这种。如果使用TCP,则需要有以太网相关电路。
- 软件工具
- 代码编辑器:既然涉及到软件,那免不了需要有一些编写软件的工具,如Source Insight、VS Code
- 代码编译工具:配套的编译工具链也不可少,初期可以先从一些IDE集成工具入手,常用的有Keil、IAR、STM32Cube IDE。
- 调试工具:应用时则需要一些调试测试用的软件调试工具,对于Modbus协议的测试,可以使用Modbus Poll和Modbus Slave这一对,另外也有Modscan、Modsim这样类似的工具。对于串口调试,可以使用串口调试助手,这种网上一搜都有很多,个人推荐sscom5.13.1,简洁好用。如果是调试以太网,则可以使用网络抓包工具wireshark。
- 硬件工具:对于这种时序类型的,少不了示波器。另外针对特定的电路,就需要使用对应的转换工具,比如TTL电路,要使用电脑进行调试,就需要使用USB转TTL的工具,232、485、422这些都同理。对于一些复杂的时序,用示波器可能还不好分析,这时候就可以借助逻辑分析仪进行辅助分析。
通信形式
目前总共有4种通信形式,RTU、ASCII、TCP、Plus,下面分别予以讲解:
RTU
作为Modbus的一种通信形式,RTU是一种远程终端控制系统,一般是基于串口进行通信。其报文格式是十六进制的,由 Slave ID + 数据 + CRC校验 三部分组成。数据部分详见上面报文解析。剩下的就是数据校验了,这里用的是CRC校验(循环冗余校验,Cyclic Redundancy Check,简称CRC)。要注意的是,数据部分高位在前、低位在后,而CRC校验则是低位在前,高位在后。
名称 | 从机ID | 数据 | CRC校验(低位) | CRC校验(高位) |
---|---|---|---|---|
长度 | 1字节 | 多字节 | 1字节 | 1字节 |
CRC校验
//CRC校验的C语言实现,摘自FreeModbus
static const uint8_t aucCRCHi[256] = {
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40};static const uint8_t aucCRCLo[256] = {
0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7, 0x05, 0xC5, 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09, 0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC, 0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3, 0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 0xF2, 0x32, 0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4, 0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A, 0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF, 0x2D, 0xED, 0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26, 0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, 0x61, 0xA1, 0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67, 0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F, 0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68, 0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA, 0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5, 0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0, 0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C, 0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89, 0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C, 0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83, 0x41, 0x81, 0x80, 0x40};uint16_t usMBCRC16( uint8_t * pucFrame, uint16_t usLen )
{int iIndex;uint8_t ucCRCHi = 0xFF;uint8_t ucCRCLo = 0xFF;while( usLen-- ){iIndex = ucCRCLo ^ *( pucFrame++ );ucCRCLo = (uint8_t)( ucCRCHi ^ aucCRCHi[iIndex] );ucCRCHi = aucCRCLo[iIndex];}return (uint16_t)( ucCRCHi << 8 | ucCRCLo );
}
帧的完整性判断
国标GBT 19582-2规定:一帧中两个字符之间间隔时间不超过1.5字符,以大于3.5字符间隔时间作为一帧的结束,波特率高于19200时,间隔时间固定为1.75ms。
比如,假设串口设置的波特率为9600,1个停止位,无校验位,8个数据位(这种设置以下会简写成9600-1N8的形式),那么此设置下,断帧时间应该为:
断帧时间 = 1 s ∗ ( 1 + 串口数据位数 + 停止位数 ) ∗ 3.5 / 波特率 断帧时间 = 1s * (1+串口数据位数+停止位数)*3.5/波特率 断帧时间=1s∗(1+串口数据位数+停止位数)∗3.5/波特率
即:35/9600 = 3.64ms。
注意:国标规定,串口默认应该配置有偶校验。
技巧:在9600-1N8的串口设置下,可以快速估算数据发送时间,即1个字节发送时间约为1ms。即 (9600/1000)*(1+8+1)=0.96ms
ASCII
ASCII是一种字符型的通信方式,一般也是基于串口进行通信。其报文格式是以ASCII码编码的,由 帧头(:)+Slave ID+数据+LRC校验+帧尾(/r/n)五部分组成,其中Slave ID、数据部分跟RTU完全一样,只不过是以ASCII编码形式,如Slave ID,RTU是 0x01 一个字节的时候,ASCII表示就是30 31两个字节。所以实际工业应用场合很少会用到Modbus/ASCII,因为通信效率太低。
LRC校验——纵向冗余校验,Longitudinal Redundancy Check,简称:LRC。
LRC具体算法如下:
- 对需要校验的数据(2n个字符)两两组成一个16进制的数值求和。
- 将求和结果与256求模。
- 用256减去所得模值得到校验结果(另一种方法:将模值按位取反然后加1)
static UCHARprvucMBLRC( uint8_t * pucFrame, uint16_t usLen )
{uint8_t ucLRC = 0; /* LRC char initialized */while( usLen-- ){ucLRC += *pucFrame++;} ucLRC = (uint8_t) ( -( (int8_t) ucLRC ) );return ucLRC;
}
因为整个报文都是ASCII的编码形式,所以如果使用单片机串口通信,可以把串口的数据位设置为7位(ASCII的码表范围就是0~0x7F,只用低7位)。
TCP/IP
TCP是一种网络协议,而Modbus/TCP就是基于网络协议上的一种应用层协议。其报文格式是十六进制的,由报头(2字节的帧序号+2字节的协议类型+2字节的数据长度+1字节的Slave ID)+数据 两部分组成。由于该通信方式是基于TCP/IP这种可靠协议上,所以通信不需要有额外的校验机制。
Plus
Modbus Plus(又称MB+)是一种高速现场总线网络,也是一种典型的令牌总线网。
协议描述
Modbus 是一个请求、 应答协议,并且提供统一的功能码用于数据传输服务。 Modbus 功能码是 Modbus 请求/应答 PDU (Protocol Data Unit,协议数据单元)的元素之一,所谓的PDU 其实就是 Modbus 协议定义的一个与基础通信层无关的简单协议数据单元。特定总线或网络上的 Modbus 协议映射能够在 ADU (Application Data UInit ,应用数据单元)上引入
一些附加域,从而实现完整而准确的数据传输。
为了寻求一种简洁的通信格式, Modbus 协议定义了 PDU 模型,即功能码+数据的格式,而为了适应多种传输模式,又在 PDU 的基础上增加了必要的前缀 (如地址域)和后缀(如差错校验) ,形成了 ADU 模型。通用 MODBUS 帧如下:
MODBUS 协议建立了客户机启动的请求格式。客户机创建 MODBUS 应用数据单元(ADU),其中包含的PDU中的功能码向服务器指示具体执行哪种操作。PDU中的数据域包括执行功能码操作需要的附加信息,例如,地址、数量以及域中的实际数据字节数等。在一些特定请求中,功能码指示的操作不需要附加信息,此时数据域可以是不存在的。
如果传输无误,且服务器能够正确响应,那么响应PDU中将使用与客户机相同的功能码加包含了请求数据的数据域。如果出现与请求 MODBUS 功能有关的差错,那么将原功能码的MSB将置1,且数据域包括一个异常码,客户机应用能够使用这个域确定下一个执行的操作。
用一个字节编码 MODBUS 数据单元的功能码域。有效的码字范围是十进制 1-255(128-255 为异常响应保留)。Modbus功能码表如下:
-
上述 MODBUS 功能码可分为三类:
-
公共功能码:有3个范围,即[ 1, 64 ] + [ 73, 99 ] + [ 111, 127 ],是较好地被定义的功能码,被公众认可和唯一的。
- 其中常用的功能码有8个(01、02、03、04、05、06、15、16),可以分为位操作和字操作两类,其中,位操作的是线圈和离散输入,两者区别在于,线圈是可读可写的,而离散输入是只读。字操作的是保持寄存器和输入寄存器,两者区别在于,保持寄存器是可读可写的,而输入寄存器是只读的。
-
用户定义功能码:有2个定义范围,即[] 65, 72 ] 和[100, 110] ,用户可自己任意指定选用,但不能保证被选功能码的使用是唯一和通用的
-
保留功能码:其它一些公司对传统产品通常使用的功能码,并且对公共使用是无效的功能码
-
-
Modbus异常码
报文解析
报文长度
- 对串行链路通信来说,最大PDU长度 = 256 - 服务器地址(1 字节)- CRC(2 字节)=253字节。
- 对TCP链路通信来说,最大PDU长度 = 256 - MBAP (7 字节) =249 字节。
PDU类型
- MODBUS 协议定义了三种 PDU:
- 请求 PDU:mb_req_pdu——功能码(1 个字节) + 请求数据(通常包括诸如可变参考、变量、数据偏移量、子功能码等信息)
- 响应 PDU:mb_rsp_pdu——功能码(1 个字节) + 请求数据(与功能码有关,通常包括诸如可变参考、变量、数据偏移量、子功能码等信息)
- 异常响应 PDU:mb_excep_rsp_pdu——功能码(1 个字节,最高位置1)+ 异常码1 个字节)
- 注意:MODBUS 采用大端字节序来传输寄存器地址和数据项
数据模型
一个设备里,可能有多个“线圈状态”、多个“ 离散输入状态”、多个“保存寄存
器”、多个“输入寄存器” 。

- 上图示出了某设备结构,该设备含有数字量和模拟量、输入量和输出量。由于不同块中的数据不相关,每个块是相互独立。按不同MODBUS 功能码访问每个块。
- 在这个实例中,设备仅有 1 个数据块。通过不同 MODBUS 功能码可能得到一个相同数据,或者通过 16 比特访问或 1 个访问比特。
怎么分辨某类寄存器中的某一个?通过“寄存器地址” ,如下表所示:
寄存器种类 | PLC 寄存器地址范围 | Modbus 寄存器地址范围 | 简称 | 读写状态 |
---|---|---|---|---|
线圈状态 | 00001~09999 | 0000H~FFFFH | 0x | 可读可写 |
离散输入状态 | 10001~19999 | 0000H~FFFFH | 1x | 只读 |
保持寄存器 | 40001~49999 | 0000H~FFFFH | 4x | 可读可写 |
输入寄存器 | 30001~39999 | 0000H~FFFFH | 3x | 只读 |
简单记忆方法:
① 偶数类的寄存器”是可读可写的,比如“0x”和“4x”;
② “奇数类的寄存器”是只读的,比如“1x”和“3x” ;
③ “0x”和“1x”是 bit 寄存器;
④ “3x”和“4x”是 16bit 寄存器。
服务器侧 MODBUS 事务处理流程
线圈
读线圈(功能码0x01)
-
通常,将一个字节内的比特表示为 MSB 位于左侧,LSB 位于右侧。第一字节的输出从左至右27 至 20。下一个字节的输出从左到右为 35 至 28。当串行发射比特时,从 LSB 向 MSB 传输:20 . . .27、28 . . . 35 等等。在最后的数据字节中,将输出状态 38-36 表示为十六进制字节值 05,或二进制 0000 0101。输出 38 是左侧第六个比特位置,输出 36 是这个字节的 LSB。用零填充五个剩余高位比特。
-
客户端发送帧(请求PDU)格式:
序号 0 1 2 3 4 5 6 7 字段定义 ADDR CMD MSB LSB MSB LSB LSB MSB 解释 从节点地址 命令类型 线圈起始地址高位 线圈起始地址低位 线圈个数高位 线圈个数低位 CRC校验低位 CRC校验高位 数值 XXH 00H 13H 00H 13H XXH XXH -
服务器应答帧(响应PDU)格式
-
正常应答帧格式:原功能码+线圈状态(1bit对应1个线圈状态)
-
异常应答帧格式:将原功能码的最高位bit7置1后,跟异常码(1~4)
-
-
读取线圈状态图
写单个线圈(功能码0x05)
写多个线圈(功能码0X0F)
-
上图是一个请求从线圈 20 开始写入 10 个线圈的实例:
请求的数据内容为两个字节:十六进制 0xCD01 (二进制 1100 1101 0000 0001)。使用下列方法,二进制比特对应输出。
输入的比特:1 1 0 0 1 1 0 1 0 0 0 0 0 0 0 1
对应线圈号:27,26,25,24 23,22,21,20 X,X,X,X X,X,29,28
传输的第一字节(十六进制 CD)寻址为输出 27-20,在这种设置中,最低有效比特寻址为最低输出(20)。
传输的下一字节(十六进制 01)寻址为输出 29-28,在这种设置中,最低有效比特寻址为最低输出(28)。
应该用零填充最后数据字节中的未使用比特。
读离散输入–只读(功能码02)
-
读离散量输入的状态图
保持寄存器
读保持寄存器(功能码03)
写单个保持寄存器(功能码0x06)
写多个保持寄存器(功能码0x10)
输入寄存器–只读(功能码04)
文件记录
读文件记录(功能码0x14)
-
这是一个请求从远程设备读取两个参考组的实例:
- 组1 包括文件 4 中的 2 个寄存器,以寄存器 1 开始(地址 0001)。
- 组2 包括文件 3 中的 2 个寄存器,以寄存器 9 开始(地址 0009)。
写文件记录(功能码0x15)
-
这是一个请求将一个参考组写入远程设备的实例:组包括文件4 中的3 个寄存器,以寄存器7 开始(地址0007)。
Modbus的PLC应用
这是一种基于标准Modbus上的PLC应用协议,目前貌似没有一个绝对的标准,不同的PLC厂家有自己的定义,但实际通信链路层走的协议还是按标准的Modbus协议来的。
比如某PLC厂家规定,读取保持寄存器的地址是40001~49999,当PLC读取40001地址时,实际链路层读的是0000地址(就是在标准的基准地址前加上一个分段标识符,并且地址偏移1)。
常用Modbus 软件与使用
为了更好的学习和理解 Modbus,这里推出几个软件 :
-
Modbus Poll,可模拟主机,跟从机进行通信。
-
Modbus Slave,可模拟从机,响应主机的通信。
-
Modbus Scan,更偏向于PLC的操作界面,个人觉得没有Modbus Poll灵活。
-
串口调试助手,可以完全从指令层面上进行深入了解,而且也没有上述工具一些问题(因为一般集成的功能越多,问题可能越多),但有个缺点就是如果作为从机应答,没办法做到及时响应主机的指令。
Modbus Poll(主站设备)
Modbus Poll 是 Modbus 主站设备仿真器,用于测试和调试 Modbus 从设备便于观察Modbus 通信过程中的各种报文数据。该软件支持 ModbusRTU、 ASCII、 TCP/IP。用来帮助开发人员测试Modbus从设备,或者其它Modbus协议的测试和仿真。它支持多文档接口,即,可以同时监视多个从设备/数据域。每个窗口简单地设定从设备 ID,功能,地址,大小和轮询间隔。你可以从任意一个窗口读写寄存器和线圈。如果你想改变一个单独的寄存器,简单地双击这个值即可。或者你可以改变多个寄存器/线圈值。提供数据的多种格式方式,比如浮点、双精度、长整型(可以字节序列交换)。该软件支持 Modbus RTU、 ASCII、TCP/IP 、UDP/IP等协议模式。
软件下载地址:https://pan.baidu.com/s/1SpTRz6Z1XlkoCZjDozwqog 提取码: timc
-
软件界面如下:
-
状态栏:
- ① Tx = 0 表示向主站发送数据帧次数,图中为 0 次;
- ② Err = 0 表示通讯错误次数,图中为 0 次;
- ③ ID = 1 表示模拟的 Modbus 子设备的设备地址,图中地址为 1;
- ④ F = 03 表示所使用的 Modbus 功能码,图中为 03 功能码;
- ⑤ SR = 1000ms 表示发送周期, 1S 一次。
- ⑥ 红字部分,表示当前的错误状态, “No Connection”表示未连接状态。
-
建立连接:
-
点击 Connection->Connect 进入配置页面,选择我们想要的连接,选择我们虚拟出来的串口,选择模式,例如:我们选择串口的连接方式,选则 RTU 模式,对应我们的 ModbusRTU 协议;接下来再设置波特率、比特位、校验位、停止位,如下图所示:
-
设置参数:点击 Setup->Read/Write Definition 进入配置页面,配置从机地址、功能
码、地址类型、寄存器地址、访问数量、轮询时间,具体配置如下图
-
-
Modbus Slave(从站设备)
Modbus 从设备仿真器,可以仿真 32 个从设备/地址域。每个接口都提供了对 EXCEL 报表的 OLE 自动化支持。主要用来模拟 Modbus 从站设备,接收主站的命令包,回送数据包。帮助 Modbus 通讯设备开发人员进行 Modbus 通讯协议的模拟和测试,用于模拟、测试、调试Modbus 通讯设备,便于观察 Modbus 通信过程中的各种报文数据;可以 32 个窗口中模拟多达 32 个 Modbus 子设备。与 Modbus Poll 的用户界面相同,支持功能 01, 02, 03, 04, 05,06, 15, 16, 22 和 23,监视串口数据。
获取软件链接同上,下载完后主页面如图所示:
-
建立连接 (同上)
-
设置参数 (点击 Setup->Read/Write Definition 进入配置页面,配置从机地址、功能
码、地址类型、寄存器地址、访问数量,具体配置如下图 ) -
注意: 软件中的Address 表示 Modbus 寄存器地址,其取值范围与设备寄存器地址存在映射关系,如下表所示
Device address Modbus address Description Function R/W 1…10000 Device address - 1 Coils(outputs) 01 Read/Write 10001…20000 Device address - 10001 Discrete Inputs 02 Read 40001…50000 Device address - 40001 Holding Registers 03 Read/Write 30001…40000 Device address - 30001 Input Registers 04 Read
虚拟串口助手
虚拟串口工具,可以创建 2 个互联的串口(例如将COM1与COM2互联), 此时,我们可以通过 Modbus Poll 工具使用 COM1 发送数据给 COM2, Modbus Slave 从 COM2 读到数据。使用虚拟串口,就可以不使用开发板也可以体验 Modbus Poll、 Modbus Slave。
安装后运行虚拟串口程序“Virtual Serial Port Tools”,安装下图创建 2 个串口:
打开设备管理器,应该可以看到刚才创建的2个串口。
Modbus Poll 与 Modbus Slave 互通实验
根据前面的设定我们已将知道了如何运用 Modbus 学习必备三件套,下面我们就通过三件套来进行实验,首先打开 VSPD 虚拟串口软件,设置虚拟串口,我这里就以上面设订 COM1,COM2 为例,接下来我们再来配置我们的 Modbus Poll 与 Modbus Slave;
首先,打开 Modbus Slave 端,设置连接,连接方式我们选择 Serial Port 串口连接,选择我们设置的串口 COM1,模式选择 RTU 模式;再设置参数,从机地址设定为 1(也可随意设定), Function 项选择03 Holding register(4x),地址类型选择 DEC(十进制格式), Address 首地址设置为 0,访问寄存器数量设置为 10 。
然后,打开 Modbus Poll 端,设置方法也是和 Modbus Slave 端一一对应的,连接设定,参数设定,如下图所示:
设置好后, 主设备和从设备分别连接了我们已经安装好的互联的 COM1, COM2,这样我们便可观察当前寄存器的读取情况。
双击 Modbus Poll(主设备端)地址中的 0 值,便可打开值设置窗口:
修改值为 66,点击 Send 打开 Modbus Slave(从设备端)便可发现也做出了改变
我们还可以在 Modbus Poll中,点击 Display,选择 Commuaction,查看发送的报文:TX 是我们主站发送的报文, RX 是从站返回的报文。
C语言源码推荐
- Free Modbus,Github上链接https://github.com/armink/FreeModbus_Slave-Master-RTT-STM32。原本只有从机是开源的,主机收费,但这个作者自己写了主机部分,实现了带操作系统和裸机的接口。其设计架构也是根据操作的寄存器类型和通信方式进行区分。如从机的保持寄存器的实现,封装在HoldingReg_S文件中,RTU的通信形式,则在mb_rtu中。架构清晰明了,使用起来也很简单,只需要实现mbport.h里的几个接口就行。
- 事件类:xMBPortEventInit、xMBPortEventPost、xMBPortEventGet。
- 串口类:xMBPortSerialInit、vMBPortClose、xMBPortSerialClose、vMBPortSerialEnable、xMBPortSerialGetByte、xMBPortSerialPutByte。
- 定时类:xMBPortTimersInit、xMBPortTimersClose、vMBPortTimersEnable、vMBPortTimersDisable。
- 状态回调:pxMBFrameCBByteReceived、pxMBFrameCBTransmitterEmpty、pxMBPortCBTimerExpired。