Modbus协议的相关知识
协议概要
简而言之,Modbus协议是一种单主/多从的通讯协议,其特点是在同一时间总线上只能有一个主设备,但可以有一个或者多个(最多247个)从设备。Modbus通信总是由主设备发起,当从设备没有收到来自主设备的请求时,不会主动发送数据。从设备之间不能相互通信,主设备同时只能启动一个Modbus访问事务处理。
主设备可以采用两种方式向从设备发送Modbus请求报文,即主设备可以对指定的单个从设备或者线路上所有的从设备发送请求报文,而从设备只能被动接收请求报文后给出响应报文即应答。
- 单薄模式:主设备仅仅寻址单个从设备。从设备接收并处理完请求之后,向主设备返回一个响应报文,即应答。在这种模式下,一个Modbus事务处理包含两个报文:一个是主设备的请求报文,一个是从设备的响应报文。
每个从设备必须有唯一的地址(地址范围1~247),这样才能区别于其他从设备从而可以独立被寻址,而主设备不占用地址。
- 广播模式:此种模式下,主设备可以向所有的从设备发送请求指令。而从设备在接收到广播指令后,仅仅进行相关指令的事务处理而不要求返回应答·基于此,请求指令必须是Modbus标准功能中的写指令。
根据Modbus标准协议的要求,所有从设备必须接收广播模式下的写指令,且地址0被保留用来识别广播通信。
请求
主设备发送的请求报文主要包括从设备地址(或者广播地址0)、功能码、传输的数据以及差错检验字段。
查询消息中的功能码告知被选中的从设备要执行何种功能。数据段包含了从设备要执行功能的任何附加信息。例如功能代码03元哦求从设备读保持寄存器并返回其内容。
数据段必须包含要告知从设备的信息:从何寄存器开始读取及要读取的寄存器数量。差错检测域为从设备提供了一种验证消息内容是否正确的方法。
应答
从设备的应答报文包括地址、功能码、差错检测域等。
如果从设备产生一个正常的回应,则在回应消息中的功能码是在查询消息中的功能码的回应。数据段包括了从设备手机的数据,如寄存器值或状态。如果有错误发生,功能代码将被修改以用于指出回应消息是错误的,同时数据段包含了描述此错误信息的代码。差错检测域允许主设备确认消息内容是否可用。
对于串行链路来说,又存在两种传输帧模式ASCII模式和RTU模式。但是对于同一网络或链路来说,所有的设备必须保持同一,要么统一为ASCII模式,要么同一为RTU模式,不可共存。相对来说,RTU模式传输效率更高,因此,在当前普通的生产环境中RTU模式获得了广泛应用,而ASCII模式只作为特殊情况下的可选项。
Modbus寄存器
寄存器种类说明
Modbus协议中一个重要的概念是寄存器,所有的数据均存放于寄存器中。Modbus寄存器更具存放的数据类型以及各自读写特性,将寄存器分为4个部分,这4个部分可以连续也可以不连续,由开发者决定。寄存器的意义如下:
寄存器种类 | 说明 | 与PLC类比 | 举例说明 |
---|---|---|---|
线圈状态 (Coil Status) |
输出端口 可设定端口的输出状态,也可以读取该位的输出状态。可分为两种不同的执行状态,例如保持型或边缘触发型 |
DO(数字量输出) | 电磁阀输出,MOSFET输出、LED显示等 |
离散输入状态 (Input Status) |
输入端口 通过外部设定改变输入状态,可读但不可写 |
DI(数字量输入) | 拨码开关、接近开关等 |
保持寄存器 (Holding Register) |
输出参数或保持参数,控制器运行时被设定的某些参数,可读可写 | AO(模拟量输出) | 模拟量输出设定值,PID运行参数,变量阀输出大小,传感器报警上限 |
输入寄存器 (Input Register) |
输入参数 控制器运行时从外部设备获得的参数,可读但不可写 |
AI(模拟量输入) | 模拟量输入 |
寄存器地址分配
Modbus寄存器地址分配如下:
寄存器种类 | 寄存器PLC地址 | 寄存器Modbus协议地址 | 简称 | 读写状态 |
---|---|---|---|---|
线圈状态 | 00001~09999 | 0000H~FFFFFH | 0x | 可读可写 |
离散输入状态 | 10001~19999 | 0000H~FFFFH | 1x | 只读 |
保持寄存器 | 40001~49999 | 0000H~FFFFH | 4x | 可读可写 |
输入寄存器 | 30001~39999 | 0000H~FFFFH | 3x | 只读 |
该表中的PLC地址可以理解为Modbus协议地址的变种,在触摸屏和PLC编程中应用较为广泛。寄存器PLC地址指存放于控制器中的地址,这些控制器可以是PLC,也可以是触摸屏、或是文本显示器。PLC地址一般采用10进制描述,共有5位,其中第一位数字代表寄存器类型。第一位数字和寄存器类型的对应关系表3-2所示。例如,PLC地址40001、30002等。
寄存器Modbus协议地址指的是通信时使用的寄存器寻址地址,例如PLC地址40001对应寻址地址时0x0000,40002对应地址0x0001,寄存器寻址地址一般使用16进制描述。再如,PLC寄存器地址40003对应的协议地址时0x0002,PLC寄存器地址30003对应的协议地址也是0x0002,虽然两个PLC寄存器通信时使用相同的Modbus协议地址,但是因为不同寄存器的功能码不同,需要使用不同的命令访问,所以访问时不存在冲突。
Modbus 串行消息帧格式
Modbus ASCII或RTU模式仅适用于标准的Modbus协议串行网络,它定义了在这些网络上连续传输的消息段的每一个字节,以及决定怎样将消息打包成消息域或如何解码等功能。
ASCII消息桢格式
当控制器设为在Modbus网络上以ASCII模式通信时,在消息中每个8位(bit)的字节都将作为两个ASCII字符发送。这种方式的主要优点是字符发送的时间间隔可达到1秒而不产生错误。
在ASCII模式下,消息以冒号(:)字符(ASCII码0x3A)开始,以回车换行符结束(ASCII码0x0D,0x0A)消息帧的其他字段(域)可以使用的传输字符是十六进制的0·····9,A·····F。处于网络上的Modbus设备不断侦测“:”字符,当有一个冒号接收到时,每个设备进入解码阶段,并解码下一个字段(地址域)来判断是否是发给自己的。消息桢中的字符间发送的时间间隔最长不能超过1秒,否则接收的设备将认为发生传输错误。
一个典型的ASCII消息帧格式如下:
起始 | 地址 | 功能码 | 数据 | LRC校验 | 结束 |
---|---|---|---|---|---|
1字符 : |
2字符 | 2字符 | 0~2*252字符 | 2字符 | 2字符 CR,LF |
RTU消息帧格式
传输设备(主/从设备)将Modbus报文放置在带有已知起始和结束点的消息中,这就要求接收消息帧的设备在报文的起始处开始接收,并且要知道报文传输合适结束。两外还必须能够检测到不完整的报文,且能够清晰地设置错误标志。
在RTU模式中,消息的发送和接收以至少3.5字符时间的停顿间隔为标志。实际使用中,网络设备不断侦测网络总线,计算字符间的停顿间隔时间,判断消息帧的起点。当接收到第一个域(地址域)时,每个设备都进行解码以判断是否是发给自己的。在最后一个传输字符结束之后,一个至少3.5个字符时间的停顿标定了消息的结束,而一个新的消息可在此停顿后开始。另外,在一帧报文中,必须以连续的字符流发送整个报文帧。如果两个字符之间的空闲间隔大于1.5个字符时间,那么认为报文帧不完整,该报文将被丢弃。
3.5报文需要记住的有:
- 3.5时间间隔目的是作为区别前后两帧数据的分隔符
- 3.5时间间隔只对RTU模式有效
Modbus通信时规定主机发送完一组命令必须间隔3.5个字符再发送下一组新命令,这3.5个字符主要用来告诉其他设备这次命令(数据)已结束。这3.5个字符的时间间隔采用以下方式计算:
通常情况下再串行通信中,1个字符包括1位起始位、8位数据位、1位校验位(或者没有)、1位停止位(一般情况下)。这样一般情况下1个字符就包括11位,那么3.5个字符就是3.5*11=38.5位。
而串行通信中波特率的含义时每秒传输的二进制位的个数。例如波特率为9600bps,则意义就是说每1S(也就是1000ms)传输9600个位的数据;反过来说传输9600个二进制位的数据需要1000ms,那么传输38.5个二进制位的数据需要的时间就是:38.5*(1000/9600)=4.0104167ms
Modbus RTU要求相邻两帧数据的起始和结束之间至少有大于等于3.5个字符的时间间隔,那么在波特率位9600bps的情况下,只要大于4.0104167即可。
注意:为了实现RTU通信中的时间间隔管理,定时器将引起大量中断处理,在较高的通信波特率下,这将导致CPU的沉重负担。为此协议规定当波特率等于或低于19200bps时,需要严格遵守时间间隔;而在波特率大于19200bps的情况下,时间间隔使用固定值。建议1.5个字符时间间隔位750us,帧时间间隔位1750us。
地址域
所谓地址域,指的是Modbus通信帧中地址字段,其内容为从设备地址。Modbus消息帧的地址域包括2个字符(ASCII模式)或者1个字节(RTU模式)。
消息帧中可能的从设备地址时0247(十进制)单个设备的实际地址范围是1247.主设备通过将要联络的从设备的地址放入消息中的地址域域来选通从设备。当从设备发送回应消息时,它把自己的地址放入回应的地址域中,以便知道是哪一个设备做出回应。
地址0用作广播地址,以使所有的从设备都能认识。当Modbus协议用于更高级别的网络时,广播方式可能不被允许或以其他方式替代。
0 | 1~247 | 248~255 |
---|---|---|
广播地址 | 从站地址 | 保留 |
功能码域
功能码在Modbus协议中用于表示消息帧的功能。
功能码域由1个字节构成,因此其取值范围为1~255(十进制)。例如,常用的功能码有03、04、06、16等,其中03的功能码的作用是读保持寄存器内容,04功能码的作用是读输入寄存器内容,06功能码的内容是预置单个保存寄存器,16功能码的内容则是预置多个保持寄存器。
从设备根据功能码执行对应的功能,执行完成后,正常情况下则在返回的响应消息帧中设置同样的功能码;如果出现异常,则在返回的消息帧中将功能码最高位(MSB)设置为1,主设备可获知对应从设备的执行情况。
另外,对于主设备发送的功能码,则从设备根据具体配置来决定是否支持此功能码。如果不支持,则返回异常响应。
数据域
数据域与功能码紧密相关,存放功能码需要操作的具体数据。数据域以字节为单位,长度是可变的,对于有些功能码,数据域可以为空。
Modbus差错校验
在Modbus串行通信中,根据传输模式(ASCII或RTU)的不同,差错校验域采用了不同的校验方法。
ASCII模式
报文包含一个错误校验字段。该字段由两个字符组成,其值基于对全部报文内容执行的纵向冗余校验,计算的结果而来,计算对象不包括起始的冒号(:)和回车换行符。
RTU模式
报文包含一个错误校验字段。与ASCII模式不同的是,该字段由16个比特位共两个字节组成。其值基于对全部报文内容执行的循环冗余校验计算结果而来,计算对象包括校验域之前的所有字节。
LRC校验
在ASCII模式中,消息是由特定的字符作为帧头和帧尾来分隔的。
一条消息必须以“冒号”(:)字符(ASCII码为0x3A)开始,以“回车换行”(CRLF)(ASCII码为0x0D和0x0A)结束。LRC校验算法的计算范围为(:)与(CRLF)之间的字符。
从算法本质来说,LRC域自身为1个字节,即包含一个8位二进制数据,由发送设备通过LRC算法计算,并把计算值附到信息末尾。接收设备在接收信息时,通过LRC算法重新计算值,并把计算值与LRC字段中接收的实际值进行比较。若两者不同,则产生一个错误,返回一个异常响应帧。即对报文中的所有相邻2个8位字节相加,丢弃任何进位,然后对结果进行二进制补码,计算出LRC值。
必须注意的是,计算LRC校验码的时机,是在对报文中每个原始字节进行ASCII码编码之前,对每个原始字节进行LRC校验的计算操作。
生成LRC校验值的过程如下:
- 对消息帧中的全部字节相加(不包括起始“:”和结束符“CR-LF”),并把结果送入8位数据区,舍弃进位。
- 由0xFF(即全1)减去最终的数据值,产生1的补码(即二进制反码)。
- 加“1”产生二进制补码。
以上产生的LRC值占用1个字节,但实际上在通过串行链路由ASCII模式传递消息帧的时候,LRC的结果(1个字节)被编码为2个字节的ASCII字符,并将其放置在ASCII模式报文帧的CR-LF字段之前。
Modbus标准协议协议英文版提供了LRC的算法。其中参数意义如下:
unsigned char * auchMsg;含有生成LRC所使用的二进制数据的报文缓存区指针。
unsigned short usDataLen;报文缓存区中的字节数。
CRC校验
在ModbusRTU传输模式下,通信报文(帧)包括一个基于循环冗余校验(CRC)方法的差错校验字段。
CRC的全称是循环冗余校验,其特点是检错能力极强,开销小,易于用编码器及检测电路实现。从其检错能力来看,它不能发现的错误的机率在0.0047%以下,在Modbus通信中基本可以忽略。CRC校验包括多个版本,常用的CRC校验有CRC-8、CRC-12、CRC-16、CRC-CCITT、CRC-32等。
从性能和开销考虑,CRC校验均远远优于奇偶校验及算数和校验等方式,因而在数据储存和数据通信领域,CRC无处不在。
而在Modbus协议中,则采用了CRC-16标准校验方法。在RTU模式下,CRC自身由两个字节组成,即CRC是一个16位的值。CRC字段校验整个报文的内容,无论报文中的单个字节采用何种奇偶校验方式,整个通信报文均可应用CRC16校验算法。CRC字段作为报文的最后字段添加到整个报文末尾。
有一点需要注意,因为CRC-16由两个字节构成,所以设计那个字节放在前面,那个字节放在后面传输的问题,即大小端模式的选择问题。
接收设备在接收信息时,会通过CRC算法重新计算,并把计算值与CRC字段中接收的实际值进行比较。若两者不同,则产生一个错误,并返回一个异常响应报文(帧)告知发送设备。
Modbus协议中的RTU校验码(CRC)计算,运算规则(即CRC计算方法)如下:
- 预置一个值位0xFFFF的16位寄存器,此寄存器位CRC寄存器
- 把第1个8位二进制数据(即通信消息帧的第一个字节)与16位的CRC寄存器的相异或,异或的结果仍存在于该CRC寄存器中。
- 把CRC寄存器的内容右移一位,用0填补最高位,并检测移出位是0还是1
- 如果移出位为零,则重复步骤(3)(再次右移一位);如果移出位位1,则CRC寄存器与0xA001进行异或
- 重复步骤(3)和(4),直到右移8次,这样整个8位数据全部进行了处理。
- 重复步骤(2)~(5),进行通信消息帧下一个字节的处理。
- 将该通信消息帧所有字节按上述步骤计算完成后,得到的16位CRC寄存器的高、低字节进行交换。即发送时首先添加低位字节,然后添加高位字节。
- 最后得到的CRC寄存器内容即为CRC校验码
在CRC计算时只有串行链路上每个字符中的8个数据为参与计算,而其他比如起始位及停止位,如有奇偶校验位也包括奇偶校验位,都不参与CRC计算。
常用的CRC-16查表法和计算法
-
查表法
CRC查表法是将移位异或的计算机结果做成了一个表,就是将0~256放入一个长度为16的寄存器中的低8位,高8位填充0,然后将该寄存器与多项式0xA001按照上述步骤(3)、(4),直到8位全部移出,最后寄存器中的值就是表格中的数据,高8位、低8位分别单独一个表。
实际上,Modbus标准协议英文版提供了CRC查表算法。
函数的输入参数意义如下:
unsingned char puchMsg /要进行CRC校验的消息*/
unsingned short usDataLen /消息中字节数/
查表法的特点是:以字节为单位进行计算,速度快,语句少,但是表格占用一定的程序空间。
-
计算法
计算法按位计算。这个方法可以适用于所有长度的数据校验,最为灵活,但由于是按位计算,其效率并不是最优,知识和对速度不敏感的场合。基本的算法如下:
输入参数的意义:
unsingned char *puchMsg 要进行CRC校验的消息
unsingned short usDataLen 消息中的字节数
这里举一个简单的例子。假设从设备地址位1,要求读取输入寄存器地址30001的值,则RTU模式下具体的查询消息帧如下:
0x01,0x04,0x00,0x00,0x00,0x01,0x31,0xCA
其中,0xCA31即为CRC值。因为Modbus规定发送时CRC必须低字节在前,高字节在后,因此实际的消息帧的发送顺序位0x31,0xCA.
字节序和大小端
在学习Modbus协议时,字节序和大小端是一个非常容易忽视而又容易造成困扰的问题。
在计算机里,对于地址的描述,很少用“大”和“小”来形容;对应地,用的更多的是“高”和“低”;很不幸,这对属于直接按字面翻译过来就成了“大端”和“小端”。
为什么会有大小端
这是因为在计算机系统中,是以字节为单位的,每个地址单元都对应着一个字节。一个字节位8位(bit)。在C语言中除了8位的char型,还有16位的short型,32位的long型(要看具体的编译器)。另外,对于位数大于8位的处理器,例如16位或32位处理器,由于寄存器宽度大于一个字节,那么必然存在着一个如何将多个字节安排的问题。因此就导致了大端存储模式和小端存储模式。
例如一个16位的short型x,在内存中的地址位0x0010,x的值位0x1122,那么0x11为高字节,0x22为低字节。对于大端模式,就将0x11放在低地址中,即0x0010中;0x22放在高地址中,即0x0011中。对于小端模式,刚好相反。常用的X86结构是小端模式,而KEIL C51则为大端模式。很多ARM、DSP都为小端模式,有些ARM处理器还可以由硬件来选择是大端模式还是小端模式。
什么是“大端”和“小端”
大端模式,是指数据的低位保存在内存的高地址中,数据的高位保存在内存的低地址中。
小端模式,是指数据的低位保存在内存的低地址中,而数据的高位保存在内存的高地址中。
实际上,Modbus协议中规定一个寄存器占用16位即2个字节长度,因此开发之前有必要搞清楚系统的大小端模式和字节序。
对于32位的整数或者实数来说,存在以下4中不同的字节序(ABCD代表各字节)
- Long(float)AB CD
- Long(float)CD AB
- Long(float)BA DC
- Long(float)DC BA
例如:若系统采用字节序为AB CD,则十进制整数123456789(其十六进制为 07 5B CD 15)在Modbus消息中发送顺序为07 5B CD 15;而十进制实数123456.00(其十六进制为47 F1 20 00),在Modbus消息中发送顺序为47 F1 20 00
而对于64位的双进度实数来说,也存在以下4中不同的字节序:
- Double AB CD EF GH
- Double GH EF CD AB
- Double BA DC FE HG
- Double HG FE DC BA
例如:若采用字节序AB CE EF GH,则双精度实数123456789.00(其十六进制为 41 9D 6F 34 54 00 00 00)在Modbus消息中发送顺序为41 9D 6F 34 54 00 00 00.
Modbus TCP消息帧格式
协议描述
在Modbus TCP/IP协议中,串行链路中的主/从设备分别演变成客户端/服务器端设备,即客户端相当于主站设备,服务器端相当于从站设备。基于TCP/IP网络的传输特性,串行链路上一主多从的结构也演变为多客户端/多服务器端的结构模型。Modbus协议在TCP/IP上的实现是在TCP/IP协议层上的应用,它需要一个完整的TCP/IP协议栈作为支撑,Modbus TCP/IP服务器端通常使用端口502作为接收报文的端口。
为了便于传输或者提取各报文、保证报文传输的完整性,Modbus协议在应用数据单元(ADU)中引入了附加字段。如串行链路中,针对ASCII模式分别引入了(:)作为报文分隔标记,并引入LRC作为错误校验;而针对RTU模式则引入了T3.5时间间隔作为报文分隔,引入CRC作为错误校验。
同样地,在TCP/IP网络上的Modbus协议也需要引入一个称为MBAP报文头的字段。
Modbus TCP/IP 协议最大帧数据长度为260字节,其中字节0~6构成MBAP报头,各字段意义如下:
字节 | 字段名 | 说明 | 客户端 | 服务器端 |
---|---|---|---|---|
0 1 | Transaction Identifier 传输标识 |
标记某个Modbus查询/应答的传输过程,可以设置为0,也可以设置为每次通信时自动+1 | 由客户端生成 | 应答时复制该值 |
2 3 | Protocol Identifier 协议标识 |
Modbus协议=0x00标记Modbus协议,设置为0x00 | 由客户端生成 | 应答时复制该值 |
4 5 | Length 字节长度Hi 字节长度Lo |
设置为0x00,因此后续字节必须在256字节以内 记录后续字节的个数 |
由客户端生成 | 应答时重新生成 |
6 | Unit Identifier 单元标识符 |
用以识别从机设备,例如可以设置为从机设备的地址 | 由客户端生成 | 应答时复制改值 |
- 传输标识
传输标识用于将查询报文与未来响应之间建立联系。因此,对TCP/IP连接来说,在同一时刻,这个标识符必须是唯一的。有几种使用此标识符的方式:
例如可以将传输标识作为一个带有计数器的简单“TCP发送顺序号”,在每一个请求发送时自动+1;也可以用作智能索引或指针,来识别事务处理的内容,以便记忆当前的远端服务器和未处理的请求。
服务器端可接受的请求数量取决于其容量,即:服务器资源量和TCP窗口尺寸。同样,客户端同时启动事务处理的数量也取决于客户机的资源容量。
- 单元标识符
在对Modbus或Modbus+等串行链路子网中的设备进行寻址时,这个域是用于路由的目的。在这种情况下,“Unit Identifier”携带一个远端设备的Modbus从站地址。
如果Modbus服务器连接到Modbus+或Modbus串行链路子网,并通过一个网桥或网关后的子网的从站设备时必须的。TCP连接中的目的IP地址识别了网桥本身的地址,而网桥则使用Modbus单元标识将请求转交给正确的从站设备。
分配给串行链路上的Modbus从站设备地址为1~247(十进制),地址0作为广播地址。
对单纯的Modbus TCP/IP设备来说,利用IP地址即可寻址Modbus服务器端设备,此时Modbus单元标识符是无用的,必须使用值0xFF填充。当对直接连接到TCP/IP网络上的Modbus服务器寻址时,建议不要在“单元标识符”域使用有效的Modbus从站地址。
以上时MBAP报头个字段含义的详细说明。
实际上在Modbus TCP/IP传输过程中,服务端(从机)返回的响应报文中同样包含MBAP报头,除了Length字段外,其他字段与客户端一致。Modbus消息帧由TCP/IP层提供,不需要像串行链路那样自己判断一帧是否结束,所有数据传输由TCP/IP层处理。因为底层TCP/IP协议确保了端到端的连接,而且TCP/IP链路层已确保传输数据的准确性,所以Modbus TCP/IP协议中已不再需要LRC或CRC等校验功能。
查询与响应报文例
对于Modbus TCP消息帧格式,举例说明各部分的含义:
-
查询报文:00 00 00 00 00 06 09 03 00 04 00 01
- 0x06:后续还有6个字节
- 0x09:单元标识符为9
- 0x03:功能码3,即读保持寄存器的值
- 0x00 0x04 :Modbus 起始地址4(即40005)
- 0x00 0x01:读取寄存器个数为1
-
响应报文:00 00 00 00 00 05 09 03 02 00 05
- 0x05:表示后续还有5个字节
- 0x09:同查询报文,单元标识符
- 0x03:功能码,同查询报文
- 0x02:返回数据字节数
- 0x00 0x05:寄存器的值
可见在Modbus TCP模式下,差错校验字段已不复存在。但在某些特殊场合,例如串行Modbus协议转Modbus TCP的情况下,串行协议数据可以完整地装载到Modbus TCP协议的数据字段,这时CRC或者LRC差错校验字段仍然存在。例如:Modbus RTU Over TCP/IP 或Modbus ASCII Over TCP/IP等。