SDS2000X示波器设备调试

This commit is contained in:
hsc
2026-07-16 14:55:14 +08:00
parent 0ed080f63c
commit 2dfc819bcc
9 changed files with 362 additions and 114 deletions

View File

@@ -3,6 +3,7 @@ using DeviceCommand.Base;
using Model.Models;
using System;
using System.Globalization;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
@@ -11,12 +12,11 @@ namespace DeviceCommand.Device
[ADPCommand]
public class SDS2000X_HD : Tcp
{
// 示波器底层 Socket 字符串命令通常以换行符 \n 结束
// 示波器底层 Socket 字符串命令以换行符 \n 结束
private const string ScpiDelimiter = "\n";
/// <summary>
/// 构造函数:传入 <see cref="TcpConfig"/> 一次性初始化示波器通信参数。
/// 鼎阳示波器网口 Socket 默认端口通常为 5025请在配置中设置。
/// </summary>
public SDS2000X_HD(TcpConfig config) : base(config)
{
@@ -49,8 +49,7 @@ namespace DeviceCommand.Device
}
/// <summary>
/// 【补充】查询先前操作是否完成。
/// 在重置设备(*RST)或切换大物理量程后调用,返回 "1" 代表示波器继电器切换就绪,防止后续指令引发阻塞。
/// 查询先前操作是否完成。
/// </summary>
public virtual async Task<bool> _OPC(CancellationToken ct = default)
{
@@ -63,65 +62,82 @@ namespace DeviceCommand.Device
#region 2. (Run / Stop / Single)
/// <summary>
/// 控制示波器开始捕获波形 (等同于按下前端面板的 Run 键)
/// 控制示波器开始捕获波形
/// </summary>
public virtual async Task _RUN(CancellationToken ct = default)
{
await SendAsync($"RUN{ScpiDelimiter}", ct);
await SendAsync($":TRIGger:RUN{ScpiDelimiter}", ct);
}
/// <summary>
/// 停止捕获波形 (等同于按下前端面板的 Stop 键)
/// 停止捕获波形
/// </summary>
public virtual async Task _STOP(CancellationToken ct = default)
{
await SendAsync($"STOP{ScpiDelimiter}", ct);
await SendAsync($"TRIGger:STOP{ScpiDelimiter}", ct);
}
/// <summary>
/// 强制示波器进入单次触发捕获模式 (常用于捕捉充电瞬间的过冲浪涌波形)
/// 强制示波器进入单次触发捕获模式
/// </summary>
public virtual async Task _SINGLE(CancellationToken ct = default)
{
await SendAsync($"SINGle{ScpiDelimiter}", ct);
await SendAsync($":TRIGger:MODE SINGle{ScpiDelimiter}", ct);
}
/// <summary>
/// 触发一次波形采样 (当触发源设为 Manual 时使用)
/// 触发一次波形采样
/// </summary>
public virtual async Task (CancellationToken ct = default)
{
await SendAsync($"*TRG{ScpiDelimiter}", ct);
await SendAsync($"::TRIGger:MODE FTRIG{ScpiDelimiter}", ct);
}
/// <summary>
/// 【补充】设置触发模式 (AUTO, NORM, SINGLE)
/// 示波器触发控制模式(符合 SDS2000X-HD 规范)。
/// </summary>
public virtual async Task (string mode, CancellationToken ct = default)
public enum TriggerMode
{
string modeUpper = mode.ToUpper();
if (modeUpper != "AUTO" && modeUpper != "NORM" && modeUpper != "SINGLE")
throw new ArgumentException("触发模式只能为 AUTO, NORM, 或 SINGLE");
/// <summary>
/// 自动触发模式 (即使无触发信号也周期性刷屏)
/// </summary>
AUTO,
await SendAsync($"TRMD {modeUpper}{ScpiDelimiter}", ct);
/// <summary>
/// 普通触发模式 (仅当满足触发条件时才刷新)
/// </summary>
NORM,
/// <summary>
/// 单次触发模式 (捕捉到一次满足条件的信号后立刻 STOP)
/// </summary>
SINGLE
}
/// <summary>
/// 设置触发模式 (AUTO 自动, NORM 普通, SINGLE 单次)
/// </summary>
/// <param name="mode">触发模式枚举</param>
/// <param name="ct">取消令牌</param>
public virtual async Task (TriggerMode mode, CancellationToken ct = default)
{
// 例如发送TRMD AUTO\n、TRMD NORM\n 或 TRMD SINGLE\n
string cmd = $"TRMD {mode}{ScpiDelimiter}";
await SendAsync(cmd, ct);
}
#endregion
#region 3. Channel (C1 ~ C4)
/// <summary>
/// 开启或关闭指定的模拟通道
/// 开启或关闭指定的模拟通道 (符合手册 57 页规范)
/// </summary>
public virtual async Task (int channel, bool enable, CancellationToken ct = default)
{
// 修正:根据手册规范,使用 1/0 比 ON/OFF 在高低版本固件中兼容性更稳定
string state = enable ? "1" : "0";
string state = enable ? "ON" : "OFF";
await SendAsync($"C{channel}:TRAce {state}{ScpiDelimiter}", ct);
}
/// <summary>
/// 设置指定通道的垂直电压档位 (Volts/Div,单位: V例如 0.05 代表 50mV/div)
/// 设置指定通道的垂直电压档位 (Volts/Div)
/// </summary>
public virtual async Task (int channel, double volts, CancellationToken ct = default)
{
@@ -130,7 +146,7 @@ namespace DeviceCommand.Device
}
/// <summary>
/// 【补充验证】查询指定通道当前的电压档位 (用于 Setup-Verify 闭环验证逻辑)
/// 查询指定通道当前的电压档位
/// </summary>
public virtual async Task<string> (int channel, CancellationToken ct = default)
{
@@ -138,22 +154,29 @@ namespace DeviceCommand.Device
}
/// <summary>
/// 设置指定通道的垂直偏移量 (Offset,单位: V)
/// 设置指定通道的垂直偏移量 (Offset)
/// </summary>
public virtual async Task (int channel, double offset, CancellationToken ct = default)
{
string cmd = string.Format(CultureInfo.InvariantCulture, "C{0}:OFST {1:F4}{2}", channel, offset, ScpiDelimiter);
await SendAsync(cmd, ct);
}
/// <summary>
/// 设置通道输入阻抗与耦合模式
/// 示波器通道输入阻抗类型。
/// </summary>
/// <param name="coupling">合法参数A1M (交流1M), D1M (直流1M), D50 (直流50欧)</param>
public virtual async Task (int channel, string coupling, CancellationToken ct = default)
public enum ImpedanceType
{
string coupUpper = coupling.ToUpper();
await SendAsync($"C{channel}:COUPling {coupUpper}{ScpiDelimiter}", ct);
/// <summary>50Ω 阻抗</summary>
FIFty,
/// <summary>1MΩ 阻抗</summary>
ONEM
}
public virtual async Task (int channel, ImpedanceType impedance, CancellationToken ct = default)
{
// 例如发送C1:IMPedance FIFty\n 或 C1:IMPedance ONEM\n
string cmd = $"CHANnel{channel}:IMPedance {impedance}{ScpiDelimiter}";
await SendAsync(cmd, ct);
}
#endregion
@@ -161,21 +184,19 @@ namespace DeviceCommand.Device
#region 4. Timebase
/// <summary>
/// 设置示波器的水平时基档位 (Time/Div,单位: s例如 0.001 代表 1ms/div)
/// 设置示波器的水平时基档位 (Time/Div)
/// </summary>
public virtual async Task (double scale, CancellationToken ct = default)
{
// 修正:统一使用更精简且符合手册定义的简写形式 TDIV
string cmd = string.Format(CultureInfo.InvariantCulture, "TDIV {0:E6}{1}", scale, ScpiDelimiter);
await SendAsync(cmd, ct);
}
/// <summary>
/// 设置示波器的触发水平延迟位置 (Horizontal Delay,单位: s)
/// 设置示波器的触发水平延迟位置 (Horizontal Delay)
/// </summary>
public virtual async Task (double delay, CancellationToken ct = default)
{
// 修正:采用手册标准简写 TRDL 效率更高
string cmd = string.Format(CultureInfo.InvariantCulture, "TRDL {0:E6}{1}", delay, ScpiDelimiter);
await SendAsync(cmd, ct);
}
@@ -185,12 +206,12 @@ namespace DeviceCommand.Device
#region 5. Trigger
/// <summary>
/// 设置边沿触发的电平值 (Trigger Level,单位: V)
/// 设置边沿触发的电平值 (Trigger Level)
/// </summary>
public virtual async Task (double level, CancellationToken ct = default)
{
// 修正:采用标准简写 TRLV
string cmd = string.Format(CultureInfo.InvariantCulture, "TRLV {0:F3}{1}", level, ScpiDelimiter);
string cmd = string.Format(CultureInfo.InvariantCulture, "TRIGger:EDGE:LEVel {0:F3}{1}", level, ScpiDelimiter);
await SendAsync(cmd, ct);
}
@@ -199,30 +220,24 @@ namespace DeviceCommand.Device
/// </summary>
public virtual async Task (string source, CancellationToken ct = default)
{
// 修正:采用标准简写 TRSE 体系配置指令
await SendAsync($"TRIGger:SOURce {source.ToUpper()}{ScpiDelimiter}", ct);
await SendAsync($"TRSE EDGE,SR,{source.ToUpper()}{ScpiDelimiter}", ct);
}
#endregion
#region 6. Measure ()
#region 6. Measure
/// <summary>
/// 查询指定通道自动测量项的当前实时测量数值
/// </summary>
/// <param name="channel">通道号 (1-4)</param>
/// <param name="paramName">参数名称助记符:
/// PKPK(峰峰值), MAX(最大值), MIN(最小值), AMPL(振幅值),
/// FREQ(频率), PER(周期), MEAN(平均值), RMS(均方根) 等</param>
public virtual async Task<string> (int channel, string paramName, CancellationToken ct = default)
{
// 优化:采用更兼容的测量读取指令语法格式
string query = string.Format(CultureInfo.InvariantCulture, "C{0}:PAVA? {1}{2}", channel, paramName.ToUpper(), ScpiDelimiter);
return await WriteReadAsync(query, ScpiDelimiter, ct);
}
/// <summary>
/// 轮询便捷接口:查询指定通道的电压峰峰值 (Vpp)
/// 查询指定通道的电压峰峰值 (Vpp)
/// </summary>
public virtual async Task<string> (int channel, CancellationToken ct = default)
{
@@ -230,7 +245,7 @@ namespace DeviceCommand.Device
}
/// <summary>
/// 轮询便捷接口:查询指定通道的频率值 (Frequency)
/// 查询指定通道的频率值 (Frequency)
/// </summary>
public virtual async Task<string> (int channel, CancellationToken ct = default)
{
@@ -238,7 +253,7 @@ namespace DeviceCommand.Device
}
/// <summary>
/// 轮询便捷接口:查询指定通道的真均方根电压值 (Vrms)
/// 查询指定通道的真均方根电压值 (Vrms)
/// </summary>
public virtual async Task<string> (int channel, CancellationToken ct = default)
{
@@ -246,5 +261,60 @@ namespace DeviceCommand.Device
}
#endregion
#region 7.
/// 【一键获取并保存截图】
/// 自动下发 :PRINt? PNG 指令,接收示波器返回的纯 PNG 二进制流并直接保存到指定路径。
/// </summary>
/// <param name="saveFilePath">本地绝对保存路径 (例如: @"D:\Oscilloscope\Screen_01.png")</param>
/// <param name="ct">取消令牌</param>
/// <returns>返回是否获取并保存成功</returns>
public virtual async Task<bool> (string saveFilePath, CancellationToken ct = default)
{
try
{
// 1. 调用我们在基类 Tcp 中全新实现的 ReadRawAsync
// 它会在内部下发 ":PRINt? PNG\n",自动接收完整的网络字节流
byte[] pureImageBytes = await ReadAllBytesAsync($":PRINt? PNG{ScpiDelimiter}", ct);
// 2. 校验返回数据
if (pureImageBytes == null || pureImageBytes.Length < 8)
{
System.Diagnostics.Debug.WriteLine("错误:接收到的图片数据为空或长度不足。");
return false;
}
// 3. 核心校验:验证是否为标准的 PNG 文件格式 (PNG 头通常为 89 50 4E 47)
if (pureImageBytes[0] != 0x89 || pureImageBytes[1] != 'P' || pureImageBytes[2] != 'N' || pureImageBytes[3] != 'G')
{
System.Diagnostics.Debug.WriteLine("错误:接收到的二进制数据不符合标准 PNG 格式头!");
return false;
}
// 4. 如果传入的目标文件夹路径不存在,自动帮其创建
string directory = Path.GetDirectoryName(saveFilePath);
if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
// 5. 使用文件流直接将 PNG 字节数据落地写入本地硬盘
using (FileStream fs = new FileStream(saveFilePath, FileMode.Create, FileAccess.Write))
{
await fs.WriteAsync(pureImageBytes, 0, pureImageBytes.Length, ct);
await fs.FlushAsync(ct); // 强制刷新,确保数据完整落地
}
System.Diagnostics.Debug.WriteLine($"成功:截图已保存至路径: {saveFilePath}");
return true;
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"崩溃:保存截图时发生未知异常: {ex.Message}");
return false;
}
}
#endregion
}
}