C#分享辅助类:Modbus串口通信(信捷)

30 篇文章 1 订阅
using System;
using System.Collections.Generic;
using System.IO.Ports;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Wsfly
{
    /// <summary>
    /// 委托-回调
    /// </summary>
    /// <param name="receiveData">十进制列表</param>
    /// <param name="receiveHexData">十六进制字符串</param>
    public delegate void Modbus_Callback(bool success, string msg, List<int> receiveData, string receiveHexData, string resultData);

    /// <summary>
    /// 信捷PLC Modbus 指令
    /// </summary>
    public class ModbusXJHandler
    {
        /*
         * Modbus-RTU
            功能码说明:
            功能码    功能
            01          读取线圈状态
            02          读取输入状态
            03          读取寄存器
            04          读取输入寄存器
            05          写单个线圈
            06          写单个寄存器
            0F          写多个线圈(0F=15)
            10          写多个寄存器(10H=16)

            协议格式:
            模块地址    功能码     数据      CRC校验

            1Byte=8Bit

            03H格式:
            发送:模块地址[1Byte]    功能码[1Byte]    寄存器起始地址[2Byte]    读取寄存器数目[2Byte]    CRC校验(低字节在前)[2Byte]
            返回:模块地址[1Byte]    功能码[1Byte]    读取字节数[2Byte]           读取寄存器内容[2Byte]    CRC校验[2Byte]

            06H格式:
            发送:模块地址[1Byte]    功能码[1Byte]    寄存器起始地址[2Byte]    写入寄存器内容[2Byte]    CRC校验(低字节在前)[2Byte]
            返回:模块地址[1Byte]    功能码[1Byte]    寄存器起始地址[2Byte]    写入寄存器内容[2Byte]    CRC校验[2Byte]

            报文格式:
            发送: 01 03 00 01 00 01 D5 CA
            接收: 01 03 02 00 01 79 84

            发送: 01 06 01 00 00 01 49 F6
            接收: 01 06 01 00 00 01 49 F6 
         */

        /// <summary>
        /// 串口 SerialPort
        /// </summary>
        private SerialPort _serialPort = null;
        /// <summary>
        /// 回调函数
        /// </summary>
        private Modbus_Callback _callback = null;
        /// <summary>
        /// 收到的数据 DataReceive
        /// </summary>
        private string _receiveHexData = null;
        /// <summary>
        /// 最后发送的数据
        /// </summary>
        private string _lastSendData = "";

        /// <summary>
        /// 自动打开与关闭串口
        /// </summary>
        public bool AutoOpenAndCloseSerialPort = false;

        #region 构造函数
        /*
        RS-232C接口定义(DB9) 
        引脚  定义                  符号
        1     载波检测              DCD(Data Carrier Detect)
        2     接收数据              RXD(Received Data)
        3     发送数据              TXD(Transmit Data)
        4     数据终端搜索准备好    DTR(Data Terminal Ready)
        5     信号地                SG(Signal Ground)
        6     数据准备好            DSR(Data Set Ready)
        7     请求发送              RTS(Request To Send)
        8     清除发送              CTS(Clear To Send)
        9     振铃提示              RI(Ring Indicator)
        */
        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="serialPort"></param>
        public ModbusXJHandler(SerialPort serialPort)
        {
            _serialPort = serialPort;
        }
        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="port">端口号</param>
        /// <param name="baudRate">波特率</param>
        /// <param name="dataBits">数据位</param>
        /// <param name="parity">奇偶检验位</param>
        /// <param name="stopBits">停止位</param>
        /// <param name="handshake">握手协议</param>
        public ModbusXJHandler(int port, int baudRate = 9600, int dataBits = 8, Parity parity = Parity.Even, StopBits stopBits = StopBits.One, Handshake handshake = Handshake.None, bool dtrEnable = true, bool rtsEnable = true)
        {
            //实例化串口
            _serialPort = new SerialPort();

            _serialPort.PortName = "COM" + port;            //端口号
            _serialPort.BaudRate = baudRate;                //波特率
            _serialPort.DataBits = dataBits;                //数据位
            _serialPort.Parity = parity;                    //奇偶检验位
            _serialPort.StopBits = stopBits;                //停止位
            _serialPort.Handshake = handshake;              //握手协议
            _serialPort.DtrEnable = dtrEnable;
            _serialPort.RtsEnable = rtsEnable;
        }
        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="port">端口号</param>
        /// <param name="baudRate">波特率</param>
        /// <param name="dataBits">数据位</param>
        /// <param name="parity">奇偶检验位</param>
        /// <param name="stopBits">停止位</param>
        /// <param name="handshake">握手协议</param>
        public ModbusXJHandler(int port, int baudRate, int dataBits, int parity, int stopBits, int handshake, bool dtrEnable = true, bool rtsEnable = true)
        {
            //实例化串口
            _serialPort = new SerialPort();

            _serialPort.PortName = "COM" + port;            //端口号
            _serialPort.BaudRate = baudRate;                //波特率
            _serialPort.DataBits = dataBits;                //数据位
            _serialPort.Parity = (Parity)parity;            //奇偶检验位
            _serialPort.StopBits = (StopBits)stopBits;      //停止位
            _serialPort.Handshake = (Handshake)handshake;   //握手协议
            _serialPort.DtrEnable = dtrEnable;
            _serialPort.RtsEnable = rtsEnable;
        }
        #endregion

        #region 获取串口
        /// <summary>
        /// 获取所有串口
        /// </summary>
        /// <returns></returns>
        public static List<string> GetCom()
        {
            List<string> comList = new List<string>();
            foreach (string portName in SerialPort.GetPortNames())
            {
                comList.Add(portName);
            }
            return comList;
        }
        /// <summary>
        /// 获取所有串口 通过注册表
        /// </summary>
        /// <returns></returns>
        public static List<string> GetComByReg()
        {
            Microsoft.Win32.RegistryKey keyCom = Microsoft.Win32.Registry.LocalMachine.OpenSubKey("Hardware\\DeviceMap\\SerialComm");
            if (keyCom != null) return keyCom.GetValueNames().ToList();
            return null;
        }
        #endregion

        #region 打开、关闭串口
        /// <summary>
        /// 打开串口
        /// </summary>
        public void Open()
        {
            //是否有串口
            if (_serialPort == null) return;
            //打开连接
            if (!_serialPort.IsOpen)
            {
                //打开串口
                _serialPort.Open();
                //串口收到数据处理
                _serialPort.DataReceived += _serialPort_DataReceived;
            }
        }
        /// <summary>
        /// 关闭串口
        /// </summary>
        public void Close()
        {
            //是否有串口
            if (_serialPort == null) return;
            //关闭连接
            if (_serialPort.IsOpen)
            {
                //串口收到数据处理
                _serialPort.DataReceived -= _serialPort_DataReceived;
                _serialPort.Close();
                //_serialPort.Dispose();
            }
        }
        #endregion

        #region 读写线圈 COL
        /// <summary>
        /// 读取线圈 状态
        /// </summary>
        /// <param name="device"></param>
        /// <param name="addr"></param>
        /// <param name="count"></param>
        /// <param name="callback"></param>
        public void COL_ReadData(short device, short addr, short count, Modbus_Callback callback)
        {
            //参考:https://blog.csdn.net/xukai871105/article/details/16330471
            //读到的状态要 将16进制转为2进制
            //比如:读1-10  开状态的有:1.2.5.8.9.10
            //发送指令:01 01 0001 000A
            //接收指令:01 01 02 93 03 95 0D
            //解析:02示收到2个字节  即:93  03
            //          每8位为一个字节 即 1-8  对应 93
            //                                         9-10 对应 03 
            //          十六进制 转为 二进制 不足8位在前补0
            //          93                    03
            //          10010011        00000011
            //          高位在前,低位在后,将顺序颠倒即可对应如下
            //          返回状态二进制:11001001            11000000
            //          线圈对应的位置:1.2.3.4.5.6.7.8      9.10
            //二进制:1表示开 0表示关

            string cmd = device.ToString("X2") + " 01 " + addr.ToString("X4") + " " + count.ToString("X4");
            SendData(cmd, callback);
        }
        /// <summary>
        /// 写入线圈 状态
        /// </summary>
        /// <param name="device"></param>
        /// <param name="addr"></param>
        /// <param name="value"></param>
        /// <param name="callback"></param>
        public void COL_WriteData(short device, short addr, short value, Modbus_Callback callback)
        {
            string cmd = device.ToString("X2") + " 05 " + addr.ToString("X4") + " " + value.ToString("X4");
            SendData(cmd, callback);
        }
        /// <summary>
        /// 写入多个线圈 状态
        /// </summary>
        /// <param name="device"></param>
        /// <param name="addr"></param>
        /// <param name="value"></param>
        /// <param name="callback"></param>
        public void COL_WriteMoreData(short device, short addr, short addrLength, short[] values, Modbus_Callback callback)
        {
            int ys = addrLength % 8;
            int byteCount = ys == 0 ? addrLength / 8 : (addrLength / 8) + 1;
            string cmd = device.ToString("X2") + " 0F " + addr.ToString("X4") + " " + addrLength.ToString("X4") + " " + byteCount.ToString("X2");

            List<int> sendValues = new List<int>();

            //拼合 二进制 转为 十进制
            string byteStr = "";
            for (var i = 0; i < values.Length; i++)
            {
                byteStr += values[i];
                if (i > 0 && i % 7 == 0)
                {
                    byteStr = string.Concat(byteStr.Reverse());
                    sendValues.Add(Convert.ToInt32(byteStr, 2));
                    byteStr = "";
                }
            }

            if (!string.IsNullOrWhiteSpace(byteStr))
            {
                byteStr = string.Concat(byteStr.Reverse());
                sendValues.Add(Convert.ToInt32(byteStr, 2));
                byteStr = "";
            }


            //转为十六进制
            foreach (int value in sendValues)
            {
                cmd += value.ToString("X2") + " ";
            }

            SendData(cmd, callback);
        }
        #endregion

        #region 读输入指令 INR
        /// <summary>
        /// 读输入线圈指令
        /// </summary>
        /// <param name="device"></param>
        /// <param name="addr"></param>
        /// <param name="count"></param>
        /// <param name="callback"></param>
        public void INR_ReadData(short device, short addr, short count, Modbus_Callback callback)
        {
            string cmd = device.ToString("X2") + " 02 " + addr.ToString("X4") + " " + count.ToString("X4");
            SendData(cmd, callback);
        }
        /// <summary>
        /// 读输入寄存器指令
        /// </summary>
        /// <param name="device"></param>
        /// <param name="addr"></param>
        /// <param name="count"></param>
        /// <param name="callback"></param>
        public void INR_ReadMoreData(short device, short addr, short count, Modbus_Callback callback)
        {
            string cmd = device.ToString("X2") + " 04 " + addr.ToString("X4") + " " + count.ToString("X4");
            SendData(cmd, callback);
        }
        #endregion

        #region 读写寄存器 REG
        /// <summary>
        /// 读取数据
        /// </summary>
        /// <param name="device"></param>
        /// <param name="addr"></param>
        /// <param name="count"></param>
        /// <param name="callback"></param>
        public void REG_ReadData(short device, short addr, short count, Modbus_Callback callback)
        {
            string cmd = device.ToString("X2") + " 03 " + addr.ToString("X4") + " " + count.ToString("X4");
            SendData(cmd, callback);
        }
        /// <summary>
        /// 写入数据
        /// </summary>
        /// <param name="device"></param>
        /// <param name="addr"></param>
        /// <param name="value"></param>
        /// <param name="callback"></param>
        public void REG_WriteData(short device, short addr, short value, Modbus_Callback callback)
        {
            string cmd = device.ToString("X2") + " 06 " + addr.ToString("X4") + " " + value.ToString("X4");
            SendData(cmd, callback);
        }
        /// <summary>
        /// 写入多个数据
        /// </summary>
        /// <param name="device"></param>
        /// <param name="addr"></param>
        /// <param name="values"></param>
        /// <param name="callback"></param>
        public void REG_WriteMoreData(short device, short addr, short addrLength, short[] values, Modbus_Callback callback)
        {
            //报文格式示例:
            //从机地址   功能码   寄存器起始地址高字节   寄存器起始地址低字节   寄存器数量高字节   寄存器数量低字节   字节数   数据1高字节   数据1低字节   数据2高字节  数据2低字节   CRC校验高字节   CRC校验低字节
            //11             10         00                                 01                                00                           02                          04         00                  0A                 01                  02                 C6                       F0

            string cmd = device.ToString("X2") + " 10 " + addr.ToString("X4") + " " + addrLength.ToString("X4") + " " + (addrLength * 2).ToString("X2");

            //对应写入 不够写入0
            for (int i = 0; i < addrLength; i++)
            {
                int value = (values.Length >= (i - 1)) ? values[i] : 0;
                cmd += value.ToString("x4");
            }

            SendData(cmd, callback);
        }
        #endregion

        #region 发送数据
        /// <summary>
        /// 发送数据
        /// </summary>
        /// <param name="sendData">要发送的十六进制数据 如:01 06 01 00 00 01</param>
        /// <param name="callback">结果回调</param>
        public void SendData(string sendData, Modbus_Callback callback)
        {
            //检查参数
            if (_serialPort == null)
            {
                //回调
                callback?.Invoke(false, "串口未定义", null, null, null);
                return;
            }
            if (string.IsNullOrWhiteSpace(sendData))
            {
                //回调
                callback?.Invoke(false, "发送的数据不可为空", null, null, null);
                return;
            }

            if (!AutoOpenAndCloseSerialPort && !_serialPort.IsOpen)
            {
                //回调
                callback?.Invoke(false, "请先打开串口!", null, null, null);
                return;
            }

            try
            {
                //回调函数
                _callback = callback;
                //最后发送的数据
                _lastSendData = sendData;
                //清空回传数据
                _receiveHexData = string.Empty;
                //发送十六进制
                SendHexadecimalData(sendData);
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
        /// <summary>
        /// 发送16进制数据
        /// </summary>
        /// <param name="sendData">发送的十六进制的数据 如:01 06 01 00 00 01</param>
        private void SendHexadecimalData(string sendData)
        {
            //发送的十六进制的数据
            sendData = sendData.Replace(" ", "");

            //定义发送的字节大小除以2是因为两个字位一个字节,加2是因为RTU发送后面还要加两个字节的校验码
            byte[] sendBytes = new byte[sendData.Length / 2 + 2];
            int k = 0;

            //截取待发送的数据每次截取两位转换成10进制放到一个字节中
            for (int i = 0; i < sendData.Length - 1; i = i + 2)
            {
                //第I个字截取两个
                string str = sendData.Substring(i, 2);
                //将截取到的两个字符转换成10进制方便异或
                sendBytes[k] = Convert.ToByte(str, 16);
                k++;
            }

            //调用CRC检验函数
            string crc = CRCCode(sendData);
            //获取CRC高位
            string crcH = crc.Substring(2, 2);
            //获取CRC低位
            string crcL = crc.Substring(0, 2);

            //将CRC转换成一个字节的10进制
            sendBytes[sendData.Length / 2] = Convert.ToByte(crcH, 16);
            //将CRC转换成一个字节的10进制
            sendBytes[sendData.Length / 2 + 1] = Convert.ToByte(crcL, 16);

            //自动打开串口
            if (AutoOpenAndCloseSerialPort)
            {
                while (true)
                {
                    try
                    {
                        //打开串口
                        Open();
                        break;
                    }
                    catch (Exception ex)
                    {
                        //打开串口异常,暂停0.1秒,重新打开
                        System.Threading.Thread.Sleep(100);
                    }
                }
            }

            //发送数据
            _serialPort.Write(sendBytes, 0, sendBytes.Length);
        }
        #endregion

        #region 接收数据
        /// <summary>
        /// 接收数据处理
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void _serialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            //暂停0.1秒
            System.Threading.Thread.Sleep(100);

            //发回的字符数
            int bytesLength = _serialPort.BytesToRead;
            if (bytesLength <= 0) return;

            //发回的数据
            byte[] bytes = new byte[bytesLength];

            //读取数据
            int readDataLength = _serialPort.Read(bytes, 0, bytesLength);
            if (readDataLength <= 2 || bytes == null) return;

            //对数据进行转换
            string resultData = Encoding.ASCII.GetString(bytes, 0, bytes.Length);
            //要返回的十进制列表
            List<int> results = new List<int>();

            //转换为10进制、16进制
            foreach (byte b in bytes)
            {
                results.Add(Convert.ToInt32(b));
                _receiveHexData += b.ToString("X2") + " ";
            }
            _receiveHexData = _receiveHexData.Trim();

            //验证CRC是否正确
            string rhd = _receiveHexData.Replace(" ", "");
            string rhd_Check = bytes[bytes.Length - 2].ToString("X2") + bytes.Last().ToString("X2");
            string rhd_Data = rhd.Replace(rhd_Check, "");

            //验证校验码
            bool isRightCRC = false;
            string rhdCRC = CRCCode(rhd_Data);
            if (rhdCRC == rhd_Check)
            {
                //校验码是正确的
                isRightCRC = true;
            }

            //回调通知
            _callback?.Invoke(isRightCRC, "OK", results, _receiveHexData, resultData);

            //自动关闭串口
            if (AutoOpenAndCloseSerialPort)
            {
                //关闭串口
                Close();
            }
        }
        #endregion

        #region CRC16-MODBUS
        /// <summary>
        /// CRC检验函数
        /// </summary>
        /// <returns></returns>
        private static string CRCCode(string checkData)
        {
            Int32 crc = 65535;
            string crcesult = "";
            for (int i = 0; i < checkData.Length - 1; i = i + 2)
            {
                string str = "";
                Int32 datadec = 0;
                str = checkData.Substring(i, 2);//第I个字截取两个
                datadec = Convert.ToInt32(str, 16); //讲截取到的两个字符转换成10进制方便异或
                crc = crc ^ datadec;//异或之后又放到CRC里面

                for (int j = 0; j < 8; j++)
                {
                    string binstr;
                    binstr = DECToBIN(crc);
                    if (binstr.Substring(15, 1) == "0")//截取二进制的最低位判断是不是0 
                    {
                        crc = crc >> 1;  //如果是0直接右边移1位
                    }
                    else
                    {
                        crc = crc >> 1;  //右边移1位
                        crc = crc ^ 40961;  //与多项式异或  
                    }
                }
            }

            //保持4位字符如果不够就用0填上并且以大写形式
            crcesult = Convert.ToString(crc, 16).PadLeft(4, '0').ToUpper();

            //返回CRC检验值
            return crcesult;
        }
        /// <summary>
        /// 十进制转2进制
        /// </summary>
        /// <param name="dec"></param>
        /// <returns></returns>
        private static string DECToBIN(int dec)
        {
            string result = "";
            while (dec > 0)
            {
                result = (dec % 2).ToString() + result;
                dec = dec / 2;
            }

            //返回16位的二进制
            return result.PadLeft(16, '0');
        }
        #endregion
    }
}

调用 DEMO:

//实例化
ModbusXJHandler handler = new ModbusXJHandler(3, 19200, 8, 2, 1, 0);
//打开串口
handler.Open();
//读D100
handler.REG_ReadData(1, 500, 3, new Modbus_Callback(delegate (bool success, string msg, List<int> receiveData, string receiveHexData, string resultData)
{
    if (success)
    {
        //成功
        //receiveHexData  十六进制数据
    }
}));

  • 2
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

MOZ-Soft

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值