This commit is contained in:
GukSang.Jin
2026-01-03 18:55:39 +08:00
parent 57cea85e89
commit ed0fe12366

View File

@@ -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();
}
/// <summary>
/// 初始化串口和Modbus通信
/// </summary>
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}");
}
}
/// <summary>
/// 定时读取Modbus数据信号量触发模式
/// </summary>
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}");
}
}
/// <summary>
/// 读取单个线圈状态
/// </summary>
private bool ReadCoil(byte slaveId, ushort address)
{
try
{
bool[] coils = _modbusMaster.ReadCoils(slaveId, address, 1);
return coils[0];
}
catch
{
return false;
}
}
/// <summary>
/// 写入单个线圈状态
/// </summary>
private void WriteCoil(byte slaveId, ushort address, bool value)
{
try
{
_modbusMaster.WriteSingleCoil(slaveId, address, value);
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"写入线圈失败:{ex.Message}");
}
}
/// <summary>
/// 读取Form1数据液体吸收时间
/// PLC地址D200 - 时间(s)
/// 信号量M103
/// </summary>
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}");
}
}
/// <summary>
/// 读取Form2数据液体吸收量
/// PLC地址D420 - 初始重量, D422 - 浸润后重量, D402 - 浸润时间, D406 - 悬挂时间
/// 信号量M252
/// </summary>
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}");
}
}
/// <summary>
/// 读取Form3数据液体芯吸速率
/// PLC地址D456 - 吸水时间(s)
/// 信号量M310
/// </summary>
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}");
}
}
/// <summary>
/// 计算标准偏差
/// </summary>
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));
}
/// <summary>
/// 显示错误消息
/// </summary>
private void ShowErrorMsg(string message)
{
MessageBox.Show(message, "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
/// <summary>
/// 显示信息消息
/// </summary>
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();
}