diff --git a/压差法气体渗透仪/NET_HRF4838_C#/Data/DataChange.cs b/压差法气体渗透仪/NET_HRF4838_C#/Data/DataChange.cs new file mode 100644 index 0000000..4bb6044 --- /dev/null +++ b/压差法气体渗透仪/NET_HRF4838_C#/Data/DataChange.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace 薄膜气体透过率测试仪 +{ + public class DataChange + { + /// + /// ushort转为float类型 + /// + /// + /// + /// float型数据 + public float UshortToFloat(ushort P1, ushort P2) + { + int intSign, intSignRest, intExponent, intExponentRest; + float faResult, faDigit; + intSign = P1 / 32768; + intSignRest = P1 % 32768; + intExponent = intSignRest / 128; + intExponentRest = intSignRest % 128; + faDigit = (float)(intExponentRest * 65536 + P2) / 8388608; + faResult = (float)Math.Pow(-1, intSign) * (float)Math.Pow(2, intExponent - 127) * (faDigit + 1); + return faResult; + } + /// + /// ushort转为int类型 + /// + /// + /// + /// 返回int型数据 + public int UshortToInt1(ushort u1, ushort u2) + { + ushort[] maidong = new ushort[2] { u1, u2 }; + byte[] bytes = new byte[maidong.Length * 2]; + Buffer.BlockCopy(maidong, 0, bytes, 0, bytes.Length); + int result = BitConverter.ToInt32(bytes, 0); + // 将字节数组转换为32位无符号整数 + return result; + + } + /// + /// Float转为Ushort数组发送 + /// + /// + /// 返回ushort数组 + public ushort[] SplitFloatToUShortArray(float value) + { + byte[] floatBytes = BitConverter.GetBytes(value); + ushort[] ushortArray = new ushort[floatBytes.Length / 2]; + + for (int i = 0, j = 0; i < floatBytes.Length; i += 2, j++) + { + ushortArray[j] = BitConverter.ToUInt16(floatBytes, i); + } + + return ushortArray; + } + /// + /// Int转为ushort数组发送 + /// + /// + /// 返回ushort数组 + public ushort[] intToushorts(int res) + { + ushort ust1 = (ushort)(res >> 16); + ushort ust2 = (ushort)res; + return new ushort[] { ust2, ust1 }; + } + } +} diff --git a/压差法气体渗透仪/NET_HRF4838_C#/Data/Function.cs b/压差法气体渗透仪/NET_HRF4838_C#/Data/Function.cs new file mode 100644 index 0000000..c96399a --- /dev/null +++ b/压差法气体渗透仪/NET_HRF4838_C#/Data/Function.cs @@ -0,0 +1,235 @@ +using Modbus.Device; +using Modbus; +using Sunny.UI; +using System; +using System.Threading; +using System.Windows; +using System.Windows.Forms; + +namespace 薄膜气体透过率测试仪 +{ + public class Function + { + ModbusMaster master; + IModbusMaster modbusMaster; + DataChange dc = new DataChange(); + public enum ButtonType + { + 复归型, + 切换型, + 置位型, + 复位型 + } + public enum DataType + { + 整形, + 浮点型 + } + public Function(ModbusMaster master_in) + { + this.master = master_in; + } + + public Function(IModbusMaster modbusMaster) + { + this.modbusMaster = modbusMaster; + } + + public void BtnClickFunction(ButtonType buttonType, ushort address) + { + try + { + switch (buttonType) + { + case ButtonType.复归型: + master.WriteSingleCoil(1, address, true); + Thread.Sleep(100); + master.WriteSingleCoil(1, address, false); + Thread.Sleep(100); + break; + case ButtonType.切换型: + if (master.ReadCoils(1, address, 1)[0]) + { + master.WriteSingleCoil(1, address, false); Thread.Sleep(100); + } + else + { master.WriteSingleCoil(1, address, true); Thread.Sleep(100); } + break; + case ButtonType.置位型: + master.WriteSingleCoil(1, address, true); + Thread.Sleep(100); + break; + case ButtonType.复位型: + master.WriteSingleCoil(1, address, false); + Thread.Sleep(100); + break; + default: + break; + } + } + catch (Exception ex) + { + + } + + } + + public void BtnClickFunctionForNew(ButtonType buttonType, ushort address) + { + try + { + switch (buttonType) + { + case ButtonType.复归型: + modbusMaster.WriteSingleCoil(1, address, true); + Thread.Sleep(100); + modbusMaster.WriteSingleCoil(1, address, false); + Thread.Sleep(100); + break; + case ButtonType.切换型: + if (modbusMaster.ReadCoils(1, address, 1)[0]) + { + modbusMaster.WriteSingleCoil(1, address, false); Thread.Sleep(100); + } + else + { modbusMaster.WriteSingleCoil(1, address, true); Thread.Sleep(100); } + break; + case ButtonType.置位型: + modbusMaster.WriteSingleCoil(1, address, true); + Thread.Sleep(100); + break; + case ButtonType.复位型: + modbusMaster.WriteSingleCoil(1, address, false); + Thread.Sleep(100); + break; + default: + break; + } + } + catch (Exception ex) + { + + } + + } + public void WriteToPLC(string inPutValue, ushort address, DataType dataType) + { + + try + { + switch (dataType) + { + case DataType.浮点型: + double value = inPutValue.ToDouble(); + if (UIInputDialog.ShowInputDoubleDialog(ref value, UIStyle.Inherited, desc: "请输入值", showMask: false)) + { + + master.WriteMultipleRegisters(1, address, dc.SplitFloatToUShortArray((float)value)); + } + break; + case DataType.整形: + int value_int = inPutValue.ToInt(); + if (UIInputDialog.ShowInputIntegerDialog(ref value_int, UIStyle.Inherited, desc: "请输入数据:")) + { + + master.WriteMultipleRegisters(1, address, dc.intToushorts(value_int)); + } + break; + default: + break; + } + } + catch (Exception ex) + { + MessageBox.Show("操作失败!" + "\n" + "\n" + ex.Message, "错误"); + } + + } + + public void WriteToPLCForNew(string inPutValue, ushort address, DataType dataType, bool isok=false,float max=0,float min=0) + { + try + { + //KeyboardHelper.ShowSoftKeyboard(); + switch (dataType) + { + case DataType.浮点型: + double value = inPutValue.ToDouble(); + + if (UIInputDialog.ShowInputDoubleDialog(ref value, UIStyle.Inherited, desc: "请输入值", showMask: false)) + { + if ( isok&&value > max || value < min) + { + MessageBox.Show("数据不正确"); + return; + } + modbusMaster.WriteMultipleRegisters(1, address, dc.SplitFloatToUShortArray((float)value)); + } + break; + case DataType.整形: + int value_int = inPutValue.ToInt(); + if (UIInputDialog.ShowInputIntegerDialog(ref value_int, UIStyle.Inherited, desc: "请输入数据:")) + { + + modbusMaster.WriteMultipleRegisters(1, address, dc.intToushorts(value_int)); + //if (isok) + //{ modbusMaster.WriteSingleCoil(1, 25, true); } + } + break; + default: + break; + } + + //KeyboardHelper.HideSoftKeyboard(); + } + catch (Exception ex) + { + MessageBox.Show("操作失败!" + "\n" + "\n" + ex.Message, "错误"); + } + + } + + public void WriteToPLCForNew(string inPutValue, ushort address, DataType dataType, int d) + { + try + { + //KeyboardHelper.ShowSoftKeyboard(); + switch (dataType) + { + case DataType.浮点型: + double value = inPutValue.ToDouble(); + + if (UIInputDialog.ShowInputDoubleDialog(ref value, UIStyle.Inherited, d, desc: "请输入值", showMask: false)) + { + + modbusMaster.WriteMultipleRegisters(1, address, dc.SplitFloatToUShortArray((float)value)); + } + break; + case DataType.整形: + int value_int = inPutValue.ToInt(); + if (UIInputDialog.ShowInputIntegerDialog(ref value_int, UIStyle.Inherited, desc: "请输入数据:")) + { + + modbusMaster.WriteMultipleRegisters(1, address, dc.intToushorts(value_int)); + //if (isok) + //{ modbusMaster.WriteSingleCoil(1, 25, true); } + } + break; + default: + break; + } + + //KeyboardHelper.HideSoftKeyboard(); + } + catch (Exception ex) + { + MessageBox.Show("操作失败!" + "\n" + "\n" + ex.Message, "错误"); + } + + } + + + + } +} + diff --git a/压差法气体渗透仪/NET_HRF4838_C#/Data/ModbusResourceManager.cs b/压差法气体渗透仪/NET_HRF4838_C#/Data/ModbusResourceManager.cs new file mode 100644 index 0000000..86ea0d9 --- /dev/null +++ b/压差法气体渗透仪/NET_HRF4838_C#/Data/ModbusResourceManager.cs @@ -0,0 +1,69 @@ +using System; +using Modbus.Device; +using System.Net.Sockets; + +namespace 薄膜气体透过率测试仪.Data +{ + // 单例模式:全局唯一资源管理器 + public class ModbusResourceManager + { + // 私有构造函数:禁止外部new + private ModbusResourceManager() { } + + // 唯一实例 + private static readonly Lazy _instance = new Lazy(() => new ModbusResourceManager()); + public static ModbusResourceManager Instance => _instance.Value; + + // 共享资源 + public TcpClient TcpClient { get; private set; } + public IModbusMaster ModbusMaster { get; private set; } + + // 初始化资源(在程序启动时调用,如MainWindow加载时) + public bool Init(string ip, int port) + { + try + { + // 先释放旧资源 + ReleaseResource(); + + // 创建新连接 + TcpClient = new TcpClient(); + TcpClient.Connect(ip, port); + ModbusMaster = ModbusIpMaster.CreateIp(TcpClient); + return true; + } + catch (Exception ex) + { + Console.WriteLine($"资源初始化失败:{ex.Message}"); + ReleaseResource(); + return false; + } + } + + public void Dispose() + { + try + { + ModbusMaster?.Dispose(); + TcpClient?.Close(); + } + catch + { + // 忽略清理时的异常 + } + } + // 释放资源(统一释放,避免重复关闭) + public void ReleaseResource() + { + ModbusMaster?.Dispose(); + ModbusMaster = null; + + //if (TcpClient?.Connected ?? false) + //{ + // TcpClient.Close(); + //} + TcpClient?.Dispose(); + TcpClient = null; + } + } +} \ No newline at end of file diff --git a/压差法气体渗透仪/NET_HRF4838_C#/Form1.cs b/压差法气体渗透仪/NET_HRF4838_C#/Form1.cs index 81f243d..09b3fed 100644 --- a/压差法气体渗透仪/NET_HRF4838_C#/Form1.cs +++ b/压差法气体渗透仪/NET_HRF4838_C#/Form1.cs @@ -1,24 +1,37 @@ -using System; +using Modbus.Device; +using Sunny.UI; +using System; +using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; -using System.Text; -using System.Windows.Forms; -using System.Collections; -using System.Runtime.InteropServices; -using System.Threading; -using System.Windows.Forms.DataVisualization.Charting; -using System.Net.Sockets; using System.Net; using System.Net.NetworkInformation; -using Sunny.UI; +using System.Net.Sockets; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; +using System.Windows.Forms.DataVisualization.Charting; +using 薄膜气体透过率测试仪; +using 薄膜气体透过率测试仪.Data; //新 namespace NET_HRF4826 { public partial class Form1 : UIForm { + private TcpClient _tcpClient => ModbusResourceManager.Instance.TcpClient; + private IModbusMaster _modbusMaster => ModbusResourceManager.Instance.ModbusMaster; + + Function ma; + DataChange c = new DataChange(); + private System.Windows.Forms.Timer _readtimer; + System.Windows.Forms.Timer Timer; + + private int smoothWindow = 100; // 平滑窗口大小,可根据需要调整 Thread threadReceiveData = new Thread(ReceiveData); static int waitbytes = 7000; UInt16 temp; @@ -65,12 +78,140 @@ namespace NET_HRF4826 // 新增:是否已开始记录有效数据 private bool hasValidDataStarted = false; + + + + // 存储从PLC读取的最新压力值(单位Pa) + private float? _plcLowPressurePa; + private float? _plcHighPressurePa; + private readonly object _plcPressureLock = new object(); + + + public Form1() { InitializeComponent(); } + + + private System.Windows.Forms.Timer InitTimer() + { + var timer = new System.Windows.Forms.Timer() + { + Interval = 80, + }; + timer.Tick += async (s, e) => + { + if (_modbusMaster != null) + { + try + { + await ReadDataAsync(); + } + catch { } + } + }; + return timer; + + } + + private async System.Threading.Tasks.Task ReadDataAsync() + { + try + { + if (_modbusMaster == null || _tcpClient == null || !_tcpClient.Connected) + return; + + // 高压:使用ReadFloatAsync读取浮点数 + var highPressureTask = ReadFloatAsync(1034, 2); + + // 低压:使用ReadDWordAsync读取32位整数 + var lowPressureTask = ReadDWordAsync(1140, null); // 低压地址假设为1140,请确认实际地址 + + await Task.WhenAll(highPressureTask, lowPressureTask); + + // 更新高压字段(单位假设为Pa) + if (highPressureTask.Result.HasValue) + { + lock (_plcPressureLock) + _plcHighPressurePa = highPressureTask.Result.Value; + } + + // 更新低压字段(从DWord读取的整数值,假设单位已经是Pa) + if (lowPressureTask.Result.HasValue) + { + lock (_plcPressureLock) + _plcLowPressurePa = lowPressureTask.Result.Value; // 直接使用整数值 + } + } + catch (Exception ex) + { + _readtimer?.Stop(); + Timer?.Stop(); + System.Diagnostics.Debug.WriteLine($"读取数据失败:{ex.Message}"); + } + } + + private async Task ReadFloatAsync(int address, int length) + { + try + { + ushort[] registers = await Task.Run(async () => + { + if (_modbusMaster == null) return null; + return await _modbusMaster.ReadHoldingRegistersAsync(1, (ushort)address, (ushort)length); + }); + if (registers != null && registers.Length >= 2) + { + return c.UshortToFloat(registers[1], registers[0]); + } + } + catch { } + return null; + } + + + + + + private async Task ReadDWordAsync(int startAddress, Label control, string format = "D", string unit = "") + { + try + { + // 32位 DWord 占用 2 个连续寄存器 + ushort[] registers = await Task.Run(async () => + { + if (_modbusMaster == null) return null; + return await _modbusMaster.ReadHoldingRegistersAsync(1, (ushort)startAddress, 2); + }); + + if (registers != null && registers.Length >= 2) + { + // 将两个16位寄存器组合成32位有符号整数 + // 注意寄存器顺序:通常是 低16位在前(D1140),高16位在后(D1141) + int value = (registers[1] << 16) | registers[0]; + + // 如果提供了control,则更新UI + if (control != null && !control.IsDisposed) + { + control.Invoke(new Action(() => + { + control.Text = value.ToString() + unit; + })); + } + + return value; + } + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"读取DWord地址{startAddress}失败:{ex.Message}"); + } + return null; + } + private void Read_Data() { // ★ 关键修复:复制 readdata 到本地缓冲区,避免被后台线程修改 @@ -171,25 +312,31 @@ namespace NET_HRF4826 if (shouldRecord) { if (!hasValidDataStarted) hasValidDataStarted = true; - // 记录当前采样点 + var record = new DataRecord { Timestamp = DateTime.Now, SampleIndex = sampleCounter++, AI = new float[] { volt[i,0], volt[i,1], volt[i,2], volt[i,3], - volt[i,4], volt[i,5], volt[i,6], volt[i,7], - volt[i,8], volt[i,9], volt[i,10], volt[i,11], - volt[i,12], volt[i,13], volt[i,14], volt[i,15] }, + volt[i,4], volt[i,5], volt[i,6], volt[i,7], + volt[i,8], volt[i,9], volt[i,10], volt[i,11], + volt[i,12], volt[i,13], volt[i,14], volt[i,15] }, DI = new byte[] { dat[i, 32], dat[i, 33], dat[i, 34], dat[i, 35] }, PulseCount = new uint[] { pulseCount0, pulseCount1, pulseCount2 }, Frequency = new uint[] { frequency0, frequency1, frequency2 } }; + // 从PLC字段获取当前压力值(单位Pa) + lock (_plcPressureLock) + { + record.LowPressureCalibrated = _plcLowPressurePa.HasValue ? (float)_plcLowPressurePa.Value : 0f; + record.HighPressureCalibrated = _plcHighPressurePa ?? 0f; + } + lock (dataLock) { allDataRecords.Add(record); totalRecordsAdded++; - // 调试输出(每1000条一次) if (totalRecordsAdded % 1000 == 0) { System.Diagnostics.Debug.WriteLine($"已添加 {totalRecordsAdded} 条记录,AI0电压={volt[i, 0]:F4}V"); @@ -206,21 +353,32 @@ namespace NET_HRF4826 } - // 更新UI(使用第一个采样点的数据) - // AI0 压力转换(0 ~ 100 kPa) - double pressure0 = volt[0, 0] * 10.0; - AI0_textBox.Text = pressure0.ToString("f3") + " kPa"; - // AI1 压力转换(0 ~ 600 kPa) - double pressure1 = volt[0, 1] * 60.0; - AI1_textBox.Text = pressure1.ToString("f3") + " kPa"; + + //// 更新UI(使用第一个采样点的数据) + //// AI0 压力转换(0 ~ 100 kPa) + //double pressure0 = volt[0, 0] * 10.0; + //AI0_textBox.Text = pressure0.ToString("f3") + " kPa"; + + //// AI1 压力转换(0 ~ 600 kPa) + //double pressure1 = volt[0, 1] * 60.0; + //AI1_textBox.Text = pressure1.ToString("f3") + " kPa"; + + + lock (_plcPressureLock) + { + if (_plcLowPressurePa.HasValue) + AI0_textBox.Text = _plcLowPressurePa.Value.ToString("F1") + " Pa"; + if (_plcHighPressurePa.HasValue) + AI1_textBox.Text = _plcHighPressurePa.Value.ToString("F1") + " kPa"; + } // 更新DI按钮状态(使用第一个采样点) DI0 = dat[0, 32]; DI1 = dat[0, 33]; DI2 = dat[0, 34]; DI3 = dat[0, 35]; - // ... DI按钮更新代码(略,保持原样)... + // 更新脉冲计数和频率文本框 PulseCount0_textBox.Text = Convert.ToUInt32((dat[0, 51] << 24) + (dat[0, 50] << 16) + (dat[0, 49] << 8) + dat[0, 48]).ToString(); @@ -705,6 +863,36 @@ namespace NET_HRF4826 Y = this.Height;//获取窗体的高度 setTag(this);//调用方法 + + + string plcIp = "192.168.1.10"; + //string plcIp = "127.0.0.1"; + bool initSuccess = ModbusResourceManager.Instance.Init(plcIp, 502); + if (!initSuccess) + { + MessageBox.Show("连接Modbus服务器失败!", "错误"); + this.Close(); + return; + } + + // 检查连接状态 + if (_tcpClient == null || !_tcpClient.Connected) + { + MessageBox.Show("Modbus连接异常!", "错误"); + this.Close(); + return; + } + ma = new Function(_modbusMaster); + + _readtimer = InitTimer(); + if (_modbusMaster != null) + { + _readtimer.Start(); + } + + + + } private float X;//当前窗体的宽度 @@ -785,8 +973,8 @@ namespace NET_HRF4826 { // 写入表头 var header = new List { "序号", "时间" }; - header.Add("低压(kPa)"); // AI0:0~100 kPa - header.Add("高压(kPa)"); // AI1:0~600 kPa + header.Add("低压(Pa)"); // AI0 + header.Add("高压(kPa)"); // AI1 for (int i = 2; i < 16; i++) header.Add($"AI{i}(V)"); for (int i = 0; i < 32; i++) header.Add($"DI{i}"); for (int i = 0; i < 3; i++) header.Add($"PulseCount{i}"); @@ -806,17 +994,15 @@ namespace NET_HRF4826 { if (i == 0) { - // AI0:0~100 kPa(与界面一致) - double pressure0 = record.AI[i] * 10.0; - row.Add(pressure0.ToString("F3")); + // 使用校准后的低压(Pa) + row.Add(record.LowPressureCalibrated.ToString("F1")); if (rowCount < 10) - System.Diagnostics.Debug.WriteLine($"记录{rowCount}: 索引={record.SampleIndex}, AI0电压={record.AI[i]:F4}V → 压力={pressure0:F3}kPa"); + System.Diagnostics.Debug.WriteLine($"记录{rowCount}: 低压校准值={record.LowPressureCalibrated:F1}Pa"); } else if (i == 1) { - // AI1:0~600 kPa - double pressure1 = record.AI[i] * 60.0; - row.Add(pressure1.ToString("F3")); + // 使用校准后的高压(Pa) + row.Add(record.HighPressureCalibrated.ToString("F1")); } else { @@ -855,5 +1041,8 @@ namespace NET_HRF4826 public byte[] DI { get; set; } = new byte[4]; // 4个字节的DI状态(共32位) public uint[] PulseCount { get; set; } = new uint[3]; // 3个脉冲计数器 public uint[] Frequency { get; set; } = new uint[3]; // 3个频率值 + + public float LowPressureCalibrated { get; set; } // 低压校准值(Pa) + public float HighPressureCalibrated { get; set; } // 高压校准值(Pa) } } \ No newline at end of file diff --git a/压差法气体渗透仪/NET_HRF4838_C#/NET_HRF4826.csproj b/压差法气体渗透仪/NET_HRF4838_C#/NET_HRF4826.csproj index 007ce14..ac48bef 100644 --- a/压差法气体渗透仪/NET_HRF4838_C#/NET_HRF4826.csproj +++ b/压差法气体渗透仪/NET_HRF4838_C#/NET_HRF4826.csproj @@ -58,6 +58,12 @@ + + ..\..\packages\Newtonsoft.Json.11.0.2\lib\net45\Newtonsoft.Json.dll + + + ..\..\packages\NModbus4.2.1.0\lib\net40\NModbus4.dll + ..\..\packages\SunnyUI.3.8.9\lib\net472\SunnyUI.dll @@ -79,6 +85,9 @@ + + + Form diff --git a/压差法气体渗透仪/NET_HRF4838_C#/packages.config b/压差法气体渗透仪/NET_HRF4838_C#/packages.config index a8c3390..e757169 100644 --- a/压差法气体渗透仪/NET_HRF4838_C#/packages.config +++ b/压差法气体渗透仪/NET_HRF4838_C#/packages.config @@ -1,5 +1,7 @@  + + \ No newline at end of file