diff --git a/BenchMovementModule/ViewModels/BenchMovementViewModel.cs b/BenchMovementModule/ViewModels/BenchMovementViewModel.cs
index 6560420..f13aaf9 100644
--- a/BenchMovementModule/ViewModels/BenchMovementViewModel.cs
+++ b/BenchMovementModule/ViewModels/BenchMovementViewModel.cs
@@ -287,7 +287,7 @@ namespace BenchMovementModule.ViewModels
if (!EnsureConnected()) return;
try
{
- await _gantry!.回零();
+ await _gantry!.回零(15);
LoggerHelper.Info("[BenchMovement] 回原点已触发");
}
catch (Exception ex)
diff --git a/DeviceCommand/Base/ModbusTcp.cs b/DeviceCommand/Base/ModbusTcp.cs
index 1e370df..0b2c27e 100644
--- a/DeviceCommand/Base/ModbusTcp.cs
+++ b/DeviceCommand/Base/ModbusTcp.cs
@@ -12,8 +12,8 @@ namespace DeviceCommand.Base
{
public string IPAddress { get; private set; } = "127.0.0.1";
public int Port { get; private set; } = 502;
- public int SendTimeout { get; private set; } = 3000;
- public int ReceiveTimeout { get; private set; } = 3000;
+ public int SendTimeout { get; private set; } = 10000;
+ public int ReceiveTimeout { get; private set; } = 10000;
private TcpClient _tcpClient;
public IModbusMaster Modbus { get; private set; }
diff --git a/DeviceCommand/Devices/GantryControlTcp.cs b/DeviceCommand/Devices/GantryControlTcp.cs
index 1350fd7..b82a11d 100644
--- a/DeviceCommand/Devices/GantryControlTcp.cs
+++ b/DeviceCommand/Devices/GantryControlTcp.cs
@@ -7,6 +7,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
+using System.ComponentModel;
namespace DeviceCommand.Devices
{
@@ -18,6 +19,14 @@ namespace DeviceCommand.Devices
// 向后兼容:TcpDevice 即自身(继承自 ModbusTcp)
public ModbusTcp TcpDevice => this;
+ // =================================================================
+ // ------------------ 软件位置跟踪(绝对位置寄存器会被清零)----------
+ // =================================================================
+ /// 当前位置相对于机械零点的位置(每次移动后累加偏移)
+ private int _originX, _originY, _originZ;
+ /// 是否已完成回零初始化
+ private bool _isHomed;
+
public GantryControlTcp(TcpConfig config) : base(config)
{
@@ -341,17 +350,70 @@ namespace DeviceCommand.Devices
}
///
- /// 全局回零(安全校验版:先检查回原点速度是否为 0,为0则拒绝触发)
+ /// 全局回零(安全校验版:先检查回原点速度是否为 0,为0则拒绝触发)。
+ /// 回零完成后读取绝对位置寄存器保存为初始值,重置累计偏移。
///
+ /// 回零点等待时间
/// 取消令牌
- public async Task 回零(CancellationToken 取消令牌 = default)
+ public async Task 回零(int 回零点等待时间, CancellationToken 取消令牌 = default)
{
int currentSpeed = await 查询回零速度(取消令牌);
if (currentSpeed == 0)
throw new InvalidOperationException("无法触发回原点!当前回原点定位速度为 0,请先设置合法的回零速度。");
+ // 1. 触发回零
await TriggerCoilAsync(ADDR_M_HOME_TRIGGER, 取消令牌);
+
+ // 2. 等待到达机械零点(绝对位置 = 0)
+ await 等待到达零点(回零点等待时间, 取消令牌);
+ await Task.Delay(500, 取消令牌);
+
+ // 3. 清零绝对位置寄存器
+ await WriteMultipleRegistersAsync(_slaveAddress, ADDR_ABS_AXIS1_POS, Int32ToUshorts(0), 取消令牌);
+ await WriteMultipleRegistersAsync(_slaveAddress, ADDR_ABS_AXIS2_POS, Int32ToUshorts(0), 取消令牌);
+ await WriteMultipleRegistersAsync(_slaveAddress, ADDR_ABS_AXIS3_POS, Int32ToUshorts(0), 取消令牌);
+
+ // 4. 设置当前位置为机械零点 (0, 0, 0)
+ // 设置目标位置
+ await WriteMultipleRegistersAsync(_slaveAddress, ADDR_HD_X_TARGET, Int32ToUshorts(0), 取消令牌);
+ await WriteMultipleRegistersAsync(_slaveAddress, ADDR_HD_Y_TARGET, Int32ToUshorts(0), 取消令牌);
+ await WriteMultipleRegistersAsync(_slaveAddress, ADDR_HD_Z_TARGET, Int32ToUshorts(0), 取消令牌);
+
+ // 5. 读取配置的待定位置(原点位置)
+ var (xHome, yHome, zHome) = await 查询原点位置(取消令牌);
+
+ // 6. 移动到待定位置
+ if (xHome != 0 || yHome != 0 || zHome != 0)
+ {
+
+ _originX = xHome;
+ _originY = yHome;
+ _originZ = zHome;
+
+ // 启动三轴联动
+ await 启动全部轴(取消令牌);
+
+ // 等待到达待定位置
+ await 等待到达待定位置(xHome, yHome, zHome, 回零点等待时间, 取消令牌);
+
+ // 7. 到达后清零绝对位置寄存器
+ await Task.Delay(500, 取消令牌);
+ await WriteMultipleRegistersAsync(_slaveAddress, ADDR_ABS_AXIS1_POS, Int32ToUshorts(0), 取消令牌);
+ await WriteMultipleRegistersAsync(_slaveAddress, ADDR_ABS_AXIS2_POS, Int32ToUshorts(0), 取消令牌);
+ await WriteMultipleRegistersAsync(_slaveAddress, ADDR_ABS_AXIS3_POS, Int32ToUshorts(0), 取消令牌);
+
+ // 清零目标寄存器
+ await WriteMultipleRegistersAsync(_slaveAddress, ADDR_HD_X_TARGET, Int32ToUshorts(0), 取消令牌);
+ await WriteMultipleRegistersAsync(_slaveAddress, ADDR_HD_Y_TARGET, Int32ToUshorts(0), 取消令牌);
+ await WriteMultipleRegistersAsync(_slaveAddress, ADDR_HD_Z_TARGET, Int32ToUshorts(0), 取消令牌);
+ }
+
+ // 8. 更新当前位置为待定位置
+ _originX = xHome;
+ _originY = yHome;
+ _originZ = zHome;
+ _isHomed = true;
}
///
@@ -467,22 +529,6 @@ namespace DeviceCommand.Devices
int yTarget = await 查询轴目标位置(2, 取消令牌);
int zTarget = await 查询轴目标位置(3, 取消令牌);
- // X轴安全范围联动校验
- int xMaxAllowed = 50000 - xHome;
- if (xTarget < 0 || xTarget > xMaxAllowed)
- throw new ArgumentOutOfRangeException(nameof(xTarget), $"X轴目标位置 [{xTarget}] 越界,当前原点下最大范围 0-{xMaxAllowed}");
-
- // Y轴安全范围联动校验
- int yMaxAllowed = 50000 - yHome;
- if (yTarget < 0 || yTarget > yMaxAllowed)
- throw new ArgumentOutOfRangeException(nameof(yTarget), $"Y轴目标位置 [{yTarget}] 越界,当前原点下最大范围 0-{yMaxAllowed}");
-
- // Z轴安全范围联动校验
- int zMaxAllowed = 20000 - zHome;
- if (zTarget < 0 || zTarget > zMaxAllowed)
- throw new ArgumentOutOfRangeException(nameof(zTarget), $"Z轴目标位置 [{zTarget}] 越界,当前原点下最大范围 0-{zMaxAllowed}");
-
- // 4. 全部联动校验通过,三轴点动同步启动
await WriteSingleCoilAsync(_slaveAddress, ADDR_M_X_START, true, 取消令牌);
await WriteSingleCoilAsync(_slaveAddress, ADDR_M_Y_START, true, 取消令牌);
await WriteSingleCoilAsync(_slaveAddress, ADDR_M_Z_START, true, 取消令牌);
@@ -494,6 +540,68 @@ namespace DeviceCommand.Devices
await WriteSingleCoilAsync(_slaveAddress, ADDR_M_Z_START, false, 取消令牌);
}
+ ///
+ /// 三轴相对移动:以(初始绝对值 + 累计偏移 + 本次偏移)做越界校验,
+ /// 清零绝对位置寄存器后将偏移量写入目标寄存器,启动三轴联动,
+ /// 等待到达后清零目标寄存器并更新累计偏移。偏移量可为负值。
+ /// 调用前必须先执行回零()完成初始化。
+ ///
+ /// X轴相对偏移量(可为负)
+ /// Y轴相对偏移量(可为负)
+ /// Z轴相对偏移量(可为负)
+ /// 等待到达目标位置的最大超时时间(秒)
+ /// 取消令牌
+ public async Task 相对移动(int X偏移量, int Y偏移量, int Z偏移量, int 超时秒数 = 60, CancellationToken 取消令牌 = default)
+ {
+ if (!_isHomed)
+ throw new InvalidOperationException("尚未完成回零初始化,请先调用 回零() 方法。");
+
+ // 1. 越界校验:当前位置 + 本次偏移是否在合法范围
+ int targetAbsX = _originX + X偏移量;
+ int targetAbsY = _originY + Y偏移量;
+ int targetAbsZ = _originZ + Z偏移量;
+
+ if (targetAbsX > 50000)
+ throw new ArgumentOutOfRangeException(nameof(X偏移量),
+ $"X轴越界:当前 {_originX} + 偏移 {X偏移量} = {targetAbsX},最大范围 50000");
+
+ if (targetAbsY > 50000)
+ throw new ArgumentOutOfRangeException(nameof(Y偏移量),
+ $"Y轴越界:当前 {_originY} + 偏移 {Y偏移量} = {targetAbsY},最大范围 50000");
+
+ if (targetAbsZ > 20000)
+ throw new ArgumentOutOfRangeException(nameof(Z偏移量),
+ $"Z轴越界:当前 {_originZ} + 偏移 {Z偏移量} = {targetAbsZ},最大范围 20000");
+
+ // 2. 将偏移量写入目标位置寄存器(ABS已在上次移动后清零,目标 = 偏移量)
+ await WriteMultipleRegistersAsync(_slaveAddress, ADDR_HD_X_TARGET, Int32ToUshorts(X偏移量), 取消令牌);
+ await WriteMultipleRegistersAsync(_slaveAddress, ADDR_HD_Y_TARGET, Int32ToUshorts(Y偏移量), 取消令牌);
+ await WriteMultipleRegistersAsync(_slaveAddress, ADDR_HD_Z_TARGET, Int32ToUshorts(Z偏移量), 取消令牌);
+
+ // 3. 启动三轴联动
+ await 启动全部轴(取消令牌);
+ await 等待到达指定位置(超时秒数);
+ await 清空绝对寄存器();
+
+ // 5. 到达后更新当前位置(累加偏移)
+ _originX += X偏移量;
+ _originY += Y偏移量;
+ _originZ += Z偏移量;
+
+
+
+
+ }
+ public async Task 清空绝对寄存器( CancellationToken 取消令牌 = default)
+ {
+ // 6. 清零 6 轴绝对位置寄存器
+ await WriteMultipleRegistersAsync(_slaveAddress, ADDR_ABS_AXIS1_POS, Int32ToUshorts(0), 取消令牌);
+ await WriteMultipleRegistersAsync(_slaveAddress, ADDR_ABS_AXIS2_POS, Int32ToUshorts(0), 取消令牌);
+ await WriteMultipleRegistersAsync(_slaveAddress, ADDR_ABS_AXIS3_POS, Int32ToUshorts(0), 取消令牌);
+ await WriteMultipleRegistersAsync(_slaveAddress, ADDR_ABS_AXIS4_POS, Int32ToUshorts(0), 取消令牌);
+ await WriteMultipleRegistersAsync(_slaveAddress, ADDR_ABS_AXIS5_POS, Int32ToUshorts(0), 取消令牌);
+ await WriteMultipleRegistersAsync(_slaveAddress, ADDR_ABS_AXIS6_POS, Int32ToUshorts(0), 取消令牌);
+ }
#region 内部高低字工具转化方法
///
@@ -582,12 +690,12 @@ namespace DeviceCommand.Devices
#endregion
#region 位置到达判定(超时轮询)
-
///
- /// 轮询等待三轴绝对位置均回到零点(容差 ±10),超时则抛出异常
+ /// 轮询等待三轴绝对位置均回到机械零点(容差 ±10),超时则抛出异常
///
/// 最大等待时间(秒)
/// 取消令牌
+ [Browsable(false)]
public async Task 等待到达零点(int 超时秒数, CancellationToken 取消令牌 = default)
{
var deadline = DateTime.Now.AddSeconds(超时秒数);
@@ -597,13 +705,43 @@ namespace DeviceCommand.Devices
取消令牌.ThrowIfCancellationRequested();
var pos = await 查询全部轴绝对位置(取消令牌);
- if (Math.Abs(pos.Axis1) <= 10 && Math.Abs(pos.Axis2) <= 10 && Math.Abs(pos.Axis3) <= 10)
+ if (Math.Abs(pos.Axis1) <= 10
+ && Math.Abs(pos.Axis6) <= 10
+ && Math.Abs(pos.Axis2) <= 10)
return;
await Task.Delay(100, 取消令牌);
}
- throw new TimeoutException($"等待 {超时秒数} 秒后,三轴仍未回到零点");
+ throw new TimeoutException($"等待 {超时秒数} 秒后,三轴仍未到达机械零点");
+ }
+
+ ///
+ /// 轮询等待三轴绝对位置到达指定的待定位置(容差 ±10),超时则抛出异常
+ ///
+ /// X轴目标位置
+ /// Y轴目标位置
+ /// Z轴目标位置
+ /// 最大等待时间(秒)
+ /// 取消令牌
+ private async Task 等待到达待定位置(int xTarget, int yTarget, int zTarget, int 超时秒数, CancellationToken 取消令牌 = default)
+ {
+ var deadline = DateTime.Now.AddSeconds(超时秒数);
+
+ while (DateTime.Now < deadline)
+ {
+ 取消令牌.ThrowIfCancellationRequested();
+
+ var pos = await 查询全部轴绝对位置(取消令牌);
+ if (Math.Abs(pos.Axis1 - xTarget) <= 10
+ && Math.Abs(pos.Axis6 - yTarget) <= 10
+ && Math.Abs(pos.Axis2 - zTarget) <= 10)
+ return;
+
+ await Task.Delay(100, 取消令牌);
+ }
+
+ throw new TimeoutException($"等待 {超时秒数} 秒后,三轴仍未到达待定位置 (X:{xTarget}, Y:{yTarget}, Z:{zTarget})");
}
///
@@ -625,8 +763,8 @@ namespace DeviceCommand.Devices
var pos = await 查询全部轴绝对位置(取消令牌);
if (Math.Abs(pos.Axis1 - targetX) <= 10
- && Math.Abs(pos.Axis2 - targetY) <= 10
- && Math.Abs(pos.Axis3 - targetZ) <= 10)
+ && Math.Abs(pos.Axis6 - targetY) <= 10
+ && Math.Abs(pos.Axis2 - targetZ) <= 10)
return;
await Task.Delay(100, 取消令牌);
diff --git a/DeviceCommand/Devices/N69200.cs b/DeviceCommand/Devices/N69200.cs
index 8002ae6..652715b 100644
--- a/DeviceCommand/Devices/N69200.cs
+++ b/DeviceCommand/Devices/N69200.cs
@@ -336,6 +336,18 @@ namespace DeviceCommand.Devices
#region 3.6. 系统控制及报警清除
+
+ ///
+ /// 退出远程控制 (MEASure:EVENT?)
+ ///
+ /// true 开启远程;false关闭远程
+ /// 取消令牌
+
+ public virtual async Task 启动远程控制(bool 是否开启, CancellationToken ct = default)
+ {
+ int val = 是否开启 ? 0 : 1;
+ await SendAsync($"SYSTem:EXIT:REMOte {val}{ScpiDelimiter}", ct);
+ }
///
/// 读取负载报警/事件寄存器信息 (MEASure:EVENT?)
///
diff --git a/ZLGUSBCANFD/USBCANFD.cs b/ZLGUSBCANFD/USBCANFD.cs
index be70fe9..7e3dba4 100644
--- a/ZLGUSBCANFD/USBCANFD.cs
+++ b/ZLGUSBCANFD/USBCANFD.cs
@@ -688,6 +688,7 @@ namespace ZLGUSBCANFD
///
/// 发送自定义报文(原始字节,帧 ID 支持 string 兼容模式)。
+ /// 循环发送间隔毫秒 = 0 时只发送一次;>0 时按指定间隔循环发送,可调用 停止循环发送 停止。
///
/// CAN/CANFD 通道索引
/// 帧 ID(可以是 "26"、"0x1A"、"1A");扩展帧会自动置位 0x80000000
@@ -696,6 +697,7 @@ namespace ZLGUSBCANFD
/// 是否为 29 位扩展帧
/// true = CANFD;false = 经典 CAN
/// CANFD 是否开启波特率加速
+ /// 0 = 单次发送;>0 = 周期循环发送(毫秒)
public virtual bool 发送自定义报文(
uint 通道号,
string 帧IDStr,
@@ -703,7 +705,8 @@ namespace ZLGUSBCANFD
byte dlcLength,
bool 是否是扩展帧 = false,
bool 是否是CANFD = true,
- bool 开启波特率加速BRS = true)
+ bool 开启波特率加速BRS = true,
+ int 循环发送间隔毫秒 = 0)
{
if (!TryParseFrameId(帧IDStr, out uint 帧ID))
{
@@ -713,23 +716,88 @@ namespace ZLGUSBCANFD
if (通道号 >= _maxChannels || _channelHandles[通道号] == IntPtr.Zero) return false;
if (原始数据 == null) return false;
+ if (循环发送间隔毫秒 < 0) throw new ArgumentOutOfRangeException(nameof(循环发送间隔毫秒), "循环发送间隔必须大于或等于 0。");
// 扩展帧需要把最高位标志位置起来
uint canId = 帧ID & 0x7FFFFFFF;
if (是否是扩展帧) canId |= 0x80000000;
+ // 单次发送
+ if (循环发送间隔毫秒 == 0)
+ {
+ return 发送自定义报文单次(通道号, canId, 原始数据, dlcLength, 是否是扩展帧, 是否是CANFD, 开启波特率加速BRS);
+ }
+
+ // 循环发送:先停止同通道同帧 ID 的旧循环,再启动新循环
+ 停止循环发送(通道号, 帧ID);
+
+ // 拷贝原始数据,避免外部修改影响循环发送
+ byte[] dataCopy = new byte[原始数据.Length];
+ Array.Copy(原始数据, dataCopy, 原始数据.Length);
+
+ var cts = new CancellationTokenSource();
+ var sendTask = Task.Run(async () =>
+ {
+ while (!cts.Token.IsCancellationRequested)
+ {
+ if (_isClosing) break;
+ try
+ {
+ lock (_channelLocks[通道号])
+ {
+ if (_channelHandles[通道号] == IntPtr.Zero || _isClosing) break;
+ 发送自定义报文单次(通道号, canId, dataCopy, dlcLength, 是否是扩展帧, 是否是CANFD, 开启波特率加速BRS);
+ }
+
+ await Task.Delay(循环发送间隔毫秒, cts.Token);
+ }
+ catch (OperationCanceledException)
+ {
+ break;
+ }
+ catch (Exception ex)
+ {
+ Logger.LoggerHelper.Error($"[ZLGCANFD] 循环发送自定义报文失败: {ex.Message}");
+ break;
+ }
+ }
+
+ if (_cyclicSenders.TryGetValue((通道号, 帧ID), out var current) && ReferenceEquals(current.Cts, cts))
+ {
+ _cyclicSenders.TryRemove((通道号, 帧ID), out _);
+ }
+ cts.Dispose();
+ }, cts.Token);
+
+ _cyclicSenders[(通道号, 帧ID)] = (cts, sendTask);
+
+ return true;
+ }
+
+ ///
+ /// 发送自定义报文的单次执行(内部方法,无循环逻辑)。
+ ///
+ [Browsable(false)]
+ private bool 发送自定义报文单次(
+ uint 通道号,
+ uint canId,
+ byte[] 原始数据,
+ byte dlcLength,
+ bool 是否是扩展帧,
+ bool 是否是CANFD,
+ bool 开启波特率加速BRS)
+ {
lock (_channelLocks[通道号])
{
if (是否是CANFD)
{
- // CANFD 有效 DLC 校验
byte validFdDlc = 校验CANFD的Dlc(dlcLength);
ZLGCAN.canfd_frame fdFrame = new ZLGCAN.canfd_frame
{
can_id = canId,
len = validFdDlc,
- flags = (byte)((是否是扩展帧 ? 0x01 : 0x00) | (开启波特率加速BRS ? 0x02 : 0x00) | 0x20), // 0x20 本地回显
+ flags = (byte)((是否是扩展帧 ? 0x01 : 0x00) | (开启波特率加速BRS ? 0x02 : 0x00) | 0x20),
data = new byte[64]
};
Array.Copy(原始数据, 0, fdFrame.data, 0, Math.Min(原始数据.Length, 64));
@@ -749,7 +817,7 @@ namespace ZLGUSBCANFD
{
can_id = canId,
can_dlc = dlcLength > 8 ? (byte)8 : dlcLength,
- __pad = 0x20, // 发送回显
+ __pad = 0x20,
data = new byte[8]
};
Array.Copy(原始数据, 0, standardFrame.data, 0, Math.Min(原始数据.Length, 8));