在学习modbus协议之前,读者最好能够具备有串行通信的基础知识。串行通信是指数据以串行的方式传输的一种通信,即仅使用一条数据线,将数据一位一位地依次传输,每一位数据占据一个固定的时间长度。这样,其只需要少数几条线就可以实现设备间的数据交互。像UART、IIC、SPI等都是使用串行通信方式。
从我们最熟悉的单片的的UART开始说起,我们知道,两个单片机之间通过串口电路,并且遵循已经制定好的“串口通信协议”,就可以实现数据交互。但是这种单片机之间直接通过导线相连的方式并没那么可靠,因为导线中的电平容易收到外界的干扰,可能会导致信息传输错误。为了解决这个问题,就出现了RS-485传输标准。如图是RS-485电路的示意图,MCU_1发送端产生的TTL电平经过电平转换电路之后,变成AB两根导线之间的差分信号。当MCU给转换器输入低电平时,转换器会使得B的电压比A的电压高,反之,A的电压比B的电压高。之后,再通过一个电平转换电路,将差分信号转换成MUC_2能够识别的TTL信号。
RS-485传输方式具有如下特性:
1)差分传输增加噪声抗扰度,减少噪声辐射;
2)长距离链路,最长可达4000英尺(约1219米);
3)数据速率高达10Mbps(40英寸内,约12.2米);
4)同一总线可以连接多个驱动器和接收器;
5)宽共模范围允许驱动器和接收器之间存在地电位差异,允许最大共模电压-7-12V
我们由此可以看出,RS-485电路是单片机串口的拓展,可以提高通信电路的抗干扰能力,增加传输距离。
到此,已经解决了设备之间的数据传输问题,但是显然还有一些问题没有考虑,第一,如果通信网络上有多个设备,发送设备要如何指定哪一个设备要接收这条消息呢?第二,接收设备接收到一条消息之后,它要如何解析这条消息呢?要想解决这些问题,设备厂家们可以定义一种在UART协议之上的“上层协议”来约定一些规则。但是如果每个厂家都设计了不同的协议,那么不同厂家的设备之间有存在通信的问题。由此,Modbus协议的意义就显现出来了。
Modbus通信协议由Modicon公司(现在的施耐德电气Schneider Electric)于1979年为可编程逻辑控制(即PLC)通信而发表。目前,Modbus已经成为工业领域通信协议的业界标准,并且现在是工业电子设备之间常用的连接方式。Modbus作为目前工业领域应用最广泛的协议,与其他通信协议相比,有以下特点:
1. Modbus协议标准开放、公开发表且无版权要求。
2.Modbus协议支持多种电气接口,包括RS232、RS485、TCP/IP等,还可以在各种介质上传输,如双绞线、光纤、红外、无线等。
3.Modbus协议消息帧格式简单、紧凑、通俗易懂。用户理解和使用简单,厂商容易开发和集成,方便形成工业控制网络。
此协议定义了一个控制器能够认识的消息结构,而不管它们是经过何种网络进行通信的;而且描述了控制器请求访问其他设备的过程,如何应答来自其他设备的请求,以及怎样侦测错误并记录;并制定了统一的消息域结构和内容。
Modbus协议是一种单主/多从的通信协议。在同一时间,总线上只能有一个主设备,但可以有一个或者多个从设备(最多247个)。Modbus通信总是由主设备发起,从设备没有收到来自主设备的请求时,不会主动发送数据。从设备之间不能相互通信。
主设备可以采用两种方式向从设备发送modbus请求:
单播模式:主设备仅仅寻址单个从设备,从设备接收并处理完请求后,向主设备返回一个响应报文。
广播模式:主设备向总线上所有从设备发送请求,而从设备收到请求指令后,仅进行相关的事物处理而不返回应答。因此,广播模式下,请求指令只能是modbus标准功能中的写指令,而不是读指令。地址0被保留作为广播地址。
为了寻求一种简洁的通信格式,Modbus协议定义了PDU(Protocol Data Unit)模型,即“功能码+数据帧”格式;而为了适应多种传输模式,在PDU的基础上增加了必要的前缀(如地址域)和后缀(如差错校验),形成了ADU(Application
Data Unit)模型。
图2-1 通用消息帧格式
Modbus协议包含ASCII、RTU、TCP三种传输模式。当使用串口传输数据时可以选择ASCII或RTU模式。对于同一网络,所有设备必须保持统一的模式。不同模式中的消息帧格式有所不同。
在ASCII模式中,消息中每个字节都将作为两个ASCII字符发送,如0x53要分成0x35(5的ASCII码)和0x33(3的ASCII码)两个字节发送,因此传输效率较低。
在该模式中,消息帧以冒号(:)字符(ASCII码为0x3A)开始,以回车换行符(ASCII码为0x0A,0x0D)结束。消息帧的其他字段可以使用的字符是十六进制的0…9,A…F。处于网络上的设备不断侦测“:”字符,当有一个冒号收到时,每个设备进入解码阶段,并解码下一个字段(地址域),判断消息是不是发给自己的。消息帧的字符间发送时间间隔不能超过1秒,否则接收设备认为是发生传输错误。
一个典型的ASCII消息帧格式如表所示:
在RTU模式中,消息帧不存在起始字符和结束字符,而是以T3.5作为两帧数据的分隔标志。在实际使用中,网络中的设备不断侦测字符间的停顿时间间隔,判断消息帧的起始点。当收到第一个字节(地址域)时,判断消息是不是发给自己的。在最后一个字符传输结束之后,一个至少T3.5的停顿代表消息的结束,而一个新的消息可以在此停顿后开始。另外,在一帧数据中,如果两个字符之间的空闲间隔大于T1.5,那么认为报文不完整,该报文将被丢弃。
T3.5即3.5个字符时间,在串行通信中,1个字符通常包括1位起始位、8位数据位、1位奇偶校验位、1位停止位。这样,一个字符就包括11位,那么3.5个字符就是38.5位。
在串行通信中,波特率代表每秒传输二进制位的个数,如波特率为9600bps代表每秒传输9600bit。那么传输38.5bit的时间为
(38.5/9600)*1000ms = 4.0104167ms
在波特率为9600bps的情况下,ModbusRTU要求“前一帧数据的结束”和“后一帧数据的开始”的时间间隔大于4.0104167ms。
为了实现RTU通信中的时间间隔管理,定时器将引起大量的中断处理,在较高波特率的情况下,这将导致CPU的沉重负担。为此,协议规定当波特率大于19200bps的情况下,时间间隔使用固定值,建议T1.5为750us,T3.5为1750us。
地址码是信息帧的第一个字节(8位),从0到255。每个从机都必须有唯一的地址。地址的作用有3个:第一,当主机下发指令时,可以有针对性的指定哪个从机接受指令;第二,从机回复消息时,可以让主机知道该信息来自于何处;第三,从机回复消息时,其他从机不会误认为是主机下发的请求。
如果地址为0,则认为是一个广播命令,就是所有从机要接收并处理主机发来的信息。
表2-2 Modbus寻址范围
功能码域由一个字节构成,其取值范围为1到127。从设备根据功能码执行相应的动作,执行完成后,正常情况下,从机回应的功能码与主机下发的功能码一样。如果出现异常,则在返回消息帧中将功能码最高位置1(129~255代表异常码)。据此,主设备可以获取从设备的执行情况。
另外,对于主设备发送的功能码,从设备根据具体配置来决定是否支持此功能码,如果不支持,则返回异常响应。
Modbus协议规定了三类功能码,分别是:
公共功能码
1)被明确定义的功能码;
2)保证唯一性;
3)由Modbus协会确认,并提供公开文档;
4)可进行一致性测试;
5)包括协议定义的功能码和保留将来使用的功能码
用户自定义的功能码
1)有两个用户自定义的功能码区域,分别是65~72和100~110;
2)用户自定义,不保证唯一性。
保留功能码
保留的功能码是因为历史遗留原因,某些公司传统产品上现行使用的功能码不作为公共使用。
Modbus部分功能码如表所示:
表2-3 Modbus部分功能码
代码 | 中文名称 | 寄存器PLC地址 | 位/字操作 | 操作数量 |
---|---|---|---|---|
01 | 读线圈状态 | 00001-09999 | 位操作 | 单个或多个 |
02 | 读离散输入状态 | 10001-19999 | 位操作 | 单个或多个 |
03 | 读保持寄存器 | 40001-49999 | 字操作 | 单个或多个 |
04 | 读输入寄存器 | 30001-39999 | 字操作 | 单个或多个 |
05 | 写单个线圈 | 00001-09999 | 位操作 | 单个 |
06 | 写单个保持寄存器 | 40001-49999 | 字操作 | 单个 |
15 | 写多个线圈 | 00001-09999 | 位操作 | 多个 |
16 | 写多个保持寄存器 | 40001-49999 | 字操作 | 多个 |
功能码可以分为位操作和字操作两类。位操作的最小单位为BIT,字操作的最小单位为两个字节。
1)位操作指令:读线圈状态01H,读(离散)输入状态02H,写单个线圈06H和写多个线圈0FH。
2)字操作指令:读保持寄存器03H,写单个寄存器06H,写多个保持寄存器10H。
表2-4 MODBUS寄存器地址分配
寄存器PLC地址 | 寄存器协议地址 | 适用功能 | 寄存器种类 | 读写状态 |
---|---|---|---|---|
00001-09999 | 0000H-FFFFH | 01H05H0FH | 线圈状态 | 可读可写 |
10001-19999 | 0000H-FFFFH | 02H | 离散输入状态 | 可读 |
30001-39999 | 0000H-FFFFH | 04H | 输入寄存器 | 可读 |
40001-49999 | 0000H-FFFFH | 03H06H0FH | 保持寄存器 | 可读可写 |
表2-5 MODBUS寄存器种类说明
寄存器种类 | 说明 | PLC类比 | 举例说明 |
---|---|---|---|
线圈状态 | 输出端口。可设定端口的输出状态,也可以读取该位的输出状态。可分为两种不同的执行状态,例如保持型或边沿触发型。 | DO数字量输出 | 电磁阀输出,MOSFET输出,LED显示等。 |
离散输入状态 | 输入端口。通过外部设定改变输入状态,可读但不可写。 | DI数字量输入 | 拨码开关,接近开关等。 |
保持寄存器 | 输出参数或保持参数,控制器运行时被设定的某些参数。可读可写。 | AO模拟量输出 | 模拟量输出设定值,PID运行参数,变量阀输出大小,传感器报警上限下限。 |
输入寄存器 | 输入参数。控制器运行时从外部设备获得的参数。可读但不可写。 | AI模拟量输入 | 模拟量输入 |
PLC地址可以理解为协议地址的变种,在触摸屏和PLC编程中应用较为广泛。
1)寄存器PLC地址
寄存器PLC地址指存放于控制器中的地址,这些控制器可以是PLC,也可以是触摸屏,或是文本显示器。PLC地址一般采用10进制描述,共有5位,其中第一位代码寄存器类型。第一位数字和寄存器类型的对应关系如表2-4所示。PLC地址例如40001、30002等。
2)寄存器协议地址
寄存器协议地址指通信时使用的寄存器地址,例如PLC地址40001对应寻址地址0x0000,40002对应寻址地址0x0001,寄存器寻址地址一般使用16进制描述。再如,PLC寄存器地址40003对应协议地址0002,PLC寄存器地址30003对应协议地址0002,虽然两个PLC寄存器寄存器通信时使用相同的地址,但是需要使用不同的功能码访问,所以访问时不存在冲突。
数据域与功能码紧密相关,存放功能码需要操作的具体数据,数据域以字节为单位,长度不是固定的,对于有些功能码,数据域可以为空。
校验码由发送设备计算,放置于发送消息帧的尾部。接受消息的设备再重新计算接收到的信息的校验码,比较计算得到的校验码是否与接收到的相符,如果不相符,则表明出错。它用于保证主机或从机对传送过程中出错的信息起不了作用,增加了系统的安全与效率。
在串行通信中,根据传输模式(RTU或ASCII)的不同,采用了不同的校验方法。
在ASCII模式中,校验域由两个字符组成,其值基于对全部报文内容执行纵向冗余校验(LRC)计算的结果而来,计算对象不包括冒号和回车换行符。
在RTU模式中,校验域由16bit即两个字节构成,其值基于对全部报文内容执行循环冗余校验(CRC)计算的结果而来,计算对象包括校验域之前所有字节。
产生CRC-16码的步骤如下:
1)预置一个16位的寄存器为全1(即十六进制FFFFH),称此寄存器为CRC寄存器;
2)把第一个8位数据与CRC寄存器的低8位相异或,结果放回CRC寄存器;
3)把16位CRC寄存器右移一位,用0添补最高位,检测移出位:
4)如果移出位为0,则重复第3步骤(再次移出);如果移出位为1,则CRC寄存器与多项式A001H相异或,结果放回CRC寄存器;
5)重复第3、4步骤,直至移出8位;
6)将下一个8位数据与CRC寄存器低8位相异或,结果放回CRC寄存器,重复第2、3、4、5步骤;
7)消息帧中所有字节按照上述步骤计算完成后,当放置CRC值于报文时,必须将得到的16位CRC寄存器高、低字节进行交换。
8)最后得到的CRC寄存器内容即为产生的CRC校验码。
有了以上理论基础之后,下面针对各个功能码进行详细分析。
发送报文格式如下:
从站地址 | 功能码 | 起始(高) | 起始(低) | 数量(高) | 数量(低) | 校验 |
---|---|---|---|---|---|---|
0x01 | 0x01 | 0x00 | 0x13 | 0x00 | 0x1B | XXXX |
发送报文含义:读取1号从站输出线圈,起始地址为0x13=19,对应寄存器PLC地址为00020,线圈数量为0x1B=27,即读取1号从站输出线圈,地址从00020-00046,共27个线圈的状态值。
返回报文格式如下:
从站地址 | 功能码 | 字节计数 | 字节1 | 字节2 | 字节3 | 字节4 | 校验 |
---|---|---|---|---|---|---|---|
0x01 | 0x01 | 0x04 | 0xCD | 0x6B | 0xB2 | 0x05 | XXXX |
返回报文含义:返回1号从站输出线圈00020-00046,共27个线圈的状态值,返回字节数为4个,分别为CD 6B B2 05。
CD=1100 1101 对应 00020-00027 6B=01101011 对应 00028-00035
B2=1011 0010 对应 00036-00043 05=00000101 对应 00044-00046
发送报文格式如下:
从站地址 | 功能码 | 起始(高) | 起始(低) | 数量(高) | 数量(低) | 校验 |
---|---|---|---|---|---|---|
0x01 | 0x02 | 0x00 | 0xC4 | 0x00 | 0x1D | XXXX |
发送报文含义:读取1号从站输入线圈,起始地址为0xC4=196,对应地址为10197,线圈数量为0x1D=29,即读取1号从站输入线圈,地址从10197-10225,共29个线圈的状态值。
返回报文格式如下:
从站地址 | 功能码 | 字节计数 | 字节1 | 字节2 | 字节3 | 字节4 | 校验 |
---|---|---|---|---|---|---|---|
0x01 | 0x02 | 0x04 | 0xCD | 0x6B | 0xB2 | 0x05 | XXXX |
返回报文含义:返回1号从站输入线圈10197-10225,共29个线圈的状态值,返回字节数为4个,分别为CD 6B B2 05。
CD=1100 1101 对应 10197-10204 6B=01101011 对应 10205-10212
B2=1011 0010 对应 10213-10220 05=00000101 对应 10221-10225
发送报文格式如下:
从站地址 | 功能码 | 起始(高) | 起始(低) | 数量(高) | 数量(低) | 校验 |
---|---|---|---|---|---|---|
0x01 | 0x03 | 0x00 | 0x6B | 0x00 | 0x02 | XXXX |
发送报文含义:读取1号从站保持寄存器,起始地址为0x6B=107,对应地址为40108,寄存器数量为0x02=2,即读取1号从站保持寄存器,地址从40108-40109,共2个寄存器的数值。
返回报文格式如下:
从站地址 | 功能码 | 字节计数 | 1高 | 1低 | 2高 | 2低 | 校验 |
---|---|---|---|---|---|---|---|
0x01 | 0x03 | 0x04 | 0x02 | 0x2B | 0x01 | 0x06 | XXXX |
返回报文含义:返回1号从站保持寄存器40108-40109,共2个寄存器的数值,返回字节数为4个,分别为02 2B 01 06,40108对应数值为0x022B,40109对应数值为0x0106。
发送报文格式如下:
从站地址 | 功能码 | 起始(高) | 起始(低) | 数量(高) | 数量(低) | 校验 |
---|---|---|---|---|---|---|
0x01 | 0x04 | 0x00 | 0x6B | 0x00 | 0x02 | XXXX |
发送报文含义:读取1号从站输入寄存器,起始地址为0x6B=107,对应地址为30108,寄存器数量为0x02=2,即读取1号从站保持寄存器,地址从30108-30109,共2个寄存器的数值。
返回报文格式如下:
从站地址 | 功能码 | 字节计数 | 1高 | 1低 | 2高 | 2低 | 校验 |
---|---|---|---|---|---|---|---|
0x01 | 0x04 | 0x04 | 0x02 | 0x2B | 0x01 | 0x06 | XXXX |
返回报文含义:返回1号从站输入寄存器30108-30109,共2个寄存器的数值,返回字节数为4个,分别为02 2B 01 06,30108对应数值为0x022B,30109对应数值为0x0106。
发送报文格式如下:
从站地址 | 功能码 | 线圈(高) | 线圈(低) | 断通标志 | 断通标志 | 校验 |
---|---|---|---|---|---|---|
0x01 | 0x05 | 0x00 | 0xAC | 0xFF | 0x00 | XXXX |
发送报文含义:预置1号从站单个线圈的值,线圈地址为0x00AC=172,对应地址为00173,断通标志0xFF00表示置位,0x0000表示复位,即置位1号从站输出线圈00173。
返回报文格式如下:
从站地址 | 功能码 | 线圈(高) | 线圈(低) | 断通标志 | 断通标志 | 校验 |
---|---|---|---|---|---|---|
0x01 | 0x05 | 0x00 | 0xAC | 0xFF | 0x00 | XXXX |
返回报文含义:预置单输出线圈原报文返回。
发送报文格式如下:
从站地址 | 功能码 | 寄存器高 | 寄存器低 | 写入值高 | 写入值低 | 校验 |
---|---|---|---|---|---|---|
0x01 | 0x06 | 0x00 | 0x87 | 0x03 | 0x9E | XXXX |
发送报文含义:预置1号从站单个保持寄存器的值,寄存器地址为0x0087=135,对应地址为40136,写入值为0x039E,即预置1号从站保持寄存器40136值为0x039E。
返回报文格式如下:
从站地址 | 功能码 | 寄存器高 | 寄存器低 | 写入值高 | 写入值低 | 校验 |
---|---|---|---|---|---|---|
0x01 | 0x06 | 0x00 | 0x87 | 0x03 | 0x9E | XXXX |
返回报文含义:预置单保持寄存器原报文返回。
发送报文格式如下:
从站地址 | 功能码 | 起始高 | 起始低 | 数量高 | 数量低 | 字节数 | 字节 | 校验 |
---|---|---|---|---|---|---|---|---|
0x01 | 0x0F | 0x00 | 0x13 | 0x00 | 0x0A | 0x02 | 0xCD00 | XXXX |
发送报文含义:预置1号从站多个线圈的值,线圈地址为0x0013=19,对应地址为00020,线圈数为0x0A=10,写入值为0xCD00,即预置1号从站线圈00020-00027=0xCD=11001101,00028-00029=0x00=0000
0000。
返回报文格式如下:
从站地址 | 功能码 | 起始高 | 起始低 | 数量高 | 数量低 | 校验 |
---|---|---|---|---|---|---|
0x01 | 0x0F | 0x00 | 0x13 | 0x00 | 0x0A | XXXX |
返回报文含义:预置多输出线圈返回报文是在原报文基础上除去字节数及具体字节后返回。
发送报文格式如下:
从站地址 | 功能码 | 起始高 | 起始低 | 数量高 | 数量低 | 字节数 | 字节 | 校验 |
---|---|---|---|---|---|---|---|---|
0x01 | 0x10 | 0x00 | 0x87 | 0x00 | 0x02 | 0x04 | 0x01050A10 | XXXX |
发送报文含义:预置1号从站多个寄存器的值,寄存器地址为0x0087=135,起始地址为40136,寄存器数量为0x02=2,结束地址为40137,写入值为0x0105和0x0A10,即预置1号从站寄存器40136=0x0105,40137=0x0A10。
返回报文格式如下:
从站地址 | 功能码 | 起始高 | 起始低 | 数量高 | 数量低 | 校验 |
---|---|---|---|---|---|---|
0x01 | 0x10 | 0x00 | 0x87 | 0x00 | 0x02 | XXXX |
返回报文含义:预置多保持寄存器返回报文是在原报文基础上除去字节数及具体字节后返回。
存器地址为0x0087=135,起始地址为40136,寄存器数量为0x02=2,结束地址为40137,写入值为0x0105和0x0A10,即预置1号从站寄存器40136=0x0105,40137=0x0A10。
返回报文格式如下:
从站地址 | 功能码 | 起始高 | 起始低 | 数量高 | 数量低 | 校验 |
---|---|---|---|---|---|---|
0x01 | 0x10 | 0x00 | 0x87 | 0x00 | 0x02 | XXXX |
返回报文含义:预置多保持寄存器返回报文是在原报文基础上除去字节数及具体字节后返回。