Modbus-RTU协议 & FreeModbus的移植

1. Modbus-RTU协议

modbus-rtu分为主机(Master)和从机(Slaver)。

主机就是用来读写从机数据的:它通过发送指令来读写从机的数据;之后,接收从机的返回信息,以评估指令的执行情况。

从机就是用来存储数据的:只有对应的从机收到了我需要执行动作的指令,才会执行相应的操作,并将执行结果返回给主机。不同的指令,不同的执行情况,返回的信息都不相同。

以上内容概括了Modbus-RTU的通信过程。

Modbus是异步半双工协议,与I2C类似,同一时间只能由单独一端(主机或从机)占用总线

四种数据类型

虽然概括来讲,Modbus-RTU协议就是主机读写各从机数据 那么,主机读写的都是什么类型的数据呢?

Modbus-RTU协议规定,从机有四种数据类型,分为两个大类

第一类是单位为 bit的数据:

线圈 Coils;每个coils对应着一个bit,所有的coils都是可读可写的

离散输入 Discrete input;它也代表着1个bit,只不过它是只读的,这是它和coils的区别。

第二类是单位为word(2个字节)的数据:

保持寄存器 Holding register;可读可写

输入寄存器 Input register;只读 从上面来看:带有“register”字样的数据类型,是以word为单位的,否则是以bit为单位的 带有“input”字样的,是只读类型

Modbus-RTU的指令

既然已经明确了读写的数据类型,那么具体操作是怎样的呢?或者说发送、返回指令的格式? 这一部分网上的内容众说纷纭。为了保证协议符合标准,我们最好参考官方Modbus协议规范 (虽说是英文文档,但四级水平足矣^_^)

文档中非常详细地定义了各种指令的具体格式。借此即可确定下来各个指令的详细内容,进而 使得从机执行特定的操作。下面仅举一例——怎样令主机读取001号从机的第5-第14线圈的内容?

想令主机读取1号从机,地址码即为0x01;0x01就是发送指令的首个字节。

第二个字节是功能码。现在使用读取从机线圈的功能,功能码即为0x01。

第3、4字节是起始地址(2个字节的地址)。我们从5号线圈开始,即起始地址是0x0004(每个从机的线圈编号是从1开始的,但对应地址是从0开始,即地址比编号-1)。因此0x00、0x04是发送指令的第3、4字节。

第5、6字节是读取的线圈数,这里是10个,即0x000A。因此0x00、0x0A分别是发送指令的第5、6字节。

第7、8字节是CRC校验码1,根据前面所有字节的内容,通过计算,可得CRC校验码为0xFDCC; 因此0xFD、0xCC是发送指令的最后2个字节。

至此可确定主机发送指令的全部内容:

01

01

00

04

00

0A

FD

CC

从机地址

功能码

起始地址H

起始地址L

数量H

数量L

CRC校验H

CRC校验L

由此,主机通过总线将此指令发给所有的从机。 指令的收发建议使用串口完成。且每帧(即每个指令)之间至少要有3.5Byte传输时间的间隔(即传输28个bit所需的时间长度),用于区分不同的帧。这个间隔是标准协议的规定,我们需要遵守。

现在假设每个从机都已经完整无误地收到了上面的指令,那么所有的从机都会执行这个命令吗?然后它们都会向主机返回各自的执行情况吗?

不,从机会将指令的地址码0x04和自身的地址码匹配。只有两者相同时,或者指令中的地址码是广播地址0x00时,从机才会响应指令。

对于地址码是0x00的广播指令,由于所有的从机都会执行这一命令,因此它们都不会向主机返回任何信息,避免出现错误。所以广播指令适合写从机,而不适合读从机。

如果从机正确地执行了读线圈的命令,将返回怎样的结果呢?或者说返回帧是怎样的呢?

按照官方标准,返回帧的首字节是从机地址,即0x01第2字节是功能码,即0x01

第3字节是读取的字节数。由于读取了10个线圈,且按协议标准,不足1字节,按1字节计算,不足的部分补零。因此,字节数是2,即0x02

第4、5字节是读取的具体内容。假设001号从机的0-15线圈的内容是0010 0101 0110 1011。

现暂定从机以uint8_t数组的形式存储线圈的内容,即缓存区的内容为Coils_Buf[0] = 0xA4, Coils_Buf[1] = 0xD6(这里要注意缓存区每个字节的低二进制位对应线圈的低地址,高二进制位对应高地址;不理解的话可以将缓存区的内容展开为二进制位,和线圈的内容对比一下)。

现在要将第4-第13个二进制位在返回帧中以2个字节显示,即0110 1010(地址11 - 4的线圈内容),000000 01(高6位的补零&地址是13 - 12的线圈内容)。因此, 第4、第5字节分别是0x6A、0X01。

最后2个字节是CRC校验位。与之前一样的方法,可得此帧的CRC校验字节是0x56、0x9C。

至此,返回帧的全部内容已被确定:

01

01

02

6A

02

56

9C

从机地址

功能码

字节数

内容L

内容H

CRC校验H

CRC校验L

从机会在数据读取完毕后将此帧向主机返回,主机由此读到了从机线圈的内容。至此,01H指令运行完毕。

2. FreeModbus的移植

什么是FreeModbus这里不再赘述。

本文基于Github上的FreeModbus 程序中使用了RT-Thread操作系统

移植过程时将Modbus协议栈复制到自己的工程模板下即可,后序的操作可以参照其附带的说明。 在收发指令时,最好通过串口+DMA的方式一次性地收发整个帧,而不是原程序中逐个字节地收发。否则所发送的帧容易被理解成多个单字节的帧,导致通信失败。

#endif


  1. 这里的CRC校验码是对前面所有字节进行特定的计算而得出的,用于检测数据是否无失真地传输。从机收到指令时,会对指令再次进行CRC计算。一旦两次计算的结果不同,说明指令在传输过程中发生了错误。 ↩︎