2021-11-22 WPF上位机 96-Modbus通信代码的封装

该代码实现了Modbus协议的基类及Modbus RTU子类,用于通过串口进行通信。包含打开、关闭串口以及读取保持寄存器的功能,并进行了异常处理和CRC校验。同时,针对读取数据的响应超时和协议错误进行了检查。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在这里插入图片描述

public class ModbusBase
{


    public List<byte> GetReadCommand(byte deviceAddr, byte funcCode, ushort startAddr, ushort length)
    {
        List<byte> buffer = new List<byte>();
        buffer.Add(deviceAddr);
        buffer.Add(funcCode);
        buffer.Add(BitConverter.GetBytes(startAddr)[1]);
        buffer.Add(BitConverter.GetBytes(startAddr)[0]);
        buffer.Add(BitConverter.GetBytes(length)[1]);
        buffer.Add(BitConverter.GetBytes(length)[0]);
        return buffer;
    }
}
public class SerialPortBase : ModbusBase
{
}
public class ModbusRTU : SerialPortBase
{
    SerialPort serialPort = null;
    public ModbusRTU(string portName, int baudRate, Parity parity, int dataBits, StopBits stopBits)
    {
        serialPort = new SerialPort(portName, baudRate, parity, dataBits, stopBits);
    }

    public bool Open()
    {
        try
        {
            if (serialPort == null)
                throw new Exception();
            if (!serialPort.IsOpen)
                serialPort.Open();
            return true;
        }
        catch
        {
            return false;
        }
    }

    // Socket 掉线重连
    // 1、服务端(从站)踢掉一个连接:重新创建一个连接对象
    // 2、断网,网线中断(关键服务端的处理  监听)

    public bool Close()
    {
        try
        {
            if (serialPort != null && serialPort.IsOpen)
                serialPort.Close();
            serialPort = null;

            return true;
        }
        catch
        {
            return false;
        }
    }

    // 半双工 读时不能写 业务功能(任务队列(读、写))  List<Operation>

    // 1、重复代码非常多,泛型   转换类型怎么处理    最终方案  不是Switch
    // 2、字节序
    // 3、掉线重连


    // 发送报文 03
    public List<ushort> ReadKeepRegister(byte deviceAddr, byte funcCode, ushort startAddr, ushort length)
    {
        // 获取基本报文 
        List<byte> bytes = this.GetReadCommand(deviceAddr, funcCode, startAddr, length);
        // CRC
        bytes.AddRange(CRC16(bytes));

        if (serialPort.IsOpen)
        {
            serialPort.Write(bytes.ToArray(), 0, bytes.Count);

            // 进行响应报文的解析
            int time = 1;
            while (time < 2000 && serialPort.BytesToRead <= 0)
            {
                time += 1;
                //Debug.WriteLine("1");
                Thread.Sleep(1);
            }
            if (serialPort.BytesToRead <= 0)
                throw new Exception("响应超时");


            byte[] buffer = new byte[serialPort.BytesToRead];
            serialPort.Read(buffer, 0, buffer.Length);

            List<byte> byteList = new List<byte>(buffer);
            // CRC校验检查
            List<byte> checkCRC = byteList.GetRange(byteList.Count - 2, 2);
            byteList.RemoveRange(byteList.Count - 2, 2);
            List<byte> validCode = CRC16(byteList);
            if (validCode.SequenceEqual(checkCRC))
            {

                // 进行协议异常码的检查
                // 1-检查功能码  最高位是否为1
                int fc = byteList[1];
                if ((fc & 0x80) == 0x80)
                {
                    // 说明报文 异常
                    // 取异常码 
                    throw new Exception(byteList[2].ToString());
                }

                // 数据解析
                // 01 03 14 00 00 ...... (CRC)
                byteList.RemoveRange(0, 3);

                List<ushort> values = new List<ushort>();
                List<byte> valueBytes = new List<byte>();
                for (int i = 0; i < length; i++)
                {
                    valueBytes.Clear();
                    valueBytes.Add(byteList[i * 2 + 1]);
                    valueBytes.Add(byteList[i * 2]);

                    var value = BitConverter.ToUInt16(valueBytes.ToArray(), 0);

                    values.Add(value);
                }

                return values;
            }
        }
        else
        {
            Task.Run(async () =>
            {
                // 尝试次数     会有升级
                while (true)
                {
                    await Task.Delay(2000);

                    if (serialPort.IsOpen)
                    {
                        Console.WriteLine("连接正常");
                        continue;
                    }

                    try
                    {
                        Console.WriteLine("连接断开,尝试重新连接");
                        serialPort.Open();
                    }
                    catch (System.IO.IOException ex)
                    {
                        Console.WriteLine("设备连接断开");
                        continue;
                    }
                }
            });
        }

        return null;
    }


    public List<float> ReadKeepRegisterFloat(byte deviceAddr, byte funcCode, ushort startAddr, ushort length)
    {
        // 获取基本报文 
        List<byte> bytes = this.GetReadCommand(deviceAddr, funcCode, startAddr, length);
        // CRC
        bytes.AddRange(CRC16(bytes));

        if (serialPort.IsOpen)
        {
            serialPort.Write(bytes.ToArray(), 0, bytes.Count);

            // 进行响应报文的解析
            int time = 1;
            while (time < 2000 && serialPort.BytesToRead <= 0)
            {
                time += 1;
                //Debug.WriteLine("1");
                Thread.Sleep(1);
            }
            if (serialPort.BytesToRead <= 0)
                throw new Exception("响应超时");


            byte[] buffer = new byte[serialPort.BytesToRead];
            serialPort.Read(buffer, 0, buffer.Length);

            List<byte> byteList = new List<byte>(buffer);
            // CRC校验检查
            List<byte> checkCRC = byteList.GetRange(byteList.Count - 2, 2);
            byteList.RemoveRange(byteList.Count - 2, 2);
            List<byte> validCode = CRC16(byteList);
            if (validCode.SequenceEqual(checkCRC))
            {

                // 进行协议异常码的检查
                // 1-检查功能码  最高位是否为1
                int fc = byteList[1];
                if ((fc & 0x80) == 0x80)
                {
                    // 说明报文 异常
                    // 取异常码 
                    throw new Exception(byteList[2].ToString());
                }

                // 数据解析
                // 01 03 14 00 00 ...... (CRC)
                byteList.RemoveRange(0, 3);

                List<float> values = new List<float>();
                List<byte> valueBytes = new List<byte>();
                for (int i = 0; i < length / 2; i++)
                {
                    valueBytes.Clear();
                    valueBytes.Add(byteList[i * 4 + 3]);
                    valueBytes.Add(byteList[i * 4 + 2]);
                    valueBytes.Add(byteList[i * 4 + 1]);
                    valueBytes.Add(byteList[i * 4]);

                    var value = BitConverter.ToSingle(valueBytes.ToArray(), 0);

                    values.Add(value);
                }

                return values;
            }
        }
        else
        {
            Task.Run(async () =>
            {
                // 尝试次数     会有升级
                while (true)
                {
                    await Task.Delay(2000);

                    if (serialPort.IsOpen)
                    {
                        Console.WriteLine("连接正常");
                        continue;
                    }

                    try
                    {
                        Console.WriteLine("连接断开,尝试重新连接");
                        serialPort.Open();
                    }
                    catch (System.IO.IOException ex)
                    {
                        Console.WriteLine("设备连接断开");
                        continue;
                    }
                }
            });
        }

        return null;
    }

    /// <summary>
    /// CRC校验
    /// </summary>
    /// <param name="value"></param>
    /// <returns></returns>
    private List<byte> CRC16(List<byte> value)
    {
        ushort poly = 0xA001;
        ushort crcInit = 0xFFFF;

        if (value == null || !value.Any())
            throw new ArgumentException("");

        //运算
        ushort crc = crcInit;
        for (int i = 0; i < value.Count; 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);         //低位置

        List<byte> buffer = new List<byte>();
        buffer.Add(lo);
        buffer.Add(hi);
        return buffer;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值