目录
功能码:0x06[写单一保持寄存器]
功能码:0x0f[写多个线圈寄存器]
功能码:0x10[写多个保持寄存器]
一. 基本介绍
modbus协议是一种已广泛应用于当今工业控制领域的通用通讯协议。通过此协议,控制器相互之间、或控制器经由网络(如以太网)可以和其它设备之间进行通信。Modbus协议使用的是主从通讯技术,即由主设备主动查询和操作从设备。一般将主控设备方所使用的协议称为Modbus Master,从设备方使用的协议称为Modbus Slave。
Modbus通讯物理接口可以选用串口(包括RS232、RS485和RS422),也可以选择以太网口。其通信遵循以下的过程:
1.主设备向从设备发送请求
2.从设备分析并处理主设备的请求,然后向主设备发送结果
3.如果出现任何差错,从设备将返回一个异常功能码
此协议定义了一个控制器能认识使用的消息结构,而不管它们是经过何种网络进行通信的。它描述了一控制器请求访问其它设备的过程,如何回应来自其它设备的请求,以及怎样侦测错误并记录。它制定了消息域格局和内容的公共格式。
当在Modbus网络上通信时,此协议决定了每个控制器须要知道它们的设备地址,识别按地址发来的消息,决定要产生何种行动。如果需要回应,控制器将生成反馈信息并用Modbus协议发出。在其它网络上,包含了Modbus协议的消息转换为在此网络上使用的帧或包结构。这种转换也扩展了根据具体的网络解决节地址、路由路径及错误检测的方法。
Modbus的工作方式是请求/应答,每次通讯都是主站先发送指令,可以是广播,或是向特定从站的单播;从站响应指令,并按要求应答,或者报告异常。当主站不发送请求时,从站不会自己发出数据,从站和从站之间不能直接通讯。
Modbus协议是应用层(协议层)报文传输协议,它定义了一个与物理层无关的协议数据单元(PDU),即PDU=功能码+数据域,功能码1byte,数据域不确定。
Modbus协议能够应用在不同类型的总线或网络。对应不同的总线或网络,Modbus协议引入一些附加域映射成应用数据单元(ADU),即ADU=附加域+PDU,例如modbus tcp/ip------ ADU=MBAP+ADU。
二. MODBUS协议概述
Modbus 是 OSI 模型第七层上的应用层报文传输协议,它在连接至不同类型总线或网络的设备之间提供客户机、服务器通信。
Modbus与OSI模型关系
层数 | ISO模型/OSI模型 | 对应协议或硬件 |
7 | 应用层 | Modbus 协议 |
6 | 表示层 | 空 |
5 | 会话层 | 空 |
4 | 传输层 | 空 |
3 | 网络层 | 空 |
2 | 数据链路层 | Modbus串行链路协议 |
1 | 物理层 | EIA/TIA-485(或 EIA/TIA232) |
Modbus 串行链路协议是一个主/从协议,该协议位于 OSI 模型的第二层。一个主从类型的系统有一个向某个“子”节点发出显式命令并处理响应的节点(主节点)。典型的子节点在没有收到主节点的请求时并不主动发送数据,也不与其它子节点通信,简单来说就是子节点只听老大的命令,也不与其它同事交流。
在物理层,可以使用的物理接口是:RS485 和 RS232,最常用的是 TIA/EIA-485(RS485)两线制接口。
2.1 MODBUS主/从协议原理
Modbus 串行链路协议是一个主-从协议,在同一时刻,只有一个主节点,一个或多个子节点连接于同一串行总线。子节点不会主动发送数据,只有在收到来自主节点的请求时才会发送,主节点在同一时刻只会发起一个Modbus事务处理。
为了方便理解,我们将主节点以及子节点分别称为主设备和从设备。
主设备可单独与从设备通信,也能以广播方式和所有从设备通信。如果是单独通信,从设备返回一消息作为回应;如果是广播方式查询的,则不作任何回应。
当数据帧到达终端设备(从设备)时,它通过一个简单的“端口”进入被寻址到的设备,该设备去掉数据帧的“信封”(数据头),读取数据,如果没有错误,就执行数据所请求的任务,然后将自己生成的数据加入到取得的“信封”中,把数据帧返回给发送者。返回的响应数据中包含了以下内容:终端从机地址、被执行了的命令、执行命令生成的被请求数据和一个校验码。发生任何错误都不会有成功的响应,或者返回一个错误指示帧。
2.2 MODBUS消息帧
Modbus 协议定义了一个与基础通信层无关的简单协议数据单元(PDU)。特定总线或网络上的 Modbus 协议映射能够在应用数据单元(ADU)上引入一些附加域。
2.2.1 通用MODBUS帧
地址域在帧的开始部分,由一个字节(8 位二进制)组成,十进制位 0~255,在我们系统中只使用 1~147,其它地址保留。这些位标明了用户指定的从设备的地址,该设备将接受来自与之相连主设备数据。每个从设备的地址必须是唯一的,仅仅被寻址到的从设备会响应包含了该地址的查询。当从设备发送回一个响应,响应中的从设备地址数据便告诉了主设备是哪台设备与之进行通信。
功能码的作用是指明从设备要执行的动作。
数据域包括附加信息,从设备使用这个信息执行功能码定义的操作。这个域还包括离散项目和寄存器地址、处理的项目数量以及域中的实际数据字节数。在某种请求中,数据域可以是不存在的(0 长度),在此情况下服务器不需要任何附加信息,功能码仅说明操作。
错误校验域是对报文内容执行“冗余校验”的计算结果。根据不同的传输模式(RTU 或 ASCII)使用两种不同的计算方法。
两种传输模式中(ASCII或RTU),传输设备以将Modbus消息转为有起点和终点的帧,这就允许接收的设备在消息起始处开始工作,读地址分配信息,判断哪一个设备被选中(广播方式则传给所有设备),判知何时信息已完成。部分的消息也能侦测到并且错误能设置为返回结果。
2.2.2 ASCII帧
使用ASCII模式,消息以冒号(:)字符(ASCII码 3AH)开始,以回车换行符结束(ASCII码 0DH,0AH)。
其它域可以使用的传输字符是十六进制的0...9,A...F。网络上的设备不断侦测“:”字符,当有一个冒号接收到时,每个设备都解码下个域(地址域)来判断是否发给自己的。
消息中字符间发送的时间间隔最长不能超过1秒,否则接收的设备将认为传输错误。一个典型消息帧如下所示:
起始位 | 设备地址 | 功能代码 | 数据 | LRC校验 | 结束符 |
1个字符 | 2个字符 | 2个字符 | n个字符 | 2个字符 | 2个字符 |
2.2.3 RTU帧
使用RTU模式,消息发送至少要以3.5个字符时间的停顿间隔开始。在网络波特率下多样的字符时间,这是最容易实现的(如下图的T1-T2-T3-T4所示)。传输的第一个域是设备地址。可以使用的传输字符是十六进制的0...9,A...F。网络设备不断侦测网络总线,包括停顿间隔时间内。当第一个域(地址域)接收到,每个设备都进行解码以判断是否发往自己的。在最后一个传输字符之后,一个至少3.5个字符时间的停顿标定了消息的结束。一个新的消息可在此停顿后开始。
整个消息帧必须作为一连续的流转输。如果在帧完成之前有超过1.5个字符时间的停顿时间,接收设备将刷新不完整的消息并假定下一字节是一个新消息的地址域。同样地,如果一个新消息在小于3.5个字符时间内接着前个消息开始,接收的设备将认为它是前一消息的延续。这将导致一个错误,因为在最后的CRC域的值不可能是正确的。一典型的消息帧如下所示:
起始位 | 设备地址 | 功能代码 | 数据 | CRC校验 | 结束符 |
T1-T2-T3-T4 | 8Bit | 8Bit | n个8Bit | 16Bit | T1-T2-T3-T4 |
2.2.4 两种帧的分析
1.地址域
消息帧的地址域包含两个字节(ASCII)或一个字节(RTU)。可能的从设备地址是0...247 (十进制)。单个设备的地址范围是1...247。主设备通过将要联络的从设备的地址放入消息中的地址域来选通从设备。当从设备发送回应消息时,它把自己的地址放入回应的地址域中,以便主设备知道是哪一个设备作出回应。
地址0是用作广播地址,以使所有的从设备都能认识。当Modbus协议用于更高水准的网络,广播可能不允许或以其它方式代替。
2. 如何处理功能域
消息帧中的功能代码域包含了两个字节(ASCII)或一个字节(RTU)。可能的代码范围是十进制的1...255。当然,有些代码是适用于所有控制器,有此是应用于某种控制器,还有些保留以备后用。
当消息从主设备发往从设备时,功能代码域将告之从设备需要执行哪些行为。例如去读取输入的开关状态,读一组寄存器的数据内容,读从设备的诊断状态,允许调入、记录、校验在从设备中的程序等。
当从设备回应时,它使用功能代码域来指示是正常回应(无误)还是有某种错误发生(称作异议回应)。对正常回应,从设备仅回应相应的功能代码。对异议回应,从设备返回一等同于正常代码的代码,但最重要的位置为逻辑1。
例如:一从主设备发往从设备的消息要求读一组保持寄存器,将产生如下功能代码:0 0 0 0 0 0 1 1 (十六进制03H)
对正常回应,从设备仅回应同样的功能代码。对异议回应,它返回:1 0 0 0 0 0 1 1 (十六进制83H)
除功能代码因异议错误作了修改外,从设备将一独特的代码放到回应消息的数据域中,这能告诉主设备发生了什么错误。
主设备应用程序得到异议的回应后,典型的处理过程是重发消息,或者诊断发给从设备的消息并报告给操作员。
3. 数据域
数据域是由两个十六进制数集合构成的,范围00...FF。根据网络传输模式,这可以是由一对ASCII字符组成或由一RTU字符组成。
从主设备发给从设备消息的数据域包含附加的信息:从设备必须用于进行执行由功能代码所定义的所为。这包括了象不连续的寄存器地址,要处理项的数目,域中实际数据字节数。
例如,如果主设备需要从设备读取一组保持寄存器(功能代码03),数据域指定了起始寄存器以及要读的寄存器数量。如果主设备写一组从设备的寄存器(功能代码10十六进制),数据域则指明了要写的起始寄存器以及要写的寄存器数量,数据域的数据字节数,要写入寄存器的数据。
如果没有错误发生,从从设备返回的数据域包含请求的数据。如果有错误发生,此域包含一异议代码,主设备应用程序可以用来判断采取下一步行动。
在某种消息中数据域可以是不存在的(0长度)。例如,主设备要求从设备回应通信事件记录(功能代码0B十六进制),从设备不需任何附加的信息。
4.错误检测域
标准的Modbus网络有两种错误检测方法。错误检测域的内容视所选的检测方法而定。
ASCII:当选用ASCII模式作字符帧,错误检测域包含两个ASCII字符。这是使用LRC(纵向冗长检测)方法对消息内容计算得出的,不包括开始的冒号符及回车换行符。LRC字符附加在回车换行符前面。
RTU: 当选用RTU模式作字符帧,错误检测域包含一16Bits值(用两个8位的字符来实现)。错误检测域的内容是通过对消息内容进行循环冗长检测方法得出的。CRC域附加在消息的最后,添加时先是低字节然后是高字节。故CRC的高位字节是发送消息的最后一个字节。
5. 字符的连续传输
当消息在标准的Modbus系列网络传输时,每个字符或字节以如下方式发送(从左到右):
最低有效位...最高有效位
使用ASCII字符帧时,位的序列是:
有奇偶校验
启始位 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 奇偶位 | 停止位 |
无奇偶校验:
启始位 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 停止位 | 停止位 |
使用RTU字符帧时,位的序列是:
有奇偶校验
启始位 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 奇偶位 | 停止位 |
无奇偶校验
启始位 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 停止位 | 停止位 |
6、错误检测方法
标准的Modbus串行网络采用两种错误检测方法。奇偶校验对每个字符都可用,帧检测(LRC或CRC)应用于整个消息。它们都是在消息发送前由主设备产生的,从设备在接收过程中检测每个字符和整个消息帧。
用户要给主设备配置一预先定义的超时时间间隔,这个时间间隔要足够长,以使任何从设备都能作为正常反应。如果从设备测到一传输错误,消息将不会接收,也不会向主设备作出回应。这样超时事件将触发主设备来处理错误。发往不存在的从设备的地址也会产生超时。
5.1、奇偶校验
用户可以配置控制器是奇或偶校验,或无校验。这将决定了每个字符中的奇偶校验位是如何设置的。
如果指定了奇或偶校验,“1”的位数将算到每个字符的位数中(ASCII模式7个数据位,RTU中8个数据位)。例如RTU字符帧中包含以下8个数据位:1 1 0 0 0 1 0 1
整个“1”的数目是4个。如果便用了偶校验,帧的奇偶校验位将是0,便得整个“1”的个数仍是4个。如果便用了奇校验,帧的奇偶校验位将是1,便得整个“1”的个数是5个。
如果没有指定奇偶校验位,传输时就没有校验位,也不进行校验检测。代替一附加的停止位填充至要传输的字符帧中。
LRC检测
使用ASCII模式,消息包括了一基于LRC方法的错误检测域。LRC域检测了消息域中除开始的冒号及结束的回车换行号外的内容。
LRC域是一个包含一个8位二进制值的字节。LRC值由传输设备来计算并放到消息帧中,接收设备在接收消息的过程中计算LRC,并将它和接收到消息中LRC域中的值比较,如果两值不等,说明有错误。
LRC方法是将消息中的8Bit的字节连续累加,丢弃了进位。
LRC简单函数如下:
//C代码 static unsigned char LRC(auchMsg,usDataLen) unsigned char *auchMsg ; /* 要进行计算的消息 */ unsigned short usDataLen ; /* LRC 要处理的字节的数量*/ { unsigned char uchLRC = 0 ; /* LRC 字节初始化 */ while (usDataLen--) /* 传送消息 */ uchLRC += *auchMsg++ ; /* 累加*/ return ((unsigned char)(-((char_uchLRC))) ; } |
CRC检测
使用RTU模式,消息包括了一基于CRC方法的错误检测域。CRC域检测了整个消息的内容。
CRC域是两个字节,包含一16位的二进制值。它由传输设备计算后加入到消息中。接收设备重新计算收到消息的CRC,并与接收到的CRC域中的值比较,如果两值不同,则有误。
CRC是先调入一值是全“1”的16位寄存器,然后调用一过程将消息中连续的8位字节各当前寄存器中的值进行处理。仅每个字符中的8Bit数据对CRC有效,起始位和停止位以及奇偶校验位均无效。
CRC产生过程中,每个8位字符都单独和寄存器内容相或(OR),结果向最低有效位方向移动,最高有效位以0填充。LSB被提取出来检测,如果LSB为1,寄存器单独和预置的值或一下,如果LSB为0,则不进行。整个过程要重复8次。在最后一位(第8位)完成后,下一个8位字节又单独和寄存器的当前值相或。最终寄存器中的值,是消息中所有的字节都执行之后的CRC值。
CRC添加到消息中时,低字节先加入,然后高字节。
计算CRC码的步骤为:
- 预置16位寄存器为十六进制FFFF(即全为1)。称此寄存器为CRC寄存器;
- 把第一个8位数据与16位CRC寄存器的低位相异或,把结果放于CRC寄存器;
- 把寄存器的内容右移一位(朝低位),用0填补最高位,检查最低位;
- 如果最低位为0:重复第3步(再次移位); 如果最低位为1:CRC寄存器与多项式A001(1010 0000 0000 0001)进行异或;
- 重复步骤3和4,直到右移8次,这样整个8位数据全部进行了处理;
- 重复步骤2到步骤5,进行下一个8位数据的处理;
- 最后得到的CRC寄存器即为CRC码。
/************************************ * @brief modbus rtu校验 * @param p_data:要校验的数据的地址 * data_len:要校验数据的长度(字节) * data_crc:数据的校验码 * @retval 无 **************************************/ void CRC_Checkout_16(uint8_t *p_data,uint32_t data_ len,uint8_t *data_crc) { uint16_t wcrc = 0xFFFF; uint8_t temp; uint32_t i=0,j=0; for(i=0;i<data_len;i++) { temp = *p_data & 0X00FF; p_data++; wcrc = wcrc^temp; for(j=0;j<8;j++) { if(wcrc & 0X0001) { wcrc>>=1; wcrc^=0XA001; } else { wcrc>>=1; } } } temp=wcrc; data_crc[0]=wcrc; data_crc[1]=wcrc>>8; return ; } /* CRC 高位字节值表 */ 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 } ; /* CRC低位字节值表*/ static 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, 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 } ; |
2.3 传输模式
Modbus三种通讯方式
Modbus有下列三种通信方式:
(1)、以太网:对应的通信模式是Modbus TCP/IP
(2)、异步串行传输(各种介质如有线RS-232-/422/485/;光纤、无线等):对应的通信模式是Modbus RTU或Modbus ASCII
(3)、高速令牌传递网络:对应的通信模式是Modbus PLUS
Modbus RTU和Modbus ASCII协议应用于串口链接(RS232、RS485、RS422),Modbus tcp/ip协议应用于以太网链接。
如图所示,串行传输的物理层是RS-485或RS-232,数据链路层是Modbus的串行传输协议;Modbus TCP传输的1、2、3、4层实现和日常所见的以太网、因特网一样,Modbus默认采用的TCP端口号是502。
以太网(modbus tcp/ip)
对于Modbus TCP而言,主站通常称为Client,从站称为Server;而对于Modbus RTU和Modbus ASCII来说,主站是Master,从站是Slave。
ModbusTCP的数据帧可分为两部分:ADU=MBAP+PDU = MBAP + 功能码 + 数据域,MBAP 7byte,功能码1byte,数据域不确定,由具体功能决定。
MBAP为报文头,长度为7字节,组成如下:
事务处理标识 | 协议标识 | 长度 | 单元标识符 |
2byte | 2byte | 2byte | 1byte |
在Modbus网络上传输
标准的Modbus口是使用RS-232C兼容串行接口,它定义了连接口的针脚、电缆、信号位、传输波特率、奇偶校验。控制器能直接或经由Modem组网。
控制器通信使用主/从技术,即仅一设备(主设备)能初始化传输(查询)。其它设备(从设备)根据主设备查询提供的数据作出相应反应。
典型的主设备:主机和可编程仪表。
典型的从设备:可编程控制器。
主设备可单独和从设备通信,也能以广播方式和所有从设备通信。如果单独通信,从设备返回一消息作为回应,如果是以广播方式查询的,则不作任何回应。
Modbus协议建立了主设备查询的格式:设备(或广播)地址、功能代码、所有要发送的数据、一错误检测域。
从设备回应消息也由Modbus协议构成,包括确认要行动的域、任何要返回的数据、和一错误检测域。如果在消息接收过程中发生一错误,或从设备不能执行其命令,从设备将建立一错误消息并把它作为回应发送出去。
在其它类型网络上转输
在其它网络上,控制器使用对等技术通信,故任何控制都能初始和其它控制器的通信。这样在单独的通信过程中,控制器既可作为主设备也可作为从设备。提供的多个内部通道可允许同时发生的传输进程。
在消息位,Modbus协议仍提供了主/从原则,尽管网络通信方法是“对等”。如果一控制器发送一消息,它只是作为主设备,并期望从从设备得到回应。同样,当控制器接收到一消息,它将建立一从设备回应格式并返回给发送的控制器。
查询---回应
(1)、查询
查询消息中的功能代码告之被选中的从设备要执行何种功能。数据段包含了从设备要执行功能的任何附加信息。例如功能代码03是要求从设备读保持寄存器并返回它们的内容。数据段必须包含要告之从设备的信息:从何寄存器开始读及要读的寄存器数量。错误检测域为从设备提供了一种验证消息内容是否正确的方法。
(2)、回应
如果从设备产生正常的回应,在回应消息中的功能代码是在查询消息中的功能代码的回应。数据段包括了从设备收集的数据:象寄存器值或状态。如果有错误发生,功能代码将被修改以用于指出回应消息是错误的,同时数据段包含了描述此错误信息的代码。错误检测域允许主设备确认消息内容是否可用。
三种通讯方式的报文格式
Modbus协议的报文(或帧)的基本格式是:表头 + 功能码 + 数据区 + 校验码
功能码和数据区在不同类型的网络都是固定不变的,表头和校验码则因网络底层的实现方式不同而有所区别。表头包含了从站的地址,功能码告诉从站要执行何种功能,数据区是具体的信息。
对于不同类型的网络,Modbus的协议层实现是一样的,区别在于下层的实现方式,常见的有TCP/IP和串行通讯两种。
Modbus TCP基于以太网和TCP/IP协议,Modbus RTU和Modbus ASCII则是使用异步串行传输(通常是RS-232/422/485)。
下图说明了Modbus TCP的改动:
(1)、取消了校验位。数据链路层上就进行了CRC-32的校验,TCP/IP是面向连接的可靠性的协议,因此没必要再加上校验位。
(2)、Slave地址换成了Unit Identifier。当网络里的设备全是使用TCP/IP,这个地址是没有意义的,因为IP就能进行路由寻址。如果网络里还有串行通讯的设备,则需要网关来实现Modbus TCP到Modbus RTU或ASCII之间的协议转换,这时用Unit Identifier来标识网关后面的每个串行通讯设备。
(3)、Length是指后面的字节总数。实际上数据区的长度是能确定的,有的功能码就可以确定数据区的长度,有的功能码虽不能确定数据区长度,但是数据区有字节计数,参见上文举的从站应答的例子。表头增加的Length是为了应对有些情况下TCP/IP协议会将应用层的数据拆包传输。
(4)、Transaction Identifier和Protocol Identifier由Client生成,Server的响应将复制这些参数。
modbus tcp/ip通信方式
Modbus设备可分为主站(poll)和从站(slave)。主站只有一个,从站有多个,主站向各从站发送请求帧,从站给予响应。在使用TCP通信时,主站为client端,主动建立连接;从站为server端,等待连接。
主站请求:功能码+数据
从站正常响应:请求功能码+响应数据
从站异常响应:异常功能码+异常码,其中异常功能码即将请求功能码的最高有效位置1,异常码指示差错类型
注意:需要超时管理机制,避免无期限的等待可能不出现的应答
IANA(Internet Assigned Numbers Authority,互联网编号分配管理机构)给Modbus协议赋予TCP端口号为502,这是目前在仪表与自动化行业中唯一分配到的端口号。
通信过程
A、connect 建立TCP连接
B、准备Modbus报文
C、使用send命令发送报文
D、在同一连接下等待应答
E、使用recv命令读取报文,完成一次数据交换
F、通信任务结束时,关闭TCP连接
异步串行传输的两种传输方式(modbus RTU和modbus ASCII)
异步串行传输时, 控制器可以设置为两种传输模式(ASCII或RTU)中的任何一种在标准的Modbus网络通信。用户选择想要的模式,包括串口通信参数(波特率、校验方式等),在配置每个控制器的时候,在一个Modbus网络上的所有设备都必须选择相同的传输模式和串口参数。
控制器能设置为两种传输模式(ASCII和RTU)中的任何一种在标准的Modbus网络通信。用户选择想要的模式,包括串口通信参数(波特率、校验方式等),在配置每个控制器的时候,在一个 Modbus 网络上所有设备都必须选择相同的传输模式和串口参数。
两种模式分类
RTU传输模式
当设备使用 RTU(Remote Terminal Unit)模式在 Modbus 串行链路通信,消息中每个 8 位域都是一个两个十六进制字符组成。该模式的主要优点是较高的数据密度,在相同的波特率下比ASCII 模式有更高的吞吐率。RTU 模式的每个报文必须以连续的字符流传送。
RTU模式每个字节(11位)的格式为:
1. 编码系统:8 位二进制,报文中每个 8 位字节含有两个 4 位十六进制字符(0-9,A-F)。
2. 每字节 bit 流:1 起始位、8 数据位,首先发送最低有效位、1 位奇偶检验、1 停止位。
偶校验是要求的,其它模式(奇校验、无校验)也可以使用,为了保证兼容性,同时支持无校验模式是建议的。默认校验模式模式必须为偶校验。
字符如何串行传送:
每个字符或字节均由此顺序发送(从左到右),最低有效位(LSB)…最高有效位(MSB)。
RTU模式位序列
设备配置为奇校验、偶校验或无校验都可以接受,如果无奇偶校验,就传送一个附加的停止位以填充字符帧。
RTU模式位序列(无校验的特殊情况)
帧检验域:
循环冗余检验(CRC)。
帧描述:
RTU报文帧
Modbus报文RTU帧
由发送设备将 Modbus 报文构造为带有已知起始和结束标记的帧。这使设备可以在报文的开始接收新帧,并且知道何时报文结束。不完整的报文必须能够被检测到,而错误标志必须作为结果被设置。
在 RTU 模式中,报文帧由时长至少为 3.5 个字符时间的空闲间隔区分。在后续部分,这个时间区间被称为 t3.5。
RTU报文帧
整个报文帧必须以连续的字符流发送。
如果两个字符直接的空闲间隔大于 1.5 个字符时间,则报文被认为不完整应该被接收设备丢弃,如图。
非正常报文帧
注:RTU 接受驱动程序的实现,由于 t1.5 和 t3.5 的定时,隐含了大量的对中断的管理。在高速通信速率下,这导致 CPU 负担加重。因此,在通信速率等于或低于 19200bps 时,这两个定时必须严格遵守;对于波特率大于 19200bps 的情形,应该使用 2 个定时的固定值:建议的字符间超时时间(t1.5)位 750us,帧间的超时时间(t1.5)位 1.750ms。
下图表示了对 RTU 传输模式状态图的描述。“主设备”和“从设备”的不同角度均在相同的图中表示:
RTU传输模式状态图
1.从“初始”态到“空闲”态转换需要 t3.5 定时超时:这保证帧间延迟。
2.“空闲”态是没有发送和接收报文要处理的正常状态。
3.在 RTU 模式,当没有活动的传输的实际间隔打达 3.5 个字符长时,通信链路被认为在“空闲”态。
4.在链路空闲时,在链路上检测到的任何传输的字符都被识别为帧起始。链路变为“活动”状态。然后,当链路上没有字符传输的时间间隔达到 t3.5 后,被识别为帧结束。
5.检测到帧结束后,完成 CRC 计算和校验。然后,分析地址域以确定帧是否发往此设备,如果不是,则丢弃此帧。为了减少接收处理时间,地址域可以在一接到就分析,而不需要等到整个帧结束。这样,CRC 计算只需要在帧寻址到该节点(包括广播帧)时进行。
CRC计算
在 RTU 模式包含一个对全部报文内容执行的,基于循环冗余校验(CRC-Cyclical Redundancy Checking)算法的错误检验域。CRC 域检验整个报文的内容。不管报文有无奇偶校验,均执行此检验。
CRC 包含由两个 8 位字节组成的一个 16 位值。
CRC 域作为报文的最后的域附加在报文之后。计算后,首先附加低字节。然后是高字节。CRC 高字节为报文发送的最后一个字节。
附加在报文后面的 CRC 的值由发送设备计算。接收设备在接收报文时重新计算 CRC 的值,并将计算结果于实际接收到的 CRC 值相比较,如果两个值不相等,则为错误。
CRC 的计算,开始对一个 16 位寄存器预装全“1”,然后将报文中连续的 8位字节对其进行后续的计算。只有字符中的 8 个数据位参与到生成 CRC 的运算,起始位、停止位和校验位不参与 CRC 计算。
CRC 生成的过程中,每个 8 位字符与寄存器中的值异或,然后结果向最低有效位(LSB)方向移动 1 位,而最高有效位(MSB)置 0.然后提取并检查 LSB:如果 LSB 为 1,则寄存器中的值与一个固定的预置值异或;如果 LSB 为 0,则不进行异或操作。
这个过程将重复直到执行完 8 次移位,完成最后一次(第八次)移位及相关操作后,下一个 8 位字节与寄存器的当前值异或,然后又同上面描述过的一样重复 8 次。当所有报文中字节都预算之后得到的寄存器中的最终值,就是 CRC。
ASCII传输模式
当 Modbus 串行链路的设备被配置为使用 ASCII(American Standard Code For Information Interchange)模式通信时,报文中的每个 8 位字节以两个 ASCII 字符发送。当通信链路或者设备无法符合 RTU 模式的定时管理时使用该模式。
注:由于一个字节需要两个字符,此模式比 RTU 效率低。
例:字节 0x5B 会被编码为两个字符:0x35 和 0x42(ASCII 编码 0x35=”5”,0x42=”B”)。
ASCII模式每个字节(10位)的格式为:
1.编码系统:十六进制,ASCII 字符 0-9,A-F,报文中每个 ASCII 字符含有 1 个十六进制字符。
2.每字节 bit 流:1 起始位、7 数据位,首先发送最低有效位、1 位奇偶检验、1 停止位。
奇偶校验是要求的,其它模式(奇校验,无校验)也可以使用。为了保证与其它产品最大的兼容性,同时支持无校验模式是建议的。默认校验模式必须为偶校验。
字符是如何串行传送的:
每个字符或字节均由此顺序发送(从左到右):最低有效位(LSB)…最高有效位(MSB)
ASCII 模式位序列
设备配置为奇校验、偶校验或无校验都可以接受。如果是无奇偶校验,将传送一个附加的停止位以填充字符帧:
ASCII模式位序列(无校验的特殊情况)
帧检验域:
纵向冗余校验(LRC-Longitudinal Redundancy Checking)。
Modbus ASCII报文帧
由发送设备将 Modbus 报文构造为带有已知起始和结束标记的帧。这使设备可以在报文的开始接收新帧,并且知道何时报文结束。不完整的报文必须能够被检测到而错误标志必须作为结果被设置。
报文帧的地址域含有两个字符。
在ASCII模式,报文用特殊的字符区分帧起始和帧结束。一个报文必须以一个‘冒号’(:)(ASCII 十六进制 3A)起始,以‘回车-换行’(CR LF)对(ASCII十六进制0D和0A)结束。
注:LF 字符可以通过特定的 Modbus 应用命令改变。
对于所有的域,允许传送的字符为十六进制 0-9,A-F(ASCII 编码)。设备连续的监视总线上‘冒号’字符。当收到这个字符后,每个设备解码后续的字符一直到帧结束。
报文中字符间的时间间隔可以达1秒。如果有更大的间隔,则接收设备认为发生了错误。
ASCII报文帧
起始 | 地址 | 功能 | 数据 | LRC | 结束 |
1字符 | 2字符 | 2字符 | 0到to 2x252字符 | 2 字符 | 2字符CR,LF |
注:每个字符字节需要用两个字符编码。因此,为了确保 ASCII 模式和 RTU模式在 Modbus 应用级兼容,ASCII 数据域最大长度为(2x252)是 RTU 数据域(252)的两倍。
必然的,Modbus ASCII 帧的最大尺寸为 513 个字符。
ASCII 报文帧的要求在下面的状态图中综合。“主设备”和“从设备”的不同角度均在相同的图中表示。
ASCII传输模式状态图
1.“空闲”态是没有发送和接收报文要处理的正常状态。
2.每次接收到“:”字符表示新的报文的开始。如果在一个报文的接收过程中收到该字符。则当前地报文被认为不完整并被丢弃。而一个新的接收缓冲区被重新分配。
3.检测到帧结束后,完成 LRC 计算和校验,然后分析地址域以确定帧是否发往此设备,如果不是,则丢弃此帧。为了减少接收处理时间,地址域可以在一接到就分析,而不需要等到整个帧结束。
LRC校验
在 ASCII 模式,包含一对全部报文内容执行的,基于纵向冗余校验(LRC-Longitudinal Redundancy Checking)算法的错误检验域。LRC 域检验不包括起始“冒号”和结尾 CRLF 对的整个报文的内容。不关报文有无奇偶校验,均执行此检验。
LRC 域为一个字节,包含一个 8 位二进制值。LRC 值由发送设备计算,然后将 LRC 附在报文后面。接收设备在接收报文时重新计算 LRC 的值,并将计算结果于实际接收到的 LRC 值相比较,如果两个值不相等,则为错误。
LRC 的计算,对报文中的所有的连续 8 位字节相加,忽略任何进位,然后求出其二进制补码。执行检验针对不包括起始“冒号”和结尾 CRLF 对的整个 ASCII报文域的内容。在 ASCII 模式,LRC 的结果被 ASCII 编码为两个字节并放置于 ASCII模式报文帧的结尾,CRLF 之前。
异常码定义
客户机请求和服务器异常响应的实例:(仅功能码和数据区)
请求 | 响应 | ||
域名 | (十六进制) | 域名 | (十六进制) |
功能 起始地址 Hi 起始地址 Lo 输出数量 Hi 输出数量 Lo | 01 | 功能 异常码 | 81 |
04 | 02 | ||
A1 | |||
00 | |||
01 |
在此实例中,客户机对服务器设备寻址请求。功能码(01)用于读输出状态操作。它将请求地址 1245(十六进制 04A1)的输出状态。值得注意的是,像输出域(0001)号码说明的那样,只读出一个输出。
如果在服务器设备中不存在输出地址,那么服务器将返回异常码(02)的异常响应,这就说明从站的非法数据地址。
MODBUS异常码 | ||
代码 | 名称 | 含义 |
01 | 非法功能 | 对于服务器(或从站)来说,询问中接收到的功能码是不可允许的操作。这也许是因为功能码仅仅适用于新设备而在被选单元中是不可能实现的。同时,还支持服务器(或从站)在错误状态中处理这种请求,例如:因为它是未配置的,并且要求返回寄存器值。 |
02 | 非法数据地址 | 对于服务器(或从站)来说,询问中接收到的数据地址是不可允许的地址。特别是,参考号和传输长度的组合是无效的。对于带有 100 个寄存器的控制器来说,带有偏移量 96 和长度 4 的请求会成功,带有偏移量 96 和长度 5 的请求将产生异常码 02。 |
03 | 非法数据值 | 对于服务器(或从站)来说,询问中包括的值是不可允许的值。这个值指示了组合请求剩余结构中的故障,例如:隐含长度是不正确的。并不意味着,因为 MODBUS 协议不知道任何特殊寄存器的任何特殊值得重要意义,寄存器中被提交存储的数据项有一个应用程序期望之外的值。 |
04 | 从站设备故障 | 当服务器(或从站)正在设法执行请求的操作时,产生不可重新获得的差错。 |
05 | 确认 | 与编程命令一起使用。服务器(或从站)已经接受请求,并且正在处理这个请求,但是需要长的持续时间进行这些操作。返回这个响应防止在客户机(或主站)中发生超时错误。客户机(或主站)可以继续发送轮询程序完成报文来确定是否完成处理。 |
06 | 从属设备忙 | 与编程命令一起使用。服务器(或从站)正在处理长持续时间的程序命令。当服务器(或从站)空闲时,用户(或主站)应该稍后重新传输报文。 |
08 | 存储奇偶性差错 | 与功能码20和21以及参考类型6一起使用,只是扩展文件区不能通过一致性校验。服务器(或从站) 设法读取记录文件,但是在存储器中发现一个奇偶 校验错误。客户机(或主站)可以重新发送请求, 但可以在服务器(或从站)设备上要求服务。 |
0A | 不可用网关路径 | 与网关一起使用,指示网关不能为处理请求分配输 入端口至输出端口的内部通信路径。通常意味着网 关是错误配置的或过载的。 |
0B | 网关目标设备相应失败 | 与网关一起使用,指示没有从目标设备中获得响 应。通常意味着设备未在网络中。 |
三类功能码分别为:公共功能码、用户定义功能码、保留功能码。
功能码分类 | |
1-64 | 公共功能码 |
65-72 | 用户定义功能码 |
72-100 | 公共功能码 |
100-110 | 用户定义功能码 |
110-127 | 公共功能码 |
三. 常用功能码描述:
功能码 | 名称 | 功能 |
01 | 读线圈状态 | 读位(读N个bit)---读从机线圈寄存器,位操作 |
02 | 读输入离散量 | 读位(读N个bit)---读离散输入寄存器,位操作 |
03 | 读多个寄存器 | 读整型、字符型、状态字、浮点型(读N个words)---读保持寄存器,字节操作 |
04 | 读输入寄存器 | 读整型、状态字、浮点型(读N个words)---读输入寄存器,字节操作 |
05 | 写单个线圈 | 写位(写一个bit)---写线圈寄存器,位操作 |
06 | 写单个保持寄存器 | 写整型、字符型、状态字、浮点型(写一个word)---写保持寄存器,字节操作 |
07 | 读取异常状态 | 取得8个内部线圈的通断状态,这8个线圈的地址由控制器决定,用户逻辑可以将这些线圈定义,以说明从机状态,短报文适宜于迅速读取状态 |
08 | 回送诊断校验 | 把诊断校验报文送从机,以对通信处理进行评鉴 |
09 | 编程(只用于484) | 使主机模拟编程器作用,修改PC从机逻辑 |
0A | 控询(只用于484) | 可使主机与一台正在执行长程序任务从机通信,探询该从机是否已完成其操作任务,仅在含有功能码9的报文发送后,本功能码才发送 |
0B | 读取事件计数 | 可使主机发出单询问,并随即判定操作是否成功,尤其是该命令或其他应答产生通信错误时 |
0C | 读取通讯事件记录 | 可是主机检索每台从机的ModBus事务处理通信事件记录。如果某项事务处理完成,记录会给出有关错误 |
0D | 编程(184/384/484/584) | 可使主机模拟编程器功能修改PC从机逻辑 |
0E | 探询(184/384/484/584) | 可使主机与正在执行任务的从机通信,定期控询该从机是否已完成其程序操作,仅在含有功能13的报文发送后,本功能码才得发送 |
0F | 写多个线圈 | 可以写多个线圈---强置一串连续逻辑线圈的通断 |
10 | 写多个保持寄存器 | 写多个保持寄存器---把具体的二进制值装入一串连续的保持寄存器 |
11 | 报告从机标识 | 可使主机判断编址从机的类型及该从机运行指示灯的状态 |
12 | (884和MICRO84) | 可使主机模拟编程功能,修改PC状态逻辑 |
13 | 重置通信链路 | 发生非可修改错误后,是从机复位于已知状态,可重置顺序字节 |
14 | 读取通用参数(584L) | 显示扩展存储文件中的数据信息 |
15 | 写入通用参数(584L) | 把通用参数写入扩展存储文件 |
16~40 | 保留做扩展功能备用 | |
41~48 | 保留以备用户功能所用 | 留作用户功能的扩展编码 |
49~77 | 非法功能 |
|
78~7F | 保留 | 留作内部作用 |
80~FF | 保留 | 用于异常应答 |
modbus完整支持很多功能码,但是实际在应用的时候常用的也就那么几个。具体如下:
0x01: 读线圈寄存器
0x02: 读离散输入寄存器
0x03: 读保持寄存器
0x04: 读输入寄存器
0x05: 写单个线圈寄存器
0x06: 写单个保持寄存器
0x0f: 写多个线圈寄存器
0x10: 写多个保持寄存器
如上所示一共8种功能码。这其中有涉及到线圈、离散输入、保持、输入四种寄存器。下面分别解释一下:
线圈寄存器:实际上就可以类比为开关量,每个bit都对应一个信号的开关状态。所以一个byte就可以同时控制8路的信号。比如控制外部8路io的高低。 线圈寄存器支持读也支持写,写在功能码里面又分为写单个线圈寄存器和写多个线圈寄存器。对应上面的功能码也就是:0x01 0x05 0x0f。
离散输入寄存器:如果线圈寄存器理解了这个自然也明白了。离散输入寄存器就相当于线圈寄存器的只读模式,他也是每个bit表示一个开关量,而他的开关量只能读取输入的开关信号,是不能够写的。比如我读取外部按键的按下还是松开。所以功能码也简单就一个读的 0x02。
保持寄存器:这个寄存器的单位不再是bit而是两个byte,也就是可以存放具体的数据量的,并且是可读写的。比如我我设置时间年月日,不但可以写也可以读出来现在的时间。写也分为单个写和多个写,所以功能码有对应的三个:0x03 0x06 0x10。
输入寄存器:只剩下这最后一个了,这个和保持寄存器类似,但是也是只支持读而不能写。一个寄存器也是占据两个byte的空间。类比我我通过读取输入寄存器获取现在的AD采集值。对应的功能码也就一个 0x04。
对应的错误返回:在对应功能码基础上加上0x80。
功能码: 0x01[读取线圈]
在一个远程设备中,使用该功能码读取线圈的1至2000连续状态。指令列表详细说明了起始地址,即指定的第一个线圈地址和线圈编号。从零开始寻找线圈,因此寻址线圈1-16为0-15。
根据数据域的每个比特将响应报文中的线圈分成一个线圈。指示状态 1=ON和 0=OFF。第一个数据字节的 LSB(最低有效位)包括在询问中寻址的输出。其它线圈依次类推,一直到这个字节的高位端为止,并在后续字节中从低位到高位的顺序。
如果返回的输出数量不是八的倍数,将用零填充最后字节中的剩余比特(一直到字节的高位端)。字节数量域说明了数据的完整字节数。
主机发送帧:
名称 | 字节数 | 数值 |
从机地址 | 1字节 | 0x01 |
功能码 | 1字节 | 0x01 |
起始地址 | 2字节(高字节在前) | 0x0000-0xffff |
线圈数量 | 2字节(高字节在前) | 1-2000(0x7D0) |
CRC | 2字节(高字节在前) |
从机应答帧:
名称 | 字节数 | 数值 |
从机地址 | 1字节 | 0x01 |
功能码 | 1字节 | 0x01 |
字节数 | 2字节(高字节在前) | N |
线圈状态 | n字节 | n=N/N+1 |
CRC | 2字节(高字节在前) |
N=读取线圈个数/8,如果余数不为0,则N=N+1.
出错返回
名称 | 字节数 | 数值 |
从机地址 | 1字节 | 0x01 |
功能码 | 1字节 | 0x81 |
错误代码 | 1字节 | 01/02/03/04 |
CRC | 2字节(高字节在前) |
功能码:0x02[读离散输入]
该功能码作用是读离散输入寄存器,位操作,可读单个或多个,协议类似功能码 0x01,具体的就不讲解了,参考 0x01 功能码即可。
主机发送帧:
名称 | 字节数 | 数值 |
从机地址 | 1字节 | 0x01 |
功能码 | 1字节 | 0x02 |
起始地址 | 2字节(高字节在前) | 0x0000-0xffff |
线圈数量 | 2字节(高字节在前) | 1-2000(0x7D0) |
CRC | 2字节(高字节在前) |
从机应答帧:
名称 | 字节数 | 数值 |
从机地址 | 1字节 | 0x01 |
功能码 | 1字节 | 0x02 |
字节数 | 2字节(高字节在前) | N |
线圈状态 | n字节 | n=N/N+1 |
CRC | 2字节(高字节在前) |
N=读取线圈个数/8,如果余数不为0,则N=N+1.
出错返回:
名称 | 字节数 | 数值 |
从机地址 | 1字节 | 0x01 |
功能码 | 1字节 | 0x82 |
错误代码 | 1字节 | 01/02/03/04 |
CRC | 2字节(高字节在前) |
例子: [发送]01 02 00 00 00 09 B8 0C
[接收]01 02 02 3E 00 A9 D8
功能码:0x03”[读保持寄存器]
读保存寄存器,字节指令操作,可读单个或多个。
主机发送帧:
名称 | 字节数 | 数值 |
从机地址 | 1字节 | 0x01 |
功能码 | 1字节 | 0x03 |
起始地址 | 2字节(高字节在前) | 0x0000-0xffff |
寄存器数量 | 2字节(高字节在前) | 1-125(0x7D) |
CRC | 2字节(高字节在前) |
从机应答帧:
名称 | 字节数 | 数值 |
从机地址 | 1字节 | 0x01 |
功能码 | 1字节 | 0x03 |
字节数 | 1字节 | N |
寄存器值 | n字节 | N |
CRC | 2字节(高字节在前) |
N=寄存器数量。
出错返回:
名称 | 字节数 | 数值 |
从机地址 | 1字节 | 0x01 |
功能码 | 1字节 | 0x83 |
错误代码 | 1字节 | 01/02/03/04 |
CRC | 2字节(高字节在前) |
例子:
[发送]01 03 00 00 00 0A C5 CD
[接收]01 03 14 00 00 00 FF 00 00 00 B4 01 01 01 01 00 01 00 00 00 00 00 00 D7 B8
功能码:0x04[读输入寄存器]
读输入寄存器,字节指令操作,可读单个或多个。发送指令以及响应都和 03H一样。
主机发送帧:
名称 | 字节数 | 数值 |
从机地址 | 1字节 | 0x01 |
功能码 | 1字节 | 0x04 |
起始地址 | 2字节(高字节在前) | 0x0000-0xffff |
线圈数量 | 2字节(高字节在前) | 1-2000(0x7D0) |
CRC | 2字节(高字节在前) |
从机应答帧:
名称 | 字节数 | 数值 |
从机地址 | 1字节 | 0x01 |
功能码 | 1字节 | 0x01 |
字节数 | 1字节 | N |
寄存器状态 | n字节 | n=N |
CRC | 2字节(高字节在前) |
N=读取线圈个数/8,如果余数不为0,则N=N+1.
出错返回:
名称 | 字节数 | 数值 |
从机地址 | 1字节 | 0x01 |
功能码 | 1字节 | 0x81 |
错误代码 | 1字节 | 01/02/03/04 |
CRC | 2字节(高字节在前) |
例子:[发送]01 04 00 00 00 17 B0 04 (读取输入寄存器0x0000-0x0016地址数据)
[接收]01 04 2E 00 04 00 13 00 0D 00 33 00 1C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 CD E9
功能码:0x05[写单一线圈]
写单个线圈,位操作,只能写一个,写0xff00表示设置线圈状态为ON,写0x0000表示设置线圈状态为OFF。
主机发送帧:
名称 | 字节数 | 数值 |
从机地址 | 1字节 | 0x01 |
功能码 | 1字节 | 0x05 |
线圈地址 | 2字节(高字节在前) | 0x0000-0xffff |
写入值 | 2字节(高字节在前) | |
CRC | 2字节(高字节在前) |
从机应答帧:
名称 | 字节数 | 数值 |
从机地址 | 1字节 | 0x01 |
功能码 | 1字节 | 0x05 |
线圈地址 | 2字节(高字节在前) | |
写入值 | 2字节(高字节在前) | |
CRC | 2字节(高字节在前) |
出错返回:
名称 | 字节数 | 数值 |
从机地址 | 1字节 | 0x01 |
功能码 | 1字节 | 0x85 |
错误代码 | 1字节 | 01/02/03/04 |
CRC | 2字节(高字节在前) |
例子:
功能码:0x06[写单一保持寄存器]
写单个保持寄存器,字节指令操作,只能写一个。
主机发送帧:
名称 | 字节数 | 数值 |
从机地址 | 1字节 | 0x01 |
功能码 | 1字节 | 0x06 |
寄存器地址 | 2字节(高字节在前) | 0x0000-0xffff |
写入值 | 2字节(高字节在前) | 0x0000-0xffff |
CRC | 2字节(高字节在前) |
从机应答帧:
名称 | 字节数 | 数值 |
从机地址 | 1字节 | 0x01 |
功能码 | 1字节 | 0x06 |
寄存器地址 | 2字节(高字节在前) | |
写入值 | 2字节(高字节在前) | |
CRC | 2字节(高字节在前) |
出错返回:
名称 | 字节数 | 数值 |
从机地址 | 1字节 | 0x01 |
功能码 | 1字节 | 0x86 |
错误代码 | 1字节 | 01/02/03/04 |
CRC | 2字节(高字节在前) |
例子: [发送]01 06 00 00 01 02 09 9B
[接收]01 06 00 00 01 02 09 9B
响应:和发送指令相同。
功能码:0x0f[写多个线圈寄存器]
写多个线圈寄存器,若数据区的某位值为“1”表示被请求的相应线圈状态为 ON,若某位值为“0”,则状态为 OFF。
主机发送帧:
名称 | 字节数 | 数值 |
从机地址 | 1字节 | 0x01 |
功能码 | 1字节 | 0x0F |
起始地址 | 2字节(高字节在前) | 0x0000-0xffff |
写入线圈个数 | 2字节(高字节在前) | 0x01-0x07B0 |
写入字节数 | 1字节N | 1-2000(0x7D0) |
写入值 | N字节 | |
CRC | 2字节(高字节在前) |
从机应答帧:
名称 | 字节数 | 数值 |
从机地址 | 1字节 | 0x01 |
功能码 | 1字节 | 0x0F |
起始地址 | 2字节(高字节在前) | N |
写入线圈个数 | 2字节 | |
CRC | 2字节(高字节在前) |
N=读取线圈个数/8,如果余数不为0,则N=N+1.
出错返回:
名称 | 字节数 | 数值 |
从机地址 | 1字节 | 0x01 |
功能码 | 1字节 | 0x8F |
错误代码 | 1字节 | 01/02/03/04 |
CRC | 2字节(高字节在前) |
例子:
功能码:0x10[写多个保持寄存器]
写多个保持寄存器,字节指令操作,可写多个。
主机发送帧:
名称 | 字节数 | 数值 |
从机地址 | 1字节 | 0x01 |
功能码 | 1字节 | 0x10 |
起始地址 | 2字节(高字节在前) | 0x0000-0xffff |
写入寄存器个数 | 2字节(高字节在前) | N(0x0001-0x007B) |
写入字节数 | 1字节 | 2*N |
写入值 | 2*N字节 | 2*N |
CRC | 2字节(高字节在前) |
从机应答帧:
名称 | 字节数 | 数值 |
从机地址 | 1字节 | 0x01 |
功能码 | 1字节 | 0x01 |
起始地址 | 2字节(高字节在前) | |
写入寄存器个数 | 2字节 | |
CRC | 2字节(高字节在前) |
出错返回:
名称 | 字节数 | 数值 |
从机地址 | 1字节 | 0x90 |
功能码 | 1字节 | 0x81 |
错误代码 | 1字节 | 01/02/03/04 |
CRC | 2字节(高字节在前) |
例子:
[发送]01 10 00 02 00 02 04 00 00 00 F3 32 33
[接收]01 10 00 02 00 02 E0 08
[发送]01 10 00 00 00 02 00 04 00 01 00 03 0E C0
[接收]01 90 03 0C 01