Modbus Com SerialPort

项目中用到的工具,串口modbus协议读写数据。

    public class ModbusHelper
    {
        private readonly SerialPort _serialPort;
        private readonly ILog _logger;
        private bool _isReceivedData = false;
        private byte _modbusAdd = 0x02;
        private int _readTimes = 0;

        public ModbusHelper(string port, int baudRate, byte slave)
        {
            try
            {
                _modbusAdd = slave;
                _logger = LogManager.GetLogger(this.GetType());
                _serialPort = new SerialPort(port, baudRate, Parity.Even, 8, StopBits.One);
                _serialPort.DataReceived += _serialPort_DataReceived;
                _logger = LogManager.GetLogger(this.GetType());
            }
            catch (Exception e)
            {
                _logger.Error("Modbus串口初始化失败", e);
            }
        }

        public bool Open()
        {
            try
            {
                if (!_serialPort.IsOpen)
                {
                    _serialPort.Open();
                }
                _logger.Debug("串口" + _serialPort.PortName + "正常打开");
                return true;
            }
            catch (Exception e)
            {
                _logger.Error("打开串口打开失败", e);
                return false;
            }
        }

        public bool Close()
        {
            try
            {
                if (_serialPort.IsOpen)
                {
                    _serialPort.Close();
                    _serialPort.Dispose();
                }
                _logger.Debug("串口" + _serialPort.PortName + "正常关闭");
                return true;
            }
            catch (Exception err)
            {
                _logger.Error("关闭串口失败", err);
                return false;
            }
        }

        private void ResetBuffer()
        {
            _readTimes = 0;
            _serialPort.DiscardInBuffer();
        }

        /// <summary>
        /// 串口寄存器写值
        /// </summary>
        /// <param name="beginAddress">开始写入相对地址</param>
        /// <param name="data">写入数据</param>
        public void WriteSingleWord(short beginAddress, short data)
        {
            try
            {
                var writeData = new List<byte>(0);
                writeData.Add(_modbusAdd);
                writeData.Add(0x06);
                writeData.AddRange(BitConverter.GetBytes(beginAddress).Reverse());
                writeData.AddRange(BitConverter.GetBytes(data).Reverse());
                byte[] crc = CrcCheck(writeData.ToArray());
                writeData.AddRange(crc.Reverse());

                if (_serialPort.IsOpen)
                {
                    ResetBuffer();
                    _serialPort.Write(writeData.ToArray(), 0, writeData.Count);
                    _history.Add(writeData);
                    _serialPort.ReceivedBytesThreshold = 8;
                    while (_readTimes < 20 && !_isReceivedData)
                    {
                        _readTimes++;
                        Thread.Sleep(100);
                    }
                    _logger.Debug("写单字数据成功,数据:" + string.Join(",", writeData));
                }
            }
            catch (Exception err)
            {
                _logger.Error("写入单字数据时失败", err);
            }
        }

        /// <summary>
        /// 向寄存器写入多个字
        /// </summary>
        /// <param name="beginAddress">起始地址(相对地址)</param>
        /// <param name="writeLen">写入的字数</param>
        /// <param name="data">按字写入数据(字节高地址,字节低地址;字节高地址,字节低地址;)</param>
        public void WriteMutiWord(short beginAddress, short writeLen, byte[] data)
        {
            try
            {
                var writeData = new List<byte>(0);
                writeData.Add(_modbusAdd);
                writeData.Add(0x10);
                writeData.AddRange(BitConverter.GetBytes(beginAddress).Reverse());
                writeData.AddRange(BitConverter.GetBytes(writeLen).Reverse());
                writeData.Add((byte)(writeLen * 2));//写入的字节长度
                writeData.AddRange(data);
                byte[] crc = CrcCheck(writeData.ToArray());
                writeData.AddRange(crc.Reverse());

                if (_serialPort.IsOpen)
                {
                    ResetBuffer();
                    _serialPort.Write(writeData.ToArray(), 0, writeData.Count);
                    _history.Add(writeData);
                    _serialPort.ReceivedBytesThreshold = 8;
                    while (_readTimes < 20 && !_isReceivedData)
                    {
                        _readTimes++;
                        Thread.Sleep(100);
                    }
                    _logger.Debug("多字写数据成功,数据:" + string.Join(",", writeData));
                }
            }
            catch (Exception err)
            {
                _logger.Error("往串口写入数据时失败", err);
            }
        }

        /// <summary>
        /// 向寄存器写入双字
        /// </summary>
        /// <param name="beginAddress">起始地址(相对地址)</param>
        /// <param name="data">写入数据</param>
        public void WriteDoubleWord(short beginAddress, int data)
        {
            try
            {
                var writeData = new List<byte>(0);
                writeData.Add(_modbusAdd);
                writeData.Add(0x10);
                writeData.AddRange(BitConverter.GetBytes(beginAddress).Reverse());
                writeData.Add(0);//寄存器数量高字节
                writeData.Add(2);//寄存器数量低字节
                writeData.Add(4);//写入的字节长度
                writeData.AddRange(BitConverter.GetBytes(data).Reverse());

                byte[] crc = CrcCheck(writeData.ToArray());
                writeData.AddRange(crc.Reverse());

                if (_serialPort.IsOpen)
                {
                    ResetBuffer();
                    _serialPort.Write(writeData.ToArray(), 0, writeData.Count);
                    _history.Add(writeData);
                    _serialPort.ReceivedBytesThreshold = 8;
                    while (_readTimes < 20 && !_isReceivedData)
                    {
                        _readTimes++;
                        Thread.Sleep(100);
                    }
                    _logger.Debug("写双字数据成功,数据:" + string.Join(",", writeData));
                }
            }
            catch (Exception err)
            {
                _logger.Error("写入双字数据时失败", err);
            }
        }
        
        /// <summary>
        /// 串口读取寄存器值
        /// </summary>
        /// <param name="beginAddress">开始读取相对地址</param>
        /// <param name="readLen">读取的字数</param>
        /// <returns></returns>
        public byte[] Read(short beginAddress, short readLen)
        {
            try
            {
                var readData = new List<byte>(0);
                readData.Add(_modbusAdd);
                readData.Add(0x03);
                readData.AddRange(BitConverter.GetBytes(beginAddress).Reverse());
                readData.AddRange(BitConverter.GetBytes(readLen).Reverse());
                byte[] crc = CrcCheck(readData.ToArray());
                readData.AddRange(crc.Reverse());

                if (_serialPort.IsOpen)
                {
                    ResetBuffer();
                    _serialPort.Write(readData.ToArray(), 0, readData.Count);
                    List<string> logdata = new List<string>();
                    readData.ForEach(x => logdata.Add(Convert.ToString(x, 16).ToUpper().PadLeft(2,'0')));
                    _logger.Debug("串口写入数据:" + string.Join(" ", logdata));
                    _serialPort.ReceivedBytesThreshold = 5 + readLen * 2;
                    Thread.Sleep(50);
                    while (_serialPort.BytesToRead == 0 && _readTimes < 60)
                    {
                        _readTimes++;
                        Thread.Sleep(50);
                    }
                    if (_serialPort.BytesToRead > 0)
                    {
                        var bytesToRead = _serialPort.BytesToRead;
                        byte[] buffer = new byte[bytesToRead];
                        _serialPort.Read(buffer, 0, bytesToRead);
                        _serialPort.DiscardInBuffer();

                        _logger.Debug("串口返回的数据:" + string.Join(",", buffer));
                        int readDataLength = bytesToRead - 5;//减去开头的modbus地址、功能码、字节数,和结尾的2字节校验位
                        if (readDataLength > 0)
                        {
                            return buffer.Skip(3).Take(readDataLength).ToArray();
                        }
                    }
                    return null;
                }
                return null;
            }
            catch (Exception err)
            {
                _logger.Error("写入<读取>指令时失败", err);
                return null;
            }
        }

        private void _serialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            try
            {

            }
            catch (Exception err)
            {
                _logger.Error("读取串口数据失败", err);
            }
        }

        /// <summary>
        /// 获取Modbus校验值
        /// </summary>
        /// <param name="data">数据字节流</param>
        /// <returns>返回2byte,byte0高位,byte1低位</returns>
        private byte[] CrcCheck(byte[] data)
        {
            try
            {
                byte CRC16Lo;
                byte CRC16Hi;
                byte CL; byte CH;
                byte SaveHi; byte SaveLo;
                byte[] tmpData;
                int Flag;
                CRC16Lo = 0xFF;
                CRC16Hi = 0xFF;
                CL = 0x01;
                CH = 0xA0;
                tmpData = data;
                for (int i = 0; i < tmpData.Length; i++)
                {
                    CRC16Lo = (byte)(CRC16Lo ^ tmpData[i]);
                    for (Flag = 0; Flag <= 7; Flag++)
                    {
                        SaveHi = CRC16Hi;
                        SaveLo = CRC16Lo;
                        CRC16Hi = (byte)(CRC16Hi >> 1);
                        CRC16Lo = (byte)(CRC16Lo >> 1);
                        if ((SaveHi & 0x01) == 0x01)
                        {
                            CRC16Lo = (byte)(CRC16Lo | 0x80);
                        }

                        if ((SaveLo & 0x01) == 0x01)
                        {
                            CRC16Hi = (byte)(CRC16Hi ^ CH);
                            CRC16Lo = (byte)(CRC16Lo ^ CL);
                        }
                    }
                }
                byte[] ReturnData = new byte[2];
                ReturnData[0] = CRC16Hi;
                ReturnData[1] = CRC16Lo;
                return ReturnData;
            }
            catch (Exception err)
            {
                _logger.Error("计算crc校验值出错", err);
                return null;
            }
        }

        private readonly List<List<byte>> _history = new List<List<byte>>();

        public void ClearHistory()
        {
            _history.Clear();
        }

        public void WriteHistory()
        {
            foreach (List<byte> item in _history)
            {
                _serialPort.Write(item.ToArray(), 0, item.Count);
                _serialPort.ReceivedBytesThreshold = 8;
                while (_readTimes < 10 && !_isReceivedData)
                {
                    _readTimes++;
                    Thread.Sleep(100);
                }
            }
        }

    }

 

posted on 2018-10-31 13:37  jonney_wang  阅读(820)  评论(0编辑  收藏  举报

导航