C#Modbus通信

目录

1,辅助工具。

2,初识Modbus。

3,基于ModbusRTU的通信。

3.1,RTU与ASCII模式区别

3.2,Modbus存储区

3.3,报文格式

3.4,异常代码

3.5,详细文档连接 。

3.6,代码

3.6.1,准备

3.6.2,UI部分

3.6.3,UI代码部分

3.6.4,Modbus类

3.6.5,校验码类

4,基于ModbusTCP通信。

4.1,ModbusTCP报文格式参考链接

4.2,代码

4.2.1,准备

4.2.2,UI部分

4.2.3,UI代码部分

5,基于OPC服务的Modbus通信

5.1,OPC介绍

5.2,OPCDAAuto.dll链接

5.3,配置OPC服务软件KEPServerEx V4.0

5.4,代码

6,Demo链接。


1,辅助工具。

1,虚拟串口工具(vspd.exe)
2,Modubus模拟主站(Modbus Poll)
3,Modbus模拟从站(Modbus Slave)
4,OPC服务软件(KEPServerEx V4.0)。

下载链接:https://download.csdn.net/download/lingxiao16888/89516857

2,初识Modbus。

1,Modbus协议是一种应用层报文传输协议,包含RTU,ASCII,TCP(分配的端口号为:502)三种报文类型。2,标准的Modbus协议物理层接口有RS232,RS485,RS422,以太网口。采用主从(Master/Slave)方式通信。3,Modbus主从站协议原理Modubus串行链路协议是一种主从协议。在同一时刻,只有一个主站,一个从站或者多个从站(从站最大编号247)连接在同一串行总线。Modbus通信由主站发起,从站在没有收到主站的请求时是不会发送数据。从站之间互不通信。主站在同一时刻只能发起一个Modbus事务请求。主站有两种模式对从站发起请求:广播,单播。4,Modbus帧结构Modbus串行链路协议是一种主从协议。网络上的每个从站必须有唯一的地址(从1到247)从站地址用于寻址从站设备,由主站发起。地址0用于广播模式,不需要响应。RS-485,RS-232定义了标准的物理端口,提高了操作性。

3,基于ModbusRTU的通信。

3.1,RTU与ASCII模式区别

3.2,Modbus存储区

3.3,报文格式

 

 

 

3.4,异常代码

 

3.5,详细文档连接 。

https://download.csdn.net/download/lingxiao16888/89516928?spm=1001.2014.3001.5503

3.6,代码

目的:通过标准Modbus协议完成对线圈,寄存器的读写。

  • 效果展示:

3.6.1,准备

第1步,使用虚拟串口软件创建虚拟串口。

第2步,使用ModbusSlave软件建立虚拟Modbus从站。

配置通信连接参数

配置功能定义(是进行线圈读写还是寄存器读写)

定义数据显示格式

注明:此截图中的ID=1;F=01表示从站地址为01,功能码为01(即读取线圈,具体参考前面报文格式阐述)

3.6.2,UI部分

3.6.3,UI代码部分
 public partial class Form1 : Form{ModbusSerial modbus;int slaveStation = 10;Timer timer = new Timer();public Form1(){InitializeComponent();UIControlStatusChange(false);timer.Interval = 1000;foreach (Control item in groupBox1.Controls){if (item is GroupBox){foreach (Control c in item.Controls){if (c is Button){(c as Button).Click += Button_Click;}}}}InitDataLoad();}void InitDataLoad(){//获取串口号comboPortName.DataSource = SerialPort.GetPortNames();//波特率comboBaudRate.DataSource = new int[] { 4800, 9600, 19200 };comboBaudRate.SelectedIndex = 1;//数据位comboDataBits.DataSource = new int[] { 5, 6, 7, 8 };comboDataBits.SelectedIndex = 3;//校验位comboParity.DataSource = Enum.GetNames(typeof(Parity));//停止位----不支持None所以应该None去除comboStopBits.DataSource = Enum.GetNames(typeof(StopBits)).Where(item => !item.Equals("None", StringComparison.CurrentCultureIgnoreCase)).ToArray();modbus = new ModbusSerial();modbus.ErrorMessage += Modbus_ErrorMessage;}private void Button_Click(object sender, EventArgs e){lblErrorMsg.Text = "";}private void Modbus_ErrorMessage(int arg1, string arg2){this.Invoke(new Action(() =>{lblErrorMsg.Text = $"从站[{arg1}]出现异常,原因:{arg2}";}));}/// <summary>/// 变更UI控件状态/// </summary>/// <param name="status">True:已连接,False:连接断开</param>void UIControlStatusChange(bool status){if (status){gbParam.Enabled = false;btnConnect.BackColor = Color.LightGreen;btnConnect.Text = "断开连接";groupBox1.Enabled = true;}else{gbParam.Enabled = true;btnConnect.BackColor = Color.Red;btnConnect.Text = "连接";groupBox1.Enabled = false;}}private void btnConnect_Click(object sender, EventArgs e){if (btnConnect.Text.Equals("连接")){if (modbus.IsOpen){modbus.Close();}//更新端口的设置if (comboPortName.DataSource == null || comboPortName.Items.Count==0){MessageBox.Show("没有可用的串口!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);return;}string portName = comboPortName.Text;int baudRate = Convert.ToInt32(comboBaudRate.Text);int dataBits = Convert.ToInt32(comboDataBits.Text);Parity parity = (Parity)Enum.Parse(typeof(Parity), comboParity.Text);StopBits stopBits = (StopBits)Enum.Parse(typeof(StopBits), comboStopBits.Text);modbus.BaudRate = baudRate;modbus.DataBits = dataBits;modbus.Parity = parity;modbus.StopBits = stopBits;modbus.Open(portName);}else{if (modbus.IsOpen){modbus.Close();}}UIControlStatusChange(modbus.IsOpen);}//读取多个线圈private void btnReadCoils_Click(object sender, EventArgs e){int address; int len;if (!int.TryParse(txtCoilsAddress.Text.Trim(), out address)){txtCoilsAddress.Focus();MessageBox.Show("线圈地址错误!");return;}if (!int.TryParse(txtCoilsCount.Text.Trim(), out len)){txtCoilsCount.Focus();MessageBox.Show("线圈数量只能为正整数!");return;}bool[] reuslt = modbus.ReadCoils(slaveStation, address, len);if (reuslt == null) return;StringBuilder sb = new StringBuilder();foreach (var item in reuslt){if (item){sb.Append(" 1");}else{sb.Append(" 0");}}lblCoilsResult.Text = sb.ToString();}private void txtSlave_TextChanged(object sender, EventArgs e){if (!int.TryParse(txtSlave.Text.Trim(), out slaveStation)){slaveStation = 10;}}private void btnForced_Click(object sender, EventArgs e){int address;if (!int.TryParse(txtCoilsWriteAdress.Text.Trim(), out address)){txtCoilsWriteAdress.Focus();MessageBox.Show("线圈写入地址错误!");return;}if (btnForced.Text.Equals("ON")){bool result = modbus.ForcedCoil(slaveStation, address, false);if (result){btnForced.Text = "OFF";btnForced.BackColor = Color.Red;}}else{bool result = modbus.ForcedCoil(slaveStation, address, true);if (result){btnForced.Text = "ON";btnForced.BackColor = Color.LightGreen;}}}private void btnRegistersRead_Click(object sender, EventArgs e){int address; int len;if (!int.TryParse(txtRegistersAdress.Text.Trim(), out address)){txtRegistersAdress.Focus();MessageBox.Show("寄存器地址错误!");return;}if (!int.TryParse(txtRegistersCount.Text.Trim(), out len)){txtRegistersCount.Focus();MessageBox.Show("寄存器数量只能为正整数!");return;}short[] result = modbus.ReadHoldingRegisters(slaveStation, address, len);if (result == null) return;StringBuilder sb = new StringBuilder();foreach (var item in result){sb.Append(" " + item.ToString());}lblRegistersResult.Text = sb.ToString();}private void btnReadFloat_Click(object sender, EventArgs e){int address; int len;if (!int.TryParse(txtRegistersAdress.Text.Trim(), out address)){txtRegistersAdress.Focus();MessageBox.Show("寄存器地址错误!");return;}if (!int.TryParse(txtRegistersCount.Text.Trim(), out len)){txtRegistersCount.Focus();MessageBox.Show("寄存器数量只能为正整数!");return;}float[] result = modbus.ReadFloatRegisters(slaveStation, address, len);if (result == null) return;StringBuilder sb = new StringBuilder();foreach (var item in result){sb.Append(" " + item.ToString());}lblRegistersResult.Text = sb.ToString();}private void btnWriteShort_Click(object sender, EventArgs e){int address; short val;if (!int.TryParse(txtWriteShortAdress.Text.Trim(), out address)){txtWriteShortAdress.Focus();MessageBox.Show("寄存器地址错误!");return;}if (!short.TryParse(txtWriteShortVal.Text.Trim(), out val)){txtWriteShortVal.Focus();MessageBox.Show("写入的值只能为正整数!");return;}if (modbus.WriteSingleRegister(slaveStation, address, val)){MessageBox.Show("Short数已成功写入");}else{MessageBox.Show("Short数写入失败");}}private void btnWriteFloat_Click(object sender, EventArgs e){int address; float val;if (!int.TryParse(txtFloatWriteAddress.Text.Trim(), out address)){txtFloatWriteAddress.Focus();MessageBox.Show("寄存器地址错误!");return;}if (!float.TryParse(txtWriteFloatVal.Text.Trim(), out val)){txtWriteFloatVal.Focus();MessageBox.Show("写入的值只能为正整数!");return;}if (modbus.WriteFloatRegister(slaveStation, address, val)){MessageBox.Show("Float数已成功写入");}else{MessageBox.Show("Float数写入失败");}}//给线圈批量赋值private void btnBatch_Click(object sender, EventArgs e){int address; short val;if (!int.TryParse(txtCoils.Text.Trim(), out address)){txtCoils.Focus();MessageBox.Show("线圈地址错误!");return;}if (!short.TryParse(txtCoilsVal.Text.Trim(), out val)){txtCoilsVal.Focus();MessageBox.Show("写入的值只能为正整数!");return;}bool result = modbus.WriteMultipleCoils(slaveStation, address, 16, new short[] { val });if (result){MessageBox.Show("线圈已批量写入");}else{MessageBox.Show("线圈批量失败");}}private void btnMulShortWrite_Click(object sender, EventArgs e){int address; short val; int len;if (!int.TryParse(txtMulAdress.Text.Trim(), out address)){txtMulAdress.Focus();MessageBox.Show("寄存器地址错误!");return;}if (!short.TryParse(txtMulShortVal.Text.Trim(), out val)){txtMulShortVal.Focus();MessageBox.Show("写入的值只能为正整数!");return;}if (!int.TryParse(txtMulCount.Text.Trim(), out len)){txtMulCount.Focus();MessageBox.Show("寄存器数量只能为正整数!");return;}if (modbus.WriteMultipleRegisters(slaveStation, address, len, Enumerable.Repeat(val, len).ToArray())){MessageBox.Show("寄存器已批量写入");}else{MessageBox.Show("寄存器批量写入失败");}}private void comboPortName_DropDown(object sender, EventArgs e){comboPortName.DataSource = SerialPort.GetPortNames();}}
3.6.4,Modbus类
 public class ModbusSerial : IDisposable{SerialPort serialPortObject = null;/// <summary>/// 目标站点号/// </summary>int targetStation = 0;/// <summary>/// 消息接收到标志位/// </summary>bool msgRecFlag = false;/// <summary>/// 接收到有效的数据/// </summary>byte[] RecBytes = null;AutoResetEvent slim = new AutoResetEvent(true);/// <summary>/// 返回的异常信息/// </summary>public event Action<int, string> ErrorMessage;/// <summary>/// 构建基于SerialPort的Modbus/// </summary>/// <param name="baudRate">波特率</param>/// <param name="dataBits">数据位</param>/// <param name="parity">校验位</param>/// <param name="stopBits">停止位</param>public ModbusSerial(int baudRate, int dataBits, Parity parity, StopBits stopBits){serialPortObject = new SerialPort();serialPortObject.BaudRate = baudRate;serialPortObject.DataBits = dataBits;serialPortObject.Parity = parity;serialPortObject.StopBits = stopBits;serialPortObject.DataReceived += SerialPortObject_DataReceived;}/// <summary>/// 该构造方法需通过属性设置串口参数/// </summary>public ModbusSerial(){serialPortObject = new SerialPort();serialPortObject.DataReceived += SerialPortObject_DataReceived;}ModbusSerial(SerialPort port){serialPortObject = port;serialPortObject.DataReceived += SerialPortObject_DataReceived;}static ModbusSerial modbus;/// <summary>/// 获取Modbus/// </summary>/// <param name="port"></param>/// <returns></returns>public static ModbusSerial CreateModbus(SerialPort port){if (modbus == null){modbus = new ModbusSerial(port);}return modbus;}private void SerialPortObject_DataReceived(object sender, SerialDataReceivedEventArgs e){int num = serialPortObject.BytesToRead;if (num == 0) return;byte[] bytes = new byte[num];//需要通过事件监听不可采用循环阻塞接收,如果采用循环阻塞将抛出异常serialPortObject.Read(bytes, 0, num);//对消息进行解析//检查报文是否正确msgRecFlag = true;byte[] checkSum = Checkhelper.CrcModbus(bytes, 0, bytes.Length - 2);if (checkSum[0] == bytes[bytes.Length - 1] && checkSum[1] == bytes[bytes.Length - 2]){//验证通过byte stationNo = bytes[0];targetStation = stationNo;byte commandCode = bytes[1];if (commandCode > 80){//异常信息ErrorCode errorCode = (ErrorCode)bytes[2];//这里使用异步调用如果使用同步调用将大大增加阻塞时间导致后面的AutoResetEvent等待超时ErrorMessage?.BeginInvoke(stationNo, errorCode.ToString(), null, null);}else{FunctionCode function = (FunctionCode)commandCode;byte length = bytes[2];switch (function){case FunctionCode.ReadCoils:case FunctionCode.ReadDiscreteInputs://返回字节长度RecBytes = new byte[length];Array.Copy(bytes, 3, RecBytes, 0, length);break;case FunctionCode.ReadHoldingRegisters:case FunctionCode.ReadInputRegisters://读取寄存器数据RecBytes = new byte[length];Array.Copy(bytes, 3, RecBytes, 0, length);break;case FunctionCode.WriteSingleCoil:case FunctionCode.WriteSingleRegister:case FunctionCode.WriteMultipleCoils:case FunctionCode.WriteMultipleRegisters:RecBytes = new byte[4];Array.Copy(bytes, 2, RecBytes, 0, RecBytes.Length);break;case FunctionCode.ReportSlaveID:break;case FunctionCode.MaskWriteRegister:break;case FunctionCode.ReadWriteRegister:break;default:break;}}}else{ErrorMessage?.BeginInvoke(0, "报文校验未通过,可能传送中收到干扰!", null, null);}//接收完成,取消主线程阻塞slim.Set();}/// <summary>/// 串口波特率/// </summary>public int BaudRate{get{return serialPortObject.BaudRate;}set{serialPortObject.BaudRate = value;}}/// <summary>/// 数据位长度/// </summary>public int DataBits{get{return serialPortObject.DataBits;}set{serialPortObject.DataBits = value;}}/// <summary>/// 奇偶校验协议/// </summary>public Parity Parity{get{return serialPortObject.Parity;}set{serialPortObject.Parity = value;}}/// <summary>/// 停止位/// </summary>public StopBits StopBits{get{return serialPortObject.StopBits;}set{serialPortObject.StopBits = value;}}/// <summary>/// 当前的SerialPort对象/// </summary>public SerialPort SerialPortObject{get{return serialPortObject;}}/// <summary>/// 获取计算机所有的串行端口/// </summary>public string[] SerialPorts{get{return SerialPort.GetPortNames();}}/// <summary>/// 打开指定的串口/// </summary>/// <param name="serialPortName"></param>public void Open(string serialPortName){if (serialPortObject.IsOpen){serialPortObject.Close();}serialPortObject.PortName = serialPortName;serialPortObject.Open();}/// <summary>/// 关闭串口/// </summary>public void Close(){if (serialPortObject.IsOpen){serialPortObject.Close();}}/// <summary>/// 串口是否打开/// </summary>public bool IsOpen{get{return serialPortObject.IsOpen;}}/// <summary>///发送报文/// </summary>/// <param name="bytes"></param>/// <param name="protocol">报文协议</param>void Send(string sendMsg){//对消息进行判断是否合规if (!Regex.IsMatch(sendMsg, @"^[0-9a-fA-F]+$")){throw new Exception("报文错误,存在非16进制字符");}byte[] bytes = new byte[sendMsg.Length / 2];for (int i = 0; i < sendMsg.Length / 2; i++){bytes[i] = Convert.ToByte(sendMsg.Substring(i * 2, 2), 16);}//添加校验码,并发送byte[] checkSum = Checkhelper.CrcModbus(bytes, 0, bytes.Length);var checkarr = checkSum.Reverse().ToArray();byte[] msgBytes = new byte[bytes.Length + checkSum.Length];Array.Copy(bytes, msgBytes, bytes.Length);Array.Copy(checkarr, 0, msgBytes, bytes.Length, checkarr.Length);serialPortObject.Write(msgBytes, 0, msgBytes.Length);}/// <summary>/// 编制报文/// </summary>/// <param name="slaveStation">从站站号</param>/// <param name="function">功能代码</param>/// <param name="devAdress">起始地址</param>/// <param name="devLenght">数量</param>/// <param name="values">写入的值</param>///  /// <param name="fromBase">如果写入的数值类型所占用的byte,例如short类型为2,int类型为4,flost类型为4,double类型为8</param>/// <param name="set">强制线圈通断,true:ON ;false:OFF</param>void ComposeMessage(int slaveStation, FunctionCode function, int devAdress, int devLenght = 1, byte[] values = null, int fromBase = 2, bool set = false){lock (this){slim.Reset();StringBuilder sb = new StringBuilder();//添加从站点,功能代码,起始位置sb.AppendFormat("{0}{1}{2}", slaveStation.ToString("x2"), ((int)function).ToString("X2"), devAdress.ToString("X4"));switch (function){case FunctionCode.ReadCoils:case FunctionCode.ReadDiscreteInputs:case FunctionCode.ReadHoldingRegisters:case FunctionCode.ReadInputRegisters://读取的数量,占用2bytesb.Append(devLenght.ToString("X4"));break;case FunctionCode.WriteSingleCoil:if (set){//线圈置ONsb.Append("FF00");}else{//线圈置OFFsb.Append("0000");}break;case FunctionCode.WriteSingleRegister://向单个寄存器写入值,占用两个byteint curVal = BitConverter.ToInt16(values, 0);sb.Append(curVal.ToString("X4"));break;case FunctionCode.WriteMultipleCoils://向多个线圈写入值//1,需操作的线圈数量,数值占用两个bytesb.Append(devLenght.ToString("X4"));//2,需要操作的寄存器占用的字节数,数值占用1个bytesb.Append(((int)Math.Ceiling(devLenght * 1.0 / 8)).ToString("X2"));//3,写入线圈状态值foreach (var item in values){sb.Append(item.ToString("X2"));}break;case FunctionCode.WriteMultipleRegisters://向多个寄存器写入值//1,需操作的寄存器数量,数值占用两个bytesb.Append((values.Length * 2 / fromBase).ToString("X4"));//2,需要操作的寄存器占用的字节数,数值占用1个bytesb.Append((values.Length).ToString("X2"));//3,写入寄存器的值,根据frombase判定值//此时姑且以占2个Byte计算for (int i = 0; i < values.Length; i++){sb.Append(values[i].ToString("X2"));}break;case FunctionCode.ReportSlaveID:break;case FunctionCode.MaskWriteRegister:break;case FunctionCode.ReadWriteRegister:break;default:break;}RecBytes = null;Send(sb.ToString().Trim());serialPortObject.DiscardInBuffer();//100ms等待消息回复msgRecFlag = slim.WaitOne(3000);System.Threading.Thread.Sleep(20);if (!msgRecFlag){ErrorMessage?.Invoke(slaveStation, $"从站:{slaveStation},回复超时");}}}/// <summary>/// 强制指定单个线圈动作/// </summary>/// <param name="slaveStationNo">从站站号</param>/// <param name="devAdress">线圈地址</param>/// <param name="onOrOff">True为ON,false为Off</param>/// <returns></returns>public bool ForcedCoil(int slaveStationNo, int devAdress, bool set){ComposeMessage(slaveStationNo, FunctionCode.WriteSingleCoil, devAdress, set: set);if (slaveStationNo == targetStation && msgRecFlag && RecBytes != null){if (RecBytes[2] == 0xff && RecBytes[3] == 0 && set){return true;}if (RecBytes[3] == 0 && RecBytes[2] == 0 && !set){return true;}}return false;}/// <summary>/// 读取多个线圈状态/// </summary>/// <param name="slaveStationNo"></param>/// <param name="devAdress"></param>/// <param name="iLenght"></param>public bool[] ReadCoils(int slaveStationNo, int devAdress, int iLenght){ComposeMessage(slaveStationNo, FunctionCode.ReadCoils, devAdress, iLenght);if (slaveStationNo == targetStation && msgRecFlag && RecBytes != null){BitArray array = new BitArray(RecBytes);if (iLenght > array.Count){throw new Exception("线圈查询结果异常!");}bool[] coils = new bool[iLenght];for (int i = 0; i < iLenght; i++){coils[i] = array[i];}return coils;}else{return null;}}/// <summary>/// 向多个线圈写入值/// </summary>/// <param name="slaveStationNo">从站站号</param>/// <param name="devAdress"></param>/// <param name="iLenght">地址长度</param>/// <param name="arr">写入的值</param>public bool WriteMultipleCoils(int slaveStationNo, int devAdress, int iLenght, short[] arr){if ((int)Math.Ceiling(iLenght * 1.0 / 16) != arr.Length){throw new Exception("线圈数量与所赋值数量不匹配");}byte[] bytes = new byte[arr.Length * 2];int index = 0;foreach (var item in arr){byte[] temBytes = BitConverter.GetBytes(item);Array.Copy(temBytes, 0, bytes, index, temBytes.Length);index += temBytes.Length;}ComposeMessage(slaveStationNo, FunctionCode.WriteMultipleCoils, devAdress, iLenght, bytes);if (slaveStationNo == targetStation && msgRecFlag && RecBytes != null && RecBytes.Length == 4){int startAddress = BitConverter.ToInt16(new byte[] { RecBytes[1], RecBytes[0] }, 0);int addressLen = BitConverter.ToInt16(new byte[] { RecBytes[3], RecBytes[2] }, 0);if (startAddress == devAdress && addressLen == iLenght){return true;}else{return false;}}else{return false;}}/// <summary>/// 读取从站多个寄存器的值/// </summary>/// <param name="slaveStationNo"></param>/// <param name="devAdress">起始寄存器</param>/// <param name="iLenght">寄存器数量</param>public short[] ReadHoldingRegisters(int slaveStationNo, int devAdress, int iLenght){ComposeMessage(slaveStationNo, FunctionCode.ReadHoldingRegisters, devAdress, iLenght);if (slaveStationNo == targetStation && msgRecFlag && RecBytes != null){List<short> shortList = new List<short>();for (int i = 0; i < RecBytes.Length; i += 2){shortList.Add(BitConverter.ToInt16(new byte[] { RecBytes[i + 1], RecBytes[i] }, 0));}return shortList.ToArray();}else{return null;}}/// <summary>/// 读取单精度浮点数/// </summary>/// <param name="slaveStationNo">从站站点</param>/// <param name="devAdress">起始地址</param>/// <param name="iLenght">连续读取浮点数的个数</param>/// <returns></returns>public float[] ReadFloatRegisters(int slaveStationNo, int devAdress, int iLenght){ComposeMessage(slaveStationNo, FunctionCode.ReadHoldingRegisters, devAdress, iLenght * 2);if (slaveStationNo == targetStation && msgRecFlag && RecBytes != null){List<float> floatList = new List<float>();for (int i = 0; i < RecBytes.Length; i += 4){floatList.Add(BitConverter.ToSingle(new byte[] { RecBytes[i + 1], RecBytes[i], RecBytes[i + 3], RecBytes[i + 2] }, 0));}return floatList.ToArray();}else{return null;}}/// <summary>/// 向从站单个寄存器写入值/// </summary>/// <param name="slaveStationNo"></param>/// <param name="devAdress"></param>/// <param name="value"></param>public bool WriteSingleRegister(int slaveStationNo, int devAdress, short value){ComposeMessage(slaveStationNo, FunctionCode.WriteSingleRegister, devAdress, values: BitConverter.GetBytes(value));if (slaveStationNo == targetStation && msgRecFlag && RecBytes != null && RecBytes.Length == 4){int startAddress = BitConverter.ToInt16(new byte[] { RecBytes[1], RecBytes[0] }, 0);int val = BitConverter.ToInt16(new byte[] { RecBytes[3], RecBytes[2] }, 0);if (startAddress == devAdress && val == value){return true;}else{return false;}}else{return false;}}/// <summary>/// 写入单精度浮点数/// </summary>/// <param name="slaveStationNo">从站站点</param>/// <param name="devAdress">寄存器地址</param>/// <param name="value">值</param>/// <returns>true写入成功,false写入失败</returns>public bool WriteFloatRegister(int slaveStationNo, int devAdress, float value){//进行拆解组合byte[] bytes = BitConverter.GetBytes(value);byte[] valBytes = new byte[] { bytes[1], bytes[0], bytes[3], bytes[2] };ComposeMessage(slaveStationNo, FunctionCode.WriteMultipleRegisters, devAdress, values: valBytes, fromBase: 4);if (slaveStationNo == targetStation && msgRecFlag && RecBytes != null){short adress = BitConverter.ToInt16(new byte[] { RecBytes[1], RecBytes[0] }, 0);short registerCount = BitConverter.ToInt16(new byte[] { RecBytes[3], RecBytes[2] }, 0);if (adress == devAdress && registerCount == 2){return true;}else{return false;}}else{return false;}}/// <summary>/// 向从站多个寄存器写入值/// </summary>/// <param name="slaveStationNo"></param>/// <param name="devAdress"></param>/// <param name="iLenght"></param>/// <param name="arr"></param>public bool WriteMultipleRegisters(int slaveStationNo, int devAdress, int iLenght, short[] arr){if (iLenght != arr.Length){throw new Exception("寄存器数量与所赋值数量不匹配");}byte[] bytes = new byte[arr.Length * 2];int index = 0;foreach (var item in arr){byte[] temBytes = BitConverter.GetBytes(item);byte[] temBytes2 = new byte[] { temBytes[1], temBytes[0] };Array.Copy(temBytes2, 0, bytes, index, temBytes2.Length);index += temBytes.Length;}ComposeMessage(slaveStationNo, FunctionCode.WriteMultipleRegisters, devAdress, iLenght, bytes, fromBase: 4);if (slaveStationNo == targetStation && msgRecFlag && RecBytes != null && RecBytes.Length == 4){int startAddress = BitConverter.ToInt16(new byte[] { RecBytes[1], RecBytes[0] }, 0);int addressLen = BitConverter.ToInt16(new byte[] { RecBytes[3], RecBytes[2] }, 0);if (startAddress == devAdress && addressLen == iLenght){return true;}else{return false;}}else{return false;}}public void Dispose(){serialPortObject?.Dispose();}
}
3.6.5,校验码类
/// <summary>/// 数据校验帮助类,计算常见的CRC校验;LRC校验;BCC校验;累加和校验/// </summary>public class Checkhelper{#region CRC校验说明// CRC即循环冗余校验码(Cyclic Redundancy Check):是数据通信领域中最常用的一种查错校验码,//其特征是信息字段和校验字段的长度可以任意选定。循环冗余检查(CRC)是一种数据传输检错功能,对数据进行多项式计算,//并将得到的结果附在帧的后面,接收设备也执行类似的算法,以保证数据传输的正确性和完整性。// CRC算法参数模型解释:// NAME:参数模型名称。//WIDTH:宽度,即CRC比特数。// POLY:生成项的简写,以16进制表示。例如:CRC-32即是0x04C11DB7,忽略了最高位的"1",即完整的生成项是0x104C11DB7。//INIT:这是算法开始时寄存器(crc)的初始化预置值,十六进制表示。//REFIN:待测数据的每个字节是否按位反转,True或False。// REFOUT:在计算后之后,异或输出之前,整个数据是否按位反转,True或False。// XOROUT:计算结果与此参数异或后得到最终的CRC值。#endregion// **********************************************************************// Name: CRC-4/ITU    x4+x+1// Poly: 0x03// Init: 0x00// Refin: true// Refout: true// Xorout: 0x00//*************************************************************************/// <summary>/// CRC-4/ITU/// </summary>/// <param name="buffer"></param>/// <param name="start"></param>/// <param name="len"></param>/// <returns></returns>public static byte[] Crc1(byte[] buffer, int start = 0, int len = 0){if (buffer == null || buffer.Length == 0) return null;if (start < 0) return null;if (len == 0) len = buffer.Length - start;int length = start + len;if (length > buffer.Length) return null;byte crc = 0;// Initial valuefor (int i = start; i < length; i++){crc ^= buffer[i];for (int j = 0; j < 8; j++){if ((crc & 1) > 0)crc = (byte)((crc >> 1) ^ 0x0C);//0x0C = (reverse 0x03)>>(8-4)elsecrc = (byte)(crc >> 1);}}return new byte[] { crc };}// **********************************************************************// Name: CRC-5/EPC    x5+x3+1// Poly: 0x09// Init: 0x09// Refin: false// Refout: false// Xorout: 0x00//*************************************************************************/// <summary>/// CRC-5/EPC/// </summary>/// <param name="buffer"></param>/// <param name="start"></param>/// <param name="len"></param>/// <returns></returns>public static byte[] Crc2(byte[] buffer, int start = 0, int len = 0){if (buffer == null || buffer.Length == 0) return null;if (start < 0) return null;if (len == 0) len = buffer.Length - start;int length = start + len;if (length > buffer.Length) return null;byte crc = 0x48;// Initial value: 0x48 = 0x09<<(8-5)for (int i = start; i < length; i++){crc ^= buffer[i];for (int j = 0; j < 8; j++){if ((crc & 0x80) > 0)crc = (byte)((crc << 1) ^ 0x48);// 0x48 = 0x09<<(8-5)elsecrc = (byte)(crc << 1);}}return new byte[] { (byte)(crc >> 3) };}// **********************************************************************// Name: CRC-5/ITU    x5+x4+x2+1// Poly: 0x15// Init: 0x00// Refin: true// Refout: true// Xorout: 0x00//*************************************************************************/// <summary>/// CRC-5/ITU /// </summary>/// <param name="buffer"></param>/// <param name="start"></param>/// <param name="len"></param>/// <returns></returns>public static byte[] Crc3(byte[] buffer, int start = 0, int len = 0){if (buffer == null || buffer.Length == 0) return null;if (start < 0) return null;if (len == 0) len = buffer.Length - start;int length = start + len;if (length > buffer.Length) return null;byte crc = 0;// Initial valuefor (int i = start; i < length; i++){crc ^= buffer[i];for (int j = 0; j < 8; j++){if ((crc & 1) > 0)crc = (byte)((crc >> 1) ^ 0x15);// 0x15 = (reverse 0x15)>>(8-5)elsecrc = (byte)(crc >> 1);}}return new byte[] { crc };}// **********************************************************************// Name: CRC-5/USB    x5+x2+1// Poly: 0x05// Init: 0x1F// Refin: true// Refout: true// Xorout: 0x1F//*************************************************************************/// <summary>/// CRC-5/USB/// </summary>/// <param name="buffer"></param>/// <param name="start"></param>/// <param name="len"></param>/// <returns></returns>public static byte[] Crc4(byte[] buffer, int start = 0, int len = 0){if (buffer == null || buffer.Length == 0) return null;if (start < 0) return null;if (len == 0) len = buffer.Length - start;int length = start + len;if (length > buffer.Length) return null;byte crc = 0x1F;// Initial valuefor (int i = start; i < length; i++){crc ^= buffer[i];for (int j = 0; j < 8; j++){if ((crc & 1) > 0)crc = (byte)((crc >> 1) ^ 0x14);// 0x14 = (reverse 0x05)>>(8-5)elsecrc = (byte)(crc >> 1);}}return new byte[] { (byte)(crc ^ 0x1F) };}// **********************************************************************// Name: CRC-6/ITU    x6+x+1// Poly: 0x03// Init: 0x00// Refin: true// Refout: true// Xorout: 0x00//*************************************************************************/// <summary>/// CRC-6/ITU/// </summary>/// <param name="buffer"></param>/// <param name="start"></param>/// <param name="len"></param>/// <returns></returns>public static byte[] Crc5(byte[] buffer, int start = 0, int len = 0){if (buffer == null || buffer.Length == 0) return null;if (start < 0) return null;if (len == 0) len = buffer.Length - start;int length = start + len;if (length > buffer.Length) return null;byte crc = 0;// Initial valuefor (int i = start; i < length; i++){crc ^= buffer[i];for (int j = 0; j < 8; j++){if ((crc & 1) > 0)crc = (byte)((crc >> 1) ^ 0x30);// 0x30 = (reverse 0x03)>>(8-6)elsecrc = (byte)(crc >> 1);}}return new byte[] { crc };}// **********************************************************************// Name: CRC-7/MMC    x7+x3+1// Poly: 0x09// Init: 0x00// Refin: false// Refout: false// Xorout: 0x00//*************************************************************************/// <summary>/// CRC-7/MMC/// </summary>/// <param name="buffer"></param>/// <param name="start"></param>/// <param name="len"></param>/// <returns></returns>public static byte[] Crc6(byte[] buffer, int start = 0, int len = 0){if (buffer == null || buffer.Length == 0) return null;if (start < 0) return null;if (len == 0) len = buffer.Length - start;int length = start + len;if (length > buffer.Length) return null;byte crc = 0;// Initial valuefor (int i = start; i < length; i++){crc ^= buffer[i];for (int j = 0; j < 8; j++){if ((crc & 0x80) > 0)crc = (byte)((crc << 1) ^ 0x12);// 0x12 = 0x09<<(8-7)elsecrc = (byte)(crc << 1);}}return new byte[] { (byte)(crc >> 1) };}// **********************************************************************// Name: CRC8    x8+x2+x+1// Poly: 0x07// Init: 0x00// Refin: false// Refout: false// Xorout: 0x00//*************************************************************************/// <summary>/// CRC8 /// </summary>/// <param name="buffer"></param>/// <param name="start"></param>/// <param name="len"></param>/// <returns></returns>public static byte[] Crc7(byte[] buffer, int start = 0, int len = 0){if (buffer == null || buffer.Length == 0) return null;if (start < 0) return null;if (len == 0) len = buffer.Length - start;int length = start + len;if (length > buffer.Length) return null;byte crc = 0;// Initial valuefor (int i = start; i < length; i++){crc ^= buffer[i];for (int j = 0; j < 8; j++){if ((crc & 0x80) > 0)crc = (byte)((crc << 1) ^ 0x07);elsecrc = (byte)(crc << 1);}}return new byte[] { crc };}// **********************************************************************// Name: CRC-8/ITU    x8+x2+x+1// Poly: 0x07// Init: 0x00// Refin: false// Refout: false// Xorout: 0x55//*************************************************************************/// <summary>/// CRC-8/ITU/// </summary>/// <param name="buffer"></param>/// <param name="start"></param>/// <param name="len"></param>/// <returns></returns>public static byte[] Crc8(byte[] buffer, int start = 0, int len = 0){if (buffer == null || buffer.Length == 0) return null;if (start < 0) return null;if (len == 0) len = buffer.Length - start;int length = start + len;if (length > buffer.Length) return null;byte crc = 0;// Initial valuefor (int i = start; i < length; i++){crc ^= buffer[i];for (int j = 0; j < 8; j++){if ((crc & 0x80) > 0)crc = (byte)((crc << 1) ^ 0x07);elsecrc = (byte)(crc << 1);}}return new byte[] { (byte)(crc ^ 0x55) };}// **********************************************************************// Name: CRC-8/MAXIM    x8+x5+x4+1// Poly: 0x31// Init: 0x00// Refin: true// Refout: true// Xorout: 0x00//*************************************************************************/// <summary>/// CRC-8/MAXIM /// </summary>/// <param name="buffer"></param>/// <param name="start"></param>/// <param name="len"></param>/// <returns></returns>public static byte[] Crc9(byte[] buffer, int start = 0, int len = 0){if (buffer == null || buffer.Length == 0) return null;if (start < 0) return null;if (len == 0) len = buffer.Length - start;int length = start + len;if (length > buffer.Length) return null;byte crc = 0;// Initial valuefor (int i = start; i < length; i++){crc ^= buffer[i];for (int j = 0; j < 8; j++){if ((crc & 1) > 0)crc = (byte)((crc >> 1) ^ 0x8C);// 0x8C = reverse 0x31elsecrc = (byte)(crc >> 1);}}return new byte[] { crc };}// **********************************************************************// Name: CRC-8/ROHC    x8+x2+x+1// Poly: 0x07// Init: 0xFF// Refin: true// Refout: true// Xorout: 0x00//*************************************************************************/// <summary>/// CRC-8/ROHC/// </summary>/// <param name="buffer"></param>/// <param name="start"></param>/// <param name="len"></param>/// <returns></returns>public static byte[] Crc10(byte[] buffer, int start = 0, int len = 0){if (buffer == null || buffer.Length == 0) return null;if (start < 0) return null;if (len == 0) len = buffer.Length - start;int length = start + len;if (length > buffer.Length) return null;byte crc = 0xFF;// Initial valuefor (int i = start; i < length; i++){crc ^= buffer[i];for (int j = 0; j < 8; j++){if ((crc & 1) > 0)crc = (byte)((crc >> 1) ^ 0xE0);// 0xE0 = reverse 0x07elsecrc = (byte)(crc >> 1);}}return new byte[] { crc };}/// Z1协议校验码计算static byte[] table = { 0x00, 0x1C, 0x38, 0x24, 0x70, 0x6C, 0x48, 0x54, 0xE0, 0xFC,0xD8, 0xC4, 0x90, 0x8C, 0xA8, 0xB4, 0xDC, 0xC0, 0xE4, 0xF8,0xAC, 0xB0, 0x94, 0x88, 0x3C, 0x20, 0x04, 0x18, 0x4C, 0x50,0x74, 0x68, 0xA4, 0xB8, 0x9C, 0x80, 0xD4, 0xC8, 0xEC, 0xF0,0x44, 0x58, 0x7C, 0x60, 0x34, 0x28, 0x0C, 0x10, 0x78, 0x64,0x40, 0x5C, 0x08, 0x14, 0x30, 0x2C, 0x98, 0x84, 0xA0, 0xBC,0xE8, 0xF4, 0xD0, 0xCC, 0x54, 0x48, 0x6C, 0x70, 0x24, 0x38,0x1C, 0x00, 0xB4, 0xA8, 0x8C, 0x90, 0xC4, 0xD8, 0xFC, 0xE0,0x88, 0x94, 0xB0, 0xAC, 0xF8, 0xE4, 0xC0, 0xDC, 0x68, 0x74,0x50, 0x4C, 0x18, 0x04, 0x20, 0x3C, 0xF0, 0xEC, 0xC8, 0xD4,0x80, 0x9C, 0xB8, 0xA4, 0x10, 0x0C, 0x28, 0x34, 0x60, 0x7C,0x58, 0x44, 0x2C, 0x30, 0x14, 0x08, 0x5C, 0x40, 0x64, 0x78,0xCC, 0xD0, 0xF4, 0xE8, 0xBC, 0xA0, 0x84, 0x98, 0xA8, 0xB4,0x90, 0x8C, 0xD8, 0xC4, 0xE0, 0xFC, 0x48, 0x54, 0x70, 0x6C,0x38, 0x24, 0x00, 0x1C, 0x74, 0x68, 0x4C, 0x50, 0x04, 0x18,0x3C, 0x20, 0x94, 0x88, 0xAC, 0xB0, 0xE4, 0xF8, 0xDC, 0xC0,0x0C, 0x10, 0x34, 0x28, 0x7C, 0x60, 0x44, 0x58, 0xEC, 0xF0,0xD4, 0xC8, 0x9C, 0x80, 0xA4, 0xB8, 0xD0, 0xCC, 0xE8, 0xF4,0xA0, 0xBC, 0x98, 0x84, 0x30, 0x2C, 0x08, 0x14, 0x40, 0x5C,0x78, 0x64, 0xFC, 0xE0, 0xC4, 0xD8, 0x8C, 0x90, 0xB4, 0xA8,0x1C, 0x00, 0x24, 0x38, 0x6C, 0x70, 0x54, 0x48, 0x20, 0x3C,0x18, 0x04, 0x50, 0x4C, 0x68, 0x74, 0xC0, 0xDC, 0xF8, 0xE4,0xB0, 0xAC, 0x88, 0x94, 0x58, 0x44, 0x60, 0x7C, 0x28, 0x34,0x10, 0x0C, 0xB8, 0xA4, 0x80, 0x9C, 0xC8, 0xD4, 0xF0, 0xEC,0x84, 0x98, 0xBC, 0xA0, 0xF4, 0xE8, 0xCC, 0xD0, 0x64, 0x78,0x5C, 0x40, 0x14, 0x08, 0x2C, 0x30};public static byte[] Crc11(byte[] buffer, int start = 0, int len = 0){if (buffer == null || buffer.Length == 0) return null;if (start < 0) return null;if (len == 0) len = buffer.Length - start;int length = start + len;if (length > buffer.Length) return null;int i;byte crc = 0x00;int tableIndex;for (i = start; i < length; i++){tableIndex = crc ^ (buffer[i] & 0xFF);crc = table[tableIndex];}return new byte[] { crc };}// **********************************************************************// Name: CRC-12    x16+x12+x5+1// Poly: 0x80// Init: 0x0000// Refin: true// Refout: true// Xorout: 0x0000//*************************************************************************/// <summary>/// CRC-12 /// </summary>/// <param name="buffer"></param>/// <param name="start"></param>/// <param name="len"></param>/// <returns></returns>public static byte[] Crc12(byte[] buffer, int start = 0, int len = 0){if (buffer == null || buffer.Length == 0) return null;if (start < 0) return null;if (len == 0) len = buffer.Length - start;int length = start + len;if (length > buffer.Length) return null;ushort crc = 0;// Initial valueshort iQ = 0, iR = 0;for (int i = start; i < length; i++){// 多项式除法// 如果该位为1if ((buffer[i] & (0x80 >> iR)) > 0){// 则在余数尾部添1否则添0crc |= 0x01;}// 如果12位除数中的最高位为1,则够除if (crc >= 0x1000){crc ^= 0x180D;}crc <<= 1;iR++;if (8 == iR){iR = 0;iQ++;}}// 对后面添加的12个0做处理for (int i = 0; i < 12; i++){if (crc >= 0x1000){crc ^= 0x180D;}crc <<= 1;}crc >>= 1;byte[] ret = BitConverter.GetBytes(crc);Array.Reverse(ret);return ret;}// **********************************************************************// Name: CRC-16/CCITT    x16+x12+x5+1// Poly: 0x1021// Init: 0x0000// Refin: true// Refout: true// Xorout: 0x0000//*************************************************************************/// <summary>/// CRC-16/CCITT/// </summary>/// <param name="buffer"></param>/// <param name="start"></param>/// <param name="len"></param>/// <returns></returns>public static byte[] Crc13(byte[] buffer, int start = 0, int len = 0){if (buffer == null || buffer.Length == 0) return null;if (start < 0) return null;if (len == 0) len = buffer.Length - start;int length = start + len;if (length > buffer.Length) return null;ushort crc = 0;// Initial valuefor (int i = start; i < length; i++){crc ^= buffer[i];for (int j = 0; j < 8; j++){if ((crc & 1) > 0)crc = (ushort)((crc >> 1) ^ 0x8408);// 0x8408 = reverse 0x1021elsecrc = (ushort)(crc >> 1);}}byte[] ret = BitConverter.GetBytes(crc);Array.Reverse(ret);return ret;}// **********************************************************************// Name: CRC-16/CCITT FALSE    x16+x12+x5+1// Poly: 0x1021// Init: 0xFFFF// Refin: false// Refout: false// Xorout: 0x0000//*************************************************************************/// <summary>/// CRC-16/CCITT FALSE/// </summary>/// <param name="buffer"></param>/// <param name="start"></param>/// <param name="len"></param>/// <returns></returns>public static byte[] Crc14(byte[] buffer, int start = 0, int len = 0){if (buffer == null || buffer.Length == 0) return null;if (start < 0) return null;if (len == 0) len = buffer.Length - start;int length = start + len;if (length > buffer.Length) return null;ushort crc = 0xFFFF;// Initial valuefor (int i = start; i < length; i++){crc ^= (ushort)(buffer[i] << 8);for (int j = 0; j < 8; j++){if ((crc & 0x8000) > 0)crc = (ushort)((crc << 1) ^ 0x1021);elsecrc = (ushort)(crc << 1);}}byte[] ret = BitConverter.GetBytes(crc);Array.Reverse(ret);return ret;}// **********************************************************************// Name: CRC-16/DNP    x16+x13+x12+x11+x10+x8+x6+x5+x2+1// Poly: 0x3D65// Init: 0x0000// Refin: true// Refout: true// Xorout: 0xFFFF//*************************************************************************/// <summary>/// CRC-16/DNP /// </summary>/// <param name="buffer"></param>/// <param name="start"></param>/// <param name="len"></param>/// <returns></returns>public static byte[] Crc15(byte[] buffer, int start = 0, int len = 0){if (buffer == null || buffer.Length == 0) return null;if (start < 0) return null;if (len == 0) len = buffer.Length - start;int length = start + len;if (length > buffer.Length) return null;ushort crc = 0;// Initial valuefor (int i = start; i < length; i++){crc ^= buffer[i];for (int j = 0; j < 8; j++){if ((crc & 1) > 0)crc = (ushort)((crc >> 1) ^ 0xA6BC);// 0xA6BC = reverse 0x3D65elsecrc = (ushort)(crc >> 1);}}byte[] ret = BitConverter.GetBytes((ushort)~crc);Array.Reverse(ret);return ret;}// **********************************************************************// Name: CRC-16/IBM    x16+x15+x2+1// Poly: 0x8005// Init: 0x0000// Refin: true// Refout: true// Xorout: 0x0000//*************************************************************************/// <summary>/// CRC-16/IBM/// </summary>/// <param name="buffer"></param>/// <param name="start"></param>/// <param name="len"></param>/// <returns></returns>public static byte[] Crc16(byte[] buffer, int start = 0, int len = 0){if (buffer == null || buffer.Length == 0) return null;if (start < 0) return null;if (len == 0) len = buffer.Length - start;int length = start + len;if (length > buffer.Length) return null;ushort crc = 0;// Initial valuefor (int i = start; i < length; i++){crc ^= buffer[i];for (int j = 0; j < 8; j++){if ((crc & 1) > 0)crc = (ushort)((crc >> 1) ^ 0xA001);// 0xA001 = reverse 0x8005elsecrc = (ushort)(crc >> 1);}}byte[] ret = BitConverter.GetBytes(crc);Array.Reverse(ret);return ret;}// **********************************************************************// Name: CRC-16/MAXIM    x16+x15+x2+1// Poly: 0x8005// Init: 0x0000// Refin: true// Refout: true// Xorout: 0xFFFF//*************************************************************************/// <summary>/// CRC-16/MAXIM/// </summary>/// <param name="buffer"></param>/// <param name="start"></param>/// <param name="len"></param>/// <returns></returns>public static byte[] Crc17(byte[] buffer, int start = 0, int len = 0){if (buffer == null || buffer.Length == 0) return null;if (start < 0) return null;if (len == 0) len = buffer.Length - start;int length = start + len;if (length > buffer.Length) return null;ushort crc = 0;// Initial valuefor (int i = start; i < length; i++){crc ^= buffer[i];for (int j = 0; j < 8; j++){if ((crc & 1) > 0)crc = (ushort)((crc >> 1) ^ 0xA001);// 0xA001 = reverse 0x8005elsecrc = (ushort)(crc >> 1);}}byte[] ret = BitConverter.GetBytes((ushort)~crc);Array.Reverse(ret);return ret;}// **********************************************************************// Name: CRC-16/MODBUS    x16+x15+x2+1// Poly: 0x8005// Init: 0xFFFF// Refin: true// Refout: true// Xorout: 0x0000//*************************************************************************/// <summary>/// CRC-16/MODBUS/// </summary>/// <param name="buffer"></param>/// <param name="start"></param>/// <param name="len"></param>/// <returns></returns>public static byte[] CrcModbus(byte[] buffer, int start = 0, int len = 0){if (buffer == null || buffer.Length == 0) return null;if (start < 0) return null;if (len == 0) len = buffer.Length - start;int length = start + len;if (length > buffer.Length) return null;ushort crc = 0xFFFF;// Initial valuefor (int i = start; i < length; i++){crc ^= buffer[i];for (int j = 0; j < 8; j++){if ((crc & 1) > 0)crc = (ushort)((crc >> 1) ^ 0xA001);// 0xA001 = reverse 0x8005elsecrc = (ushort)(crc >> 1);}}byte[] ret = BitConverter.GetBytes(crc);Array.Reverse(ret);return ret;}// **********************************************************************// Name: CRC-16/USB    x16+x15+x2+1// Poly: 0x8005// Init: 0xFFFF// Refin: true// Refout: true// Xorout: 0xFFFF//*************************************************************************/// <summary>/// CRC-16/USB/// </summary>/// <param name="buffer"></param>/// <param name="start"></param>/// <param name="len"></param>/// <returns></returns>public static byte[] Crc19(byte[] buffer, int start = 0, int len = 0){if (buffer == null || buffer.Length == 0) return null;if (start < 0) return null;if (len == 0) len = buffer.Length - start;int length = start + len;if (length > buffer.Length) return null;ushort crc = 0xFFFF;// Initial valuefor (int i = start; i < length; i++){crc ^= buffer[i];for (int j = 0; j < 8; j++){if ((crc & 1) > 0)crc = (ushort)((crc >> 1) ^ 0xA001);// 0xA001 = reverse 0x8005elsecrc = (ushort)(crc >> 1);}}byte[] ret = BitConverter.GetBytes((ushort)~crc);Array.Reverse(ret);return ret;}// **********************************************************************// Name: CRC-16/X25    x16+x12+x5+1// Poly: 0x1021// Init: 0xFFFF// Refin: true// Refout: true// Xorout: 0xFFFF//*************************************************************************/// <summary>/// CRC-16/X25/// </summary>/// <param name="buffer"></param>/// <param name="start"></param>/// <param name="len"></param>/// <returns></returns>public static byte[] Crc20(byte[] buffer, int start = 0, int len = 0){if (buffer == null || buffer.Length == 0) return null;if (start < 0) return null;if (len == 0) len = buffer.Length - start;int length = start + len;if (length > buffer.Length) return null;ushort crc = 0xFFFF;// Initial valuefor (int i = start; i < length; i++){crc ^= buffer[i];for (int j = 0; j < 8; j++){if ((crc & 1) > 0)crc = (ushort)((crc >> 1) ^ 0x8408);// 0x8408 = reverse 0x1021elsecrc = (ushort)(crc >> 1);}}byte[] ret = BitConverter.GetBytes((ushort)~crc);Array.Reverse(ret);return ret;}// **********************************************************************// Name: CRC-16/XMODEM    x16+x12+x5+1// Poly: 0x1021// Init: 0x0000// Refin: false// Refout: false// Xorout: 0x0000//*************************************************************************/// <summary>/// CRC-16/XMODEM/// </summary>/// <param name="buffer"></param>/// <param name="start"></param>/// <param name="len"></param>/// <returns></returns>public static byte[] Crc21(byte[] buffer, int start = 0, int len = 0){if (buffer == null || buffer.Length == 0) return null;if (start < 0) return null;if (len == 0) len = buffer.Length - start;int length = start + len;if (length > buffer.Length) return null;ushort crc = 0;// Initial valuefor (int i = start; i < length; i++){crc ^= (ushort)(buffer[i] << 8);for (int j = 0; j < 8; j++){if ((crc & 0x8000) > 0)crc = (ushort)((crc << 1) ^ 0x1021);elsecrc = (ushort)(crc << 1);}}byte[] ret = BitConverter.GetBytes(crc);Array.Reverse(ret);return ret;}// **********************************************************************// Name: CRC32    x32+x26+x23+x22+x16+x12+x11+x10+x8+x7+x5+x4+x2+x+1// Poly: 0x04C11DB7// Init: 0xFFFFFFFF// Refin: true// Refout: true// Xorout: 0xFFFFFFFF//*************************************************************************/// <summary>/// CRC32/// </summary>/// <param name="buffer"></param>/// <param name="start"></param>/// <param name="len"></param>/// <returns></returns>public static byte[] Crc22(byte[] buffer, int start = 0, int len = 0){if (buffer == null || buffer.Length == 0) return null;if (start < 0) return null;if (len == 0) len = buffer.Length - start;int length = start + len;if (length > buffer.Length) return null;uint crc = 0xFFFFFFFF;// Initial valuefor (int i = start; i < length; i++){crc ^= buffer[i];for (int j = 0; j < 8; j++){if ((crc & 1) > 0)crc = (crc >> 1) ^ 0xEDB88320;// 0xEDB88320= reverse 0x04C11DB7elsecrc = crc >> 1;}}byte[] ret = BitConverter.GetBytes(~crc);Array.Reverse(ret);return ret;}// **********************************************************************// Name: CRC32/MPEG-2    x32+x26+x23+x22+x16+x12+x11+x10+x8+x7+x5+x4+x2+x+1// Poly: 0x04C11DB7// Init: 0xFFFFFFFF// Refin: false// Refout: false// Xorout: 0x00000000//*************************************************************************/// <summary>/// CRC32/MPEG-2/// </summary>/// <param name="buffer"></param>/// <param name="start"></param>/// <param name="len"></param>/// <returns></returns>public static byte[] Crc23(byte[] buffer, int start = 0, int len = 0){if (buffer == null || buffer.Length == 0) return null;if (start < 0) return null;if (len == 0) len = buffer.Length - start;int length = start + len;if (length > buffer.Length) return null;uint crc = 0xFFFFFFFF;// Initial valuefor (int i = start; i < length; i++){crc ^= (uint)(buffer[i] << 24);for (int j = 0; j < 8; j++){if ((crc & 0x80000000) > 0)crc = (crc << 1) ^ 0x04C11DB7;elsecrc = crc << 1;}}byte[] ret = BitConverter.GetBytes(crc);Array.Reverse(ret);return ret;}//***************************************************************/* 纵向冗余校验(Longitudinal Redundancy Check,简称:LRC)是通信中常用的一种校验形式,也称LRC校验或纵向校验。 它是一种从纵向通道上的特定比特串产生校验比特的错误检测方法。在行列格式中(如磁带),LRC经常是与VRC一起使用,这样就会为每个字符校验码。在工业领域Modbus协议Ascii模式采用该算法。 LRC计算校验码,具体算法如下:*/// 1、对需要校验的数据(2n个字符)两两组成一个16进制的数值求和。// 2、将求和结果与256求模。// 3、用256减去所得模值得到校验结果(另一种方法:将模值按位取反然后加1)。/// <summary>/// LRC校验(纵向冗余校验)工业领域Modbus协议Ascii模式采用该算法/// </summary>/// <param name="buffer"></param>/// <param name="start"></param>/// <param name="len"></param>/// <returns></returns>public static byte[] Lrc(byte[] buffer, int start = 0, int len = 0){if (buffer == null || buffer.Length == 0) return null;if (start < 0) return null;if (len == 0) len = buffer.Length - start;int length = start + len;if (length > buffer.Length) return null;byte lrc = 0;// Initial valuefor (int i = start; i < len; i++){lrc += buffer[i];}lrc = (byte)((lrc ^ 0xFF) + 1);return new byte[] { lrc };}/// <summary>/// BCC(Block Check Character/信息组校验码),因校验码是将所有数据异或得出,故俗称异或校验。具体算法是:将每一个字节的数据(一般是两个16进制的字符)进行异或后即得到校验码。/// </summary>/// <param name="buffer"></param>/// <param name="start"></param>/// <param name="len"></param>/// <returns></returns>public static byte[] Bcc(byte[] buffer, int start = 0, int len = 0){if (buffer == null || buffer.Length == 0) return null;if (start < 0) return null;if (len == 0) len = buffer.Length - start;int length = start + len;if (length > buffer.Length) return null;byte bcc = 0;// Initial valuefor (int i = start; i < len; i++){bcc ^= buffer[i];}return new byte[] { bcc };}/// <summary>///  检验和(checksum),在数据处理和数据通信领域中,用于校验目的地一组数据项的和。它通常是以十六进制为数制表示的形式。如果校验和的数值超过十六进制的FF,也就是255. 就要求其补码作为校验和。通常用来在通信中,尤其是远距离通信中保证数据的完整性和准确性。/// </summary>/// <param name="buffer"></param>/// <param name="start"></param>/// <param name="len"></param>/// <returns></returns>public static byte[] allAdd(byte[] buffer, int start = 0, int len = 0){if (buffer == null || buffer.Length == 0) return null;if (start < 0) return null;if (len == 0) len = buffer.Length - start;int length = start + len;if (length > buffer.Length) return null;byte bcc = 0;// Initial valuefor (int i = start; i < len; i++){bcc ^= buffer[i];}return new byte[] { bcc };}}
    /// <summary>/// 操作功能码/// </summary>public enum FunctionCode{/// <summary>/// 读写从站输出线圈状态0XXXX状态/// </summary>ReadCoils = 1,/// <summary>/// 只读从站输入线圈状态1XXXX状态/// </summary>ReadDiscreteInputs = 2,/// <summary>/// 读写从站保存寄存器4XXXX状态/// </summary>ReadHoldingRegisters = 3,/// <summary>/// 只读从站输入寄存器3XXXX值/// </summary>ReadInputRegisters = 4,/// <summary>/// 强制从站单个线圈状态/// </summary>WriteSingleCoil = 5,/// <summary>/// 向从站单个寄存器写入值/// </summary>WriteSingleRegister = 6,/// <summary>/// 强制从站多个线圈状态/// </summary>WriteMultipleCoils = 15,/// <summary>/// 向从站多个寄存器写入值/// </summary>WriteMultipleRegisters = 16,ReportSlaveID = 17,MaskWriteRegister = 22,ReadWriteRegister = 23}/// <summary>/// 从站返回异常代码/// </summary>public enum ErrorCode{/// <summary>/// 非法功能/// </summary>IllegalFunction,/// <summary>/// 非法数据地址/// </summary>IllegalDataAddress,/// <summary>/// 非法数据/// </summary>IllegalData,/// <summary>/// 相关设备故障/// </summary>RelatedEquipmentFailure,/// <summary>/// 确认/// </summary>Verify,/// <summary>/// 忙碌、拒绝执行/// </summary>Busy,}

4,基于ModbusTCP通信。

4.1,ModbusTCP报文格式参考链接

https://www.cnblogs.com/Kirito-Asuna-Yoyi/p/ModbusTCP.html

注明:ModbusTCP是基于以太网Socket通信的协议,无需校验码。

4.2,代码

4.2.1,准备

配置连接参数。

配置功能

4.2.2,UI部分

4.2.3,UI代码部分
  public partial class Form1 : Form{ModbusTCP modbus;int slaveStation = 10;Timer timer = new Timer();public Form1(){InitializeComponent();UIControlStatusChange(false);Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);modbus = new ModbusTCP();modbus.ErrorMessage += Modbus_ErrorMessage;timer.Interval = 1000;foreach (Control item in groupBox1.Controls){if (item is GroupBox){foreach (Control c in item.Controls){if (c is Button){(c as Button).Click += Button_Click;}}}}}private void Button_Click(object sender, EventArgs e){lblErrorMsg.Text = "";}private void Modbus_ErrorMessage(int arg1, string arg2){this.Invoke(new Action(() =>{lblErrorMsg.Text = $"从站[{arg1}]出现异常,原因:{arg2}";}));}/// <summary>/// 变更UI控件状态/// </summary>/// <param name="status">True:已连接,False:连接断开</param>void UIControlStatusChange(bool status){if (status){txtIP.Enabled = false;txtPort.Enabled = false;txtSlave.Enabled = false;btnConnect.BackColor = Color.LightGreen;btnConnect.Text = "断开连接";groupBox1.Enabled = true;}else{txtIP.Enabled = true;txtPort.Enabled = true;txtSlave.Enabled = true;btnConnect.BackColor = Color.Red;btnConnect.Text = "连接";groupBox1.Enabled = false;}}private void btnConnect_Click(object sender, EventArgs e){if (btnConnect.Text.Equals("连接")){if (modbus.IsConnected){modbus.Disconnect();}IPEndPoint endPoint = new IPEndPoint(IPAddress.Parse(txtIP.Text.Trim()), int.Parse(txtPort.Text.Trim()));modbus.RemoteEndPoint = endPoint;modbus.Connect();}else{if (modbus.IsConnected){modbus.Disconnect();}}UIControlStatusChange(modbus.IsConnected);}//读取多个线圈private void btnReadCoils_Click(object sender, EventArgs e){int address; int len;if (!int.TryParse(txtCoilsAddress.Text.Trim(), out address)){txtCoilsAddress.Focus();MessageBox.Show("线圈地址错误!");return;}if (!int.TryParse(txtCoilsCount.Text.Trim(), out len)){txtCoilsCount.Focus();MessageBox.Show("线圈数量只能为正整数!");return;}bool[] reuslt = modbus.ReadCoils(slaveStation, address, len);if (reuslt == null) return;StringBuilder sb = new StringBuilder();foreach (var item in reuslt){if (item){sb.Append(" 1");}else{sb.Append(" 0");}}lblCoilsResult.Text = sb.ToString();}private void txtSlave_TextChanged(object sender, EventArgs e){if (!int.TryParse(txtSlave.Text.Trim(), out slaveStation)){slaveStation = 10;}}private void btnForced_Click(object sender, EventArgs e){int address;if (!int.TryParse(txtCoilsWriteAdress.Text.Trim(), out address)){txtCoilsWriteAdress.Focus();MessageBox.Show("线圈写入地址错误!");return;}if (btnForced.Text.Equals("ON")){bool result = modbus.ForcedCoil(slaveStation, address, false);if (result){btnForced.Text = "OFF";btnForced.BackColor = Color.Red;}}else{bool result = modbus.ForcedCoil(slaveStation, address, true);if (result){btnForced.Text = "ON";btnForced.BackColor = Color.LightGreen;}}}private void btnRegistersRead_Click(object sender, EventArgs e){int address; int len;if (!int.TryParse(txtRegistersAdress.Text.Trim(), out address)){txtRegistersAdress.Focus();MessageBox.Show("寄存器地址错误!");return;}if (!int.TryParse(txtRegistersCount.Text.Trim(), out len)){txtRegistersCount.Focus();MessageBox.Show("寄存器数量只能为正整数!");return;}short[] result = modbus.ReadHoldingRegisters(slaveStation, address, len);if (result == null) return;StringBuilder sb = new StringBuilder();foreach (var item in result){sb.Append(" " + item.ToString());}lblRegistersResult.Text = sb.ToString();}private void btnReadFloat_Click(object sender, EventArgs e){int address; int len;if (!int.TryParse(txtRegistersAdress.Text.Trim(), out address)){txtRegistersAdress.Focus();MessageBox.Show("寄存器地址错误!");return;}if (!int.TryParse(txtRegistersCount.Text.Trim(), out len)){txtRegistersCount.Focus();MessageBox.Show("寄存器数量只能为正整数!");return;}float[] result = modbus.ReadFloatRegisters(slaveStation, address, len);if (result == null) return;StringBuilder sb = new StringBuilder();foreach (var item in result){sb.Append(" " + item.ToString());}lblRegistersResult.Text = sb.ToString();}private void btnWriteShort_Click(object sender, EventArgs e){int address; short val;if (!int.TryParse(txtWriteShortAdress.Text.Trim(), out address)){txtWriteShortAdress.Focus();MessageBox.Show("寄存器地址错误!");return;}if (!short.TryParse(txtWriteShortVal.Text.Trim(), out val)){txtWriteShortVal.Focus();MessageBox.Show("写入的值只能为正整数!");return;}if (modbus.WriteSingleRegister(slaveStation, address, val)){MessageBox.Show("Short数已成功写入"); }else{MessageBox.Show("Short数写入失败");}}private void btnWriteFloat_Click(object sender, EventArgs e){int address; float val;if (!int.TryParse(txtFloatWriteAddress.Text.Trim(), out address)){txtFloatWriteAddress.Focus();MessageBox.Show("寄存器地址错误!");return;}if (!float.TryParse(txtWriteFloatVal.Text.Trim(), out val)){txtWriteFloatVal.Focus();MessageBox.Show("写入的值只能为正整数!");return;}if (modbus.WriteFloatRegister(slaveStation, address, val)){MessageBox.Show("Float数已成功写入");}else{MessageBox.Show("Float数写入失败");}}//给线圈批量赋值private void btnBatch_Click(object sender, EventArgs e){int address; short val;if (!int.TryParse(txtCoils.Text.Trim(), out address)){txtCoils.Focus();MessageBox.Show("线圈地址错误!");return;}if (!short.TryParse(txtCoilsVal.Text.Trim(), out val)){txtCoilsVal.Focus();MessageBox.Show("写入的值只能为正整数!");return;}bool result = modbus.WriteMultipleCoils(slaveStation, address, 16, new short[] { val });if (result){MessageBox.Show("线圈已批量写入");}else{MessageBox.Show("线圈批量失败");}}private void btnMulShortWrite_Click(object sender, EventArgs e){int address; short val;int len;if (!int.TryParse(txtMulAdress.Text.Trim(), out address)){txtMulAdress.Focus();MessageBox.Show("寄存器地址错误!");return;}if (!short.TryParse(txtMulShortVal.Text.Trim(), out val)){txtMulShortVal.Focus();MessageBox.Show("写入的值只能为正整数!");return;}if (!int.TryParse(txtMulCount.Text.Trim(), out len)){txtMulCount.Focus();MessageBox.Show("寄存器数量只能为正整数!");return;}if (modbus.WriteMultipleRegisters(slaveStation, address, len, Enumerable.Repeat(val, len).ToArray())){MessageBox.Show("寄存器已批量写入");}else{MessageBox.Show("寄存器批量写入失败");}}}

4.2.4,ModbusTCP部分

 public class ModbusTCP : IDisposable{#region 说明//---------------------------------------------------//前8个字节的规律是一模一样了,都是标识号+modbus号+长度+站号,后面基本是跟地址和长度,或是直接是地址和数据。//---------------------------------------------------//功能码0x01:读线圈//发送的数据--------------------------------------------//根据协议指定,需要填写长度为12的byte数组//byte[0]    byte[1]    byte[2]   byte[3]   byte[4]   byte[5]   byte[6]   byte[7]   byte[8]   byte[9]   byte[10]   byte[11]  //byte[0] byte[1]: 消息号---------随便指定,服务器返回的数据的前两个字和这个一样//byte[2] byte[3]:modbus标识,强制为0即可//byte[4] byte[5]:指示排在byte[5] 后面所有字节的个数,也就是总长度-6//byte[6]:站号,对于TCP协议来说,某些情况不重要,可以随便指定,对于rtu及ascii来说,就需要选择设备的站号信息。//byte[7]:功能码,这里就需要填入我们的真正的想法了//byte[8] byte[9]:起始地址,比如我们想读取地址0的数据,就填 00 00 ,如果我们想读取地址1000的数据,怎么办,填入 03 E8 ,也就是将1000转化十六进制填进去。//byte[10] byte[11]:指定想读取的数据长度,比如我们就想读取地址0的一个数据,这里就写 00 01,如果我们想读取地址0-999共计一个数据的长度,就写 03 E8。和起始地址是一样的。//有了上面的格式之后,如下//00 00 00 00 00 06 FF 01 00 00 00 01//表示:消息号设为0,站号FF,功能码01,地址01,长度01//响应的数据--------------------------------------------//00 00 00 00 00 04 FF 01 01 00 共计10个字节的数据。//byte[0] byte[1] :消息号,发送指令是多少,这里就是多少。//byte[2] byte[3]:必须都为0,代表这是modbus 通信//byte[4] byte[5]:指示byte[5] 后面的所有字节数,这里是00 04,如果后面共有100个,那么这里就是 00 64//byte[6]:站号,发送为FF,那么这里也就是FF//byte[7]:功能码,发送指令为01的功能码,这里也是01,和发送的指令是一致的//byte[8]:指示byte[8] 后面跟随的字节数量,因为跟在byte[8] 后面的就是真实的数据,结果就在byte[8] 后面//byte[9]:真实的数据,一个byte有8位,这里只读取了一个位数据,所有这里的有效值只是byte[9] 的最低位,二进制为 0000 0000 我们看到最低位为0,所以最终我们读取的地址0的线圈为断。// 功能码0x05:写单个线圈//如果指定地址0的线圈为通:00 00 00 00 00 06 FF 05 00 00 FF 00 // 前面的含义都是一致的,不同的是: 05 00 00 FF 00//05 是功能码, 00 00 是指定的地址,如果想地址1000为通,那么就为 03 E8,规定数据 FF 00 线圈通,00 00线圈断,其他任何的值都对结果无效。//响应数据:  00 00 00 00 00 06 FF 05 00 00 FF 00   因执行写入的操作,是不带读取数据,所以服务器会直接复制一遍你的指令并返回。//下面再举例一些方便理解(我们只需要指定地址及是否通断的情况即可)://写入地址100为通: 00 00 00 00 00 06 FF 05 00 64 FF 00   //写入地址1000为断:00 00 00 00 00 06 FF 05 03 E8 00 00// 功能码0x0F:写入多个线圈//  00 00 00 00 00 84 FF 0F 00 00 03 E8 7D ...(后面跟125个byte,都是00) //00 00 :消息标识号,任意数字,服务器回复相同数字。//00 00 :modbus标志号,规定为00 。//00 84 :0x0084转化十进制就是132,也就是说,00 84(不包含00 84)后面跟了132个字节//FF :站号//0F :功能码//00 00:起始地址,此处就是0,如果起始地址为100,那么就写00 64,如果起始地址为1000,那么就写03 E8//03 E8: 我们需要写的数据长度,如果需要写1000个线圈,就是03 E8,如果写999个线圈,那么就是03 E7。//7D : 这个字节代表后面跟随的真实写入的数据的长度,为125个字节。    //响应的数据://00 00 00 00 00 06 FF 0F 00 00 03 E8 // 功能码0x03:读取寄存器的值//该功能码实现寄存器的数据读取,我们需要知道的是,一个寄存器占2个byte,而且是高位在前,地位在后,那么如果寄存器0的数据为1000,那么我们读取到的数据就是03 E8,这是我们最终想要的东西,03功能码和01功能码很接近,就是功能码替换一下,返回的数据解析不一样而已,比如我们需要读取地址0的寄存器数据://00 00 00 00 00 06 FF 03 00 00 00 01             //功能码03,读取寄存器,地址0,长度1 返回如下://00 00 00 00 00 05 FF 03 02 03 E8 //02 03 E8:02代表后面的字节长度是2个字节,03 E8是真实的数据了,代表了寄存器0存储了1000这个数据。#endregionSocket socketObject = null;/// <summary>/// 目标站点号/// </summary>int targetStation = 0;/// <summary>/// 消息接收到标志位/// </summary>bool msgRecFlag = false;/// <summary>/// 接收到有效的数据/// </summary>byte[] RecBytes = null;AutoResetEvent slim = new AutoResetEvent(true);/// <summary>/// 返回的异常信息/// </summary>public event Action<int, string> ErrorMessage;/// <summary>/// 消息号/// </summary>int MsgId = 0;CancellationTokenSource source;public ModbusTCP(){}EndPoint remoteEndPoint;/// <summary>/// 远程终结点/// </summary>public EndPoint RemoteEndPoint{get{remoteEndPoint = socketObject.RemoteEndPoint;return remoteEndPoint;}set{remoteEndPoint = value;}}/// <summary>/// 本地终结点/// </summary>public EndPoint LocalEndPoint{get{return socketObject.LocalEndPoint;}}/// <summary>/// 当前的Socket对象/// </summary>public Socket SocketObject{get{return socketObject;}}bool isConnected = false;public bool IsConnected{get{if (socketObject==null || !socketObject.Connected){isConnected = false;}return isConnected;}}/// <summary>/// 使用RemoteEndPoint属性值建立与远程主机的连接/// </summary>/// <param name="serialPortName"></param>public void Connect(){if (socketObject != null){Disconnect();}socketObject = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);socketObject.Connect(remoteEndPoint);isConnected = socketObject.Connected;source = new CancellationTokenSource();if (IsConnected){DataReceivedTask();}}/// <summary>/// 建立与远程主机的连接/// </summary>/// <param name="address">远程主机IP</param>/// <param name="port">远程主机端口号</param>public void Connect(string address, int port){if (socketObject != null){Disconnect();}socketObject = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);socketObject.Connect(IPAddress.Parse(address), port);isConnected = socketObject.Connected;source = new CancellationTokenSource();if (IsConnected){DataReceivedTask();}}/// <summary>/// 关闭连接/// </summary>/// <param name="reuseSocket">如果关闭当前连接后可以重用此套接字,则为 true;否则为 false。</param>public void Disconnect(){if (socketObject != null && IsConnected){socketObject.Shutdown(SocketShutdown.Both);socketObject.Disconnect(false);socketObject.Close();socketObject.Dispose();socketObject = null;}isConnected = false;source.Cancel();}/// <summary>/// 接收数据任务/// </summary>async void DataReceivedTask(){try{await Task.Run(() =>{while (true){if (source.Token.IsCancellationRequested){source.Token.ThrowIfCancellationRequested();}// int num = socketObject.Available;// if (num == 0) return;byte[] data = new byte[10 * 1024 * 1024];byte[] bytes;try{int len = socketObject.Receive(data);if (len == 0){isConnected = false;return;}bytes = new byte[len];Array.Copy(data, bytes, len);}catch (Exception){isConnected = false;return;}//对消息进行解析//检查报文是否正确msgRecFlag = true;//ModbusTCP无校验位//获取salve Station Nobyte stationNo = bytes[6];targetStation = stationNo;//功能码byte commandCode = bytes[7];if (commandCode > 80){//异常信息ErrorCode errorCode = (ErrorCode)bytes[8];ErrorMessage?.BeginInvoke(stationNo, errorCode.ToString(),null,null);}else{FunctionCode function = (FunctionCode)commandCode;//有效数据占用的字节数                      byte length = bytes[8];switch (function){case FunctionCode.ReadCoils:case FunctionCode.ReadDiscreteInputs://返回字节长度RecBytes = new byte[length];Array.Copy(bytes, 9, RecBytes, 0, length);break;case FunctionCode.ReadHoldingRegisters:case FunctionCode.ReadInputRegisters://读取寄存器数据RecBytes = new byte[length];Array.Copy(bytes, 9, RecBytes, 0, length);break;case FunctionCode.WriteSingleCoil:case FunctionCode.WriteSingleRegister:case FunctionCode.WriteMultipleCoils:case FunctionCode.WriteMultipleRegisters:RecBytes = new byte[4];Array.Copy(bytes, 8, RecBytes, 0, RecBytes.Length);break;case FunctionCode.ReportSlaveID:break;case FunctionCode.MaskWriteRegister:break;case FunctionCode.ReadWriteRegister:break;default:break;}}//接收完成,取消主线程阻塞slim.Set();}});}catch (OperationCanceledException ex){socketObject.Disconnect(true);}catch (Exception ex){ErrorMessage?.Invoke(targetStation, "出现异常,原因:" + ex.Message);}}/// <summary>///发送报文/// </summary>/// <param name="bytes"></param>/// <param name="protocol">报文协议</param>void Send(string sendMsg){//对消息进行判断是否合规if (!Regex.IsMatch(sendMsg, @"^[0-9a-fA-F]+$")){throw new Exception("报文错误,存在非16进制字符");}byte[] bytes = new byte[sendMsg.Length / 2];for (int i = 0; i < sendMsg.Length / 2; i++){bytes[i] = Convert.ToByte(sendMsg.Substring(i * 2, 2), 16);}//ModbusTCP不需要添加校验码//  byte[] checkSum = Checkhelper.CrcModbus(bytes, 0, bytes.Length);//  var checkarr = checkSum.Reverse().ToArray();// byte[] msgBytes = new byte[bytes.Length + checkSum.Length];byte[] msgBytes = new byte[bytes.Length];Array.Copy(bytes, msgBytes, bytes.Length);// Array.Copy(checkarr, 0, msgBytes, bytes.Length, checkarr.Length);socketObject.Send(msgBytes, 0, msgBytes.Length, SocketFlags.None);}/// <summary>/// 编制报文/// </summary>/// <param name="slaveStation">从站站号</param>/// <param name="function">功能代码</param>/// <param name="devAdress">起始地址</param>/// <param name="devLenght">数量</param>/// <param name="values">写入的值</param>///  /// <param name="fromBase">如果写入的数值类型所占用的byte,例如short类型为2,int类型为4,flost类型为4,double类型为8</param>/// <param name="set">强制线圈通断,true:ON ;false:OFF</param>void ComposeMessage(int slaveStation, FunctionCode function, int devAdress, int devLenght = 1, byte[] values = null, int fromBase = 2, bool set = false){lock (this){slim.Reset();StringBuilder sb = new StringBuilder();int afterBytesCount = 0;sb.Append(devAdress.ToString("X4"));switch (function){case FunctionCode.ReadCoils:case FunctionCode.ReadDiscreteInputs:case FunctionCode.ReadHoldingRegisters:case FunctionCode.ReadInputRegisters://功能码0x01:读线圈//发送的数据--------------------------------------------//根据协议指定,需要填写长度为12的byte数组//byte[0]    byte[1]    byte[2]   byte[3]   byte[4]   byte[5]   byte[6]   byte[7]   byte[8]   byte[9]   byte[10]   byte[11]  //byte[0] byte[1]: 消息号---------随便指定,服务器返回的数据的前两个字和这个一样//byte[2] byte[3]:modbus标识,强制为0即可//byte[4] byte[5]:指示排在byte[5] 后面所有字节的个数,也就是总长度-6//byte[6]:站号,对于TCP协议来说,某些情况不重要,可以随便指定,对于rtu及ascii来说,就需要选择设备的站号信息。//byte[7]:功能码,这里就需要填入我们的真正的想法了//byte[8] byte[9]:起始地址,比如我们想读取地址0的数据,就填 00 00 ,如果我们想读取地址1000的数据,怎么办,填入 03 E8 ,也就是将1000转化十六进制填进去。//byte[10] byte[11]:指定想读取的数据长度,比如我们就想读取地址0的一个数据,这里就写 00 01,如果我们想读取地址0-999共计一个数据的长度,就写 03 E8。和起始地址是一样的。//有了上面的格式之后,如下//00 00 00 00 00 06 FF 01 00 00 00 01//表示:消息号设为0,站号FF,功能码01,地址01,长度01afterBytesCount = 6;sb.Append(devLenght.ToString("X4"));break;case FunctionCode.WriteSingleCoil:// 功能码0x05:写单个线圈//如果指定地址0的线圈为通:00 00 00 00 00 06 FF 05 00 00 FF 00 // 前面的含义都是一致的,不同的是: 05 00 00 FF 00//05 是功能码, 00 00 是指定的地址,如果想地址1000为通,那么就为 03 E8,规定数据 FF 00 线圈通,00 00线圈断,其他任何的值都对结果无效。//响应数据:  00 00 00 00 00 06 FF 05 00 00 FF 00   因执行写入的操作,是不带读取数据,所以服务器会直接复制一遍你的指令并返回。//下面再举例一些方便理解(我们只需要指定地址及是否通断的情况即可)://写入地址100为通: 00 00 00 00 00 06 FF 05 00 64 FF 00   //写入地址1000为断:00 00 00 00 00 06 FF 05 03 E8 00 00afterBytesCount = 6;if (set){//线圈置ONsb.Append("FF00");}else{//线圈置OFFsb.Append("0000");}break;case FunctionCode.WriteSingleRegister://向单个寄存器写入值,占用两个byteafterBytesCount = 6;int curVal = BitConverter.ToInt16(values, 0);sb.Append(curVal.ToString("X4"));break;case FunctionCode.WriteMultipleCoils:// 功能码0x0F:写入多个线圈//  00 00 00 00 00 84 FF 0F 00 00 03 E8 7D ...(后面跟125个byte,都是00) //00 00 :消息标识号,任意数字,服务器回复相同数字。//00 00 :modbus标志号,规定为00 。//00 84 :0x0084转化十进制就是132,也就是说,00 84(不包含00 84)后面跟了132个字节//FF :站号//0F :功能码//00 00:起始地址,此处就是0,如果起始地址为100,那么就写00 64,如果起始地址为1000,那么就写03 E8//03 E8: 我们需要写的数据长度,如果需要写1000个线圈,就是03 E8,如果写999个线圈,那么就是03 E7。//7D : 这个字节代表后面跟随的真实写入的数据的长度,为125个字节。   int len = (int)Math.Ceiling(devLenght * 1.0 / 8);afterBytesCount = 7 + len;sb.Append(devLenght.ToString("X4"));sb.Append(len.ToString("X2"));foreach (var item in values){sb.Append(item.ToString("X2"));}break;case FunctionCode.WriteMultipleRegisters://向多个寄存器写入值//1,需操作的寄存器数量,数值占用两个byteafterBytesCount = 7 + values.Length;//2,寄存器数量sb.Append((values.Length*2 / fromBase ).ToString("X4"));//2,需要操作的寄存器占用的字节数,数值占用1个bytesb.Append((values.Length).ToString("X2"));//3,写入寄存器的值,根据frombase判定值//此时姑且以占2个Byte计算for (int i = 0; i < values.Length; i++){sb.Append(values[i].ToString("X2"));}break;case FunctionCode.ReportSlaveID:break;case FunctionCode.MaskWriteRegister:break;case FunctionCode.ReadWriteRegister:break;default:break;}//---------------------------------------------------//前8个字节的规律是一模一样了,都是:消息ID+modbus号+长度+站号,后面基本是跟地址和长度,或是直接是地址和数据。//byte[0] byte[1]: 消息号---------随便指定,服务器返回的数据的前两个字和这个一样//byte[2] byte[3]:modbus标识,强制为0即可//byte[4] byte[5]:指示排在byte[5] 后面所有字节的个数,也就是总长度//byte[6]:站号,对于TCP协议来说,某些情况不重要,可以随便指定,对于rtu及ascii来说,就需要选择设备的站号信息。//byte[7]:功能码,这里就需要填入我们的真正的想法了//---------------------------------------------------//添加从站点,功能代码,起始位置if (MsgId > 1000) MsgId = 0;MsgId++;string msg = $"{MsgId.ToString("X4")}0000{afterBytesCount.ToString("X4")}{ slaveStation.ToString("X2")}{((int)function).ToString("X2")}";msg += sb;RecBytes = null;Send(msg.Trim());//100ms等待消息回复msgRecFlag = slim.WaitOne(3000);System.Threading.Thread.Sleep(20);if (!msgRecFlag){ErrorMessage?.Invoke(slaveStation, $"从站:{slaveStation},回复超时");}}}/// <summary>/// 强制指定单个线圈动作/// </summary>/// <param name="slaveStationNo">从站站号</param>/// <param name="devAdress">线圈地址</param>/// <param name="onOrOff">True为ON,false为Off</param>/// <returns></returns>public bool ForcedCoil(int slaveStationNo, int devAdress, bool set){ComposeMessage(slaveStationNo, FunctionCode.WriteSingleCoil, devAdress, set: set);if (slaveStationNo == targetStation && msgRecFlag && RecBytes != null){if (RecBytes[2] == 0xff && RecBytes[3] == 0 && set){return true;}if (RecBytes[3] == 0 && RecBytes[2] == 0 && !set){return true;}}return false;}/// <summary>/// 读取多个线圈状态/// </summary>/// <param name="slaveStationNo"></param>/// <param name="devAdress"></param>/// <param name="iLenght"></param>public bool[] ReadCoils(int slaveStationNo, int devAdress, int iLenght){ComposeMessage(slaveStationNo, FunctionCode.ReadCoils, devAdress, iLenght);if (slaveStationNo == targetStation && msgRecFlag && RecBytes != null){BitArray array = new BitArray(RecBytes);if (iLenght > array.Count){throw new Exception("线圈查询结果异常!");}bool[] coils = new bool[iLenght];for (int i = 0; i < iLenght; i++){coils[i] = array[i];}return coils;}else{return null;}}/// <summary>/// 将short值写入到多个线圈/// </summary>/// <param name="slaveStationNo">从站站号</param>/// <param name="devAdress"></param>/// <param name="iLenght">地址长度</param>/// <param name="arr">写入的值</param>public bool WriteMultipleCoils(int slaveStationNo, int devAdress, int iLenght, short[] arr){if ((int)Math.Ceiling(iLenght * 1.0 / 16) != arr.Length){throw new Exception("线圈数量与所赋值数量不匹配");}byte[] bytes = new byte[arr.Length * 2];int index = 0;foreach (var item in arr){byte[] temBytes = BitConverter.GetBytes(item);Array.Copy(temBytes, 0, bytes, index, temBytes.Length);index += temBytes.Length;}ComposeMessage(slaveStationNo, FunctionCode.WriteMultipleCoils, devAdress, iLenght, bytes);if (slaveStationNo == targetStation && msgRecFlag && RecBytes != null && RecBytes.Length == 4){int startAddress = BitConverter.ToInt16(new byte[] { RecBytes[1], RecBytes[0] }, 0);int addressLen = BitConverter.ToInt16(new byte[] { RecBytes[3], RecBytes[2] }, 0);if (startAddress == devAdress && addressLen == iLenght){return true;}else{return false;}}else{return false;}}/// <summary>/// 读取从站多个寄存器的值/// </summary>/// <param name="slaveStationNo"></param>/// <param name="devAdress">起始寄存器</param>/// <param name="iLenght">寄存器数量</param>public short[] ReadHoldingRegisters(int slaveStationNo, int devAdress, int iLenght){ComposeMessage(slaveStationNo, FunctionCode.ReadHoldingRegisters, devAdress, iLenght);if (slaveStationNo == targetStation && msgRecFlag && RecBytes != null){List<short> shortList = new List<short>();for (int i = 0; i < RecBytes.Length; i += 2){shortList.Add(BitConverter.ToInt16(new byte[] { RecBytes[i + 1], RecBytes[i] }, 0));}return shortList.ToArray();}else{return null;}}/// <summary>/// 读取单精度浮点数/// </summary>/// <param name="slaveStationNo">从站站点</param>/// <param name="devAdress">起始地址</param>/// <param name="iLenght">连续读取浮点数的个数</param>/// <returns></returns>public float[] ReadFloatRegisters(int slaveStationNo, int devAdress, int iLenght){ComposeMessage(slaveStationNo, FunctionCode.ReadHoldingRegisters, devAdress, iLenght * 2);if (slaveStationNo == targetStation && msgRecFlag && RecBytes != null){List<float> floatList = new List<float>();for (int i = 0; i < RecBytes.Length; i += 4){floatList.Add(BitConverter.ToSingle(new byte[] { RecBytes[i + 1], RecBytes[i], RecBytes[i + 3], RecBytes[i + 2] }, 0));}return floatList.ToArray();}else{return null;}}/// <summary>/// 向从站单个寄存器写入值/// </summary>/// <param name="slaveStationNo"></param>/// <param name="devAdress"></param>/// <param name="value"></param>public bool WriteSingleRegister(int slaveStationNo, int devAdress, short value){ComposeMessage(slaveStationNo, FunctionCode.WriteSingleRegister, devAdress, values: BitConverter.GetBytes(value));if (slaveStationNo == targetStation && msgRecFlag && RecBytes != null && RecBytes.Length == 4){int startAddress = BitConverter.ToInt16(new byte[] { RecBytes[1], RecBytes[0] }, 0);int val = BitConverter.ToInt16(new byte[] { RecBytes[3], RecBytes[2] }, 0);if (startAddress == devAdress && val == value){return true;}else{return false;}}else{return false;}}/// <summary>/// 写入单精度浮点数/// </summary>/// <param name="slaveStationNo">从站站点</param>/// <param name="devAdress">寄存器地址</param>/// <param name="value">值</param>/// <returns>true写入成功,false写入失败</returns>public bool WriteFloatRegister(int slaveStationNo, int devAdress, float value){//进行拆解组合byte[] bytes = BitConverter.GetBytes(value);byte[] valBytes= new byte[] { bytes[1], bytes[0], bytes[ 3], bytes[2] };ComposeMessage(slaveStationNo, FunctionCode.WriteMultipleRegisters, devAdress, values: valBytes, fromBase: 4);if (slaveStationNo == targetStation && msgRecFlag && RecBytes != null){short adress = BitConverter.ToInt16(new byte[] { RecBytes[1], RecBytes[0] }, 0);short registerCount = BitConverter.ToInt16(new byte[] { RecBytes[3], RecBytes[2] }, 0);if (adress == devAdress && registerCount == 2){return true;}else{return false;}}else{return false;}}/// <summary>/// 向从站多个寄存器写入值/// </summary>/// <param name="slaveStationNo"></param>/// <param name="devAdress"></param>/// <param name="iLenght"></param>/// <param name="arr"></param>public bool WriteMultipleRegisters(int slaveStationNo, int devAdress, int iLenght, short[] arr){if (iLenght != arr.Length){throw new Exception("寄存器数量与所赋值数量不匹配");}byte[] bytes = new byte[arr.Length * 2];int index = 0;foreach (var item in arr){byte[] temBytes = BitConverter.GetBytes(item);byte[] temBytes2 = new byte[] { temBytes[1], temBytes[0] };Array.Copy(temBytes2, 0, bytes, index, temBytes2.Length);index += temBytes.Length;}ComposeMessage(slaveStationNo, FunctionCode.WriteMultipleRegisters, devAdress, iLenght, bytes,fromBase:4);if (slaveStationNo == targetStation && msgRecFlag && RecBytes != null && RecBytes.Length == 4){int startAddress = BitConverter.ToInt16(new byte[] { RecBytes[1], RecBytes[0] }, 0);int addressLen = BitConverter.ToInt16(new byte[] { RecBytes[3], RecBytes[2] }, 0);if (startAddress == devAdress && addressLen == iLenght){return true;}else{return false;}}else{return false;}}public void Dispose(){socketObject.Close();socketObject.Dispose();}}

5,基于OPC服务的Modbus通信

5.1,OPC介绍

1,OPC协议OPC是Object Linking and Embedding(OLE)for Process Controls的缩写,是微软公司得对象连接和嵌入技术在过程控制方面的应用。OPC主要基于微软的OLE(现在的Active X),COM(部件对象模型)和DCOM(分布式部件对象模型)技术。2,常见的OPCServer1,KEPWSRE	2,SIMATIC NET 	3,PC ACCESS(PC ACCESS SMART) 4,Matrikon,Knight公司得OPC软件。3,OPC读写方式。3.1,同步方式
客户端发送请求,必须等待服务器响应全部完成后才能返回,期间处于等待状态。
多客户端向服务器操作时,客户端程序产生阻塞。
同步通讯适用于客户端较少,数据量较少的场合。3.2,异步方式。
客户端发送请求后立即返回,不需要等待服务器的响应,可以进行其他操作。
服务器完成响应后再通知客户端。
相对于同步通讯,异步通讯的效率更高。3.3,订阅模式。
客户端发送请求后立即返回,不需要等待服务器的响应,可以进行其他操作。
服务器有数据发生变化时,自动刷新客户端数据。
客户端只向服务端发送一次请求。3.4,OPC存在同步写和异步写两种模式。4,OPC访问接口方式。
OPC主要包含两种接口:Custom标准接口和OLE自动化标准接口。
自定义接口是一组COM接口,主要用于C++语言的应用程序开发。
自动化接口是一组OLE接口,主要用于VB,DELPHI,Excel等基本脚本语言的应用程序开发。OPC基金会提供了OPCRcw动态链接库,OPC NET COM 和 OPC NET API,将复杂的OPC规范封装为简单的C#类,从而可以较为简单的实现数据访问。5,OPCClient开发
使用OPCNetApi2.0,需要使用OPCNetAPI.dll。该dll由OPC Foundation提供,需付费注册会员。
使用自动化接口,需要使用OPCDAAuto.dll.不需付费注册,比较简单,但是不够灵活。
使用自定义接口,需要用到多个.Net Wrapper:OpcRcw.Ae.dll,OpcRcw.Bath.dll,OpcRcw.sec.dll,OpcRcw.Da.dll等

5.2,OPCDAAuto.dll链接

https://download.csdn.net/download/lingxiao16888/89517377

5.3,配置OPC服务软件KEPServerEx V4.0

创建Modbus通信频道

选择设备驱动为modbus serial

通信参数设置

 创建Tag

 点击OPCQuickClient 测试通信,通信OK。

5.4,代码

 public partial class Form1 : Form{OPCHelper opc = new OPCHelper();List<OPCItem> opcList = new List<OPCItem>();public Form1(){InitializeComponent();dataGridView1.AutoGenerateColumns = false;btnRefresh_Click(null, null);opc.AsyncWriteComplete += Opc_AsyncWriteComplete;}private void Opc_AsyncWriteComplete(List<OPCItem> result){dataGridView1.DataSource = null;dataGridView1.DataSource = result;}private void btnRefresh_Click(object sender, EventArgs e){comboNode.Items.Clear();//获取HostNameforeach (var item in Dns.GetHostAddresses(Environment.MachineName)){if (comboNode.Items.Contains(Dns.GetHostEntry(item).HostName))continue;comboNode.Items.Add(Dns.GetHostEntry(item).HostName);}//如果存在则将第一项设置为当前项comboNode.SelectedIndex = comboNode.Items.Count > 0 ? 0 : -1;}private void comboNode_SelectedIndexChanged(object sender, EventArgs e){if (comboNode.SelectedItem != null){//获取所有可用的OPC服务var servers = OPCHelper.GetOPCServers(comboNode.Text);comboServer.DataSource = servers;}}private void comboServer_SelectedIndexChanged(object sender, EventArgs e){}private void btnConnect_Click(object sender, EventArgs e){if (btnConnect.Text.Equals("Connect", StringComparison.CurrentCultureIgnoreCase)){if (comboServer.SelectedItem != null){opcList.Clear();opc.Connected(comboServer.Text.Trim());//获取OPC上所有注册的tagif (opc.IsConnected){var tags = opc.GetOPCServerTags();lsbBrowser.DataSource = tags;btnConnect.Text = "Disconnect";btnConnect.BackColor = Color.LightGreen;}}else{MessageBox.Show("请先选择需要连接OPC服务!");}}else{opc.Disconnect();if (!opc.IsConnected){opcList.Clear();lsbBrowser.DataSource = null;dataGridView1.DataSource = null;btnConnect.Text = "Connect";btnConnect.BackColor = Color.Red;}}}private void btnRead_Click(object sender, EventArgs e){if(dataGridView1.Rows.Count==0){MessageBox.Show("请先添加需要读取的寄存器!");return;}if (opc.IsConnected){List<string> tags = opcList.Select(item => item.Tag).ToList();var list=   opc.ReadMultipleRegisters<float>(tags,ckAsync.Checked);if (list != null){StringBuilder sb = new StringBuilder();foreach (var item in list){sb.AppendFormat("{0} ", item);}MessageBox.Show(sb.ToString());}}else{MessageBox.Show("未连接服务!");return;}}private void lsbBrowser_DoubleClick(object sender, EventArgs e){if (lsbBrowser.SelectedItem != null){string tag = lsbBrowser.SelectedItem.ToString();if (!opcList.Any(item => item.Tag.Equals(tag))){opcList.Add(new OPCItem { Tag = tag });}dataGridView1.DataSource = null;dataGridView1.DataSource = opcList;}}private void dataGridView1_CellDoubleClick(object sender, DataGridViewCellEventArgs e){if (dataGridView1.CurrentRow != null){//获取当前值if (opc.IsConnected){DataGridViewRow row = dataGridView1.CurrentRow;string tag = row.Cells["Tag"].Value.ToString();if (row.Cells["Value"].Value == null){btnRead_Click(null, null);}string val = row.Cells["Value"].Value?.ToString();WriteFrm frm = new WriteFrm(tag, val);frm.StartPosition = FormStartPosition.CenterScreen;frm.ModifyCompleted += Frm_ModifyCompleted;frm.ShowDialog();}}}private void Frm_ModifyCompleted(string tag, float val, bool async){if (opc.IsConnected){opc.WriteSingleValue<float>(tag, val, async);btnRead_Click(null, null);}}}

  //定义一个委托public delegate void AsyncReadCompleteEventHandler(List<OPCItem> result);/// <summary>/// 基于OPC协议(对象连接与嵌入流程控制)的读写类/// </summary>public class OPCHelper{//--------------使用指南-----------------------//第1步,使用静态方法获取本机或者局域网的hostName或者IpAdress所拥有的OPS服务//  public static string[] GetOPCServers(string hostNameorAdress)//第2步,使用实例化方法连接OPS服务//   public bool Connected(string serverName)//第3步,在连接的基础上使用实例化方法获取对应OPC服务器上所声明定义的所有可用寄存器//   public string[] GetOPCServerTags()//第4步,在连接的基础上使用实例化方法对寄存器进行读写//   public void WriteMultipleRegisters(List<string> tags, List<object> values, bool async = true)//   public List<T> ReadMultipleRegisters<T>(List<string> tags, bool async = true)//第5步,如果使用的是异步读取,请在异步读取完成事件中接收值。//   public event AsyncReadCompleteEventHandler AsyncWriteComplete;//***********************************************************************************************//***********************************************************************************************//--------------特别注意事项----------------------//第1点://OPC中数组Array有效的下标是从1开始,至于下标为0的值需要填充同类型的任意值,int类型一般采用0,string类型一般为"0"//同样,下标1开始的才是有效的值才可以进行取值操作。//如果按照C#中数组从下表0开始赋值取值操作,将抛出异常。////第2点//进行异步或者同步写入时//OPC.SyncWrite(int NumItems, ref Array ServerHandles, ref Array Values, out Array Errors);//参数Values必须是Object类型//----------------------------------------------//定义OPC相关对象OPCServer kepServer = new OPCServer();OPCGroups kepGroups;OPCGroup kepGroup;OPCBrowser kepBrowser;Dictionary<int, string> currentItemId = new Dictionary<int, string>();//定义OPC标签错误Array iErrors;bool isConnected;/// <summary>/// 服务句柄/// </summary>Array ServerHandles;int cancelId;/// <summary>/// 异步读取完成事件/// </summary>public event AsyncReadCompleteEventHandler AsyncWriteComplete;/// <summary>/// OPC是否连接/// </summary>public bool IsConnected{get{if (kepGroup == null){isConnected = false;}return isConnected;}}/// <summary>/// 当前OPCOPCGroup服务,可用于寄存器的读取与写入/// </summary>public OPCGroup CurrentOPCGroup{get{return kepGroup;}}/// <summary>/// 根据hostName或者Adress获取可用的OPC服务/// </summary>/// <param name="hostNameorAdress"></param>/// <returns>返回可用的服务列表</returns>public static List<string> GetOPCServers(string hostNameorAdress){//获取hostNamestring hostName = Dns.GetHostEntry(hostNameorAdress).HostName;//注意:这里须使用Object接收值,若使用Var,后续进行遍历时将报异常OPCServer opcServer = new OPCServer();object result = opcServer.GetOPCServers(hostName);List<string> list = new List<string>();foreach (var item in (Array)result){list.Add(item.ToString());}return list;}/// <summary>/// 连接OPC服务/// </summary>/// <param name="serverName">OPC服务名</param>/// <returns></returns>public bool Connected(string serverName){try{kepServer.Connect(serverName);isConnected = true;kepServer.OPCGroups.RemoveAll();kepGroups = kepServer.OPCGroups;kepGroups.DefaultGroupDeadband = 0;kepGroup = kepGroups.Add("Test");kepGroup.IsActive = true;kepGroup.IsSubscribed = true;kepGroup.UpdateRate = 250;//监视数据变化事件获取值// kepGroup.DataChange += KepGroup_DataChange;kepGroup.AsyncReadComplete += KepGroup_AsyncReadComplete;kepGroup.AsyncWriteComplete += KepGroup_AsyncWriteComplete;return true;}catch (Exception){return false;}}private void KepGroup_AsyncWriteComplete(int TransactionID, int NumItems, ref Array ClientHandles, ref Array Errors){// throw new NotImplementedException();}private void KepGroup_AsyncReadComplete(int TransactionID, int NumItems, ref Array ClientHandles, ref Array ItemValues, ref Array Qualities, ref Array TimeStamps, ref Array Errors){List<OPCItem> opcItemList = new List<OPCItem>();//获取值for (int i = 0; i < NumItems; i++){//kepOPC有效所有从1开始int clientId = Convert.ToInt32(ClientHandles.GetValue(i + 1));string tag = null;if (currentItemId.Keys.Contains(clientId)){tag = currentItemId[clientId];}object val = ItemValues.GetValue(i + 1);//TimeStamps:是标准时间即格林时间是0时区计时,所以这里需要+8DateTime dt = Convert.ToDateTime(TimeStamps.GetValue(i + 1)).AddHours(8);opcItemList.Add(new OPCItem { Tag = tag, Value = val.ToString(), Time = dt });}AsyncWriteComplete?.Invoke(opcItemList);}/// <summary>/// 获取OPC服务中所有已定义可用的Tag即定义的寄存器名/// </summary>/// <returns></returns>public string[] GetOPCServerTags(){if (!IsConnected){throw new Exception("OPC服务未连接!");}kepBrowser = kepServer.CreateBrowser();kepBrowser.ShowBranches();kepBrowser.ShowLeafs(true);List<string> list = new List<string>();foreach (var item in kepBrowser){list.Add(item.ToString());}return list.ToArray();}/// <summary>/// 向OPC的多个寄存器写入多个值/// </summary>/// <param name="tags">OPC服务上定义的寄存器名</param>/// <param name="values">写入的值的集合</param>/// <param name="async">是否异步写入</param>public void WriteMultipleRegisters(List<string> tags, List<object> values, bool async = true){if (!IsConnected){throw new Exception("OPC未连接,写入失败!");}if (kepGroup.OPCItems.Count > 0){//移除项目kepGroup.OPCItems.Remove(kepGroup.OPCItems.Count, ref ServerHandles, out iErrors);}var itemIds = new List<string>(tags);//OPC Array中有效值是从下标1开始,所以需对下标0填充任意值,这里填充"0"itemIds.Insert(0, "0");Array ItemIds = itemIds.ToArray();List<int> clientList = new List<int>();//OPC Array中有效值是从下标1开始,所以需对下标0填充任意值,这里填充 0clientList.Add(0);for (int i = 0; i < tags.Count; i++){clientList.Add(i + 20);}Array ClientHandles = clientList.ToArray();kepGroup.OPCItems.AddItems(tags.Count, ref ItemIds, ref ClientHandles, out ServerHandles, out iErrors);//------------------特别注意----------------//写入的值的类型只能是Object类型,如果是其他类型将抛出异常List<object> writeValues = new List<object>(values);writeValues.Insert(0,default(object));Array valueArray = writeValues.ToArray();if (async){kepGroup.AsyncWrite(1, ref ServerHandles, valueArray, out iErrors, 0, out cancelId);}else{kepGroup.SyncWrite(1, ref ServerHandles, ref valueArray, out iErrors);}}/// <summary>/// 从OPC中读取多个寄存器的值,当选择异步模式时返回null,真实的值通过AsyncReadComplete事件获取/// </summary>/// <param name="tags">OPC服务上定义的寄存器名</param>/// <param name="async">是否异步读取</param>/// <returns>异步读取时返回null</returns>public List<T> ReadMultipleRegisters<T>(List<string> tags, bool async = true){if (!IsConnected){throw new Exception("OPC未连接,读取失败!");}if (kepGroup.OPCItems.Count > 0){//移除项目kepGroup.OPCItems.Remove(kepGroup.OPCItems.Count, ref ServerHandles, out iErrors);}var itemIds = new List<string>(tags);//OPC Array中有效值是从下标1开始,所以需对下标0填充任意值,这里填充"0"itemIds.Insert(0, "0");Array ItemIds = itemIds.ToArray();List<int> clientList = new List<int>();//OPC Array中有效值是从下标1开始,所以需对下标0填充任意值,这里填充 0clientList.Add(0);for (int i = 0; i < tags.Count; i++){clientList.Add(i + 20);}Array ClientHandles = clientList.ToArray();kepGroup.OPCItems.AddItems(tags.Count, ref ItemIds, ref ClientHandles, out ServerHandles, out iErrors);//获取服务句柄,并写入到字典中currentItemId.Clear();for (int i = 0; i < ItemIds.Length - 1; i++){string tag = ItemIds.GetValue(i + 1).ToString();int id = Convert.ToInt32(ClientHandles.GetValue(i + 1));currentItemId.Add(id, tag);}if (async){kepGroup.AsyncRead(ItemIds.Length - 1, ref ServerHandles, out iErrors, 1, out cancelId);return null;}else{Array result;object qualities;object tiemstamps;kepGroup.SyncRead(2, ItemIds.Length - 1, ref ServerHandles, out result, out iErrors, out qualities, out tiemstamps);List<T> list = new List<T>();for (int i = 0; i < result.Length; i++){list.Add((T)result.GetValue(i + 1));}return list;}}/// <summary>/// 写入单个值/// </summary>/// <typeparam name="T"></typeparam>/// <param name="tag">寄存器在OPC上的tag名</param>/// <param name="value">写入的值</param>/// <param name="async">是否异步</param>public void WriteSingleValue<T>(string tag, T value, bool async = true){WriteMultipleRegisters(new List<string> { tag }, new List<object> { value }, async);}/// <summary>/// 读取单个值,当选择异步模式时返回null,真实的值通过AsyncReadComplete事件获取/// </summary>/// <typeparam name="T"></typeparam>/// <param name="tag">寄存器在OPC上的tag名</param>/// <param name="async">是否异步,异步时返回null,具体的值通过AsyncReadComplete事件获取</param>public List<T> ReadSingleValue<T>(string tag, bool async = true){return ReadMultipleRegisters<T>(new List<string> { tag }, async);}/// <summary>/// 断开OPC连接/// </summary>/// <returns></returns>public bool Disconnect(){try{if (kepGroup == null){isConnected = false;return false;}kepServer.Disconnect();isConnected = false;return true;}catch (Exception){return false;}}}public class OPCItem{public string Tag { get; set; }public string Value { get; set; }public DateTime Time { get; set; }}

6,Demo链接。

https://download.csdn.net/download/lingxiao16888/89517465

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/41611.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2024 年第十四届亚太数学建模竞赛(中文赛项)浅析

需要完整B题资料&#xff0c;请关注&#xff1a;“小何数模”&#xff01; 本次亚太(中文赛)数学建模的赛题已正式出炉&#xff0c;无论是赛题难度还是认可度&#xff0c;该比赛都是仅次于数模国赛的独一档&#xff0c;可以用于国赛前的练手训练。考虑到大家解题实属不易&…

纸飞机社工库

收集了一些比较好用的纸飞机社工库&#xff0c;有纸飞机的可以加一下 Space X 隐私信息查询平台https://t.me/SpaceSGK_bot?startKhbOsEdELmingeek社工库 https://t.me/ingeeksgkbot?startNzM3ODE5NDM5Nw Botnet免费社工机器人https://t.me/SGK_0001_bot?start7378194397暗…

TZDYM001矩阵系统源码 矩阵营销系统多平台多账号一站式管理

外面稀有的TZDYM001矩阵系统源码&#xff0c;矩阵营销系统多平台多账号一站式管理&#xff0c;一键发布作品。智能标题&#xff0c;关键词优化&#xff0c;排名查询&#xff0c;混剪生成原创视频&#xff0c;账号分组&#xff0c;意向客户自动采集&#xff0c;智能回复&#xf…

【C++ | 继承】C++的继承详解 及 例子源码演示

&#x1f601;博客主页&#x1f601;&#xff1a;&#x1f680;https://blog.csdn.net/wkd_007&#x1f680; &#x1f911;博客内容&#x1f911;&#xff1a;&#x1f36d;嵌入式开发、Linux、C语言、C、数据结构、音视频&#x1f36d; ⏰发布时间⏰&#xff1a; 本文未经允许…

信用卡没逾期就万事大吉了吗?

6月28日&#xff0c;中国人民银行揭晓了《2024年第一季度支付体系概览》&#xff0c;数据显示&#xff0c;截至本季度末&#xff0c;信用卡及借贷合一卡的总量为7.6亿张&#xff0c;与上一季度相比&#xff0c;这一数字微降了0.85个百分点。同时&#xff0c;报告还指出&#xf…

AE的合成

目录 合成的概念 合成设置 预设 像素长宽比 分辨率​编辑 开始时间码和持续时间 背景颜色 合成的实战理解 在AE的操作界面中&#xff0c;当我们新建了一个项目之后&#xff0c;画面中最主要的位置显示的是新建合成 合成的概念 AE是一款专业特效合成软件&#xff0c;可…

【吊打面试官系列-MyBatis面试题】MyBatis 实现一对一有几种方式?具体怎么操作的?

大家好&#xff0c;我是锋哥。今天分享关于 【MyBatis 实现一对一有几种方式?具体怎么操作的&#xff1f;】面试题&#xff0c;希望对大家有帮助&#xff1b; MyBatis 实现一对一有几种方式?具体怎么操作的&#xff1f; 有联合查询和嵌套查询,联合查询是几个表联合查询,只查询…

【记录】如何使用IDEA2023

前言&#xff1a; 记录IDEA2023的激活与安装 第一步&#xff1a;官网下载安装包&#xff1a; 下载地址&#xff1a;https://www.jetbrains.com/idea/download/other.html 这个最好选择2023版本&#xff0c;用着很nice。 安装步骤就不详解了&#xff0c;无脑下一步就可以了…

【并发编程】-3.锁的类型、CAS、UNSAFE、原子操作

锁的类型 可重入锁和不可重入锁 可重入锁&#xff1a;一个线程可以多次抢占同一个锁&#xff1b;Synchronized、ReentrantLock都是可重入锁&#xff0c;用Synchronized进行锁的可重入测试&#xff0c;在同一个线程中定义childMethod()和childMethod2()两个方法&#xff0c;在这…

使用Github Actions自建Docker镜像仓库

使用Github Actions自建Docker镜像仓库 背景使用Github Actions自建Docker镜像仓库fork项目[docker_image_sync](https://github.com/xqxyxchy/docker_image_sync)获取云厂商容器镜像服务信息配置github secrets运行github action配置需要同步的镜像同步后效果华为云配置 背景 …

VCS+Vivado联合仿真BUG

场景&#xff1a; 在vcsvivado联合仿真过程中&#xff0c;对vivado导出的shell脚本修改&#xff0c;修改某些source文件路径&#xff0c;vcs编译时会报Permission Denied。 问题描述 对shell脚本修改如下&#xff1a; 修改仅为注释掉某一行&#xff0c;下面变为source文件新…

昇思25天学习打卡营第07天|函数式自动微分

神经网络的训练主要使用反向传播算法&#xff0c;模型预测值&#xff08;logits&#xff09;与正确标签&#xff08;label&#xff09;送入损失函数&#xff08;loss function&#xff09;获得loss&#xff0c;然后进行反向传播计算&#xff0c;求得梯度&#xff08;gradients&…

hid-ft260驱动学习笔记 1 - 驱动模块注册与注销

目录 1. ft260_driver_init初始化 1.1 tty设备 1.1.1 申请tty驱动设备 1.1.2 初始化tty驱动程序 1.1.3 注册tty设备 1.2 hid设备 2. ft260_driver_exit注销模块 3. 调试 hid-ft260.c的最底部可以看到该驱动的注册与注销接口的申明。 module_init(ft260_driver_init); …

eclipse ide中文件编码的修改,解决中文乱码的问题。

1、先上一张图&#xff1a; 记得之前设置过&#xff0c;但是稍微一变&#xff0c;环境编码又到了ISO-8859-1了&#xff0c;然后就出现了乱码。 2、设置eclipse的编码&#xff1a; Preferences--General -- Content Types -- Text -- Java Properties File -- Default encoding…

使用myCobot280和OAK-D OpenCV DepthAI摄像头制作一个实时脸部跟踪的手机支架!

引言 由于YouTube和Netflix的出现&#xff0c;我们开始躺着看手机。然而&#xff0c;长时间用手拿着手机会让人感到疲劳。这次我们制作了一个可以在你眼前保持适当距离并调整位置的自动移动手机支架&#xff0c;让你无需用手拿着手机。请务必试试&#xff01; 准备工作 这次我们…

Vue3从入门到精通(三)

vue3插槽Slots 在 Vue3 中&#xff0c;插槽&#xff08;Slots&#xff09;的使用方式与 Vue2 中基本相同&#xff0c;但有一些细微的差异。以下是在 Vue3 中使用插槽的示例&#xff1a; // ChildComponent.vue <template><div><h2>Child Component</h2&…

昇思25天学习打卡营第08天|模型训练

模型训练 模型训练一般分为四个步骤&#xff1a; 构建数据集。定义神经网络模型。定义超参、损失函数及优化器。输入数据集进行训练与评估。 现在我们有了数据集和模型后&#xff0c;可以进行模型的训练与评估。 ps&#xff1a;这里的训练和Stable Diffusion中的训练是一样…

进程的概念

一.进程和程序的理解 首先抛出结论&#xff1a;进程是动态的&#xff0c;暂时存在于内存中&#xff0c;进程是程序的一次执行&#xff0c;而进程总是对应至少一个特定的程序。 程序是静态的&#xff0c;永久的存在于磁盘中。 程序是什么呢&#xff1f;程序其实就是存放在我们…

图像分类-数据驱动方法

K近邻算法&#xff08;K-Nearest Neighbors&#xff0c;简称KNN&#xff09; KNN算法通过比较新样本与训练集中的样本的距离&#xff0c;然后根据最近的K个邻居的投票结果来决定新样本的分类。 如图所示&#xff0c;K越大的边界会更加平滑&#xff0c;本质上是根据某一样本最近…

红薯小眼睛接口分析与Python脚本实现

文章目录 1. 写在前面2. 接口分析3. 算法脚本实现 【&#x1f3e0;作者主页】&#xff1a;吴秋霖 【&#x1f4bc;作者介绍】&#xff1a;擅长爬虫与JS加密逆向分析&#xff01;Python领域优质创作者、CSDN博客专家、阿里云博客专家、华为云享专家。一路走来长期坚守并致力于Py…