From ed0fe1236689ad3017b5784f319be8adb012b33c Mon Sep 17 00:00:00 2001 From: "GukSang.Jin" Date: Sat, 3 Jan 2026 18:55:39 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- WindowsFormsApp6/MainForm.cs | 491 +++++++++++++++++++++++++++++++++++ 1 file changed, 491 insertions(+) diff --git a/WindowsFormsApp6/MainForm.cs b/WindowsFormsApp6/MainForm.cs index e0b1fed..f194590 100644 --- a/WindowsFormsApp6/MainForm.cs +++ b/WindowsFormsApp6/MainForm.cs @@ -3,11 +3,14 @@ using System.Data; using System.Drawing; using System.Windows.Forms; using System.IO; +using System.IO.Ports; +using System.Linq; using NPOI.SS.UserModel; using NPOI.XSSF.UserModel; using NPOI.SS.Util; using NPOIBorderStyle = NPOI.SS.UserModel.BorderStyle; using NPOIHorizontalAlignment = NPOI.SS.UserModel.HorizontalAlignment; +using Modbus.Device; namespace WindowsFormsApp6 { @@ -17,6 +20,11 @@ namespace WindowsFormsApp6 private Form2 form2Instance; private Form3 form3Instance; private System.Windows.Forms.Timer timeUpdateTimer; + + // 串口和Modbus相关 + private SerialPort _serialPort; + private IModbusMaster _modbusMaster; + private System.Windows.Forms.Timer _readTimer; public MainForm() { @@ -24,6 +32,7 @@ namespace WindowsFormsApp6 InitializeTabControl(); InitializeEmbeddedForms(); InitializeTimeUpdate(); + InitializeSerialPortAndModbus(); CenterInfoControls(); } @@ -40,6 +49,467 @@ namespace WindowsFormsApp6 timeUpdateTimer.Start(); } + /// + /// 初始化串口和Modbus通信 + /// + private void InitializeSerialPortAndModbus() + { + // 初始化读取定时器(信号量触发模式) + _readTimer = new System.Windows.Forms.Timer(); + _readTimer.Interval = 100; // 100ms检查一次信号量 + _readTimer.Tick += ReadTimer_Tick; + + // 配置串口参数 + _serialPort = new SerialPort + { + PortName = "COM2", + BaudRate = 19200, + DataBits = 8, + Parity = Parity.None, + StopBits = StopBits.One, + ReadTimeout = 500, + WriteTimeout = 500, + RtsEnable = false, + DtrEnable = false + }; + + try + { + // 打开串口 + if (!_serialPort.IsOpen) + { + _serialPort.Open(); + + // 创建Modbus RTU主站 + _modbusMaster = ModbusSerialMaster.CreateRtu(_serialPort); + _modbusMaster.Transport.WaitToRetryMilliseconds = 200; + _modbusMaster.Transport.Retries = 1; + _modbusMaster.Transport.ReadTimeout = 500; + + // 启动定时读取 + _readTimer.Start(); + + ShowInfoMsg("数据采集器初始化成功"); + } + } + catch (Exception ex) + { + ShowErrorMsg($"串口/Modbus初始化失败:{ex.Message}"); + } + } + + /// + /// 定时读取Modbus数据(信号量触发模式) + /// + private void ReadTimer_Tick(object sender, EventArgs e) + { + if (_modbusMaster == null || _serialPort == null || !_serialPort.IsOpen) + return; + + try + { + byte slaveId = 1; // PLC从站地址 + + // 读取三个信号量(M103, M252, M310) + // Modbus地址:M103 = 103, M252 = 252, M310 = 310 + // 使用ReadCoils读取线圈状态(M区) + + bool signalM103 = ReadCoil(slaveId, 103); // Form1信号量 + bool signalM252 = ReadCoil(slaveId, 252); // Form2信号量 + bool signalM310 = ReadCoil(slaveId, 310); // Form3信号量 + + // 根据信号量触发相应的数据读取 + if (signalM103) + { + ReadForm1Data(slaveId); + // 读取完成后清除信号量 + WriteCoil(slaveId, 103, false); + } + + if (signalM252) + { + ReadForm2Data(slaveId); + // 读取完成后清除信号量 + WriteCoil(slaveId, 252, false); + } + + if (signalM310) + { + ReadForm3Data(slaveId); + // 读取完成后清除信号量 + WriteCoil(slaveId, 310, false); + } + } + catch (Exception ex) + { + // 读取失败时不弹窗,避免频繁打扰用户 + System.Diagnostics.Debug.WriteLine($"Modbus读取失败:{ex.Message}"); + } + } + + /// + /// 读取单个线圈状态 + /// + private bool ReadCoil(byte slaveId, ushort address) + { + try + { + bool[] coils = _modbusMaster.ReadCoils(slaveId, address, 1); + return coils[0]; + } + catch + { + return false; + } + } + + /// + /// 写入单个线圈状态 + /// + private void WriteCoil(byte slaveId, ushort address, bool value) + { + try + { + _modbusMaster.WriteSingleCoil(slaveId, address, value); + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"写入线圈失败:{ex.Message}"); + } + } + + /// + /// 读取Form1数据(液体吸收时间) + /// PLC地址:D200 - 时间(s) + /// 信号量:M103 + /// + private void ReadForm1Data(byte slaveId) + { + try + { + // 读取D200寄存器(时间数据) + // 假设有5个试样,每个试样1个时间值 + ushort[] registers = _modbusMaster.ReadHoldingRegisters(slaveId, 200, 5); + + // 使用反射获取Form1的私有字段 + var sampleDataTableField = form1Instance.GetType() + .GetField("sampleDataTable", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); + + if (sampleDataTableField != null) + { + DataTable dataTable = sampleDataTableField.GetValue(form1Instance) as DataTable; + + if (dataTable != null) + { + // 清除旧的平均值行 + var avgRows = dataTable.Select("序号 = '平均时间(s)'"); + foreach (var row in avgRows) + { + dataTable.Rows.Remove(row); + } + + // 创建新数据行 + DataRow dataRow = dataTable.NewRow(); + dataRow["序号"] = "时间(s)"; + + // 填充时间数据(将寄存器值转换为实际时间,假设寄存器值为整数秒*10) + for (int i = 0; i < Math.Min(registers.Length, 5); i++) + { + double timeValue = registers[i] / 10.0; // 转换为秒 + dataRow[$"试样{i + 1}"] = timeValue; + } + + dataTable.Rows.Add(dataRow); + + // 计算并添加平均值行 + double totalAvg = 0; + int count = 0; + for (int i = 0; i < Math.Min(registers.Length, 5); i++) + { + totalAvg += registers[i] / 10.0; + count++; + } + + if (count > 0) + { + DataRow avgRow = dataTable.NewRow(); + avgRow["序号"] = "平均时间(s)"; + avgRow["试样1"] = totalAvg / count; + + for (int i = 2; i <= 5; i++) + { + avgRow[$"试样{i}"] = DBNull.Value; + } + + dataTable.Rows.Add(avgRow); + } + + // 刷新显示 + dataTable.AcceptChanges(); + this.Invoke(new Action(() => + { + // 触发Form1的界面刷新 + var refreshMethod = form1Instance.GetType() + .GetMethod("RefreshDataGridView", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); + refreshMethod?.Invoke(form1Instance, null); + })); + } + } + + System.Diagnostics.Debug.WriteLine($"Form1数据读取成功:{registers.Length}个寄存器"); + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"读取Form1数据失败:{ex.Message}"); + } + } + + /// + /// 读取Form2数据(液体吸收量) + /// PLC地址:D420 - 初始重量, D422 - 浸润后重量, D402 - 浸润时间, D406 - 悬挂时间 + /// 信号量:M252 + /// + private void ReadForm2Data(byte slaveId) + { + try + { + // 读取初始重量(D420开始,5个试样) + ushort[] initialWeights = _modbusMaster.ReadHoldingRegisters(slaveId, 420, 5); + + // 读取浸润后重量(D422开始,5个试样) + ushort[] afterWeights = _modbusMaster.ReadHoldingRegisters(slaveId, 422, 5); + + // 读取浸润时间(D402) + ushort[] soakTime = _modbusMaster.ReadHoldingRegisters(slaveId, 402, 1); + + // 读取悬挂时间(D406) + ushort[] hangTime = _modbusMaster.ReadHoldingRegisters(slaveId, 406, 1); + + // 使用反射获取Form2的私有字段 + var sampleDataTableField = form2Instance.GetType() + .GetField("sampleDataTable", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); + + if (sampleDataTableField != null) + { + DataTable dataTable = sampleDataTableField.GetValue(form2Instance) as DataTable; + + if (dataTable != null) + { + dataTable.Clear(); + + int count = Math.Min(initialWeights.Length, 5); + + // 转换数据(假设寄存器值为实际值*100) + double[] initialWeightValues = new double[count]; + double[] afterWeightValues = new double[count]; + + for (int i = 0; i < count; i++) + { + initialWeightValues[i] = initialWeights[i] / 100.0; + afterWeightValues[i] = afterWeights[i] / 100.0; + } + + // 1. 初始重量行 + DataRow initialRow = dataTable.NewRow(); + initialRow["序号"] = "初始重量(g)"; + for (int i = 0; i < count; i++) + { + initialRow[$"试样{i + 1}"] = initialWeightValues[i]; + } + dataTable.Rows.Add(initialRow); + + // 2. 浸润后重量行 + DataRow afterRow = dataTable.NewRow(); + afterRow["序号"] = "浸润后重量(g)"; + for (int i = 0; i < count; i++) + { + afterRow[$"试样{i + 1}"] = afterWeightValues[i]; + } + dataTable.Rows.Add(afterRow); + + // 3. 液体吸收量行 (%) + DataRow absorptionRow = dataTable.NewRow(); + absorptionRow["序号"] = "液体吸收量(%)"; + double[] absorptions = new double[count]; + for (int i = 0; i < count; i++) + { + absorptions[i] = ((afterWeightValues[i] - initialWeightValues[i]) / initialWeightValues[i]) * 100; + absorptionRow[$"试样{i + 1}"] = absorptions[i]; + } + dataTable.Rows.Add(absorptionRow); + + // 4. 浸润时间行 + DataRow soakTimeRow = dataTable.NewRow(); + soakTimeRow["序号"] = "浸润时间"; + for (int i = 1; i <= count; i++) + { + soakTimeRow[$"试样{i}"] = $"{soakTime[0]}s"; + } + dataTable.Rows.Add(soakTimeRow); + + // 5. 悬挂时间行 + DataRow hangTimeRow = dataTable.NewRow(); + hangTimeRow["序号"] = "悬挂时间"; + for (int i = 1; i <= count; i++) + { + hangTimeRow[$"试样{i}"] = $"{hangTime[0]}s"; + } + dataTable.Rows.Add(hangTimeRow); + + // 6. 运行速度行(固定值或从其他寄存器读取) + DataRow runSpeedRow = dataTable.NewRow(); + runSpeedRow["序号"] = "运行速度"; + for (int i = 1; i <= count; i++) + { + runSpeedRow[$"试样{i}"] = "100mm/min"; + } + dataTable.Rows.Add(runSpeedRow); + + // 7. 液体吸收量平均值行 + DataRow avgRow = dataTable.NewRow(); + avgRow["序号"] = "液体吸收量平均值(%)"; + double avgAbsorption = absorptions.Average(); + avgRow["试样1"] = avgAbsorption; + for (int i = 2; i <= count; i++) + { + avgRow[$"试样{i}"] = DBNull.Value; + } + dataTable.Rows.Add(avgRow); + + // 8. 液体吸收量最大值行 + DataRow maxRow = dataTable.NewRow(); + maxRow["序号"] = "液体吸收量最大值(%)"; + double maxAbsorption = absorptions.Max(); + maxRow["试样1"] = maxAbsorption; + for (int i = 2; i <= count; i++) + { + maxRow[$"试样{i}"] = DBNull.Value; + } + dataTable.Rows.Add(maxRow); + + // 9. 标准偏差行 + DataRow stdDevRow = dataTable.NewRow(); + stdDevRow["序号"] = "标准偏差"; + double stdDev = CalculateStandardDeviation(absorptions); + stdDevRow["试样1"] = stdDev; + for (int i = 2; i <= count; i++) + { + stdDevRow[$"试样{i}"] = DBNull.Value; + } + dataTable.Rows.Add(stdDevRow); + + // 刷新显示 + dataTable.AcceptChanges(); + this.Invoke(new Action(() => + { + var refreshMethod = form2Instance.GetType() + .GetMethod("RefreshDataGridView", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); + refreshMethod?.Invoke(form2Instance, null); + })); + } + } + + System.Diagnostics.Debug.WriteLine("Form2数据读取成功"); + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"读取Form2数据失败:{ex.Message}"); + } + } + + /// + /// 读取Form3数据(液体芯吸速率) + /// PLC地址:D456 - 吸水时间(s) + /// 信号量:M310 + /// + private void ReadForm3Data(byte slaveId) + { + try + { + // 读取吸水时间(D456开始,5个试样 * 3次测试 = 15个寄存器) + ushort[] wickingTimes = _modbusMaster.ReadHoldingRegisters(slaveId, 456, 15); + + // 使用反射获取Form3的私有字段 + var sampleDataTableField = form3Instance.GetType() + .GetField("sampleDataTable", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); + + if (sampleDataTableField != null) + { + DataTable dataTable = sampleDataTableField.GetValue(form3Instance) as DataTable; + + if (dataTable != null && dataTable.Rows.Count > 0) + { + // 获取第一行(吸水时间行) + DataRow timeRow = dataTable.Rows[0]; + + // 填充吸水时间数据(假设寄存器值为实际时间*10) + int registerIndex = 0; + for (int i = 1; i <= 5; i++) + { + if (registerIndex < wickingTimes.Length) + { + timeRow[$"试样{i}_1"] = wickingTimes[registerIndex++] / 10.0; + } + if (registerIndex < wickingTimes.Length) + { + timeRow[$"试样{i}_2"] = wickingTimes[registerIndex++] / 10.0; + } + if (registerIndex < wickingTimes.Length) + { + timeRow[$"试样{i}_3"] = wickingTimes[registerIndex++] / 10.0; + } + } + + // 调用Form3的计算方法 + this.Invoke(new Action(() => + { + var calculateMethod = form3Instance.GetType() + .GetMethod("CalculateAllRows", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); + calculateMethod?.Invoke(form3Instance, null); + + var refreshMethod = form3Instance.GetType() + .GetMethod("RefreshDataGridView", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); + refreshMethod?.Invoke(form3Instance, null); + })); + } + } + + System.Diagnostics.Debug.WriteLine("Form3数据读取成功"); + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"读取Form3数据失败:{ex.Message}"); + } + } + + /// + /// 计算标准偏差 + /// + private double CalculateStandardDeviation(double[] values) + { + if (values == null || values.Length <= 1) return 0; + + double avg = values.Average(); + double sumOfSquares = values.Sum(val => Math.Pow(val - avg, 2)); + return Math.Sqrt(sumOfSquares / (values.Length - 1)); + } + + /// + /// 显示错误消息 + /// + private void ShowErrorMsg(string message) + { + MessageBox.Show(message, "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + + /// + /// 显示信息消息 + /// + private void ShowInfoMsg(string message) + { + MessageBox.Show(message, "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); + } + private void InitializeTabControl() { tabControl1.SelectedIndexChanged += TabControl1_SelectedIndexChanged; @@ -830,8 +1300,29 @@ namespace WindowsFormsApp6 private void MainForm_FormClosing(object sender, FormClosingEventArgs e) { + // 停止并释放定时器 timeUpdateTimer?.Stop(); timeUpdateTimer?.Dispose(); + + _readTimer?.Stop(); + _readTimer?.Dispose(); + + // 关闭Modbus和串口 + try + { + _modbusMaster?.Dispose(); + + if (_serialPort != null && _serialPort.IsOpen) + { + _serialPort.Close(); + } + _serialPort?.Dispose(); + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"关闭串口失败:{ex.Message}"); + } + Application.Exit(); }