项目中用到的工具,串口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); } } } }