Modbus协议

㈠MODBUS规约

MODBUS规约是MODICOM公司开发的一个为很多厂商支持的开放规约, Modbus 协议是应用于电子控制器上的一种通用语言。通过此协议,控制器相互之间、控制器经由网络(例如以太网)和其它设备之间可以通信。它已经成为一通用工业标准。有了它,不同厂商生产的控制设备可以连成工业网络,进行集中监控。

最主要的是它被很多组态软件所兼容,开发速度较快,受到了很多工控厂商的追捧。但是协议还是有点小麻烦的。

此协议定义了一个控制器能认识使用的消息结构,而不管它们是经过何种网络进行通信的。它描述了控制器请求访问其它设备的过程,如果回应来自其它设备的请求,以及怎样侦测错误并记录。它制定了消息域格局和内容的公共格式。

当在Modbus网络上通信时,此协议决定了每个控制器须要知道它们的设备地址,识别按地址发来的消息,决定要产生何种行动。如果需要回应,控制器将生成反馈信息并用Modbus协议发出。在其它网络上,包含了Modbus协议的消息转换为在此网络上使用的帧或包结构。这种转换也扩展了根据具体的网络解决节地址、路由路径及错误检测的方法。

标准的Modbus口是使用RS-232C兼容串行接口,它定义了连接口的针脚、电缆、信号位、传输波特率、奇偶校验。控制器能直接或经由Modem组网。

控制器通信使用主—从技术,即仅设备(主设备)能初始化传输(查询)。其它设备(从设备)根据主设备查询提供的数据做出相应反应。典型的主设备:主机和可编程仪表。典型的从设备:可编程控制器。

主设备可单独和从设备通信,也能以广播方式和所有从设备通信。如果单独通信,从设备返回消息作为回应,如果是以广播方式查询的,则不作任何回应。Modbus协议建立了主设备查询的格式:设备(或广播)地址、功能代码、所有要发送的数据、错误检测域。

从设备回应消息也由Modbus协议构成,包括确认要行动的域、任何要返回的数据和错误检测域。如果在消息接收过程中发生错误,或从设备不能执行其命令,从设备将建立错误消息并把它作为回应发送出去。

在其它网络上,控制器使用对等技术通信,故任何控制都能初始和其它控制器的通信。这样在单独的通信过程中,控制器既可作为主设备也可作为从设备。提供的多个内部通道可允许同时发生的传输进程。

在消息位,Modbus协议仍提供了主—从原则,尽管网络通信方法是“对等”。如果控制器发送消息,它只是作为主设备,并期望从从设备得到回应。同样,当控制器接收到消息,它将建立一从设备回应格式并返回给发送的控制器。

.主设备查询

查询消息中的功能代码告之被选中的从设备要执行何种功能。数据段包含了从设备要执行功能的任何附加信息。例如功能代码03是要求从设备读保持寄存器并返回它们的内容。数据段必须包含要告之从设备的信息:从何寄存器开始读及要读的寄存器数量。错误检测域为从设备提供了一种验证消息内容是否正确的方法。

.从设备回应

如果从设备产生正常的回应,在回应消息中的功能代码是在查询消息中的功能代码的回应。数据段包括了从设备收集的数据:像寄存器值或状态。如果有错误发生,功能代码将被修改以用于指出回应消息是错误的,同时数据段包含了描述此错误信息的代码。错误检测域允许主设备确认消息内容是否可用。

每个MODBUS帧都包括地址域  功能域  数据域 错误检测域

 

接下来详细介绍Modbus协议。Modbus协议分为三种通信方式:Modbus RTU、Modbus ASCII以及Modbus TCP。
  首先,Modbus TCP的通信格式和Modbus RTU非常相似,唯一的差别只是Modbus RTU最后带两个字节的CRC校验,而Modbus TCP没有。
  其次,Modbus ASCII的通信格式与Modbus RTU其实“神合貌离”,就是把Modbus RTU的每一个字节(例如:27H)高四位(2)和低四位(7)拆分为两个字节,并以ASCII码的方式表现出来(32 37),再给命令帧分别加上起始符和结束符便可以,当然Modbus RTU和Modbus ASCII的校验的方式不同,这里暂不详述,所以同一条命令用Modbus RTU方式和Modbus ASCII方式表现出来,虽然在命令长度的上有很大的区别,但其实际表达的意思却是一样。

㈡RTU方式

地址

功能

代码

数据

数量

数据1

...

数据n

CRC

字节

CRC

字节

 地址域 功能域                  数据域                        错误检测域

 

帧定界 :MODBUS RTU方式下,每两个字符之间发送或者接收的时间间隔不能超过1.5倍 字符传输时间。如果两个字符时间间隔超过了3.5倍的字符传输时间,规约就认为一帧数据已经接收,新的一帧数据传输开始。

 

㈢ASCII方式

ASCII模式

:

地址

功能

代码

数据

数量

数据1

...

数据n

LRC低字节

LRC高字节

回车

换行

地址域  功能域                   数据域                       错误检测域

帧定界:

“:”帧起始   “CR LF” 帧结束

 ASCII方式用两个ASCII字符表示一个8位数据,比如16进制的3A用字符“3”和字符“A”表示。

 

㈣MODBUS TCP

MODBUS TCP模式下,由于模块的地址由IP地址确定,所以不再有地址域内容,考虑到TCP网络是可靠的数据传输网络,故不再有校验数据。但是考虑到在IP网上数据到达的顺序可能与我们预期的数据不一致,故增加了一个数据序号,考虑到在MODBUS TCP协议上承载MODBUS协议,还在头部数据中增加了一个地址域。

因为Modbus/TCP是一种应用层的协议,上层为Modbus 协议,下层为TCP协议,它规定了网络互联节点间的请求/应答的通信方式。帧格式必须严格遵守协议所规定的ADU ( Application Data Unit)格式,才能在以太网上实现数据的传输。

MODBUS TCP模式

事务标识符

协议标识符

长度字段

从机地址

功能码

数据

 

名称

长度

功能

MBAP报文头

事务标识符

2字节

Modbus请求/响应

事务处理的标识码

协议标识符

2字节

0=Modbus协议

长度字段

2字节

  以下(包括了单儿标识域和数据域)的宇节数量

从机地址

1字节

串行链路或其它总线上连接的远程从站识别码

功能码

1字节

功能标识

数据

N字节

数据或者指令

 

㈤所有命令

功能码

名称

作用

1

读取线圈状态

取得一组逻辑线圈的当前状态(ON/OFF)

2

读取输入状态

取得一组开关输入的当前状态(ON/OFF)

3

读取保持寄存器

在一个或多个保持寄存器中取得当前的二进制值

4

读取输入寄存器

在一个或多个输入寄存器中取得当前的二进制值

5

强置单线圈

强置一个逻辑线圈的通断状态

6

预置单寄存器

把具体二进值装入一个保持寄存器

7

读取异常状态

取得8个内部线圈的通断状态,这8个线圈的地址由控制器决定,用户逻辑可以将这些线圈定义,以说明从机状态,短报文适宜于迅速读取状态

8

回送诊断校验

把诊断校验报文送从机,以对通信处理进行评鉴

9

编程(只用于484)

使主机模拟编程器作用,修改PC从机逻辑

10

控询(只用于484)

可使主机与一台正在执行长程序任务从机通信,探询该从机是否已完成其操作任务,仅在含有功能码9的报文发送后,本功能码才发送

11

读取事件计数

可使主机发出单询问,并随即判定操作是否成功,尤其是该命令或其他应答产生通信错误时

12

读取通信事件记录

可是主机检索每台从机的ModBus事务处理通信事件记录。如果某项事务处理完成,记录会给出有关错误

13

编程(184/384 484 584)

可使主机模拟编程器功能修改PC从机逻辑

14

探询(184/384 484 584)

可使主机与正在执行任务的从机通信,定期控询该从机是否已完成其程序操作,仅在含有功能13的报文发送后,本功能码才得发送

15

强置多线圈

强置一串连续逻辑线圈的通断

16

预置多寄存器

把具体的二进制值装入一串连续的保持寄存器

17

报告从机标识

可使主机判断编址从机的类型及该从机运行指示灯的状态

18

(884和MICRO 84)

可使主机模拟编程功能,修改PC状态逻辑

19

重置通信链路

发生非可修改错误后,是从机复位于已知状态,可重置顺序字节

20

读取通用参数(584L)

显示扩展存储器文件中的数据信息

21

写入通用参数(584L)

把通用参数写入扩展存储文件,或修改之

22~64

保留作扩展功能备用

 

65~72

保留以备用户功能所用

留作用户功能的扩展编码

73~119

非法功能

 

120~127

保留

留作内部作用

128~255

保留

用于异常应答



51单片机实现实现MODBUS RTU协议
 

一、通讯协议:

1、通讯传送方式:

通讯传送分为独立的信息头,和发送的编码数据。以下的通讯传送方式定义也与MODBUS RTU通讯规约相兼容:

编 码 8位二进制 
起始位 1位 
数据位 8位 
奇偶校验位 1位(偶校验位) 
停止位 1位 
错误校检 CRC(冗余循环码)

初始结构 = ≥4字节的时间

地址码 = 1 字节

功能码 = 1 字节

数据区 = N 字节

错误校检 = 16位CRC码

结束结构 = ≥4字节的时间

地址码:地址码为通讯传送的第一个字节。这个字节表明由用户设定地址码的从机将接收由主机发送来的信息。并且每个从机都有具有唯一的地址码,并且响应回送均以各自的地址码开始。主机发送的地址码表明将发送到的从机地址,而从机发送的地址码表明回送的从机地址。

功能码:通讯传送的第二个字节。ModBus通讯规约定义功能号为1到127。本仪表只利用其中的一部分功能码。作为主机请求发送,通过功能码告诉从机执行什么动作。作为从机响应,从机发送的功能码与从主机发送来的功能码一样,并表明从机已响应主机进行操作。如果从机发送的功能码的最高位为1(比如功能码大与此同时127),则表明从机没有响应操作或发送出错。

数据区:数据区是根据不同的功能码而不同。数据区可以是实际数值、设置点、主机发送给从机或从机发送给主机的地址。

读取保持寄存器(单个和多个,以字为最小单位)

发送命令帧:

设备地址

功能码

地址H

地址L

数据量H

数据量L

CRC H

CRC L

Addr0

3 H

HoldStart

DataNum

CRC高位

CRC低位

帧 长 度:8个字节

设备地址:1~247

功 能 码:3H

数据地址:0~65535   具体范围与相关设备有关         

数    量:1~65535   具体范围与相关设备有关

校 验 码:CRC16校验

 

返回命令帧:

设备地址

功能码

数据量

数据1

数据N

CRC H

CRC L

Addr1

3 H

返回数据的字节数N

Data (1N)

CRC高位

CRC低位

帧 长 度:5+N 个字节

设备地址:1~247

功 能 码:3H

数 据 量:实际的读取数据数量         

数    据:返回数据的意义

aHoldStart

n= DataNum-1

VW a VB a

VWaVB a+1

VW a+nVB a+n

VWa+nVB a+n+1

Data1

Data(2)

Data(N-1)

Data(N)

校 验 码:CRC16校验

 

命令有误:

1)        没有任何返回

2)        返回异议帧

设备地址

功能码

错误信息

CRC H

CRC L

Addr1

83 H

一个字节的错误信息

CRC高位

CRC低位

 

设置保持寄存器(多个,以字为最小单位)

发送命令帧:

 

设备地址

功能码

地址H

地址L

数据量H

数据量L

数据字节数

具体

数据

CRC H

CRC L

Addr0

10 H

HoldStart

DataNum

bytN

1bytN

CRC高位

CRC低位

帧 长 度:9+bytN 个字节

设备地址:1~247

功 能 码:10H

数据地址:0~65535   具体范围与相关设备有关       

数    量:1~122     具体范围与相关设备有关

字 节 数:设置的字节个数 bytN= DataNum×2

数    据:具体的字节数据

校 验 码:CRC16校验

 

返回命令帧:

设备地址

功能码

地址H

地址L

数据量H

数据量L

CRC H

CRC L

Addr1

10 H

HoldStart

DataNum

CRC高位

CRC低位

帧 长 度:8 个字节

设备地址:1~247

功 能 码:10H

数据地址:0~65535   具体范围与相关设备有关    

数    量:1~122     具体范围与相关设备有关

校 验 码:CRC16校验

 

命令有误:

1)    没有任何返回

2)      返回异议帧

地址

功能码

错误信息

CRC H

CRC L

Addr1

90 H

一个字节的错误信息

CRC高位

CRC低位

uint8   testCoil; //用于测试 位地址1
uint16  testRegister; //用于测试 字地址0

uint8 localAddr = 1; //地址
uint8 sendCount;  //发送字节个数
uint8 receCount;  //接收字节个数
uint8 sendPosi;  //发送位置
uint8 sendBuf[32],receBuf[32]; //发送,接收缓冲区   
uint8 checkoutError;    //校验结果
uint8 receTimeOut;    //接收超时

//CRC较验计算
uint16 crc16(uint8 *puchMsg, uint16 usDataLen) 

 uint8 uchCRCHi = 0xFF ; /* 高CRC字节初始化 */ 
 uint8 uchCRCLo = 0xFF ; /* 低CRC 字节初始化 */ 
 uint32 uIndex ; /* CRC循环中的索引 */ 
 while (usDataLen--) /* 传输消息缓冲区 */ 
 { 
  uIndex = uchCRCHi ^ *puchMsg++ ; /* 计算CRC */ 
  uchCRCHi = uchCRCLo ^ auchCRCHi[uIndex] ; 
  uchCRCLo = auchCRCLo[uIndex] ; 
 } 
 return (uchCRCHi << 8 | uchCRCLo) ; 
}//uint16 crc16(uint8 *puchMsg, uint16 usDataLen)


//MODBUS状态机,在主程序中调用即可
void checkComm0Modbus(void)
{
 uint16 crcData;
 uint16 tempData;
 
 if(receCount > 4)
 {
  switch(receBuf[1])
  {

   // 其它的功能码可以参照协议自行编写
   case 1://读取线圈状态(读取点 16位以内)
   case 3://读取保持寄存器(一个或多个)
   case 5://强制单个线圈
   case 6://设置单个寄存器   
     if(receCount >= 8)
     {//接收完成一组数据     
      //应该关闭接收中断
      UCSRB &= ~BIT(7);    
      if(receBuf[0]==localAddr && checkoutError==0)
      {
       crcData = crc16(receBuf,6);
       if(crcData == receBuf[7]+(receBuf[6]<<8))
       {//校验正确
        if(receBuf[1] == 1)
        {//读取线圈状态(读取点 16位以内)
         readCoil();        
        }
        else if(receBuf[1] == 3)
        {//读取保持寄存器(一个或多个)
         readRegisters();
        }
        else if(receBuf[1] == 5)
        {//强制单个线圈
         forceSingleCoil();        
        }
        else if(receBuf[1] == 6)
        {
         //presetSingleRegister();        
        }

       }
      }           
      receCount = 0; 
      checkoutError = 0; 
      UCSRB |= BIT(7);           
     }
     break;
  
   case 15://设置多个线圈
     tempData = receBuf[6]; 
     tempData += 9; //数据个数
     if(receCount >= tempData)
     {//应该关闭接收中断
      UCSRB &= ~BIT(7);      
      if(receBuf[0]==localAddr && checkoutError==0)
      {
       crcData = crc16(receBuf,tempData-2);
       if(crcData == (receBuf[tempData-2]<<8)+ receBuf[tempData-1])
       {
        //forceMultipleCoils();   
       }
      } 
      receCount = 0;
      checkoutError = 0;
      UCSRB |= BIT(7);
     }
     break;
   
   case 16://设置多个寄存器
     tempData = (receBuf[4]<<8) + receBuf[5];
     tempData = tempData * 2; //数据个数
     tempData += 9;
     if(receCount >= tempData)
     {//应该关闭接收中断
      UCSRB &= ~BIT(7);           
      if(receBuf[0]==localAddr && checkoutError==0)
      {
       crcData = crc16(receBuf,tempData-2);
       if(crcData == (receBuf[tempData-2]<<8)+ receBuf[tempData-1])
       {
        presetMultipleRegisters();   
       }
      } 
      receCount = 0;
      checkoutError = 0;
      UCSRB |= BIT(7);
     }
     break;
        
   default:
     break;   
  }
 }
}


你可能感兴趣的:(编程,网络,tcp,扩展,vb,通讯)