• IoTClient开发5


    前言

    前面我们介绍了ModBusTcp协议。今天我们接着来介绍ModBusRtu协议。和ModBusTcp不同的是ModBusRtu基于串口通信,ModBusTcp是基于Tcp以太网通信。
    所以我们在讲解ModBusRtu协议之前会先介绍下串口通信。

    串口通信

    串口出现在1980年前后,当初主要目的是用来做电脑外设设备的连接,如鼠标、键盘等。现在最新的电脑慢慢的取消了原始的串口接口,不过依然广泛用于工控和测量等设备。

    串口通信参数

    串口通信指的是串口按位(bit)发送和接收字节,串口通信参数主要有波特率、数据位、停止位、校验位。

    波特率

    波特率表达的是串口通信的速率,一秒钟内传送的信号单元(码元)个数。信号单元一般包含10位(7个数据位、1个校验位、1到2个停止位)。注意:波特率和距离成反比

    数据位

    通信中实际的数据,有效值为6、7和8。

    停止位

    用来表示单个包的最后一位,有效值为1、1.5和2。停止位可用来表示传输的结束和校正时钟同步。注意:停止位的位数越多,时钟同步的容忍程度越大,但是数据传输率会越慢。

    校验位

    奇偶校验作为通信中的检错方式,如果发现错误则重新发送。

    示例数据 偶校验位 奇校验位
    0000000 00000000 00000001
    1010001 10100011 10100010
    1101001 11010010 11010011
    1111111 11111111 11111110

    从上可以看出奇偶校验就是在数据最后加一位,使数据中的1的数量保持偶数或奇数。

    波特率和比特率 (扩展知识)

    比特率是我们常用来表达宽带速率的一种方法。看上去和波特率很像,如果波特率的信号码元只传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。

    CRC16校验

    CRC,Cyclic Redundancy Check循环冗余检验,是基于数据计算一组效验码,用于核对数据传输过程中是否被更改或传输错误。而ModBusRtu用到的是其中的CRC16校验。
    其计算原理,可参考 123
    以下是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;
        }
    }
    

    协议报文分析

    数据【读取-请求报文】:01 03 00 04 00 01 C5 CB

    • 01 站号
    • 03 功能码
    • 00 04 读取的寄存器的起始地址
    • 00 01 读取寄存器的个数
    • C5 CB 为CRC16的校验码【使用上面的CRC16类进行的计算结果,CRC16.GetCRC16([01,03,00,04,00,01])】

    数据【读取-响应报文】:01 03 02 00 21 78 5C

    • 01 站号
    • 03 功能码
    • 02 数据的字节长度
    • 00 21 数据
    • 78 5C 为CRC16的校验码

    数据【写入-请求报文】:01 10 00 04 00 01 02 00 21 67 CC

    • 01 站号
    • 10 功能码
    • 00 04 写入的寄存器的起始地址
    • 00 01 写入寄存器的个数
    • 02 写字节的个数
    • 00 21 要写的数据
    • 67 CC 为CRC16的校验码

    数据【写入-响应报文】:01 10 00 04 00 01 40 08

    • 01 站号
    • 10 功能码
    • 00 04 写入的寄存器的起始地址
    • 00 01 写入寄存器的个数
    • 40 08 为CRC16的校验码

    有了报文的分析,具体的协议实现也就不难了。完整实现可参考https://github.com/zhaopeiym/IoTClient/blob/master/IoTClient/Clients/ModBus/ModBusRtuClient.cs

    IoTClient中ModBusRtu协议的使用

    安装

    Nuget安装 Install-Package IoTClient
    或图形化安装

    使用

    //1、实例化客户端 - [COM端口名称,波特率,数据位,停止位,奇偶校验]
    ModBusRtuClient client = new ModBusRtuClient("COM3", 9600, 8, StopBits.One, Parity.None);
    
    //2、写操作 - 参数依次是:地址 、值 、站号 、功能码
    client.Write("4", (short)33, 2, 16);
    client.Write("4", (short)3344, 2, 16);
    
    //3、读操作 - 参数依次是:地址 、站号 、功能码
    var value = client.ReadInt16("4", 2, 3).Value;
    var value2 = client.ReadInt32("4", 2, 3).Value;
    
    //4、如果没有主动Open,则会每次读写操作的时候自动打开自动和关闭连接,这样会使读写效率大大减低。所以建议手动Open和Close。
    client.Open();
    
    //5、读写操作都会返回操作结果对象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;
    

    参考

  • 相关阅读:
    [zz]Ubuntu源签名错误/Ubuntu 更新源签名错误 –BADSIG 40976EAF437D05B5
    [zz]GNU C __attribute__ 机制简介
    [zz]为小米创建虚拟机路由器
    liburcu 库
    多代理集群调度:可伸缩性和灵活性
    automake的使用速查
    automake之简单的例子
    ajax原生
    Cookie 和Session 的原理
    路径问题
  • 原文地址:https://www.cnblogs.com/zhaopei/p/12047465.html
Copyright © 2020-2023  润新知