This commit is contained in:
xyy
2026-05-29 18:35:36 +08:00
parent a888691151
commit 5e72a9aefa
4 changed files with 164 additions and 61 deletions

View File

@@ -46,7 +46,7 @@ public class TestParameters
public int MeasurementCount { get; set; } = 10;
[Range(5, 300)]
public int IntervalSeconds { get; set; } = 30;
public double PlatinumWireLength { get; set; } = 0.04;
public double PlatinumWireLength { get; set; } = 0.056; // 默认 5.6 cm
public double PlatinumWireDiameter { get; set; } = 0.00006;
public string ReportOutputPath { get; set; } = "Reports\\";
@@ -60,6 +60,18 @@ public class TestParameters
public CalibrationCoefficients CalibrationCoefficients { get; set; } = new();
// 可选:强制使用固定的物性值,便于测试和校准
public bool UseFixedAlpha { get; set; } = false;
public double FixedAlpha { get; set; } = 1.4e-7; // m^2/s
public bool UseFixedLambda { get; set; } = false;
public double FixedLambda { get; set; } = 0.606; // W/(m·K)
public double FitStartTime { get; set; } = 0.01; // 默认0.35秒,避开早期扰动
public double FitEndTime { get; set; } = 0.1;
}

View File

@@ -8,7 +8,9 @@ using OxyPlot.Axes;
using OxyPlot.Series;
using System;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
@@ -87,7 +89,8 @@ public partial class D7896ViewModel : ObservableObject
private const double EulerGamma = 0.5772156649; // 欧拉常数
private const double WireRadius = 0.00003; // 铂丝半径 (0.03 mm)
//private const double WireRadius = 0.00003; // 铂丝半径 (0.03 mm)
private const double WireRadius = 0.00010; // 铂丝半径 (0.03 mm)
[ObservableProperty] private double _sampleDensity = 1000.0; // 新增密度默认值1000 kg/m³
int samples = 1000; // 1秒 * 1000点/秒
double heatingDuration = 1; // 加热时间 0.8 秒(需与您的加热脉冲宽度一致)
@@ -273,46 +276,88 @@ public partial class D7896ViewModel : ObservableObject
CurrentMeasurementIndex = i;
StatusMessage = $"正在执行第 {i} 次测量...";
// === 正式加热与采集 ===
// --- 步骤1基线采集加热前---
await _th1963Ustd.PrepareBatchAsync(50);
await _th1953Ustd.PrepareBatchAsync(50);
await Task.WhenAll(_th1963Ustd.TriggerAsync(), _th1953Ustd.TriggerAsync());
await Task.Delay(100);
double[] ustdBase = await _th1963Ustd.FetchBatchAsync();
double[] uptBase = await _th1953Ustd.FetchBatchAsync();
double dynamicR0 = 2.45; // 默认值
if (ustdBase != null && ustdBase.Length > 0 && uptBase != null && uptBase.Length > 0)
{
double sumR0 = 0; int cnt = 0;
for (int j = 0; j < ustdBase.Length; j++)
{
if (ustdBase[j] > 0.01 && uptBase[j] > 0.01)
{
sumR0 += uptBase[j] / ustdBase[j];
cnt++;
}
}
if (cnt > 0)
{
dynamicR0 = sumR0 / cnt;
Logger.Log($"基线采集 R0 = {dynamicR0:F6} Ω (有效点数: {cnt})");
}
else
{
Logger.Log("基线采集数据无效(电压过低),使用加热前瞬间点");
}
}
else
{
Logger.Log("基线采集未返回数据,将使用加热段早期点");
}
// --- 步骤2正式加热采集 ---
await _th1963Ustd.PrepareBatchAsync(samples);
await _th1953Ustd.PrepareBatchAsync(samples);
// 启动加热脉冲 (PLC)
await _plcService.WriteCoilAsync(_config.PlcRegisterAddresses.StartCommand, true);
try { await Task.Delay(5, _testCts.Token); } catch (OperationCanceledException) { break; }
// 触发采集
await Task.WhenAll(_th1963Ustd.TriggerAsync(), _th1953Ustd.TriggerAsync());
// 等待加热结束
try { await Task.Delay((int)(heatingDuration * 1000), _testCts.Token); } catch (OperationCanceledException) { break; }
// 停止加热
await _plcService.WriteCoilAsync(_config.PlcRegisterAddresses.StartCommand, false);
// 等待采集完成
int remainingMs = (int)((totalDuration - heatingDuration) * 1000) + 100;
try { await Task.Delay(remainingMs, _testCts.Token); } catch (OperationCanceledException) { break; }
// 获取采集数据
double[] ustd = await _th1963Ustd.FetchBatchAsync();
double[] upt = await _th1953Ustd.FetchBatchAsync();
// 动态计算初始电阻 R0取第2~11点跳过初始扰动
double sumR0 = 0;
int r0Cnt = 0;
for (int j = 2; j < Math.Min(12, ustd.Length); j++)
if (dynamicR0 == 2.45) // 仍为默认值,说明基线无效
{
if (ustd[j] > 0.01 && upt[j] > 0.01)
double sumR0 = 0; int cnt = 0;
// 改用第 10 到 19 点共10个点避开前10个点的干扰
for (int j = 10; j < Math.Min(20, ustd.Length); j++)
{
sumR0 += upt[j] / ustd[j];
r0Cnt++;
if (ustd[j] > 0.01 && upt[j] > 0.01)
{
sumR0 += upt[j] / ustd[j];
cnt++;
}
}
if (cnt > 0)
{
dynamicR0 = sumR0 / cnt;
Logger.Log($"使用加热段第10~19点计算 R0 = {dynamicR0:F6} Ω");
}
}
double dynamicR0 = r0Cnt > 0 ? sumR0 / r0Cnt : 2.34;
Logger.Log($"动态计算 R0 = {dynamicR0:F6} Ω (有效点数: {r0Cnt})");
@@ -398,7 +443,6 @@ public partial class D7896ViewModel : ObservableObject
private (double lambda, double alpha, double[] deltaT, List<DataPoint> coolingPoints) ComputeThermalProperties(
double[] upt, double[] ustd, double[] time, double initialResistance, double bathTemp)
{
@@ -406,12 +450,14 @@ public partial class D7896ViewModel : ObservableObject
double[] deltaT = new double[n];
double[] ptResistance = new double[n];
double[] current = new double[n];
double tStart = _config.TestParameters.FitStartTime;
double tEnd = _config.TestParameters.FitEndTime;
// 1. 瞬时计算
// 1. 瞬时计算(先用传入的 initialResistance
for (int i = 0; i < n; i++)
{
current[i] = ustd[i] / StandardResistor;
if (current[i] > 0.001) // 降低阈值到 1mA
if (current[i] > 0.001)
{
ptResistance[i] = upt[i] / current[i];
deltaT[i] = (ptResistance[i] - initialResistance) / (AlphaPt * initialResistance);
@@ -423,7 +469,7 @@ public partial class D7896ViewModel : ObservableObject
}
}
// 1.5 滑动平均平滑窗口5
// 滑动平均平滑窗口5
double[] smoothDeltaT = new double[n];
for (int i = 0; i < n; i++)
{
@@ -435,22 +481,16 @@ public partial class D7896ViewModel : ObservableObject
smoothDeltaT[i] = cnt > 0 ? sum / cnt : double.NaN;
}
// 2. 寻找温升峰值点(使用平滑后的数据)
int peakIdx = 5;
// 寻找温升峰值点
double maxDeltaT = 0;
for (int i = 5; i < n; i++)
{
if (!double.IsNaN(smoothDeltaT[i]) && smoothDeltaT[i] > maxDeltaT)
{
maxDeltaT = smoothDeltaT[i];
peakIdx = i;
}
}
Logger.Log($"最大温升 = {maxDeltaT:F4} ℃");
// 3. 固定时间窗口0.1 ~ 0.8 秒),避开初始扰动和冷却段
double tStart = 0.15;
double tEnd = 0.4;
// 拟合窗口
int startIdx = FindIndex(time, tStart);
int endIdx = FindIndex(time, tEnd);
if (startIdx < 0) startIdx = 5;
@@ -458,13 +498,12 @@ public partial class D7896ViewModel : ObservableObject
if (endIdx <= startIdx) endIdx = Math.Min(startIdx + 50, n - 1);
Logger.Log($"拟合窗口: startIdx={startIdx}, endIdx={endIdx}, 点数={endIdx - startIdx + 1}");
// 4. 收集拟合点
// 收集拟合点
var points = new List<DataPoint>();
for (int i = startIdx; i <= endIdx; i++)
{
if (i <= startIdx + 5) // 只打印前5个点
Logger.Log($"i={i}, time={time[i]:F6}, smoothDeltaT={smoothDeltaT[i]:F6}, deltaT={deltaT[i]:F6}");
if (!double.IsNaN(smoothDeltaT[i]) && smoothDeltaT[i] > 0 && time[i] > 0)
points.Add(new DataPoint(Math.Log(time[i]), smoothDeltaT[i]));
}
@@ -482,35 +521,41 @@ public partial class D7896ViewModel : ObservableObject
return (0, 0, deltaT, new List<DataPoint>());
}
// 打印前10个拟合点
foreach (var p in points.Take(10))
Logger.Log($"ln(t)={p.X:F4}, ΔT={p.Y:F4}");
// 5. 计算功率
double sumPower = 0;
int validCount = 0;
// 计算功率密度
double sumI = 0, sumR = 0;
int cntI = 0, cntR = 0;
for (int i = startIdx; i <= endIdx; i++)
{
if (!double.IsNaN(ptResistance[i]))
{
sumPower += current[i] * current[i] * ptResistance[i];
validCount++;
}
if (!double.IsNaN(current[i]) && Math.Abs(current[i]) > 1e-12) { sumI += current[i]; cntI++; }
if (!double.IsNaN(ptResistance[i]) && ptResistance[i] > 1e-12) { sumR += ptResistance[i]; cntR++; }
}
if (validCount == 0) return (0, 0, deltaT, new List<DataPoint>());
double avgPower = sumPower / validCount;
double wireLength = _config.TestParameters.PlatinumWireLength;
double powerPerLength = avgPower / wireLength;
if (cntI == 0 || cntR == 0) return (0, 0, deltaT, new List<DataPoint>());
double avgCurrent = sumI / cntI;
double avgResistance = sumR / cntR;
double wireLength = Math.Max(1e-12, _config.TestParameters.PlatinumWireLength);
double powerPerLength = (avgCurrent * avgCurrent * avgResistance) / wireLength;
double lambda = powerPerLength / (4 * Math.PI * slope);
Logger.Log($"功率密度 = {powerPerLength:F3} W/m, 斜率 B = {slope:F5}");
if (_config.TestParameters.UseFixedLambda)
{
lambda = _config.TestParameters.FixedLambda;
Logger.Log($"使用固定 lambda={lambda:F6} W/(m·K)");
}
Logger.Log($"constantCurrent(avg)={avgCurrent:E6} A, avgResistance={avgResistance:F6} Ω, powerPerLength={powerPerLength:E6} W/m, 斜率 B = {slope:F5}");
// 6. α 计算
// 计算热扩散率
double exponent = intercept / slope + EulerGamma;
if (exponent > 30) exponent = 30;
double alpha = (WireRadius * WireRadius / 4.0) * Math.Exp(exponent);
if (_config.TestParameters.UseFixedAlpha)
{
alpha = _config.TestParameters.FixedAlpha;
Logger.Log($"使用固定 alpha={alpha:E6} m²/s");
}
if (alpha <= 0 || double.IsNaN(alpha) || alpha > 1e-5)
{
Logger.Log($"警告:α 计算异常 ({alpha:E}),数据可能不可靠");
@@ -526,11 +571,27 @@ public partial class D7896ViewModel : ObservableObject
if (!double.IsNaN(deltaT[i]) && deltaT[i] > 0.01)
coolingPoints.Add(new DataPoint(time[i], deltaT[i]));
// 导出CSV
try
{
string tmp = Path.GetTempPath();
string baseName = $"measure_{SampleId}_{DateTime.Now:yyyyMMdd_HHmmss}_{CurrentMeasurementIndex}";
string dataPath = Path.Combine(tmp, baseName + ".csv");
ExportMeasurementCsv(dataPath, time, ustd, upt, deltaT, startIdx, endIdx);
Logger.Log($"已导出测量数据 CSV: {dataPath}");
string winPath = Path.Combine(tmp, baseName + "_windows.csv");
ExportCandidateWindowsCsv(winPath, time, deltaT, startIdx, endIdx);
Logger.Log($"已导出候选拟合窗 CSV: {winPath}");
}
catch (Exception ex)
{
Logger.Log($"导出CSV失败: {ex.Message}");
}
return (lambda, alpha, deltaT, coolingPoints);
}
/// <summary>
/// 最小二乘法线性回归,返回 (斜率, 截距)
/// </summary>
@@ -566,6 +627,34 @@ public partial class D7896ViewModel : ObservableObject
return timeArray.Length - 1;
}
private void ExportMeasurementCsv(string path, double[] time, double[] ustd, double[] upt, double[] deltaT, int fitStart, int fitEnd)
{
using var sw = new StreamWriter(path, false, Encoding.UTF8);
sw.WriteLine("index,time,U_std,U_pt,deltaT,fitWindow");
int n = Math.Min(Math.Min(time.Length, ustd.Length), deltaT.Length);
for (int i = 0; i < n; i++)
{
bool inFit = (i >= fitStart && i <= fitEnd);
sw.WriteLine($"{i},{time[i]:F6},{ustd[i]:F6},{upt[i]:F6},{(double.IsNaN(deltaT[i])?0:deltaT[i]):F6},{(inFit?1:0)}");
}
}
private void ExportCandidateWindowsCsv(string path, double[] time, double[] deltaT, int startIdx, int endIdx)
{
using var sw = new StreamWriter(path, false, Encoding.UTF8);
sw.WriteLine("s,e,t_start,t_end,meanDelta");
int n = time.Length;
int minPts = 10;
for (int s = startIdx; s <= endIdx - minPts; s++)
{
for (int e = s + minPts - 1; e <= endIdx; e++)
{
double mean = deltaT.Skip(s).Take(e - s + 1).Where(x => !double.IsNaN(x)).DefaultIfEmpty(0).Average();
sw.WriteLine($"{s},{e},{time[s]:F6},{time[e]:F6},{mean:F6}");
}
}
}
///// <summary>
///// 最小二乘法拟合斜率 (X轴为横坐标Y轴为纵坐标) — 用于加热段 ln(t) vs ΔT
///// </summary>

View File

@@ -12,7 +12,7 @@ public partial class MeasurementResult : ObservableObject
private double _thermalConductivity; // W/m·K
[ObservableProperty]
private double _thermalDiffusivity; // ×10⁻⁶ m²/s
private double _thermalDiffusivity; // m²/s
[ObservableProperty]
private double _specificHeatCapacity; // 比热容 J/(kg·K)
@@ -50,11 +50,12 @@ public partial class MeasurementResult : ObservableObject
public void CalculateVhc()
{
Debug.WriteLine($"[MeasurementResult] 计算体积热容 - 热导率: {ThermalConductivity} W/(m·K), 热扩散率: {ThermalDiffusivity} ×10⁻⁶ m²/s");
Debug.WriteLine($"[MeasurementResult] 计算体积热容 - 热导率: {ThermalConductivity} W/(m·K), 热扩散率: {ThermalDiffusivity} m²/s");
if (ThermalDiffusivity > 0)
{
VolumetricHeatCapacity = ThermalConductivity / (ThermalDiffusivity * 1e-6) / 1000.0;
// VHC (kJ/(m³·K)) = (λ / α) / 1000
VolumetricHeatCapacity = ThermalConductivity / ThermalDiffusivity / 1000.0;
Debug.WriteLine($"[MeasurementResult] 计算得到体积热容: {VolumetricHeatCapacity} kJ/(m³·K)");
}
else

View File

@@ -25,8 +25,9 @@
"TestParameters": {
"MeasurementCount": 10,
"IntervalSeconds": 30,
"PlatinumWireLength": 0.07, //铂丝长度(单位:米)
"PlatinumWireDiameter": 0.00006,
//"PlatinumWireLength": 0.056, //铂丝长度(单位:米)
"PlatinumWireLength": 0.41, //铂丝长度(单位:米)
"PlatinumWireDiameter": 0.000025,
"ReportOutputPath": "Reports\\",
"DefaultSampleVolume": 40.0,
"DefaultPressure": 0.0,