chroma synthesis模式

This commit is contained in:
“hsc”
2026-10-09 09:23:32 +08:00
parent c017efdb49
commit 8dd08fcc7a
3 changed files with 572 additions and 364 deletions
+160 -1
View File
@@ -97,6 +97,25 @@ namespace DeviceCommand.Devices
await SendAsync($"*D {valStr}{ScpiDelimiter}", ct);
}
/// <summary>
/// 退出远程状态、回到本地 (SYST:LOC,即 SYSTem:LOCal)
/// <para>设定后仪器回到 LOCAL 状态,前面板恢复可用。手册注明本命令僅可用於 RS-232 控制之下,
/// 经 Ethernet/USB 发送时仪器可能不响应</para>
/// </summary>
public virtual async Task 退出远程Async(CancellationToken ct = default)
{
await SendAsync($"SYST:LOC{ScpiDelimiter}", ct);
}
/// <summary>
/// 进入远程状态 (SYST:REM,即 SYSTem:REMote)
/// <para>设定后除 LOCAL/REMOTE 键外前面板其他键皆无效。手册注明本命令僅可用於 RS-232 控制之下</para>
/// </summary>
public virtual async Task 进入远程Async(CancellationToken ct = default)
{
await SendAsync($"SYST:REM{ScpiDelimiter}", ct);
}
#endregion
#region 2. 基础输出与耦合控制 (OUTPut 子系统)
@@ -509,7 +528,8 @@ namespace DeviceCommand.Devices
/// <summary>
/// 开启/关闭谐波量测 (SENSe:HARMonic)
/// <para>每次新的 Fetch/Measure 前必须先 ON,约需 3 秒得到结果;
/// <para>每次新的 Fetch
/// ./Measure 前必须先 ON,约需 3 秒得到结果;
/// 若要量测其他数据,须先设回 OFF。分相量测配合 INST:COUP NONE + INST:NSEL 逐相进行</para>
/// </summary>
/// <param name="isOn">true: 开启谐波量测(ON); false: 关闭(OFF)</param>
@@ -556,5 +576,144 @@ namespace DeviceCommand.Devices
#endregion
#region 8. SYNTHESIS 波形合成子系统 (SYNThesis 子系统 · 自定义谐波波形输出)
/// <summary>
/// 设定每个谐波阶的数据格式 (SOUR:SYNT:COMP)
/// <para>VALUE: 绝对值; PERCENT: 基频电压的百分率。后缀 1/2/3 对应三段可编程波形</para>
/// </summary>
/// <param name="compose">数据格式: VALUE1 | VALUE2 | VALUE3 | PERCENT1 | PERCENT2 | PERCENT3</param>
public virtual async Task 设置合成数据格式Async(string compose, CancellationToken ct = default)
{
await SendAsync($"SOUR:SYNT:COMP {compose}{ScpiDelimiter}", ct);
}
/// <summary>
/// 设定每个谐波阶的振幅 (SOUR:SYNT:AMPL)
/// <para>最大位阶数为 50;各阶取值上限随阶次递减(2~10 阶 0~90.0,11~20 阶 0~60.0,
/// 21~30 阶 0~30.0,31~40 阶 0~30.0,41~50 阶 0~15.0;PERCENT 格式对应减半)</para>
/// </summary>
/// <param name="amplitudes">各次谐波振幅序列(按阶次升序,逗号下发)</param>
public virtual async Task 设置合成谐波振幅Async(double[] amplitudes, CancellationToken ct = default)
{
string valStr = string.Join(",", Array.ConvertAll(amplitudes, v => v.ToString("0.###", CultureInfo.InvariantCulture)));
await SendAsync($"SOUR:SYNT:AMPL {valStr}{ScpiDelimiter}", ct);
}
/// <summary>
/// 设定每个谐波阶的相位角 (SOUR:SYNT:PHAS)
/// </summary>
/// <param name="phases">各次谐波相位角序列(单位: °,每个值 0.0 ~ 359.9)</param>
public virtual async Task 设置合成谐波相位Async(double[] phases, CancellationToken ct = default)
{
string valStr = string.Join(",", Array.ConvertAll(phases, v => v.ToString("0.#", CultureInfo.InvariantCulture)));
await SendAsync($"SOUR:SYNT:PHAS {valStr}{ScpiDelimiter}", ct);
}
/// <summary>
/// 设定 SYNTHESIS 模式的基波交流电压 (SOUR:SYNT:FUND)
/// </summary>
/// <param name="voltage">基波交流电压 (单位: V,0.0 ~ 300.0)</param>
public virtual async Task 设置合成基波电压Async(double voltage, CancellationToken ct = default)
{
string valStr = voltage.ToString("0.###", CultureInfo.InvariantCulture);
await SendAsync($"SOUR:SYNT:FUND {valStr}{ScpiDelimiter}", ct);
}
/// <summary>
/// 设定叠加于 SYNTHESIS 波形的直流电压 (SOUR:SYNT:DC)
/// </summary>
/// <param name="voltage">直流电压 (单位: V,-424.2 ~ 424.2)</param>
public virtual async Task 设置合成直流电压Async(double voltage, CancellationToken ct = default)
{
string valStr = voltage.ToString("0.###", CultureInfo.InvariantCulture);
await SendAsync($"SOUR:SYNT:DC {valStr}{ScpiDelimiter}", ct);
}
/// <summary>
/// 设定 SYNTHESIS 模式的基频 (SOUR:SYNT:FREQ)
/// </summary>
/// <param name="frequency">基频: 50 或 60 (Hz)</param>
public virtual async Task 设置合成基频Async(int frequency, CancellationToken ct = default)
{
await SendAsync($"SOUR:SYNT:FREQ {frequency}{ScpiDelimiter}", ct);
}
/// <summary>
/// 设定 SYNTHESIS 模式的起始相位角 (SOUR:SYNT:SPH)
/// </summary>
/// <param name="degree">起始相位角 (单位: °,0.0 ~ 359.9)</param>
public virtual async Task 设置合成起始相位Async(double degree, CancellationToken ct = default)
{
string valStr = degree.ToString("0.#", CultureInfo.InvariantCulture);
await SendAsync($"SOUR:SYNT:SPH {valStr}{ScpiDelimiter}", ct);
}
/// <summary>
/// 查询当前合成数据格式 (SOUR:SYNT:COMP?),用于下发后回读校验
/// </summary>
/// <returns>VALUE1|2|3 / PERCENT1|2|3</returns>
public virtual async Task<string> 查询合成数据格式Async(CancellationToken ct = default)
{
string resp = await WriteReadAsync($"SOUR:SYNT:COMP?{ScpiDelimiter}", ScpiDelimiter, ct);
return resp?.Trim() ?? string.Empty;
}
/// <summary>
/// 查询 SYNTHESIS 基波交流电压 (SOUR:SYNT:FUND?),用于下发后回读校验
/// </summary>
public virtual async Task<double> 查询合成基波电压Async(CancellationToken ct = default)
{
string resp = await WriteReadAsync($"SOUR:SYNT:FUND?{ScpiDelimiter}", ScpiDelimiter, ct);
return ExtractDouble(resp);
}
/// <summary>
/// 查询 SYNTHESIS 基频 (SOUR:SYNT:FREQ?),返回 50 或 60
/// </summary>
public virtual async Task<int> 查询合成基频Async(CancellationToken ct = default)
{
string resp = await WriteReadAsync($"SOUR:SYNT:FREQ?{ScpiDelimiter}", ScpiDelimiter, ct);
return (int)ExtractDouble(resp);
}
/// <summary>
/// 查询 SYNTHESIS 叠加直流电压 (SOUR:SYNT:DC?)
/// </summary>
public virtual async Task<double> 查询合成直流电压Async(CancellationToken ct = default)
{
string resp = await WriteReadAsync($"SOUR:SYNT:DC?{ScpiDelimiter}", ScpiDelimiter, ct);
return ExtractDouble(resp);
}
/// <summary>
/// 查询 SYNTHESIS 起始相位角 (SOUR:SYNT:SPH?)
/// </summary>
public virtual async Task<double> 查询合成起始相位Async(CancellationToken ct = default)
{
string resp = await WriteReadAsync($"SOUR:SYNT:SPH?{ScpiDelimiter}", ScpiDelimiter, ct);
return ExtractDouble(resp);
}
/// <summary>
/// 查询各谐波阶振幅数组 (SOUR:SYNT:AMPL?),首值对应 2 次谐波
/// </summary>
public virtual async Task<double[]> 查询合成谐波振幅Async(CancellationToken ct = default)
{
string resp = await WriteReadAsync($"SOUR:SYNT:AMPL?{ScpiDelimiter}", ScpiDelimiter, ct);
return ExtractDoubleArray(resp);
}
/// <summary>
/// 查询各谐波阶相位角数组 (SOUR:SYNT:PHAS?),首值对应 2 次谐波
/// </summary>
public virtual async Task<double[]> 查询合成谐波相位Async(CancellationToken ct = default)
{
string resp = await WriteReadAsync($"SOUR:SYNT:PHAS?{ScpiDelimiter}", ScpiDelimiter, ct);
return ExtractDoubleArray(resp);
}
#endregion
}
}
@@ -1,9 +1,5 @@
using DeviceCommand.Devices;
using Newtonsoft.Json;
using OxyPlot;
using OxyPlot.Axes;
using OxyPlot.Legends;
using OxyPlot.Series;
using Prism.Commands;
using Prism.Ioc;
using System;
@@ -22,34 +18,15 @@ namespace PowerAdaptabilityModule.ViewModels
{
/// <summary>
/// 电源适应性测试 ViewModel(Chroma61800 交流源一拖三):
/// 左侧为设备命令面板(三相输出设置 + LIST 模式),右侧为 A/B/C 三相实时波形图表。
/// 设备命令面板(三相输出设置 + LIST 模式 + SYNTHESIS 自定义波形输出)。
/// </summary>
public class PowerAdaptabilityViewModel : NavigateViewModelBase, IDisposable
{
#region 常量
/// <summary>采样周期(毫秒)</summary>
private const int SampleIntervalMs = 1000;
/// <summary>三相曲线配色:A 红、B 绿、C 蓝(三张图统一,便于对照)</summary>
private static readonly OxyColor[] PhaseColors =
{ OxyColor.FromRgb(0xE5, 0x39, 0x35), OxyColor.FromRgb(0x43, 0xA0, 0x47), OxyColor.FromRgb(0x1E, 0x88, 0xE5) };
#endregion
#region 私有字段
private readonly DeviceManager _dm;
private readonly GlobalInfo _globalInfo;
private Chroma61800? _dev;
private CancellationTokenSource? _cmdCts;
private CancellationTokenSource? _sampleCts;
/// <summary>采样计时器,记录从开始采集到当前的经过秒数(X 轴)</summary>
private System.Diagnostics.Stopwatch? _sampleStopwatch;
// 三相电压时间序列曲线(A/B/C 三相)
private readonly LineSeries _voltA, _voltB, _voltC;
// 输出频率时间序列曲线(单条,ALL 模式读一个总频率)
private readonly LineSeries _freq;
#endregion
#region 设备状态属性
@@ -61,36 +38,6 @@ namespace PowerAdaptabilityModule.ViewModels
private bool _isBusy;
public bool IsBusy { get => _isBusy; set => SetProperty(ref _isBusy, value); }
private bool _isSampling;
/// <summary>是否正在采集波形数据</summary>
public bool IsSampling { get => _isSampling; set => SetProperty(ref _isSampling, value); }
private bool _isFrequencySamplingEnabled;
/// <summary>是否采集频率(默认关闭:频率一般无需持续采集,开启后采样循环才读取并绘制频率曲线)</summary>
public bool IsFrequencySamplingEnabled { get => _isFrequencySamplingEnabled; set => SetProperty(ref _isFrequencySamplingEnabled, value); }
#endregion
#region 图表(三相电压时间序列 + 输出频率时间序列)
/// <summary>三相电压波形图表</summary>
public PlotModel VoltagePlot { get; }
/// <summary>输出频率时间序列图表(受“采集频率”开关控制,默认不采集)</summary>
public PlotModel FrequencyPlot { get; }
#endregion
#region 实时测量值
private double _voltageA; public double VoltageA { get => _voltageA; set => SetProperty(ref _voltageA, value); }
private double _voltageB; public double VoltageB { get => _voltageB; set => SetProperty(ref _voltageB, value); }
private double _voltageC; public double VoltageC { get => _voltageC; set => SetProperty(ref _voltageC, value); }
private double _frequencyNow;
/// <summary>当前输出频率 (Hz)</summary>
public double FrequencyNow { get => _frequencyNow; set => SetProperty(ref _frequencyNow, value); }
private double _powerNow;
/// <summary>当前真实输出功率 (W)</summary>
public double PowerNow { get => _powerNow; set => SetProperty(ref _powerNow, value); }
#endregion
#region 输入参数(三相输出设置)
@@ -204,6 +151,87 @@ namespace PowerAdaptabilityModule.ViewModels
public IReadOnlyList<string> ListTimeBaseOptions { get; } = new[] { "TIME", "CYCLE" };
#endregion
#region 输入参数(SYNTHESIS 自定义谐波波形输出)
/// <summary>谐波阶数据格式选项 (SYNT:COMP):VALUE=绝对值,PERCENT=基频百分率,后缀 1/2/3 为三段波形</summary>
public IReadOnlyList<string> SynthComposeOptions { get; } = new[]
{ "VALUE1", "VALUE2", "VALUE3", "PERCENT1", "PERCENT2", "PERCENT3" };
/// <summary>基频选项 (SYNT:FREQ):仅支持 50 / 60 Hz</summary>
public IReadOnlyList<int> SynthFrequencyOptions { get; } = new[] { 50, 60 };
private string _synthCompose = "VALUE1";
/// <summary>谐波阶数据格式 (SYNT:COMP)</summary>
public string SynthCompose { get => _synthCompose; set => SetProperty(ref _synthCompose, value); }
private int _synthFrequency = 50;
/// <summary>基频 (SYNT:FREQ, 50 | 60 Hz)</summary>
public int SynthFrequency { get => _synthFrequency; set => SetProperty(ref _synthFrequency, value); }
private double _synthFundamental = 220;
/// <summary>基波交流电压 (SYNT:FUND, 0.0 ~ 300.0 V)</summary>
public double SynthFundamental { get => _synthFundamental; set => SetProperty(ref _synthFundamental, value); }
private double _synthDc;
/// <summary>叠加直流电压 (SYNT:DC, -424.2 ~ 424.2 V)</summary>
public double SynthDc { get => _synthDc; set => SetProperty(ref _synthDc, value); }
private double _synthStartPhase;
/// <summary>起始相位角 (SYNT:SPH, 0.0 ~ 359.9 °)</summary>
public double SynthStartPhase { get => _synthStartPhase; set => SetProperty(ref _synthStartPhase, value); }
private string _synthAmplitudeText = "";
/// <summary>各次谐波振幅序列文本 (SYNT:AMPL, 英文逗号分隔, 最多 50 阶)</summary>
public string SynthAmplitudeText { get => _synthAmplitudeText; set => SetProperty(ref _synthAmplitudeText, value); }
private string _synthPhaseText = "";
/// <summary>各次谐波相位角序列文本 (SYNT:PHAS, 英文逗号分隔, 0~359.9°)</summary>
public string SynthPhaseText { get => _synthPhaseText; set => SetProperty(ref _synthPhaseText, value); }
#endregion
#region SYNTHESIS 波形预设(文件:D:\acp\源测试\合成波形,一键载入/保存自定义谐波波形)
/// <summary>合成波形预设文件目录(LIST 测试项目录下的子目录,避免与 LIST 预设混淆)</summary>
private static readonly string SynthStorageDir = Path.Combine(PresetStorageDir, "合成波形");
/// <summary>合成波形预设文件目录提示(供界面显示)</summary>
public string SynthStorageHint => $"波形文件目录:{SynthStorageDir}";
// 从磁盘加载的合成波形预设集合
private List<SynthPreset> _synthPresets = new();
/// <summary>合成波形预设集合</summary>
public IReadOnlyList<SynthPreset> SynthPresets => _synthPresets;
private List<string> _synthPresetOptions = new();
/// <summary>合成波形预设名称列表(供 ComboBox 显示)</summary>
public IReadOnlyList<string> SynthPresetOptions => _synthPresetOptions;
private string _synthPresetName = "";
/// <summary>当前波形名称(保存时作为文件名;载入后自动填充)</summary>
public string SynthPresetName { get => _synthPresetName; set => SetProperty(ref _synthPresetName, value); }
private string _synthPresetNote = "";
/// <summary>当前波形说明(随文件保存,载入后自动填充)</summary>
public string SynthPresetNote { get => _synthPresetNote; set => SetProperty(ref _synthPresetNote, value); }
private int _selectedSynthPresetIndex = -1;
/// <summary>当前选中的合成波形预设索引(-1 表示未选择)</summary>
public int SelectedSynthPresetIndex
{
get => _selectedSynthPresetIndex;
set
{
if (SetProperty(ref _selectedSynthPresetIndex, value))
{
// 选中后把名称/说明填入编辑框,便于修改后另存或覆盖保存
if (value >= 0 && value < _synthPresets.Count)
{
SynthPresetName = _synthPresets[value].Name;
SynthPresetNote = _synthPresets[value].Note;
}
}
}
}
#endregion
#region 试验预设(测试项文件:D:\acp\源测试,一键载入大纲典型波形)
/// <summary>测试项文件默认存储目录(保存与查询均在此目录)</summary>
private const string PresetStorageDir = @"D:\acp\源测试";
@@ -261,6 +289,7 @@ namespace PowerAdaptabilityModule.ViewModels
#region 命令
public ICommand QueryIdnCommand { get; }
public ICommand ResetCommand { get; }
public ICommand ExitRemoteCommand { get; }
public ICommand OutputOnCommand { get; }
public ICommand OutputOffCommand { get; }
public ICommand RelayOnCommand { get; }
@@ -280,9 +309,11 @@ namespace PowerAdaptabilityModule.ViewModels
public ICommand CopyPhaseToThreeCommand { get; }
public ICommand DownloadThreePhaseListCommand { get; }
public ICommand ListStopCommand { get; }
public ICommand StartSamplingCommand { get; }
public ICommand StopSamplingCommand { get; }
public ICommand ClearChartsCommand { get; }
public ICommand DownloadSynthesisCommand { get; }
public ICommand SynthStopCommand { get; }
public ICommand LoadSynthPresetCommand { get; }
public ICommand SaveSynthPresetCommand { get; }
public ICommand DeleteSynthPresetCommand { get; }
#endregion
public PowerAdaptabilityViewModel(IContainerProvider containerProvider) : base(containerProvider)
@@ -290,19 +321,14 @@ namespace PowerAdaptabilityModule.ViewModels
_dm = containerProvider.Resolve<DeviceManager>();
_globalInfo = containerProvider.Resolve<GlobalInfo>();
// 初始化三相电压时间序列图表(X=时间秒, Y=实测 RMS 电压, 叠加 A/B/C 三相)
VoltagePlot = BuildTimeSeriesPlot("三相电压", "V");
(_voltA, _voltB, _voltC) = AddThreePhaseSeries(VoltagePlot);
// 输出频率时间序列图表(单条曲线,受“采集频率”开关控制)
FrequencyPlot = BuildTimeSeriesPlot("输出频率", "Hz");
_freq = NewPhaseSeries("频率", OxyColor.FromRgb(0x8E, 0x24, 0xAA));
FrequencyPlot.Series.Add(_freq);
// 初始化测试项文件目录(首次运行迁移内置预设),再从磁盘加载测试项
EnsurePresetStorage();
RefreshPresets();
// 初始化合成波形文件目录(首次运行迁移内置示例),再从磁盘加载波形
EnsureSynthStorage();
RefreshSynthPresets();
#region 命令初始化
QueryIdnCommand = new DelegateCommand(async () =>
await Exec(async () => Log("IDN: " + await _dev!.查询设备信息Async(Ct()))));
@@ -310,6 +336,9 @@ namespace PowerAdaptabilityModule.ViewModels
ResetCommand = new DelegateCommand(async () =>
await Exec(async () => { await _dev!.复位Async(Ct()); Log("设备已复位"); }));
ExitRemoteCommand = new DelegateCommand(async () =>
await Exec(async () => { await _dev!.退出远程Async(Ct()); Log("已发送退出远程 SYST:LOC,前面板应恢复可用(手册注明僅 RS-232 下有效)"); }));
OutputOnCommand = new DelegateCommand(async () =>
await Exec(async () => { await _dev!.设置输出开关Async(true, Ct()); Log("输出已开启"); }));
@@ -436,9 +465,47 @@ namespace PowerAdaptabilityModule.ViewModels
ListStopCommand = new DelegateCommand(async () =>
await Exec(async () => { await _dev!.触发列表执行Async(false, Ct()); Log("LIST 已停止执行(TRIG OFF)"); }));
StartSamplingCommand = new DelegateCommand(StartSampling);
StopSamplingCommand = new DelegateCommand(StopSampling);
ClearChartsCommand = new DelegateCommand(ClearCharts);
// 下发 SYNTHESIS 自定义谐波波形并启动:手册要求进入 SYNTH 前先离开输出,参数下发后 OUTP:MODE SYNTH → TRIG ON
DownloadSynthesisCommand = new DelegateCommand(async () => await Exec(async () =>
{
// 先强制 TRIG OFF 停止连续执行模态并等状态回 OFF,再改写参数(执行中改写会被仪器忽略)
await 等待List空闲Async();
await _dev!.清除错误队列Async(Ct()); // *CLS
await _dev.设置输出开关Async(false, Ct()); // OUTP OFF(进入 SYNTH 前先离开输出)
await _dev.设置操作模态Async("FIXED", Ct()); // OUTP:MODE FIXED(清模态)
await _dev.设置合成数据格式Async(SynthCompose, Ct()); // SYNT:COMP
await _dev.设置合成基频Async(SynthFrequency, Ct()); // SYNT:FREQ
await _dev.设置合成基波电压Async(SynthFundamental, Ct()); // SYNT:FUND
await _dev.设置合成直流电压Async(SynthDc, Ct()); // SYNT:DC
await _dev.设置合成起始相位Async(SynthStartPhase, Ct()); // SYNT:SPH
var ampl = ParseDoubles(SynthAmplitudeText);
if (ampl.Length > 0) await _dev.设置合成谐波振幅Async(ampl, Ct()); // SYNT:AMPL
var phas = ParseDoubles(SynthPhaseText);
if (phas.Length > 0) await _dev.设置合成谐波相位Async(phas, Ct()); // SYNT:PHAS
// 回读校验:用仪器自己的回答确认参数是否真的写入(设定与回读不符=被拒绝或落到别的槽位)
string compBack = await _dev.查询合成数据格式Async(Ct());
double fundBack = await _dev.查询合成基波电压Async(Ct());
double[] amplBack = await _dev.查询合成谐波振幅Async(Ct());
Log($"回读校验:COMP 设={SynthCompose} 读={compBack};FUND 设={SynthFundamental} 读={fundBack};AMPL 读前6阶={string.Join(",", amplBack.Take(6))}");
await _dev.延时Async(StartDelayMs, Ct()); // *D 200(等待仪器就绪)
await _dev.设置操作模态Async("SYNTH", Ct()); // OUTP:MODE SYNTH
await _dev.触发列表执行Async(true, Ct()); // TRIG ON
double vrms = await _dev.读取交流电压Async(Ct()); // MEAS:VOLT:AC? 实测输出
Log($"SYNTHESIS 波形已下发并启动(格式={SynthCompose} 基频={SynthFrequency}Hz 基波={SynthFundamental}V 谐波阶数={ampl.Length})实测Vrms={vrms}");
}));
SynthStopCommand = new DelegateCommand(async () =>
await Exec(async () => { await _dev!.触发列表执行Async(false, Ct()); Log("SYNTHESIS 已停止执行(TRIG OFF)"); }));
// 合成波形的载入 / 保存 / 删除(与 LIST 测试项同机制,文件存于 D:\acp\源测试\合成波形)
LoadSynthPresetCommand = new DelegateCommand(LoadCurrentSynthPreset);
SaveSynthPresetCommand = new DelegateCommand(SaveCurrentSynthPreset);
DeleteSynthPresetCommand = new DelegateCommand(DeleteCurrentSynthPreset);
#endregion
Initialize();
@@ -482,202 +549,9 @@ namespace PowerAdaptabilityModule.ViewModels
public override void OnNavigatedFrom(NavigationContext navigationContext)
{
// 离开页面时停止采样,避免后台持续占用设备通信
StopSampling();
}
#endregion
#region 数据采集
private void StartSampling()
{
if (_dev == null) { Log("未关联 Chroma61800 设备,无法采集"); return; }
if (IsSampling) return;
IsSampling = true;
_sampleStopwatch = System.Diagnostics.Stopwatch.StartNew();
_sampleCts = new CancellationTokenSource();
var token = _sampleCts.Token;
_ = Task.Run(() => SamplingLoopAsync(token), token);
Log($"开始采集(周期 {SampleIntervalMs} ms)");
}
private void StopSampling()
{
if (!IsSampling) return;
IsSampling = false;
_sampleStopwatch?.Stop();
try { _sampleCts?.Cancel(); } catch { }
_sampleCts?.Dispose();
_sampleCts = null;
Log("停止采集");
}
/// <summary>
/// 采样循环:通过 INST:COUP NONE + INST:NSEL 逐相切换读取三相交流电压,
/// 恢复 ALL 后读取总功率;频率仅在“采集频率”开关开启时读取(一般不采集)。
/// </summary>
private async Task SamplingLoopAsync(CancellationToken token)
{
while (!token.IsCancellationRequested)
{
try
{
if (_dev != null && _dev.IsConnected)
{
// 切换为独立相位模式
await _dev.设置相位耦合Async("NONE", token);
// A 相 (Φ1)
await _dev.选择相位Async(1, token);
double vA = await _dev.读取交流电压Async(token);
// B 相 (Φ2)
await _dev.选择相位Async(2, token);
double vB = await _dev.读取交流电压Async(token);
// C 相 (Φ3)
await _dev.选择相位Async(3, token);
double vC = await _dev.读取交流电压Async(token);
// 恢复 ALL 模式后读取总功率;频率按开关决定是否采集
await _dev.设置相位耦合Async("ALL", token);
double power = await _dev.读取真实功率Async(token);
double freq = IsFrequencySamplingEnabled
? await _dev.读取频率Async(token)
: double.NaN;
double elapsedSec = _sampleStopwatch?.Elapsed.TotalSeconds ?? 0;
var dispatcher = Application.Current?.Dispatcher;
if (dispatcher != null)
{
_ = dispatcher.BeginInvoke(new Action(() =>
{
AppendSample(elapsedSec, vA, vB, vC, freq, power);
IsConnected = _dev.IsConnected;
}));
}
}
}
catch (OperationCanceledException) { break; }
catch (Exception ex)
{
Log($"采样异常:{ex.Message}");
}
try { await Task.Delay(SampleIntervalMs, token); }
catch (OperationCanceledException) { break; }
}
}
/// <summary>
/// 追加一个采样点到电压 / 频率时间序列图表(UI 线程调用)。
/// <para>X 轴为采集开始后的经过秒数;电压为三相 RMS,频率仅在采集开关开启(非 NaN)时追加。</para>
/// </summary>
private void AppendSample(double elapsedSec, double vA, double vB, double vC, double freq, double power)
{
// 三相电压
_voltA.Points.Add(new DataPoint(elapsedSec, vA));
_voltB.Points.Add(new DataPoint(elapsedSec, vB));
_voltC.Points.Add(new DataPoint(elapsedSec, vC));
// 频率(开关关闭时 freq 为 NaN,不追加曲线)
bool hasFreq = !double.IsNaN(freq);
if (hasFreq) _freq.Points.Add(new DataPoint(elapsedSec, freq));
// 实时数值
VoltageA = vA;
VoltageB = vB;
VoltageC = vC;
if (hasFreq) FrequencyNow = freq;
PowerNow = power;
VoltagePlot.InvalidatePlot(true);
if (hasFreq) FrequencyPlot.InvalidatePlot(true);
}
private void ClearCharts()
{
foreach (var s in new[] { _voltA, _voltB, _voltC, _freq })
{
s.Points.Clear();
}
_sampleStopwatch?.Restart();
VoltagePlot.InvalidatePlot(true);
FrequencyPlot.InvalidatePlot(true);
Log("已清空波形数据,计时器已重置");
}
#endregion
#region 图表构建
/// <summary>
/// 构建时间序列图表:横轴为经过时间(秒),纵轴为对应物理量实测 RMS 值,自动缩放。
/// </summary>
/// <param name="title">图表标题(如"三相电压")</param>
/// <param name="unit">纵轴单位(V / A / W)</param>
private static PlotModel BuildTimeSeriesPlot(string title, string unit)
{
var pm = new PlotModel
{
Title = $"{title} ({unit})",
PlotAreaBorderColor = OxyColors.LightGray,
Background = OxyColors.White
};
pm.Axes.Add(new LinearAxis
{
Position = AxisPosition.Bottom,
Title = "时间 (s)",
Minimum = 0,
Maximum = 10,
MajorGridlineStyle = LineStyle.Dot,
MinorGridlineStyle = LineStyle.None,
StringFormat = "0.0",
IsZoomEnabled = true,
IsPanEnabled = true
});
pm.Axes.Add(new LinearAxis
{
Position = AxisPosition.Left,
Title = unit,
MajorGridlineStyle = LineStyle.Dot,
MinorGridlineStyle = LineStyle.None,
IsZoomEnabled = true,
IsPanEnabled = true
});
pm.Legends.Add(new Legend
{
LegendPosition = LegendPosition.RightTop,
LegendBackground = OxyColor.FromAColor(200, OxyColors.White),
LegendBorder = OxyColors.LightGray
});
return pm;
}
/// <summary>向图表添加 A/B/C 三相时间序列曲线(红/绿/蓝,带标记点)。</summary>
private static (LineSeries A, LineSeries B, LineSeries C) AddThreePhaseSeries(PlotModel plot)
{
var a = NewPhaseSeries("A 相", PhaseColors[0]);
var b = NewPhaseSeries("B 相", PhaseColors[1]);
var c = NewPhaseSeries("C 相", PhaseColors[2]);
plot.Series.Add(a);
plot.Series.Add(b);
plot.Series.Add(c);
return (a, b, c);
}
/// <summary>创建一条相曲线(带小圆标记,便于辨识离散采样点)。</summary>
private static LineSeries NewPhaseSeries(string title, OxyColor color) => new LineSeries
{
Title = title,
Color = color,
StrokeThickness = 1.8,
MarkerType = MarkerType.Circle,
MarkerSize = 3,
MarkerFill = color
};
#endregion
#region 测试项文件读写(D:\acp\源测试)
/// <summary>
/// 确保测试项目录存在;若目录中尚无任何测试项文件,则把内置大纲预设迁移写入为 .json 文件。
@@ -834,6 +708,156 @@ namespace PowerAdaptabilityModule.ViewModels
}
#endregion
#region SYNTHESIS 波形文件读写(D:\acp\源测试\合成波形)
/// <summary>
/// 确保合成波形目录存在(波形 .json 一律由用户自行编写或界面保存,不再自动生成内置示例)。
/// </summary>
private void EnsureSynthStorage()
{
try
{
if (!Directory.Exists(SynthStorageDir))
Directory.CreateDirectory(SynthStorageDir);
}
catch (Exception ex)
{
Log($"初始化合成波形目录失败:{ex.Message}");
}
}
/// <summary>从磁盘重新加载合成波形预设列表,刷新下拉框并保持选中项有效。</summary>
private void RefreshSynthPresets()
{
var list = new List<SynthPreset>();
try
{
if (Directory.Exists(SynthStorageDir))
{
foreach (var file in Directory.GetFiles(SynthStorageDir, "*" + PresetFileExt).OrderBy(f => f))
{
try
{
var json = File.ReadAllText(file);
var item = JsonConvert.DeserializeObject<SynthPreset>(json);
if (item != null) list.Add(item);
}
catch (Exception ex)
{
Log($"波形文件解析失败 [{Path.GetFileName(file)}]:{ex.Message}");
}
}
}
}
catch (Exception ex)
{
Log($"读取波形目录失败:{ex.Message}");
}
_synthPresets = list;
_synthPresetOptions = list.Select(p => p.Name).ToList();
RaisePropertyChanged(nameof(SynthPresetOptions));
RaisePropertyChanged(nameof(SynthStorageHint));
if (_selectedSynthPresetIndex >= _synthPresets.Count)
SelectedSynthPresetIndex = -1;
}
/// <summary>把一个合成波形预设写入 {SynthStorageDir}\{清洗后名称}.json(缩进格式化)。</summary>
private void WriteSynthPresetFile(SynthPreset preset)
{
string path = Path.Combine(SynthStorageDir, SanitizeFileName(preset.Name) + PresetFileExt);
string json = JsonConvert.SerializeObject(preset, Formatting.Indented);
File.WriteAllText(path, json);
}
/// <summary>载入选中波形:把文件参数一次性回填到界面各 SYNTHESIS 输入框。</summary>
private void LoadCurrentSynthPreset()
{
if (SelectedSynthPresetIndex < 0 || SelectedSynthPresetIndex >= _synthPresets.Count)
{
Log("请先在下拉框选择一项波形");
return;
}
var p = _synthPresets[SelectedSynthPresetIndex];
SynthCompose = p.Compose;
SynthFrequency = p.Frequency;
SynthFundamental = p.Fundamental;
SynthDc = p.Dc;
SynthStartPhase = p.StartPhase;
SynthAmplitudeText = p.AmplitudeText;
SynthPhaseText = p.PhaseText;
SynthPresetName = p.Name;
SynthPresetNote = p.Note;
Log($"已载入波形:{p.Name}(格式={p.Compose} 基频={p.Frequency}Hz 基波={p.Fundamental}V)");
}
/// <summary>
/// 保存当前界面的 SYNTHESIS 参数快照为波形文件(名称取自“波形名称”输入框)。
/// </summary>
private void SaveCurrentSynthPreset()
{
string name = (SynthPresetName ?? "").Trim();
if (string.IsNullOrEmpty(name))
{
Log("请先在“波形名称”填写要保存的名称");
return;
}
try
{
if (!Directory.Exists(SynthStorageDir))
Directory.CreateDirectory(SynthStorageDir);
var preset = new SynthPreset(name, SynthPresetNote ?? "", SynthCompose, SynthFrequency,
SynthFundamental, SynthDc, SynthStartPhase, SynthAmplitudeText, SynthPhaseText);
WriteSynthPresetFile(preset);
RefreshSynthPresets();
int idx = _synthPresets.FindIndex(p => p.Name == name);
if (idx >= 0) SelectedSynthPresetIndex = idx;
Log($"波形已保存:{Path.Combine(SynthStorageDir, SanitizeFileName(name) + PresetFileExt)}");
}
catch (Exception ex)
{
Log($"保存波形失败:{ex.Message}");
}
}
/// <summary>删除当前选中的波形文件(带二次确认),删除后刷新列表。</summary>
private void DeleteCurrentSynthPreset()
{
if (SelectedSynthPresetIndex < 0 || SelectedSynthPresetIndex >= _synthPresets.Count)
{
Log("请先在下拉框选择要删除的波形");
return;
}
var p = _synthPresets[SelectedSynthPresetIndex];
string path = Path.Combine(SynthStorageDir, SanitizeFileName(p.Name) + PresetFileExt);
var confirm = MessageBox.Show(
$"确定删除波形「{p.Name}」?\n文件:{path}\n此操作不可撤销。",
"删除确认", MessageBoxButton.YesNo, MessageBoxImage.Warning);
if (confirm != MessageBoxResult.Yes) return;
try
{
if (File.Exists(path))
File.Delete(path);
SelectedSynthPresetIndex = -1;
RefreshSynthPresets();
Log($"已删除波形:{path}");
}
catch (Exception ex)
{
Log($"删除波形失败:{ex.Message}");
}
}
#endregion
#region 辅助
/// <summary>创建 10 秒超时的取消令牌(命令用)。</summary>
private CancellationToken Ct() => (_cmdCts = new CancellationTokenSource(TimeSpan.FromSeconds(10))).Token;
@@ -958,6 +982,10 @@ namespace PowerAdaptabilityModule.ViewModels
private async Task 等待List空闲Async(int timeoutMs = 8000)
{
if (_dev == null) return;
// SYNTH / 无限 LIST 属连续执行模态,不会自行回到 OFF;必须先强制 TRIG OFF 停止,
// 否则执行中改写参数会被仪器忽略(输出沿用本地已存波形)。
try { await _dev.触发列表执行Async(false, Ct()); }
catch { /* 停止失败不阻塞,继续靠轮询兜底 */ }
var sw = System.Diagnostics.Stopwatch.StartNew();
while (sw.ElapsedMilliseconds < timeoutMs)
{
@@ -968,7 +996,7 @@ namespace PowerAdaptabilityModule.ViewModels
catch { return; } // 查询失败不阻塞下发流程
await Task.Delay(200);
}
Log("等待 LIST 空闲超时(可能仍在执行),将继续下发,请留意仪器状态");
Log("等待空闲超时(可能仍在执行),将继续下发,请留意仪器状态");
}
/// <summary>
@@ -1085,7 +1113,6 @@ namespace PowerAdaptabilityModule.ViewModels
public void Dispose()
{
StopSampling();
try { _cmdCts?.Cancel(); } catch { }
_cmdCts?.Dispose();
}
@@ -1153,4 +1180,46 @@ namespace PowerAdaptabilityModule.ViewModels
TimeBase = timeBase;
}
}
/// <summary>
/// SYNTHESIS 自定义谐波波形预设:一次载入即可填充合成波形全部参数
/// (数据格式/基频/基波电压/直流/起始相位/谐波振幅与相位序列)。
/// </summary>
public class SynthPreset
{
/// <summary>预设名称(保存时作为文件名,供下拉显示)</summary>
public string Name { get; }
/// <summary>波形说明</summary>
public string Note { get; }
/// <summary>谐波阶数据格式 (SYNT:COMP)</summary>
public string Compose { get; }
/// <summary>基频 (SYNT:FREQ, 50 | 60)</summary>
public int Frequency { get; }
/// <summary>基波交流电压 (SYNT:FUND, V)</summary>
public double Fundamental { get; }
/// <summary>叠加直流电压 (SYNT:DC, V)</summary>
public double Dc { get; }
/// <summary>起始相位角 (SYNT:SPH, °)</summary>
public double StartPhase { get; }
/// <summary>各次谐波振幅序列文本 (SYNT:AMPL, 逗号分隔, 首值=2次谐波)</summary>
public string AmplitudeText { get; }
/// <summary>各次谐波相位角序列文本 (SYNT:PHAS, 逗号分隔, 首值=2次谐波)</summary>
public string PhaseText { get; }
[JsonConstructor]
public SynthPreset(string name, string note, string compose, int frequency,
double fundamental, double dc, double startPhase,
string amplitudeText, string phaseText)
{
Name = name;
Note = note;
Compose = compose;
Frequency = frequency;
Fundamental = fundamental;
Dc = dc;
StartPhase = startPhase;
AmplitudeText = amplitudeText;
PhaseText = phaseText;
}
}
}
@@ -8,7 +8,6 @@
xmlns:prism="http://prismlibrary.com/"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:converters="clr-namespace:UIShare.Converters;assembly=UIShare"
xmlns:oxy="http://oxyplot.org/wpf"
prism:ViewModelLocator.AutoWireViewModel="True"
d:DesignHeight="1080"
d:DesignWidth="1920">
@@ -19,7 +18,6 @@
<Border Background="#F5F7FA">
<Grid Margin="8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="440" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
@@ -97,6 +95,9 @@
Style="{StaticResource WarnBtn}" />
<Button Content="复位" Command="{Binding ResetCommand}"
Style="{StaticResource CmdBtn}" />
<Button Content="退出远程" Command="{Binding ExitRemoteCommand}"
Style="{StaticResource CmdBtn}"
ToolTip="SYST:LOC:退出远程状态、恢复前面板操作(手册注明僅 RS-232 控制下有效)" />
<Button Content="查询 IDN" Command="{Binding QueryIdnCommand}"
Style="{StaticResource CmdBtn}" />
</StackPanel>
@@ -324,45 +325,88 @@
</StackPanel>
</GroupBox>
<!-- 数据采集 -->
<GroupBox Header="数据采集" Margin="0,0,0,8"
<!-- SYNTHESIS 自定义谐波波形输出 -->
<GroupBox Header="SYNTHESIS 波形输出(自定义谐波)" Margin="0,0,0,8"
materialDesign:ColorZoneAssist.Mode="PrimaryLight">
<StackPanel Margin="4">
<StackPanel Orientation="Horizontal" Margin="0,4">
<Button Content="开始采集" Command="{Binding StartSamplingCommand}"
Style="{StaticResource CmdBtn}"
Background="#388E3C" Foreground="White" />
<Button Content="停止采集" Command="{Binding StopSamplingCommand}"
<TextBlock Text="波形文件" Style="{StaticResource ParamLabel}" />
<ComboBox Width="210" Height="32" FontSize="12" Margin="4,0"
ItemsSource="{Binding SynthPresetOptions}"
SelectedIndex="{Binding SelectedSynthPresetIndex}" />
<Button Content="载入" Command="{Binding LoadSynthPresetCommand}"
Style="{StaticResource CmdBtn}" />
<Button Content="清空图表" Command="{Binding ClearChartsCommand}"
Style="{StaticResource CmdBtn}" />
<TextBlock VerticalAlignment="Center" FontSize="12" Margin="8,0,0,0">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Text" Value="采集中…" />
<Setter Property="Foreground" Value="#4CAF50" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsSampling}" Value="False">
<Setter Property="Text" Value="采集已停止" />
<Setter Property="Foreground" Value="#9E9E9E" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="0,4">
<CheckBox Content="采集频率" IsChecked="{Binding IsFrequencySamplingEnabled}"
VerticalAlignment="Center" Margin="4,0,10,0"
ToolTip="频率一般无需持续采集;勾选后采样循环才读取频率并绘制到右侧频率曲线" />
<TextBlock Text="频率" Style="{StaticResource ParamLabel}" />
<TextBox Style="{StaticResource MeasureBox}"
Text="{Binding FrequencyNow, Mode=OneWay, StringFormat={}{0:F2}}" />
<TextBlock Text="Hz" VerticalAlignment="Center" Margin="2,0,12,0" />
<TextBlock Text="功率" Style="{StaticResource ParamLabel}" />
<TextBox Style="{StaticResource MeasureBox}"
Text="{Binding PowerNow, Mode=OneWay, StringFormat={}{0:F2}}" />
<TextBlock Text="W" VerticalAlignment="Center" Margin="2,0,0,0" />
<TextBlock Text="名称" Style="{StaticResource ParamLabel}" />
<TextBox Style="{StaticResource NumInput}" Width="150"
ToolTip="保存时作为文件名(写入 D:\acp\源测试\合成波形);选中已有波形会自动填充,可改名另存"
Text="{Binding SynthPresetName, UpdateSourceTrigger=PropertyChanged}" />
<Button Content="保存" Command="{Binding SaveSynthPresetCommand}"
Style="{StaticResource CmdBtn}"
Background="#388E3C" Foreground="White" />
<Button Content="删除" Command="{Binding DeleteSynthPresetCommand}"
Style="{StaticResource WarnBtn}"
ToolTip="删除下拉框当前选中的波形文件(带二次确认)" />
</StackPanel>
<TextBox Margin="4,0,4,2" FontSize="11" Foreground="#1565C0"
Height="40" TextWrapping="Wrap" AcceptsReturn="True"
VerticalScrollBarVisibility="Auto" VerticalContentAlignment="Top"
ToolTip="波形说明(随文件一并保存)"
Text="{Binding SynthPresetNote, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Text="{Binding SynthStorageHint}"
FontSize="11" Foreground="#757575"
Margin="4,0,4,4" TextWrapping="Wrap" />
<Separator Margin="0,4" />
<StackPanel Orientation="Horizontal" Margin="0,4">
<TextBlock Text="数据格式" Style="{StaticResource ParamLabel}" />
<ComboBox Width="120" Height="32" FontSize="12" Margin="4,0"
ToolTip="SYNT:COMP,VALUE=绝对值 / PERCENT=基频百分率,后缀 1/2/3 为三段波形"
ItemsSource="{Binding SynthComposeOptions}"
SelectedItem="{Binding SynthCompose}" />
<TextBlock Text="基频" Style="{StaticResource ParamLabel}" />
<ComboBox Width="70" Height="32" FontSize="12" Margin="4,0"
ToolTip="SYNT:FREQ,仅支持 50 / 60 Hz"
ItemsSource="{Binding SynthFrequencyOptions}"
SelectedItem="{Binding SynthFrequency}" />
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="0,4">
<TextBlock Text="基波电压" Style="{StaticResource ParamLabel}" />
<TextBox Style="{StaticResource NumInput}"
ToolTip="SYNT:FUND,基波交流电压,单位 V,0.0 ~ 300.0"
Text="{Binding SynthFundamental, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Text="V" VerticalAlignment="Center" Margin="2,0,10,0" />
<TextBlock Text="直流" Style="{StaticResource ParamLabel}" />
<TextBox Style="{StaticResource NumInput}"
ToolTip="SYNT:DC,叠加直流电压,单位 V,-424.2 ~ 424.2"
Text="{Binding SynthDc, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Text="V" VerticalAlignment="Center" Margin="2,0,0,0" />
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="0,4">
<TextBlock Text="起始相位" Style="{StaticResource ParamLabel}" />
<TextBox Style="{StaticResource NumInput}"
ToolTip="SYNT:SPH,起始相位角,单位 °,0.0 ~ 359.9"
Text="{Binding SynthStartPhase, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Text="°" VerticalAlignment="Center" Margin="2,0,0,0" />
</StackPanel>
<TextBlock Text="谐波振幅(SYNT:AMPL,英文逗号分隔,首值=2次谐波,依次 3/4/5…最多 50 阶,留空不下发)"
FontSize="11" Foreground="#616161" Margin="4,4,4,2" TextWrapping="Wrap" />
<TextBox Style="{StaticResource NumInput}" Width="380" HorizontalAlignment="Left" Margin="4,0"
ToolTip="如 0,10,0,5 表示 2次=0、3次=10、4次=0、5次=5(VALUE 为绝对值 V,PERCENT 为基波百分率)"
Text="{Binding SynthAmplitudeText, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Text="谐波相位(SYNT:PHAS,英文逗号分隔,与振幅逐阶对应,单位 °,0~359.9,留空不下发)"
FontSize="11" Foreground="#616161" Margin="4,4,4,2" TextWrapping="Wrap" />
<TextBox Style="{StaticResource NumInput}" Width="380" HorizontalAlignment="Left" Margin="4,0"
ToolTip="如 0,180,90 与振幅序列逐阶对应"
Text="{Binding SynthPhaseText, UpdateSourceTrigger=PropertyChanged}" />
<Separator Margin="0,6" />
<StackPanel Orientation="Horizontal" Margin="0,4">
<Button Content="下发并启动 SYNTH" Command="{Binding DownloadSynthesisCommand}"
Style="{StaticResource CmdBtn}"
Background="#388E3C" Foreground="White" />
<Button Content="停止 SYNTH" Command="{Binding SynthStopCommand}"
Style="{StaticResource WarnBtn}" />
</StackPanel>
</StackPanel>
</GroupBox>
@@ -379,70 +423,6 @@
</GroupBox>
</StackPanel>
</ScrollViewer>
<!-- ============ 右侧:三相电压 + 输出频率(横轴为采集经过时间 s) ============ -->
<Grid Grid.Column="1">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<!-- 三相电压 -->
<Border Grid.Row="0" Background="White" BorderBrush="#DDD" BorderThickness="1" Margin="0,0,0,4">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal" Margin="10,6">
<TextBlock Text="三相电压 (V)" FontWeight="Bold" FontSize="13"
VerticalAlignment="Center" Margin="0,0,16,0" />
<TextBlock Text="A" FontSize="12" Foreground="#E53935" VerticalAlignment="Center" />
<TextBlock Text="{Binding VoltageA, StringFormat={}{0:F1}}" FontSize="12"
FontWeight="Bold" VerticalAlignment="Center" Margin="3,0,14,0" />
<TextBlock Text="B" FontSize="12" Foreground="#43A047" VerticalAlignment="Center" />
<TextBlock Text="{Binding VoltageB, StringFormat={}{0:F1}}" FontSize="12"
FontWeight="Bold" VerticalAlignment="Center" Margin="3,0,14,0" />
<TextBlock Text="C" FontSize="12" Foreground="#1E88E5" VerticalAlignment="Center" />
<TextBlock Text="{Binding VoltageC, StringFormat={}{0:F1}}" FontSize="12"
FontWeight="Bold" VerticalAlignment="Center" Margin="3,0,0,0" />
</StackPanel>
<oxy:PlotView Grid.Row="1" Model="{Binding VoltagePlot}"
Background="Transparent" Margin="4,0,4,4" />
</Grid>
</Border>
<!-- 输出频率(受左侧“采集频率”开关控制,一般不采集) -->
<Border Grid.Row="1" Background="White" BorderBrush="#DDD" BorderThickness="1">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal" Margin="10,6">
<TextBlock Text="输出频率 (Hz)" FontWeight="Bold" FontSize="13"
VerticalAlignment="Center" Margin="0,0,16,0" />
<TextBlock Text="{Binding FrequencyNow, StringFormat={}{0:F2}}" FontSize="12"
FontWeight="Bold" Foreground="#8E24AA" VerticalAlignment="Center" Margin="3,0,14,0" />
<TextBlock FontSize="11" Foreground="#4CAF50" VerticalAlignment="Center">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Text" Value="频率采集中" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsFrequencySamplingEnabled}" Value="False">
<Setter Property="Text" Value="频率采集未开启(在左侧“数据采集”勾选后才会绘制)" />
<Setter Property="Foreground" Value="#9E9E9E" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</StackPanel>
<oxy:PlotView Grid.Row="1" Model="{Binding FrequencyPlot}"
Background="Transparent" Margin="4,0,4,4" />
</Grid>
</Border>
</Grid>
</Grid>
</Border>
</UserControl>