当前位置:   article > 正文

C#Modbus通信_modbustcp

modbustcp

目录

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

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

2,初识Modbus。

  1. 1,Modbus协议是一种应用层报文传输协议,包含RTU,ASCII,TCP(分配的端口号为:502)三种报文类型。
  2. 2,标准的Modbus协议物理层接口有RS232,RS485,RS422,以太网口。采用主从(Master/Slave)方式通信。
  3. 3,Modbus主从站协议原理
  4. Modubus串行链路协议是一种主从协议。在同一时刻,只有一个主站,一个从站或者多个从站(从站最大编号247)连接在同一串行总线。
  5. Modbus通信由主站发起,从站在没有收到主站的请求时是不会发送数据。
  6. 从站之间互不通信。
  7. 主站在同一时刻只能发起一个Modbus事务请求。
  8. 主站有两种模式对从站发起请求:广播,单播。
  9. 4,Modbus帧结构
  10. Modbus串行链路协议是一种主从协议。
  11. 网络上的每个从站必须有唯一的地址(从1到247)
  12. 从站地址用于寻址从站设备,由主站发起。
  13. 地址0用于广播模式,不需要响应。
  14. 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代码部分
  1. public partial class Form1 : Form
  2. {
  3. ModbusSerial modbus;
  4. int slaveStation = 10;
  5. Timer timer = new Timer();
  6. public Form1()
  7. {
  8. InitializeComponent();
  9. UIControlStatusChange(false);
  10. timer.Interval = 1000;
  11. foreach (Control item in groupBox1.Controls)
  12. {
  13. if (item is GroupBox)
  14. {
  15. foreach (Control c in item.Controls)
  16. {
  17. if (c is Button)
  18. {
  19. (c as Button).Click += Button_Click;
  20. }
  21. }
  22. }
  23. }
  24. InitDataLoad();
  25. }
  26. void InitDataLoad()
  27. {
  28. //获取串口号
  29. comboPortName.DataSource = SerialPort.GetPortNames();
  30. //波特率
  31. comboBaudRate.DataSource = new int[] { 4800, 9600, 19200 };
  32. comboBaudRate.SelectedIndex = 1;
  33. //数据位
  34. comboDataBits.DataSource = new int[] { 5, 6, 7, 8 };
  35. comboDataBits.SelectedIndex = 3;
  36. //校验位
  37. comboParity.DataSource = Enum.GetNames(typeof(Parity));
  38. //停止位----不支持None所以应该None去除
  39. comboStopBits.DataSource = Enum.GetNames(typeof(StopBits)).Where(item => !item.Equals("None", StringComparison.CurrentCultureIgnoreCase)).ToArray();
  40. modbus = new ModbusSerial();
  41. modbus.ErrorMessage += Modbus_ErrorMessage;
  42. }
  43. private void Button_Click(object sender, EventArgs e)
  44. {
  45. lblErrorMsg.Text = "";
  46. }
  47. private void Modbus_ErrorMessage(int arg1, string arg2)
  48. {
  49. this.Invoke(new Action(() =>
  50. {
  51. lblErrorMsg.Text = $"从站[{arg1}]出现异常,原因:{arg2}";
  52. }));
  53. }
  54. /// <summary>
  55. /// 变更UI控件状态
  56. /// </summary>
  57. /// <param name="status">True:已连接,False:连接断开</param>
  58. void UIControlStatusChange(bool status)
  59. {
  60. if (status)
  61. {
  62. gbParam.Enabled = false;
  63. btnConnect.BackColor = Color.LightGreen;
  64. btnConnect.Text = "断开连接";
  65. groupBox1.Enabled = true;
  66. }
  67. else
  68. {
  69. gbParam.Enabled = true;
  70. btnConnect.BackColor = Color.Red;
  71. btnConnect.Text = "连接";
  72. groupBox1.Enabled = false;
  73. }
  74. }
  75. private void btnConnect_Click(object sender, EventArgs e)
  76. {
  77. if (btnConnect.Text.Equals("连接"))
  78. {
  79. if (modbus.IsOpen)
  80. {
  81. modbus.Close();
  82. }
  83. //更新端口的设置
  84. if (comboPortName.DataSource == null || comboPortName.Items.Count==0)
  85. {
  86. MessageBox.Show("没有可用的串口!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
  87. return;
  88. }
  89. string portName = comboPortName.Text;
  90. int baudRate = Convert.ToInt32(comboBaudRate.Text);
  91. int dataBits = Convert.ToInt32(comboDataBits.Text);
  92. Parity parity = (Parity)Enum.Parse(typeof(Parity), comboParity.Text);
  93. StopBits stopBits = (StopBits)Enum.Parse(typeof(StopBits), comboStopBits.Text);
  94. modbus.BaudRate = baudRate;
  95. modbus.DataBits = dataBits;
  96. modbus.Parity = parity;
  97. modbus.StopBits = stopBits;
  98. modbus.Open(portName);
  99. }
  100. else
  101. {
  102. if (modbus.IsOpen)
  103. {
  104. modbus.Close();
  105. }
  106. }
  107. UIControlStatusChange(modbus.IsOpen);
  108. }
  109. //读取多个线圈
  110. private void btnReadCoils_Click(object sender, EventArgs e)
  111. {
  112. int address; int len;
  113. if (!int.TryParse(txtCoilsAddress.Text.Trim(), out address))
  114. {
  115. txtCoilsAddress.Focus();
  116. MessageBox.Show("线圈地址错误!");
  117. return;
  118. }
  119. if (!int.TryParse(txtCoilsCount.Text.Trim(), out len))
  120. {
  121. txtCoilsCount.Focus();
  122. MessageBox.Show("线圈数量只能为正整数!");
  123. return;
  124. }
  125. bool[] reuslt = modbus.ReadCoils(slaveStation, address, len);
  126. if (reuslt == null) return;
  127. StringBuilder sb = new StringBuilder();
  128. foreach (var item in reuslt)
  129. {
  130. if (item)
  131. {
  132. sb.Append(" 1");
  133. }
  134. else
  135. {
  136. sb.Append(" 0");
  137. }
  138. }
  139. lblCoilsResult.Text = sb.ToString();
  140. }
  141. private void txtSlave_TextChanged(object sender, EventArgs e)
  142. {
  143. if (!int.TryParse(txtSlave.Text.Trim(), out slaveStation))
  144. {
  145. slaveStation = 10;
  146. }
  147. }
  148. private void btnForced_Click(object sender, EventArgs e)
  149. {
  150. int address;
  151. if (!int.TryParse(txtCoilsWriteAdress.Text.Trim(), out address))
  152. {
  153. txtCoilsWriteAdress.Focus();
  154. MessageBox.Show("线圈写入地址错误!");
  155. return;
  156. }
  157. if (btnForced.Text.Equals("ON"))
  158. {
  159. bool result = modbus.ForcedCoil(slaveStation, address, false);
  160. if (result)
  161. {
  162. btnForced.Text = "OFF";
  163. btnForced.BackColor = Color.Red;
  164. }
  165. }
  166. else
  167. {
  168. bool result = modbus.ForcedCoil(slaveStation, address, true);
  169. if (result)
  170. {
  171. btnForced.Text = "ON";
  172. btnForced.BackColor = Color.LightGreen;
  173. }
  174. }
  175. }
  176. private void btnRegistersRead_Click(object sender, EventArgs e)
  177. {
  178. int address; int len;
  179. if (!int.TryParse(txtRegistersAdress.Text.Trim(), out address))
  180. {
  181. txtRegistersAdress.Focus();
  182. MessageBox.Show("寄存器地址错误!");
  183. return;
  184. }
  185. if (!int.TryParse(txtRegistersCount.Text.Trim(), out len))
  186. {
  187. txtRegistersCount.Focus();
  188. MessageBox.Show("寄存器数量只能为正整数!");
  189. return;
  190. }
  191. short[] result = modbus.ReadHoldingRegisters(slaveStation, address, len);
  192. if (result == null) return;
  193. StringBuilder sb = new StringBuilder();
  194. foreach (var item in result)
  195. {
  196. sb.Append(" " + item.ToString());
  197. }
  198. lblRegistersResult.Text = sb.ToString();
  199. }
  200. private void btnReadFloat_Click(object sender, EventArgs e)
  201. {
  202. int address; int len;
  203. if (!int.TryParse(txtRegistersAdress.Text.Trim(), out address))
  204. {
  205. txtRegistersAdress.Focus();
  206. MessageBox.Show("寄存器地址错误!");
  207. return;
  208. }
  209. if (!int.TryParse(txtRegistersCount.Text.Trim(), out len))
  210. {
  211. txtRegistersCount.Focus();
  212. MessageBox.Show("寄存器数量只能为正整数!");
  213. return;
  214. }
  215. float[] result = modbus.ReadFloatRegisters(slaveStation, address, len);
  216. if (result == null) return;
  217. StringBuilder sb = new StringBuilder();
  218. foreach (var item in result)
  219. {
  220. sb.Append(" " + item.ToString());
  221. }
  222. lblRegistersResult.Text = sb.ToString();
  223. }
  224. private void btnWriteShort_Click(object sender, EventArgs e)
  225. {
  226. int address; short val;
  227. if (!int.TryParse(txtWriteShortAdress.Text.Trim(), out address))
  228. {
  229. txtWriteShortAdress.Focus();
  230. MessageBox.Show("寄存器地址错误!");
  231. return;
  232. }
  233. if (!short.TryParse(txtWriteShortVal.Text.Trim(), out val))
  234. {
  235. txtWriteShortVal.Focus();
  236. MessageBox.Show("写入的值只能为正整数!");
  237. return;
  238. }
  239. if (modbus.WriteSingleRegister(slaveStation, address, val))
  240. {
  241. MessageBox.Show("Short数已成功写入");
  242. }
  243. else
  244. {
  245. MessageBox.Show("Short数写入失败");
  246. }
  247. }
  248. private void btnWriteFloat_Click(object sender, EventArgs e)
  249. {
  250. int address; float val;
  251. if (!int.TryParse(txtFloatWriteAddress.Text.Trim(), out address))
  252. {
  253. txtFloatWriteAddress.Focus();
  254. MessageBox.Show("寄存器地址错误!");
  255. return;
  256. }
  257. if (!float.TryParse(txtWriteFloatVal.Text.Trim(), out val))
  258. {
  259. txtWriteFloatVal.Focus();
  260. MessageBox.Show("写入的值只能为正整数!");
  261. return;
  262. }
  263. if (modbus.WriteFloatRegister(slaveStation, address, val))
  264. {
  265. MessageBox.Show("Float数已成功写入");
  266. }
  267. else
  268. {
  269. MessageBox.Show("Float数写入失败");
  270. }
  271. }
  272. //给线圈批量赋值
  273. private void btnBatch_Click(object sender, EventArgs e)
  274. {
  275. int address; short val;
  276. if (!int.TryParse(txtCoils.Text.Trim(), out address))
  277. {
  278. txtCoils.Focus();
  279. MessageBox.Show("线圈地址错误!");
  280. return;
  281. }
  282. if (!short.TryParse(txtCoilsVal.Text.Trim(), out val))
  283. {
  284. txtCoilsVal.Focus();
  285. MessageBox.Show("写入的值只能为正整数!");
  286. return;
  287. }
  288. bool result = modbus.WriteMultipleCoils(slaveStation, address, 16, new short[] { val });
  289. if (result)
  290. {
  291. MessageBox.Show("线圈已批量写入");
  292. }
  293. else
  294. {
  295. MessageBox.Show("线圈批量失败");
  296. }
  297. }
  298. private void btnMulShortWrite_Click(object sender, EventArgs e)
  299. {
  300. int address; short val; int len;
  301. if (!int.TryParse(txtMulAdress.Text.Trim(), out address))
  302. {
  303. txtMulAdress.Focus();
  304. MessageBox.Show("寄存器地址错误!");
  305. return;
  306. }
  307. if (!short.TryParse(txtMulShortVal.Text.Trim(), out val))
  308. {
  309. txtMulShortVal.Focus();
  310. MessageBox.Show("写入的值只能为正整数!");
  311. return;
  312. }
  313. if (!int.TryParse(txtMulCount.Text.Trim(), out len))
  314. {
  315. txtMulCount.Focus();
  316. MessageBox.Show("寄存器数量只能为正整数!");
  317. return;
  318. }
  319. if (modbus.WriteMultipleRegisters(slaveStation, address, len, Enumerable.Repeat(val, len).ToArray()))
  320. {
  321. MessageBox.Show("寄存器已批量写入");
  322. }
  323. else
  324. {
  325. MessageBox.Show("寄存器批量写入失败");
  326. }
  327. }
  328. private void comboPortName_DropDown(object sender, EventArgs e)
  329. {
  330. comboPortName.DataSource = SerialPort.GetPortNames();
  331. }
  332. }
3.6.4,Modbus类
  1. public class ModbusSerial : IDisposable
  2. {
  3. SerialPort serialPortObject = null;
  4. /// <summary>
  5. /// 目标站点号
  6. /// </summary>
  7. int targetStation = 0;
  8. /// <summary>
  9. /// 消息接收到标志位
  10. /// </summary>
  11. bool msgRecFlag = false;
  12. /// <summary>
  13. /// 接收到有效的数据
  14. /// </summary>
  15. byte[] RecBytes = null;
  16. AutoResetEvent slim = new AutoResetEvent(true);
  17. /// <summary>
  18. /// 返回的异常信息
  19. /// </summary>
  20. public event Action<int, string> ErrorMessage;
  21. /// <summary>
  22. /// 构建基于SerialPort的Modbus
  23. /// </summary>
  24. /// <param name="baudRate">波特率</param>
  25. /// <param name="dataBits">数据位</param>
  26. /// <param name="parity">校验位</param>
  27. /// <param name="stopBits">停止位</param>
  28. public ModbusSerial(int baudRate, int dataBits, Parity parity, StopBits stopBits)
  29. {
  30. serialPortObject = new SerialPort();
  31. serialPortObject.BaudRate = baudRate;
  32. serialPortObject.DataBits = dataBits;
  33. serialPortObject.Parity = parity;
  34. serialPortObject.StopBits = stopBits;
  35. serialPortObject.DataReceived += SerialPortObject_DataReceived;
  36. }
  37. /// <summary>
  38. /// 该构造方法需通过属性设置串口参数
  39. /// </summary>
  40. public ModbusSerial()
  41. {
  42. serialPortObject = new SerialPort();
  43. serialPortObject.DataReceived += SerialPortObject_DataReceived;
  44. }
  45. ModbusSerial(SerialPort port)
  46. {
  47. serialPortObject = port;
  48. serialPortObject.DataReceived += SerialPortObject_DataReceived;
  49. }
  50. static ModbusSerial modbus;
  51. /// <summary>
  52. /// 获取Modbus
  53. /// </summary>
  54. /// <param name="port"></param>
  55. /// <returns></returns>
  56. public static ModbusSerial CreateModbus(SerialPort port)
  57. {
  58. if (modbus == null)
  59. {
  60. modbus = new ModbusSerial(port);
  61. }
  62. return modbus;
  63. }
  64. private void SerialPortObject_DataReceived(object sender, SerialDataReceivedEventArgs e)
  65. {
  66. int num = serialPortObject.BytesToRead;
  67. if (num == 0) return;
  68. byte[] bytes = new byte[num];
  69. //需要通过事件监听不可采用循环阻塞接收,如果采用循环阻塞将抛出异常
  70. serialPortObject.Read(bytes, 0, num);
  71. //对消息进行解析
  72. //检查报文是否正确
  73. msgRecFlag = true;
  74. byte[] checkSum = Checkhelper.CrcModbus(bytes, 0, bytes.Length - 2);
  75. if (checkSum[0] == bytes[bytes.Length - 1] && checkSum[1] == bytes[bytes.Length - 2])
  76. {
  77. //验证通过
  78. byte stationNo = bytes[0];
  79. targetStation = stationNo;
  80. byte commandCode = bytes[1];
  81. if (commandCode > 80)
  82. {
  83. //异常信息
  84. ErrorCode errorCode = (ErrorCode)bytes[2];
  85. //这里使用异步调用如果使用同步调用将大大增加阻塞时间导致后面的AutoResetEvent等待超时
  86. ErrorMessage?.BeginInvoke(stationNo, errorCode.ToString(), null, null);
  87. }
  88. else
  89. {
  90. FunctionCode function = (FunctionCode)commandCode;
  91. byte length = bytes[2];
  92. switch (function)
  93. {
  94. case FunctionCode.ReadCoils:
  95. case FunctionCode.ReadDiscreteInputs:
  96. //返回字节长度
  97. RecBytes = new byte[length];
  98. Array.Copy(bytes, 3, RecBytes, 0, length);
  99. break;
  100. case FunctionCode.ReadHoldingRegisters:
  101. case FunctionCode.ReadInputRegisters:
  102. //读取寄存器数据
  103. RecBytes = new byte[length];
  104. Array.Copy(bytes, 3, RecBytes, 0, length);
  105. break;
  106. case FunctionCode.WriteSingleCoil:
  107. case FunctionCode.WriteSingleRegister:
  108. case FunctionCode.WriteMultipleCoils:
  109. case FunctionCode.WriteMultipleRegisters:
  110. RecBytes = new byte[4];
  111. Array.Copy(bytes, 2, RecBytes, 0, RecBytes.Length);
  112. break;
  113. case FunctionCode.ReportSlaveID:
  114. break;
  115. case FunctionCode.MaskWriteRegister:
  116. break;
  117. case FunctionCode.ReadWriteRegister:
  118. break;
  119. default:
  120. break;
  121. }
  122. }
  123. }
  124. else
  125. {
  126. ErrorMessage?.BeginInvoke(0, "报文校验未通过,可能传送中收到干扰!", null, null);
  127. }
  128. //接收完成,取消主线程阻塞
  129. slim.Set();
  130. }
  131. /// <summary>
  132. /// 串口波特率
  133. /// </summary>
  134. public int BaudRate
  135. {
  136. get
  137. {
  138. return serialPortObject.BaudRate;
  139. }
  140. set
  141. {
  142. serialPortObject.BaudRate = value;
  143. }
  144. }
  145. /// <summary>
  146. /// 数据位长度
  147. /// </summary>
  148. public int DataBits
  149. {
  150. get
  151. {
  152. return serialPortObject.DataBits;
  153. }
  154. set
  155. {
  156. serialPortObject.DataBits = value;
  157. }
  158. }
  159. /// <summary>
  160. /// 奇偶校验协议
  161. /// </summary>
  162. public Parity Parity
  163. {
  164. get
  165. {
  166. return serialPortObject.Parity;
  167. }
  168. set
  169. {
  170. serialPortObject.Parity = value;
  171. }
  172. }
  173. /// <summary>
  174. /// 停止位
  175. /// </summary>
  176. public StopBits StopBits
  177. {
  178. get
  179. {
  180. return serialPortObject.StopBits;
  181. }
  182. set
  183. {
  184. serialPortObject.StopBits = value;
  185. }
  186. }
  187. /// <summary>
  188. /// 当前的SerialPort对象
  189. /// </summary>
  190. public SerialPort SerialPortObject
  191. {
  192. get
  193. {
  194. return serialPortObject;
  195. }
  196. }
  197. /// <summary>
  198. /// 获取计算机所有的串行端口
  199. /// </summary>
  200. public string[] SerialPorts
  201. {
  202. get
  203. {
  204. return SerialPort.GetPortNames();
  205. }
  206. }
  207. /// <summary>
  208. /// 打开指定的串口
  209. /// </summary>
  210. /// <param name="serialPortName"></param>
  211. public void Open(string serialPortName)
  212. {
  213. if (serialPortObject.IsOpen)
  214. {
  215. serialPortObject.Close();
  216. }
  217. serialPortObject.PortName = serialPortName;
  218. serialPortObject.Open();
  219. }
  220. /// <summary>
  221. /// 关闭串口
  222. /// </summary>
  223. public void Close()
  224. {
  225. if (serialPortObject.IsOpen)
  226. {
  227. serialPortObject.Close();
  228. }
  229. }
  230. /// <summary>
  231. /// 串口是否打开
  232. /// </summary>
  233. public bool IsOpen
  234. {
  235. get
  236. {
  237. return serialPortObject.IsOpen;
  238. }
  239. }
  240. /// <summary>
  241. ///发送报文
  242. /// </summary>
  243. /// <param name="bytes"></param>
  244. /// <param name="protocol">报文协议</param>
  245. void Send(string sendMsg)
  246. {
  247. //对消息进行判断是否合规
  248. if (!Regex.IsMatch(sendMsg, @"^[0-9a-fA-F]+$"))
  249. {
  250. throw new Exception("报文错误,存在非16进制字符");
  251. }
  252. byte[] bytes = new byte[sendMsg.Length / 2];
  253. for (int i = 0; i < sendMsg.Length / 2; i++)
  254. {
  255. bytes[i] = Convert.ToByte(sendMsg.Substring(i * 2, 2), 16);
  256. }
  257. //添加校验码,并发送
  258. byte[] checkSum = Checkhelper.CrcModbus(bytes, 0, bytes.Length);
  259. var checkarr = checkSum.Reverse().ToArray();
  260. byte[] msgBytes = new byte[bytes.Length + checkSum.Length];
  261. Array.Copy(bytes, msgBytes, bytes.Length);
  262. Array.Copy(checkarr, 0, msgBytes, bytes.Length, checkarr.Length);
  263. serialPortObject.Write(msgBytes, 0, msgBytes.Length);
  264. }
  265. /// <summary>
  266. /// 编制报文
  267. /// </summary>
  268. /// <param name="slaveStation">从站站号</param>
  269. /// <param name="function">功能代码</param>
  270. /// <param name="devAdress">起始地址</param>
  271. /// <param name="devLenght">数量</param>
  272. /// <param name="values">写入的值</param>
  273. /// /// <param name="fromBase">如果写入的数值类型所占用的byte,例如short类型为2,int类型为4,flost类型为4,double类型为8</param>
  274. /// <param name="set">强制线圈通断,true:ON ;false:OFF</param>
  275. void ComposeMessage(int slaveStation, FunctionCode function, int devAdress, int devLenght = 1, byte[] values = null, int fromBase = 2, bool set = false)
  276. {
  277. lock (this)
  278. {
  279. slim.Reset();
  280. StringBuilder sb = new StringBuilder();
  281. //添加从站点,功能代码,起始位置
  282. sb.AppendFormat("{0}{1}{2}", slaveStation.ToString("x2"), ((int)function).ToString("X2"), devAdress.ToString("X4"));
  283. switch (function)
  284. {
  285. case FunctionCode.ReadCoils:
  286. case FunctionCode.ReadDiscreteInputs:
  287. case FunctionCode.ReadHoldingRegisters:
  288. case FunctionCode.ReadInputRegisters:
  289. //读取的数量,占用2byte
  290. sb.Append(devLenght.ToString("X4"));
  291. break;
  292. case FunctionCode.WriteSingleCoil:
  293. if (set)
  294. {
  295. //线圈置ON
  296. sb.Append("FF00");
  297. }
  298. else
  299. {
  300. //线圈置OFF
  301. sb.Append("0000");
  302. }
  303. break;
  304. case FunctionCode.WriteSingleRegister:
  305. //向单个寄存器写入值,占用两个byte
  306. int curVal = BitConverter.ToInt16(values, 0);
  307. sb.Append(curVal.ToString("X4"));
  308. break;
  309. case FunctionCode.WriteMultipleCoils:
  310. //向多个线圈写入值
  311. //1,需操作的线圈数量,数值占用两个byte
  312. sb.Append(devLenght.ToString("X4"));
  313. //2,需要操作的寄存器占用的字节数,数值占用1个byte
  314. sb.Append(((int)Math.Ceiling(devLenght * 1.0 / 8)).ToString("X2"));
  315. //3,写入线圈状态值
  316. foreach (var item in values)
  317. {
  318. sb.Append(item.ToString("X2"));
  319. }
  320. break;
  321. case FunctionCode.WriteMultipleRegisters:
  322. //向多个寄存器写入值
  323. //1,需操作的寄存器数量,数值占用两个byte
  324. sb.Append((values.Length * 2 / fromBase).ToString("X4"));
  325. //2,需要操作的寄存器占用的字节数,数值占用1个byte
  326. sb.Append((values.Length).ToString("X2"));
  327. //3,写入寄存器的值,根据frombase判定值
  328. //此时姑且以占2个Byte计算
  329. for (int i = 0; i < values.Length; i++)
  330. {
  331. sb.Append(values[i].ToString("X2"));
  332. }
  333. break;
  334. case FunctionCode.ReportSlaveID:
  335. break;
  336. case FunctionCode.MaskWriteRegister:
  337. break;
  338. case FunctionCode.ReadWriteRegister:
  339. break;
  340. default:
  341. break;
  342. }
  343. RecBytes = null;
  344. Send(sb.ToString().Trim());
  345. serialPortObject.DiscardInBuffer();
  346. //100ms等待消息回复
  347. msgRecFlag = slim.WaitOne(3000);
  348. System.Threading.Thread.Sleep(20);
  349. if (!msgRecFlag)
  350. {
  351. ErrorMessage?.Invoke(slaveStation, $"从站:{slaveStation},回复超时");
  352. }
  353. }
  354. }
  355. /// <summary>
  356. /// 强制指定单个线圈动作
  357. /// </summary>
  358. /// <param name="slaveStationNo">从站站号</param>
  359. /// <param name="devAdress">线圈地址</param>
  360. /// <param name="onOrOff">True为ON,false为Off</param>
  361. /// <returns></returns>
  362. public bool ForcedCoil(int slaveStationNo, int devAdress, bool set)
  363. {
  364. ComposeMessage(slaveStationNo, FunctionCode.WriteSingleCoil, devAdress, set: set);
  365. if (slaveStationNo == targetStation && msgRecFlag && RecBytes != null)
  366. {
  367. if (RecBytes[2] == 0xff && RecBytes[3] == 0 && set)
  368. {
  369. return true;
  370. }
  371. if (RecBytes[3] == 0 && RecBytes[2] == 0 && !set)
  372. {
  373. return true;
  374. }
  375. }
  376. return false;
  377. }
  378. /// <summary>
  379. /// 读取多个线圈状态
  380. /// </summary>
  381. /// <param name="slaveStationNo"></param>
  382. /// <param name="devAdress"></param>
  383. /// <param name="iLenght"></param>
  384. public bool[] ReadCoils(int slaveStationNo, int devAdress, int iLenght)
  385. {
  386. ComposeMessage(slaveStationNo, FunctionCode.ReadCoils, devAdress, iLenght);
  387. if (slaveStationNo == targetStation && msgRecFlag && RecBytes != null)
  388. {
  389. BitArray array = new BitArray(RecBytes);
  390. if (iLenght > array.Count)
  391. {
  392. throw new Exception("线圈查询结果异常!");
  393. }
  394. bool[] coils = new bool[iLenght];
  395. for (int i = 0; i < iLenght; i++)
  396. {
  397. coils[i] = array[i];
  398. }
  399. return coils;
  400. }
  401. else
  402. {
  403. return null;
  404. }
  405. }
  406. /// <summary>
  407. /// 向多个线圈写入值
  408. /// </summary>
  409. /// <param name="slaveStationNo">从站站号</param>
  410. /// <param name="devAdress"></param>
  411. /// <param name="iLenght">地址长度</param>
  412. /// <param name="arr">写入的值</param>
  413. public bool WriteMultipleCoils(int slaveStationNo, int devAdress, int iLenght, short[] arr)
  414. {
  415. if ((int)Math.Ceiling(iLenght * 1.0 / 16) != arr.Length)
  416. {
  417. throw new Exception("线圈数量与所赋值数量不匹配");
  418. }
  419. byte[] bytes = new byte[arr.Length * 2];
  420. int index = 0;
  421. foreach (var item in arr)
  422. {
  423. byte[] temBytes = BitConverter.GetBytes(item);
  424. Array.Copy(temBytes, 0, bytes, index, temBytes.Length);
  425. index += temBytes.Length;
  426. }
  427. ComposeMessage(slaveStationNo, FunctionCode.WriteMultipleCoils, devAdress, iLenght, bytes);
  428. if (slaveStationNo == targetStation && msgRecFlag && RecBytes != null && RecBytes.Length == 4)
  429. {
  430. int startAddress = BitConverter.ToInt16(new byte[] { RecBytes[1], RecBytes[0] }, 0);
  431. int addressLen = BitConverter.ToInt16(new byte[] { RecBytes[3], RecBytes[2] }, 0);
  432. if (startAddress == devAdress && addressLen == iLenght)
  433. {
  434. return true;
  435. }
  436. else
  437. {
  438. return false;
  439. }
  440. }
  441. else
  442. {
  443. return false;
  444. }
  445. }
  446. /// <summary>
  447. /// 读取从站多个寄存器的值
  448. /// </summary>
  449. /// <param name="slaveStationNo"></param>
  450. /// <param name="devAdress">起始寄存器</param>
  451. /// <param name="iLenght">寄存器数量</param>
  452. public short[] ReadHoldingRegisters(int slaveStationNo, int devAdress, int iLenght)
  453. {
  454. ComposeMessage(slaveStationNo, FunctionCode.ReadHoldingRegisters, devAdress, iLenght);
  455. if (slaveStationNo == targetStation && msgRecFlag && RecBytes != null)
  456. {
  457. List<short> shortList = new List<short>();
  458. for (int i = 0; i < RecBytes.Length; i += 2)
  459. {
  460. shortList.Add(BitConverter.ToInt16(new byte[] { RecBytes[i + 1], RecBytes[i] }, 0));
  461. }
  462. return shortList.ToArray();
  463. }
  464. else
  465. {
  466. return null;
  467. }
  468. }
  469. /// <summary>
  470. /// 读取单精度浮点数
  471. /// </summary>
  472. /// <param name="slaveStationNo">从站站点</param>
  473. /// <param name="devAdress">起始地址</param>
  474. /// <param name="iLenght">连续读取浮点数的个数</param>
  475. /// <returns></returns>
  476. public float[] ReadFloatRegisters(int slaveStationNo, int devAdress, int iLenght)
  477. {
  478. ComposeMessage(slaveStationNo, FunctionCode.ReadHoldingRegisters, devAdress, iLenght * 2);
  479. if (slaveStationNo == targetStation && msgRecFlag && RecBytes != null)
  480. {
  481. List<float> floatList = new List<float>();
  482. for (int i = 0; i < RecBytes.Length; i += 4)
  483. {
  484. floatList.Add(BitConverter.ToSingle(new byte[] { RecBytes[i + 1], RecBytes[i], RecBytes[i + 3], RecBytes[i + 2] }, 0));
  485. }
  486. return floatList.ToArray();
  487. }
  488. else
  489. {
  490. return null;
  491. }
  492. }
  493. /// <summary>
  494. /// 向从站单个寄存器写入值
  495. /// </summary>
  496. /// <param name="slaveStationNo"></param>
  497. /// <param name="devAdress"></param>
  498. /// <param name="value"></param>
  499. public bool WriteSingleRegister(int slaveStationNo, int devAdress, short value)
  500. {
  501. ComposeMessage(slaveStationNo, FunctionCode.WriteSingleRegister, devAdress, values: BitConverter.GetBytes(value));
  502. if (slaveStationNo == targetStation && msgRecFlag && RecBytes != null && RecBytes.Length == 4)
  503. {
  504. int startAddress = BitConverter.ToInt16(new byte[] { RecBytes[1], RecBytes[0] }, 0);
  505. int val = BitConverter.ToInt16(new byte[] { RecBytes[3], RecBytes[2] }, 0);
  506. if (startAddress == devAdress && val == value)
  507. {
  508. return true;
  509. }
  510. else
  511. {
  512. return false;
  513. }
  514. }
  515. else
  516. {
  517. return false;
  518. }
  519. }
  520. /// <summary>
  521. /// 写入单精度浮点数
  522. /// </summary>
  523. /// <param name="slaveStationNo">从站站点</param>
  524. /// <param name="devAdress">寄存器地址</param>
  525. /// <param name="value"></param>
  526. /// <returns>true写入成功,false写入失败</returns>
  527. public bool WriteFloatRegister(int slaveStationNo, int devAdress, float value)
  528. {
  529. //进行拆解组合
  530. byte[] bytes = BitConverter.GetBytes(value);
  531. byte[] valBytes = new byte[] { bytes[1], bytes[0], bytes[3], bytes[2] };
  532. ComposeMessage(slaveStationNo, FunctionCode.WriteMultipleRegisters, devAdress, values: valBytes, fromBase: 4);
  533. if (slaveStationNo == targetStation && msgRecFlag && RecBytes != null)
  534. {
  535. short adress = BitConverter.ToInt16(new byte[] { RecBytes[1], RecBytes[0] }, 0);
  536. short registerCount = BitConverter.ToInt16(new byte[] { RecBytes[3], RecBytes[2] }, 0);
  537. if (adress == devAdress && registerCount == 2)
  538. {
  539. return true;
  540. }
  541. else
  542. {
  543. return false;
  544. }
  545. }
  546. else
  547. {
  548. return false;
  549. }
  550. }
  551. /// <summary>
  552. /// 向从站多个寄存器写入值
  553. /// </summary>
  554. /// <param name="slaveStationNo"></param>
  555. /// <param name="devAdress"></param>
  556. /// <param name="iLenght"></param>
  557. /// <param name="arr"></param>
  558. public bool WriteMultipleRegisters(int slaveStationNo, int devAdress, int iLenght, short[] arr)
  559. {
  560. if (iLenght != arr.Length)
  561. {
  562. throw new Exception("寄存器数量与所赋值数量不匹配");
  563. }
  564. byte[] bytes = new byte[arr.Length * 2];
  565. int index = 0;
  566. foreach (var item in arr)
  567. {
  568. byte[] temBytes = BitConverter.GetBytes(item);
  569. byte[] temBytes2 = new byte[] { temBytes[1], temBytes[0] };
  570. Array.Copy(temBytes2, 0, bytes, index, temBytes2.Length);
  571. index += temBytes.Length;
  572. }
  573. ComposeMessage(slaveStationNo, FunctionCode.WriteMultipleRegisters, devAdress, iLenght, bytes, fromBase: 4);
  574. if (slaveStationNo == targetStation && msgRecFlag && RecBytes != null && RecBytes.Length == 4)
  575. {
  576. int startAddress = BitConverter.ToInt16(new byte[] { RecBytes[1], RecBytes[0] }, 0);
  577. int addressLen = BitConverter.ToInt16(new byte[] { RecBytes[3], RecBytes[2] }, 0);
  578. if (startAddress == devAdress && addressLen == iLenght)
  579. {
  580. return true;
  581. }
  582. else
  583. {
  584. return false;
  585. }
  586. }
  587. else
  588. {
  589. return false;
  590. }
  591. }
  592. public void Dispose()
  593. {
  594. serialPortObject?.Dispose();
  595. }
3.6.5,校验码类
  1. /// <summary>
  2. /// 数据校验帮助类,计算常见的CRC校验;LRC校验;BCC校验;累加和校验
  3. /// </summary>
  4. public class Checkhelper
  5. {
  6. #region CRC校验说明
  7. // CRC即循环冗余校验码(Cyclic Redundancy Check):是数据通信领域中最常用的一种查错校验码,
  8. //其特征是信息字段和校验字段的长度可以任意选定。循环冗余检查(CRC)是一种数据传输检错功能,对数据进行多项式计算,
  9. //并将得到的结果附在帧的后面,接收设备也执行类似的算法,以保证数据传输的正确性和完整性。
  10. // CRC算法参数模型解释:
  11. // NAME:参数模型名称。
  12. //WIDTH:宽度,即CRC比特数。
  13. // POLY:生成项的简写,以16进制表示。例如:CRC-32即是0x04C11DB7,忽略了最高位的"1",即完整的生成项是0x104C11DB7。
  14. //INIT:这是算法开始时寄存器(crc)的初始化预置值,十六进制表示。
  15. //REFIN:待测数据的每个字节是否按位反转,True或False。
  16. // REFOUT:在计算后之后,异或输出之前,整个数据是否按位反转,True或False。
  17. // XOROUT:计算结果与此参数异或后得到最终的CRC值。
  18. #endregion
  19. // **********************************************************************
  20. // Name: CRC-4/ITU x4+x+1
  21. // Poly: 0x03
  22. // Init: 0x00
  23. // Refin: true
  24. // Refout: true
  25. // Xorout: 0x00
  26. //*************************************************************************
  27. /// <summary>
  28. /// CRC-4/ITU
  29. /// </summary>
  30. /// <param name="buffer"></param>
  31. /// <param name="start"></param>
  32. /// <param name="len"></param>
  33. /// <returns></returns>
  34. public static byte[] Crc1(byte[] buffer, int start = 0, int len = 0)
  35. {
  36. if (buffer == null || buffer.Length == 0) return null;
  37. if (start < 0) return null;
  38. if (len == 0) len = buffer.Length - start;
  39. int length = start + len;
  40. if (length > buffer.Length) return null;
  41. byte crc = 0;// Initial value
  42. for (int i = start; i < length; i++)
  43. {
  44. crc ^= buffer[i];
  45. for (int j = 0; j < 8; j++)
  46. {
  47. if ((crc & 1) > 0)
  48. crc = (byte)((crc >> 1) ^ 0x0C);//0x0C = (reverse 0x03)>>(8-4)
  49. else
  50. crc = (byte)(crc >> 1);
  51. }
  52. }
  53. return new byte[] { crc };
  54. }
  55. // **********************************************************************
  56. // Name: CRC-5/EPC x5+x3+1
  57. // Poly: 0x09
  58. // Init: 0x09
  59. // Refin: false
  60. // Refout: false
  61. // Xorout: 0x00
  62. //*************************************************************************
  63. /// <summary>
  64. /// CRC-5/EPC
  65. /// </summary>
  66. /// <param name="buffer"></param>
  67. /// <param name="start"></param>
  68. /// <param name="len"></param>
  69. /// <returns></returns>
  70. public static byte[] Crc2(byte[] buffer, int start = 0, int len = 0)
  71. {
  72. if (buffer == null || buffer.Length == 0) return null;
  73. if (start < 0) return null;
  74. if (len == 0) len = buffer.Length - start;
  75. int length = start + len;
  76. if (length > buffer.Length) return null;
  77. byte crc = 0x48;// Initial value: 0x48 = 0x09<<(8-5)
  78. for (int i = start; i < length; i++)
  79. {
  80. crc ^= buffer[i];
  81. for (int j = 0; j < 8; j++)
  82. {
  83. if ((crc & 0x80) > 0)
  84. crc = (byte)((crc << 1) ^ 0x48);// 0x48 = 0x09<<(8-5)
  85. else
  86. crc = (byte)(crc << 1);
  87. }
  88. }
  89. return new byte[] { (byte)(crc >> 3) };
  90. }
  91. // **********************************************************************
  92. // Name: CRC-5/ITU x5+x4+x2+1
  93. // Poly: 0x15
  94. // Init: 0x00
  95. // Refin: true
  96. // Refout: true
  97. // Xorout: 0x00
  98. //*************************************************************************
  99. /// <summary>
  100. /// CRC-5/ITU
  101. /// </summary>
  102. /// <param name="buffer"></param>
  103. /// <param name="start"></param>
  104. /// <param name="len"></param>
  105. /// <returns></returns>
  106. public static byte[] Crc3(byte[] buffer, int start = 0, int len = 0)
  107. {
  108. if (buffer == null || buffer.Length == 0) return null;
  109. if (start < 0) return null;
  110. if (len == 0) len = buffer.Length - start;
  111. int length = start + len;
  112. if (length > buffer.Length) return null;
  113. byte crc = 0;// Initial value
  114. for (int i = start; i < length; i++)
  115. {
  116. crc ^= buffer[i];
  117. for (int j = 0; j < 8; j++)
  118. {
  119. if ((crc & 1) > 0)
  120. crc = (byte)((crc >> 1) ^ 0x15);// 0x15 = (reverse 0x15)>>(8-5)
  121. else
  122. crc = (byte)(crc >> 1);
  123. }
  124. }
  125. return new byte[] { crc };
  126. }
  127. // **********************************************************************
  128. // Name: CRC-5/USB x5+x2+1
  129. // Poly: 0x05
  130. // Init: 0x1F
  131. // Refin: true
  132. // Refout: true
  133. // Xorout: 0x1F
  134. //*************************************************************************
  135. /// <summary>
  136. /// CRC-5/USB
  137. /// </summary>
  138. /// <param name="buffer"></param>
  139. /// <param name="start"></param>
  140. /// <param name="len"></param>
  141. /// <returns></returns>
  142. public static byte[] Crc4(byte[] buffer, int start = 0, int len = 0)
  143. {
  144. if (buffer == null || buffer.Length == 0) return null;
  145. if (start < 0) return null;
  146. if (len == 0) len = buffer.Length - start;
  147. int length = start + len;
  148. if (length > buffer.Length) return null;
  149. byte crc = 0x1F;// Initial value
  150. for (int i = start; i < length; i++)
  151. {
  152. crc ^= buffer[i];
  153. for (int j = 0; j < 8; j++)
  154. {
  155. if ((crc & 1) > 0)
  156. crc = (byte)((crc >> 1) ^ 0x14);// 0x14 = (reverse 0x05)>>(8-5)
  157. else
  158. crc = (byte)(crc >> 1);
  159. }
  160. }
  161. return new byte[] { (byte)(crc ^ 0x1F) };
  162. }
  163. // **********************************************************************
  164. // Name: CRC-6/ITU x6+x+1
  165. // Poly: 0x03
  166. // Init: 0x00
  167. // Refin: true
  168. // Refout: true
  169. // Xorout: 0x00
  170. //*************************************************************************
  171. /// <summary>
  172. /// CRC-6/ITU
  173. /// </summary>
  174. /// <param name="buffer"></param>
  175. /// <param name="start"></param>
  176. /// <param name="len"></param>
  177. /// <returns></returns>
  178. public static byte[] Crc5(byte[] buffer, int start = 0, int len = 0)
  179. {
  180. if (buffer == null || buffer.Length == 0) return null;
  181. if (start < 0) return null;
  182. if (len == 0) len = buffer.Length - start;
  183. int length = start + len;
  184. if (length > buffer.Length) return null;
  185. byte crc = 0;// Initial value
  186. for (int i = start; i < length; i++)
  187. {
  188. crc ^= buffer[i];
  189. for (int j = 0; j < 8; j++)
  190. {
  191. if ((crc & 1) > 0)
  192. crc = (byte)((crc >> 1) ^ 0x30);// 0x30 = (reverse 0x03)>>(8-6)
  193. else
  194. crc = (byte)(crc >> 1);
  195. }
  196. }
  197. return new byte[] { crc };
  198. }
  199. // **********************************************************************
  200. // Name: CRC-7/MMC x7+x3+1
  201. // Poly: 0x09
  202. // Init: 0x00
  203. // Refin: false
  204. // Refout: false
  205. // Xorout: 0x00
  206. //*************************************************************************
  207. /// <summary>
  208. /// CRC-7/MMC
  209. /// </summary>
  210. /// <param name="buffer"></param>
  211. /// <param name="start"></param>
  212. /// <param name="len"></param>
  213. /// <returns></returns>
  214. public static byte[] Crc6(byte[] buffer, int start = 0, int len = 0)
  215. {
  216. if (buffer == null || buffer.Length == 0) return null;
  217. if (start < 0) return null;
  218. if (len == 0) len = buffer.Length - start;
  219. int length = start + len;
  220. if (length > buffer.Length) return null;
  221. byte crc = 0;// Initial value
  222. for (int i = start; i < length; i++)
  223. {
  224. crc ^= buffer[i];
  225. for (int j = 0; j < 8; j++)
  226. {
  227. if ((crc & 0x80) > 0)
  228. crc = (byte)((crc << 1) ^ 0x12);// 0x12 = 0x09<<(8-7)
  229. else
  230. crc = (byte)(crc << 1);
  231. }
  232. }
  233. return new byte[] { (byte)(crc >> 1) };
  234. }
  235. // **********************************************************************
  236. // Name: CRC8 x8+x2+x+1
  237. // Poly: 0x07
  238. // Init: 0x00
  239. // Refin: false
  240. // Refout: false
  241. // Xorout: 0x00
  242. //*************************************************************************
  243. /// <summary>
  244. /// CRC8
  245. /// </summary>
  246. /// <param name="buffer"></param>
  247. /// <param name="start"></param>
  248. /// <param name="len"></param>
  249. /// <returns></returns>
  250. public static byte[] Crc7(byte[] buffer, int start = 0, int len = 0)
  251. {
  252. if (buffer == null || buffer.Length == 0) return null;
  253. if (start < 0) return null;
  254. if (len == 0) len = buffer.Length - start;
  255. int length = start + len;
  256. if (length > buffer.Length) return null;
  257. byte crc = 0;// Initial value
  258. for (int i = start; i < length; i++)
  259. {
  260. crc ^= buffer[i];
  261. for (int j = 0; j < 8; j++)
  262. {
  263. if ((crc & 0x80) > 0)
  264. crc = (byte)((crc << 1) ^ 0x07);
  265. else
  266. crc = (byte)(crc << 1);
  267. }
  268. }
  269. return new byte[] { crc };
  270. }
  271. // **********************************************************************
  272. // Name: CRC-8/ITU x8+x2+x+1
  273. // Poly: 0x07
  274. // Init: 0x00
  275. // Refin: false
  276. // Refout: false
  277. // Xorout: 0x55
  278. //*************************************************************************
  279. /// <summary>
  280. /// CRC-8/ITU
  281. /// </summary>
  282. /// <param name="buffer"></param>
  283. /// <param name="start"></param>
  284. /// <param name="len"></param>
  285. /// <returns></returns>
  286. public static byte[] Crc8(byte[] buffer, int start = 0, int len = 0)
  287. {
  288. if (buffer == null || buffer.Length == 0) return null;
  289. if (start < 0) return null;
  290. if (len == 0) len = buffer.Length - start;
  291. int length = start + len;
  292. if (length > buffer.Length) return null;
  293. byte crc = 0;// Initial value
  294. for (int i = start; i < length; i++)
  295. {
  296. crc ^= buffer[i];
  297. for (int j = 0; j < 8; j++)
  298. {
  299. if ((crc & 0x80) > 0)
  300. crc = (byte)((crc << 1) ^ 0x07);
  301. else
  302. crc = (byte)(crc << 1);
  303. }
  304. }
  305. return new byte[] { (byte)(crc ^ 0x55) };
  306. }
  307. // **********************************************************************
  308. // Name: CRC-8/MAXIM x8+x5+x4+1
  309. // Poly: 0x31
  310. // Init: 0x00
  311. // Refin: true
  312. // Refout: true
  313. // Xorout: 0x00
  314. //*************************************************************************
  315. /// <summary>
  316. /// CRC-8/MAXIM
  317. /// </summary>
  318. /// <param name="buffer"></param>
  319. /// <param name="start"></param>
  320. /// <param name="len"></param>
  321. /// <returns></returns>
  322. public static byte[] Crc9(byte[] buffer, int start = 0, int len = 0)
  323. {
  324. if (buffer == null || buffer.Length == 0) return null;
  325. if (start < 0) return null;
  326. if (len == 0) len = buffer.Length - start;
  327. int length = start + len;
  328. if (length > buffer.Length) return null;
  329. byte crc = 0;// Initial value
  330. for (int i = start; i < length; i++)
  331. {
  332. crc ^= buffer[i];
  333. for (int j = 0; j < 8; j++)
  334. {
  335. if ((crc & 1) > 0)
  336. crc = (byte)((crc >> 1) ^ 0x8C);// 0x8C = reverse 0x31
  337. else
  338. crc = (byte)(crc >> 1);
  339. }
  340. }
  341. return new byte[] { crc };
  342. }
  343. // **********************************************************************
  344. // Name: CRC-8/ROHC x8+x2+x+1
  345. // Poly: 0x07
  346. // Init: 0xFF
  347. // Refin: true
  348. // Refout: true
  349. // Xorout: 0x00
  350. //*************************************************************************
  351. /// <summary>
  352. /// CRC-8/ROHC
  353. /// </summary>
  354. /// <param name="buffer"></param>
  355. /// <param name="start"></param>
  356. /// <param name="len"></param>
  357. /// <returns></returns>
  358. public static byte[] Crc10(byte[] buffer, int start = 0, int len = 0)
  359. {
  360. if (buffer == null || buffer.Length == 0) return null;
  361. if (start < 0) return null;
  362. if (len == 0) len = buffer.Length - start;
  363. int length = start + len;
  364. if (length > buffer.Length) return null;
  365. byte crc = 0xFF;// Initial value
  366. for (int i = start; i < length; i++)
  367. {
  368. crc ^= buffer[i];
  369. for (int j = 0; j < 8; j++)
  370. {
  371. if ((crc & 1) > 0)
  372. crc = (byte)((crc >> 1) ^ 0xE0);// 0xE0 = reverse 0x07
  373. else
  374. crc = (byte)(crc >> 1);
  375. }
  376. }
  377. return new byte[] { crc };
  378. }
  379. /// Z1协议校验码计算
  380. static byte[] table = { 0x00, 0x1C, 0x38, 0x24, 0x70, 0x6C, 0x48, 0x54, 0xE0, 0xFC,
  381. 0xD8, 0xC4, 0x90, 0x8C, 0xA8, 0xB4, 0xDC, 0xC0, 0xE4, 0xF8,
  382. 0xAC, 0xB0, 0x94, 0x88, 0x3C, 0x20, 0x04, 0x18, 0x4C, 0x50,
  383. 0x74, 0x68, 0xA4, 0xB8, 0x9C, 0x80, 0xD4, 0xC8, 0xEC, 0xF0,
  384. 0x44, 0x58, 0x7C, 0x60, 0x34, 0x28, 0x0C, 0x10, 0x78, 0x64,
  385. 0x40, 0x5C, 0x08, 0x14, 0x30, 0x2C, 0x98, 0x84, 0xA0, 0xBC,
  386. 0xE8, 0xF4, 0xD0, 0xCC, 0x54, 0x48, 0x6C, 0x70, 0x24, 0x38,
  387. 0x1C, 0x00, 0xB4, 0xA8, 0x8C, 0x90, 0xC4, 0xD8, 0xFC, 0xE0,
  388. 0x88, 0x94, 0xB0, 0xAC, 0xF8, 0xE4, 0xC0, 0xDC, 0x68, 0x74,
  389. 0x50, 0x4C, 0x18, 0x04, 0x20, 0x3C, 0xF0, 0xEC, 0xC8, 0xD4,
  390. 0x80, 0x9C, 0xB8, 0xA4, 0x10, 0x0C, 0x28, 0x34, 0x60, 0x7C,
  391. 0x58, 0x44, 0x2C, 0x30, 0x14, 0x08, 0x5C, 0x40, 0x64, 0x78,
  392. 0xCC, 0xD0, 0xF4, 0xE8, 0xBC, 0xA0, 0x84, 0x98, 0xA8, 0xB4,
  393. 0x90, 0x8C, 0xD8, 0xC4, 0xE0, 0xFC, 0x48, 0x54, 0x70, 0x6C,
  394. 0x38, 0x24, 0x00, 0x1C, 0x74, 0x68, 0x4C, 0x50, 0x04, 0x18,
  395. 0x3C, 0x20, 0x94, 0x88, 0xAC, 0xB0, 0xE4, 0xF8, 0xDC, 0xC0,
  396. 0x0C, 0x10, 0x34, 0x28, 0x7C, 0x60, 0x44, 0x58, 0xEC, 0xF0,
  397. 0xD4, 0xC8, 0x9C, 0x80, 0xA4, 0xB8, 0xD0, 0xCC, 0xE8, 0xF4,
  398. 0xA0, 0xBC, 0x98, 0x84, 0x30, 0x2C, 0x08, 0x14, 0x40, 0x5C,
  399. 0x78, 0x64, 0xFC, 0xE0, 0xC4, 0xD8, 0x8C, 0x90, 0xB4, 0xA8,
  400. 0x1C, 0x00, 0x24, 0x38, 0x6C, 0x70, 0x54, 0x48, 0x20, 0x3C,
  401. 0x18, 0x04, 0x50, 0x4C, 0x68, 0x74, 0xC0, 0xDC, 0xF8, 0xE4,
  402. 0xB0, 0xAC, 0x88, 0x94, 0x58, 0x44, 0x60, 0x7C, 0x28, 0x34,
  403. 0x10, 0x0C, 0xB8, 0xA4, 0x80, 0x9C, 0xC8, 0xD4, 0xF0, 0xEC,
  404. 0x84, 0x98, 0xBC, 0xA0, 0xF4, 0xE8, 0xCC, 0xD0, 0x64, 0x78,
  405. 0x5C, 0x40, 0x14, 0x08, 0x2C, 0x30
  406. };
  407. public static byte[] Crc11(byte[] buffer, int start = 0, int len = 0)
  408. {
  409. if (buffer == null || buffer.Length == 0) return null;
  410. if (start < 0) return null;
  411. if (len == 0) len = buffer.Length - start;
  412. int length = start + len;
  413. if (length > buffer.Length) return null;
  414. int i;
  415. byte crc = 0x00;
  416. int tableIndex;
  417. for (i = start; i < length; i++)
  418. {
  419. tableIndex = crc ^ (buffer[i] & 0xFF);
  420. crc = table[tableIndex];
  421. }
  422. return new byte[] { crc };
  423. }
  424. // **********************************************************************
  425. // Name: CRC-12 x16+x12+x5+1
  426. // Poly: 0x80
  427. // Init: 0x0000
  428. // Refin: true
  429. // Refout: true
  430. // Xorout: 0x0000
  431. //*************************************************************************
  432. /// <summary>
  433. /// CRC-12
  434. /// </summary>
  435. /// <param name="buffer"></param>
  436. /// <param name="start"></param>
  437. /// <param name="len"></param>
  438. /// <returns></returns>
  439. public static byte[] Crc12(byte[] buffer, int start = 0, int len = 0)
  440. {
  441. if (buffer == null || buffer.Length == 0) return null;
  442. if (start < 0) return null;
  443. if (len == 0) len = buffer.Length - start;
  444. int length = start + len;
  445. if (length > buffer.Length) return null;
  446. ushort crc = 0;// Initial value
  447. short iQ = 0, iR = 0;
  448. for (int i = start; i < length; i++)
  449. {
  450. // 多项式除法
  451. // 如果该位为1
  452. if ((buffer[i] & (0x80 >> iR)) > 0)
  453. {
  454. // 则在余数尾部添1否则添0
  455. crc |= 0x01;
  456. }
  457. // 如果12位除数中的最高位为1,则够除
  458. if (crc >= 0x1000)
  459. {
  460. crc ^= 0x180D;
  461. }
  462. crc <<= 1;
  463. iR++;
  464. if (8 == iR)
  465. {
  466. iR = 0;
  467. iQ++;
  468. }
  469. }
  470. // 对后面添加的12个0做处理
  471. for (int i = 0; i < 12; i++)
  472. {
  473. if (crc >= 0x1000)
  474. {
  475. crc ^= 0x180D;
  476. }
  477. crc <<= 1;
  478. }
  479. crc >>= 1;
  480. byte[] ret = BitConverter.GetBytes(crc);
  481. Array.Reverse(ret);
  482. return ret;
  483. }
  484. // **********************************************************************
  485. // Name: CRC-16/CCITT x16+x12+x5+1
  486. // Poly: 0x1021
  487. // Init: 0x0000
  488. // Refin: true
  489. // Refout: true
  490. // Xorout: 0x0000
  491. //*************************************************************************
  492. /// <summary>
  493. /// CRC-16/CCITT
  494. /// </summary>
  495. /// <param name="buffer"></param>
  496. /// <param name="start"></param>
  497. /// <param name="len"></param>
  498. /// <returns></returns>
  499. public static byte[] Crc13(byte[] buffer, int start = 0, int len = 0)
  500. {
  501. if (buffer == null || buffer.Length == 0) return null;
  502. if (start < 0) return null;
  503. if (len == 0) len = buffer.Length - start;
  504. int length = start + len;
  505. if (length > buffer.Length) return null;
  506. ushort crc = 0;// Initial value
  507. for (int i = start; i < length; i++)
  508. {
  509. crc ^= buffer[i];
  510. for (int j = 0; j < 8; j++)
  511. {
  512. if ((crc & 1) > 0)
  513. crc = (ushort)((crc >> 1) ^ 0x8408);// 0x8408 = reverse 0x1021
  514. else
  515. crc = (ushort)(crc >> 1);
  516. }
  517. }
  518. byte[] ret = BitConverter.GetBytes(crc);
  519. Array.Reverse(ret);
  520. return ret;
  521. }
  522. // **********************************************************************
  523. // Name: CRC-16/CCITT FALSE x16+x12+x5+1
  524. // Poly: 0x1021
  525. // Init: 0xFFFF
  526. // Refin: false
  527. // Refout: false
  528. // Xorout: 0x0000
  529. //*************************************************************************
  530. /// <summary>
  531. /// CRC-16/CCITT FALSE
  532. /// </summary>
  533. /// <param name="buffer"></param>
  534. /// <param name="start"></param>
  535. /// <param name="len"></param>
  536. /// <returns></returns>
  537. public static byte[] Crc14(byte[] buffer, int start = 0, int len = 0)
  538. {
  539. if (buffer == null || buffer.Length == 0) return null;
  540. if (start < 0) return null;
  541. if (len == 0) len = buffer.Length - start;
  542. int length = start + len;
  543. if (length > buffer.Length) return null;
  544. ushort crc = 0xFFFF;// Initial value
  545. for (int i = start; i < length; i++)
  546. {
  547. crc ^= (ushort)(buffer[i] << 8);
  548. for (int j = 0; j < 8; j++)
  549. {
  550. if ((crc & 0x8000) > 0)
  551. crc = (ushort)((crc << 1) ^ 0x1021);
  552. else
  553. crc = (ushort)(crc << 1);
  554. }
  555. }
  556. byte[] ret = BitConverter.GetBytes(crc);
  557. Array.Reverse(ret);
  558. return ret;
  559. }
  560. // **********************************************************************
  561. // Name: CRC-16/DNP x16+x13+x12+x11+x10+x8+x6+x5+x2+1
  562. // Poly: 0x3D65
  563. // Init: 0x0000
  564. // Refin: true
  565. // Refout: true
  566. // Xorout: 0xFFFF
  567. //*************************************************************************
  568. /// <summary>
  569. /// CRC-16/DNP
  570. /// </summary>
  571. /// <param name="buffer"></param>
  572. /// <param name="start"></param>
  573. /// <param name="len"></param>
  574. /// <returns></returns>
  575. public static byte[] Crc15(byte[] buffer, int start = 0, int len = 0)
  576. {
  577. if (buffer == null || buffer.Length == 0) return null;
  578. if (start < 0) return null;
  579. if (len == 0) len = buffer.Length - start;
  580. int length = start + len;
  581. if (length > buffer.Length) return null;
  582. ushort crc = 0;// Initial value
  583. for (int i = start; i < length; i++)
  584. {
  585. crc ^= buffer[i];
  586. for (int j = 0; j < 8; j++)
  587. {
  588. if ((crc & 1) > 0)
  589. crc = (ushort)((crc >> 1) ^ 0xA6BC);// 0xA6BC = reverse 0x3D65
  590. else
  591. crc = (ushort)(crc >> 1);
  592. }
  593. }
  594. byte[] ret = BitConverter.GetBytes((ushort)~crc);
  595. Array.Reverse(ret);
  596. return ret;
  597. }
  598. // **********************************************************************
  599. // Name: CRC-16/IBM x16+x15+x2+1
  600. // Poly: 0x8005
  601. // Init: 0x0000
  602. // Refin: true
  603. // Refout: true
  604. // Xorout: 0x0000
  605. //*************************************************************************
  606. /// <summary>
  607. /// CRC-16/IBM
  608. /// </summary>
  609. /// <param name="buffer"></param>
  610. /// <param name="start"></param>
  611. /// <param name="len"></param>
  612. /// <returns></returns>
  613. public static byte[] Crc16(byte[] buffer, int start = 0, int len = 0)
  614. {
  615. if (buffer == null || buffer.Length == 0) return null;
  616. if (start < 0) return null;
  617. if (len == 0) len = buffer.Length - start;
  618. int length = start + len;
  619. if (length > buffer.Length) return null;
  620. ushort crc = 0;// Initial value
  621. for (int i = start; i < length; i++)
  622. {
  623. crc ^= buffer[i];
  624. for (int j = 0; j < 8; j++)
  625. {
  626. if ((crc & 1) > 0)
  627. crc = (ushort)((crc >> 1) ^ 0xA001);// 0xA001 = reverse 0x8005
  628. else
  629. crc = (ushort)(crc >> 1);
  630. }
  631. }
  632. byte[] ret = BitConverter.GetBytes(crc);
  633. Array.Reverse(ret);
  634. return ret;
  635. }
  636. // **********************************************************************
  637. // Name: CRC-16/MAXIM x16+x15+x2+1
  638. // Poly: 0x8005
  639. // Init: 0x0000
  640. // Refin: true
  641. // Refout: true
  642. // Xorout: 0xFFFF
  643. //*************************************************************************
  644. /// <summary>
  645. /// CRC-16/MAXIM
  646. /// </summary>
  647. /// <param name="buffer"></param>
  648. /// <param name="start"></param>
  649. /// <param name="len"></param>
  650. /// <returns></returns>
  651. public static byte[] Crc17(byte[] buffer, int start = 0, int len = 0)
  652. {
  653. if (buffer == null || buffer.Length == 0) return null;
  654. if (start < 0) return null;
  655. if (len == 0) len = buffer.Length - start;
  656. int length = start + len;
  657. if (length > buffer.Length) return null;
  658. ushort crc = 0;// Initial value
  659. for (int i = start; i < length; i++)
  660. {
  661. crc ^= buffer[i];
  662. for (int j = 0; j < 8; j++)
  663. {
  664. if ((crc & 1) > 0)
  665. crc = (ushort)((crc >> 1) ^ 0xA001);// 0xA001 = reverse 0x8005
  666. else
  667. crc = (ushort)(crc >> 1);
  668. }
  669. }
  670. byte[] ret = BitConverter.GetBytes((ushort)~crc);
  671. Array.Reverse(ret);
  672. return ret;
  673. }
  674. // **********************************************************************
  675. // Name: CRC-16/MODBUS x16+x15+x2+1
  676. // Poly: 0x8005
  677. // Init: 0xFFFF
  678. // Refin: true
  679. // Refout: true
  680. // Xorout: 0x0000
  681. //*************************************************************************
  682. /// <summary>
  683. /// CRC-16/MODBUS
  684. /// </summary>
  685. /// <param name="buffer"></param>
  686. /// <param name="start"></param>
  687. /// <param name="len"></param>
  688. /// <returns></returns>
  689. public static byte[] CrcModbus(byte[] buffer, int start = 0, int len = 0)
  690. {
  691. if (buffer == null || buffer.Length == 0) return null;
  692. if (start < 0) return null;
  693. if (len == 0) len = buffer.Length - start;
  694. int length = start + len;
  695. if (length > buffer.Length) return null;
  696. ushort crc = 0xFFFF;// Initial value
  697. for (int i = start; i < length; i++)
  698. {
  699. crc ^= buffer[i];
  700. for (int j = 0; j < 8; j++)
  701. {
  702. if ((crc & 1) > 0)
  703. crc = (ushort)((crc >> 1) ^ 0xA001);// 0xA001 = reverse 0x8005
  704. else
  705. crc = (ushort)(crc >> 1);
  706. }
  707. }
  708. byte[] ret = BitConverter.GetBytes(crc);
  709. Array.Reverse(ret);
  710. return ret;
  711. }
  712. // **********************************************************************
  713. // Name: CRC-16/USB x16+x15+x2+1
  714. // Poly: 0x8005
  715. // Init: 0xFFFF
  716. // Refin: true
  717. // Refout: true
  718. // Xorout: 0xFFFF
  719. //*************************************************************************
  720. /// <summary>
  721. /// CRC-16/USB
  722. /// </summary>
  723. /// <param name="buffer"></param>
  724. /// <param name="start"></param>
  725. /// <param name="len"></param>
  726. /// <returns></returns>
  727. public static byte[] Crc19(byte[] buffer, int start = 0, int len = 0)
  728. {
  729. if (buffer == null || buffer.Length == 0) return null;
  730. if (start < 0) return null;
  731. if (len == 0) len = buffer.Length - start;
  732. int length = start + len;
  733. if (length > buffer.Length) return null;
  734. ushort crc = 0xFFFF;// Initial value
  735. for (int i = start; i < length; i++)
  736. {
  737. crc ^= buffer[i];
  738. for (int j = 0; j < 8; j++)
  739. {
  740. if ((crc & 1) > 0)
  741. crc = (ushort)((crc >> 1) ^ 0xA001);// 0xA001 = reverse 0x8005
  742. else
  743. crc = (ushort)(crc >> 1);
  744. }
  745. }
  746. byte[] ret = BitConverter.GetBytes((ushort)~crc);
  747. Array.Reverse(ret);
  748. return ret;
  749. }
  750. // **********************************************************************
  751. // Name: CRC-16/X25 x16+x12+x5+1
  752. // Poly: 0x1021
  753. // Init: 0xFFFF
  754. // Refin: true
  755. // Refout: true
  756. // Xorout: 0xFFFF
  757. //*************************************************************************
  758. /// <summary>
  759. /// CRC-16/X25
  760. /// </summary>
  761. /// <param name="buffer"></param>
  762. /// <param name="start"></param>
  763. /// <param name="len"></param>
  764. /// <returns></returns>
  765. public static byte[] Crc20(byte[] buffer, int start = 0, int len = 0)
  766. {
  767. if (buffer == null || buffer.Length == 0) return null;
  768. if (start < 0) return null;
  769. if (len == 0) len = buffer.Length - start;
  770. int length = start + len;
  771. if (length > buffer.Length) return null;
  772. ushort crc = 0xFFFF;// Initial value
  773. for (int i = start; i < length; i++)
  774. {
  775. crc ^= buffer[i];
  776. for (int j = 0; j < 8; j++)
  777. {
  778. if ((crc & 1) > 0)
  779. crc = (ushort)((crc >> 1) ^ 0x8408);// 0x8408 = reverse 0x1021
  780. else
  781. crc = (ushort)(crc >> 1);
  782. }
  783. }
  784. byte[] ret = BitConverter.GetBytes((ushort)~crc);
  785. Array.Reverse(ret);
  786. return ret;
  787. }
  788. // **********************************************************************
  789. // Name: CRC-16/XMODEM x16+x12+x5+1
  790. // Poly: 0x1021
  791. // Init: 0x0000
  792. // Refin: false
  793. // Refout: false
  794. // Xorout: 0x0000
  795. //*************************************************************************
  796. /// <summary>
  797. /// CRC-16/XMODEM
  798. /// </summary>
  799. /// <param name="buffer"></param>
  800. /// <param name="start"></param>
  801. /// <param name="len"></param>
  802. /// <returns></returns>
  803. public static byte[] Crc21(byte[] buffer, int start = 0, int len = 0)
  804. {
  805. if (buffer == null || buffer.Length == 0) return null;
  806. if (start < 0) return null;
  807. if (len == 0) len = buffer.Length - start;
  808. int length = start + len;
  809. if (length > buffer.Length) return null;
  810. ushort crc = 0;// Initial value
  811. for (int i = start; i < length; i++)
  812. {
  813. crc ^= (ushort)(buffer[i] << 8);
  814. for (int j = 0; j < 8; j++)
  815. {
  816. if ((crc & 0x8000) > 0)
  817. crc = (ushort)((crc << 1) ^ 0x1021);
  818. else
  819. crc = (ushort)(crc << 1);
  820. }
  821. }
  822. byte[] ret = BitConverter.GetBytes(crc);
  823. Array.Reverse(ret);
  824. return ret;
  825. }
  826. // **********************************************************************
  827. // Name: CRC32 x32+x26+x23+x22+x16+x12+x11+x10+x8+x7+x5+x4+x2+x+1
  828. // Poly: 0x04C11DB7
  829. // Init: 0xFFFFFFFF
  830. // Refin: true
  831. // Refout: true
  832. // Xorout: 0xFFFFFFFF
  833. //*************************************************************************
  834. /// <summary>
  835. /// CRC32
  836. /// </summary>
  837. /// <param name="buffer"></param>
  838. /// <param name="start"></param>
  839. /// <param name="len"></param>
  840. /// <returns></returns>
  841. public static byte[] Crc22(byte[] buffer, int start = 0, int len = 0)
  842. {
  843. if (buffer == null || buffer.Length == 0) return null;
  844. if (start < 0) return null;
  845. if (len == 0) len = buffer.Length - start;
  846. int length = start + len;
  847. if (length > buffer.Length) return null;
  848. uint crc = 0xFFFFFFFF;// Initial value
  849. for (int i = start; i < length; i++)
  850. {
  851. crc ^= buffer[i];
  852. for (int j = 0; j < 8; j++)
  853. {
  854. if ((crc & 1) > 0)
  855. crc = (crc >> 1) ^ 0xEDB88320;// 0xEDB88320= reverse 0x04C11DB7
  856. else
  857. crc = crc >> 1;
  858. }
  859. }
  860. byte[] ret = BitConverter.GetBytes(~crc);
  861. Array.Reverse(ret);
  862. return ret;
  863. }
  864. // **********************************************************************
  865. // Name: CRC32/MPEG-2 x32+x26+x23+x22+x16+x12+x11+x10+x8+x7+x5+x4+x2+x+1
  866. // Poly: 0x04C11DB7
  867. // Init: 0xFFFFFFFF
  868. // Refin: false
  869. // Refout: false
  870. // Xorout: 0x00000000
  871. //*************************************************************************
  872. /// <summary>
  873. /// CRC32/MPEG-2
  874. /// </summary>
  875. /// <param name="buffer"></param>
  876. /// <param name="start"></param>
  877. /// <param name="len"></param>
  878. /// <returns></returns>
  879. public static byte[] Crc23(byte[] buffer, int start = 0, int len = 0)
  880. {
  881. if (buffer == null || buffer.Length == 0) return null;
  882. if (start < 0) return null;
  883. if (len == 0) len = buffer.Length - start;
  884. int length = start + len;
  885. if (length > buffer.Length) return null;
  886. uint crc = 0xFFFFFFFF;// Initial value
  887. for (int i = start; i < length; i++)
  888. {
  889. crc ^= (uint)(buffer[i] << 24);
  890. for (int j = 0; j < 8; j++)
  891. {
  892. if ((crc & 0x80000000) > 0)
  893. crc = (crc << 1) ^ 0x04C11DB7;
  894. else
  895. crc = crc << 1;
  896. }
  897. }
  898. byte[] ret = BitConverter.GetBytes(crc);
  899. Array.Reverse(ret);
  900. return ret;
  901. }
  902. //***************************************************************
  903. /* 纵向冗余校验(Longitudinal Redundancy Check,简称:LRC)是通信中常用的一种校验形式,也称LRC校验或纵向校验。
  904. 它是一种从纵向通道上的特定比特串产生校验比特的错误检测方法。在行列格式中(如磁带),LRC经常是与VRC一起使用,这样就会
  905. 为每个字符校验码。在工业领域Modbus协议Ascii模式采用该算法。 LRC计算校验码,具体算法如下:*/
  906. // 1、对需要校验的数据(2n个字符)两两组成一个16进制的数值求和。
  907. // 2、将求和结果与256求模。
  908. // 3、用256减去所得模值得到校验结果(另一种方法:将模值按位取反然后加1)。
  909. /// <summary>
  910. /// LRC校验(纵向冗余校验)工业领域Modbus协议Ascii模式采用该算法
  911. /// </summary>
  912. /// <param name="buffer"></param>
  913. /// <param name="start"></param>
  914. /// <param name="len"></param>
  915. /// <returns></returns>
  916. public static byte[] Lrc(byte[] buffer, int start = 0, int len = 0)
  917. {
  918. if (buffer == null || buffer.Length == 0) return null;
  919. if (start < 0) return null;
  920. if (len == 0) len = buffer.Length - start;
  921. int length = start + len;
  922. if (length > buffer.Length) return null;
  923. byte lrc = 0;// Initial value
  924. for (int i = start; i < len; i++)
  925. {
  926. lrc += buffer[i];
  927. }
  928. lrc = (byte)((lrc ^ 0xFF) + 1);
  929. return new byte[] { lrc };
  930. }
  931. /// <summary>
  932. /// BCC(Block Check Character/信息组校验码),因校验码是将所有数据异或得出,故俗称异或校验。具体算法是:将每一个字节的数据(一般是两个16进制的字符)进行异或后即得到校验码。
  933. /// </summary>
  934. /// <param name="buffer"></param>
  935. /// <param name="start"></param>
  936. /// <param name="len"></param>
  937. /// <returns></returns>
  938. public static byte[] Bcc(byte[] buffer, int start = 0, int len = 0)
  939. {
  940. if (buffer == null || buffer.Length == 0) return null;
  941. if (start < 0) return null;
  942. if (len == 0) len = buffer.Length - start;
  943. int length = start + len;
  944. if (length > buffer.Length) return null;
  945. byte bcc = 0;// Initial value
  946. for (int i = start; i < len; i++)
  947. {
  948. bcc ^= buffer[i];
  949. }
  950. return new byte[] { bcc };
  951. }
  952. /// <summary>
  953. /// 检验和(checksum),在数据处理和数据通信领域中,用于校验目的地一组数据项的和。它通常是以十六进制为数制表示的形式。如果校验和的数值超过十六进制的FF,也就是255. 就要求其补码作为校验和。通常用来在通信中,尤其是远距离通信中保证数据的完整性和准确性。
  954. /// </summary>
  955. /// <param name="buffer"></param>
  956. /// <param name="start"></param>
  957. /// <param name="len"></param>
  958. /// <returns></returns>
  959. public static byte[] allAdd(byte[] buffer, int start = 0, int len = 0)
  960. {
  961. if (buffer == null || buffer.Length == 0) return null;
  962. if (start < 0) return null;
  963. if (len == 0) len = buffer.Length - start;
  964. int length = start + len;
  965. if (length > buffer.Length) return null;
  966. byte bcc = 0;// Initial value
  967. for (int i = start; i < len; i++)
  968. {
  969. bcc ^= buffer[i];
  970. }
  971. return new byte[] { bcc };
  972. }
  973. }
  1. /// <summary>
  2. /// 操作功能码
  3. /// </summary>
  4. public enum FunctionCode
  5. {
  6. /// <summary>
  7. /// 读写从站输出线圈状态0XXXX状态
  8. /// </summary>
  9. ReadCoils = 1,
  10. /// <summary>
  11. /// 只读从站输入线圈状态1XXXX状态
  12. /// </summary>
  13. ReadDiscreteInputs = 2,
  14. /// <summary>
  15. /// 读写从站保存寄存器4XXXX状态
  16. /// </summary>
  17. ReadHoldingRegisters = 3,
  18. /// <summary>
  19. /// 只读从站输入寄存器3XXXX值
  20. /// </summary>
  21. ReadInputRegisters = 4,
  22. /// <summary>
  23. /// 强制从站单个线圈状态
  24. /// </summary>
  25. WriteSingleCoil = 5,
  26. /// <summary>
  27. /// 向从站单个寄存器写入值
  28. /// </summary>
  29. WriteSingleRegister = 6,
  30. /// <summary>
  31. /// 强制从站多个线圈状态
  32. /// </summary>
  33. WriteMultipleCoils = 15,
  34. /// <summary>
  35. /// 向从站多个寄存器写入值
  36. /// </summary>
  37. WriteMultipleRegisters = 16,
  38. ReportSlaveID = 17,
  39. MaskWriteRegister = 22,
  40. ReadWriteRegister = 23
  41. }
  42. /// <summary>
  43. /// 从站返回异常代码
  44. /// </summary>
  45. public enum ErrorCode
  46. {
  47. /// <summary>
  48. /// 非法功能
  49. /// </summary>
  50. IllegalFunction,
  51. /// <summary>
  52. /// 非法数据地址
  53. /// </summary>
  54. IllegalDataAddress,
  55. /// <summary>
  56. /// 非法数据
  57. /// </summary>
  58. IllegalData,
  59. /// <summary>
  60. /// 相关设备故障
  61. /// </summary>
  62. RelatedEquipmentFailure,
  63. /// <summary>
  64. /// 确认
  65. /// </summary>
  66. Verify,
  67. /// <summary>
  68. /// 忙碌、拒绝执行
  69. /// </summary>
  70. Busy,
  71. }

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代码部分
  1. public partial class Form1 : Form
  2. {
  3. ModbusTCP modbus;
  4. int slaveStation = 10;
  5. Timer timer = new Timer();
  6. public Form1()
  7. {
  8. InitializeComponent();
  9. UIControlStatusChange(false);
  10. Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
  11. modbus = new ModbusTCP();
  12. modbus.ErrorMessage += Modbus_ErrorMessage;
  13. timer.Interval = 1000;
  14. foreach (Control item in groupBox1.Controls)
  15. {
  16. if (item is GroupBox)
  17. {
  18. foreach (Control c in item.Controls)
  19. {
  20. if (c is Button)
  21. {
  22. (c as Button).Click += Button_Click;
  23. }
  24. }
  25. }
  26. }
  27. }
  28. private void Button_Click(object sender, EventArgs e)
  29. {
  30. lblErrorMsg.Text = "";
  31. }
  32. private void Modbus_ErrorMessage(int arg1, string arg2)
  33. {
  34. this.Invoke(new Action(() =>
  35. {
  36. lblErrorMsg.Text = $"从站[{arg1}]出现异常,原因:{arg2}";
  37. }));
  38. }
  39. /// <summary>
  40. /// 变更UI控件状态
  41. /// </summary>
  42. /// <param name="status">True:已连接,False:连接断开</param>
  43. void UIControlStatusChange(bool status)
  44. {
  45. if (status)
  46. {
  47. txtIP.Enabled = false;
  48. txtPort.Enabled = false;
  49. txtSlave.Enabled = false;
  50. btnConnect.BackColor = Color.LightGreen;
  51. btnConnect.Text = "断开连接";
  52. groupBox1.Enabled = true;
  53. }
  54. else
  55. {
  56. txtIP.Enabled = true;
  57. txtPort.Enabled = true;
  58. txtSlave.Enabled = true;
  59. btnConnect.BackColor = Color.Red;
  60. btnConnect.Text = "连接";
  61. groupBox1.Enabled = false;
  62. }
  63. }
  64. private void btnConnect_Click(object sender, EventArgs e)
  65. {
  66. if (btnConnect.Text.Equals("连接"))
  67. {
  68. if (modbus.IsConnected)
  69. {
  70. modbus.Disconnect();
  71. }
  72. IPEndPoint endPoint = new IPEndPoint(IPAddress.Parse(txtIP.Text.Trim()), int.Parse(txtPort.Text.Trim()));
  73. modbus.RemoteEndPoint = endPoint;
  74. modbus.Connect();
  75. }
  76. else
  77. {
  78. if (modbus.IsConnected)
  79. {
  80. modbus.Disconnect();
  81. }
  82. }
  83. UIControlStatusChange(modbus.IsConnected);
  84. }
  85. //读取多个线圈
  86. private void btnReadCoils_Click(object sender, EventArgs e)
  87. {
  88. int address; int len;
  89. if (!int.TryParse(txtCoilsAddress.Text.Trim(), out address))
  90. {
  91. txtCoilsAddress.Focus();
  92. MessageBox.Show("线圈地址错误!");
  93. return;
  94. }
  95. if (!int.TryParse(txtCoilsCount.Text.Trim(), out len))
  96. {
  97. txtCoilsCount.Focus();
  98. MessageBox.Show("线圈数量只能为正整数!");
  99. return;
  100. }
  101. bool[] reuslt = modbus.ReadCoils(slaveStation, address, len);
  102. if (reuslt == null) return;
  103. StringBuilder sb = new StringBuilder();
  104. foreach (var item in reuslt)
  105. {
  106. if (item)
  107. {
  108. sb.Append(" 1");
  109. }
  110. else
  111. {
  112. sb.Append(" 0");
  113. }
  114. }
  115. lblCoilsResult.Text = sb.ToString();
  116. }
  117. private void txtSlave_TextChanged(object sender, EventArgs e)
  118. {
  119. if (!int.TryParse(txtSlave.Text.Trim(), out slaveStation))
  120. {
  121. slaveStation = 10;
  122. }
  123. }
  124. private void btnForced_Click(object sender, EventArgs e)
  125. {
  126. int address;
  127. if (!int.TryParse(txtCoilsWriteAdress.Text.Trim(), out address))
  128. {
  129. txtCoilsWriteAdress.Focus();
  130. MessageBox.Show("线圈写入地址错误!");
  131. return;
  132. }
  133. if (btnForced.Text.Equals("ON"))
  134. {
  135. bool result = modbus.ForcedCoil(slaveStation, address, false);
  136. if (result)
  137. {
  138. btnForced.Text = "OFF";
  139. btnForced.BackColor = Color.Red;
  140. }
  141. }
  142. else
  143. {
  144. bool result = modbus.ForcedCoil(slaveStation, address, true);
  145. if (result)
  146. {
  147. btnForced.Text = "ON";
  148. btnForced.BackColor = Color.LightGreen;
  149. }
  150. }
  151. }
  152. private void btnRegistersRead_Click(object sender, EventArgs e)
  153. {
  154. int address; int len;
  155. if (!int.TryParse(txtRegistersAdress.Text.Trim(), out address))
  156. {
  157. txtRegistersAdress.Focus();
  158. MessageBox.Show("寄存器地址错误!");
  159. return;
  160. }
  161. if (!int.TryParse(txtRegistersCount.Text.Trim(), out len))
  162. {
  163. txtRegistersCount.Focus();
  164. MessageBox.Show("寄存器数量只能为正整数!");
  165. return;
  166. }
  167. short[] result = modbus.ReadHoldingRegisters(slaveStation, address, len);
  168. if (result == null) return;
  169. StringBuilder sb = new StringBuilder();
  170. foreach (var item in result)
  171. {
  172. sb.Append(" " + item.ToString());
  173. }
  174. lblRegistersResult.Text = sb.ToString();
  175. }
  176. private void btnReadFloat_Click(object sender, EventArgs e)
  177. {
  178. int address; int len;
  179. if (!int.TryParse(txtRegistersAdress.Text.Trim(), out address))
  180. {
  181. txtRegistersAdress.Focus();
  182. MessageBox.Show("寄存器地址错误!");
  183. return;
  184. }
  185. if (!int.TryParse(txtRegistersCount.Text.Trim(), out len))
  186. {
  187. txtRegistersCount.Focus();
  188. MessageBox.Show("寄存器数量只能为正整数!");
  189. return;
  190. }
  191. float[] result = modbus.ReadFloatRegisters(slaveStation, address, len);
  192. if (result == null) return;
  193. StringBuilder sb = new StringBuilder();
  194. foreach (var item in result)
  195. {
  196. sb.Append(" " + item.ToString());
  197. }
  198. lblRegistersResult.Text = sb.ToString();
  199. }
  200. private void btnWriteShort_Click(object sender, EventArgs e)
  201. {
  202. int address; short val;
  203. if (!int.TryParse(txtWriteShortAdress.Text.Trim(), out address))
  204. {
  205. txtWriteShortAdress.Focus();
  206. MessageBox.Show("寄存器地址错误!");
  207. return;
  208. }
  209. if (!short.TryParse(txtWriteShortVal.Text.Trim(), out val))
  210. {
  211. txtWriteShortVal.Focus();
  212. MessageBox.Show("写入的值只能为正整数!");
  213. return;
  214. }
  215. if (modbus.WriteSingleRegister(slaveStation, address, val))
  216. {
  217. MessageBox.Show("Short数已成功写入");
  218. }
  219. else
  220. {
  221. MessageBox.Show("Short数写入失败");
  222. }
  223. }
  224. private void btnWriteFloat_Click(object sender, EventArgs e)
  225. {
  226. int address; float val;
  227. if (!int.TryParse(txtFloatWriteAddress.Text.Trim(), out address))
  228. {
  229. txtFloatWriteAddress.Focus();
  230. MessageBox.Show("寄存器地址错误!");
  231. return;
  232. }
  233. if (!float.TryParse(txtWriteFloatVal.Text.Trim(), out val))
  234. {
  235. txtWriteFloatVal.Focus();
  236. MessageBox.Show("写入的值只能为正整数!");
  237. return;
  238. }
  239. if (modbus.WriteFloatRegister(slaveStation, address, val))
  240. {
  241. MessageBox.Show("Float数已成功写入");
  242. }
  243. else
  244. {
  245. MessageBox.Show("Float数写入失败");
  246. }
  247. }
  248. //给线圈批量赋值
  249. private void btnBatch_Click(object sender, EventArgs e)
  250. {
  251. int address; short val;
  252. if (!int.TryParse(txtCoils.Text.Trim(), out address))
  253. {
  254. txtCoils.Focus();
  255. MessageBox.Show("线圈地址错误!");
  256. return;
  257. }
  258. if (!short.TryParse(txtCoilsVal.Text.Trim(), out val))
  259. {
  260. txtCoilsVal.Focus();
  261. MessageBox.Show("写入的值只能为正整数!");
  262. return;
  263. }
  264. bool result = modbus.WriteMultipleCoils(slaveStation, address, 16, new short[] { val });
  265. if (result)
  266. {
  267. MessageBox.Show("线圈已批量写入");
  268. }
  269. else
  270. {
  271. MessageBox.Show("线圈批量失败");
  272. }
  273. }
  274. private void btnMulShortWrite_Click(object sender, EventArgs e)
  275. {
  276. int address; short val;int len;
  277. if (!int.TryParse(txtMulAdress.Text.Trim(), out address))
  278. {
  279. txtMulAdress.Focus();
  280. MessageBox.Show("寄存器地址错误!");
  281. return;
  282. }
  283. if (!short.TryParse(txtMulShortVal.Text.Trim(), out val))
  284. {
  285. txtMulShortVal.Focus();
  286. MessageBox.Show("写入的值只能为正整数!");
  287. return;
  288. }
  289. if (!int.TryParse(txtMulCount.Text.Trim(), out len))
  290. {
  291. txtMulCount.Focus();
  292. MessageBox.Show("寄存器数量只能为正整数!");
  293. return;
  294. }
  295. if (modbus.WriteMultipleRegisters(slaveStation, address, len, Enumerable.Repeat(val, len).ToArray()))
  296. {
  297. MessageBox.Show("寄存器已批量写入");
  298. }
  299. else
  300. {
  301. MessageBox.Show("寄存器批量写入失败");
  302. }
  303. }
  304. }

4.2.4,ModbusTCP部分

  1. public class ModbusTCP : IDisposable
  2. {
  3. #region 说明
  4. //---------------------------------------------------
  5. //前8个字节的规律是一模一样了,都是标识号+modbus号+长度+站号,后面基本是跟地址和长度,或是直接是地址和数据。
  6. //---------------------------------------------------
  7. //功能码0x01:读线圈
  8. //发送的数据--------------------------------------------
  9. //根据协议指定,需要填写长度为12的byte数组
  10. //byte[0] byte[1] byte[2] byte[3] byte[4] byte[5] byte[6] byte[7] byte[8] byte[9] byte[10] byte[11]
  11. //byte[0] byte[1]: 消息号---------随便指定,服务器返回的数据的前两个字和这个一样
  12. //byte[2] byte[3]:modbus标识,强制为0即可
  13. //byte[4] byte[5]:指示排在byte[5] 后面所有字节的个数,也就是总长度-6
  14. //byte[6]:站号,对于TCP协议来说,某些情况不重要,可以随便指定,对于rtu及ascii来说,就需要选择设备的站号信息。
  15. //byte[7]:功能码,这里就需要填入我们的真正的想法了
  16. //byte[8] byte[9]:起始地址,比如我们想读取地址0的数据,就填 00 00 ,如果我们想读取地址1000的数据,怎么办,填入 03 E8 ,也就是将1000转化十六进制填进去。
  17. //byte[10] byte[11]:指定想读取的数据长度,比如我们就想读取地址0的一个数据,这里就写 00 01,如果我们想读取地址0-999共计一个数据的长度,就写 03 E8。和起始地址是一样的。
  18. //有了上面的格式之后,如下
  19. //00 00 00 00 00 06 FF 01 00 00 00 01
  20. //表示:消息号设为0,站号FF,功能码01,地址01,长度01
  21. //响应的数据--------------------------------------------
  22. //00 00 00 00 00 04 FF 01 01 00 共计10个字节的数据。
  23. //byte[0] byte[1] :消息号,发送指令是多少,这里就是多少。
  24. //byte[2] byte[3]:必须都为0,代表这是modbus 通信
  25. //byte[4] byte[5]:指示byte[5] 后面的所有字节数,这里是00 04,如果后面共有100个,那么这里就是 00 64
  26. //byte[6]:站号,发送为FF,那么这里也就是FF
  27. //byte[7]:功能码,发送指令为01的功能码,这里也是01,和发送的指令是一致的
  28. //byte[8]:指示byte[8] 后面跟随的字节数量,因为跟在byte[8] 后面的就是真实的数据,结果就在byte[8] 后面
  29. //byte[9]:真实的数据,一个byte有8位,这里只读取了一个位数据,所有这里的有效值只是byte[9] 的最低位,二进制为 0000 0000 我们看到最低位为0,所以最终我们读取的地址0的线圈为断。
  30. // 功能码0x05:写单个线圈
  31. //如果指定地址0的线圈为通:00 00 00 00 00 06 FF 05 00 00 FF 00
  32. // 前面的含义都是一致的,不同的是: 05 00 00 FF 00
  33. //05 是功能码, 00 00 是指定的地址,如果想地址1000为通,那么就为 03 E8,规定数据 FF 00 线圈通,00 00线圈断,其他任何的值都对结果无效。
  34. //响应数据: 00 00 00 00 00 06 FF 05 00 00 FF 00 因执行写入的操作,是不带读取数据,所以服务器会直接复制一遍你的指令并返回。
  35. //下面再举例一些方便理解(我们只需要指定地址及是否通断的情况即可):
  36. //写入地址100为通: 00 00 00 00 00 06 FF 05 00 64 FF 00
  37. //写入地址1000为断:00 00 00 00 00 06 FF 05 03 E8 00 00
  38. // 功能码0x0F:写入多个线圈
  39. // 00 00 00 00 00 84 FF 0F 00 00 03 E8 7D ...(后面跟125个byte,都是00)
  40. //00 00 :消息标识号,任意数字,服务器回复相同数字。
  41. //00 00 :modbus标志号,规定为00 。
  42. //00 84 :0x0084转化十进制就是132,也就是说,00 84(不包含00 84)后面跟了132个字节
  43. //FF :站号
  44. //0F :功能码
  45. //00 00:起始地址,此处就是0,如果起始地址为100,那么就写00 64,如果起始地址为1000,那么就写03 E8
  46. //03 E8: 我们需要写的数据长度,如果需要写1000个线圈,就是03 E8,如果写999个线圈,那么就是03 E7。
  47. //7D : 这个字节代表后面跟随的真实写入的数据的长度,为125个字节。
  48. //响应的数据:
  49. //00 00 00 00 00 06 FF 0F 00 00 03 E8
  50. // 功能码0x03:读取寄存器的值
  51. //该功能码实现寄存器的数据读取,我们需要知道的是,一个寄存器占2个byte,而且是高位在前,地位在后,那么如果寄存器0的数据为1000,那么我们读取到的数据就是03 E8,这是我们最终想要的东西,03功能码和01功能码很接近,就是功能码替换一下,返回的数据解析不一样而已,比如我们需要读取地址0的寄存器数据:
  52. //00 00 00 00 00 06 FF 03 00 00 00 01
  53. //功能码03,读取寄存器,地址0,长度1 返回如下:
  54. //00 00 00 00 00 05 FF 03 02 03 E8
  55. //02 03 E8:02代表后面的字节长度是2个字节,03 E8是真实的数据了,代表了寄存器0存储了1000这个数据。
  56. #endregion
  57. Socket socketObject = null;
  58. /// <summary>
  59. /// 目标站点号
  60. /// </summary>
  61. int targetStation = 0;
  62. /// <summary>
  63. /// 消息接收到标志位
  64. /// </summary>
  65. bool msgRecFlag = false;
  66. /// <summary>
  67. /// 接收到有效的数据
  68. /// </summary>
  69. byte[] RecBytes = null;
  70. AutoResetEvent slim = new AutoResetEvent(true);
  71. /// <summary>
  72. /// 返回的异常信息
  73. /// </summary>
  74. public event Action<int, string> ErrorMessage;
  75. /// <summary>
  76. /// 消息号
  77. /// </summary>
  78. int MsgId = 0;
  79. CancellationTokenSource source;
  80. public ModbusTCP()
  81. {
  82. }
  83. EndPoint remoteEndPoint;
  84. /// <summary>
  85. /// 远程终结点
  86. /// </summary>
  87. public EndPoint RemoteEndPoint
  88. {
  89. get
  90. {
  91. remoteEndPoint = socketObject.RemoteEndPoint;
  92. return remoteEndPoint;
  93. }
  94. set
  95. {
  96. remoteEndPoint = value;
  97. }
  98. }
  99. /// <summary>
  100. /// 本地终结点
  101. /// </summary>
  102. public EndPoint LocalEndPoint
  103. {
  104. get
  105. {
  106. return socketObject.LocalEndPoint;
  107. }
  108. }
  109. /// <summary>
  110. /// 当前的Socket对象
  111. /// </summary>
  112. public Socket SocketObject
  113. {
  114. get
  115. {
  116. return socketObject;
  117. }
  118. }
  119. bool isConnected = false;
  120. public bool IsConnected
  121. {
  122. get
  123. {
  124. if (socketObject==null || !socketObject.Connected)
  125. {
  126. isConnected = false;
  127. }
  128. return isConnected;
  129. }
  130. }
  131. /// <summary>
  132. /// 使用RemoteEndPoint属性值建立与远程主机的连接
  133. /// </summary>
  134. /// <param name="serialPortName"></param>
  135. public void Connect()
  136. {
  137. if (socketObject != null)
  138. {
  139. Disconnect();
  140. }
  141. socketObject = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
  142. socketObject.Connect(remoteEndPoint);
  143. isConnected = socketObject.Connected;
  144. source = new CancellationTokenSource();
  145. if (IsConnected)
  146. {
  147. DataReceivedTask();
  148. }
  149. }
  150. /// <summary>
  151. /// 建立与远程主机的连接
  152. /// </summary>
  153. /// <param name="address">远程主机IP</param>
  154. /// <param name="port">远程主机端口号</param>
  155. public void Connect(string address, int port)
  156. {
  157. if (socketObject != null)
  158. {
  159. Disconnect();
  160. }
  161. socketObject = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
  162. socketObject.Connect(IPAddress.Parse(address), port);
  163. isConnected = socketObject.Connected;
  164. source = new CancellationTokenSource();
  165. if (IsConnected)
  166. {
  167. DataReceivedTask();
  168. }
  169. }
  170. /// <summary>
  171. /// 关闭连接
  172. /// </summary>
  173. /// <param name="reuseSocket">如果关闭当前连接后可以重用此套接字,则为 true;否则为 false。</param>
  174. public void Disconnect()
  175. {
  176. if (socketObject != null && IsConnected)
  177. {
  178. socketObject.Shutdown(SocketShutdown.Both);
  179. socketObject.Disconnect(false);
  180. socketObject.Close();
  181. socketObject.Dispose();
  182. socketObject = null;
  183. }
  184. isConnected = false;
  185. source.Cancel();
  186. }
  187. /// <summary>
  188. /// 接收数据任务
  189. /// </summary>
  190. async void DataReceivedTask()
  191. {
  192. try
  193. {
  194. await Task.Run(() =>
  195. {
  196. while (true)
  197. {
  198. if (source.Token.IsCancellationRequested)
  199. {
  200. source.Token.ThrowIfCancellationRequested();
  201. }
  202. // int num = socketObject.Available;
  203. // if (num == 0) return;
  204. byte[] data = new byte[10 * 1024 * 1024];
  205. byte[] bytes;
  206. try
  207. {
  208. int len = socketObject.Receive(data);
  209. if (len == 0)
  210. {
  211. isConnected = false;
  212. return;
  213. }
  214. bytes = new byte[len];
  215. Array.Copy(data, bytes, len);
  216. }
  217. catch (Exception)
  218. {
  219. isConnected = false;
  220. return;
  221. }
  222. //对消息进行解析
  223. //检查报文是否正确
  224. msgRecFlag = true;
  225. //ModbusTCP无校验位
  226. //获取salve Station No
  227. byte stationNo = bytes[6];
  228. targetStation = stationNo;
  229. //功能码
  230. byte commandCode = bytes[7];
  231. if (commandCode > 80)
  232. {
  233. //异常信息
  234. ErrorCode errorCode = (ErrorCode)bytes[8];
  235. ErrorMessage?.BeginInvoke(stationNo, errorCode.ToString(),null,null);
  236. }
  237. else
  238. {
  239. FunctionCode function = (FunctionCode)commandCode;
  240. //有效数据占用的字节数
  241. byte length = bytes[8];
  242. switch (function)
  243. {
  244. case FunctionCode.ReadCoils:
  245. case FunctionCode.ReadDiscreteInputs:
  246. //返回字节长度
  247. RecBytes = new byte[length];
  248. Array.Copy(bytes, 9, RecBytes, 0, length);
  249. break;
  250. case FunctionCode.ReadHoldingRegisters:
  251. case FunctionCode.ReadInputRegisters:
  252. //读取寄存器数据
  253. RecBytes = new byte[length];
  254. Array.Copy(bytes, 9, RecBytes, 0, length);
  255. break;
  256. case FunctionCode.WriteSingleCoil:
  257. case FunctionCode.WriteSingleRegister:
  258. case FunctionCode.WriteMultipleCoils:
  259. case FunctionCode.WriteMultipleRegisters:
  260. RecBytes = new byte[4];
  261. Array.Copy(bytes, 8, RecBytes, 0, RecBytes.Length);
  262. break;
  263. case FunctionCode.ReportSlaveID:
  264. break;
  265. case FunctionCode.MaskWriteRegister:
  266. break;
  267. case FunctionCode.ReadWriteRegister:
  268. break;
  269. default:
  270. break;
  271. }
  272. }
  273. //接收完成,取消主线程阻塞
  274. slim.Set();
  275. }
  276. });
  277. }
  278. catch (OperationCanceledException ex)
  279. {
  280. socketObject.Disconnect(true);
  281. }
  282. catch (Exception ex)
  283. {
  284. ErrorMessage?.Invoke(targetStation, "出现异常,原因:" + ex.Message);
  285. }
  286. }
  287. /// <summary>
  288. ///发送报文
  289. /// </summary>
  290. /// <param name="bytes"></param>
  291. /// <param name="protocol">报文协议</param>
  292. void Send(string sendMsg)
  293. {
  294. //对消息进行判断是否合规
  295. if (!Regex.IsMatch(sendMsg, @"^[0-9a-fA-F]+$"))
  296. {
  297. throw new Exception("报文错误,存在非16进制字符");
  298. }
  299. byte[] bytes = new byte[sendMsg.Length / 2];
  300. for (int i = 0; i < sendMsg.Length / 2; i++)
  301. {
  302. bytes[i] = Convert.ToByte(sendMsg.Substring(i * 2, 2), 16);
  303. }
  304. //ModbusTCP不需要添加校验码
  305. // byte[] checkSum = Checkhelper.CrcModbus(bytes, 0, bytes.Length);
  306. // var checkarr = checkSum.Reverse().ToArray();
  307. // byte[] msgBytes = new byte[bytes.Length + checkSum.Length];
  308. byte[] msgBytes = new byte[bytes.Length];
  309. Array.Copy(bytes, msgBytes, bytes.Length);
  310. // Array.Copy(checkarr, 0, msgBytes, bytes.Length, checkarr.Length);
  311. socketObject.Send(msgBytes, 0, msgBytes.Length, SocketFlags.None);
  312. }
  313. /// <summary>
  314. /// 编制报文
  315. /// </summary>
  316. /// <param name="slaveStation">从站站号</param>
  317. /// <param name="function">功能代码</param>
  318. /// <param name="devAdress">起始地址</param>
  319. /// <param name="devLenght">数量</param>
  320. /// <param name="values">写入的值</param>
  321. /// /// <param name="fromBase">如果写入的数值类型所占用的byte,例如short类型为2,int类型为4,flost类型为4,double类型为8</param>
  322. /// <param name="set">强制线圈通断,true:ON ;false:OFF</param>
  323. void ComposeMessage(int slaveStation, FunctionCode function, int devAdress, int devLenght = 1, byte[] values = null, int fromBase = 2, bool set = false)
  324. {
  325. lock (this)
  326. {
  327. slim.Reset();
  328. StringBuilder sb = new StringBuilder();
  329. int afterBytesCount = 0;
  330. sb.Append(devAdress.ToString("X4"));
  331. switch (function)
  332. {
  333. case FunctionCode.ReadCoils:
  334. case FunctionCode.ReadDiscreteInputs:
  335. case FunctionCode.ReadHoldingRegisters:
  336. case FunctionCode.ReadInputRegisters:
  337. //功能码0x01:读线圈
  338. //发送的数据--------------------------------------------
  339. //根据协议指定,需要填写长度为12的byte数组
  340. //byte[0] byte[1] byte[2] byte[3] byte[4] byte[5] byte[6] byte[7] byte[8] byte[9] byte[10] byte[11]
  341. //byte[0] byte[1]: 消息号---------随便指定,服务器返回的数据的前两个字和这个一样
  342. //byte[2] byte[3]:modbus标识,强制为0即可
  343. //byte[4] byte[5]:指示排在byte[5] 后面所有字节的个数,也就是总长度-6
  344. //byte[6]:站号,对于TCP协议来说,某些情况不重要,可以随便指定,对于rtu及ascii来说,就需要选择设备的站号信息。
  345. //byte[7]:功能码,这里就需要填入我们的真正的想法了
  346. //byte[8] byte[9]:起始地址,比如我们想读取地址0的数据,就填 00 00 ,如果我们想读取地址1000的数据,怎么办,填入 03 E8 ,也就是将1000转化十六进制填进去。
  347. //byte[10] byte[11]:指定想读取的数据长度,比如我们就想读取地址0的一个数据,这里就写 00 01,如果我们想读取地址0-999共计一个数据的长度,就写 03 E8。和起始地址是一样的。
  348. //有了上面的格式之后,如下
  349. //00 00 00 00 00 06 FF 01 00 00 00 01
  350. //表示:消息号设为0,站号FF,功能码01,地址01,长度01
  351. afterBytesCount = 6;
  352. sb.Append(devLenght.ToString("X4"));
  353. break;
  354. case FunctionCode.WriteSingleCoil:
  355. // 功能码0x05:写单个线圈
  356. //如果指定地址0的线圈为通:00 00 00 00 00 06 FF 05 00 00 FF 00
  357. // 前面的含义都是一致的,不同的是: 05 00 00 FF 00
  358. //05 是功能码, 00 00 是指定的地址,如果想地址1000为通,那么就为 03 E8,规定数据 FF 00 线圈通,00 00线圈断,其他任何的值都对结果无效。
  359. //响应数据: 00 00 00 00 00 06 FF 05 00 00 FF 00 因执行写入的操作,是不带读取数据,所以服务器会直接复制一遍你的指令并返回。
  360. //下面再举例一些方便理解(我们只需要指定地址及是否通断的情况即可):
  361. //写入地址100为通: 00 00 00 00 00 06 FF 05 00 64 FF 00
  362. //写入地址1000为断:00 00 00 00 00 06 FF 05 03 E8 00 00
  363. afterBytesCount = 6;
  364. if (set)
  365. {
  366. //线圈置ON
  367. sb.Append("FF00");
  368. }
  369. else
  370. {
  371. //线圈置OFF
  372. sb.Append("0000");
  373. }
  374. break;
  375. case FunctionCode.WriteSingleRegister:
  376. //向单个寄存器写入值,占用两个byte
  377. afterBytesCount = 6;
  378. int curVal = BitConverter.ToInt16(values, 0);
  379. sb.Append(curVal.ToString("X4"));
  380. break;
  381. case FunctionCode.WriteMultipleCoils:
  382. // 功能码0x0F:写入多个线圈
  383. // 00 00 00 00 00 84 FF 0F 00 00 03 E8 7D ...(后面跟125个byte,都是00)
  384. //00 00 :消息标识号,任意数字,服务器回复相同数字。
  385. //00 00 :modbus标志号,规定为00 。
  386. //00 84 :0x0084转化十进制就是132,也就是说,00 84(不包含00 84)后面跟了132个字节
  387. //FF :站号
  388. //0F :功能码
  389. //00 00:起始地址,此处就是0,如果起始地址为100,那么就写00 64,如果起始地址为1000,那么就写03 E8
  390. //03 E8: 我们需要写的数据长度,如果需要写1000个线圈,就是03 E8,如果写999个线圈,那么就是03 E7。
  391. //7D : 这个字节代表后面跟随的真实写入的数据的长度,为125个字节。
  392. int len = (int)Math.Ceiling(devLenght * 1.0 / 8);
  393. afterBytesCount = 7 + len;
  394. sb.Append(devLenght.ToString("X4"));
  395. sb.Append(len.ToString("X2"));
  396. foreach (var item in values)
  397. {
  398. sb.Append(item.ToString("X2"));
  399. }
  400. break;
  401. case FunctionCode.WriteMultipleRegisters:
  402. //向多个寄存器写入值
  403. //1,需操作的寄存器数量,数值占用两个byte
  404. afterBytesCount = 7 + values.Length;
  405. //2,寄存器数量
  406. sb.Append((values.Length*2 / fromBase ).ToString("X4"));
  407. //2,需要操作的寄存器占用的字节数,数值占用1个byte
  408. sb.Append((values.Length).ToString("X2"));
  409. //3,写入寄存器的值,根据frombase判定值
  410. //此时姑且以占2个Byte计算
  411. for (int i = 0; i < values.Length; i++)
  412. {
  413. sb.Append(values[i].ToString("X2"));
  414. }
  415. break;
  416. case FunctionCode.ReportSlaveID:
  417. break;
  418. case FunctionCode.MaskWriteRegister:
  419. break;
  420. case FunctionCode.ReadWriteRegister:
  421. break;
  422. default:
  423. break;
  424. }
  425. //---------------------------------------------------
  426. //前8个字节的规律是一模一样了,都是:消息ID+modbus号+长度+站号,后面基本是跟地址和长度,或是直接是地址和数据。
  427. //byte[0] byte[1]: 消息号---------随便指定,服务器返回的数据的前两个字和这个一样
  428. //byte[2] byte[3]:modbus标识,强制为0即可
  429. //byte[4] byte[5]:指示排在byte[5] 后面所有字节的个数,也就是总长度
  430. //byte[6]:站号,对于TCP协议来说,某些情况不重要,可以随便指定,对于rtu及ascii来说,就需要选择设备的站号信息。
  431. //byte[7]:功能码,这里就需要填入我们的真正的想法了
  432. //---------------------------------------------------
  433. //添加从站点,功能代码,起始位置
  434. if (MsgId > 1000) MsgId = 0;
  435. MsgId++;
  436. string msg = $"{MsgId.ToString("X4")}0000{afterBytesCount.ToString("X4")}{ slaveStation.ToString("X2")}{((int)function).ToString("X2")}";
  437. msg += sb;
  438. RecBytes = null;
  439. Send(msg.Trim());
  440. //100ms等待消息回复
  441. msgRecFlag = slim.WaitOne(3000);
  442. System.Threading.Thread.Sleep(20);
  443. if (!msgRecFlag)
  444. {
  445. ErrorMessage?.Invoke(slaveStation, $"从站:{slaveStation},回复超时");
  446. }
  447. }
  448. }
  449. /// <summary>
  450. /// 强制指定单个线圈动作
  451. /// </summary>
  452. /// <param name="slaveStationNo">从站站号</param>
  453. /// <param name="devAdress">线圈地址</param>
  454. /// <param name="onOrOff">True为ON,false为Off</param>
  455. /// <returns></returns>
  456. public bool ForcedCoil(int slaveStationNo, int devAdress, bool set)
  457. {
  458. ComposeMessage(slaveStationNo, FunctionCode.WriteSingleCoil, devAdress, set: set);
  459. if (slaveStationNo == targetStation && msgRecFlag && RecBytes != null)
  460. {
  461. if (RecBytes[2] == 0xff && RecBytes[3] == 0 && set)
  462. {
  463. return true;
  464. }
  465. if (RecBytes[3] == 0 && RecBytes[2] == 0 && !set)
  466. {
  467. return true;
  468. }
  469. }
  470. return false;
  471. }
  472. /// <summary>
  473. /// 读取多个线圈状态
  474. /// </summary>
  475. /// <param name="slaveStationNo"></param>
  476. /// <param name="devAdress"></param>
  477. /// <param name="iLenght"></param>
  478. public bool[] ReadCoils(int slaveStationNo, int devAdress, int iLenght)
  479. {
  480. ComposeMessage(slaveStationNo, FunctionCode.ReadCoils, devAdress, iLenght);
  481. if (slaveStationNo == targetStation && msgRecFlag && RecBytes != null)
  482. {
  483. BitArray array = new BitArray(RecBytes);
  484. if (iLenght > array.Count)
  485. {
  486. throw new Exception("线圈查询结果异常!");
  487. }
  488. bool[] coils = new bool[iLenght];
  489. for (int i = 0; i < iLenght; i++)
  490. {
  491. coils[i] = array[i];
  492. }
  493. return coils;
  494. }
  495. else
  496. {
  497. return null;
  498. }
  499. }
  500. /// <summary>
  501. /// 将short值写入到多个线圈
  502. /// </summary>
  503. /// <param name="slaveStationNo">从站站号</param>
  504. /// <param name="devAdress"></param>
  505. /// <param name="iLenght">地址长度</param>
  506. /// <param name="arr">写入的值</param>
  507. public bool WriteMultipleCoils(int slaveStationNo, int devAdress, int iLenght, short[] arr)
  508. {
  509. if ((int)Math.Ceiling(iLenght * 1.0 / 16) != arr.Length)
  510. {
  511. throw new Exception("线圈数量与所赋值数量不匹配");
  512. }
  513. byte[] bytes = new byte[arr.Length * 2];
  514. int index = 0;
  515. foreach (var item in arr)
  516. {
  517. byte[] temBytes = BitConverter.GetBytes(item);
  518. Array.Copy(temBytes, 0, bytes, index, temBytes.Length);
  519. index += temBytes.Length;
  520. }
  521. ComposeMessage(slaveStationNo, FunctionCode.WriteMultipleCoils, devAdress, iLenght, bytes);
  522. if (slaveStationNo == targetStation && msgRecFlag && RecBytes != null && RecBytes.Length == 4)
  523. {
  524. int startAddress = BitConverter.ToInt16(new byte[] { RecBytes[1], RecBytes[0] }, 0);
  525. int addressLen = BitConverter.ToInt16(new byte[] { RecBytes[3], RecBytes[2] }, 0);
  526. if (startAddress == devAdress && addressLen == iLenght)
  527. {
  528. return true;
  529. }
  530. else
  531. {
  532. return false;
  533. }
  534. }
  535. else
  536. {
  537. return false;
  538. }
  539. }
  540. /// <summary>
  541. /// 读取从站多个寄存器的值
  542. /// </summary>
  543. /// <param name="slaveStationNo"></param>
  544. /// <param name="devAdress">起始寄存器</param>
  545. /// <param name="iLenght">寄存器数量</param>
  546. public short[] ReadHoldingRegisters(int slaveStationNo, int devAdress, int iLenght)
  547. {
  548. ComposeMessage(slaveStationNo, FunctionCode.ReadHoldingRegisters, devAdress, iLenght);
  549. if (slaveStationNo == targetStation && msgRecFlag && RecBytes != null)
  550. {
  551. List<short> shortList = new List<short>();
  552. for (int i = 0; i < RecBytes.Length; i += 2)
  553. {
  554. shortList.Add(BitConverter.ToInt16(new byte[] { RecBytes[i + 1], RecBytes[i] }, 0));
  555. }
  556. return shortList.ToArray();
  557. }
  558. else
  559. {
  560. return null;
  561. }
  562. }
  563. /// <summary>
  564. /// 读取单精度浮点数
  565. /// </summary>
  566. /// <param name="slaveStationNo">从站站点</param>
  567. /// <param name="devAdress">起始地址</param>
  568. /// <param name="iLenght">连续读取浮点数的个数</param>
  569. /// <returns></returns>
  570. public float[] ReadFloatRegisters(int slaveStationNo, int devAdress, int iLenght)
  571. {
  572. ComposeMessage(slaveStationNo, FunctionCode.ReadHoldingRegisters, devAdress, iLenght * 2);
  573. if (slaveStationNo == targetStation && msgRecFlag && RecBytes != null)
  574. {
  575. List<float> floatList = new List<float>();
  576. for (int i = 0; i < RecBytes.Length; i += 4)
  577. {
  578. floatList.Add(BitConverter.ToSingle(new byte[] { RecBytes[i + 1], RecBytes[i], RecBytes[i + 3], RecBytes[i + 2] }, 0));
  579. }
  580. return floatList.ToArray();
  581. }
  582. else
  583. {
  584. return null;
  585. }
  586. }
  587. /// <summary>
  588. /// 向从站单个寄存器写入值
  589. /// </summary>
  590. /// <param name="slaveStationNo"></param>
  591. /// <param name="devAdress"></param>
  592. /// <param name="value"></param>
  593. public bool WriteSingleRegister(int slaveStationNo, int devAdress, short value)
  594. {
  595. ComposeMessage(slaveStationNo, FunctionCode.WriteSingleRegister, devAdress, values: BitConverter.GetBytes(value));
  596. if (slaveStationNo == targetStation && msgRecFlag && RecBytes != null && RecBytes.Length == 4)
  597. {
  598. int startAddress = BitConverter.ToInt16(new byte[] { RecBytes[1], RecBytes[0] }, 0);
  599. int val = BitConverter.ToInt16(new byte[] { RecBytes[3], RecBytes[2] }, 0);
  600. if (startAddress == devAdress && val == value)
  601. {
  602. return true;
  603. }
  604. else
  605. {
  606. return false;
  607. }
  608. }
  609. else
  610. {
  611. return false;
  612. }
  613. }
  614. /// <summary>
  615. /// 写入单精度浮点数
  616. /// </summary>
  617. /// <param name="slaveStationNo">从站站点</param>
  618. /// <param name="devAdress">寄存器地址</param>
  619. /// <param name="value"></param>
  620. /// <returns>true写入成功,false写入失败</returns>
  621. public bool WriteFloatRegister(int slaveStationNo, int devAdress, float value)
  622. {
  623. //进行拆解组合
  624. byte[] bytes = BitConverter.GetBytes(value);
  625. byte[] valBytes= new byte[] { bytes[1], bytes[0], bytes[ 3], bytes[2] };
  626. ComposeMessage(slaveStationNo, FunctionCode.WriteMultipleRegisters, devAdress, values: valBytes, fromBase: 4);
  627. if (slaveStationNo == targetStation && msgRecFlag && RecBytes != null)
  628. {
  629. short adress = BitConverter.ToInt16(new byte[] { RecBytes[1], RecBytes[0] }, 0);
  630. short registerCount = BitConverter.ToInt16(new byte[] { RecBytes[3], RecBytes[2] }, 0);
  631. if (adress == devAdress && registerCount == 2)
  632. {
  633. return true;
  634. }
  635. else
  636. {
  637. return false;
  638. }
  639. }
  640. else
  641. {
  642. return false;
  643. }
  644. }
  645. /// <summary>
  646. /// 向从站多个寄存器写入值
  647. /// </summary>
  648. /// <param name="slaveStationNo"></param>
  649. /// <param name="devAdress"></param>
  650. /// <param name="iLenght"></param>
  651. /// <param name="arr"></param>
  652. public bool WriteMultipleRegisters(int slaveStationNo, int devAdress, int iLenght, short[] arr)
  653. {
  654. if (iLenght != arr.Length)
  655. {
  656. throw new Exception("寄存器数量与所赋值数量不匹配");
  657. }
  658. byte[] bytes = new byte[arr.Length * 2];
  659. int index = 0;
  660. foreach (var item in arr)
  661. {
  662. byte[] temBytes = BitConverter.GetBytes(item);
  663. byte[] temBytes2 = new byte[] { temBytes[1], temBytes[0] };
  664. Array.Copy(temBytes2, 0, bytes, index, temBytes2.Length);
  665. index += temBytes.Length;
  666. }
  667. ComposeMessage(slaveStationNo, FunctionCode.WriteMultipleRegisters, devAdress, iLenght, bytes,fromBase:4);
  668. if (slaveStationNo == targetStation && msgRecFlag && RecBytes != null && RecBytes.Length == 4)
  669. {
  670. int startAddress = BitConverter.ToInt16(new byte[] { RecBytes[1], RecBytes[0] }, 0);
  671. int addressLen = BitConverter.ToInt16(new byte[] { RecBytes[3], RecBytes[2] }, 0);
  672. if (startAddress == devAdress && addressLen == iLenght)
  673. {
  674. return true;
  675. }
  676. else
  677. {
  678. return false;
  679. }
  680. }
  681. else
  682. {
  683. return false;
  684. }
  685. }
  686. public void Dispose()
  687. {
  688. socketObject.Close();
  689. socketObject.Dispose();
  690. }
  691. }

5,基于OPC服务的Modbus通信

5.1,OPC介绍

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

  1. public partial class Form1 : Form
  2. {
  3. OPCHelper opc = new OPCHelper();
  4. List<OPCItem> opcList = new List<OPCItem>();
  5. public Form1()
  6. {
  7. InitializeComponent();
  8. dataGridView1.AutoGenerateColumns = false;
  9. btnRefresh_Click(null, null);
  10. opc.AsyncWriteComplete += Opc_AsyncWriteComplete;
  11. }
  12. private void Opc_AsyncWriteComplete(List<OPCItem> result)
  13. {
  14. dataGridView1.DataSource = null;
  15. dataGridView1.DataSource = result;
  16. }
  17. private void btnRefresh_Click(object sender, EventArgs e)
  18. {
  19. comboNode.Items.Clear();
  20. //获取HostName
  21. foreach (var item in Dns.GetHostAddresses(Environment.MachineName))
  22. {
  23. if (comboNode.Items.Contains(Dns.GetHostEntry(item).HostName))
  24. continue;
  25. comboNode.Items.Add(Dns.GetHostEntry(item).HostName);
  26. }
  27. //如果存在则将第一项设置为当前项
  28. comboNode.SelectedIndex = comboNode.Items.Count > 0 ? 0 : -1;
  29. }
  30. private void comboNode_SelectedIndexChanged(object sender, EventArgs e)
  31. {
  32. if (comboNode.SelectedItem != null)
  33. {
  34. //获取所有可用的OPC服务
  35. var servers = OPCHelper.GetOPCServers(comboNode.Text);
  36. comboServer.DataSource = servers;
  37. }
  38. }
  39. private void comboServer_SelectedIndexChanged(object sender, EventArgs e)
  40. {
  41. }
  42. private void btnConnect_Click(object sender, EventArgs e)
  43. {
  44. if (btnConnect.Text.Equals("Connect", StringComparison.CurrentCultureIgnoreCase))
  45. {
  46. if (comboServer.SelectedItem != null)
  47. {
  48. opcList.Clear();
  49. opc.Connected(comboServer.Text.Trim());
  50. //获取OPC上所有注册的tag
  51. if (opc.IsConnected)
  52. {
  53. var tags = opc.GetOPCServerTags();
  54. lsbBrowser.DataSource = tags;
  55. btnConnect.Text = "Disconnect";
  56. btnConnect.BackColor = Color.LightGreen;
  57. }
  58. }
  59. else
  60. {
  61. MessageBox.Show("请先选择需要连接OPC服务!");
  62. }
  63. }
  64. else
  65. {
  66. opc.Disconnect();
  67. if (!opc.IsConnected)
  68. {
  69. opcList.Clear();
  70. lsbBrowser.DataSource = null;
  71. dataGridView1.DataSource = null;
  72. btnConnect.Text = "Connect";
  73. btnConnect.BackColor = Color.Red;
  74. }
  75. }
  76. }
  77. private void btnRead_Click(object sender, EventArgs e)
  78. {
  79. if(dataGridView1.Rows.Count==0)
  80. {
  81. MessageBox.Show("请先添加需要读取的寄存器!");
  82. return;
  83. }
  84. if (opc.IsConnected)
  85. {
  86. List<string> tags = opcList.Select(item => item.Tag).ToList();
  87. var list= opc.ReadMultipleRegisters<float>(tags,ckAsync.Checked);
  88. if (list != null)
  89. {
  90. StringBuilder sb = new StringBuilder();
  91. foreach (var item in list)
  92. {
  93. sb.AppendFormat("{0} ", item);
  94. }
  95. MessageBox.Show(sb.ToString());
  96. }
  97. }
  98. else
  99. {
  100. MessageBox.Show("未连接服务!");
  101. return;
  102. }
  103. }
  104. private void lsbBrowser_DoubleClick(object sender, EventArgs e)
  105. {
  106. if (lsbBrowser.SelectedItem != null)
  107. {
  108. string tag = lsbBrowser.SelectedItem.ToString();
  109. if (!opcList.Any(item => item.Tag.Equals(tag)))
  110. {
  111. opcList.Add(new OPCItem { Tag = tag });
  112. }
  113. dataGridView1.DataSource = null;
  114. dataGridView1.DataSource = opcList;
  115. }
  116. }
  117. private void dataGridView1_CellDoubleClick(object sender, DataGridViewCellEventArgs e)
  118. {
  119. if (dataGridView1.CurrentRow != null)
  120. {
  121. //获取当前值
  122. if (opc.IsConnected)
  123. {
  124. DataGridViewRow row = dataGridView1.CurrentRow;
  125. string tag = row.Cells["Tag"].Value.ToString();
  126. if (row.Cells["Value"].Value == null)
  127. {
  128. btnRead_Click(null, null);
  129. }
  130. string val = row.Cells["Value"].Value?.ToString();
  131. WriteFrm frm = new WriteFrm(tag, val);
  132. frm.StartPosition = FormStartPosition.CenterScreen;
  133. frm.ModifyCompleted += Frm_ModifyCompleted;
  134. frm.ShowDialog();
  135. }
  136. }
  137. }
  138. private void Frm_ModifyCompleted(string tag, float val, bool async)
  139. {
  140. if (opc.IsConnected)
  141. {
  142. opc.WriteSingleValue<float>(tag, val, async);
  143. btnRead_Click(null, null);
  144. }
  145. }
  146. }

  1. //定义一个委托
  2. public delegate void AsyncReadCompleteEventHandler(List<OPCItem> result);
  3. /// <summary>
  4. /// 基于OPC协议(对象连接与嵌入流程控制)的读写类
  5. /// </summary>
  6. public class OPCHelper
  7. {
  8. //--------------使用指南-----------------------
  9. //第1步,使用静态方法获取本机或者局域网的hostName或者IpAdress所拥有的OPS服务
  10. // public static string[] GetOPCServers(string hostNameorAdress)
  11. //第2步,使用实例化方法连接OPS服务
  12. // public bool Connected(string serverName)
  13. //第3步,在连接的基础上使用实例化方法获取对应OPC服务器上所声明定义的所有可用寄存器
  14. // public string[] GetOPCServerTags()
  15. //第4步,在连接的基础上使用实例化方法对寄存器进行读写
  16. // public void WriteMultipleRegisters(List<string> tags, List<object> values, bool async = true)
  17. // public List<T> ReadMultipleRegisters<T>(List<string> tags, bool async = true)
  18. //第5步,如果使用的是异步读取,请在异步读取完成事件中接收值。
  19. // public event AsyncReadCompleteEventHandler AsyncWriteComplete;
  20. //***********************************************************************************************
  21. //***********************************************************************************************
  22. //--------------特别注意事项----------------------
  23. //第1点:
  24. //OPC中数组Array有效的下标是从1开始,至于下标为0的值需要填充同类型的任意值,int类型一般采用0,string类型一般为"0"
  25. //同样,下标1开始的才是有效的值才可以进行取值操作。
  26. //如果按照C#中数组从下表0开始赋值取值操作,将抛出异常。
  27. //
  28. //第2点
  29. //进行异步或者同步写入时
  30. //OPC.SyncWrite(int NumItems, ref Array ServerHandles, ref Array Values, out Array Errors);
  31. //参数Values必须是Object类型
  32. //----------------------------------------------
  33. //定义OPC相关对象
  34. OPCServer kepServer = new OPCServer();
  35. OPCGroups kepGroups;
  36. OPCGroup kepGroup;
  37. OPCBrowser kepBrowser;
  38. Dictionary<int, string> currentItemId = new Dictionary<int, string>();
  39. //定义OPC标签错误
  40. Array iErrors;
  41. bool isConnected;
  42. /// <summary>
  43. /// 服务句柄
  44. /// </summary>
  45. Array ServerHandles;
  46. int cancelId;
  47. /// <summary>
  48. /// 异步读取完成事件
  49. /// </summary>
  50. public event AsyncReadCompleteEventHandler AsyncWriteComplete;
  51. /// <summary>
  52. /// OPC是否连接
  53. /// </summary>
  54. public bool IsConnected
  55. {
  56. get
  57. {
  58. if (kepGroup == null)
  59. {
  60. isConnected = false;
  61. }
  62. return isConnected;
  63. }
  64. }
  65. /// <summary>
  66. /// 当前OPCOPCGroup服务,可用于寄存器的读取与写入
  67. /// </summary>
  68. public OPCGroup CurrentOPCGroup
  69. {
  70. get
  71. {
  72. return kepGroup;
  73. }
  74. }
  75. /// <summary>
  76. /// 根据hostName或者Adress获取可用的OPC服务
  77. /// </summary>
  78. /// <param name="hostNameorAdress"></param>
  79. /// <returns>返回可用的服务列表</returns>
  80. public static List<string> GetOPCServers(string hostNameorAdress)
  81. {
  82. //获取hostName
  83. string hostName = Dns.GetHostEntry(hostNameorAdress).HostName;
  84. //注意:这里须使用Object接收值,若使用Var,后续进行遍历时将报异常
  85. OPCServer opcServer = new OPCServer();
  86. object result = opcServer.GetOPCServers(hostName);
  87. List<string> list = new List<string>();
  88. foreach (var item in (Array)result)
  89. {
  90. list.Add(item.ToString());
  91. }
  92. return list;
  93. }
  94. /// <summary>
  95. /// 连接OPC服务
  96. /// </summary>
  97. /// <param name="serverName">OPC服务名</param>
  98. /// <returns></returns>
  99. public bool Connected(string serverName)
  100. {
  101. try
  102. {
  103. kepServer.Connect(serverName);
  104. isConnected = true;
  105. kepServer.OPCGroups.RemoveAll();
  106. kepGroups = kepServer.OPCGroups;
  107. kepGroups.DefaultGroupDeadband = 0;
  108. kepGroup = kepGroups.Add("Test");
  109. kepGroup.IsActive = true;
  110. kepGroup.IsSubscribed = true;
  111. kepGroup.UpdateRate = 250;
  112. //监视数据变化事件获取值
  113. // kepGroup.DataChange += KepGroup_DataChange;
  114. kepGroup.AsyncReadComplete += KepGroup_AsyncReadComplete;
  115. kepGroup.AsyncWriteComplete += KepGroup_AsyncWriteComplete;
  116. return true;
  117. }
  118. catch (Exception)
  119. {
  120. return false;
  121. }
  122. }
  123. private void KepGroup_AsyncWriteComplete(int TransactionID, int NumItems, ref Array ClientHandles, ref Array Errors)
  124. {
  125. // throw new NotImplementedException();
  126. }
  127. private void KepGroup_AsyncReadComplete(int TransactionID, int NumItems, ref Array ClientHandles, ref Array ItemValues, ref Array Qualities, ref Array TimeStamps, ref Array Errors)
  128. {
  129. List<OPCItem> opcItemList = new List<OPCItem>();
  130. //获取值
  131. for (int i = 0; i < NumItems; i++)
  132. {
  133. //kepOPC有效所有从1开始
  134. int clientId = Convert.ToInt32(ClientHandles.GetValue(i + 1));
  135. string tag = null;
  136. if (currentItemId.Keys.Contains(clientId))
  137. {
  138. tag = currentItemId[clientId];
  139. }
  140. object val = ItemValues.GetValue(i + 1);
  141. //TimeStamps:是标准时间即格林时间是0时区计时,所以这里需要+8
  142. DateTime dt = Convert.ToDateTime(TimeStamps.GetValue(i + 1)).AddHours(8);
  143. opcItemList.Add(new OPCItem { Tag = tag, Value = val.ToString(), Time = dt });
  144. }
  145. AsyncWriteComplete?.Invoke(opcItemList);
  146. }
  147. /// <summary>
  148. /// 获取OPC服务中所有已定义可用的Tag即定义的寄存器名
  149. /// </summary>
  150. /// <returns></returns>
  151. public string[] GetOPCServerTags()
  152. {
  153. if (!IsConnected)
  154. {
  155. throw new Exception("OPC服务未连接!");
  156. }
  157. kepBrowser = kepServer.CreateBrowser();
  158. kepBrowser.ShowBranches();
  159. kepBrowser.ShowLeafs(true);
  160. List<string> list = new List<string>();
  161. foreach (var item in kepBrowser)
  162. {
  163. list.Add(item.ToString());
  164. }
  165. return list.ToArray();
  166. }
  167. /// <summary>
  168. /// 向OPC的多个寄存器写入多个值
  169. /// </summary>
  170. /// <param name="tags">OPC服务上定义的寄存器名</param>
  171. /// <param name="values">写入的值的集合</param>
  172. /// <param name="async">是否异步写入</param>
  173. public void WriteMultipleRegisters(List<string> tags, List<object> values, bool async = true)
  174. {
  175. if (!IsConnected)
  176. {
  177. throw new Exception("OPC未连接,写入失败!");
  178. }
  179. if (kepGroup.OPCItems.Count > 0)
  180. {
  181. //移除项目
  182. kepGroup.OPCItems.Remove(kepGroup.OPCItems.Count, ref ServerHandles, out iErrors);
  183. }
  184. var itemIds = new List<string>(tags);
  185. //OPC Array中有效值是从下标1开始,所以需对下标0填充任意值,这里填充"0"
  186. itemIds.Insert(0, "0");
  187. Array ItemIds = itemIds.ToArray();
  188. List<int> clientList = new List<int>();
  189. //OPC Array中有效值是从下标1开始,所以需对下标0填充任意值,这里填充 0
  190. clientList.Add(0);
  191. for (int i = 0; i < tags.Count; i++)
  192. {
  193. clientList.Add(i + 20);
  194. }
  195. Array ClientHandles = clientList.ToArray();
  196. kepGroup.OPCItems.AddItems(tags.Count, ref ItemIds, ref ClientHandles, out ServerHandles, out iErrors);
  197. //------------------特别注意----------------
  198. //写入的值的类型只能是Object类型,如果是其他类型将抛出异常
  199. List<object> writeValues = new List<object>(values);
  200. writeValues.Insert(0,default(object));
  201. Array valueArray = writeValues.ToArray();
  202. if (async)
  203. {
  204. kepGroup.AsyncWrite(1, ref ServerHandles, valueArray, out iErrors, 0, out cancelId);
  205. }
  206. else
  207. {
  208. kepGroup.SyncWrite(1, ref ServerHandles, ref valueArray, out iErrors);
  209. }
  210. }
  211. /// <summary>
  212. /// 从OPC中读取多个寄存器的值,当选择异步模式时返回null,真实的值通过AsyncReadComplete事件获取
  213. /// </summary>
  214. /// <param name="tags">OPC服务上定义的寄存器名</param>
  215. /// <param name="async">是否异步读取</param>
  216. /// <returns>异步读取时返回null</returns>
  217. public List<T> ReadMultipleRegisters<T>(List<string> tags, bool async = true)
  218. {
  219. if (!IsConnected)
  220. {
  221. throw new Exception("OPC未连接,读取失败!");
  222. }
  223. if (kepGroup.OPCItems.Count > 0)
  224. {
  225. //移除项目
  226. kepGroup.OPCItems.Remove(kepGroup.OPCItems.Count, ref ServerHandles, out iErrors);
  227. }
  228. var itemIds = new List<string>(tags);
  229. //OPC Array中有效值是从下标1开始,所以需对下标0填充任意值,这里填充"0"
  230. itemIds.Insert(0, "0");
  231. Array ItemIds = itemIds.ToArray();
  232. List<int> clientList = new List<int>();
  233. //OPC Array中有效值是从下标1开始,所以需对下标0填充任意值,这里填充 0
  234. clientList.Add(0);
  235. for (int i = 0; i < tags.Count; i++)
  236. {
  237. clientList.Add(i + 20);
  238. }
  239. Array ClientHandles = clientList.ToArray();
  240. kepGroup.OPCItems.AddItems(tags.Count, ref ItemIds, ref ClientHandles, out ServerHandles, out iErrors);
  241. //获取服务句柄,并写入到字典中
  242. currentItemId.Clear();
  243. for (int i = 0; i < ItemIds.Length - 1; i++)
  244. {
  245. string tag = ItemIds.GetValue(i + 1).ToString();
  246. int id = Convert.ToInt32(ClientHandles.GetValue(i + 1));
  247. currentItemId.Add(id, tag);
  248. }
  249. if (async)
  250. {
  251. kepGroup.AsyncRead(ItemIds.Length - 1, ref ServerHandles, out iErrors, 1, out cancelId);
  252. return null;
  253. }
  254. else
  255. {
  256. Array result;
  257. object qualities;
  258. object tiemstamps;
  259. kepGroup.SyncRead(2, ItemIds.Length - 1, ref ServerHandles, out result, out iErrors, out qualities, out tiemstamps);
  260. List<T> list = new List<T>();
  261. for (int i = 0; i < result.Length; i++)
  262. {
  263. list.Add((T)result.GetValue(i + 1));
  264. }
  265. return list;
  266. }
  267. }
  268. /// <summary>
  269. /// 写入单个值
  270. /// </summary>
  271. /// <typeparam name="T"></typeparam>
  272. /// <param name="tag">寄存器在OPC上的tag名</param>
  273. /// <param name="value">写入的值</param>
  274. /// <param name="async">是否异步</param>
  275. public void WriteSingleValue<T>(string tag, T value, bool async = true)
  276. {
  277. WriteMultipleRegisters(new List<string> { tag }, new List<object> { value }, async);
  278. }
  279. /// <summary>
  280. /// 读取单个值,当选择异步模式时返回null,真实的值通过AsyncReadComplete事件获取
  281. /// </summary>
  282. /// <typeparam name="T"></typeparam>
  283. /// <param name="tag">寄存器在OPC上的tag名</param>
  284. /// <param name="async">是否异步,异步时返回null,具体的值通过AsyncReadComplete事件获取</param>
  285. public List<T> ReadSingleValue<T>(string tag, bool async = true)
  286. {
  287. return ReadMultipleRegisters<T>(new List<string> { tag }, async);
  288. }
  289. /// <summary>
  290. /// 断开OPC连接
  291. /// </summary>
  292. /// <returns></returns>
  293. public bool Disconnect()
  294. {
  295. try
  296. {
  297. if (kepGroup == null)
  298. {
  299. isConnected = false;
  300. return false;
  301. }
  302. kepServer.Disconnect();
  303. isConnected = false;
  304. return true;
  305. }
  306. catch (Exception)
  307. {
  308. return false;
  309. }
  310. }
  311. }
  312. public class OPCItem
  313. {
  314. public string Tag { get; set; }
  315. public string Value { get; set; }
  316. public DateTime Time { get; set; }
  317. }

6,Demo链接。

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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/笔触狂放9/article/detail/806153
推荐阅读
相关标签
  

闽ICP备14008679号