This commit is contained in:
xyy
2026-03-09 09:45:59 +08:00
parent 16cd6f294e
commit e66ade7736
6 changed files with 609 additions and 31 deletions

View File

@@ -0,0 +1,74 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace
{
public class DataChange
{
/// <summary>
/// ushort转为float类型
/// </summary>
/// <param name="P1"></param>
/// <param name="P2"></param>
/// <returns>float型数据</returns>
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;
}
/// <summary>
/// ushort转为int类型
/// </summary>
/// <param name="u1"></param>
/// <param name="u2"></param>
/// <returns>返回int型数据</returns>
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;
}
/// <summary>
/// Float转为Ushort数组发送
/// </summary>
/// <param name="value"></param>
/// <returns>返回ushort数组</returns>
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;
}
/// <summary>
/// Int转为ushort数组发送
/// </summary>
/// <param name="res"></param>
/// <returns>返回ushort数组</returns>
public ushort[] intToushorts(int res)
{
ushort ust1 = (ushort)(res >> 16);
ushort ust2 = (ushort)res;
return new ushort[] { ust2, ust1 };
}
}
}

View File

@@ -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, "错误");
}
}
}
}

View File

@@ -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<ModbusResourceManager> _instance = new Lazy<ModbusResourceManager>(() => 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;
}
}
}

View File

@@ -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<float?> 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<int?> 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<string> { "序号", "时间" };
header.Add("低压(kPa)"); // AI00~100 kPa
header.Add("高压(kPa)"); // AI10~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)
{
// AI00100 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)
{
// AI10600 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
}
}

View File

@@ -58,6 +58,12 @@
<ItemGroup>
<Reference Include="Microsoft.VisualBasic" />
<Reference Include="Microsoft.VisualBasic.PowerPacks.Vs, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" />
<Reference Include="Newtonsoft.Json, Version=11.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\..\packages\Newtonsoft.Json.11.0.2\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="NModbus4, Version=2.1.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\NModbus4.2.1.0\lib\net40\NModbus4.dll</HintPath>
</Reference>
<Reference Include="SunnyUI, Version=3.8.9.0, Culture=neutral, PublicKeyToken=27d7d2e821d97aeb, processorArchitecture=MSIL">
<HintPath>..\..\packages\SunnyUI.3.8.9\lib\net472\SunnyUI.dll</HintPath>
</Reference>
@@ -79,6 +85,9 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Data\DataChange.cs" />
<Compile Include="Data\Function.cs" />
<Compile Include="Data\ModbusResourceManager.cs" />
<Compile Include="Form1.cs">
<SubType>Form</SubType>
</Compile>

View File

@@ -1,5 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Newtonsoft.Json" version="11.0.2" targetFramework="net48" />
<package id="NModbus4" version="2.1.0" targetFramework="net48" />
<package id="SunnyUI" version="3.8.9" targetFramework="net48" />
<package id="SunnyUI.Common" version="3.8.9" targetFramework="net48" />
</packages>