前面咱们介绍了ModBusTcp协议。今天咱们接着来介绍ModBusRtu协议。和ModBusTcp不一样的是ModBusRtu基于串口通讯,ModBusTcp是基于Tcp以太网通讯。
因此咱们在讲解ModBusRtu协议以前会先介绍下串口通讯。html
串口出如今1980年先后,当初主要目的是用来作电脑外设设备的链接,如鼠标、键盘等。如今最新的电脑慢慢的取消了原始的串口接口,不过依然普遍用于工控和测量等设备。git
串口通讯指的是串口按位(bit)发送和接收字节,串口通讯参数主要有波特率、数据位、中止位、校验位。github
波特率表达的是串口通讯的速率,一秒钟内传送的信号单元(码元)个数。信号单元通常包含10位(7个数据位、1个校验位、1到2个中止位)。注意:波特率和距离成反比算法
通讯中实际的数据,有效值为六、7和8。ide
用来表示单个包的最后一位,有效值为一、1.5和2。中止位可用来表示传输的结束和校订时钟同步。注意:中止位的位数越多,时钟同步的容忍程度越大,可是数据传输率会越慢。测试
奇偶校验做为通讯中的检错方式,若是发现错误则从新发送。code
示例数据 | 偶校验位 | 奇校验位 |
---|---|---|
0000000 | 00000000 | 00000001 |
1010001 | 10100011 | 10100010 |
1101001 | 11010010 | 11010011 |
1111111 | 11111111 | 11111110 |
从上能够看出奇偶校验就是在数据最后加一位,使数据中的1的数量保持偶数或奇数。htm
比特率是咱们经常使用来表达宽带速率的一种方法。看上去和波特率很像,若是波特率的信号码元只传1比特(bit),那么它们之间是相等的。若是波特率的信号码元传10比特,那么波特率是比特率的10倍。因此,波特率和比特率表达的意义是不同的,不要搞混了。对象
Mbps和Mbit/s等效、kbit/s和kbps等效、bps和bit/s等效
1Mbps(Mbit/s) = 11024kbit(kbit/s) = 11024*1024bps(bit/s),注意他们的单位都是bit(比特),而不是byte(字节),因此实际下载速度要除以八。1024 / 8 = 128 kb/s。blog
CRC,Cyclic Redundancy Check循环冗余检验,是基于数据计算一组效验码,用于核对数据传输过程当中是否被更改或传输错误。而ModBusRtu用到的是其中的CRC16校验。
其计算原理,可参考 1、2、3
如下是CRC16反向算法,经测试可用于ModBusRtu的CRC计算。
public class CRC16 { /// <summary> /// 验证CRC16校验码 /// </summary> /// <param name="value">校验数据</param> /// <param name="poly">多项式码</param> /// <param name="crcInit">校验码初始值</param> /// <returns></returns> public static bool CheckCRC16(byte[] value, ushort poly = 0xA001, ushort crcInit = 0xFFFF) { if (value == null || !value.Any()) throw new ArgumentException("生成CRC16的入参有误"); var crc16 = GetCRC16(value, poly, crcInit); if (crc16[crc16.Length - 2] == crc16[crc16.Length - 1] && crc16[crc16.Length - 1] == 0) return true; return false; } /// <summary> /// 计算CRC16校验码 /// </summary> /// <param name="value">校验数据</param> /// <param name="poly">多项式码</param> /// <param name="crcInit">校验码初始值</param> /// <returns></returns> public static byte[] GetCRC16(byte[] value, ushort poly = 0xA001, ushort crcInit = 0xFFFF) { if (value == null || !value.Any()) throw new ArgumentException("生成CRC16的入参有误"); //运算 ushort crc = crcInit; for (int i = 0; i < value.Length; i++) { crc = (ushort)(crc ^ (value[i])); for (int j = 0; j < 8; j++) { crc = (crc & 1) != 0 ? (ushort)((crc >> 1) ^ poly) : (ushort)(crc >> 1); } } byte hi = (byte)((crc & 0xFF00) >> 8); //高位置 byte lo = (byte)(crc & 0x00FF); //低位置 byte[] buffer = new byte[value.Length + 2]; value.CopyTo(buffer, 0); buffer[buffer.Length - 1] = hi; buffer[buffer.Length - 2] = lo; return buffer; } }
有了报文的分析,具体的协议实现也就不难了。完整实现可参考https://github.com/zhaopeiym/IoTClient/blob/master/IoTClient/Clients/ModBus/ModBusRtuClient.cs
Nuget安装 Install-Package IoTClient
或图形化安装
//一、实例化客户端 - [COM端口名称,波特率,数据位,中止位,奇偶校验] ModBusRtuClient client = new ModBusRtuClient("COM3", 9600, 8, StopBits.One, Parity.None); //二、写操做 - 参数依次是:地址 、值 、站号 、功能码 client.Write("4", (short)33, 2, 16); client.Write("4", (short)3344, 2, 16); //三、读操做 - 参数依次是:地址 、站号 、功能码 var value = client.ReadInt16("4", 2, 3).Value; var value2 = client.ReadInt32("4", 2, 3).Value; //四、若是没有主动Open,则会每次读写操做的时候自动打开自动和关闭链接,这样会使读写效率大大减低。因此建议手动Open和Close。 client.Open(); //五、读写操做都会返回操做结果对象Result var result = client.ReadInt16("4", 2, 3); //5.1 读取是否成功(true或false) var isSucceed = result.IsSucceed; //5.2 读取失败的异常信息 var errMsg = result.Err; //5.3 读取操做实际发送的请求报文 var requst = result.Requst; //5.4 读取操做服务端响应的报文 var response = result.Response; //5.5 读取到的值 var value3 = result.Value;