SDS2000X示波器设备调试
This commit is contained in:
@@ -230,7 +230,89 @@ namespace DeviceCommand.Base
|
|||||||
_commLock.Release();
|
_commLock.Release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#region 扩展:读取所有可用的二进制网络字节 (无 SCPI 块头解析)
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 无锁核心方法:发送命令并一次性读取所有回传的二进制原始数据包(不进行 # 协议头解析,专用于读取纯文件流如 PNG)
|
||||||
|
/// </summary>
|
||||||
|
private async Task<byte[]> LoglessReadAllBytesAsync(string queryCommand, CancellationToken ct)
|
||||||
|
{
|
||||||
|
if (!IsConnected) throw new InvalidOperationException("TCP未连接。");
|
||||||
|
|
||||||
|
// 1. 发送查询命令(例如 :PRINt? PNG\n)
|
||||||
|
await LoglessSendAsync(Encoding.UTF8.GetBytes(queryCommand), ct);
|
||||||
|
|
||||||
|
NetworkStream stream = _tcpClient.GetStream();
|
||||||
|
|
||||||
|
// 2. 鼎阳示波器的截图数据大概在 100KB - 800KB 左右,使用动态内存流接收整个包
|
||||||
|
using (var ms = new MemoryStream())
|
||||||
|
{
|
||||||
|
byte[] buffer = new byte[8192]; // 8KB 缓冲区
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// 先给设备短暂的反应时间,等待数据到达网络缓冲区
|
||||||
|
int delayCount = 0;
|
||||||
|
while (!_tcpClient.GetStream().DataAvailable && delayCount < 50)
|
||||||
|
{
|
||||||
|
await Task.Delay(10, ct);
|
||||||
|
delayCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 循环读取,直到网络流中没有更多数据
|
||||||
|
do
|
||||||
|
{
|
||||||
|
ct.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
int read = ReceiveTimeout > 0
|
||||||
|
? await stream.ReadAsync(buffer, 0, buffer.Length, ct)
|
||||||
|
.WaitAsync(TimeSpan.FromMilliseconds(ReceiveTimeout), ct)
|
||||||
|
.ConfigureAwait(false)
|
||||||
|
: await stream.ReadAsync(buffer, 0, buffer.Length, ct).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (read == 0) break; // 远程流关闭
|
||||||
|
|
||||||
|
ms.Write(buffer, 0, read);
|
||||||
|
|
||||||
|
// 如果流里没有剩余数据了,退出读取(防止 ReadAsync 在没有数据时无限阻塞等待)
|
||||||
|
if (!stream.DataAvailable)
|
||||||
|
{
|
||||||
|
// 极短延时再确认一次,防止分包网络延迟引起的“假结束”
|
||||||
|
await Task.Delay(30, ct);
|
||||||
|
if (!stream.DataAvailable)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (true);
|
||||||
|
|
||||||
|
return ms.ToArray();
|
||||||
|
}
|
||||||
|
catch (TimeoutException ex)
|
||||||
|
{
|
||||||
|
await ResetConnectionAsync(ct);
|
||||||
|
throw new TimeoutException($"读取二进制大包超时(等待:{ReceiveTimeout} ms),链路已重置。", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 【公开方法】发送命令并读取设备回传的全部原始二进制字节数组(不带协议头解析,直接返回整个字节缓冲区)
|
||||||
|
/// </summary>
|
||||||
|
public async Task<byte[]> ReadAllBytesAsync(string queryCommand, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await _commLock.WaitAsync(ct);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return await LoglessReadAllBytesAsync(queryCommand, ct);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_commLock.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_tcpClient?.Dispose();
|
_tcpClient?.Dispose();
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using DeviceCommand.Base;
|
|||||||
using Model.Models;
|
using Model.Models;
|
||||||
using System;
|
using System;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
@@ -11,12 +12,11 @@ namespace DeviceCommand.Device
|
|||||||
[ADPCommand]
|
[ADPCommand]
|
||||||
public class SDS2000X_HD : Tcp
|
public class SDS2000X_HD : Tcp
|
||||||
{
|
{
|
||||||
// 示波器底层 Socket 字符串命令通常以换行符 \n 结束
|
// 示波器底层 Socket 字符串命令以换行符 \n 结束
|
||||||
private const string ScpiDelimiter = "\n";
|
private const string ScpiDelimiter = "\n";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 构造函数:传入 <see cref="TcpConfig"/> 一次性初始化示波器通信参数。
|
/// 构造函数:传入 <see cref="TcpConfig"/> 一次性初始化示波器通信参数。
|
||||||
/// 鼎阳示波器网口 Socket 默认端口通常为 5025,请在配置中设置。
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public SDS2000X_HD(TcpConfig config) : base(config)
|
public SDS2000X_HD(TcpConfig config) : base(config)
|
||||||
{
|
{
|
||||||
@@ -49,8 +49,7 @@ namespace DeviceCommand.Device
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 【补充】查询先前操作是否完成。
|
/// 查询先前操作是否完成。
|
||||||
/// 在重置设备(*RST)或切换大物理量程后调用,返回 "1" 代表示波器继电器切换就绪,防止后续指令引发阻塞。
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task<bool> 检查操作完成_OPC(CancellationToken ct = default)
|
public virtual async Task<bool> 检查操作完成_OPC(CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
@@ -63,65 +62,82 @@ namespace DeviceCommand.Device
|
|||||||
#region 2. 运行与捕获控制 (Run / Stop / Single)
|
#region 2. 运行与捕获控制 (Run / Stop / Single)
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 控制示波器开始捕获波形 (等同于按下前端面板的 Run 键)
|
/// 控制示波器开始捕获波形
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task 启动捕获_RUN(CancellationToken ct = default)
|
public virtual async Task 启动捕获_RUN(CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
await SendAsync($"RUN{ScpiDelimiter}", ct);
|
await SendAsync($":TRIGger:RUN{ScpiDelimiter}", ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 停止捕获波形 (等同于按下前端面板的 Stop 键)
|
/// 停止捕获波形
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task 停止捕获_STOP(CancellationToken ct = default)
|
public virtual async Task 停止捕获_STOP(CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
await SendAsync($"STOP{ScpiDelimiter}", ct);
|
await SendAsync($"TRIGger:STOP{ScpiDelimiter}", ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 强制示波器进入单次触发捕获模式 (常用于捕捉充电瞬间的过冲浪涌波形)
|
/// 强制示波器进入单次触发捕获模式
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task 单次触发_SINGLE(CancellationToken ct = default)
|
public virtual async Task 单次触发_SINGLE(CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
await SendAsync($"SINGle{ScpiDelimiter}", ct);
|
await SendAsync($":TRIGger:MODE SINGle{ScpiDelimiter}", ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 触发一次波形采样 (当触发源设为 Manual 时使用)
|
/// 触发一次波形采样
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task 强制触发(CancellationToken ct = default)
|
public virtual async Task 强制触发(CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
await SendAsync($"*TRG{ScpiDelimiter}", ct);
|
await SendAsync($"::TRIGger:MODE FTRIG{ScpiDelimiter}", ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 【补充】设置触发模式 (AUTO, NORM, SINGLE)
|
/// 示波器触发控制模式(符合 SDS2000X-HD 规范)。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task 设置触发模式(string mode, CancellationToken ct = default)
|
public enum TriggerMode
|
||||||
{
|
{
|
||||||
string modeUpper = mode.ToUpper();
|
/// <summary>
|
||||||
if (modeUpper != "AUTO" && modeUpper != "NORM" && modeUpper != "SINGLE")
|
/// 自动触发模式 (即使无触发信号也周期性刷屏)
|
||||||
throw new ArgumentException("触发模式只能为 AUTO, NORM, 或 SINGLE");
|
/// </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
|
#endregion
|
||||||
|
|
||||||
#region 3. Channel 垂直控制子系统 (C1 ~ C4)
|
#region 3. Channel 垂直控制子系统 (C1 ~ C4)
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 开启或关闭指定的模拟通道
|
/// 开启或关闭指定的模拟通道 (符合手册 57 页规范)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task 设置通道开关(int channel, bool enable, CancellationToken ct = default)
|
public virtual async Task 设置通道开关(int channel, bool enable, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
// 修正:根据手册规范,使用 1/0 比 ON/OFF 在高低版本固件中兼容性更稳定
|
string state = enable ? "ON" : "OFF";
|
||||||
string state = enable ? "1" : "0";
|
|
||||||
await SendAsync($"C{channel}:TRAce {state}{ScpiDelimiter}", ct);
|
await SendAsync($"C{channel}:TRAce {state}{ScpiDelimiter}", ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 设置指定通道的垂直电压档位 (Volts/Div,单位: V,例如 0.05 代表 50mV/div)
|
/// 设置指定通道的垂直电压档位 (Volts/Div)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task 设置通道电压档位(int channel, double volts, CancellationToken ct = default)
|
public virtual async Task 设置通道电压档位(int channel, double volts, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
@@ -130,7 +146,7 @@ namespace DeviceCommand.Device
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 【补充验证】查询指定通道当前的电压档位 (用于 Setup-Verify 闭环验证逻辑)
|
/// 查询指定通道当前的电压档位
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task<string> 查询通道电压档位(int channel, CancellationToken ct = default)
|
public virtual async Task<string> 查询通道电压档位(int channel, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
@@ -138,22 +154,29 @@ namespace DeviceCommand.Device
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 设置指定通道的垂直偏移量 (Offset,单位: V)
|
/// 设置指定通道的垂直偏移量 (Offset)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task 设置通道垂直偏移(int channel, double offset, CancellationToken ct = default)
|
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);
|
string cmd = string.Format(CultureInfo.InvariantCulture, "C{0}:OFST {1:F4}{2}", channel, offset, ScpiDelimiter);
|
||||||
await SendAsync(cmd, ct);
|
await SendAsync(cmd, ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 设置通道的输入阻抗与耦合模式
|
/// 示波器通道输入阻抗类型。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="coupling">合法参数:A1M (交流1M), D1M (直流1M), D50 (直流50欧)</param>
|
public enum ImpedanceType
|
||||||
public virtual async Task 设置通道耦合与阻抗(int channel, string coupling, CancellationToken ct = default)
|
|
||||||
{
|
{
|
||||||
string coupUpper = coupling.ToUpper();
|
/// <summary>50Ω 阻抗</summary>
|
||||||
await SendAsync($"C{channel}:COUPling {coupUpper}{ScpiDelimiter}", ct);
|
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
|
#endregion
|
||||||
@@ -161,21 +184,19 @@ namespace DeviceCommand.Device
|
|||||||
#region 4. Timebase 水平时基子系统
|
#region 4. Timebase 水平时基子系统
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 设置示波器的水平时基档位 (Time/Div,单位: s,例如 0.001 代表 1ms/div)
|
/// 设置示波器的水平时基档位 (Time/Div)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task 设置水平时基(double scale, CancellationToken ct = default)
|
public virtual async Task 设置水平时基(double scale, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
// 修正:统一使用更精简且符合手册定义的简写形式 TDIV
|
|
||||||
string cmd = string.Format(CultureInfo.InvariantCulture, "TDIV {0:E6}{1}", scale, ScpiDelimiter);
|
string cmd = string.Format(CultureInfo.InvariantCulture, "TDIV {0:E6}{1}", scale, ScpiDelimiter);
|
||||||
await SendAsync(cmd, ct);
|
await SendAsync(cmd, ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 设置示波器的触发水平延迟位置 (Horizontal Delay,单位: s)
|
/// 设置示波器的触发水平延迟位置 (Horizontal Delay)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task 设置水平延迟(double delay, CancellationToken ct = default)
|
public virtual async Task 设置水平延迟(double delay, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
// 修正:采用手册标准简写 TRDL 效率更高
|
|
||||||
string cmd = string.Format(CultureInfo.InvariantCulture, "TRDL {0:E6}{1}", delay, ScpiDelimiter);
|
string cmd = string.Format(CultureInfo.InvariantCulture, "TRDL {0:E6}{1}", delay, ScpiDelimiter);
|
||||||
await SendAsync(cmd, ct);
|
await SendAsync(cmd, ct);
|
||||||
}
|
}
|
||||||
@@ -185,12 +206,12 @@ namespace DeviceCommand.Device
|
|||||||
#region 5. Trigger 触发子系统
|
#region 5. Trigger 触发子系统
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 设置边沿触发的电平值 (Trigger Level,单位: V)
|
/// 设置边沿触发的电平值 (Trigger Level)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task 设置触发电平(double level, CancellationToken ct = default)
|
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);
|
await SendAsync(cmd, ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -199,30 +220,24 @@ namespace DeviceCommand.Device
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task 设置触发源(string source, CancellationToken ct = default)
|
public virtual async Task 设置触发源(string source, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
// 修正:采用标准简写 TRSE 体系配置指令
|
await SendAsync($"TRSE EDGE,SR,{source.ToUpper()}{ScpiDelimiter}", ct);
|
||||||
await SendAsync($"TRIGger:SOURce {source.ToUpper()}{ScpiDelimiter}", ct);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region 6. Measure 自动测量参数回读 (高频轮询核心)
|
#region 6. Measure 自动测量参数回读
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查询指定通道自动测量项的当前实时测量数值
|
/// 查询指定通道自动测量项的当前实时测量数值
|
||||||
/// </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)
|
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);
|
string query = string.Format(CultureInfo.InvariantCulture, "C{0}:PAVA? {1}{2}", channel, paramName.ToUpper(), ScpiDelimiter);
|
||||||
return await WriteReadAsync(query, ScpiDelimiter, ct);
|
return await WriteReadAsync(query, ScpiDelimiter, ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 轮询便捷接口:查询指定通道的电压峰峰值 (Vpp)
|
/// 查询指定通道的电压峰峰值 (Vpp)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task<string> 查询实际电压峰峰值(int channel, CancellationToken ct = default)
|
public virtual async Task<string> 查询实际电压峰峰值(int channel, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
@@ -230,7 +245,7 @@ namespace DeviceCommand.Device
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 轮询便捷接口:查询指定通道的频率值 (Frequency)
|
/// 查询指定通道的频率值 (Frequency)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task<string> 查询实际频率(int channel, CancellationToken ct = default)
|
public virtual async Task<string> 查询实际频率(int channel, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
@@ -238,7 +253,7 @@ namespace DeviceCommand.Device
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 轮询便捷接口:查询指定通道的真均方根电压值 (Vrms)
|
/// 查询指定通道的真均方根电压值 (Vrms)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task<string> 查询实际电压均方根(int channel, CancellationToken ct = default)
|
public virtual async Task<string> 查询实际电压均方根(int channel, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
@@ -246,5 +261,60 @@ namespace DeviceCommand.Device
|
|||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,10 +2,14 @@ using DeviceCommand.Device;
|
|||||||
using Prism.Commands;
|
using Prism.Commands;
|
||||||
using Prism.Ioc;
|
using Prism.Ioc;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
using UIShare.GlobalVariable;
|
using UIShare.GlobalVariable;
|
||||||
using UIShare.ViewModelBase;
|
using UIShare.ViewModelBase;
|
||||||
|
using static DeviceCommand.Device.SDS2000X_HD;
|
||||||
|
|
||||||
namespace DeviceEditModule.ViewModels
|
namespace DeviceEditModule.ViewModels
|
||||||
{
|
{
|
||||||
@@ -142,24 +146,28 @@ namespace DeviceEditModule.ViewModels
|
|||||||
|
|
||||||
#region 命令
|
#region 命令
|
||||||
|
|
||||||
public ICommand QueryIdentityCommand { get; }
|
public ICommand QueryIdentityCommand { get; }
|
||||||
public ICommand ResetDeviceCommand { get; }
|
public ICommand ResetDeviceCommand { get; }
|
||||||
public ICommand RunCommand { get; }
|
public ICommand RunCommand { get; }
|
||||||
public ICommand StopCommand { get; }
|
public ICommand StopCommand { get; }
|
||||||
public ICommand SingleCommand { get; }
|
public ICommand SingleCommand { get; }
|
||||||
public ICommand ForceTriggerCommand { get; }
|
public ICommand ForceTriggerCommand { get; }
|
||||||
public ICommand SetChannelOnCommand { get; }
|
public ICommand SetChannelOnCommand { get; }
|
||||||
public ICommand SetChannelOffCommand { get; }
|
public ICommand SetChannelOffCommand { get; }
|
||||||
public ICommand SetVoltsDivCommand { get; }
|
public ICommand SetVoltsDivCommand { get; }
|
||||||
public ICommand SetOffsetCommand { get; }
|
public ICommand SetOffsetCommand { get; }
|
||||||
|
|
||||||
public ICommand SetTimeBaseCommand { get; }
|
// 🛠️ 补全:设置阻抗的 Command
|
||||||
public ICommand SetTriggerLevelCommand { get; }
|
public ICommand SetImpedance50Command { get; }
|
||||||
|
public ICommand SetImpedance1MCommand { get; }
|
||||||
|
|
||||||
|
public ICommand SetTimeBaseCommand { get; }
|
||||||
|
public ICommand SetTriggerLevelCommand { get; }
|
||||||
public ICommand SetTriggerSourceCommand { get; }
|
public ICommand SetTriggerSourceCommand { get; }
|
||||||
public ICommand QueryMeasurementsCommand{ get; }
|
public ICommand QueryMeasurementsCommand { get; }
|
||||||
public ICommand QueryVppCommand { get; }
|
public ICommand QueryVppCommand { get; }
|
||||||
public ICommand QueryFrequencyCommand { get; }
|
public ICommand QueryFrequencyCommand { get; }
|
||||||
public ICommand QueryRmsCommand { get; }
|
public ICommand QueryRmsCommand { get; }
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
@@ -167,32 +175,73 @@ namespace DeviceEditModule.ViewModels
|
|||||||
{
|
{
|
||||||
_deviceManager = containerProvider.Resolve<DeviceManager>();
|
_deviceManager = containerProvider.Resolve<DeviceManager>();
|
||||||
|
|
||||||
QueryIdentityCommand = new DelegateCommand(async () => await Exec(async () => AppendLog("IDN: " + await _device!.查询设备标识(Ct()))));
|
QueryIdentityCommand = new DelegateCommand(async () => await Exec(async () => AppendLog("IDN: " + await _device!.查询设备标识(Ct()))));
|
||||||
ResetDeviceCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.重置设备(Ct()); AppendLog("设备已重置"); }));
|
ResetDeviceCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.重置设备(Ct()); AppendLog("设备已重置"); }));
|
||||||
RunCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.启动捕获_RUN(Ct()); AppendLog("已开始捕获"); }));
|
RunCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.启动捕获_RUN(Ct()); AppendLog("已开始捕获"); }));
|
||||||
StopCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.停止捕获_STOP(Ct()); AppendLog("已停止捕获"); }));
|
StopCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.停止捕获_STOP(Ct()); AppendLog("已停止捕获"); }));
|
||||||
SingleCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.单次触发_SINGLE(Ct()); AppendLog("已触发单次捕获"); }));
|
SingleCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.单次触发_SINGLE(Ct()); AppendLog("已触发单次捕获"); }));
|
||||||
ForceTriggerCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.强制触发(Ct()); AppendLog("已强制触发"); }));
|
ForceTriggerCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.强制触发(Ct()); AppendLog("已强制触发"); }));
|
||||||
SetChannelOnCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置通道开关(Channel, true, Ct()); AppendLog($"通道 {Channel} 已开启"); }));
|
SetChannelOnCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置通道开关(Channel, true, Ct()); AppendLog($"通道 {Channel} 已开启"); }));
|
||||||
SetChannelOffCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置通道开关(Channel, false, Ct()); AppendLog($"通道 {Channel} 已关闭"); }));
|
SetChannelOffCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置通道开关(Channel, false, Ct()); AppendLog($"通道 {Channel} 已关闭"); }));
|
||||||
SetVoltsDivCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置通道电压档位(Channel, VoltsPerDiv, Ct()); AppendLog($"C{Channel} 电压档位已设为 {VoltsPerDiv} V/div"); }));
|
SetVoltsDivCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置通道电压档位(Channel, VoltsPerDiv, Ct()); AppendLog($"C{Channel} 电压档位已设为 {VoltsPerDiv} V/div"); }));
|
||||||
SetOffsetCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置通道垂直偏移(Channel, Offset, Ct()); AppendLog($"C{Channel} 垂直偏移已设为 {Offset} V"); }));
|
SetOffsetCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置通道垂直偏移(Channel, Offset, Ct()); AppendLog($"C{Channel} 垂直偏移已设为 {Offset} V"); }));
|
||||||
|
|
||||||
SetTimeBaseCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置水平时基(TimeBase, Ct()); AppendLog($"水平时基已设为 {TimeBase} s/div"); }));
|
// 🛠️ 绑定:设置 50Ω 和 1MΩ 阻抗控制
|
||||||
SetTriggerLevelCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置触发电平(TriggerLevel, Ct()); AppendLog($"触发电平已设为 {TriggerLevel} V"); }));
|
SetImpedance50Command = new DelegateCommand(async () => await Exec(async () =>
|
||||||
|
{
|
||||||
|
await _device!.设置通道阻抗(Channel, ImpedanceType.FIFty, Ct());
|
||||||
|
Is50Ohm = true;
|
||||||
|
AppendLog($"C{Channel} 输入阻抗已设为 50Ω");
|
||||||
|
}));
|
||||||
|
|
||||||
|
SetImpedance1MCommand = new DelegateCommand(async () => await Exec(async () =>
|
||||||
|
{
|
||||||
|
await _device!.设置通道阻抗(Channel, ImpedanceType.ONEM, Ct());
|
||||||
|
Is50Ohm = false;
|
||||||
|
AppendLog($"C{Channel} 输入阻抗已设为 1MΩ");
|
||||||
|
}));
|
||||||
|
|
||||||
|
SetTimeBaseCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置水平时基(TimeBase, Ct()); AppendLog($"水平时基已设为 {TimeBase} s/div"); }));
|
||||||
|
SetTriggerLevelCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置触发电平(TriggerLevel, Ct()); AppendLog($"触发电平已设为 {TriggerLevel} V"); }));
|
||||||
SetTriggerSourceCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置触发源(TriggerSource, Ct()); AppendLog($"触发源已设为 {TriggerSource}"); }));
|
SetTriggerSourceCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置触发源(TriggerSource, Ct()); AppendLog($"触发源已设为 {TriggerSource}"); }));
|
||||||
|
|
||||||
QueryMeasurementsCommand = new DelegateCommand(async () => await Exec(async () =>
|
QueryMeasurementsCommand = new DelegateCommand(async () => await Exec(async () =>
|
||||||
{
|
{
|
||||||
MeasuredVpp = await _device!.查询实际电压峰峰值(Channel, Ct());
|
// 1. 保留设备原生吐出的完整原始字符串
|
||||||
MeasuredFrequency = await _device!.查询实际频率(Channel, Ct());
|
string rawVpp = await _device!.查询实际电压峰峰值(Channel, Ct());
|
||||||
MeasuredRms = await _device!.查询实际电压均方根(Channel, Ct());
|
string rawFreq = await _device!.查询实际频率(Channel, Ct());
|
||||||
AppendLog($"C{Channel} 测量 → Vpp:{MeasuredVpp} Freq:{MeasuredFrequency}Hz RMS:{MeasuredRms}V");
|
string rawRms = await _device!.查询实际电压均方根(Channel, Ct());
|
||||||
|
|
||||||
|
// 2. 清洗数据并将科学计数法转为常规小数后赋值给 UI 属性
|
||||||
|
MeasuredVpp = ParseMeasurement(rawVpp);
|
||||||
|
MeasuredFrequency = ParseMeasurement(rawFreq);
|
||||||
|
MeasuredRms = ParseMeasurement(rawRms);
|
||||||
|
|
||||||
|
// 3. 在日志中同时体现设备原始报文与解析呈现值,极方便联调
|
||||||
|
AppendLog($"C{Channel} 测量原始数据 → Vpp:{rawVpp.Trim()} Freq:{rawFreq.Trim()} RMS:{rawRms.Trim()}");
|
||||||
|
AppendLog($"C{Channel} 界面呈现数值 → Vpp:{MeasuredVpp}V Freq:{MeasuredFrequency}Hz RMS:{MeasuredRms}V");
|
||||||
}));
|
}));
|
||||||
|
|
||||||
QueryVppCommand = new DelegateCommand(async () => await Exec(async () => { MeasuredVpp = await _device!.查询实际电压峰峰值(Channel, Ct()); AppendLog($"C{Channel} Vpp: {MeasuredVpp}"); }));
|
QueryVppCommand = new DelegateCommand(async () => await Exec(async () =>
|
||||||
QueryFrequencyCommand = new DelegateCommand(async () => await Exec(async () => { MeasuredFrequency = await _device!.查询实际频率(Channel, Ct()); AppendLog($"C{Channel} Freq: {MeasuredFrequency}"); }));
|
{
|
||||||
QueryRmsCommand = new DelegateCommand(async () => await Exec(async () => { MeasuredRms = await _device!.查询实际电压均方根(Channel, Ct()); AppendLog($"C{Channel} RMS: {MeasuredRms}"); }));
|
string raw = await _device!.查询实际电压峰峰值(Channel, Ct());
|
||||||
|
MeasuredVpp = ParseMeasurement(raw);
|
||||||
|
AppendLog($"C{Channel} Vpp: {MeasuredVpp} (Raw: {raw.Trim()})");
|
||||||
|
}));
|
||||||
|
|
||||||
|
QueryFrequencyCommand = new DelegateCommand(async () => await Exec(async () =>
|
||||||
|
{
|
||||||
|
string raw = await _device!.查询实际频率(Channel, Ct());
|
||||||
|
MeasuredFrequency = ParseMeasurement(raw);
|
||||||
|
AppendLog($"C{Channel} Freq: {MeasuredFrequency} (Raw: {raw.Trim()})");
|
||||||
|
}));
|
||||||
|
|
||||||
|
QueryRmsCommand = new DelegateCommand(async () => await Exec(async () =>
|
||||||
|
{
|
||||||
|
string raw = await _device!.查询实际电压均方根(Channel, Ct());
|
||||||
|
MeasuredRms = ParseMeasurement(raw);
|
||||||
|
AppendLog($"C{Channel} RMS: {MeasuredRms} (Raw: {raw.Trim()})");
|
||||||
|
}));
|
||||||
|
|
||||||
Initialize();
|
Initialize();
|
||||||
}
|
}
|
||||||
@@ -224,8 +273,8 @@ namespace DeviceEditModule.ViewModels
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_device = found;
|
_device = found;
|
||||||
DeviceName = foundName ?? "SDS2000X_HD (未找到)";
|
DeviceName = foundName ?? "SDS2000X_HD (未找到)";
|
||||||
IsConnected = _device?.IsConnected ?? false;
|
IsConnected = _device?.IsConnected ?? false;
|
||||||
|
|
||||||
AppendLog(found != null
|
AppendLog(found != null
|
||||||
@@ -281,6 +330,52 @@ namespace DeviceEditModule.ViewModels
|
|||||||
: line + "\n" + ResponseLog;
|
: line + "\n" + ResponseLog;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 清洗示波器原始返回的测量字符串(例如 "C1:PAVA RMS,5.57E-03V")并安全转化为无科学计数法的小数形式。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="rawResponse">设备原始应答数据</param>
|
||||||
|
/// <returns>可直接绑定到 UI 呈现的字符串数字</returns>
|
||||||
|
private string ParseMeasurement(string rawResponse)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(rawResponse)) return "—";
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string cleanData = rawResponse.Trim();
|
||||||
|
|
||||||
|
// 1. 斩断报头,提取逗号后面的具体内容(如 "5.57E-03V" 或 "****")
|
||||||
|
int commaIndex = cleanData.IndexOf(',');
|
||||||
|
if (commaIndex == -1) return "—";
|
||||||
|
|
||||||
|
string valStr = cleanData.Substring(commaIndex + 1);
|
||||||
|
|
||||||
|
var match = Regex.Match(valStr, @"[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?");
|
||||||
|
if (match.Success)
|
||||||
|
{
|
||||||
|
valStr = match.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 校验并拦截设备未测出时的无效星号 "****"
|
||||||
|
if (valStr.Contains("*") || string.IsNullOrWhiteSpace(valStr))
|
||||||
|
{
|
||||||
|
return "0"; // 回归为零或 "—",防止触发数据异常
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 解析科学计数法,并重新以不带科学计数法的小数样式展开
|
||||||
|
if (double.TryParse(valStr, NumberStyles.Any, CultureInfo.InvariantCulture, out double result))
|
||||||
|
{
|
||||||
|
// "0.######" 样式会自动消除末尾无用的冗余零,并显示为普通小数(如 0.00557)
|
||||||
|
return result.ToString("0.######", CultureInfo.InvariantCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// 捕获可能产生的边缘转换故障,保证轮询线程绝不崩溃
|
||||||
|
}
|
||||||
|
|
||||||
|
return "—";
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
@@ -289,4 +384,4 @@ namespace DeviceEditModule.ViewModels
|
|||||||
_cts?.Dispose();
|
_cts?.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||||
xmlns:converters="clr-namespace:UIShare.Converters;assembly=UIShare"
|
xmlns:converters="clr-namespace:UIShare.Converters;assembly=UIShare"
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
prism:ViewModelLocator.AutoWireViewModel="True"
|
prism:ViewModelLocator.AutoWireViewModel="False"
|
||||||
d:DesignHeight="760" d:DesignWidth="860">
|
d:DesignHeight="760" d:DesignWidth="860">
|
||||||
|
|
||||||
<UserControl.Resources>
|
<UserControl.Resources>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||||
xmlns:converters="clr-namespace:UIShare.Converters;assembly=UIShare"
|
xmlns:converters="clr-namespace:UIShare.Converters;assembly=UIShare"
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
prism:ViewModelLocator.AutoWireViewModel="True"
|
prism:ViewModelLocator.AutoWireViewModel="False"
|
||||||
d:DesignHeight="760" d:DesignWidth="860">
|
d:DesignHeight="760" d:DesignWidth="860">
|
||||||
|
|
||||||
<UserControl.Resources>
|
<UserControl.Resources>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||||
xmlns:converters="clr-namespace:UIShare.Converters;assembly=UIShare"
|
xmlns:converters="clr-namespace:UIShare.Converters;assembly=UIShare"
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
prism:ViewModelLocator.AutoWireViewModel="True"
|
prism:ViewModelLocator.AutoWireViewModel="False"
|
||||||
d:DesignHeight="760" d:DesignWidth="860">
|
d:DesignHeight="760" d:DesignWidth="860">
|
||||||
|
|
||||||
<UserControl.Resources>
|
<UserControl.Resources>
|
||||||
|
|||||||
@@ -4,10 +4,11 @@
|
|||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:prism="http://prismlibrary.com/"
|
xmlns:prism="http://prismlibrary.com/"
|
||||||
|
xmlns:sys="clr-namespace:System;assembly=mscorlib"
|
||||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||||
xmlns:converters="clr-namespace:UIShare.Converters;assembly=UIShare"
|
xmlns:converters="clr-namespace:UIShare.Converters;assembly=UIShare"
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
prism:ViewModelLocator.AutoWireViewModel="True"
|
prism:ViewModelLocator.AutoWireViewModel="False"
|
||||||
d:DesignHeight="760" d:DesignWidth="860">
|
d:DesignHeight="760" d:DesignWidth="860">
|
||||||
|
|
||||||
<UserControl.Resources>
|
<UserControl.Resources>
|
||||||
@@ -92,10 +93,10 @@
|
|||||||
<ComboBox Width="80" Height="32" Margin="4,0"
|
<ComboBox Width="80" Height="32" Margin="4,0"
|
||||||
SelectedItem="{Binding Channel}"
|
SelectedItem="{Binding Channel}"
|
||||||
VerticalContentAlignment="Center" FontSize="12">
|
VerticalContentAlignment="Center" FontSize="12">
|
||||||
<ComboBoxItem Content="1"/>
|
<sys:Int32>1</sys:Int32>
|
||||||
<ComboBoxItem Content="2"/>
|
<sys:Int32>2</sys:Int32>
|
||||||
<ComboBoxItem Content="3"/>
|
<sys:Int32>3</sys:Int32>
|
||||||
<ComboBoxItem Content="4"/>
|
<sys:Int32>4</sys:Int32>
|
||||||
</ComboBox>
|
</ComboBox>
|
||||||
<Button Content="开启" Command="{Binding SetChannelOnCommand}"
|
<Button Content="开启" Command="{Binding SetChannelOnCommand}"
|
||||||
Style="{StaticResource CmdBtn}"/>
|
Style="{StaticResource CmdBtn}"/>
|
||||||
@@ -137,7 +138,7 @@
|
|||||||
<StackPanel Orientation="Horizontal" Margin="0,4">
|
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||||
<TextBlock Text="V/div (V)" Style="{StaticResource ParamLabel}"/>
|
<TextBlock Text="V/div (V)" Style="{StaticResource ParamLabel}"/>
|
||||||
<TextBox Style="{StaticResource NumInput}"
|
<TextBox Style="{StaticResource NumInput}"
|
||||||
Text="{Binding VoltsPerDiv, UpdateSourceTrigger=PropertyChanged}"/>
|
Text="{Binding VoltsPerDiv}"/>
|
||||||
<Button Content="设置" Command="{Binding SetVoltsDivCommand}"
|
<Button Content="设置" Command="{Binding SetVoltsDivCommand}"
|
||||||
Style="{StaticResource CmdBtn}"/>
|
Style="{StaticResource CmdBtn}"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
@@ -165,7 +166,7 @@
|
|||||||
<StackPanel Orientation="Horizontal" Margin="0,4">
|
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||||
<TextBlock Text="时基 (s/div)" Style="{StaticResource ParamLabel}"/>
|
<TextBlock Text="时基 (s/div)" Style="{StaticResource ParamLabel}"/>
|
||||||
<TextBox Style="{StaticResource NumInput}"
|
<TextBox Style="{StaticResource NumInput}"
|
||||||
Text="{Binding TimeBase, UpdateSourceTrigger=PropertyChanged}"/>
|
Text="{Binding TimeBase}"/>
|
||||||
<Button Content="设置" Command="{Binding SetTimeBaseCommand}"
|
<Button Content="设置" Command="{Binding SetTimeBaseCommand}"
|
||||||
Style="{StaticResource CmdBtn}"/>
|
Style="{StaticResource CmdBtn}"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
xmlns:sys="clr-namespace:System;assembly=mscorlib"
|
xmlns:sys="clr-namespace:System;assembly=mscorlib"
|
||||||
xmlns:converters="clr-namespace:UIShare.Converters;assembly=UIShare"
|
xmlns:converters="clr-namespace:UIShare.Converters;assembly=UIShare"
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
prism:ViewModelLocator.AutoWireViewModel="True"
|
prism:ViewModelLocator.AutoWireViewModel="False"
|
||||||
d:DesignHeight="760" d:DesignWidth="860">
|
d:DesignHeight="760" d:DesignWidth="860">
|
||||||
|
|
||||||
<UserControl.Resources>
|
<UserControl.Resources>
|
||||||
|
|||||||
@@ -155,19 +155,19 @@ namespace ZLGUSBCANFD
|
|||||||
Thread.Sleep(50); // 确保轮询线程安全退出
|
Thread.Sleep(50); // 确保轮询线程安全退出
|
||||||
_receiveThreads.Clear();
|
_receiveThreads.Clear();
|
||||||
|
|
||||||
// 动态复位所有通道并释放DBC
|
//动态复位所有通道并释放DBC
|
||||||
//for (uint i = 0; i < _maxChannels; i++)
|
for (uint i = 0; i < _maxChannels; i++)
|
||||||
//{
|
{
|
||||||
// lock (_channelLocks[i])
|
lock (_channelLocks[i])
|
||||||
// {
|
{
|
||||||
// if (_channelHandles[i] != IntPtr.Zero)
|
if (_channelHandles[i] != IntPtr.Zero)
|
||||||
// {
|
{
|
||||||
// ZLGCAN.ZCAN_ResetCAN(_channelHandles[i]);
|
ZLGCAN.ZCAN_ResetCAN(_channelHandles[i]);
|
||||||
// _channelHandles[i] = IntPtr.Zero;
|
_channelHandles[i] = IntPtr.Zero;
|
||||||
// }
|
}
|
||||||
// 释放通道DBC(i);
|
释放通道DBC(i);
|
||||||
// }
|
}
|
||||||
//}
|
}
|
||||||
|
|
||||||
// 关闭设备主句柄
|
// 关闭设备主句柄
|
||||||
if (_deviceHandle != IntPtr.Zero)
|
if (_deviceHandle != IntPtr.Zero)
|
||||||
|
|||||||
Reference in New Issue
Block a user