From cd52ca64af9785a4537467195d7d5d66aaf2b193 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E2=80=9Chsc=E2=80=9D?= <“huangsucan@kaiyili-lab.com”>
Date: Tue, 13 Oct 2026 14:36:14 +0800
Subject: [PATCH] =?UTF-8?q?it67200=E6=94=B9visa?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
ACP/App.xaml.cs | 31 ++--
DeviceCommand/Devices/IT6720.cs | 257 ++++++++------------------------
2 files changed, 77 insertions(+), 211 deletions(-)
diff --git a/ACP/App.xaml.cs b/ACP/App.xaml.cs
index 876517c..ef1487e 100644
--- a/ACP/App.xaml.cs
+++ b/ACP/App.xaml.cs
@@ -134,21 +134,22 @@ namespace ACP
}
protected override void OnStartup(StartupEventArgs e)
{
- if (System.Diagnostics.Debugger.IsAttached)
- {
- base.OnStartup(e);
- return;
- }
- string myMachineCode = MachineCodeHelper.GetDeviceMachineCode();
- if (MachineCodeHelper.VerifyMachineCode(myMachineCode))
- {
- base.OnStartup(e);
- }
- else
- {
- MessageBox.Show("机器码不匹配!");
- return;
- }
+ base.OnStartup(e);
+ //if (System.Diagnostics.Debugger.IsAttached)
+ //{
+ // base.OnStartup(e);
+ // return;
+ //}
+ //string myMachineCode = MachineCodeHelper.GetDeviceMachineCode();
+ //if (MachineCodeHelper.VerifyMachineCode(myMachineCode))
+ //{
+ // base.OnStartup(e);
+ //}
+ //else
+ //{
+ // MessageBox.Show("机器码不匹配!");
+ // return;
+ //}
}
protected override void RegisterRequiredTypes(IContainerRegistry containerRegistry)
diff --git a/DeviceCommand/Devices/IT6720.cs b/DeviceCommand/Devices/IT6720.cs
index fb637ed..19d09a3 100644
--- a/DeviceCommand/Devices/IT6720.cs
+++ b/DeviceCommand/Devices/IT6720.cs
@@ -2,7 +2,6 @@
using DeviceCommand.Base;
using Model.Models;
using System;
-using System.Collections.Generic;
using System.Globalization;
using System.Threading;
using System.Threading.Tasks;
@@ -10,237 +9,120 @@ using System.Threading.Tasks;
namespace DeviceCommand.Devices
{
///
- /// 低压电源 一拖三
+ /// 低压电源 一拖三(ITECH IT67xx 系列,标准 SCPI 指令集)。
+ /// 传输层为 VISA(USB VISA 资源字符串寻址,如 USB0::VID::PID::SN::INSTR)。
+ /// SCPI 命令语法参照同家族已在实机验证的实现 BOB/DeviceCommand/Devices/IT6724C.cs:
+ /// 设定类命令只发不读(SCPI 设置命令无应答,避免空等超时),查询类命令走 WriteReadAsync 读取一行响应。
///
[ACPCommand]
- public class IT6720 : Visa // 继承 VISA 基类(USB VISA 资源字符串寻址,如 USB0::VID::PID::SN::INSTR)
+ public class IT6720 : Visa
{
- // 帧格式:同步头 AA + 地址 + 命令字 + 数据[4-25] + 校验和,共 26 字节
- private const byte FrameSyncByte = 0xAA;
- private const int FrameLength = 26;
- private readonly byte _deviceAddress = 0x00; // 电源地址
+ // SCPI 指令结束符(VISA 消息终止符使用 LF)
+ private const string ScpiDelimiter = "\n";
+
+ // 最近一次设定的限流值(A):IT67xx SCPI 无直接 CV/CC 查询,用"实测电流是否顶到限流"启发式判断输出模式
+ private double _setCurrentLimit;
public IT6720(VisaConfig config) : base(config)
{
}
- #region 核心协议封装(不变)
+ #region SCPI 收发辅助
- ///
- /// 构建 26 字节的发送帧,并计算校验和
- ///
- private byte[] BuildFrame(byte command, byte[] dataBytes)
- {
- List frame = new List { FrameSyncByte, _deviceAddress, command };
+ /// 以不变文化格式化数值,避免小数分隔符随系统区域变化。
+ private static string Fmt(double value) => value.ToString(CultureInfo.InvariantCulture);
- if (dataBytes != null && dataBytes.Length > 0)
- {
- frame.AddRange(dataBytes);
- }
+ /// 宽松解析仪器回传的数值字符串,失败返回 0。
+ private static double ParseDouble(string raw)
+ => double.TryParse(raw, NumberStyles.Float, CultureInfo.InvariantCulture, out double v) ? v : 0;
- // 数据区必须占满 23 字节 (索引 3 到 24)
- while (frame.Count < 25)
- {
- frame.Add(0x00);
- }
+ /// 发送设定类 SCPI 命令(只发不读:SCPI 设置命令无应答,读取会空等超时)。
+ private async Task SetAsync(string command, CancellationToken ct)
+ => await SendAsync($"{command}{ScpiDelimiter}", ct).ConfigureAwait(false);
- // 计算校验和:前 25 字节之和取低 8 位
- byte checkSum = 0;
- for (int i = 0; i < 25; i++)
- {
- checkSum += frame[i];
- }
- frame.Add(checkSum);
-
- return frame.ToArray();
- }
-
- ///
- /// 检查返回的 26 字节响应帧是否正确
- ///
- private bool ValidateResponseFrame(byte[] response)
- {
- if (response == null || response.Length != FrameLength) return false;
- if (response[0] != FrameSyncByte) return false;
- if (response[1] != _deviceAddress) return false;
-
- byte checkSum = 0;
- for (int i = 0; i < 25; i++)
- {
- checkSum += response[i];
- }
- return response[25] == checkSum;
- }
-
- ///
- /// 将实际电压/电流值转换成协议要求的整型 (乘以 1000 变成整形)
- ///
- private int ConvertToProtocolValue(double value)
- {
- return (int)Math.Round(value * 1000);
- }
-
- ///
- /// 将协议返回的 4 字节小端序转换为实际数值 (除以 1000.0)
- ///
- private double Convert4BytesToValue(byte b0, byte b1, byte b2, byte b3)
- {
- uint raw = (uint)(b0 | (b1 << 8) | (b2 << 16) | (b3 << 24));
- return raw / 1000.0;
- }
-
- ///
- /// 将协议返回的 2 字节小端序转换为实际数值 (除以 1000.0)
- ///
- private double Convert2BytesToValue(byte b0, byte b1)
- {
- ushort raw = (ushort)(b0 | (b1 << 8));
- return raw / 1000.0;
- }
+ /// 发送查询类 SCPI 命令并读取一行响应。
+ private async Task QueryAsync(string query, CancellationToken ct)
+ => await WriteReadAsync($"{query}{ScpiDelimiter}", ScpiDelimiter, ct).ConfigureAwait(false);
#endregion
- #region 1. 核心二进制收发方法(使用基类新方法)
+ #region 1. 操作模式与输出控制
///
- /// 发送命令帧并读取仪器返回的 26 字节数据帧
- ///
- private async Task SendAndReadFrameAsync(byte command, byte[] dataBytes, CancellationToken ct = default)
- {
- byte[] sendFrame = BuildFrame(command, dataBytes);
-
- // 使用基类提供的 SendAsync(byte[]) 通过 VISA 会话发送二进制数据帧
- await SendAsync(sendFrame, ct).ConfigureAwait(false);
-
- // 使用基类提供的 ReadAsync(int) 读取定长 26 字节二进制数据帧
- // 注意:读写超时(IOTimeout)与终止符处理均由 VISA 基类统一控制
- return await ReadAsync(FrameLength, ct).ConfigureAwait(false);
- }
-
- #endregion
-
- #region 2. 操作模式与输出控制
-
- ///
- /// 切换电源控制模式:PC 远程控制 / 面板控制
+ /// 切换电源控制模式:PC 远程控制 (SYSTem:REMote) / 面板本地控制 (SYSTem:LOCal)
///
public virtual async Task 切换远程控制模式(bool 远程控制 = true, CancellationToken ct = default)
- {
- byte[] data = new byte[] { (byte)(远程控制 ? 0x01 : 0x00) };
- byte[] response = await SendAndReadFrameAsync(0x20, data, ct);
-
- if (!ValidateResponseFrame(response))
- throw new Exception("IT6720 切换控制模式时通讯校验失败。");
- }
+ => await SetAsync(远程控制 ? "SYSTem:REMote" : "SYSTem:LOCal", ct);
///
/// 切换为本地(面板)控制模式,与远程控制互斥
///
/// 取消令牌
public virtual async Task 切换本地控制模式(CancellationToken ct = default)
- {
- await 切换远程控制模式(false, ct);
- }
+ => await 切换远程控制模式(false, ct);
///
- /// 开启或关闭电源输出
+ /// 开启或关闭电源输出 (OUTPut 1/0)
///
public virtual async Task 设置输出开关(bool 开启 = true, CancellationToken ct = default)
- {
- byte[] data = new byte[] { (byte)(开启 ? 0x01 : 0x00) };
- byte[] response = await SendAndReadFrameAsync(0x21, data, ct);
-
- if (!ValidateResponseFrame(response))
- throw new Exception("IT6720 设置输出时通讯校验失败。");
- }
+ => await SetAsync($"OUTPut {(开启 ? 1 : 0)}", ct);
///
- /// 查询电源输出开关状态(由实时测量值的状态字节判断)
+ /// 查询电源输出开关状态 (OUTPut?)
///
/// 取消令牌
/// 输出开关状态字符串 (ON / OFF)
public virtual async Task 查询输出开关状态(CancellationToken ct = default)
{
- var (_, _, status) = await 查询实时测量值(ct);
- return (status & 0x01) != 0 ? "ON" : "OFF";
+ string r = (await QueryAsync("OUTPut?", ct)).Trim();
+ return (r == "1" || r.StartsWith("ON", StringComparison.OrdinalIgnoreCase)) ? "ON" : "OFF";
}
#endregion
- #region 3. 电压 / 电流设定
+ #region 2. 电压 / 电流设定
///
- /// 设置电源的电压上限
+ /// 设置电源的电压上限 / 过压保护阈值 (VOLT:PROTection)
///
public virtual async Task 设置电压上限(double 电压, CancellationToken ct = default)
- {
- int raw = ConvertToProtocolValue(电压);
- byte[] data = new byte[]
- {
- (byte)(raw & 0xFF), (byte)((raw >> 8) & 0xFF),
- (byte)((raw >> 16) & 0xFF), (byte)((raw >> 24) & 0xFF)
- };
- byte[] response = await SendAndReadFrameAsync(0x22, data, ct);
- if (!ValidateResponseFrame(response))
- throw new Exception("IT6720 设置电压上限时通讯校验失败。");
- }
+ => await SetAsync($"VOLT:PROTection {Fmt(电压)}", ct);
///
- /// 设置电源的输出电压
+ /// 设置电源的输出电压 (VOLTage)
///
public virtual async Task 设置输出电压(double 电压, CancellationToken ct = default)
- {
- int raw = ConvertToProtocolValue(电压);
- byte[] data = new byte[]
- {
- (byte)(raw & 0xFF), (byte)((raw >> 8) & 0xFF),
- (byte)((raw >> 16) & 0xFF), (byte)((raw >> 24) & 0xFF)
- };
- byte[] response = await SendAndReadFrameAsync(0x23, data, ct);
- if (!ValidateResponseFrame(response))
- throw new Exception("IT6720 设置输出电压时通讯校验失败。");
- }
+ => await SetAsync($"VOLTage {Fmt(电压)}", ct);
///
- /// 设置电源的输出电流限流值
+ /// 设置电源的输出电流限流值 (CURRent)
///
public virtual async Task 设置输出电流(double 电流, CancellationToken ct = default)
{
- int raw = ConvertToProtocolValue(电流);
- byte[] data = new byte[]
- {
- (byte)(raw & 0xFF), (byte)((raw >> 8) & 0xFF)
- };
- byte[] response = await SendAndReadFrameAsync(0x24, data, ct);
- if (!ValidateResponseFrame(response))
- throw new Exception("IT6720 设置输出电流时通讯校验失败。");
+ _setCurrentLimit = 电流;
+ await SetAsync($"CURRent {Fmt(电流)}", ct);
}
#endregion
- #region 4. 读取实时数据与状态(对应 0x26H)
+ #region 3. 读取实时数据与状态
///
- /// 读取电源的实时电流、电压、设定值及电源状态
- /// 返回元组:实际电流(A), 实际电压(V), 状态字节(int)
+ /// 读取电源的实时电流、电压及输出开关状态
+ /// 返回元组:实际电流(A, MEASure:CURRent?), 实际电压(V, MEASure:VOLTage?), 状态字节(int, 位0=输出开关)
///
[Monitorable("IT6720 实时测量数据")]
public virtual async Task<(double ActualCurrent, double ActualVoltage, int StatusByte)> 查询实时测量值(CancellationToken ct = default)
{
- byte[] response = await SendAndReadFrameAsync(0x26, new byte[0], ct);
-
- if (!ValidateResponseFrame(response))
- throw new Exception("IT6720 读取数据时通讯校验失败。");
-
- double actualCurrent = Convert2BytesToValue(response[4], response[5]);
- double actualVoltage = Convert4BytesToValue(response[6], response[7], response[8], response[9]);
- int statusByte = response[10];
-
- return (actualCurrent, actualVoltage, statusByte);
+ double current = ParseDouble(await QueryAsync("MEASure:CURRent?", ct));
+ double voltage = ParseDouble(await QueryAsync("MEASure:VOLTage?", ct));
+ string on = (await QueryAsync("OUTPut?", ct)).Trim();
+ int statusByte = (on == "1" || on.StartsWith("ON", StringComparison.OrdinalIgnoreCase)) ? 0x01 : 0x00;
+ return (current, voltage, statusByte);
}
///
- /// 查询电源的实时实际输出电压 (MEASure 相关寄存器)
+ /// 查询电源的实时实际输出电压 (MEASure:VOLTage?)
///
/// 取消令牌
/// 实时电压值 (单位: V,保留两位小数)
@@ -252,7 +134,7 @@ namespace DeviceCommand.Devices
}
///
- /// 查询电源的实时实际输出电流 (MEASure 相关寄存器)
+ /// 查询电源的实时实际输出电流 (MEASure:CURRent?)
///
/// 取消令牌
/// 实时电流值 (单位: A,保留两位小数)
@@ -264,47 +146,30 @@ namespace DeviceCommand.Devices
}
///
- /// 读取状态字,判断是否处于恒压 (CV) 模式或恒流 (CC) 模式
- /// 状态字节位详解:位 2,3 = 1(CV), 2(CC)
+ /// 判断输出处于恒压 (CV) 还是恒流 (CC) 模式。
+ /// IT67xx SCPI 无直接 CV/CC 查询命令,此处以"实测电流是否顶到限流设定值"启发式判断:
+ /// 输出关闭返回 OFF;实测电流达到限流值(≥98%)判为 CC,否则 CV。
///
public virtual async Task 查询输出模式(CancellationToken ct = default)
{
- var (_, _, status) = await 查询实时测量值(ct);
- int mode = (status >> 2) & 0x03;
- return mode switch
- {
- 1 => "CV",
- 2 => "CC",
- _ => "UNKNOWN"
- };
+ var (current, _, status) = await 查询实时测量值(ct);
+ if ((status & 0x01) == 0) return "OFF";
+ if (_setCurrentLimit > 0 && current >= _setCurrentLimit * 0.98) return "CC";
+ return "CV";
}
#endregion
- #region 5. 读取设备信息(命令字 31H)
+ #region 4. 设备信息与错误
///
- /// 读取电源的序列号、型号及软件版本
+ /// 读取电源的标识信息 (*IDN?:制造商, 型号, 序列号, 固件版本)
///
public virtual async Task 查询设备信息(CancellationToken ct = default)
- {
- byte[] response = await SendAndReadFrameAsync(0x31, new byte[0], ct);
- if (!ValidateResponseFrame(response)) return "Invalid Response";
-
- string model = System.Text.Encoding.ASCII.GetString(response, 4, 5).Trim('\0');
- string swVersion = $"{response[9]:X2}.{response[10]:X2}";
- string serialNum = System.Text.Encoding.ASCII.GetString(response, 11, 10).Trim('\0');
-
- return $"Model:{model}, SW:{swVersion}, SN:{serialNum}";
- }
-
- #endregion
-
- #region 6. 扩展系统命令(兼容接口)
+ => await QueryAsync("*IDN?", ct);
///
- /// 基类自带的超时和清空缓冲区已经处理了大部分错误。
- /// 如需在面板显示具体错误,需通过状态位自行判断或查看面板 ERROR 灯。
+ /// IT67xx 未提供标准错误队列查询,返回面板提示说明
///
public virtual async Task 查询错误信息(CancellationToken ct = default)
{
@@ -313,4 +178,4 @@ namespace DeviceCommand.Devices
#endregion
}
-}
\ No newline at end of file
+}