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();
}