diff --git a/8255联机通讯协议_20251218131249.pdf b/8255联机通讯协议_20251218131249.pdf new file mode 100644 index 0000000..d0bf859 Binary files /dev/null and b/8255联机通讯协议_20251218131249.pdf differ diff --git a/Services/FiveHalfDmmService.cs b/Services/FiveHalfDmmService.cs new file mode 100644 index 0000000..5f178f0 --- /dev/null +++ b/Services/FiveHalfDmmService.cs @@ -0,0 +1,129 @@ +using System; +using System.IO.Ports; +using System.Threading.Tasks; + +namespace ASTM_D7896_Tester.Services +{ + public class FiveHalfDmmService : IDisposable + { + private SerialPort _serialPort; + private bool _disposed; + + public FiveHalfDmmService(string portName, int baudRate = 115200) + { + _serialPort = new SerialPort(portName, baudRate, Parity.None, 8, StopBits.One); + _serialPort.ReadTimeout = 2000; + _serialPort.WriteTimeout = 1000; + _serialPort.NewLine = "\n"; + } + + public void Open() + { + if (!_serialPort.IsOpen) + _serialPort.Open(); + } + + public void Close() + { + if (_serialPort.IsOpen) + _serialPort.Close(); + } + + public bool IsOpen => _serialPort?.IsOpen == true; + + private async Task QueryAsync(string command) + { + if (!_serialPort.IsOpen) throw new InvalidOperationException("串口未打开"); + // 使用 Task.Run 避免阻塞 UI 线程 + var tcs = new TaskCompletionSource(); + Task.Run(() => + { + try + { + _serialPort.WriteLine(command); + string result = _serialPort.ReadLine(); + tcs.SetResult(result); + } + catch (Exception ex) + { + tcs.SetException(ex); + } + }); + return await tcs.Task; + } + + private async Task SendCommandAsync(string command) + { + if (!_serialPort.IsOpen) throw new InvalidOperationException("串口未打开"); + await Task.Run(() => _serialPort.WriteLine(command)); + } + + /// + /// 配置为高速直流电压测量(FAST 分辨率) + /// + public async Task ConfigureHighSpeedDcvAsync() + { + await SendCommandAsync("CONFigure:VOLTage:DC AUTO"); + await SendCommandAsync("SENSe:VOLTage:DC:RESolution FAST"); + await SendCommandAsync("TRIGger:SOURce BUS"); // 使用软件触发 + await SendCommandAsync("TRIGger:DELay:AUTO OFF"); + await SendCommandAsync("TRIGger:DELay 0"); + } + + /// + /// 预置采样点数并进入等待触发状态 + /// + public async Task PrepareBatchAsync(int sampleCount) + { + await SendCommandAsync($"SAMPle:COUNt {sampleCount}"); + await SendCommandAsync("INITiate"); + } + + /// + /// 发送触发信号(*TRG) + /// + public async Task TriggerAsync() + { + await SendCommandAsync("*TRG"); + } + + /// + /// 获取批量采集结果(在 PrepareBatchAsync 和 TriggerAsync 之后调用) + /// + public async Task FetchBatchAsync() + { + string response = await QueryAsync("FETCh?"); + string[] parts = response.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + double[] values = new double[parts.Length]; + for (int i = 0; i < parts.Length; i++) + { + if (!double.TryParse(parts[i], System.Globalization.NumberStyles.Float, + System.Globalization.CultureInfo.InvariantCulture, out values[i])) + throw new Exception($"解析失败: {parts[i]}"); + } + return values; + } + + /// + /// 单次读取电压(用于实时监控) + /// + public async Task ReadVoltageAsync() + { + string resp = await QueryAsync("MEASure:VOLTage:DC?"); + if (double.TryParse(resp, System.Globalization.NumberStyles.Float, + System.Globalization.CultureInfo.InvariantCulture, out double value)) + return value; + throw new Exception($"无效响应: {resp}"); + } + + public void Dispose() + { + if (!_disposed) + { + Close(); + _serialPort?.Dispose(); + _disposed = true; + } + } + } +} \ No newline at end of file diff --git a/Services/LanScpiSocket.cs b/Services/LanScpiSocket.cs index ab41547..5bbab30 100644 --- a/Services/LanScpiSocket.cs +++ b/Services/LanScpiSocket.cs @@ -15,26 +15,10 @@ namespace ASTM_D7896_Tester.Services private NetworkStream _stream; private bool _disposed = false; - /// - /// 接收缓冲区大小(字节),建议至少 1MB 以支持大量数据 - /// - public int ReceiveBufferSize { get; set; } = 2 * 1024 * 1024; // 2 MB - - /// - /// 连接超时(毫秒) - /// + public int ReceiveBufferSize { get; set; } = 2 * 1024 * 1024; public int ConnectTimeoutMs { get; set; } = 3000; - - /// - /// 读写超时(毫秒) - /// public int ReadWriteTimeoutMs { get; set; } = 5000; - /// - /// 连接到 TH1963 - /// - /// 仪器 IP 地址,如 "192.168.1.241" - /// 端口号,默认 45454 public async Task ConnectAsync(string ipAddress, int port = 45454) { if (_tcpClient != null && _tcpClient.Connected) @@ -53,16 +37,12 @@ namespace ASTM_D7896_Tester.Services _stream.WriteTimeout = ReadWriteTimeoutMs; } - /// - /// 发送 SCPI 命令并等待响应(适用于查询类命令) - /// public async Task QueryAsync(string command) { EnsureConnected(); byte[] cmdBytes = Encoding.ASCII.GetBytes(command + "\n"); await _stream.WriteAsync(cmdBytes, 0, cmdBytes.Length); - // 读取响应,直到换行符 var responseBuilder = new StringBuilder(); byte[] buffer = new byte[4096]; int bytesRead; @@ -77,9 +57,6 @@ namespace ASTM_D7896_Tester.Services return responseBuilder.ToString().Trim(); } - /// - /// 发送命令但不等待响应(适用于设置类命令) - /// public async Task SendCommandAsync(string command) { EnsureConnected(); @@ -88,38 +65,60 @@ namespace ASTM_D7896_Tester.Services } /// - /// 配置为高速直流电压测量(用于批量采集) - /// 设置:0.02 PLC(≈400μs积分时间),自动量程,关闭自动归零 + /// 配置为高速直流电压测量(外部触发模式) /// public async Task ConfigureForHighSpeedDcvAsync() { await SendCommandAsync("CONF:VOLT:DC AUTO"); - await SendCommandAsync("VOLT:DC:NPLC 0.02"); // 最快速度 - await SendCommandAsync("VOLT:DC:ZERO:AUTO OFF"); // 提高速度 - await SendCommandAsync("TRIG:SOUR EXT"); // 外部触发(与PLC同步) + await SendCommandAsync("VOLT:DC:NPLC 0.02"); + await SendCommandAsync("VOLT:DC:ZERO:AUTO OFF"); + await SendCommandAsync("TRIG:SOUR EXT"); await SendCommandAsync("TRIG:DEL:AUTO OFF"); await SendCommandAsync("TRIG:DEL 0"); } /// - /// 批量采集指定数量的电压值(使用外部触发,需等待硬件触发信号) - /// 调用前必须已执行 ConfigureForHighSpeedDcvAsync 和 SAMP:COUN + /// 预置采样点数并进入等待触发状态 + /// + public async Task PrepareBatchAsync(int sampleCount) + { + await SendCommandAsync($"SAMP:COUN {sampleCount}"); + await SendCommandAsync("INIT"); // 等待外部触发 + } + + /// + /// 发送触发信号(软件触发,用于测试,但外部触发模式不使用) + /// 此方法仅用于 BUS 触发模式,当前配置为 EXT 触发,实际触发由硬件信号完成。 + /// 保留此方法以备将来切换触发模式。 + /// + public async Task TriggerAsync() + { + // 当前为外部触发模式,不需要软件触发;若需软件触发,请先执行 TRIG:SOUR BUS + // 保留空实现或抛出 NotSupportedException,根据需求选择 + // 为避免编译错误,提供一个空实现 + await Task.CompletedTask; + } + + /// + /// 批量采集(假设已经配置并等待外部触发完成) /// - /// 采样点数(最大 5000) - /// 电压数组(单位:伏特) public async Task AcquireBatchAsync(int sampleCount) { - // 预置采样点数 await SendCommandAsync($"SAMP:COUN {sampleCount}"); - // 进入等待触发状态 await SendCommandAsync("INIT"); - - // 等待采集完成(估算:sampleCount / 1000 秒 + 稳定时间) int waitMs = (int)(sampleCount / 1000.0 * 1000) + 200; await Task.Delay(waitMs); - - // 取回数据 string response = await QueryAsync("FETCh?"); + return ParseResponse(response, sampleCount); + } + + /// + /// 获取批量采集结果(在 PrepareBatchAsync 和外部触发之后调用) + /// + public async Task FetchBatchAsync() + { + string response = await QueryAsync("FETCh?"); + // 注意:此时不知道预期点数,但可以从返回数组长度得知 string[] parts = response.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); double[] values = new double[parts.Length]; for (int i = 0; i < parts.Length; i++) @@ -128,15 +127,24 @@ namespace ASTM_D7896_Tester.Services System.Globalization.CultureInfo.InvariantCulture, out values[i])) throw new Exception($"解析电压值失败: {parts[i]}"); } - if (values.Length != sampleCount) - throw new Exception($"期望 {sampleCount} 个点,实际收到 {values.Length}"); - return values; } - /// - /// 单次读取当前电压(非批量,用于实时监控) - /// + private double[] ParseResponse(string response, int expectedCount) + { + string[] parts = response.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + double[] values = new double[parts.Length]; + for (int i = 0; i < parts.Length; i++) + { + if (!double.TryParse(parts[i], System.Globalization.NumberStyles.Float, + System.Globalization.CultureInfo.InvariantCulture, out values[i])) + throw new Exception($"解析失败: {parts[i]}"); + } + if (values.Length != expectedCount) + throw new Exception($"期望 {expectedCount} 个点,实际收到 {values.Length}"); + return values; + } + public async Task ReadVoltageAsync() { string resp = await QueryAsync("MEAS:VOLT:DC?"); @@ -146,14 +154,7 @@ namespace ASTM_D7896_Tester.Services throw new Exception($"电压读数无效: {resp}"); } - /// - /// 复位仪器 - /// public async Task ResetAsync() => await SendCommandAsync("*RST"); - - /// - /// 查询仪器标识 - /// public async Task IdentifyAsync() => await QueryAsync("*IDN?"); private void EnsureConnected() @@ -162,7 +163,6 @@ namespace ASTM_D7896_Tester.Services throw new InvalidOperationException("未连接到 TH1963,请先调用 ConnectAsync"); } - // 实现 IDisposable public void Dispose() { if (!_disposed) diff --git a/ViewModels/D7896ViewModel.cs b/ViewModels/D7896ViewModel.cs index 65b475d..952501f 100644 --- a/ViewModels/D7896ViewModel.cs +++ b/ViewModels/D7896ViewModel.cs @@ -21,15 +21,32 @@ public partial class D7896ViewModel : ObservableObject private AppConfig _config; private readonly ReportService _reportService; - // ======================= 成员变量 ======================= - private Timer? _monitorTimer; // 后台监控定时器 - private readonly object _monitorLock = new(); // 监控更新锁 - private double _lastRawPressure = 0; // 上次压力原始值 - private double _lastRawTemperature = 0; // 上次温度原始值 + // 电压表服务 + private Th1963LanService _th1963Ustd; // 6位半测量标准电阻电压 U_std + private FiveHalfDmmService _fiveHalfUpt; // 5位半测量铂丝电压 U_pt - // ======================= UI绑定属性 ======================= + private CancellationTokenSource _testCts; // 用于停止测试 + private bool _stopRequested; + + // 后台监控定时器 + private Timer? _monitorTimer; + + // 常量: 标准电阻值 1Ω + private const double StandardResistor = 1.0; + + // 铂丝电阻温度系数 (纯铂) + private const double AlphaPt = 0.00385; // /°C + + // 加热功率 Q 计算相关 + private double _heatingCurrent; // 实际加热电流平均值 + private double _wireResistanceAvg; // 铂丝平均电阻 + + // 温升曲线数据 + [ObservableProperty] private string _curveTitle = "温升曲线"; + [ObservableProperty] private PlotModel _temperatureCurveModel; + + // UI 绑定属性 (与之前一致) public ObservableCollection ReferenceLiquids { get; } = new() { "蒸馏水", "甲苯", "乙二醇" }; - [ObservableProperty] private string _sampleId = "未命名样品"; [ObservableProperty] private double _testTemperature = 25.0; [ObservableProperty] private string _testDateTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); @@ -41,7 +58,6 @@ public partial class D7896ViewModel : ObservableObject [ObservableProperty] private double _averageThermalDiffusivity; [ObservableProperty] private double _averageVolumetricHeatCapacity; - // 测试条件(测试前设置) [ObservableProperty] private double _sampleVolume = 40.0; [ObservableProperty] private bool _bubbleRemoved = true; [ObservableProperty] private bool _usePressure = false; @@ -53,12 +69,10 @@ public partial class D7896ViewModel : ObservableObject [ObservableProperty] private bool _platinumCompatible = true; [ObservableProperty] private string _liquidReactivityNote = ""; - // 实时监控参数(测试前+测试中+测试后持续更新) - [ObservableProperty] private double _platinumResistance = 0.0; // 铂丝电阻(实时) - [ObservableProperty] private double _chamberPressure = 0.0; // 样品池压力(实时) - [ObservableProperty] private double _currentTestTemperature = 0.0; // 当前测试温度(实时) + [ObservableProperty] private double _platinumResistance = 0.0; + [ObservableProperty] private double _chamberPressure = 0.0; + [ObservableProperty] private double _currentTestTemperature = 0.0; - // 系统校准 [ObservableProperty] private bool _isCalibrating = false; [ObservableProperty] private string _calibrationStatus = ""; [ObservableProperty] private string _selectedReferenceLiquid = "蒸馏水"; @@ -66,145 +80,124 @@ public partial class D7896ViewModel : ObservableObject [ObservableProperty] private double _measuredConductivity = 0.0; [ObservableProperty] private double _calibrationErrorPercent = 0.0; - // 温升曲线 - [ObservableProperty] private string _curveTitle = "温升曲线"; - [ObservableProperty] private PlotModel _temperatureCurveModel; + // 实时电压显示(可选) + [ObservableProperty] private double _platinumVoltage; + [ObservableProperty] private double _standardResistorVoltage; - private Th1963LanService _th1963Upt; - private Th1963LanService _th1963Ustd; + [ObservableProperty] private double _sampleDensity = 1000.0; // 新增,密度默认值1000 kg/m³(水) - // ======================= 构造函数 ======================= public D7896ViewModel() { _config = App.PlcConfig ?? new AppConfig(); _plcService = App.PlcService; _reportService = new ReportService(_config.TestParameters.ReportOutputPath); - // 加载配置中的默认值 SampleVolume = _config.TestParameters.DefaultSampleVolume; UsePressure = _config.TestParameters.UsePressure; PressureValue = _config.TestParameters.DefaultPressure; SelectedReferenceLiquid = _config.TestParameters.ReferenceLiquid; ReferenceConductivity = _config.TestParameters.ReferenceConductivity; - // 前置确认项默认勾选 IsCleanConfirmed = true; BubbleRemoved = true; PlatinumCompatible = true; AmbientCalibrated = true; - // 启动后台监控 + // 初始化电压表服务 + // TH1963 IP 地址需要根据实际配置修改,建议从配置文件读取 + _th1963Ustd = new Th1963LanService(); + // 5位半万用表串口名,例如 "COM3" + _fiveHalfUpt = new FiveHalfDmmService("COM3", 115200); + StartBackgroundMonitoring(); } - // ======================= 后台监控系统 ======================= private async void StartBackgroundMonitoring() { - // 等待PLC连接 await Task.Delay(1000); - _monitorTimer = new Timer(async _ => await MonitorPlcValues(), null, 0, 1000); } private async Task MonitorPlcValues() { - if (!await _plcService.IsConnectedAsync()) - return; - - lock (_monitorLock) + if (!await _plcService.IsConnectedAsync()) return; + try { - try - { - // 1. 读取铂丝电阻值(D1422,0.001Ω单位) - float rawResistance = _plcService.ReadFloatAsync(_config.PlcRegisterAddresses.Resistance).Result; - double newResistance = rawResistance / 1000.0; - if (Math.Abs(newResistance - _lastRawPressure) > 0.0001) - { - _lastRawPressure = newResistance; - Application.Current.Dispatcher.Invoke(() => PlatinumResistance = newResistance); - } + float rawResistance = await _plcService.ReadFloatAsync(_config.PlcRegisterAddresses.Resistance); + double newResistance = rawResistance; + Application.Current.Dispatcher.Invoke(() => PlatinumResistance = newResistance); - // 2. 读取压力值(D1322,0.1kPa单位) - float rawPressure = _plcService.ReadFloatAsync(_config.PlcRegisterAddresses.Pressure).Result; + float rawPressure = await _plcService.ReadFloatAsync(_config.PlcRegisterAddresses.Pressure); + Application.Current.Dispatcher.Invoke(() => ChamberPressure = rawPressure); - _lastRawTemperature = rawPressure; - Application.Current.Dispatcher.Invoke(() => ChamberPressure = rawPressure); - - - // 3. 读取温度值(D1376,0.1℃单位) - float rawTemp = _plcService.ReadFloatAsync(_config.PlcRegisterAddresses.Temperature).Result; - - - Application.Current.Dispatcher.Invoke(() => CurrentTestTemperature = rawTemp); - - } - catch (Exception ex) - { - System.Diagnostics.Debug.WriteLine($"监控读取失败: {ex.Message}"); - } + float rawTemp = await _plcService.ReadFloatAsync(_config.PlcRegisterAddresses.Temperature); + Application.Current.Dispatcher.Invoke(() => CurrentTestTemperature = rawTemp); } + catch { } } - // ======================= 测试前:获取初始铂丝电阻 ======================= private async Task GetInitialResistanceAsync() { - if (!await _plcService.IsConnectedAsync()) - return 0; - + if (!await _plcService.IsConnectedAsync()) return 0; try { float rawResistance = await _plcService.ReadFloatAsync(_config.PlcRegisterAddresses.Resistance); return rawResistance / 1000.0; } - catch - { - return 0; - } + catch { return 0; } } - // ======================= 核心测试流程 ======================= [RelayCommand] private async Task StartTestAsync() { - // ========== 第1阶段:测试前置检查(标准7.1、7.6、1.4、8.1) ========== + if (IsTesting) + { + MessageBox.Show("测试正在进行中", "提示"); + return; + } + + // 前置检查 if (!IsCleanConfirmed || !BubbleRemoved || !PlatinumCompatible || !AmbientCalibrated) { - MessageBox.Show("请完成所有测试前确认项(清洁、排泡、铂兼容性、环境校准)。", "前置条件未满足"); + MessageBox.Show("请完成所有测试前确认项", "前置条件未满足"); return; } if (SampleVolume <= 0) { - MessageBox.Show("请输入有效的样品量(≥1 mL)。", "参数错误"); + MessageBox.Show("请输入有效的样品量", "参数错误"); return; } - - // 加压条件检查(标准1.8、附录A2) if (UsePressure && PressureValue <= 0) { - MessageBox.Show("请设置有效的加压值(>0 kPa)。", "参数错误"); + MessageBox.Show("请设置有效的加压值", "参数错误"); return; } - if (IsTesting) - { - MessageBox.Show("测试正在进行中,请稍后...", "提示"); - return; - } - - // 连接PLC + // 连接PLC和电压表 if (!await _plcService.IsConnectedAsync()) { - var connected = await _plcService.ConnectAsync(); - if (!connected) + if (!await _plcService.ConnectAsync()) { - MessageBox.Show("无法连接到PLC,请检查配置。", "错误"); + MessageBox.Show("无法连接到PLC", "错误"); return; } } - // ========== 第2阶段:加压控制(标准1.7-1.8、附录A2) ========== + try + { + if (!_fiveHalfUpt.IsOpen) _fiveHalfUpt.Open(); + await _th1963Ustd.ConnectAsync("192.168.1.12", 45454); // 改为实际IP + await _th1963Ustd.ConfigureForHighSpeedDcvAsync(); + await _fiveHalfUpt.ConfigureHighSpeedDcvAsync(); + } + catch (Exception ex) + { + MessageBox.Show($"电压表连接失败: {ex.Message}", "错误"); + return; + } + if (UsePressure) { StatusMessage = "正在加压..."; @@ -212,45 +205,63 @@ public partial class D7896ViewModel : ObservableObject await Task.Delay(3000); await UpdateRealTimeParametersAsync(); if (ChamberPressure < PressureValue - 5) - MessageBox.Show($"压力未达到设定值 {PressureValue} kPa,当前 {ChamberPressure:F1} kPa", "警告"); + MessageBox.Show($"压力未达到设定值 {PressureValue} kPa", "警告"); } - // ========== 第3阶段:获取初始铂丝电阻 R₀(用于校准) ========== double initialResistance = await GetInitialResistanceAsync(); if (initialResistance > 0) - { - StatusMessage = $"初始电阻已读取: {initialResistance:F4} Ω"; - } + StatusMessage = $"初始电阻: {initialResistance:F4} Ω"; - // ========== 第4阶段:执行10次重复测量(标准4.1、5.4) ========== Measurements.Clear(); IsTesting = true; - StatusMessage = "开始测试..."; + _stopRequested = false; + _testCts = new CancellationTokenSource(); try { for (int i = 1; i <= _config.TestParameters.MeasurementCount; i++) { + if (_stopRequested) break; + CurrentMeasurementIndex = i; StatusMessage = $"正在执行第 {i} 次测量..."; - // 4.1 发送加热脉冲(标准5.4:短时电流施加) + // 准备批量采集参数(每通道采样点数,采样率1000点/秒,加热时间1秒 -> 1000点) + int samples = 1000; // 1秒 * 1000点/秒 + + // 预配置两台表:进入等待触发状态 + await _th1963Ustd.PrepareBatchAsync(samples); + await _fiveHalfUpt.PrepareBatchAsync(samples); // 需要在FiveHalfDmmService中实现PrepareBatchAsync,见补充 + + // 启动加热脉冲 (PLC) await _plcService.WriteCoilAsync(_config.PlcRegisterAddresses.StartCommand, true); - //await Task.Delay(500); - //await _plcService.WriteCoilAsync(_config.PlcRegisterAddresses.StartCommand, false); + // 同时发送触发信号给两台电压表 + await Task.WhenAll(_th1963Ustd.TriggerAsync(), _fiveHalfUpt.TriggerAsync()); - // 4.2 等待测量完成(标准5.3:0.8秒测试时间) - await Task.Delay(800); + // 等待加热脉冲持续1秒 + await Task.Delay(1000); - // 4.3 读取计算结果(标准1.2:λ和α直接测量) - float lambda = await _plcService.ReadFloatAsync(_config.PlcRegisterAddresses.ThermalConductivity); - float alpha = await _plcService.ReadFloatAsync(_config.PlcRegisterAddresses.ThermalDiffusivity); + // 停止加热 + await _plcService.WriteCoilAsync(_config.PlcRegisterAddresses.StartCommand, false); - // 记录测试温度 - if (i == 1) TestTemperature = CurrentTestTemperature; + // 等待采集完成(留出额外时间) + await Task.Delay(500); - // 生成温升曲线 - GenerateTemperatureCurve(lambda, alpha); + // 获取采集数据 + double[] ustd = await _th1963Ustd.FetchBatchAsync(); + double[] upt = await _fiveHalfUpt.FetchBatchAsync(); + + // 构建时间数组(假设采样时间正好1秒,采样点数 = ustd.Length) + double[] timeArray = new double[ustd.Length]; + for (int idx = 0; idx < timeArray.Length; idx++) + { + timeArray[idx] = idx / (double)samples; // 0 ~ 1 秒 + } + // 计算本次测量的 λ 和 α + var (lambda, alpha, deltaT, coolingPoints) = ComputeThermalProperties(upt, ustd, timeArray, initialResistance, CurrentTestTemperature); + + // 生成温升曲线图 + GenerateTemperatureCurveFromData(timeArray, deltaT, coolingPoints); var result = new MeasurementResult { @@ -258,19 +269,17 @@ public partial class D7896ViewModel : ObservableObject ThermalConductivity = lambda, ThermalDiffusivity = alpha }; - result.CalculateVhc(); - + //result.CalculateVhc(); + result.CalculateVhcAndCp(SampleDensity); Application.Current.Dispatcher.Invoke(() => Measurements.Add(result)); StatusMessage = $"第 {i} 次测量完成,λ={lambda:F4} W/m·K"; - // 4.4 间隔30秒(标准5.4) - if (i < _config.TestParameters.MeasurementCount) + if (i < _config.TestParameters.MeasurementCount && !_stopRequested) await Task.Delay(_config.TestParameters.IntervalSeconds * 1000); } - // 计算平均值 CalculateAverages(); - StatusMessage = "测试完成。"; + StatusMessage = _stopRequested ? "测试已停止。" : "测试完成。"; } catch (Exception ex) { @@ -279,7 +288,8 @@ public partial class D7896ViewModel : ObservableObject } finally { - // ========== 第5阶段:测试结束后泄压 ========== + // 停止加热,泄压 + await _plcService.WriteCoilAsync(_config.PlcRegisterAddresses.StartCommand, false); if (UsePressure) { await _plcService.WriteCoilAsync(_config.PlcRegisterAddresses.InletValveCoil, false); @@ -288,89 +298,190 @@ public partial class D7896ViewModel : ObservableObject await _plcService.WriteCoilAsync(_config.PlcRegisterAddresses.OutletValveCoil, false); } IsTesting = false; + _fiveHalfUpt.Close(); + _th1963Ustd.Dispose(); + _testCts?.Dispose(); } } - [RelayCommand] - private async Task StopTestCommandAsync() + /// + /// 根据采集到的电压序列计算热导率 λ、热扩散率 α、温升数组以及冷却曲线数据点 + /// + private (double lambda, double alpha, double[] deltaT, List coolingPoints) ComputeThermalProperties( + double[] upt, double[] ustd, double[] time, double initialResistance, double bathTemp) { - if (IsTesting) + int n = Math.Min(upt.Length, ustd.Length); + double[] current = new double[n]; + double[] ptResistance = new double[n]; + double[] deltaT = new double[n]; + + // 1. 计算电流、铂丝电阻和温升 + for (int i = 0; i < n; i++) { - IsTesting = false; - StatusMessage = "已停止测试。"; - } - if (!IsTesting) - { - MessageBox.Show("没有正在进行的测试。", "提示"); - return; + current[i] = ustd[i] / StandardResistor; + ptResistance[i] = upt[i] / current[i]; + deltaT[i] = (ptResistance[i] - initialResistance) / (AlphaPt * initialResistance); } - await Task.Delay(500); - await _plcService.WriteCoilAsync(_config.PlcRegisterAddresses.StartCommand, false); - // 泄压 - if (UsePressure) + // 2. ========== 加热段拟合 → 热导率 λ ========== + double tStart = 0.1; + double tEndHeating = 0.8; + int startIdx = FindIndex(time, tStart); + int endIdxHeating = FindIndex(time, tEndHeating); + if (startIdx < 0) startIdx = 0; + if (endIdxHeating >= n) endIdxHeating = n - 1; + + var heatingPoints = new List(); + for (int i = startIdx; i <= endIdxHeating; i++) { - await _plcService.WriteCoilAsync(_config.PlcRegisterAddresses.InletValveCoil, false); - await _plcService.WriteCoilAsync(_config.PlcRegisterAddresses.OutletValveCoil, true); - await Task.Delay(1000); - await _plcService.WriteCoilAsync(_config.PlcRegisterAddresses.OutletValveCoil, false); + heatingPoints.Add(new DataPoint(Math.Log(time[i]), deltaT[i])); } - IsTesting = false; - StatusMessage = "测试已停止。"; + + double slope = LeastSquaresSlope(heatingPoints); + if (slope <= 0) slope = 0.0001; + + double avgCurrentSq = current.Average(c => c * c); + double avgResistance = ptResistance.Average(); + double powerPerLength = (avgCurrentSq * avgResistance) / _config.TestParameters.PlatinumWireLength; + double lambda = powerPerLength / (4 * Math.PI * slope); + + // 3. ========== 冷却段拟合 → 热扩散率 α ========== + double coolingStart = 1.0; + double coolingEnd = 2.0; + int coolingStartIdx = FindIndex(time, coolingStart); + int coolingEndIdx = FindIndex(time, coolingEnd); + if (coolingStartIdx < 0) coolingStartIdx = n / 2; + if (coolingEndIdx >= n) coolingEndIdx = n - 1; + + var coolingPointsForFit = new List(); + for (int i = coolingStartIdx; i <= coolingEndIdx; i++) + { + if (deltaT[i] > 0.001) + coolingPointsForFit.Add(new DataPoint(time[i], Math.Log(deltaT[i]))); + } + + double coolingSlope = LeastSquaresSlopeOnTime(coolingPointsForFit); + double tau = -1.0 / coolingSlope; + double wireRadius = 0.00003; // 半径 = 直径0.06mm /2 + double alpha = (wireRadius * wireRadius) / (4.0 * tau); + if (alpha <= 0 || double.IsNaN(alpha) || double.IsInfinity(alpha)) + alpha = 0.12e-6; // 默认值 + + // 准备冷却曲线数据点(用于绘图) + var coolingPoints = new List(); + for (int i = coolingStartIdx; i <= coolingEndIdx; i++) + { + if (deltaT[i] > 0.001) + coolingPoints.Add(new DataPoint(time[i], deltaT[i])); + } + + return (lambda, alpha, deltaT, coolingPoints); } - // ======================= 辅助方法 ======================= - private async Task UpdateRealTimeParametersAsync() + /// + /// 查找时间数组中与目标时间最接近的索引 + /// + private int FindIndex(double[] timeArray, double targetTime) { - if (!await _plcService.IsConnectedAsync()) - return; - - try + for (int i = 0; i < timeArray.Length; i++) { - // 压力(D1322,0.1kPa单位) - float rawPressure = await _plcService.ReadFloatAsync(_config.PlcRegisterAddresses.Pressure); - ChamberPressure = rawPressure / 10.0; - } - catch (Exception ex) - { - StatusMessage = $"实时参数读取失败: {ex.Message}"; + if (timeArray[i] >= targetTime) + return i; } + return timeArray.Length - 1; } - private void GenerateTemperatureCurve(float lambda, float alpha) + /// + /// 最小二乘法拟合斜率 (X轴为横坐标,Y轴为纵坐标) — 用于加热段 ln(t) vs ΔT + /// + private double LeastSquaresSlope(List points) + { + if (points.Count < 2) return 0.001; + double sumX = 0, sumY = 0, sumXY = 0, sumX2 = 0; + foreach (var p in points) + { + sumX += p.X; + sumY += p.Y; + sumXY += p.X * p.Y; + sumX2 += p.X * p.X; + } + double n = points.Count; + double denominator = n * sumX2 - sumX * sumX; + if (Math.Abs(denominator) < 1e-10) return 0.001; + double slope = (n * sumXY - sumX * sumY) / denominator; + return slope; + } + + /// + /// 最小二乘法拟合斜率 (X轴为时间t,Y轴为 ln(ΔT)) — 用于冷却段 + /// + private double LeastSquaresSlopeOnTime(List points) + { + if (points.Count < 2) return -1.0; + double sumX = 0, sumY = 0, sumXY = 0, sumX2 = 0; + foreach (var p in points) + { + sumX += p.X; + sumY += p.Y; + sumXY += p.X * p.Y; + sumX2 += p.X * p.X; + } + double n = points.Count; + double denominator = n * sumX2 - sumX * sumX; + if (Math.Abs(denominator) < 1e-10) return -1.0; + double slope = (n * sumXY - sumX * sumY) / denominator; + return slope; + } + + private void GenerateTemperatureCurveFromData(double[] time, double[] deltaT, List coolingPoints) { if (TemperatureCurveModel == null) { - TemperatureCurveModel = new PlotModel { Title = "温升曲线对比", Background = OxyColors.White }; - TemperatureCurveModel.Axes.Add(new LinearAxis { Position = AxisPosition.Bottom, Title = "时间 (s)", Minimum = 0, Maximum = 2 }); - TemperatureCurveModel.Axes.Add(new LinearAxis { Position = AxisPosition.Left, Title = "温升 (℃)", Minimum = 0 }); + TemperatureCurveModel = new PlotModel { Title = "温升与冷却曲线", Background = OxyColors.White }; + TemperatureCurveModel.Axes.Add(new LinearAxis { Position = AxisPosition.Bottom, Title = "时间 (s)" }); + TemperatureCurveModel.Axes.Add(new LinearAxis { Position = AxisPosition.Left, Title = "温升 (℃)" }); } - var series = new LineSeries + // 加热段曲线(红色) + var heatingSeries = new LineSeries { - Title = $"第{CurrentMeasurementIndex}次测量", - Color = GetColorForIndex(CurrentMeasurementIndex), + Title = $"第{CurrentMeasurementIndex}次测量 - 加热段", + Color = OxyColors.Red, StrokeThickness = 1.5 }; - - // 理论公式 ΔT = (Q/(4πλL)) * ln(t/t0) + C - double Q = 0.01; - double L = _config.TestParameters.PlatinumWireLength; - double constant = 0.2; - for (int i = 1; i <= 200; i++) + for (int i = 0; i < time.Length && time[i] <= 1.0; i++) { - double t = i * 0.01; - double deltaT = (Q / (4 * Math.PI * lambda * L)) * Math.Log(t + 0.1) + constant; - series.Points.Add(new DataPoint(t, deltaT)); + heatingSeries.Points.Add(new DataPoint(time[i], deltaT[i])); } - TemperatureCurveModel.Series.Add(series); + TemperatureCurveModel.Series.Add(heatingSeries); + + // 冷却曲线(蓝色虚线) + if (coolingPoints != null && coolingPoints.Count > 0) + { + var coolingSeries = new LineSeries + { + Title = $"第{CurrentMeasurementIndex}次测量 - 冷却段", + Color = OxyColors.Blue, + StrokeThickness = 1.5, + LineStyle = LineStyle.Dash + }; + foreach (var p in coolingPoints) + { + coolingSeries.Points.Add(p); + } + TemperatureCurveModel.Series.Add(coolingSeries); + } + TemperatureCurveModel.InvalidatePlot(true); CurveTitle = $"已完成 {CurrentMeasurementIndex} 次测量"; } + private void GenerateTemperatureCurve(float lambda, float alpha) { /* 旧方法,不再使用 */ } + private OxyColor GetColorForIndex(int index) { - var colors = new[] { OxyColors.Red, OxyColors.Blue, OxyColors.Green, OxyColors.Orange, OxyColors.Purple, OxyColors.Brown, OxyColors.Pink, OxyColors.Cyan, OxyColors.Magenta, OxyColors.Olive }; + var colors = new[] { OxyColors.Red, OxyColors.Blue, OxyColors.Green, OxyColors.Orange, + OxyColors.Purple, OxyColors.Brown, OxyColors.Pink, OxyColors.Cyan, OxyColors.Magenta, OxyColors.Olive }; return colors[(index - 1) % colors.Length]; } @@ -382,7 +493,6 @@ public partial class D7896ViewModel : ObservableObject AverageVolumetricHeatCapacity = Measurements.Average(m => m.VolumetricHeatCapacity); } - // ======================= UI命令 ======================= [RelayCommand] private void Reset() { @@ -399,7 +509,7 @@ public partial class D7896ViewModel : ObservableObject { if (Measurements.Count == 0) { - MessageBox.Show("没有测试数据,请先执行测试。", "提示"); + MessageBox.Show("没有测试数据", "提示"); return; } try @@ -416,56 +526,65 @@ public partial class D7896ViewModel : ObservableObject ["AmbientCalibrated"] = AmbientCalibrated, ["PlatinumCompatible"] = PlatinumCompatible, ["LiquidReactivityNote"] = LiquidReactivityNote, - ["InitialResistance"] = PlatinumResistance // 测试前的初始电阻 + ["InitialResistance"] = PlatinumResistance }; string reportPath = await _reportService.GenerateReportAsync(SampleId, TestTemperature, Measurements.ToList(), AverageThermalConductivity, AverageThermalDiffusivity, AverageVolumetricHeatCapacity, _config.TestParameters, extraParams); - MessageBox.Show($"报告已生成:{reportPath}", "成功"); + MessageBox.Show($"报告已生成: {reportPath}", "成功"); } catch (Exception ex) { - MessageBox.Show($"生成报告失败:{ex.Message}", "错误"); + MessageBox.Show($"生成报告失败: {ex.Message}", "错误"); } } - // 控制命令 [RelayCommand] - private async Task PressureCalibrationAsync() + private async Task StopTestCommandAsync() { - await _plcService.WriteCoilAsync(_config.PlcRegisterAddresses.PressureCalibrationCoil, true); - } - [RelayCommand] - private async Task ResistanceZeroAsync() - { - await _plcService.WriteCoilAsync(_config.PlcRegisterAddresses.ResistanceZeroCoil, true); + if (!IsTesting) + { + MessageBox.Show("没有正在进行的测试", "提示"); + return; + } + _stopRequested = true; + _testCts?.Cancel(); + StatusMessage = "正在停止测试..."; + await _plcService.WriteCoilAsync(_config.PlcRegisterAddresses.StartCommand, false); + if (UsePressure) + { + await _plcService.WriteCoilAsync(_config.PlcRegisterAddresses.InletValveCoil, false); + await _plcService.WriteCoilAsync(_config.PlcRegisterAddresses.OutletValveCoil, true); + await Task.Delay(1000); + await _plcService.WriteCoilAsync(_config.PlcRegisterAddresses.OutletValveCoil, false); + } + IsTesting = false; + StatusMessage = "测试已停止。"; } + [RelayCommand] private async Task PressureCalibrationAsync() => await _plcService.WriteCoilAsync(_config.PlcRegisterAddresses.PressureCalibrationCoil, true); + [RelayCommand] private async Task ResistanceZeroAsync() => await _plcService.WriteCoilAsync(_config.PlcRegisterAddresses.ResistanceZeroCoil, true); [RelayCommand] private async Task InletValveControlAsync() { - await EnsureConnected(); bool current = await _plcService.ReadCoilAsync(_config.PlcRegisterAddresses.InletValveCoil); await _plcService.WriteCoilAsync(_config.PlcRegisterAddresses.InletValveCoil, !current); StatusMessage = $"进气阀已{(current ? "关闭" : "开启")}"; } - [RelayCommand] private async Task OutletValveControlAsync() { - await EnsureConnected(); bool current = await _plcService.ReadCoilAsync(_config.PlcRegisterAddresses.OutletValveCoil); await _plcService.WriteCoilAsync(_config.PlcRegisterAddresses.OutletValveCoil, !current); StatusMessage = $"排气阀已{(current ? "关闭" : "开启")}"; } - [RelayCommand] private void ConfirmBubbleRemoved() => BubbleRemoved = true; [RelayCommand] private void ConfirmClean() { if (string.IsNullOrWhiteSpace(CleanerName)) { - MessageBox.Show("请输入清洁人员姓名。", "提示"); + MessageBox.Show("请输入清洁人员姓名", "提示"); return; } IsCleanConfirmed = true; @@ -480,11 +599,20 @@ public partial class D7896ViewModel : ObservableObject AmbientCalibrated = true; StatusMessage = $"环境温度校准完成:{AmbientTemperature:F1} °C"; } - [RelayCommand] private async Task PerformSystemCalibrationAsync() { /* 系统校准逻辑 */ } - + [RelayCommand] private async Task PerformSystemCalibrationAsync() { /* 系统校准逻辑待实现 */ } private async Task EnsureConnected() { if (!await _plcService.IsConnectedAsync()) await _plcService.ConnectAsync(); } + private async Task UpdateRealTimeParametersAsync() + { + if (!await _plcService.IsConnectedAsync()) return; + try + { + float rawPressure = await _plcService.ReadFloatAsync(_config.PlcRegisterAddresses.Pressure); + ChamberPressure = rawPressure / 10.0; + } + catch { } + } } \ No newline at end of file diff --git a/ViewModels/MeasurementResult.cs b/ViewModels/MeasurementResult.cs index 5a30f66..c456edd 100644 --- a/ViewModels/MeasurementResult.cs +++ b/ViewModels/MeasurementResult.cs @@ -13,14 +13,27 @@ public partial class MeasurementResult : ObservableObject [ObservableProperty] private double _thermalDiffusivity; // ×10⁻⁶ m²/s + [ObservableProperty] + private double _specificHeatCapacity; // 比热容 J/(kg·K) + + public void CalculateVhcAndCp(double density) + { + CalculateVhc(); // 先计算 VHC + if (density > 0) + SpecificHeatCapacity = VolumetricHeatCapacity * 1000 / density; // 注意单位转换 + else + SpecificHeatCapacity = 0; + } + [ObservableProperty] private double _volumetricHeatCapacity; // kJ/m³·K (自动计算) public void CalculateVhc() { - if (_thermalDiffusivity > 0) - VolumetricHeatCapacity = _thermalConductivity / (_thermalDiffusivity * 1e-6) / 1000.0; + if (ThermalDiffusivity > 0) + VolumetricHeatCapacity = ThermalConductivity / (ThermalDiffusivity * 1e-6) / 1000.0; else VolumetricHeatCapacity = 0; } + } \ No newline at end of file diff --git a/Views/D7896View.xaml b/Views/D7896View.xaml index d4189d2..914c3f2 100644 --- a/Views/D7896View.xaml +++ b/Views/D7896View.xaml @@ -180,11 +180,16 @@ - + + + + + + @@ -245,9 +250,7 @@ IsEnabled="{Binding IsTesting, Converter={StaticResource InverseBooleanConverter}}" Background="{StaticResource PrimaryButtonBrush}"/> -