diff --git a/DeviceCommand/Devices/Chroma61800.cs b/DeviceCommand/Devices/Chroma61800.cs index 7827041..2dbe43a 100644 --- a/DeviceCommand/Devices/Chroma61800.cs +++ b/DeviceCommand/Devices/Chroma61800.cs @@ -49,64 +49,102 @@ namespace DeviceCommand.Devices #endregion - #region 3. LIST 步阶参数配置 + #region 3. LIST 步阶参数配置 (独立发送版 - 三参数固定版) /// - /// 配置单相的 LIST 序列参数 - /// 传入的数组长度必须保持一致,代表 LIST 的每个 Step(SEQ) + /// 切换目标相位并设置波形为正弦波 + /// 建议在发送具体 LIST 参数前调用一次即可 /// /// 相位 (1: L1, 2: L2, 3: L3) - /// 各步起始角度 (如 [0, 0, 0]) - /// 各步交流起始电压 (如 [230, 11.5, 230]) - /// 各步交流结束电压 (如 [230, 11.5, 230]) - /// 各步直流起始电压 - /// 各步直流结束电压 - /// 各步起始频率 - /// 各步结束频率 - /// 各步执行时间(单位: 毫秒) - public virtual async Task 配置单相List序列( - int phase, - double[] degrees, - double[] vacStart, double[] vacEnd, - double[] vdcStart, double[] vdcEnd, - double[] freqStart, double[] freqEnd, - double[] dwellTimeMs, - CancellationToken ct = default) + public virtual async Task 切换相位Async(int phase, CancellationToken ct = default) { - if (phase < 1 || phase > 3) throw new ArgumentOutOfRangeException(nameof(phase), "相位必须为 1, 2, 或 3"); + if (phase < 1 || phase > 3) + throw new ArgumentOutOfRangeException(nameof(phase), "相位必须为 1, 2, 或 3"); - // 选择相位并设置为正弦波 await SendAsync($"INST:NSEL {phase}{ScpiDelimiter}", ct); await SendAsync($"FUNC:SHAP:A SINE{ScpiDelimiter}", ct); - - // 转换数组为 SCPI 需要的逗号分隔字符串 - string strDegrees = JoinParameters(degrees); - string strVacStart = JoinParameters(vacStart); - string strVacEnd = JoinParameters(vacEnd); - string strVdcStart = JoinParameters(vdcStart); - string strVdcEnd = JoinParameters(vdcEnd); - string strFreqStart = JoinParameters(freqStart); - string strFreqEnd = JoinParameters(freqEnd); - string strDwell = JoinParameters(dwellTimeMs); - - // 下发序列参数 - // 注意 Chroma 部分指令是 DEGR,部分固件版本是 DEGRee,这里统一使用简写 DEGR,兼容性最好 - await SendAsync($"LIST:DEGR {strDegrees}{ScpiDelimiter}", ct); - await SendAsync($"LIST:VOLT:AC:STAR {strVacStart}{ScpiDelimiter}", ct); - await SendAsync($"LIST:VOLT:AC:END {strVacEnd}{ScpiDelimiter}", ct); - await SendAsync($"LIST:VOLT:DC:STAR {strVdcStart}{ScpiDelimiter}", ct); - await SendAsync($"LIST:VOLT:DC:END {strVdcEnd}{ScpiDelimiter}", ct); - await SendAsync($"LIST:FREQ:STAR {strFreqStart}{ScpiDelimiter}", ct); - await SendAsync($"LIST:FREQ:END {strFreqEnd}{ScpiDelimiter}", ct); - await SendAsync($"LIST:DWEL {strDwell}{ScpiDelimiter}", ct); } /// - /// 辅助方法:将 double 数组转换为逗号分隔的字符串,确保小数点格式正确 + /// 配置 LIST 各步起始角度 /// - private string JoinParameters(double[] values) + public virtual async Task 配置List起始角度Async(double step1, double step2, double step3, CancellationToken ct = default) { - return string.Join(",", values.Select(v => v.ToString("0.###", CultureInfo.InvariantCulture))); + string strValues = JoinParameters(step1, step2, step3); + await SendAsync($"LIST:DEGR {strValues}{ScpiDelimiter}", ct); + } + + /// + /// 配置 LIST 各步交流起始电压 + /// + public virtual async Task 配置List交流起始电压Async(double step1, double step2, double step3, CancellationToken ct = default) + { + string strValues = JoinParameters(step1, step2, step3); + await SendAsync($"LIST:VOLT:AC:STAR {strValues}{ScpiDelimiter}", ct); + } + + /// + /// 配置 LIST 各步交流结束电压 + /// + public virtual async Task 配置List交流结束电压Async(double step1, double step2, double step3, CancellationToken ct = default) + { + string strValues = JoinParameters(step1, step2, step3); + await SendAsync($"LIST:VOLT:AC:END {strValues}{ScpiDelimiter}", ct); + } + + /// + /// 配置 LIST 各步直流起始电压 + /// + public virtual async Task 配置List直流起始电压Async(double step1, double step2, double step3, CancellationToken ct = default) + { + string strValues = JoinParameters(step1, step2, step3); + await SendAsync($"LIST:VOLT:DC:STAR {strValues}{ScpiDelimiter}", ct); + } + + /// + /// 配置 LIST 各步直流结束电压 + /// + public virtual async Task 配置List直流结束电压Async(double step1, double step2, double step3, CancellationToken ct = default) + { + string strValues = JoinParameters(step1, step2, step3); + await SendAsync($"LIST:VOLT:DC:END {strValues}{ScpiDelimiter}", ct); + } + + /// + /// 配置 LIST 各步起始频率 + /// + public virtual async Task 配置List起始频率Async(double step1, double step2, double step3, CancellationToken ct = default) + { + string strValues = JoinParameters(step1, step2, step3); + await SendAsync($"LIST:FREQ:STAR {strValues}{ScpiDelimiter}", ct); + } + + /// + /// 配置 LIST 各步结束频率 + /// + public virtual async Task 配置List结束频率Async(double step1, double step2, double step3, CancellationToken ct = default) + { + string strValues = JoinParameters(step1, step2, step3); + await SendAsync($"LIST:FREQ:END {strValues}{ScpiDelimiter}", ct); + } + + /// + /// 配置 LIST 各步执行时间 (单位: 毫秒) + /// + public virtual async Task 配置List执行时间Async(double step1, double step2, double step3, CancellationToken ct = default) + { + string strValues = JoinParameters(step1, step2, step3); + await SendAsync($"LIST:DWEL {strValues}{ScpiDelimiter}", ct); + } + + /// + /// 辅助方法:将三个 double 值转换为逗号分隔的字符串,确保小数点格式正确 + /// + private string JoinParameters(double val1, double val2, double val3) + { + return $"{val1.ToString("0.###", CultureInfo.InvariantCulture)}," + + $"{val2.ToString("0.###", CultureInfo.InvariantCulture)}," + + $"{val3.ToString("0.###", CultureInfo.InvariantCulture)}"; } #endregion diff --git a/DeviceCommand/Devices/IT6015C.cs b/DeviceCommand/Devices/IT6015C.cs index 2215ece..e553872 100644 --- a/DeviceCommand/Devices/IT6015C.cs +++ b/DeviceCommand/Devices/IT6015C.cs @@ -21,7 +21,9 @@ namespace DeviceCommand.Devices { } - #region 1. IEEE 488.2 公共命令 + #region 源载公用或系统必备命令 + + // ================= 1. IEEE 488.2 公共命令 ================= public virtual async Task 清除状态寄存器(CancellationToken ct = default) { @@ -48,9 +50,8 @@ namespace DeviceCommand.Devices return await WriteReadAsync($":SYSTem:VERSion?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1] } - #endregion - #region 2. 系统远程与状态控制 + // ================= 2. 系统远程与状态控制 ================= public virtual async Task 切换远程控制模式(CancellationToken ct = default) { @@ -77,9 +78,8 @@ namespace DeviceCommand.Devices await SendAsync($"SYSTem:CLEar{ScpiDelimiter}", ct); //[cite: 1] } - #endregion - #region 3. 功能模式与优先级设置 + // ================= 3. 功能模式与优先级设置 ================= /// /// 设置电源优先工作模式 (CV 电压优先 或 CC 电流优先) @@ -107,9 +107,8 @@ namespace DeviceCommand.Devices return await WriteReadAsync($"FUNCTION:MODE?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1] } - #endregion - #region 4. 输出开关与保护清除 + // ================= 4. 输出开关与保护清除 ================= public virtual async Task 设置输出开关(bool 开启, CancellationToken ct = default) { @@ -127,12 +126,11 @@ namespace DeviceCommand.Devices await SendAsync($"OUTPut:PROTection:CLEar{ScpiDelimiter}", ct); //[cite: 1] } - #endregion - #region 5. 输出电压 / 电流设定 + // ================= 5. 基础设定 (正值代表输出,负值代表吸收) ================= /// - /// 设置输出电压 (仅在 CV 模式下生效) + /// 设置输出/吸收电压 (仅在 CV 模式下生效) /// public virtual async Task 设置电压(double 电压, CancellationToken ct = default) { @@ -146,7 +144,7 @@ namespace DeviceCommand.Devices } /// - /// 设置输出电流 (仅在 CC 模式下生效) + /// 设置输出/吸收电流 (仅在 CC 模式下生效。设定负值即作为负载吸收电流) /// public virtual async Task 设置电流(double 电流, CancellationToken ct = default) { @@ -160,21 +158,7 @@ namespace DeviceCommand.Devices } /// - /// 设置 CV 优先模式下的电流限制上限值 (I+, 限流保护) - /// - public virtual async Task 设置电流限制上限(double 电流, CancellationToken ct = default) - { - string cmd = string.Format(CultureInfo.InvariantCulture, "CURRent:LIMit:POSitive {0:F3}{1}", 电流, ScpiDelimiter); //[cite: 1] - await SendAsync(cmd, ct); - } - - public virtual async Task 查询电流限制上限(CancellationToken ct = default) - { - return await WriteReadAsync($"CURRent:LIMit:POSitive?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1] - } - - /// - /// 设置电压上升/下降斜率 (单位: V/s, 数值越大变化越快)[cite: 1] + /// 设置电压上升/下降斜率 (单位: V/s) /// public virtual async Task 设置电压斜率(double 斜率Vs, CancellationToken ct = default) { @@ -182,13 +166,9 @@ namespace DeviceCommand.Devices await SendAsync(cmd, ct); } - #endregion - #region 6. 保护功能配置 (OVP, OCP, OPP) + // ================= 6. 保护功能配置 (OVP, OCP, OPP) ================= - /// - /// 开启或关闭过压保护 (OVP)[cite: 1] - /// public virtual async Task 设置过压保护开关(bool 开启, CancellationToken ct = default) { string 参数 = 开启 ? "ON" : "OFF"; @@ -201,9 +181,6 @@ namespace DeviceCommand.Devices await SendAsync(cmd, ct); } - /// - /// 开启或关闭过流保护 (OCP)[cite: 1] - /// public virtual async Task 设置过流保护开关(bool 开启, CancellationToken ct = default) { string 参数 = 开启 ? "ON" : "OFF"; @@ -216,24 +193,20 @@ namespace DeviceCommand.Devices await SendAsync(cmd, ct); } - /// - /// 开启或关闭过功率保护 (OPP)[cite: 1] - /// public virtual async Task 设置过功率保护开关(bool 开启, CancellationToken ct = default) { string 参数 = 开启 ? "ON" : "OFF"; - await SendAsync($"POWER:PROTection:STATe {参数}{ScpiDelimiter}", ct); //[cite: 1] + await SendAsync($"POWer:PROTection:STATe {参数}{ScpiDelimiter}", ct); //[cite: 1] } public virtual async Task 设置过功率保护阈值(double 功率, CancellationToken ct = default) { - string cmd = string.Format(CultureInfo.InvariantCulture, "POWER:PROTection {0:F2}{1}", 功率, ScpiDelimiter); //[cite: 1] + string cmd = string.Format(CultureInfo.InvariantCulture, "POWer:PROTection {0:F2}{1}", 功率, ScpiDelimiter); //[cite: 1] await SendAsync(cmd, ct); } - #endregion - #region 7. 测量回读 (MEASure 与 FETCh) + // ================= 7. 测量回读 (MEASure 与 FETCh) ================= [Monitorable("直流源载实际电压")] public virtual async Task 查询实际电压(CancellationToken ct = default) @@ -250,7 +223,7 @@ namespace DeviceCommand.Devices [Monitorable("直流源载实际功率")] public virtual async Task 查询实际功率(CancellationToken ct = default) { - return await WriteReadAsync($"MEASure:POWER?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1] + return await WriteReadAsync($"MEASure:POWer?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1] } public virtual async Task 查询瞬时电压最大值(CancellationToken ct = default) @@ -265,12 +238,101 @@ namespace DeviceCommand.Devices #endregion - #region 8. 负载电阻/CR模式设置 (仅限 CC 优先模式) + + #region 直流源 (Source) 专用命令 /// - /// 开启或关闭恒阻 (CR) 负载模式[cite: 1] + /// 设置 CV 优先模式下的电流限制上限值 (I+, 正向源限流) + /// + public virtual async Task 设置电流限制上限(double 电流, CancellationToken ct = default) + { + string cmd = string.Format(CultureInfo.InvariantCulture, "CURRent:LIMit:POSitive {0:F3}{1}", 电流, ScpiDelimiter); //[cite: 1] + await SendAsync(cmd, ct); + } + + public virtual async Task 查询电流限制上限(CancellationToken ct = default) + { + return await WriteReadAsync($"CURRent:LIMit:POSitive?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1] + } + + /// + /// 设置 CC 优先模式下的电压限制上限值 (Vh, 正向源限压) + /// + public virtual async Task 设置电压限制上限(double 电压, CancellationToken ct = default) + { + string cmd = string.Format(CultureInfo.InvariantCulture, "VOLTage:LIMit:POSitive {0:F2}{1}", 电压, ScpiDelimiter); //[cite: 1] + await SendAsync(cmd, ct); + } + + public virtual async Task 查询电压限制上限(CancellationToken ct = default) + { + return await WriteReadAsync($"VOLTage:LIMit:POSitive?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1] + } + + /// + /// 设置功率上限值 (P+, 源模式功率限制) + /// + public virtual async Task 设置功率限制上限(double 功率, CancellationToken ct = default) + { + string cmd = string.Format(CultureInfo.InvariantCulture, "POWer:LIMit:POSitive {0:F2}{1}", 功率, ScpiDelimiter); //[cite: 1] + await SendAsync(cmd, ct); + } + + public virtual async Task 查询功率限制上限(CancellationToken ct = default) + { + return await WriteReadAsync($"POWer:LIMit:POSitive?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1] + } + + #endregion + + + #region 电子负载 (Load) 专用命令 + + /// + /// 设置 CV 优先模式下的电流限制下限值 (I-, 载模式电流吸收限制) + /// + public virtual async Task 设置电流限制下限(double 电流, CancellationToken ct = default) + { + string cmd = string.Format(CultureInfo.InvariantCulture, "CURRent:LIMit:NEGative {0:F3}{1}", 电流, ScpiDelimiter); //[cite: 1] + await SendAsync(cmd, ct); + } + + public virtual async Task 查询电流限制下限(CancellationToken ct = default) + { + return await WriteReadAsync($"CURRent:LIMit:NEGative?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1] + } + + /// + /// 设置 CC 优先模式下的电压限制下限值 (Vl, 载模式放电截止电压) + /// + public virtual async Task 设置电压限制下限(double 电压, CancellationToken ct = default) + { + string cmd = string.Format(CultureInfo.InvariantCulture, "VOLTage:LIMit:NEGative {0:F2}{1}", 电压, ScpiDelimiter); //[cite: 1] + await SendAsync(cmd, ct); + } + + public virtual async Task 查询电压限制下限(CancellationToken ct = default) + { + return await WriteReadAsync($"VOLTage:LIMit:NEGative?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1] + } + + /// + /// 设置功率下限值 (P-, 载模式吸收功率限制) + /// + public virtual async Task 设置功率限制下限(double 功率, CancellationToken ct = default) + { + string cmd = string.Format(CultureInfo.InvariantCulture, "POWer:LIMit:NEGative {0:F2}{1}", 功率, ScpiDelimiter); //[cite: 1] + await SendAsync(cmd, ct); + } + + public virtual async Task 查询功率限制下限(CancellationToken ct = default) + { + return await WriteReadAsync($"POWer:LIMit:NEGative?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1] + } + + /// + /// 开启或关闭恒阻 (CR) 负载模式 (执行前必须处于 CC 优先模式) /// - /// 执行该指令前,必须确保设备处于 CC 优先模式,否则会报错 public virtual async Task 设置CR模式开关(bool 开启, CancellationToken ct = default) { string 参数 = 开启 ? "ON" : "OFF"; @@ -283,9 +345,8 @@ namespace DeviceCommand.Devices } /// - /// 设置恒阻 (CR) 模式下的电阻值 (单位: Ω)[cite: 1] + /// 设置恒阻 (CR) 模式下的电阻值 (单位: Ω) /// - /// 必须在先开启 CR 模式才能使用,设置为 0 相当于关闭 CR 模式 public virtual async Task 设置CR电阻值(double 电阻, CancellationToken ct = default) { string cmd = string.Format(CultureInfo.InvariantCulture, "SINK:RESistance {0:F2}{1}", 电阻, ScpiDelimiter); //[cite: 1] diff --git a/DeviceCommand/Devices/IT6036C .cs b/DeviceCommand/Devices/IT6036C .cs index c9b2c6f..d82af8e 100644 --- a/DeviceCommand/Devices/IT6036C .cs +++ b/DeviceCommand/Devices/IT6036C .cs @@ -53,7 +53,9 @@ namespace DeviceCommand.Devices { } - #region 1. IEEE 488.2 公共命令 + #region 源载公用或系统必备命令 + + // ================= 1. IEEE 488.2 公共命令 ================= public virtual async Task 清除状态寄存器(CancellationToken ct = default) { @@ -80,9 +82,8 @@ namespace DeviceCommand.Devices return await WriteReadAsync($":SYSTem:VERSion?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1] } - #endregion - #region 2. 系统远程与状态控制 + // ================= 2. 系统远程与状态控制 ================= public virtual async Task 切换远程控制模式(CancellationToken ct = default) { @@ -109,9 +110,8 @@ namespace DeviceCommand.Devices await SendAsync($"SYSTem:CLEar{ScpiDelimiter}", ct); //[cite: 1] } - #endregion - #region 3. 功能模式与优先级设置 + // ================= 3. 功能模式与优先级设置 ================= /// /// 设置电源优先工作模式 (CV 电压优先 或 CC 电流优先) @@ -139,9 +139,8 @@ namespace DeviceCommand.Devices return await WriteReadAsync($"FUNCTION:MODE?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1] } - #endregion - #region 4. 输出开关与保护清除 + // ================= 4. 输出开关与保护清除 ================= public virtual async Task 设置输出开关(bool 开启, CancellationToken ct = default) { @@ -159,12 +158,11 @@ namespace DeviceCommand.Devices await SendAsync($"OUTPut:PROTection:CLEar{ScpiDelimiter}", ct); //[cite: 1] } - #endregion - #region 5. 输出电压 / 电流设定 + // ================= 5. 基础设定 (正值代表输出,负值代表吸收) ================= /// - /// 设置输出电压 (仅在 CV 模式下生效) + /// 设置输出/吸收电压 (仅在 CV 模式下生效) /// public virtual async Task 设置电压(double 电压, CancellationToken ct = default) { @@ -178,7 +176,7 @@ namespace DeviceCommand.Devices } /// - /// 设置输出电流 (仅在 CC 模式下生效) + /// 设置输出/吸收电流 (仅在 CC 模式下生效。设定负值即作为负载吸收电流) /// public virtual async Task 设置电流(double 电流, CancellationToken ct = default) { @@ -192,21 +190,7 @@ namespace DeviceCommand.Devices } /// - /// 设置 CV 优先模式下的电流限制上限值 (I+, 限流保护) - /// - public virtual async Task 设置电流限制上限(double 电流, CancellationToken ct = default) - { - string cmd = string.Format(CultureInfo.InvariantCulture, "CURRent:LIMit:POSitive {0:F3}{1}", 电流, ScpiDelimiter); //[cite: 1] - await SendAsync(cmd, ct); - } - - public virtual async Task 查询电流限制上限(CancellationToken ct = default) - { - return await WriteReadAsync($"CURRent:LIMit:POSitive?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1] - } - - /// - /// 设置电压上升/下降斜率 (单位: V/s, 数值越大变化越快)[cite: 1] + /// 设置电压上升/下降斜率 (单位: V/s) /// public virtual async Task 设置电压斜率(double 斜率Vs, CancellationToken ct = default) { @@ -214,13 +198,9 @@ namespace DeviceCommand.Devices await SendAsync(cmd, ct); } - #endregion - #region 6. 保护功能配置 (OVP, OCP, OPP) + // ================= 6. 保护功能配置 (OVP, OCP, OPP) ================= - /// - /// 开启或关闭过压保护 (OVP)[cite: 1] - /// public virtual async Task 设置过压保护开关(bool 开启, CancellationToken ct = default) { string 参数 = 开启 ? "ON" : "OFF"; @@ -233,9 +213,6 @@ namespace DeviceCommand.Devices await SendAsync(cmd, ct); } - /// - /// 开启或关闭过流保护 (OCP)[cite: 1] - /// public virtual async Task 设置过流保护开关(bool 开启, CancellationToken ct = default) { string 参数 = 开启 ? "ON" : "OFF"; @@ -248,24 +225,20 @@ namespace DeviceCommand.Devices await SendAsync(cmd, ct); } - /// - /// 开启或关闭过功率保护 (OPP)[cite: 1] - /// public virtual async Task 设置过功率保护开关(bool 开启, CancellationToken ct = default) { string 参数 = 开启 ? "ON" : "OFF"; - await SendAsync($"POWER:PROTection:STATe {参数}{ScpiDelimiter}", ct); //[cite: 1] + await SendAsync($"POWer:PROTection:STATe {参数}{ScpiDelimiter}", ct); //[cite: 1] } public virtual async Task 设置过功率保护阈值(double 功率, CancellationToken ct = default) { - string cmd = string.Format(CultureInfo.InvariantCulture, "POWER:PROTection {0:F2}{1}", 功率, ScpiDelimiter); //[cite: 1] + string cmd = string.Format(CultureInfo.InvariantCulture, "POWer:PROTection {0:F2}{1}", 功率, ScpiDelimiter); //[cite: 1] await SendAsync(cmd, ct); } - #endregion - #region 7. 测量回读 (MEASure 与 FETCh) + // ================= 7. 测量回读 (MEASure 与 FETCh) ================= [Monitorable("直流源载实际电压")] public virtual async Task 查询实际电压(CancellationToken ct = default) @@ -282,7 +255,7 @@ namespace DeviceCommand.Devices [Monitorable("直流源载实际功率")] public virtual async Task 查询实际功率(CancellationToken ct = default) { - return await WriteReadAsync($"MEASure:POWER?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1] + return await WriteReadAsync($"MEASure:POWer?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1] } public virtual async Task 查询瞬时电压最大值(CancellationToken ct = default) @@ -297,12 +270,101 @@ namespace DeviceCommand.Devices #endregion - #region 8. 负载电阻/CR模式设置 (仅限 CC 优先模式) + + #region 直流源 (Source) 专用命令 /// - /// 开启或关闭恒阻 (CR) 负载模式[cite: 1] + /// 设置 CV 优先模式下的电流限制上限值 (I+, 正向源限流) + /// + public virtual async Task 设置电流限制上限(double 电流, CancellationToken ct = default) + { + string cmd = string.Format(CultureInfo.InvariantCulture, "CURRent:LIMit:POSitive {0:F3}{1}", 电流, ScpiDelimiter); //[cite: 1] + await SendAsync(cmd, ct); + } + + public virtual async Task 查询电流限制上限(CancellationToken ct = default) + { + return await WriteReadAsync($"CURRent:LIMit:POSitive?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1] + } + + /// + /// 设置 CC 优先模式下的电压限制上限值 (Vh, 正向源限压) + /// + public virtual async Task 设置电压限制上限(double 电压, CancellationToken ct = default) + { + string cmd = string.Format(CultureInfo.InvariantCulture, "VOLTage:LIMit:POSitive {0:F2}{1}", 电压, ScpiDelimiter); //[cite: 1] + await SendAsync(cmd, ct); + } + + public virtual async Task 查询电压限制上限(CancellationToken ct = default) + { + return await WriteReadAsync($"VOLTage:LIMit:POSitive?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1] + } + + /// + /// 设置功率上限值 (P+, 源模式功率限制) + /// + public virtual async Task 设置功率限制上限(double 功率, CancellationToken ct = default) + { + string cmd = string.Format(CultureInfo.InvariantCulture, "POWer:LIMit:POSitive {0:F2}{1}", 功率, ScpiDelimiter); //[cite: 1] + await SendAsync(cmd, ct); + } + + public virtual async Task 查询功率限制上限(CancellationToken ct = default) + { + return await WriteReadAsync($"POWer:LIMit:POSitive?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1] + } + + #endregion + + + #region 电子负载 (Load) 专用命令 + + /// + /// 设置 CV 优先模式下的电流限制下限值 (I-, 载模式电流吸收限制) + /// + public virtual async Task 设置电流限制下限(double 电流, CancellationToken ct = default) + { + string cmd = string.Format(CultureInfo.InvariantCulture, "CURRent:LIMit:NEGative {0:F3}{1}", 电流, ScpiDelimiter); //[cite: 1] + await SendAsync(cmd, ct); + } + + public virtual async Task 查询电流限制下限(CancellationToken ct = default) + { + return await WriteReadAsync($"CURRent:LIMit:NEGative?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1] + } + + /// + /// 设置 CC 优先模式下的电压限制下限值 (Vl, 载模式放电截止电压) + /// + public virtual async Task 设置电压限制下限(double 电压, CancellationToken ct = default) + { + string cmd = string.Format(CultureInfo.InvariantCulture, "VOLTage:LIMit:NEGative {0:F2}{1}", 电压, ScpiDelimiter); //[cite: 1] + await SendAsync(cmd, ct); + } + + public virtual async Task 查询电压限制下限(CancellationToken ct = default) + { + return await WriteReadAsync($"VOLTage:LIMit:NEGative?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1] + } + + /// + /// 设置功率下限值 (P-, 载模式吸收功率限制) + /// + public virtual async Task 设置功率限制下限(double 功率, CancellationToken ct = default) + { + string cmd = string.Format(CultureInfo.InvariantCulture, "POWer:LIMit:NEGative {0:F2}{1}", 功率, ScpiDelimiter); //[cite: 1] + await SendAsync(cmd, ct); + } + + public virtual async Task 查询功率限制下限(CancellationToken ct = default) + { + return await WriteReadAsync($"POWer:LIMit:NEGative?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1] + } + + /// + /// 开启或关闭恒阻 (CR) 负载模式 (执行前必须处于 CC 优先模式) /// - /// 执行该指令前,必须确保设备处于 CC 优先模式,否则会报错 public virtual async Task 设置CR模式开关(bool 开启, CancellationToken ct = default) { string 参数 = 开启 ? "ON" : "OFF"; @@ -315,9 +377,8 @@ namespace DeviceCommand.Devices } /// - /// 设置恒阻 (CR) 模式下的电阻值 (单位: Ω)[cite: 1] + /// 设置恒阻 (CR) 模式下的电阻值 (单位: Ω) /// - /// 必须在先开启 CR 模式才能使用,设置为 0 相当于关闭 CR 模式 public virtual async Task 设置CR电阻值(double 电阻, CancellationToken ct = default) { string cmd = string.Format(CultureInfo.InvariantCulture, "SINK:RESistance {0:F2}{1}", 电阻, ScpiDelimiter); //[cite: 1] diff --git a/DeviceCommand/Devices/TektronixMSO.cs b/DeviceCommand/Devices/TektronixMSO.cs index 81ed7e8..81c36f8 100644 --- a/DeviceCommand/Devices/TektronixMSO.cs +++ b/DeviceCommand/Devices/TektronixMSO.cs @@ -3,18 +3,20 @@ using DeviceCommand.Base; using Model.Models; using System; using System.Globalization; +using System.IO; +using System.Text; using System.Threading; using System.Threading.Tasks; namespace DeviceCommand.Devices { /// - /// 示波器 一拖三 + /// 示波器 MDO/MSO/DPO 系列 /// #region 枚举定义 /// - /// Tektronix MSO 采集模式 + /// Tektronix MSO/MDO 采集模式 /// public enum TekAcquisitionMode { @@ -31,7 +33,7 @@ namespace DeviceCommand.Devices } /// - /// Tektronix MSO 测量类型 (部分常用) + /// Tektronix MSO/MDO 测量类型 (部分常用) /// public enum TekMeasurementType { @@ -71,7 +73,7 @@ namespace DeviceCommand.Devices public virtual async Task 执行自校准(CancellationToken ct = default) { - // 指示仪器执行信号路径校准 (SPC),需要几分钟时间[cite: 1] + // 指示仪器执行信号路径校准 (SPC)[cite: 1] await SendAsync($"*CAL?{ScpiDelimiter}", ct); } @@ -117,25 +119,24 @@ namespace DeviceCommand.Devices await SendAsync($"HORizontal:RECOrdlength {长度}{ScpiDelimiter}", ct); } - public virtual async Task 设置采样率(double 采样率, CancellationToken ct = default) + public virtual async Task 查询采样率(CancellationToken ct = default) { - // 设置或查询水平采样率[cite: 1] - string cmd = string.Format(CultureInfo.InvariantCulture, "HORizontal:SAMPLERate {0:E}{1}", 采样率, ScpiDelimiter); - await SendAsync(cmd, ct); + // 查询水平采样率。注:此命令在当前系列设备中为只读[cite: 1] + return await WriteReadAsync($"HORizontal:SAMPLERate?{ScpiDelimiter}", ScpiDelimiter, ct); } #endregion - #region 4. 通道与显示控制 (Display & Vertical) + #region 4. 通道与显示控制 (Vertical) /// /// 全局启用或关闭某通道的显示 /// public virtual async Task 设置通道显示开关(int 通道号, bool 开启, CancellationToken ct = default) { - // 设置或查询指定通道的显示模式(开或关)[cite: 1] + // 开启或关闭指定通道的波形显示[cite: 1] string 参数 = 开启 ? "ON" : "OFF"; - await SendAsync($"DISplay:GLObal:CH{通道号}:STATE {参数}{ScpiDelimiter}", ct); + await SendAsync($"SELect:CH{通道号} {参数}{ScpiDelimiter}", ct); } /// @@ -143,8 +144,8 @@ namespace DeviceCommand.Devices /// public virtual async Task 设置通道垂直刻度(int 通道号, double 伏特每格, CancellationToken ct = default) { - // 设置或查询指定Waveform View内指定通道的垂直缩放比例(注:WaveView 通常为1)[cite: 1] - string cmd = string.Format(CultureInfo.InvariantCulture, "DISplay:WAVEView1:CH{0}:VERTical:SCAle {1:E}{2}", 通道号, 伏特每格, ScpiDelimiter); + // 设置指定通道的垂直刻度[cite: 1] + string cmd = string.Format(CultureInfo.InvariantCulture, "CH{0}:SCAle {1:E}{2}", 通道号, 伏特每格, ScpiDelimiter); await SendAsync(cmd, ct); } @@ -153,8 +154,8 @@ namespace DeviceCommand.Devices /// public virtual async Task 设置通道垂直位置(int 通道号, double 格数, CancellationToken ct = default) { - // 设置或查询指定Waveform View内指定通道的垂直位置(以格为单位)[cite: 1] - string cmd = string.Format(CultureInfo.InvariantCulture, "DISplay:WAVEView1:CH{0}:VERTical:POSition {1:F2}{2}", 通道号, 格数, ScpiDelimiter); + // 设置指定通道的通道垂直位置[cite: 1] + string cmd = string.Format(CultureInfo.InvariantCulture, "CH{0}:POSition {1:F2}{2}", 通道号, 格数, ScpiDelimiter); await SendAsync(cmd, ct); } @@ -163,37 +164,190 @@ namespace DeviceCommand.Devices #region 5. 自动测量 (Measurement) /// - /// 新增一个测量项槽位 + /// 开启指定的测量项槽位 (1-8) /// - public virtual async Task 添加测量项(CancellationToken ct = default) + public virtual async Task 开启测量项(int 测量项编号, CancellationToken ct = default) { - // 添加一个测量项[cite: 1] - await SendAsync($"MEASUrement:ADDMEAS{ScpiDelimiter}", ct); + // 指定该测量槽位是否计算并在屏幕显示[cite: 1] + await SendAsync($"MEASUrement:MEAS{测量项编号}:STATE ON{ScpiDelimiter}", ct); } public virtual async Task 设置测量类型(int 测量项编号, TekMeasurementType 类型, CancellationToken ct = default) { - // 设置或查询测量类型[cite: 1] + // 设置或查询指定测量槽位的测量类型[cite: 1] await SendAsync($"MEASUrement:MEAS{测量项编号}:TYPe {类型}{ScpiDelimiter}", ct); } public virtual async Task 设置测量源(int 测量项编号, string 源名称, CancellationToken ct = default) { - // 设置或查询局部输入源 (例如 CH1, CH2)[cite: 1] - await SendAsync($"MEASUrement:MEAS{测量项编号}:SOURCE {源名称}{ScpiDelimiter}", ct); + // 设置单通道测量的 "from" 源信号[cite: 1] + await SendAsync($"MEASUrement:MEAS{测量项编号}:SOUrce1 {源名称}{ScpiDelimiter}", ct); + } + + [Monitorable("测量项的当前值")] + public virtual async Task 查询测量项当前值(int 测量项编号, CancellationToken ct = default) + { + // 返回指定的测量项当前值[cite: 1] + return await WriteReadAsync($"MEASUrement:MEAS{测量项编号}:VALue?{ScpiDelimiter}", ScpiDelimiter, ct); } - [Monitorable("测量项当前采集的平均值")] public virtual async Task 查询测量项平均值(int 测量项编号, CancellationToken ct = default) { - // 返回当前采集的指定测量的平均值[cite: 1] - return await WriteReadAsync($"MEASUrement:MEAS{测量项编号}:RESUlts:CURRentacq:MEAN?{ScpiDelimiter}", ScpiDelimiter, ct); + // 返回自上次统计重置以来累积的平均值[cite: 1] + return await WriteReadAsync($"MEASUrement:MEAS{测量项编号}:MEAN?{ScpiDelimiter}", ScpiDelimiter, ct); } public virtual async Task 查询测量项最大值(int 测量项编号, CancellationToken ct = default) { - // 返回自上次统计重置以来指定测量的最大值(当前采集)[cite: 1] - return await WriteReadAsync($"MEASUrement:MEAS{测量项编号}:RESUlts:CURRentacq:MAXimum?{ScpiDelimiter}", ScpiDelimiter, ct); + // 返回自上次统计重置以来找到的最大值[cite: 1] + return await WriteReadAsync($"MEASUrement:MEAS{测量项编号}:MAXimum?{ScpiDelimiter}", ScpiDelimiter, ct); + } + + #endregion + + #region 6. 屏幕截图与文件系统 (Hard Copy & File System) + + /// + /// 捕获示波器屏幕并保存到上位机本地路径 + /// + /// 保存到电脑本地的文件路径 (例如: @"C:\temp\screen.png") + /// 异步取消令牌 + public virtual async Task 保存屏幕截图(string 上位机文件路径, CancellationToken ct = default) + { + // 根据之前的排查,您的设备使用 E: 作为 U 盘挂载点 + string 临时文件 = "E:/temp1.png"; + + // 1. 设置保存图像的格式为 PNG[cite: 1] + await SendAsync($"SAVE:IMAGE:FILEFormat PNG{ScpiDelimiter}", ct); + + // 2. 捕获屏幕并保存到示波器的 U 盘中[cite: 1] + await SendAsync($"SAVE:IMAGE \"{临时文件}\"{ScpiDelimiter}", ct); + + // 3. 发送 *OPC? 并等待返回 1,确保图像文件已经完全写入磁盘[cite: 1] + await WriteReadAsync($"*OPC?{ScpiDelimiter}", ScpiDelimiter, ct); + + // 4. 将文件内容读取到当前通信接口[cite: 1] + await SendAsync($"FILESystem:READFile \"{临时文件}\"{ScpiDelimiter}", ct); + + // 5. 提取底层的二进制块 (Block Data)[cite: 1] + byte[] imageBytes = await ReadBinaryBlockAsync(ct); + + // 6. 将提取的二进制字节流写入上位机本地磁盘 + await File.WriteAllBytesAsync(上位机文件路径, imageBytes, ct); + + // 7. 删除示波器上的临时文件,保持设备存储干净[cite: 1] + await SendAsync($"FILESystem:DELEte \"{临时文件}\"{ScpiDelimiter}", ct); + } + + /// + /// ⚠️ 请在这里将您的网络流对接起来! + /// 依据您 Tcp 基类的设计,返回用于读取底层字节流的 Stream。 + /// + protected virtual Stream GetNetworkStream() + { + // 【修改提示】: + // 如果您的基类暴露了 TcpClient,请取消注释并使用这一行: + // return this.TcpClient.GetStream(); + + // 如果您的基类直接叫 _stream 或 Stream,请返回它: + // return this.Stream; + + // 假如不知道怎么填,请查看 Tcp 类的源码。 + throw new NotImplementedException("请在此方法中返回基类的 NetworkStream 或 Socket 流对象。"); + } + + /// + /// 辅助方法:读取 IEEE 488.2 标准的二进制块数据 (Block Data) + /// 手册规范: ::= {#[...][...]}[cite: 1] + /// + protected virtual async Task ReadBinaryBlockAsync(CancellationToken ct = default) + { + var stream = GetNetworkStream(); + byte[] singleByte = new byte[1]; + + // 1. 逐字节读取,直到读到起始符 '#'[cite: 1] + while (true) + { + int read = await stream.ReadAsync(singleByte, 0, 1, ct); + if (read == 0) throw new EndOfStreamException("通信中断:未找到二进制块起始符 '#'"); + if (singleByte[0] == (byte)'#') break; + } + + // 2. 读取 1 个字符 ,它代表接下来有多少个字符表示数据长度[cite: 1] + await stream.ReadAsync(singleByte, 0, 1, ct); + char nzDigChar = (char)singleByte[0]; + if (!char.IsDigit(nzDigChar)) throw new InvalidDataException($"无效的块头标识: {nzDigChar}"); + int lengthBytesCount = int.Parse(nzDigChar.ToString()); + + // 3. 读取 所指示的长度字符,将其解析为整数 N[cite: 1] + byte[] lengthBytes = new byte[lengthBytesCount]; + int currentRead = 0; + while (currentRead < lengthBytesCount) + { + int read = await stream.ReadAsync(lengthBytes, currentRead, lengthBytesCount - currentRead, ct); + if (read == 0) throw new EndOfStreamException("通信中断:读取数据长度失败"); + currentRead += read; + } + int dataLength = int.Parse(Encoding.ASCII.GetString(lengthBytes)); + + // 4. 从流中精确读取 N 个字节 ...,这就是完整的图像数据[cite: 1] + byte[] imageData = new byte[dataLength]; + int totalBytesRead = 0; + while (totalBytesRead < dataLength) + { + int read = await stream.ReadAsync(imageData, totalBytesRead, dataLength - totalBytesRead, ct); + if (read == 0) throw new EndOfStreamException("通信中断:读取图像数据不完整"); + totalBytesRead += read; + } + + // 5. 结尾可能包含 (例如 LF \n),将其读取并丢弃以清空缓冲区[cite: 1] + try + { + await stream.ReadAsync(singleByte, 0, 1, ct); + } + catch { /* 忽略末尾符读取的可能超时 */ } + + return imageData; + } + + #endregion + + #region 7. 状态与错误队列 (Status and Error) + + /// + /// 清除所有的状态寄存器和错误/事件队列 + /// + public virtual async Task 清除错误队列Async(CancellationToken ct = default) + { + // 发送 *CLS 指令清空状态[cite: 1] + await SendAsync($"*CLS{ScpiDelimiter}", ct); + } + + /// + /// 查询错误队列中当前的事件/错误数量 + /// + public virtual async Task 查询错误数量Async(CancellationToken ct = default) + { + // 返回事件队列中的事件数量[cite: 1] + return await WriteReadAsync($"EVQty?{ScpiDelimiter}", ScpiDelimiter, ct); + } + + /// + /// 从错误队列中读取最早的一个事件/错误(包含代码和详细消息),并将其从队列中弹出 + /// + public virtual async Task 读取单条错误消息Async(CancellationToken ct = default) + { + // 返回事件队列中的事件代码和消息[cite: 1] + return await WriteReadAsync($"EVMsg?{ScpiDelimiter}", ScpiDelimiter, ct); + } + + /// + /// 读取并清空队列中的所有错误和事件消息 + /// + public virtual async Task 读取所有错误消息Async(CancellationToken ct = default) + { + // 返回所有事件及其消息[cite: 1] + return await WriteReadAsync($"ALLEv?{ScpiDelimiter}", ScpiDelimiter, ct); } #endregion