diff --git a/SSPCTester.Devices/Drivers/Mock/MockDaq.cs b/SSPCTester.Devices/Drivers/Mock/MockDaq.cs
index f11a93c..f5e6674 100644
--- a/SSPCTester.Devices/Drivers/Mock/MockDaq.cs
+++ b/SSPCTester.Devices/Drivers/Mock/MockDaq.cs
@@ -17,7 +17,7 @@ public sealed class MockDaq : MockDeviceBase, IDaq
private double? _nextTriggerSec;
/// 满量程电压(V)。
- public double NominalVoltage { get; set; } = 28.0;
+ public double NominalVoltage { get; set; } = 5.0;
/// 满量程电流(A)。
public double NominalCurrent { get; set; } = 1.0;
@@ -49,6 +49,9 @@ public sealed class MockDaq : MockDeviceBase, IDaq
public async Task CaptureAsync(
int[] channels, int sampleRateHz, double durationSec, CancellationToken ct = default)
{
+ ArgumentNullException.ThrowIfNull(channels);
+ if (channels.Length == 0)
+ throw new ArgumentException("At least one channel must be requested.", nameof(channels));
if (sampleRateHz <= 0) throw new ArgumentOutOfRangeException(nameof(sampleRateHz));
if (durationSec <= 0) throw new ArgumentOutOfRangeException(nameof(durationSec));
@@ -57,9 +60,18 @@ public sealed class MockDaq : MockDeviceBase, IDaq
await Task.Delay(simulatedDelayMs, ct).ConfigureAwait(false);
int n = (int)Math.Round(sampleRateHz * durationSec);
- var v = new double[n];
- var i = new double[n];
+ var cardVoltage = new double[n];
+ var cardCurrent = new double[n];
double dt = 1.0 / sampleRateHz;
+ int channelCount = Math.Max(1, _opts.ChannelCount);
+ var requestedChannels = channels.Distinct().ToArray();
+ foreach (int channel in requestedChannels)
+ {
+ if (channel < 0 || channel >= channelCount)
+ throw new ArgumentOutOfRangeException(
+ nameof(channels),
+ $"Requested channel {channel} is outside the configured PCIe8586 channel count {channelCount}.");
+ }
bool rising = NextCaptureIsRising;
double delay = rising ? OnDelaySec : OffDelaySec;
@@ -81,7 +93,7 @@ public sealed class MockDaq : MockDeviceBase, IDaq
voltage = tRel <= 0 ? NominalVoltage : NominalVoltage * Math.Exp(-tRel / TauSec);
}
voltage += NextGaussian() * NominalVoltage * 0.005;
- v[k] = voltage;
+ cardVoltage[k] = voltage;
double current;
if (rising)
@@ -89,16 +101,10 @@ public sealed class MockDaq : MockDeviceBase, IDaq
else
current = tRel <= 0 ? NominalCurrent : NominalCurrent * Math.Exp(-tRel / TauSec);
current += NextGaussian() * NominalCurrent * 0.01;
- i[k] = current;
+ cardCurrent[k] = current;
}
- return new CurveData
- {
- SampleRateHz = sampleRateHz,
- TriggerTimeSec = triggerSec,
- Voltage = v,
- Current = i
- };
+ return BuildCurve(cardVoltage, cardCurrent, sampleRateHz, triggerSec, triggerIndex: 0, requestedChannels[0]);
}
public Task ReadInstantAsync(int[] channels, CancellationToken ct = default)
@@ -112,7 +118,7 @@ public sealed class MockDaq : MockDeviceBase, IDaq
? DaqChannelRole.Unconfigured
: Pcie8586Daq.ParseRole(cfg.Role);
- result[k] = role switch
+ double cardValue = role switch
{
DaqChannelRole.OutputVoltage or DaqChannelRole.InputVoltage =>
NominalVoltage * (1 + (_rng.NextDouble() - 0.5) * 0.004),
@@ -120,6 +126,7 @@ public sealed class MockDaq : MockDeviceBase, IDaq
NominalCurrent * (1 + (_rng.NextDouble() - 0.5) * 0.02),
_ => 0
};
+ result[k] = ApplyCalibration(cardValue, cfg);
}
return Task.FromResult(result);
}
@@ -137,7 +144,7 @@ public sealed class MockDaq : MockDeviceBase, IDaq
NextCaptureIsRising = request.IsRising;
await action(ct).ConfigureAwait(false);
_nextTriggerSec = request.PreTriggerMs / 1_000.0;
- var curve = await CaptureAsync(new[] { 0 }, sampleRate, durationSec, ct).ConfigureAwait(false);
+ var curve = await CaptureAsync(Enumerable.Range(0, Math.Max(1, _opts.ChannelCount)).ToArray(), sampleRate, durationSec, ct).ConfigureAwait(false);
int triggerIndex = (int)Math.Round(sampleRate * request.PreTriggerMs / 1_000.0);
return new CurveData
{
@@ -146,14 +153,60 @@ public sealed class MockDaq : MockDeviceBase, IDaq
TriggerTimeSec = triggerIndex / (double)curve.SampleRateHz,
Voltage = curve.Voltage,
Current = curve.Current,
- EngineeringChannels = new[]
- {
- new CurveChannelData(0, DaqChannelRole.OutputVoltage, "模拟输出电压", "V", 1, 0, curve.Voltage),
- new CurveChannelData(1, DaqChannelRole.OutputCurrent, "模拟输出电流", "A", 1, 0, curve.Current!)
- }
+ EngineeringChannels = curve.EngineeringChannels
};
}
+ private CurveData BuildCurve(
+ double[] cardVoltage,
+ double[] cardCurrent,
+ int sampleRateHz,
+ double triggerSec,
+ int triggerIndex,
+ int primaryPhysicalChannel)
+ {
+ var engineering = new List();
+ int channelCount = Math.Max(1, _opts.ChannelCount);
+ for (int channel = 0; channel < channelCount; channel++)
+ {
+ var cfg = _opts.Channels.FirstOrDefault(x => x.PhysicalChannel == channel)
+ ?? new DaqChannelOptions { PhysicalChannel = channel, Name = $"CH{channel}" };
+ var role = Pcie8586Daq.ParseRole(cfg.Role);
+ var source = role is DaqChannelRole.OutputCurrent or DaqChannelRole.InputCurrent
+ ? cardCurrent
+ : cardVoltage;
+ var values = source.Select(x => ApplyCalibration(x, cfg)).ToArray();
+ engineering.Add(new CurveChannelData(
+ channel, role, cfg.Name, UnitFor(role), cfg.Scale, cfg.Offset, values));
+ }
+
+ var outputVoltage = engineering.Where(x => x.Role == DaqChannelRole.OutputVoltage).ToArray();
+ var primary = outputVoltage.Length == 1
+ ? outputVoltage[0]
+ : engineering.FirstOrDefault(x => x.PhysicalChannel == primaryPhysicalChannel) ?? engineering.FirstOrDefault();
+ var current = engineering.FirstOrDefault(x => x.Role == DaqChannelRole.OutputCurrent)?.Samples;
+
+ return new CurveData
+ {
+ SampleRateHz = sampleRateHz,
+ TriggerSampleIndex = triggerIndex,
+ TriggerTimeSec = triggerSec,
+ Voltage = primary?.Samples ?? Array.Empty(),
+ Current = current,
+ EngineeringChannels = engineering
+ };
+ }
+
+ private static double ApplyCalibration(double value, DaqChannelOptions? cfg) =>
+ cfg is null ? value : value * cfg.Scale + cfg.Offset;
+
+ private static string UnitFor(DaqChannelRole role) => role switch
+ {
+ DaqChannelRole.OutputVoltage or DaqChannelRole.InputVoltage => "V",
+ DaqChannelRole.OutputCurrent or DaqChannelRole.InputCurrent => "A",
+ _ => ""
+ };
+
/// 由 ReadInstantAsync 衍生的"电流读数"接口(基础测试页用)。
public (double volts, double amps) ReadVoltageCurrent(int channel, bool channelOn)
{
diff --git a/SSPCTester.Logic/Testing/BasicTestCriteria.cs b/SSPCTester.Logic/Testing/BasicTestCriteria.cs
index dc5ed69..e61c31f 100644
--- a/SSPCTester.Logic/Testing/BasicTestCriteria.cs
+++ b/SSPCTester.Logic/Testing/BasicTestCriteria.cs
@@ -7,8 +7,9 @@ namespace SSPCTester.Logic.Testing;
public static class BasicTestCriteria
{
public const double OnVoltageThreshold = 1.0;
- public const double OffVoltageThreshold = 0.5;
+ public const double OffVoltageThreshold = 0.3;
public const double CurrentPresentThreshold = 0.001;
+ public const double OffCurrentThreshold = 0.3;
public const int SwitchSettleDelayMs = 1_000;
public const int OffSettleTimeoutMs = 2_000;
public const int OffInitialDelayMs = 100;
@@ -27,7 +28,7 @@ public static class BasicTestCriteria
HasVoltageData(voltage) && HasCurrentData(current);
public static bool IsOffState(double voltage, double current) =>
- IsOff(voltage) && !HasCurrentData(current);
+ IsOff(voltage) && Math.Abs(current) < OffCurrentThreshold;
///
/// Waits until the channel is stably off: no voltage and no current for consecutive samples.
diff --git a/SSPCTester.Tests/BasicTestCriteriaTests.cs b/SSPCTester.Tests/BasicTestCriteriaTests.cs
index 1c555c6..8e98eef 100644
--- a/SSPCTester.Tests/BasicTestCriteriaTests.cs
+++ b/SSPCTester.Tests/BasicTestCriteriaTests.cs
@@ -15,10 +15,10 @@ public sealed class BasicTestCriteriaTests
}
[Theory]
- [InlineData(0.49, true)]
- [InlineData(-0.49, true)]
- [InlineData(0.50, false)]
- [InlineData(-0.50, false)]
+ [InlineData(0.29, true)]
+ [InlineData(-0.29, true)]
+ [InlineData(0.30, false)]
+ [InlineData(-0.30, false)]
public void IsOff_UsesAbsoluteVoltageThreshold(double voltage, bool expected)
{
Assert.Equal(expected, BasicTestCriteria.IsOff(voltage));
@@ -35,12 +35,12 @@ public sealed class BasicTestCriteriaTests
[Theory]
[InlineData(0.0, 0.0, true)]
- [InlineData(0.49, 0.0, true)]
- [InlineData(-0.49, 0.0, true)]
- [InlineData(0.0, 0.0005, true)]
- [InlineData(0.50, 0.0, false)]
+ [InlineData(0.29, 0.0, true)]
+ [InlineData(-0.29, 0.0, true)]
+ [InlineData(0.0, 0.29, true)]
+ [InlineData(0.30, 0.0, false)]
[InlineData(0.75, 0.0, false)]
- [InlineData(0.0, 0.01, false)]
+ [InlineData(0.0, 0.30, false)]
[InlineData(28.0, 0.0, false)]
public void IsOffState_RequiresNoVoltageAndNoCurrent(double voltage, double current, bool expected)
{
diff --git a/SSPCTester.Tests/DaqIntegrationTests.cs b/SSPCTester.Tests/DaqIntegrationTests.cs
index e5ea4dd..31c06de 100644
--- a/SSPCTester.Tests/DaqIntegrationTests.cs
+++ b/SSPCTester.Tests/DaqIntegrationTests.cs
@@ -56,6 +56,51 @@ public sealed class DaqIntegrationTests
Assert.False(await sspc.GetChannelStateAsync(3));
}
+ [Fact]
+ public async Task MockDaq_AppliesScaleAndOffset()
+ {
+ var options = new DeviceOptions();
+ options.Daq.ClockDivider = 100_000;
+ options.Daq.ChannelCount = 2;
+ options.Daq.Channels[0].Role = nameof(DaqChannelRole.OutputVoltage);
+ options.Daq.Channels[0].Name = "Vout";
+ options.Daq.Channels[0].Scale = 2;
+ options.Daq.Channels[0].Offset = 1;
+ options.Daq.Channels[0].IsCalibrated = true;
+ options.Daq.Channels[1].Role = nameof(DaqChannelRole.OutputCurrent);
+ options.Daq.Channels[1].Name = "Iout";
+ options.Daq.Channels[1].Scale = 3;
+ options.Daq.Channels[1].Offset = -1;
+ options.Daq.Channels[1].IsCalibrated = true;
+ var daq = new MockDaq(Options.Create(options), NullLogger.Instance)
+ {
+ NominalVoltage = 10,
+ NominalCurrent = 1
+ };
+
+ var curve = await daq.CaptureAroundActionAsync(
+ new DaqActionCaptureRequest(10, 40, true),
+ _ => Task.CompletedTask);
+ var instant = await daq.ReadInstantAsync(new[] { 0, 1 });
+
+ Assert.True(curve.Voltage.Max() > 20);
+ Assert.True(curve.Current!.Max() > 1.8);
+ Assert.Contains(curve.EngineeringChannels, x =>
+ x.PhysicalChannel == 0 &&
+ x.Role == DaqChannelRole.OutputVoltage &&
+ x.Name == "Vout" &&
+ x.Scale == 2 &&
+ x.Offset == 1);
+ Assert.Contains(curve.EngineeringChannels, x =>
+ x.PhysicalChannel == 1 &&
+ x.Role == DaqChannelRole.OutputCurrent &&
+ x.Name == "Iout" &&
+ x.Scale == 3 &&
+ x.Offset == -1);
+ Assert.InRange(instant[0], 20.9, 21.1);
+ Assert.InRange(instant[1], 1.9, 2.1);
+ }
+
[Theory]
[InlineData(1000, 100_000)]
[InlineData(100, 1_000_000)]
diff --git a/SSPCTester.UI/ViewModels/CurveTestViewModel.cs b/SSPCTester.UI/ViewModels/CurveTestViewModel.cs
index 0ffa436..b074f70 100644
--- a/SSPCTester.UI/ViewModels/CurveTestViewModel.cs
+++ b/SSPCTester.UI/ViewModels/CurveTestViewModel.cs
@@ -298,6 +298,36 @@ public partial class CurveTestViewModel : ObservableObject
[RelayCommand]
private Task CaptureFalling() => CaptureSingleCurveAsync(CurveCaptureKind.Falling);
+ [RelayCommand]
+ private void Clear()
+ {
+ foreach (var row in Rows)
+ {
+ row.OnTimeMs = 0;
+ row.RiseTimeMs = 0;
+ row.OffTimeMs = 0;
+ row.FallTimeMs = 0;
+ row.RisingWaveformPath = null;
+ row.FallingWaveformPath = null;
+ row.ViewStartMs = null;
+ row.ViewEndMs = null;
+ row.RisingViewStartMs = null;
+ row.RisingViewEndMs = null;
+ row.FallingViewStartMs = null;
+ row.FallingViewEndMs = null;
+ row.Status = TestStatus.Untested;
+ }
+
+ RisingCurve = null;
+ FallingCurve = null;
+ RisingViewStartMs = null;
+ RisingViewEndMs = null;
+ FallingViewStartMs = null;
+ FallingViewEndMs = null;
+ SaveProjectQuietly();
+ _log.Add(UiLogLevel.Info, "开启与关断曲线结果已清空。");
+ }
+
private async Task CaptureSingleCurveAsync(CurveCaptureKind kind)
{
if (Project == null || Busy) return;
diff --git a/SSPCTester.UI/ViewModels/PowerTestViewModel.cs b/SSPCTester.UI/ViewModels/PowerTestViewModel.cs
index 700582d..ec4ed92 100644
--- a/SSPCTester.UI/ViewModels/PowerTestViewModel.cs
+++ b/SSPCTester.UI/ViewModels/PowerTestViewModel.cs
@@ -181,6 +181,23 @@ public partial class PowerTestViewModel : ObservableObject
}
}
+ [RelayCommand]
+ private void Clear()
+ {
+ foreach (var row in Rows)
+ {
+ row.Vin = 0;
+ row.Iin = 0;
+ row.Vout = 0;
+ row.Iout = 0;
+ row.MeasuredPowerOut = null;
+ row.Status = TestStatus.Untested;
+ }
+
+ Current = Rows.FirstOrDefault();
+ _log.Add(UiLogLevel.Info, "功率损耗结果已清空。");
+ }
+
/// 解析用于输入端电压的高速采集卡通道(取已标定的电压通道,优先 OutputVoltage)。
private static int ResolveDaqVoltageChannel(DaqOptions options)
{
diff --git a/SSPCTester.UI/Views/CurveTestView.xaml b/SSPCTester.UI/Views/CurveTestView.xaml
index b076f7a..1a2c23d 100644
--- a/SSPCTester.UI/Views/CurveTestView.xaml
+++ b/SSPCTester.UI/Views/CurveTestView.xaml
@@ -14,7 +14,7 @@
-
+
@@ -144,7 +144,17 @@
-
+
+
+
+
+
+
+
+
diff --git a/SSPCTester.UI/Views/PowerTestView.xaml b/SSPCTester.UI/Views/PowerTestView.xaml
index ab82bff..7d824ed 100644
--- a/SSPCTester.UI/Views/PowerTestView.xaml
+++ b/SSPCTester.UI/Views/PowerTestView.xaml
@@ -13,7 +13,7 @@
-
+
@@ -178,7 +178,17 @@
-
+
+
+
+
+
+
+
+
diff --git a/SSPCTester.UI/Views/ProjectInfoView.xaml b/SSPCTester.UI/Views/ProjectInfoView.xaml
index f188426..86a325f 100644
--- a/SSPCTester.UI/Views/ProjectInfoView.xaml
+++ b/SSPCTester.UI/Views/ProjectInfoView.xaml
@@ -15,7 +15,6 @@
-
diff --git a/SSPCTester.UI/Views/ProtectView.xaml b/SSPCTester.UI/Views/ProtectView.xaml
index fb93a5a..8bf0460 100644
--- a/SSPCTester.UI/Views/ProtectView.xaml
+++ b/SSPCTester.UI/Views/ProtectView.xaml
@@ -15,7 +15,7 @@
-
+
diff --git a/SSPCTester.UI/Views/ReportView.xaml b/SSPCTester.UI/Views/ReportView.xaml
index 7e19550..6270420 100644
--- a/SSPCTester.UI/Views/ReportView.xaml
+++ b/SSPCTester.UI/Views/ReportView.xaml
@@ -18,7 +18,10 @@
-
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+