ModBus通讯协议和libmodbus库介绍

 ModBus通讯协议

简介
Modbus由MODICON公司于1979年开发,是一种工业现场总线协议标准。1996年施耐德公司推出基于以太网TCP/IP的Modbus协议:ModbusTCP。

Modbus协议是一项应用层报文传输协议,包括ASCII、RTU、TCP三种报文类型。标准的Modbus协议物理层接口有RS232、RS422、RS485和以太网接口,采用master/slave方式通信。

ModbusTCP数据帧
ModbusTCP的数据帧可分为两部分:MBAP+PDU。

报文头MBAP
MBAP为报文头,长度为7字节,组成如下:

事务处理标识    协议标识    长度    单元标识符
2字节                 2字节        2字节    1字节
 

内容    解释
事务处理标识    可以理解为报文的序列号,一般每次通信之后就要加1以区别不同的通信数据报文。
协议标识符    00 00表示ModbusTCP协议。
长度    表示接下来的数据长度,单位为字节。
单元标识符    可以理解为设备地址。

 

ModBus通讯协议和libmodbus库介绍_第1张图片

 


帧结构PDU
PDU由功能码+数据组成。功能码为1字节,数据长度不定,由具体功能决定。

功能码

Modbus的操作对象有四种:线圈、离散输入、保持寄存器、输入寄存器。

对象    含义
线圈    PLC的输出位,开关量,在Modbus中可读可写
离散量    PLC的输入位,开关量,在Modbus中只读
输入寄存器    PLC中只能从模拟量输入端改变的寄存器,在Modbus中只读
保持寄存器    PLC中用于输出模拟量信号的寄存器,在Modbus中可读可写
根据对象的不同,Modbus的功能码有:

功能码    含义
0x01    读线圈
0x05    写单个线圈
0x0F    写多个线圈
0x02    读离散量输入
0x04    读输入寄存器
0x03    读保持寄存器
0x06    写单个保持寄存器
0x10    写多个保持寄存器
说明更详细的表

代码    中文名称    英文名    位操作/字操作    操作数量
01    读线圈状态    READ COIL STATUS    位操作    单个或多个
02    读离散输入状态    READ INPUT STATUS    位操作    单个或多个
03    读保持寄存器    READ HOLDING REGISTER    字操作    单个或多个
04    读输入寄存器    READ INPUT REGISTER    字操作    单个或多个
05    写线圈状态    WRITE SINGLE COIL    位操作    单个
06    写单个保持寄存器    WRITE SINGLE REGISTER    字操作    单个
15    写多个线圈    WRITE MULTIPLE COIL    位操作    多个
16    写多个保持寄存器    WRITE MULTIPLE REGISTER    字操作    多个
PDU详细结构
0x01:读线圈

在从站中读1~2000个连续线圈状态,ON=1,OFF=0

请求:MBAP 功能码 起始地址H 起始地址L 数量H 数量L(共12字节)
响应:MBAP 功能码 数据长度 数据(一个地址的数据为1位)
如:在从站0x01中,读取开始地址为0x0002的线圈数据,读0x0008位
00 01 00 00 00 06 01 01 00 02 00 08
回:数据长度为0x01个字节,数据为0x01,第一个线圈为ON,其余为OFF
00 01 00 00 00 04 01 01 01 01
0x05:写单个线圈

将从站中的一个输出写成ON或OFF,0xFF00请求输出为ON,0x000请求输出为OFF

请求:MBAP 功能码 输出地址H 输出地址L 输出值H 输出值L(共12字节)
响应:MBAP 功能码 输出地址H 输出地址L 输出值H 输出值L(共12字节)
如:将地址为0x0003的线圈设为ON
00 01 00 00 00 06 01 05 00 03 FF 00
回:写入成功
00 01 00 00 00 06 01 05 00 03 FF 00
0x0F:写多个线圈

将一个从站中的一个线圈序列的每个线圈都强制为ON或OFF,数据域中置1的位请求相应输出位ON,置0的位请求响应输出为OFF

请求:MBAP 功能码 起始地址H 起始地址L 输出数量H 输出数量L 字节长度 输出值H 输出值L
响应:MBAP 功能码 起始地址H 起始地址L 输出数量H 输出数量L
0x02:读离散量输入

从一个从站中读1~2000个连续的离散量输入状态

请求:MBAP 功能码 起始地址H 起始地址L 数量H 数量L(共12字节)
响应:MBAP 功能码 数据长度 数据(长度:9+ceil(数量/8))
如:从地址0x0000开始读0x0012个离散量输入
00 01 00 00 00 06 01 02 00 00 00 12
回:数据长度为0x03个字节,数据为0x01 04 00,表示第一个离散量输入和第11个离散量输入为ON,其余为OFF
00 01 00 00 00 06 01 02 03 01 04 00
0x04:读输入寄存器

从一个远程设备中读1~2000个连续输入寄存器

请求:MBAP 功能码 起始地址H 起始地址L 寄存器数量H 寄存器数量L(共12字节)
响应:MBAP 功能码 数据长度 寄存器数据(长度:9+寄存器数量×2)
如:读起始地址为0x0002,数量为0x0005的寄存器数据
00 01 00 00 00 06 01 04 00 02 00 05
回:数据长度为0x0A,第一个寄存器的数据为0x0c,其余为0x00
00 01 00 00 00 0D 01 04 0A 00 0C 00 00 00 00 00 00 00 00
0x03:读保持寄存器

从远程设备中读保持寄存器连续块的内容

请求:MBAP 功能码 起始地址H 起始地址L 寄存器数量H 寄存器数量L(共12字节)
响应:MBAP 功能码 数据长度 寄存器数据(长度:9+寄存器数量×2)
如:起始地址是0x0000,寄存器数量是 0x0003
00 01 00 00 00 06 01 03 00 00 00 03
回:数据长度为0x06,第一个寄存器的数据为0x21,其余为0x00
00 01 00 00 00 09 01 03 06 00 21 00 00 00 00
0x06:写单个保持寄存器

在一个远程设备中写一个保持寄存器

请求:MBAP 功能码 寄存器地址H 寄存器地址L 寄存器值H 寄存器值L(共12字节)
响应:MBAP 功能码 寄存器地址H 寄存器地址L 寄存器值H 寄存器值L(共12字节)
如:向地址是0x0000的寄存器写入数据0x000A
00 01 00 00 00 06 01 06 00 00 00 0A
回:写入成功
00 01 00 00 00 06 01 06 00 00 00 0A
0x10:写多个保持寄存器

在一个远程设备中写连续寄存器块(1~123个寄存器)

请求:MBAP 功能码 起始地址H 起始地址L 寄存器数量H 寄存器数量L 字节长度 寄存器值(13+寄存器数量×2)
响应:MBAP 功能码 起始地址H 起始地址L 寄存器数量H 寄存器数量L(共12字节)
如:向起始地址为0x0000,数量为0x0001的寄存器写入数据,数据长度为0x02,数据为0x000F
00 01 00 00 00 09 01 10 00 00 00 01 02 00 0F
回:写入成功
00 01 00 00 00 06 01 10 00 00 00 01

 

 通讯协议又称通信规程,是指通信双方对数据传送控制的一种约定。约定中包括对数据格式,同步方式,传送速度,传送步骤,检纠错方式以及控制字符定义等问题做出统一规定。

 通俗点来讲,ModBus约定了起停电机,主机要分别发送什么命令给从机。ModBus规定主从机之间数据的交互,需要遵循什么样的格式,如何保证数据在传输过程中不发生冲突。只要都遵循这个协议,那么不同厂家的主从机就可以共用了。
ModBus一般是工作在一主多从的场景,还是这个图:

ModBus通讯协议和libmodbus库介绍_第2张图片

  主机和从机之间的连线不一定是非要485来作为载体,也可以是IIC,SPI。因为ModBus是软件层的协议,它既可以规约485硬件接线方式,也可以规约其他硬件接线方式。很多资料会写”基于RS-485的ModBus通讯协议”,意思是底层的0、1数据是通过RS-485方式去传输的,0、1的意义则是通ModBus去解析的。注意,硬件协议可以确保数据得以传输出去,软件协议保障数据的有序传输,数据不会发生冲突。

ModBus规定:

(1) 主从模式
有的协议规定是多主模式,意思是系统中的设备都是主机,它们并没有主从之分,任何时刻,谁想发送数据都可以往总线上发送,例如网络通信、CAN总线通讯,自然它们自有一套防止数据冲突机制,485由于不具备冲突检测的硬件机制,所以它必须遵循主从模式。主从模式的原则是,整个系统只能有一个主机,每一个从机都必须有一个唯一的地址

(2) 从机的地址是作为每个从机的唯一标识

地址取值是0-247,0号地址表示广播地址,广播地址由主机保留,当主机向0号地址发数据包的时候,每一个从机设备都会收到数据包。也就是说,当主机发出的寻址帧的地址是0的时候,所有从机都要执行主机要求的动作。按理说,从机收到主机的寻址帧之后,是要做出应答包的,但是现在是0号地址,也就是要回的话每台从机都要回,那么肯定会造成RS-485通讯线上的数据混乱,因此所有从机在主机发0号地址时候不予返回数据包应答。

从机的地址有两个作用
a. 主机向目标从机发寻址帧时其地址部分为从机地址,这样主机才可以检索到目标从机
b. 对于主机的目标从机,当收到主机发来的非0地址时,要做出数据包应答,假设从机要返回数据包给主机,自然是要把数据包放到RS-485总线上,因为每台从机,其物理连线是在一起的,所以这就会造成其他从机认为数据是要发送给它的现象,所以在从机回复主机的数据包中,加上从机自身的地址,那么其他从机读取到这个地址值跟自己的地址不相同,就不会去响应了。

(3) ModBus数据包的格式
主机要寻找某台从机,需要发出相应格式的信息,这就需要谈到ModBus的两种传输方式:
a. RTU传输方式
RTU实际上也成为二进制方式。假设主机要发送0x23,那就是发送0010 0011,按照485通讯协议,先发高位,即1100 0100。前后分别加上起始、停止位: “起始位 1100 0100 停止位”共10位数据
b. ASC传输方式
同样要发送0x23,它是十六进制数,会将其拆成十位的’2’和个位的’3’,将它们的asc码依次发出去,’0’的asc码是0x32,’3’的asc是0x33,转为二进制为0011 0010和0011 0011,同样要加上停止、起始位,共20位数据
很明显,asc传输方式比较低,但是由于它传输的是asc码,所以可以利用一些串口终端将其数值打印出来。

特别提醒,RS-485硬件协议决定,对于每一个字节数据的传输是先发高位,再发地位,所以假设数组u8型数组revArr[2]存放着接收到的数据,那么接收端解析数据应该是u16型data = revArr[0] * 256 + revArr[1]。

(4)Modbus具有以下几个特点:

(1)标准、开放,用户可以免费、放心地使用Modbus协议,不需要交纳许可证费,也不会侵犯知识产权。目前,支持Modbus的厂家超过400家,支持Modbus的产品超过600种。

(2)Modbus可以支持多种电气接口,如RS-232、RS-485等,还可以在各种介质上传送,如双绞线、光纤、无线等。

(3)Modbus的帧格式简单、紧凑,通俗易懂。用户使用容易,厂商开发简单。

【RTU协议帧数据】

Modbus有两种通信传输方式,一种是ASCII模式,一种是RTU模式。由于ASCII模式的数据字节是7bit数据位,51单片机无法实现,而且应用也相对较少,所以这里我们只用RTU模式。两种模式相似,会用一种另外一种也就会了。一条典型的RTU数据帧如图所示。

和我们实用串口通信程序类似,我们一次发送的数据帧必须是作为一个连续的数据流进行传输。我们在实用串口通信程序中采用的方法是定义30ms,如果接收到的数据超过了30ms还没有接收到下一个字节,我们就认为这次的数据结束。而Modbus的RTU模式规定不同数据帧之间的间隔是3.5个字节通信时间以上。如果在一帧数据完成之前有超过3.5个字节时间的停顿,接收设备将刷新当前的消息并假定下一个字节是一个新的数据帧的开始。同样的,如果一个新消息在小于3.5个字节时间内接着前边一个数据开始的,接收的设备将会认为它是前一帧数据的延续。这将会导致一个错误,因此大家看RTU数据帧最后还有16bit的CRC校验。

起始位和结束符:前后都至少有3.5个字节的时间间隔,起始位和结束符实际上没有任何数据,T1-T2-T3-T4代表的是时间间隔3.5个字节以上的时间,而真正有意义的第一个字节是设备地址。

设备地址:在多机通信的时候,数据那么多,我们依靠什么判断这个数据帧是哪个设备的呢?没错,就是依靠这个设备地址字节。每个设备都有一个自己的地址,当设备接收到一帧数据后,程序首先对设备地址字节进行判断比较,如果与自己的地址不同,则对这帧数据直接不予理会,如果如果与自己的地址相同,就要对这帧数据进行解析,按照之后的功能码执行相应的功能。如果地址是0x00,则认为是一个广播命令,就是所有的从机设备都要执行的指令。

功能代码:在第二个字节功能代码字节中,Modbus规定了部分功能代码,此外也保留了一部分功能代码作为备用或者用户自定义,这些功能码大家不需要去记忆,甚至都不用去看,直到你有用到的那天再过来查这个表格即可。

CRC校验:CRC校验是一种数据算法,是用来校验数据对错的。CRC校验函数把一帧数据除最后两个字节外,前边所有的字节进行特定的算法计算,计算完后生成了一个16bit的数据,作为CRC校验码,添加在一帧数据的最后。接收方接收到数据后,同样会把前边的字节进行CRC计算,计算完了再和发过来的CRC的16bit的数据进行比较,如果相同则认为数据正常,没有出错,如果比较不相同,则说明数据在传输中发生了错误,这帧数据将被丢弃,就像没收到一样,而发送方会在得不到回应后做相应的处理错误处理。

 

通过上面个可以了解基于libmodbus库的开发流程,而libmodbus比较牛的地方,不仅仅可以实现modbus RTU的支持,它还支持modbus TCP,而且由于采用了所谓的后端函数,也就是提供了 标准 通用的 API接口,所以开发modbus TCP的主从设备,与开发modbus RTU的流程基本上是一致的,不同的地方也只是一些 细微的函数区别,这个可以参考libmodbus源文件中的test文件夹提供的几个案例框架。

     总结:libmodbus这个modbus协议库,对于进行linux应用下的modbus通信开发,简直就是一款神器,能够极大的 方便应用程序的开发,当我们了解了其中的构造原理后,我们能够很容易的开发各种modbus通信应用程序,在抛开稳定性的前提下,它的优势是比 freemodbus要强很多的,freemodbus需要对源码理解的特别深才行,因为需要将freemodbus的源码嵌入到我们的系统代码中,而且freemodbus目前还只是支持 modbus slave,也就是从机。而libmodbus最擅长的就是modbus master,如果libmodbus稳定性也可以的话,我们就可以在一些复杂的 modbus 网关中使用libmodbus,实现一些复杂的modbus应用场景。

   libmodbus不适合应用于非Linux/Windows 环境下的开发,比如就不适合小型嵌入式硬件STM32开发,为什么呢?这个跟大小关系反而不大,主要是因为libmodbus的各种API函数的实现是通过Linux、Windows的各种API函数,比如说在Linux进行modbus rtu开发时,串口的接收采用了select机制的,而STM32的IDE环境,很显然是不支持这个的,所以在小型系统中,还是老老实实的使用freemodbus吧,这也算是freemodbus的优点了,freemodbus之所以既能在非linux下使用,又能在linux下移值,是通过不同的代码函数来实现的,比如,非linux下,通过定时器计时+串口中断接收数据,而linux下则是通过select机制。这个看它的源码就 很容易发现了,所以需要程序员对源码很熟悉。
 

你可能感兴趣的:(协议)