Warning: mysqli_num_fields() expects parameter 1 to be mysqli_result, boolean given in /www/wwwroot/dev.zhalaotie.com/wp-includes/wp-db.php on line 3215

Warning: mysqli_num_fields() expects parameter 1 to be mysqli_result, boolean given in /www/wwwroot/dev.zhalaotie.com/wp-includes/wp-db.php on line 3215
通讯总结 陈志文 – 扎老铁
Warning: mysqli_num_fields() expects parameter 1 to be mysqli_result, boolean given in /www/wwwroot/dev.zhalaotie.com/wp-includes/wp-db.php on line 3215

Warning: mysqli_num_fields() expects parameter 1 to be mysqli_result, boolean given in /www/wwwroot/dev.zhalaotie.com/wp-includes/wp-db.php on line 3215
Warning: mysqli_num_fields() expects parameter 1 to be mysqli_result, boolean given in /www/wwwroot/dev.zhalaotie.com/wp-includes/wp-db.php on line 3215
class="post-52923 post type-post status-publish format-standard hentry">

通讯总结 陈志文

简要说明

就我所理解的ModBus不难。就是一个数据格式约定,就像写个合同要申明“甲方、乙方”,用个变量要申明类型,开个发票要有抬头一样。它就是一个数据格式的约定。

项目里我觉得应该了解的基础信息,请参看“通讯分析.docx”。

个人理解上,这个项目的modbus通讯其实就是一个“翻译”的过程,将人类的操作翻译为Modbus数据,反之将modbus数据翻译成人类能接受的信息,具体看以下两个图。

普通命令:

广播命令:

命令分析

在阅读本部份的时候,请确定已经理解“通讯分析.docx”,并且推荐使用厂家的Demo连上真实设备,一边看,一边动手实操验证结果,如果发生结果和本文不一致的情况,以实操结果为准。所有转换(编、解码)的方法都在源码里,不要纠结结果是怎么出来的,因为我也不知道:)。

读取命令和回复:

单个读:

目标:读取2号机的“当前LED光强”属性

首先找到“当前光强属性”的起始地址和线圈数量(通过产品对应的modbusData.xmls电子表格)

由上图可以看到,起始的寄存器地址为100,线圈数量为1(2 byte)

根据modbus ,要读的是2号机,那么地址域就是2,是读取操作,功能码就是3,因此得到一个功能是读取的modbus数据结构的发送数据:

02 03 00 64 00 01 C5 E6

从左往右分析:

02 :地址位,2号机。

03:功能码,读取。

00 64:寄存器地址位置,把 00 64(0x0064) 为16进制,转换为10进制为100。

00 01:线圈的数量,16进制,转换为10进制为1。

C5 E6:这个是校验码,由算法生成。

当命令发出后,会收到设备回复:

02 03 02 00 64 FD AF

从左往右分析:

02:地址位,2号机。

03:功能码,读取。

02:此次回复的数据长度,单位byte,也就是说接下来的两位byte是设备回复的数据(一个16进制数占用一个byte,也就是接下来的两个16进制数就是数据)。

00 64:回复的数据,00 64 (0x0064)为16进制数,转换为10进制为100。

FD AF:校验码,算法生成。

通过上述的一个流程,发送了modbus格式的请求,也接收到了modbus的格式的回复,通过编、解码我们得到了一个值100(转换后),那么当前2号机的“当前光强”属性为100。

连续读(批量读):

目标:读取2号机从“从机地址”到“备用106”的所有属性。

发送数据:

02 03 00 00 00 78 45 DB

从左往右分析:

02:地址位,2号机。

03:功能码,读取。

00 00:寄存器地址,从哪个位置开始读,00 00(0x0000)是“从机地址”属性的位置。

00 78:读取的线圈数量,00 78(0x0078)换为10进制为120,查看一下产品属性的电子表格,看看第120个线圈是不是属性“备用106”?

45 DB:校验码。

当命令发出后会收到设备回复:

02 03 F0 00 00 00 02 00 00 00 03 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 03 01 06 01 09 01 12 01 00 00 20 17 03 02 18 48 00 03 00 00 00 00 00 00 00 01 00 00 00 00 00 3C 00 A0 00 78 00 FF 00 3C 00 A0 00 0A 00 50 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 00 00 00 00 00 96 00 64 00 02 00 00 00 00 00 96 00 64 00 02 00 00 00 00 00 96 00 64 00 02 00 01 00 01 02 EE 00 64 00 02 00 00 00 20 00 20 00 00 00 00 00 00 00 32 00 FF 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 00 01 02 EE 00 64 00 02 00 64 00 00 00 00 00 00 00 11 00 00 00 00 00 00 00 00 00 00 C2 11 02 EE 64 20 00 00 00 00 00 00 00 00 00 00 47 D3

从左往右分析:

02:地址位,2号机。

03:功能码,读取。

F0:数据长度,F0转为10进制为240,也就是接下来的240个byte为回复的数据。

00 …(240个16进制数)…00:为回复的数据,具体分析见下文。

47 D3:校验码。

240个16进制数数据分析:

先来看个图:

图中圈出了线圈数量,对的,这就是分析的重点。

看电子表格,先把电子表格里的属性按“开始地址”排一下序,正序(由小到大)。排序后的第一个属性为“从机地址”,再看“从机地址”属性里的线圈数量,我们知道是2,2个线圈等于4个byte,所以可以做以下判断:

F0之后,第1-4位数据(4个byte)为属性“从机地址”的值。“00 00 00 02”(0x00000002),转为10进制数,为2,也就是2号机。

排序后的第二个属性为“波特率索引”,它的线圈数为1,也就是2个byte,所以可以做以下判断:

F0之后,第5-6位数据(2个byte)为属性“波特率索引”的值。(1-4位数据为第一个属性的值)。

写入命令

单个写:

目标:读取2号机的“当前LED光强”属性,写入的值为“100”

02 10 00 64 00 01 02 00 64 BB 6F

从左往右分析:

02:地址位,2号机

10:功能码,写入。

00 64:寄存器地址,同“读取”。

00 01:写入的线圈数量。

02:写入的数据长度,一个线圈2个byte。

00 64:写入的数据16进制,转为10进制值为100。

BB 6F:校验

写入回复:

02 10 00 4F 00 01 30 2D

回复未分析。

批量写:

通过对读取单个属性和连续读的的理解,是的,批量写(连续写)的编码方式也是类似的,可以从单个写推导出来。

发送

02 10 00 64 00 03 06 00 50 00 03 00 64 90 CF

从左往右分析:

02:地址位,2号机

10:功能码,写入

00 64:寄存器地址,同“读取”。

00 03:写入的线圈数量。

06 :写入的数据长度(byte)。

00 50 00 03 00 64:写入的数据,这里有6个byte,数据编码方式和连续读是一样的。首先看寄存器地址00 64,转为10进制为100,在电子表格里找到“开始地址”为100对应的属性(‘当前LED光强’),该属性的线圈数量为1(2 byte),所以此数据的1-2位为写入该属性的值“00 50”(0x0050)。

接下来看“当前LED光强”的一下个属性(‘当前LED闪烁频率’),线圈数为1,那么对应的数据就是“00 03”。

再看“当前LED闪烁频率”的下一个属性(‘内腔温度’),线圈数为1,那么对应的数据就是“00 64”。

90 CF:校验码。

回复

02 10 00 64 00 03 C1 E4

回复未分析

广播命令

广播命令没有回复。

单属性写入广播:

看“单个写”,只不过地址位固定为“00”。

批量写入广播:

看“批量写”,地址位固定为“00”。

定向批量写入广播:

看“批量写”,地址位固定为“00”,寄存器地址必须从“组号”属性开始,其它相同。

另:批量写入的官方文档:“MODBUS关于批量写控制命令说明.pdf”

其它事项

在阅读本部份的时候,请确定已经理解“通讯分析.docx”、“MODBUS关于批量写控制命令说明.pdf”以及对每个产品的modbusData电子表格有印象。

线圈:

也叫做寄存器,一个线圈看起来是2个byte,一个byte是8位bit。

大小端:

就我的认知看来,2个(或2个以上)的byte才会出现大小端隐患。比如说,C#里的int32类型,32位,需要用4个byte才能表示一个完整的数据,看下图实例:

而在当前这个项目中,编、解码操作的都是最小单位byte,所以大小端问题,理论上应该可以忽视(也不排除会有抽风现象,知道原理以后分析、解决问题就应该很简单了。哇咔咔咔)。

高低位:

数字12,高位就是1,低位就是2。产品ls810组号“1000171”(01000171),根据电子表格《modbusData-乱码2017-3-2.xlsx》高位就是0100,低位就是0171。

另:在后续2017-4-X的modbusData电子表格中,rsv3这个属性又变成了“保留”状态,以3月的为准还是4月的为准?现阶段通讯层代码把rsv3视为产品组号高4位。

线圈数量过多造成的数据被截断问题:

经过和厂家沟通,厂家的回复是“应该能把数据读完”,造成这个问题的原因是,一帧数据的最大上限好像是256个byte。属性最多的一个产品的地址结尾为“128”,128*2 =256 刚好满足,但是这个256是modbus里的“数据”长度,而不是整个数据帧的长度。所以需要再测试一下。

测试的方法:当了解了modbus和详细看了本文档前面的内容以后,我觉得应该能得一个测试方案,所以在这里就不详述了。

测试后可能有两种结果:

  1. 在开发的时候我这边搞错了,不存在数据溢出问题:

处理方式:

修正一下代码,不再有120个线圈的限制即可。

  1. 确实存在这个问题:

处理方式:不需要任何处理,因为当前代码已经对这种情况进行了处理,但是有一个缺陷,当试图进行“定向广播”时,要注意属性的数量,并且,超出范围的属性不可能被“定向广播”所控制。话说回来,漏掉的属性基本上都是“备用X”的,其中的取舍,依据情况进行决断吧。

 

总结 陈志文

发表评论

电子邮件地址不会被公开。