基础资料
- 概述
- Modbus开发环境
- Modbus协议
- Modbus功能码
- 读取线圈/离散量输出状态:
- 读取离散量输入值:
- 读取保持寄存器值:
- 读取输入寄存器值:
- 写单个线圈或单个离散输出:
- 写单个保持寄存器:
- 诊断功能:
- 获取通信事件计数器:
- 获取通信事件记录:
- 写多个线圈:
- 写多个保持寄存器:
- 报告从站ID:
- libmodbus开发库
一、概述:
Modbus协议是由MODICON在1979年开发的,是全球第一个真正用于工业现场的总线协议,现为施耐德电气公司的一个品牌。施耐德电气已将Modbus协议的所有权移交给分布式自动化接口IDA(Interface for Distributed Automation)组织,并成立Modbus-IDA组织来推动Modbus协议的广泛应用。访问Modbus官方网站http:
Modbus协议已经成为一种通用的工业标准,协议定义了控制器能够识别的消息结构,并描述了控制器请求访问其他设备的过程及如何应答来自其他设备的请求,通过这种协议可以实现控制器之间或控制器经由网络和其他设备之间的通信,这样不同厂商生产的控制设备可以连接成工业网络。
Modbus协议标准公开,无版税要求,用户可以免费获取并使用,不需要交纳许可证费,也不会侵犯知识产权;Modbus支持多种电气接口,如RS232、RS485、TCP/IP等,可以在双绞线、光纤、红外、无线等各种介质上传输;Modbus协议帧格式简单、紧凑,易于用户理解和使用,容易开发和集成,便于组成工业控制网络。
Modbus串行链路连接有RTU模式和ASCII模式两个变种,RTU模式采用二进制表示数据方式,而ASCII模式采用可读的较长的ASCII码表示,RTU模式使用循环冗余校验,ASCII模式采用纵向冗余校验,RTU节点不能与ASCII节点通信。而通过TCP/IP物理层的连接,存在多个变种,不需要校验和计算。上述通信模式,数据模型和功能调用都是相同的,只是传输报文的封装方式不同。
Modbus是一个主/从或客户端/服务器架构的协议,通信网络中有一个节点是Master节点,其他参与通信的节点是Slave节点,每一个Slave设备都有唯一的地址。在串行网络中,只有被指定的主节点可以启动一个命令,在以太网上任何一个设备都能发送一个命令,但通常也只有一个主节点设备用以引导指令。
一个Modbus命令包含了准备执行命令的设备地址,线路上所有设备都会收到命令,但只有指定地址的设备会执行并回应指令,不过地址0为广播指令,所有收到指令的设备都会运行且不回应指令。
二、Modbus开发环境:
为了开发调试Modbus,需要使用工具软件Modbus Poll和Modbus Slave,为了在PC机上通信和调试,另外需要安装虚拟串口软件VSPD(Visual Serial Port Driver)。
虚拟串口软件VSPD下载地址为http://www.eltima.com;Modbus主站设备仿真器软件Modbus Poll下载地址http:
在Windows平台上开发软件,一般都使用Visual Studio套件,可以下载使用免费的社区版,网址https:
三、Modbus协议:
Modbus是一种单主多从的通信协议,在同一时间,总线上只能有一个主设备,但可以有多个从设备,最多247个从设备。Modbus通信总是由主设备发起,当从设备没有收到来自主设备的请求时,不会主动发送数据。从设备之间不能相互通信,主设备同时只能启动一个Modbus访问事务。
1. 单播与广播:
主设备可以采用单播和广播两种方式向从设备发送Modbus请求报文:
· 单播模式:主设备仅仅寻址单个从设备,从设备接收并处理完请求后,向主设备返回一个响应报文,即应答。在这种模式下,一个Modbus事务处理包含两个报文,一个是主设备的请求报文,一个是从设备的响应报文。每个从设备必须有唯一的地址,地址范围1~247,主设备不占用地址。
· 广播模式:主设备可以向所有的从设备发送请求指令,而从设备在接收到广播指令后,仅仅进行相关指令的事务处理,而不返回应答。广播模式下,请求指令必须是Modbus标准的写指令。地址0被保留用来识别广播通信。
2. 请求与应答:
主设备发送的请求报文主要包括从设备地址、功能码、传输的数据以及差错检测字段。消息中的功能码指示被选择设备要执行何种功能,数据段包含了从设备要执行功能的附加信息,差错检验域为从设备提供了验证信息内容是否正确的方法。
从设备应答报文包括地址、功能码、差错检验域等。如果从设备产生一个正常的回应,则在回应消息中的功能代码是主设备报文中的功能代码的回应,数据段包括了从设备收集的数据;如果有错误发生,功能代码将被修改以指出回应消息是错误的,同时数据段包含了描述此错误信息的代码。差错检测域允许主设备确认消息内容是否可用。
对于串行链路来说,存在RTU和ASCII两种帧模式,但是对于同一网络或链路来说,所有设备必须保持统一,不可共存。相对来说,RTU模式传输效率更高,因此得到广泛应用,而ASCII模式只作为特殊情况下的可选项。
3. Modbus寄存器:
Modbus协议中一个重要概念是寄存器,所有数据均存放在寄存器中。最初Modbus借鉴了PLC中寄存器的含义,现在进一步泛化,不再是指具体的物理寄存器,也可能是一块内存区域。Modbus寄存器根据存放的数据类型以及各自读写特性,分为4个部分,这4个部分可以不连续,含义见下表:
寄存器种类 | 说明 | 与PLC类比 | 示例说明 |
---|---|---|---|
线圈状态Coil Status | 输出端口,可读写 | DO数字量输出 | 电磁阀输出、LED显示 |
离散输入状态Input Status | 输入端口,只读 | DI数字量输入 | 拨码开关、接近开关 |
保持寄存器Holding Status | 输出参数或保持参数,可读写 | AO模拟量输出 | PID运行参数、传感器报警限值 |
输入寄存器Input Register | 输入参数,只读 | AI模拟量输入 | 模拟量输入 |
寄存器种类 | 寄存器PLC地址 | 寄存器Modbus地址 | 简称 | 读写状态 |
---|---|---|---|---|
线圈状态Coil Status | 00001~09999 | 0000H~FFFFH | 0x | 可读可写 |
离散输入状态Input Status | 10001~19999 | 0000H~FFFFH | 1x | 只读 |
保持寄存器Holding Status | 40001~49999 | 0000H~FFFFH | 4x | 可读可写 |
输入寄存器Input Register | 30001~39999 | 0000H~FFFFH | 3x | 只读 |
4. Modbus串行消息帧格式:
Modbus串行消息帧格式定义了网络上连续传输的消息段的每一个字节,以及怎样将信息打包成消息域和如何解码等功能。Modbus报文放置在已知起始和结束点的消息帧中,接收消息帧的设备在报文的起始处开始接收,并且要知道报文传输何时结束,并且能够检测到不完整的报文,能清晰地设置错误标志。Modbus消息帧包括RTU模式或ASCII模式。
① ASCII消息帧格式:
当控制器设为在Modbus网络上以ASCII模式通信时,在消息中每个8位的字节都将作为两个ASCII字符发送。这种方式的主要优点是字符发送的时间间隔可达到1秒而不产生错误。
在ASCII模式下,消息以冒号(:)字符(ASCII码0x3A)开始,以回车换行符结束(ASCII码0x0D、0x0A)。消息帧的其他字段可以使用的传输字符是十六进制码。处于网络上的Modbus设备,不断侦测“:”字符,当有一个冒号接收到时,每个设备进入解码阶段,并解码下一个字段(地址域)来判断是否是发给自己的。消息帧中的字符间发送的时间间隔最长不能超过1秒,否则接收的设备认为发生传输错误。一个典型的ASCII消息帧格式如下:
起始 | 地址 | 功能码 | 数据 | LRC检验 | 结束 |
---|---|---|---|---|---|
1字符(:) | 2字符 | 2字符 | 0~2*252字符 | 2字符 | 2字符(CR、LF) |
在RTU模式中,消息的发送和接收以至少3.5个字符时间的停顿间隔为标志,实际使用中网络设备不断检测网络总线,计算字符间的停顿间隔时间,判断消息帧的起始点。当接收到第一个域(地址域)时,每个设备都进行解码以判断是否是发给自己的。在最后一个传输字节结束后,一个至少3.5个字符时间的停顿标定了消息的结束,而一个新的消息可在此停顿后开始。另外,在一帧报文中,必须以连续的字符流发送整个报文帧,如果两个字符之间的空闲间隔大于1.5个字符时间,那么认为报文帧不完整,该报文将被丢弃。
Modbus通信时,规定RTU模式时主机发送完一组命令必须间隔3.5个字符再发送下一组新命令,这3.5个字符主要用来告诉其他设备这次命令已结束。3.5字符的时间间隔计算方式为:在串行通信中,通常一个字符包括1位起始位、8位数据位、1位校验位、1位停止位,这样1个字符就包括11位,那么3.5个字符就是3.5×11=38.5位。
串行通信中,波特率含义是每秒传输的二进制位的个数,例如波特率为9600bps,意味着每1s传输9600个位的数据,那么传输9600个二进制位的数据需要1000ms,传输38.5个二进制位的数据需要的时间是38.5
起始 | 报文 | 结束 | |||
---|---|---|---|---|---|
地址 | 功能码 | 数据 | CRC检验 | ||
≥3.5字符 | 8位 | 8位 | N×8位 | 16位 | ≥3.5字符 |
地址域指的是Modbus通信帧中的地址字段,其内容为从设备地址。Modbus消息帧的地址域在ASCII模式包含2个字符,在RTU模式为1字节。
消息帧中可能的从设备地址是0~247(十进制),单个设备的实际地址范围是1~247。主设备通过将从设备的地址放入消息中的地址域来选通从设备,当从设备发送回应消息时把自己的地址放入回应的地址域中。地址0用作广播地址,以使所有的从设备都能认识。当Modbus协议用于更高级别的网络时,广播方式可能不允许或以其他方式替代。
④功能码域:
功能码在Modbus协议中用于表示消息帧的功能。功能码由1个字节构成,因此取值范围为1~255(十进制)。从设备根据功能码执行对应的功能,执行完成后,正常情况下则在返回的响应消息帧设置同样的功能码;如果出现异常,则在返回的消息帧中将功能码最高位置1。据此,主设备可获知对应从设备的执行情况。
另外,对于主设备发送的功能码,从设备根据具体配置来决定是否支持此功能码,如果不支持则返回异常响应。
⑤ 数据域:
数据域与功能码紧密相关,存放功能码需要操作的具体数据。数据域以字节为单位,长度是可变的,对于有些功能码,数据域可以为空。
5. Modbus差错校验:
在Modbus串行通信中,根据传输模式的不同,差错校验域采用不同的校验方法:
· ASCII模式:报文包含一个错误检验字段,由两个字符组成,其值基于对全部报文内容执行的纵向冗余校验LRC(Longitudinal Redundancy Check)计算的结果而来,计算对象不包括冒号(:)和回车换行符(CRLF)。
· RTU模式:报文包含一个错误校验字段,该字段由2字节(16比特位)组成,其值基于对全部报文内容执行的循环冗余检验CRC(Cyclical Redundancy Check)计算的结果而来,计算对象包括检验域之前的所有字节。
① LRC校验:
在ASCII模式中,消息是由特定字符作为帧头和帧尾来分隔的。一条消息必须由冒号(:)字符0x3A开始,以回车换行(CRLF)符0x0D和0x0A结束。LRC校验算法的计算范围为(:)与(CRLF)之间的字符。
LRC域自身为1字节,即8比特二进制数据,由发送设备通过LRC算法计算,并把计算值附加到信息末尾。接收设备在接收信息时,通过LRC算法重新计算值,并把计算值与LRC字段中接收的实际值进行比较。若二者不同,则产生一个错误,返回一个异常响应帧。具体算法是对报文中的所有相邻2个字节相加,丢弃进位,然后对结果计算二进制补码。以上产生的LRC值占用1字节,实际上在通过串行链路由ASCII模式传递消息帧时,被编码为2字节的ASCII字符,并将其放置在ASCII模式报文帧的CRLF字段之前。LRC计算的C语言函数:
static unsigned char LRC(unsigned char *auchMsg, unsigned short usDataLen){
unsigned char uchLCR=0; //LRC字节初始化
while(usDataLen--) //遍历报文缓冲区
uchLCR+=*auchMsg++; //缓冲区字节相加,自动舍弃进位
return ((unsigned char)(-((char)uchLRC))); //返回二进制补码
}
其中,*auchMsg为含有生成LRC所使用的二进制数据的报文缓冲区指针;usDataLen为报文缓冲区中的字节数。
② CRC校验:
在RTU模式下,通信报文包括一个基于循环冗余检验CRC方法的差错校验字段。CRC校验算法检错能力强,开销小,易于编码器及检测电路实现。CRC不能发现的错误几率在0.0047%以下,CRC校验有CRC-8、CRC-12、CRC-16、CRC-CCITT、CRC-32等。Modbus协议采用了CRC-16校验方法,CRC字段占2个字节,作为报文的最后字段添加到整个报文的末尾。计算过程为:
⑴预置一个值为0xFFFF的16位寄存器为CRC寄存器
⑵把第1个字节的二进制数据与16位的CRC寄存器相异或,异或结果仍存放在CRC寄存器中
⑶把CRC寄存器的内容右移1位,用0填补最高位,并检测移出位是0还是1
⑷如果移出位为0,则重复⑶;如果移出位为1,则CRC寄存器与0xA001进行异或
⑸重复⑶和⑷,直到右移8次,这样对整个8位数据全部进行了处理
⑹重复⑵~⑸,对通信消息帧下1字节处理
⑺将该消息帧所有字节按上述步骤计算完成后,得到16位CRC寄存器的高/低字节进行交换,即发送时首先发送低字节,然后发送高字节
⑻最后得到的CRC寄存器内容即为CRC校验码
在CRC计算时,只计算每个字符中的8个数据位,而起始位及停止位、校验位等都不参与计算。
常用的CRC-16算法有查表法和计算法:
· 查表法:
CRC计算的查表法是将移位异或的计算结果做成一个表,将0~255放入一个长度为16位的寄存器中的低8位,高8位填充0,然后将该寄存器与多项式0xA001按照上述步骤的⑶和⑷计算,直到8位全部移出,最后寄存器中的值就是表格中的数据,高8位和低8位分别单独一个表。
Modbus标准协议提供了CRC查表法:
unsigned short CRC16(unsigned char *puchMsg, unsigned short usDataLen){
unsigned char uchCRCHi=0xFF; //高CRC字节初始化
unsigned char uchCRCLo=0xFF; //低CRC字节初始化
unsigned short uIndex; //CRC循环表中的索引
while(usDataLen--){ //循环处理传输缓冲区消息
uIndex=uchCRCHi^*puchMsg++; //计算CRC
uchCRCHi=uchCRCLo^auchCRCHi[uIndex];
uchCRCLo=auchCRCLo[uIndex];
}
return (uchCRCHi<<8|uchCRCLo);
}
其中,puchMsg是要CRC校验的消息,usDataLen为消息中的字节数。auchCRCHi定义为:
static unsigned char auchCRCHi[]={
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,
}
AuchCRCLo定义为:
static unsigned char auchCRCLo[]={
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,0xDF1,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
}
查表法可以进一步简化为:
unsigned short CRC16(unsigned char *puchMsg, unsigned short usDataLen){
static const unsigned short usCRCTable[]={
0x0000,0xC0C1,0xC181,0x0140,0xC301,0x03C0,0x0280,0xC241,
0xC601,0x06C0,0x0780,0xC741,0x0500,0xC5C1,0xC481,0x0440,
0xCC01,0x0CC0,0x0D80,0xCD41,0x0F00,0xCFC1,0xCE81,0x0E40,
0x0A00,0xCAC1,0xCB81,0x0B40,0xC901,0x09C0,0x0880,0xC841,
0xD801,0x18C0,0x1980,0xD941,0x1B00,0xDBC1,0xDA81,0x1A40,
0x1E00,0xDEC1,0xDF81,0x1F40,0xDD01,0x1DC0,0x1C80,0xDC41,
0x1400,0xDC41,0xD581,0x1540,0xD701,0x17C0,0x1680,0xD641,
0xD201,0x12C0,0x1380,0xD341,0x1100,0xD1C1,0xD081,0x1040,
0xF001,0x30C0,0x3180,0xF141,0x3300,0xF3C1,0xF281,0x3240,
0x3600,0xF6C1,0xF781,0x3740,0xF501,0x35C0,0x3480,0xF441,
0x3C00,0xFCC1,0xFD81,0x3D40,0xFF01,0x3FC0,0x3E80,0xFE41,
0xFA01,0x3AC0,0x3B80,0xFB41,0x3900,0xF9C1,0xF881,0x3840,
0x2800,0xE8C1,0xE981,0x2940,0xEB01,0x2BC0,0x2A80,0xEA41,
0xEE01,0x2EC0,0x2F80,0xEF41,0x2D00,0xEDC1,0xEC81,0x2C40,
0xE401,0x24C0,0x2580,0xE541,0x2700,0xE7C1,0xE681,0x2640,
0x2200,0xE2C1,0xE381,0x2340,0xE101,0x21C0,0x2080,0xE041,
0xA001,0x60C0,0x6180,0xA141,0x6300,0xA3C1,0xA281,0x6240,
0x6600,0xA6C1,0xA781,0x6740,0xA501,0x65C0,0x6480,0xA441,
0x6C00,0xACC1,0xAD81,0x6D40,0xAF01,0x6FC0,0x6E80,0xAE41,
0xAA01,0x6AC0,0x6B80,0xAB41,0x6900,0xA9C1,0xA881,0x6840,
0x7800,0xB8C1,0xB981,0x7940,0xBB01,0x7BC0,0x7A80,0xBA41,
0xBE01,0x7EC0,0x7F80,0xBF41,0x7D00,0xBDC1,0xBC81,0x7C40,
0xB401,0x74C0,0x7580,0xB541,0x7700,0xB7C1,0xB681,0x7640,
0x7200,0xB2C1,0xB381,0x7340,0xB101,0x71C0,0x7080,0xB041,
0x5000,0x90C1,0x9181,0x5140,0x9301,0x53C0,0x5280,0x9241,
0x9601,0x56C0,0x5780,0x9741,0x5500,0x95C1,0x9481,0x5440,
0x9C01,0x5CC0,0x5D80,0x9D41,0x5F00,0x9FC1,0x9E81,0x5E40,
0x5A00,0x9AC1,0x9B81,0x5B40,0x9901,0x59C0,0x5880,0x9841,
0x8801,0x48C0,0x4980,0x8941,0x4B00,0x8BC1,0x8A81,0x4A40,
0x4E00,0x8EC1,0x8F81,0x4F40,0x8D01,0x4DC0,0x4C80,0x8C41,
0x4400,0x84C1,0x8581,0x4540,0x8701,0x47C0,0x4680,0x8641,
0x8201,0x42C0,0x4380,0x8341,0x4100,0x81C1,0x8081,0x4040
}
unsigned char nTemp;
unsigned short usRegCRC=0xFFFF;
while(usDataLen--){
nTemp=*puchMsg++^usRegCRC;
usRegCRC>>=8;
usRegCRC^=usCRCTable[nTemp];
}
return usRegCRC;
}
查表法以字节为单位计算,速度快,语句少,但表格占用一定程序空间。
· 计算法:
计算法按位计算,可以适用于所有长度的数据校验,比较灵活,但效率不高,只适用于对速度不敏感的场合。C语言计算方法:
unsigned short CRC16(unsigned char *puchMsg, unsigned short usDataLen){
int i,j;
unsigned short usRegCRC=0xFFFF;
for(i=0;i<usDataLen;i++){ //循环处理传输缓冲区消息
usRegCRC^=*puchMsg; //计算CRC
for(j=0;j<8;j++){
if(usRegCRC&0x0001);
usRegCRC=usRegCRC>>1^0xA001;
else
usRegCRC>>=1;
}
}
return usRegCRC;
}
6. 字节顺序:
Modbus寄存器是由2字节组成的16位整数,在内存中存储就有大端BIG-ENDIAN和小端LITTLE-ENDIAN两种方式。大端BIG-ENDIAN方式是将数据的低位保存在内存的高地址中,而数据高位保存在内存的低地址中,即高序字节存储在起始地址;而小端LITTLE-ENDIAN方式是将数据低位保存在内存的低地址中,数据高位保存在内存的高地址中,即低序字节存储在起始地址,X86处理器采用小端方式,而Keil C51采用大端方式。
7. Modbus TCP消息帧格式:
Modbus是与物理层无关的通信协议,可在TCP/IP网络上传输Modbus报文,这时链路中的设备演变为客户端/服务器端设备,客户端相当于主站设备,服务器端相当于从站设备。基于TCP/IP网络的传输特性,通信链路演变为多客户端/多服务器构造模型,服务器端常采用端口502接收报文。
在TCP/IP网络上的Modbus协议引入了MBAP(Modbus Application Header)报文头字段。消息帧结构为:
MBAP Header | Function Code | Data |
---|
传输标识 | 协议标识 | 字节长度 | 单元标识符 | 功能码 | 数据 |
---|---|---|---|---|---|
Byte0~1 | Byte2~3 | Byte4~5 | Byte6 |
字节 | 字段名 | 说明 | 客户端 | 服务器端 |
---|---|---|---|---|
0~1 | 传输标识Transaction Identifier | 标记查询/应答的传输过程,可以设为1,或每次通信自动+1 | 由客户端生成 | 应答时复制该值 |
2~3 | 协议标识Transaction Identifier | Modbus协议设为0x00 | 由客户端生成 | 应答时复制该值 |
4 | 字节长度Hi | 设置为0x00,因此后续字节在256字节之内 | 由客户端生成 | 应答时重新生成 |
5 | 字节长度Lo | 记录后续字节的个数 | 由客户端生成 | 应答时重新生成 |
6 | 单元标识符Unit Identifier | 用以识别从机设备,可以设为从机地址 | 由客户端生成 | 应答时复制该值 |
对单纯的Modbus TCP/IP设备,利用IP地址即可寻址Modbus报文服务器端设备,此时单元标识符无用,必须使用0xFF填充。如果Modbus服务器连接到Modbus串行链路子网,并通过一个网桥或网关配置服务器的IP地址,则Modbus单元标识符对识别从站设备是必需的,此时IP地址识别了网桥本身的地址,网桥利用Modbus单元标识符可将请求转交给从站设备。当对直接连接到TCP/IP网络上的Modbus服务器寻址时,建议不要在单元标识符域使用有效的Modbus从站地址。
Modbus消息帧由TCP/IP提供,所有数据传输由TCP/IP层处理并确保了数据的准确性,所以不再需要帧起始与结束标识,也不需要校验功能。
四、Modbus功能码:
Modbus功能码占1字节,取值1~127,当出现异常时功能码+0x80来代表异常状态,因此129~255的取值代表异常码。
Modbus功能码有3类:
· 公共功能码:是被明确定义的功能码,有唯一性,可进行一致性测试
· 用户自定义功能码:有两个用户自定义功能码区,分别是65~72和100~110
· 保留功能码:某些公司的传统产品线上使用的功能码不作为公共使用
公共功能码见下表:
代码 | 名称 | 寄存器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 | 字操作 | 多个 |
1. 读取线圈/离散量输出状态:
功能码01用于读取从设备的线圈或离散量输出的状态,即各离散输出DO(Discrete Output)的ON/OFF状态。消息帧中指定了需读取的线圈起始地址和线圈数目。Modbus协议规定,所有线圈或寄存器地址从0开始计算。
① 查询报文:
帧头 | 从设备地址 | 功能码 | 起始地址高位 | 起始地址低位 | 寄存器数高位 | 寄存器数低位 | 差错校验 | 帧尾 |
---|---|---|---|---|---|---|---|---|
: | 03 | 01 | 00 | 13 | 00 | 25 | LRC | CRLF |
0x03 | 0x01 | 0x00 | 0x13 | 0x00 | 0x25 | CRC |
查询帧的消息里,定义了从设备地址为3,并读取从设备的Modbus地址00019~00055(线圈地址00020~00056)共计37个状态值。起始线圈地址为0x13(十进制00019)。
Modbus协议规定,起始地址由2字节构成,取值范围0x0000~0xFFFF;线圈数量由2字节构成,取值范围0x0001~0x07D0(十进制1~2000)。
② 响应报文:
响应报文的数据字段中,每个线圈占用1位,状态被表示为1=ON和0=OFF两种。第1个数据字节的LSB(最低有效位)标识查询报文中的起始地址线圈的状态值,其他线圈依次类推,一直到这个字节的MSB(最高有效位)为止,并在后续字节中按照同样的由低到高的方式排列。
帧头 | 从设备地址 | 功能码 | 数据域字节数 | 数据1 | 数据2 | 数据3 | 数据4 | 数据5 | 差错校验 | 帧尾 |
---|---|---|---|---|---|---|---|---|---|---|
: | 03 | 01 | 05 | 53 | 6B | 01 | F4 | 1B | LRC | CRLF |
0x03 | 0x01 | 0x05 | 0x53 | 0x6B | 0x01 | 0xF4 | 0x1B | CRC |
可以使用工具软件Modbus Poll和Modbus Slave来调试通信信息。
2. 读取离散量输入值:
功能码02用于读取从设备的离散输入DI(Discrete Input)的ON/OFF状态。消息帧中指定了需读取的离散输入寄存器起始地址和数目,可读取1~2000个连续的离散量输状态。如果从设备接受主设备的请求则回复功能码02,并返回离散量输入各变量的当前状态;如果返回的离散量输入数量的个数不是8的整数倍,将用0填充最后数据字节的剩余位。
① 查询报文:
帧头 | 从设备地址 | 功能码 | 起始地址高位 | 起始地址低位 | 寄存器数高位 | 寄存器数低位 | 差错校验 | 帧尾 |
---|---|---|---|---|---|---|---|---|
: | 03 | 02 | 00 | 64 | 00 | 14 | LRC | CRLF |
0x03 | 0x02 | 0x00 | 0x64 | 0x00 | 0x14 | CRC |
查询帧的消息里,定义了从设备地址为3,并读取从设备的离散输入寄存器中地址10101~10120(Modbus地址100~119)共计20个离散输入状态值。起始地址为0x64(十进制100)。
Modbus协议规定,起始地址由2字节构成,取值范围0x0000~0xFFFF;离散量数量由2字节构成,取值范围0x0001~0x07D0(十进制1~2000),最多一次可以读取2000个离散输入状态值。
② 响应报文:
响应报文的各项构成与意义与功能码01一样。
帧头 | 从设备地址 | 功能码 | 数据域字节数 | 数据1 | 数据2 | 数据3 | 差错校验 | 帧尾 |
---|---|---|---|---|---|---|---|---|
: | 03 | 02 | 03 | 53 | 6B | 01 | LRC | CRLF |
0x03 | 0x02 | 0x03 | 0x53 | 0x6B | 0x01 | CRC |
也可以使用工具软件Modbus Poll和Modbus Slave来调试通信信息。
3. 读取保持寄存器值:
功能码03用于读取从设备保持寄存器的内容,不支持广播模式。消息帧中指定了需读取的保持寄存器的起始地址和数目。保持寄存器中各地址的具体内容和意义由设备开发者自行规定。
① 查询报文:
查询报文中必须指定保持寄存器的起始地址和需读取的寄存器数量。
帧头 | 从设备地址 | 功能码 | 起始地址高位 | 起始地址低位 | 寄存器数高位 | 寄存器数低位 | 差错校验 | 帧尾 |
---|---|---|---|---|---|---|---|---|
: | 07 | 03 | 00 | C8 | 00 | 03 | LRC | CRLF |
0x07 | 0x03 | 0x00 | 0xC8 | 0x00 | 0x03 | CRC |
Modbus保持寄存器和输入寄存器是以字Word为基本单位的,1Word=2byte,所以如果连续读取保持寄存器地址为40001开始的一个16位的无符号数,那么返回2字节,并可以从40002开始读取下一个16位的无符号数。如果需读取寄存器地址40001开始的是一个32位浮点数,则需要返回4字节,即必须连续读取40001和40002的内容,而且下一个32位的浮点数必须从40003开始读取。对于浮点数或32位整数而言,连续读取的两个寄存器之间存在字节序和大小端的问题,开发时必须注意。
② 响应报文:
响应报文的各项构成与意义与功能码01一样,因为保持寄存器和输入寄存器是以字为基本单位,查询报文连续读取3个寄存器的内容将返回6字节。
帧头 | 从设备地址 | 功能码 | 数据域字节数 | 数据1高位 | 数据1低位 | 数据2高位 | 数据2低位 | 数据3高位 | 数据3低位 | 差错校验 | 帧尾 |
---|---|---|---|---|---|---|---|---|---|---|---|
: | 07 | 03 | 06 | 03 | 53 | 01 | F3 | 01 | 05 | LRC | CRLF |
0x07 | 0x03 | 0x06 | 0x03 | 0x53 | 0x01 | 0xF3 | 0x01 | 0x05 | CRC |
也可以使用工具软件Modbus Poll和Modbus Slave来调试通信信息。
4. 读取输入寄存器值:
功能码04用于读取从设备输入寄存器的内容,不支持广播模式。消息帧中指定了需读取的输入寄存器的起始地址和数目。输入寄存器中各地址的具体内容和意义由设备开发者自行规定。
① 查询报文:
查询报文中必须指定输入寄存器的起始地址和需读取的寄存器数量。
帧头 | 从设备地址 | 功能码 | 起始地址高位 | 起始地址低位 | 寄存器数高位 | 寄存器数低位 | 差错校验 | 帧尾 |
---|---|---|---|---|---|---|---|---|
: | 07 | 04 | 01 | 2C | 00 | 03 | LRC | CRLF |
0x07 | 0x04 | 0x01 | 0x2C | 0x00 | 0x03 | CRC |
起始地址由2字节构成,取值范围0x0000~0xFFFF;寄存器数量由2个字节构成,取值范围0x0001~0x007D(十进制1~125),最多可以连续读取125个寄存器。
Modbus输入寄存器是以字Word为基本单位的,对于32位浮点数或32位整数而言,连续读取的两个寄存器之间存在字节序和大小端的问题,开发时必须注意。
② 响应报文:
响应报文的各项构成与意义与功能码03一样,因为输入寄存器是以字为基本单位,查询报文连续读取3个寄存器的内容将返回6字节。
帧头 | 从设备地址 | 功能码 | 数据域字节数 | 数据1高位 | 数据1低位 | 数据2高位 | 数据2低位 | 数据3高位 | 数据3低位 | 差错校验 | 帧尾 |
---|---|---|---|---|---|---|---|---|---|---|---|
: | 07 | 04 | 06 | 03 | 53 | 01 | F3 | 01 | 05 | LRC | CRLF |
0x07 | 0x04 | 0x06 | 0x03 | 0x53 | 0x01 | 0xF3 | 0x01 | 0x05 | CRC |
也可以使用工具软件Modbus Poll和Modbus Slave来调试通信信息。
5. 写单个线圈或单个离散输出:
功能码05用于将单个线圈寄存器(或离散输入)设置为ON或OFF,支持广播模式,在广播模式下所有从站设备的同一地址的值将被统一修改。查询报文中的ON/OFF状态由报文数据字段的常数指定,0xFF00表示ON状态,0x0000表示OFF状态,其他值均为非法,将会返回异常响应。
① 查询报文:
查询报文中需要指定从设备地址以及需要变更的线圈地址和设定的状态值,线圈地址从0开始计数。
帧头 | 从设备地址 | 功能码 | 起始地址高位 | 起始地址低位 | 变更数据高位 | 变更数据低位 | 差错校验 | 帧尾 |
---|---|---|---|---|---|---|---|---|
: | 03 | 05 | 00 | 95 | FF | 00 | LRC | CRLF |
0x03 | 0x05 | 0x00 | 0x95 | 0xFF | 0x00 | CRC |
起始地址由2字节构成,取值范围0x0000~0xFFFF;变更目标数据由2字节构成,取值只能为0xFF00或0x0000。
② 响应报文:
响应报文的各项构成与意义见下表:
帧头 | 从设备地址 | 功能码 | 起始地址高位 | 起始地址低位 | 变更数据高位 | 变更数据低位 | 差错校验 | 帧尾 |
---|---|---|---|---|---|---|---|---|
: | 03 | 05 | 00 | 95 | FF | 00 | LRC | CRLF |
0x03 | 0x05 | 0x00 | 0x95 | 0xFF | 0x00 | CRC |
也可以使用工具软件Modbus Poll和Modbus Slave来调试通信信息。
6. 写单个保持寄存器:
功能码06用于更新从设备的单个保持寄存器的值,支持广播模式,在广播模式下所有从设备的同一地址的值将被统一修改。
① 查询报文:
查询报文中需要指定从设备地址以及需要变更的保持寄存器地址和设定的状态值,寄存器地址从0开始计数。
帧头 | 从设备地址 | 功能码 | 起始地址高位 | 起始地址低位 | 变更数据高位 | 变更数据低位 | 差错校验 | 帧尾 |
---|---|---|---|---|---|---|---|---|
: | 03 | 06 | 00 | 95 | 04 | B0 | LRC | CRLF |
0x03 | 0x06 | 0x00 | 0x95 | 0x04 | 0xB0 | CRC |
起始地址由2字节构成,取值范围0x0000~0xFFFF;变更目标数据由2字节构成,取值范围为0x0000或0xFFFF。
② 响应报文:
响应报文的各项构成与意义见下表:
帧头 | 从设备地址 | 功能码 | 起始地址高位 | 起始地址低位 | 变更数据高位 | 变更数据低位 | 差错校验 | 帧尾 |
---|---|---|---|---|---|---|---|---|
: | 03 | 06 | 00 | 95 | 04 | B0 | LRC | CRLF |
0x03 | 0x06 | 0x00 | 0x95 | 0x04 | 0xB0 | CRC |
也可以使用工具软件Modbus Poll和Modbus Slave来调试通信信息。
7. 诊断功能:
功能码08仅用于串行链路,主要用于检测主设备和从设备之间的通信故障,或检测从设备的各种内部故障,该功能码不支持广播。为了区别各诊断类型查询报文提供了2个字节的子功能码字段。
通常,在正常的响应报文中,从设备将原样回复功能码和子功能码。
① 查询报文:
查询报文中需要指定从设备地址、功能码和子功能码。
帧头 | 从设备地址 | 功能码 | 子功能码高位 | 子功能码低位 | 数据高位 | 数据低位 | 差错校验 | 帧尾 |
---|---|---|---|---|---|---|---|---|
: | 05 | 08 | 00 | 00 | 04 | B0 | LRC | CRLF |
0x05 | 0x08 | 0x00 | 0x00 | 0x04 | 0xB0 | CRC |
子功能码“原样返回查询数据”的诊断功能,其中子功能码为0(0x0000),此时数据字段可以为任意值。
子功能码由2个字节构成,取值根据意义而不同;数据字段由2字节构成,取值由子功能码确定。
② 响应报文:
响应报文的各项构成与意义见下表:
帧头 | 从设备地址 | 功能码 | 子功能码高位 | 子功能码低位 | 数据高位 | 数据低位 | 差错校验 | 帧尾 |
---|---|---|---|---|---|---|---|---|
: | 05 | 08 | 00 | 00 | 04 | B0 | LRC | CRLF |
0x05 | 0x08 | 0x00 | 0x00 | 0x04 | 0xB0 | CRC |
③子功能码意义:
常用的诊断子功能码定义为:
子功能码 | 诊断内容 | 查询报文数据字段 | 响应报文数据字段 |
---|---|---|---|
00(0x0000) | 原样返回查询报文 | 任意十六进制数据 | 同查询报文 |
01(0x0001) | 重启通信选项,端口在Listen Only Mode下不返回响应,否则在重启之前返回响应 | 0x0000保持事件记录;0xFF00清除事件记录 | 同查询报文 |
02(0x0002) | 返回诊断寄存器 | 0x0000 | 诊断寄存器的内容 |
04(0x0004) | 强制只听模式 | 0x0000 | 不返回响应 |
10(0x000A) | 清除计数器和诊断寄存器 | 0x0000 | 同查询报文 |
11(0x000B) | 返回总线报文计数 | 0x0000 | 返回报文的计数值 |
12(0x000C) | 返回总线通信CRC差错计数 | 0x0000 | 返回报文的CRC出错总数 |
13(0x000D) | 返回总线异常差错计数 | 0x0000 | 返回异常响应的总数 |
14(0x000E) | 返回从站设备报文总数 | 0x0000 | 返回从站设备接收报文总数 |
15(0x000F) | 返回从站设备无响应计数 | 0x0000 | 返回加电后没有返回响应的报文数量 |
17(0x0011) | 返回从站设备忙计数 | 0x0000 | 返回加电后异常响应忙的报文数量 |
18(0x0012) | 返回总线字符超限的计数 | 0x0000 | 返回超限的报文数量 |
8. 获取通信事件计数器:
功能码11(0x0B)主要用于获取从设备通信计数器中的状态字和事件计数器的值,不支持广播模式。通过在通信报文之前和之后获取通信事件计数值,可以确定从设备是否正常处理报文。
对于正常完全报文处理和传输的场合,事件计数器增加1;而对异常响应、轮询命令或读事件计数器的场合,计数器不变。通过诊断功能(功能码08)中的子功能码0x0001和0x000A可以复位事件寄存器。
① 查询报文:
帧头 | 从设备地址 | 功能码 | 差错校验 | 帧尾 |
---|---|---|---|---|
: | 05 | 0B | LRC | CRLF |
0x05 | 0x0B | CRC |
② 响应报文:
从设备的响应报文返回2字节的状态字和2字节的事件计数。如果从设备处于忙状态,状态字将为0xFFFF,否则状态字为0x0000。
帧头 | 从设备地址 | 功能码 | 状态字高位 | 状态字低位 | 事件计数高位 | 事件计数低位 | 差错校验 | 帧尾 |
---|---|---|---|---|---|---|---|---|
: | 05 | 0B | 00 | 00 | 03 | E8 | LRC | CRLF |
0x05 | 0x0B | 0x00 | 0x00 | 0x03 | 0xE8 | CRC |
9. 获取通信事件记录:
功能码12(0x0C)用于从从设备获取状态字、事件计数、报文计数以及事件字节字段,其中状态字和事件计数与功能码11(0x0B)获取的值一致。
报文计数包含了加电重启、清除计数器之后的报文数量,报文计数与通过诊断功能码08、子功能码11(0x0B)获取的值一致。事件字节字段包含0~64个字节,定义各种事件。
① 查询报文:
帧头 | 从设备地址 | 功能码 | 差错校验 | 帧尾 |
---|---|---|---|---|
: | 05 | 0C | LRC | CRLF |
0x05 | 0x0C | CRC |
② 响应报文:
从设备的响应报文在正常情况下包括2字节的状态字字段、2字节的事件计数字段、2字节消息计数字段以及0~64字节的事件字段。因为事件字段是变长的,增加了1字节的数据长度字段,以方便读取响应数据。
帧头 | 从设备地址 | 功能码 | 字节数 | 状态字高位 | 状态字低位 | 事件计数高位 | 事件计数低位 | 消息计数高位 | 消息计数低位 | 事件0 | 事件1 | 差错校验 | 帧尾 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
: | 05 | 0C | 08 | 00 | 00 | 03 | E8 | 01 | F6 | 20 | 00 | LRC | CRLF |
0x05 | 0x0C | 0x08 | 0x00 | 0x00 | 0x03 | 0xE8 | 0x01 | 0xF6 | 0x20 | 0x00 | CRC |
10. 写多个线圈:
功能码15(0x0F)用于将连续的多个线圈或离散输出设为ON/OFF状态,支持广播模式,在广播模式下所有从设备的同一地址的值将被统一修改。起始地址字段由2字节构成,取值范围0x0000~0xFFFF;寄存器数量字段由2字节构成,取值范围0x0001~0x07B0。
① 查询报文:
查询报文中包含请求数据字段,用于定义ON/OFF状态,数据字段中为逻辑1的位对应ON,逻辑0的位对应OFF。
帧头 | 从设备地址 | 功能码 | 起始地址高位 | 起始地址低位 | 寄存器数高位 | 寄存器数低位 | 字节数 | 变更数据高位 | 变更数据低位 | 差错校验 | 帧尾 |
---|---|---|---|---|---|---|---|---|---|---|---|
: | 05 | F0 | 00 | 13 | 00 | 0B | 02 | D1 | 05 | LRC | CRLF |
0x05 | 0xF0 | 0x00 | 0x13 | 0x00 | 0x0B | 0x02 | 0xD1 | 0x05 | CRC |
写入的数据字段划分为2字节,值0xD1对应于27~20的线圈,值0x05对应于30~28线圈。
线圈状态表:
值 | 1 | 1 | 0 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 1 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
线圈 | 27 | 26 | 25 | 24 | 23 | 22 | 21 | 20 | - | - | - | - | - | 30 | 29 | 28 |
② 响应报文:
从设备的响应报文,正常情况下包括功能码、起始地址以及写入的线圈数量。
帧头 | 从设备地址 | 功能码 | 起始地址高位 | 起始地址低位 | 寄存器数高位 | 寄存器数低位 | 差错校验 | 帧尾 |
---|---|---|---|---|---|---|---|---|
: | 05 | 0F | 00 | 13 | 00 | 0B | LRC | CRLF |
0x05 | 0x0F | 0x00 | 0x13 | 0x00 | 0x0B | CRC |
11. 写多个保持寄存器:
功能码16(0x10)用于设置或写入从设备保持寄存器的多个连续的地址块(1~123个寄存器),支持广播模式,在广播模式下所有从站设备的同一地址的值将被统一修改。
起始地址字段由2字节构成,取值范围0x0000~0xFFFF;寄存器数量字段由2字节构成,取值范围0x0001~0x007B。
① 查询报文:
查询报文中包含请求数据字段,数据字段为需写入的数值,各数据按每个寄存器2字节存放。
帧头 | 从设备地址 | 功能码 | 起始地址高位 | 起始地址低位 | 寄存器数高位 | 寄存器数低位 | 字节数 | 变更数据1高位 | 变更数据1低位 | 变更数据2高位 | 变更数据2低位 | 变更数据3高位 | 变更数据3低位 | 差错校验 | 帧尾 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
: | 05 | 10 | 00 | 13 | 00 | 03 | 06 | 01 | 55 | 01 | 56 | 01 | 57 | LRC | CRLF |
0x05 | 0x10 | 0x00 | 0x13 | 0x00 | 0x03 | 0x06 | 0x01 | 0x55 | 0x01 | 0x56 | 0x01 | 0x57 | CRC |
保持寄存器地址40020~40022,设置值分别为0x0155、0x0156、0x0157。注意起始地址为19(0x13),即比寄存器起始地址20少1。
② 响应报文:
从设备的响应报文,正常情况下包括功能码、起始地址以及写入的寄存器数量。
帧头 | 从设备地址 | 功能码 | 起始地址高位 | 起始地址低位 | 寄存器数高位 | 寄存器数低位 | 差错校验 | 帧尾 |
---|---|---|---|---|---|---|---|---|
: | 05 | 10 | 00 | 13 | 00 | 03 | LRC | CRLF |
0x05 | 0x10 | 0x00 | 0x13 | 0x00 | 0x03 | CRC |
12. 报告从站ID:
查询报文中不包含请求数据字段。
① 查询报文:
帧头 | 从设备地址 | 功能码 | 差错校验 | 帧尾 |
---|---|---|---|---|
: | 05 | 11 | LRC | CRLF |
0x05 | 0x11 | CRC |
② 响应报文:
从设备的响应报文,正常情况下包括从站ID、运行状态以及其他附加信息,运行状态占1字节,且0x00为OFF,0xFF为ON,响应报文的组成由开发者决定。
帧头 | 从设备地址 | 功能码 | 字节数 | 从设备ID | 运行状态 | 附加信息1 | ...... | 差错校验 | 帧尾 |
---|---|---|---|---|---|---|---|---|---|
: | 05 | 11 | 设备相关 | 设备相关 | FF | 设备相关 | 设备相关 | LRC | CRLF |
0x05 | 0x11 | 设备相关 | 设备相关 | 0xFF | 设备相关 | 设备相关 | CRC |
因为通信错误等原因,造成从站设备没有接收到查询报文,主站设备将按超时处理;从站设备接收到的查询报文存在通信错误,如校验错误等,此时从站设备将丢弃报文不响应,主站设备将按超时处理;从站设备接收到正确的报文,但是超出处理范围,如不存在的功能码或寄存器等,此时从站设备将返回包含异常码的响应报文。
异常报文由从站地址、功能码、以及异常码构成,其中功能码与正常响应报文的不同,功能码最高位被设为1,也即异常功能码=正常功能码+0x80。
如功能码04查询报文起始地址为0x012C(十进制300),若从站设备中不存在输入寄存器30301,则从站设备将返回一个异常报文。
查询报文为:
帧头 | 从设备地址 | 功能码 | 起始地址高位 | 起始地址低位 | 寄存器数高位 | 寄存器数低位 | 差错校验 | 帧尾 |
---|---|---|---|---|---|---|---|---|
: | 07 | 04 | 01 | 2C | 00 | 03 | LRC | CRLF |
0x07 | 0x04 | 0x01 | 0x2C | 0x00 | 0x03 | CRC |
帧头 | 从设备地址 | 功能码 | 异常码 | 差错校验 | 帧尾 |
---|---|---|---|---|---|
: | 07 | 84 | 02 | LRC | CRLF |
0x07 | 0x84 | 0x02 | CRC |
常见的异常码为:
异常码 | 名称 | 说明 |
---|---|---|
01 | 非法功能码 | 从站设备不支持此功能码 |
02 | 非法数据地址 | 指定的数据地址在从站设备中不存在 |
03 | 非法数据值 | 指定的数据超出范围或者不允许使用 |
04 | 从站设备故障 | 从站设备处理响应过程中出现未知错误等 |
五、libmodbus开发库:
对于Modbus开发来说,网上存在多个开源的开发库,其中libmodbus(www.libmodbus.org)和freemodbus(www.freemodbus.org)比较常用。libmodbus是一个免费的跨平台的Modbus库,支持RTU和TCP,支持Linux、Mac OS、FreeBSD、QNX和Window操作系统,借助于libmodbus开发库可以非常方便地建立应用程序或将Modbus通信协议嵌入到设备中。
通常使用C#来开发客户端或者主设备端的应用程序,其主动发起通信,对服务器端或从设备端进行参数设置修改,或者监控各种数据。