Modbus学习记录

夙愿已清 提交于 2020-10-01 03:10:43

Modbus

学习Modbus通信协议前先对一些基础知识进行了解

1、什么是协议?

  协议就是互相之间的约定,如果不让别人知道那就是暗号。现在就来定义一个新的最简单协议。例如,协议: “A” --“LED灭”、“B” --“报警”、 “C” --“LED亮”,设备接收到“A”控制一个LED灭,设备接收到“B”控制报警,设备接收到“A”控制一个LED亮。那么当收到对应的信息就执行相应的动作,这就是协议。
  协议在通信中又分为硬件层协议软件层协议

  1. 硬件层协议解决的是1和0的传输的问题,相当于公路,常见的有485总线、232总线
  2. 软件层协议解决在硬件层上有序的发送数据,相当于交通规则,不然就相当于在公路上乱开车,这是不允许的,常见的有TCP/IP、MODBUS等。

2、一帧数据
  一帧数据指的是你们通过约定每次发送的数据,一帧数据包含帧头,帧尾和校验码组成 数帧头包括接收方主机物理地址的定位以及其它息。帧数据区含有一个数据体。每帧数据之间存在间隔,间隔时间根据具体的时间来定,modbus是每帧数据之间3.5个字符。

3、校验码
  校验码一般放在是由前面的数据通过某种算法得出的,用以检验该组数据的正确性。代码作为数据在向计算机或其它设备进行输入时,容易产生输入错误,为了减少这种输入错误,编码专家发明了各种校验检错方法,并依据这些方法设置了校验码。常用的校验有:累加和校验SUM字节异或校验XOR纵向冗余校验LRC循环冗余校验CRC等。
4、大小端
  这是因为在计算机系统中,是以字节为单位的,每个地址单元都对应着一个字节。一个字节为8位(bit)。在C语言中除了8位的char型之外,还有16位的short型,32位的long型(要看具体的编译器)。另外,对于位数大于8位的处理器,例如16位或者32位的处理器,由于寄存器宽度大于一个字节,那么必然存在着一个如何将多个字节安排的问题。因此就导致了大端存储模式和小端存储模式。具体如下:
大端模式:是指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中。
小端模式:是指数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中。
在这里插入图片描述





Modbus协议简介

  前文说到协议就是互相之间的约定,那么这个约定可以是两个人之间的约定,我叫A设备发0x01,使B设备的LED1亮,我A设备发0x02,使B设备的LED2亮,这样规定的协议只能使A设备和B设备之间进行通信,无法做到与其他设备进行通信,没有统一性,那么有那个协议可以使所有人都是用一个通信协议,不同公司的设备都可以互相通信呢?MODBUS就是这样的一个常见的协议。

Modbus协议特点

  1、支持多种电气接口,如RS-232、RS-485等,还可以在各种介质上传送,如双绞线、光纤、无线等。
  2、单主/多从
  3、Modbus通信总是由主设备发起,当从设备没有收到来自主设备的请求时,不会主动发送数据。
  4、从设备之间不能相互通信
  5、主设备向从设备发送报文有广播和单播两种方式



Modbus一帧数据的基本结构

基本结构:设备码+地址码+功能码+数据区+CRC码
初始结构 = ≥4字节的时间
地址码 = 1 字节
功能码 = 1 字节
数据区 = N 字节
CRC码 = 16位CRC码
结束结构 = ≥4字节的时间





Modbus工作模式

主从模式

1) 主机只能有一个,每个从机必须有一个唯一的地址(0–247);
2) 0地址为广播地址,即主机向0号地址的设备发数据时,也就是要把该数据包发给所有的从设备;
备注:主从模式没有冲突检测,多主模式有(网络通信、CAN总线)

Modbus的传输方式:RTU方式、ASC方式、TCP/IP方式

RTU方式:发送的时候十六进制的方式发送,效率相比于ASC高一倍
ASC方式:发送的时候拆分为ASCII码的方式发送,效率低,但好调试
TCP/IP方式:与RTU类似。
RTU方式发送(也叫十六进制方式)
  例:要发0x03,就发送二进制0 0 0 0 0 0 1 1;
ASC方式发送(ASCII方式)
  例:要发0x03,要先将0x03拆分为十位的‘0’和个位的‘3‘,就是拆成两个字符0和3,0和3有对应的ASCII码为0x30和0x33,依次发出去,即先发送0x30:0011 0000,再发0x33:0011 0011;





Modbus的主机寻址帧格式

1) RTU方式
RTU寻址帧格式

地址码 功能码 数据1 数据2 ……… 数据n CRCL CRCH

  地址码:地址码为通讯传送的第一个字节。这个字节表明由用户设定地址码的从机将接收由主机发送来的信息。并且每个从机都有具有唯一的地址码,并且响应回送均以各自的地址码开始。主机发送的地址码表明将发送到的从机地址,而从机发送的地址码表明回送的从机地址。
  功能码:MODBUS约定了127个功能码,就是主机找从机可能有127个事情要做;
  数据码:通过后面附加的数据来说清除,为了实现这个功能码所需要附加的一些信息,在不同的功能码里面这个数据码的解释是不一样的。
  校验码:CRCL、CRCH这两个为前面的CRC检验的CRC码;
例:03 01 00 13 00 25 0D F6
03一字节代表发给03号设备,01一字节代表读取线圈寄存器,00 13两字节代表从线圈寄存器的第13位开始读取,00 25二字节代表从00 13位开始读取25个位,0D F6代表CRC校验码




 注意点:
  程序编写中会有一个问题在RTU方式中没有起始符和结束符,不知道数据长度到底有多长,所以规定发送接收数据寻址帧时,从机以接收数据停止时间达到3.5个字节以上代表寻找帧发送完成,并开始处理。例如:波特率9600bit/s,所以每位数据传输时间t =(1000000us)/9600 =104us
因为1个字节发送需要10位,所以一个字节需要的时间是T=10t=1040us
所以3.5T=3.5
1040=3645us=4ms,停止时间大于4ms时从机就开始处理数据。
(注意:主机发送时不能停止,并且发送间隔要大于4ms)


2) ASC方式
ASC寻址帧格式

“:“ 功能码 数据1 数据2 ……… 数据n 检验码 13 10
0x3A 两字节 两字节 两字节 ……… 两字节 两字节 结束符 结束符

  帧头为字符“:“(不用拆分);
  功能码与数据和RTU方式发送的东西一样,但要注意发送是要拆分为2个ASCII码的方式发送;
  LRC校验:(地址+功能码+数据1+数据2+……+数据n)之和除以256后取余(这是这个数据就一定小于256),在取反加1,即余数的补码;
  13就是C语言中\r回车,这个10就是C语言中的换行


	备注:因为有实际的起始符和结束符,所以可以很方便的调试,但在实际中不用。

3) TCP/IP方式
  Modbus TCP协议与RTU协议非常类似,主要区别是,在RTU协议上加一个MBAP报文头,同时由于TCP是基于可靠连接的服务,RTU协议中的CRC校验码就不再需要,所以在Modbus TCP协议中是没有CRC校验码,用一句比较通俗的话说就是:Modbus TCP协议就是Modbus RTU协议在前面加上五个0以及一个6,然后去掉两个CRC校验码字节就OK.虽然这句话说得不是特别准确,但是也基本上这就是RTU与TCP之间的区别。
Modbus RTU与Modbus TCP指令对比

MBAP报文头 地址码 功能码 寄存器地址 寄存器数量 CRC校验
Modbus RTU 01 03 01 8E 00 04 25 DE
Modbus TCP 00 00 00 00 00 06 00 03 01 8E 00 04

  对上表的说明:
  由于TCP是基于TCP连接的,不存在所谓的地址码,所以06后面一般都是“00”(当其作为Modbus网关服务器挂接多个RTU设备的时候,数值从01-FF).即“00 03 01 8E 00 04”对应的是RTU中去掉校验码的指令,前面则是五个0以及一个6。其中6表示的是数据长度,即“00 03 01 8E 00 04”有6个字节长度。而当其为操作指令的时候,其指令是“00 00 00 00 00 09 01 10 01 8e 00 01 02 00 00”,其中“00 09”表示后面有9个字节。

Modbus的从设备回应数据包格式

1) 回应的数据包与主机查询的数据包格式是一致的,个别地方做修整;
2) 正常回应时:功能码与主机发送功能码一致;
异常回应时:功能码要在收到的功能码基础上加上128;
(备注:正常回应就是从机控制的传感器检测没问题,异常回应就是检测的传感器有毛病,返回的数据不能用,所以主机检测到返回的功能码(正常的功能码小于128)大于128则代表返回的数据有问题,要做出错误处理)


寄存器与功能码

在对功能码了解前先对Modbus的寄存器进行了解,
这其中有涉及到线圈离散输入保持输入四种寄存器。

  • 线圈寄存器:实际上就可以类比为开关量(继电器状态),每一个bit对应一个信号的开关状态。所以一个byte就可以同时控制8路的信号。比如控制外部8路io的高低。 线圈寄存器支持读也支持写,写在功能码里面又分为写单个线圈寄存器写多个线圈寄存器。对应下面的功能码也就是:0x01 0x05 0x0f
  • 离散输入寄存器:如果线圈寄存器理解了这个自然也明白了。离散输入寄存器就相当于线圈寄存器的只读模式,他也是每个bit表示一个开关量,而他的开关量只能读取输入的开关信号,是不能够写的。比如我读取外部按键的按下还是松开。所以功能码也简单就一个读的 0x02
  • 保持寄存器:这个寄存器的单位不再是bit而是两个byte,也就是可以存放具体的数据量的,并且是可读写的。一般对应参数设置,比如我我设置时间年月日,不但可以写也可以读出来现在的时间。写也分为单个写和多个写,所以功能码有对应的三个:0x03 0x06 0x10
  • 输入寄存器:这个和保持寄存器类似,但是也是只支持读而不能写,一般是读取各种实时数据。一个寄存器也是占据两个byte的空间。类比我我通过读取输入寄存器获取现在的AD采集值。对应的功能码也就一个 **0x04 **
常用的几个功能码如下:
    0x01: 读线圈寄存器
    0x02: 读离散输入寄存器
	0x03: 读保持寄存器
    0x04: 读输入寄存器
	0x05: 写单个线圈寄存器
    0x06: 写单个保持寄存器
	0x0f:  写多个线圈寄存器
    0x10: 写多个保持寄存器

寄存器地址分配如下:
在这里插入图片描述

常用的几个功能码的详细说明

1、01号命令,读可读写数字量寄存器(线圈状态):

计算机发送命令:[设备地址] [命令号01] [起始寄存器地址高8位] [低8位] [读取的寄存器数高8位] [低8位] [CRC校验的低8位] [CRC校验的高8位]

尺子分割线
例:[11][01][00][13][00][25][CRC低][CRC高]

意义如下:

<1>设备地址:在一个485总线上可以挂接多个设备,此处的设备地址表示想和哪一个设备通讯。例子中为想和17号(十进制的17是十六进制的11)通讯。

<2>命令号01:读取数字量的命令号固定为01。

<3>起始地址高8位、低8位:表示想读取的开关量的起始地址(起始地址为0)。比如例子中的起始地址为19。

<4>寄存器数高8位、低8位:表示从起始地址开始读多少个开关量。例子中为37个开关量。

<5>CRC校验:是从开头一直校验到此之前。

设备响应:[设备地址] [命令号01] [返回的字节个数][数据1][数据2]…[数据n] [CRC校验的高8位] [CRC校验的低8位]

尺子分割线
例:[11][01][05][CD][6B][B2][0E][1B] [CRC高] [CRC低]

意义如下:

<1>设备地址和命令号和上面的相同。

<2>返回的字节个数:表示数据的字节个数,也就是数据1,2…n中的n的值。

<3>数据1…n:由于每一个数据是一个8位的数,所以每一个数据表示8个开关量的值,每一位为0表示对应的开关断开,为1表示闭合。比如例子中,表示20号(索引号为19)开关闭合,21号断开,22闭合,23闭合,24断开,25断开,26闭合,27闭合…如果询问的开关量不是8的整倍数,那么最后一个字节的高位部分无意义,置为0。

<4>CRC校验同上。

2、05号命令,写数字量(线圈状态):

计算机发送命令:[设备地址] [命令号05] [需下置的寄存器地址高8位] [低8位] [下置的数据高8位] [低8位] [CRC校验的低8位] [CRC校验的高8位]

尺子分割线
例:[11][05][00][AC][FF][00][CRC高][CRC低]

意义如下:

<1>设备地址和上面的相同。

<2>命令号:写数字量的命令号固定为05。

<3>需下置的寄存器地址高8位,低8位:表明了需要下置的开关的地址。

<4>下置的数据高8位,低8位:表明需要下置的开关量的状态。例子中为把该开关闭合。注意,此处只可以是[FF][00]表示闭合[00][00]表示断开,其他数值非法。

<5>注意此命令一条只能下置一个开关量的状态。

设备响应:如果成功把计算机发送的命令原样返回,否则不响应。

3、03号命令,读可读写模拟量寄存器(保持寄存器):

计算机发送命令:[设备地址] [命令号03] [起始寄存器地址高8位] [低8位] [读取的寄存器数高8位] [低8位] [CRC校验的高8位] [CRC校验的低8位]

尺子分割线
例:[11][03][00][6B][00][03] [CRC高][CRC低]

意义如下:

<1>设备地址和上面的相同。

<2>命令号:读模拟量的命令号固定为03。

<3>起始地址高8位、低8位:表示想读取的模拟量的起始地址(起始地址为0)。比如例子中的起始地址为107。

<4>寄存器数高8位、低8位:表示从起始地址开始读多少个模拟量。例子中为3个模拟量。注意,在返回的信息中一个模拟量需要返回两个字节。

设备响应:[设备地址] [命令号03] [返回的字节个数][数据1][数据2]…[数据n] [CRC校验的高8位] [CRC校验的低8位]

尺子分割线
例:[11][03][06][02][2B][00][00][00][64] [CRC高] [CRC低]

意义如下:

<1>设备地址和命令号和上面的相同。

<2>返回的字节个数:表示数据的字节个数,也就是数据1,2…n中的n的值。例子中返回了3个模拟量的数据,因为一个模拟量需要2个字节所以共6个字节。

<3>数据1…n:其中[数据1][数据2]分别是第1个模拟量的高8位和低8位,[数据3][数据4]是第2个模拟量的高8位和低8位,以此类推。例子中返回的值分别是555,0,100。

<4>CRC校验同上。

4、06号命令,写单个模拟量寄存器(保持寄存器):

计算机发送命令:[设备地址] [命令号06] [需下置的寄存器地址高8位] [低8位] [下置的数据高8位] [低8位] [CRC校验的高8位] [CRC校验的低8位]

尺子分割线
例:[11][06][00][01][00][03] [CRC高] [CRC低]

意义如下:

<1>设备地址和上面的相同。

<2>命令号:写模拟量的命令号固定为06。

<3>需下置的寄存器地址高8位,低8位:表明了需要下置的模拟量寄存器的地址。

<4>下置的数据高8位,低8位:表明需要下置的模拟量数据。比如例子中就把1号寄存器的值设为3。

<5>注意此命令一条只能下置一个模拟量的状态。

设备响应:如果成功把计算机发送的命令原样返回,否则不响应。

5、16号命令,写多个模拟量寄存器(保持寄存器):

计算机发送命令:[设备地址] [命令号16] [需下置的寄存器地址高8位] [低8位] [数据数量高8位] [数据数量低8位] [下置的数据高8位] [低8位][……][……] [CRC校验的高8位] [CRC校验的低8位]

例:[11][16][00][01][00][01][00][05] [CRC高] [CRC低]

意义如下:

<1>设备地址和上面的相同。

<2>命令号:写模拟量的命令号固定为16。

<3>需下置的寄存器地址高8位,低8位:表明了需要下置的模拟量寄存器的地址。

<4>需下置的数据数量高8位,低8位:表明了需要下置的数据数量,这里为1。

<5>下置的数据高8位,低8位:表明需要下置的模拟量数据。比如例子中就把1号寄存器的值设为5。

设备响应:如果成功把计算机返回的如下命令,否则不响应。

设备响应:[设备地址] [命令号16] [需下置的寄存器地址高8位] [低8位] [数据数量高8位] [数据数量低8位] [CRC校验的高8位] [CRC校验的低8位],如上例返回:

[11][16][00][01][00][01] [CRC高] [CRC低]

对Modbus的基本开发流程

  可以使用RTU、TCP这两种最常用的模式。

主设备段开发流程

在这里插入图片描述

从设备段开发流程

在这里插入图片描述

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!