Files
XplorePlane/XP.Hardware.MotionControl/Documents/GUIDENCE.md
T

478 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 运动控制模块集成指南 | Motion Control Module Integration Guide
## 1. 模块注册 | Module Registration
`App.xaml.cs` 中注册模块:
```csharp
using XP.Hardware.MotionControl.Module;
protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog)
{
moduleCatalog.AddModule<MotionControlModule>();
}
```
模块启动时自动完成:
- 从 App.config 加载配置(轴范围、几何原点、轮询周期)
- 注册 `IMotionSystem``IMotionControlService``GeometryCalculator` 为单例
- 注册多语言资源到 Fallback Chain
- 启动 PLC 状态轮询(100ms 周期)
---
## 2. 界面集成 | UI Integration
### AxisControlView 控件使用 | AxisControlView Usage
`AxisControlView` 是运动控制模块的核心用户控件,提供完整的轴控制和调试功能。
**XAML 引用 | XAML Reference**
```xml
<UserControl x:Class="YourView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:motion="clr-namespace:XP.Hardware.MotionControl.Views;assembly=XP.Hardware.MotionControl">
<Grid>
<!-- 基本使用 | Basic usage -->
<motion:AxisControlView />
<!-- 设置最小宽度 | Set minimum width -->
<motion:AxisControlView MinWidth="400" />
</Grid>
</UserControl>
```
**自动 ViewModel 绑定 | Auto ViewModel Binding**
控件使用 Prism 的 `ViewModelLocator.AutoWireViewModel="True"` 自动绑定 `AxisControlViewModel`,无需手动设置 `DataContext`
**ViewModel 属性说明 | ViewModel Properties**
| 属性 | 类型 | 说明 |
|------|------|------|
| `StageXPosition` | double | 载物台 X 轴位置(mm),范围由配置决定 |
| `StageYPosition` | double | 载物台 Y 轴位置(mm),范围由配置决定 |
| `SourceZPosition` | double | 射线源 Z 轴位置(mm),范围由配置决定 |
| `DetectorZPosition` | double | 探测器 Z 轴位置(mm),范围由配置决定 |
| `DetectorSwingAngle` | double | 探测器摆动角度(度),范围 -45~45 |
| `StageRotationAngle` | double | 载物台旋转角度(度),范围 -360~360 |
| `FixtureRotationAngle` | double | 夹具旋转角度(度),范围 -90~90 |
| `IsJoystickEnabled` | bool | 摇杆使能开关(默认 true) |
| `SwapMouseButtons` | bool | 摇杆左右键功能切换(默认 false) |
| `SZDZLock` | bool | 射线源/探测器Z轴锁定移动(默认 false) |
| `HasSavedPositions` | bool | 是否有已保存的位置快照 |
**交互方式 | Interaction Modes**
1. **手动输入位置**
- 直接编辑 RadNumericUpDown 控件
- Enter 键确认并移动轴
- Escape 键取消修改,恢复原值
- 上下箭头键步进移动(±0.1
2. **单轴摇杆(SourceZ/DetectorZ**
- 左键拖拽:Y轴方向控制 SourceZ Jog(正/反向由拖拽方向决定)
- 右键拖拽:Y轴方向控制 DetectorZ Jog(正/反向由拖拽方向决定)
- 当 SZDZLock=true 时,左键同时控制 SourceZ + DetectorZ,右键同理
- 松开鼠标:停止 Jog
3. **双轴摇杆(StageX/Y + Rotation/Swing**
- 默认模式(SwapMouseButtons=false):
- 左键拖拽:X轴→StageX JogY轴→StageY Jog
- 右键拖拽:X轴→DetectorSwing JogY轴→StageRotation/FixtureRotation Jog
- 切换模式(SwapMouseButtons=true):左右键功能互换
- 松开鼠标:停止所有关联轴 Jog
4. **快捷按钮**
- **使能开关**:启用/禁用虚拟摇杆控制(同步写入 PLC 信号)
- **摇杆模式**:切换左右键功能映射
- **SZDZ锁定**:锁定 SourceZ/DetectorZ 同步移动(同步写入 PLC 联动使能信号)
- **保存位置**:保存当前所有轴位置到快照(需 PLC 已连接)
- **恢复位置**:恢复到上次保存的位置快照并发送移动命令
---
## 3. 获取轴状态和位置 | Reading Axis Status and Position
通过 DI 注入 `IMotionSystem`
```csharp
using XP.Hardware.MotionControl.Abstractions;
using XP.Hardware.MotionControl.Abstractions.Enums;
public class YourService
{
private readonly IMotionSystem _motionSystem;
public YourService(IMotionSystem motionSystem)
{
_motionSystem = motionSystem;
}
public void ReadStatus()
{
// 获取直线轴 | Get linear axis
ILinearAxis sourceZ = _motionSystem.GetLinearAxis(AxisId.SourceZ);
double position = sourceZ.ActualPosition; // 实际位置(mm
AxisStatus status = sourceZ.Status; // Idle/Moving/Homing/Error/Alarm
bool posLimit = sourceZ.PositiveLimitHit; // 正限位
bool negLimit = sourceZ.NegativeLimitHit; // 负限位
// 获取旋转轴 | Get rotary axis
IRotaryAxis stageRot = _motionSystem.GetRotaryAxis(RotaryAxisId.StageRotation);
double angle = stageRot.ActualAngle; // 实际角度(度)
bool enabled = stageRot.Enabled; // 是否启用
// 获取安全门 | Get safety door
ISafetyDoor door = _motionSystem.SafetyDoor;
DoorStatus doorStatus = door.Status; // Unknown/Opening/Open/Closing/Closed/Locked/Error
bool interlocked = door.IsInterlocked; // 联锁信号
// 遍历所有轴 | Iterate all axes
foreach (var kvp in _motionSystem.LinearAxes)
{
AxisId id = kvp.Key;
ILinearAxis axis = kvp.Value;
// ...
}
foreach (var kvp in _motionSystem.RotaryAxes)
{
RotaryAxisId id = kvp.Key;
IRotaryAxis axis = kvp.Value;
// ...
}
}
}
```
---
## 4. 下发运动控制命令 | Sending Motion Commands
通过 DI 注入 `IMotionControlService`
```csharp
using XP.Hardware.MotionControl.Abstractions;
using XP.Hardware.MotionControl.Abstractions.Enums;
using XP.Hardware.MotionControl.Services;
public class YourController
{
private readonly IMotionControlService _mc;
private readonly IMotionSystem _motionSystem;
public YourController(IMotionControlService motionControlService, IMotionSystem motionSystem)
{
_mc = motionControlService;
_motionSystem = motionSystem;
}
// 单轴移动 | Single axis move
public void MoveSingleAxis()
{
MotionResult result = _mc.MoveToTarget(AxisId.SourceZ, 100.0);
if (!result.Success)
Console.WriteLine($"移动失败: {result.ErrorMessage}");
// 带速度参数 | With speed parameter
_mc.MoveToTarget(AxisId.SourceZ, 100.0, speed: 50.0);
// 旋转轴 | Rotary axis
_mc.MoveRotaryToTarget(RotaryAxisId.DetectorSwing, 15.0);
_mc.MoveRotaryToTarget(RotaryAxisId.DetectorSwing, 15.0, speed: 30.0);
}
// 多轴联动 | Multi-axis coordinated move
public void MoveMultipleAxes()
{
var targets = new Dictionary<AxisId, double>
{
{ AxisId.SourceZ, 100.0 },
{ AxisId.DetectorZ, 400.0 },
{ AxisId.StageX, 0.0 },
{ AxisId.StageY, 0.0 }
};
// 原子性边界检查:任意轴越界则全部拒绝
MotionResult result = _mc.MoveAllToTarget(targets);
}
// 停止和回零 | Stop and Home
public void StopAndHome()
{
_mc.StopAll(); // 停止所有已启用轴
_mc.HomeAll(); // 所有已启用轴回零
}
// Jog 点动 | Jog
public void JogAxis()
{
_mc.JogStart(AxisId.SourceZ, positive: true); // 正向 Jog
// ... 用户松开按钮时 ...
_mc.JogStop(AxisId.SourceZ); // 停止 Jog
// 旋转轴 Jog
_mc.JogRotaryStart(RotaryAxisId.DetectorSwing, positive: false);
_mc.JogRotaryStop(RotaryAxisId.DetectorSwing);
}
// 安全门控制 | Safety door control
public void DoorControl()
{
MotionResult openResult = _mc.OpenDoor(); // 含联锁检查
_mc.CloseDoor();
_mc.StopDoor();
}
// 轴复位 | Axis reset
public void AxisReset()
{
// 通过 IMotionSystem 获取轴复位实例 | Get axis reset instance via IMotionSystem
IAxisReset axisReset = _motionSystem.AxisReset;
MotionResult result = axisReset.Reset(); // 发送复位命令
bool done = axisReset.IsResetDone; // 读取复位完成状态
}
}
```
---
## 5. 几何计算 | Geometry Calculation
```csharp
public class YourGeometryUser
{
private readonly IMotionControlService _mc;
private readonly GeometryCalculator _calc;
public YourGeometryUser(
IMotionControlService mc,
GeometryCalculator calc)
{
_mc = mc;
_calc = calc;
}
// 正算:获取当前 FOD/FDD/放大倍率 | Forward: get current geometry
public void GetCurrentGeometry()
{
var (fod, fdd, magnification) = _mc.GetCurrentGeometry();
Console.WriteLine($"FOD={fod:F2}mm, FDD={fdd:F2}mm, M={magnification:F3}");
}
// 反算:由目标 FOD+FDD 移动轴 | Inverse: move axes by target FOD+FDD
public void ApplyGeometry()
{
MotionResult result = _mc.ApplyGeometry(targetFOD: 200.0, targetFDD: 500.0);
}
// 反算:由目标 FOD+放大倍率 移动轴 | Inverse: move axes by target FOD+magnification
public void ApplyByMagnification()
{
MotionResult result = _mc.ApplyGeometryByMagnification(targetFOD: 200.0, magnification: 2.5);
}
// 直接使用 GeometryCalculator | Use GeometryCalculator directly
public void DirectCalculation()
{
double fod = _calc.CalcFOD(sourceZAbsolute: 100.0, stageRotationCenterZ: 300.0);
double fdd = _calc.CalcFDD(sourceZAbsolute: 100.0, detectorZAbsolute: 600.0);
double mag = _calc.CalcMagnification(fdd, fod); // FOD < 0.001 时返回 NaN
// 反算轴目标位置
var (szTarget, dzTarget) = _calc.CalcAxisTargets(
targetFOD: 200.0, targetFDD: 500.0,
stageRotationCenterZ: 300.0,
sourceZOrigin: 0.0, detectorZOrigin: 600.0);
}
}
```
---
## 6. 事件订阅 | Event Subscription
通过 Prism `IEventAggregator` 被动接收状态变化:
```csharp
using Prism.Events;
using XP.Hardware.MotionControl.Abstractions.Enums;
using XP.Hardware.MotionControl.Abstractions.Events;
public class YourMonitor
{
public YourMonitor(IEventAggregator eventAggregator)
{
// 轴状态变化 | Axis status changed
eventAggregator.GetEvent<AxisStatusChangedEvent>()
.Subscribe(data =>
{
Console.WriteLine($"轴 {data.AxisId} 状态变为 {data.Status}");
}, ThreadOption.UIThread);
// 门状态变化 | Door status changed
eventAggregator.GetEvent<DoorStatusChangedEvent>()
.Subscribe(status =>
{
Console.WriteLine($"门状态: {status}");
}, ThreadOption.UIThread);
// 门联锁状态变化 | Door interlock status changed
eventAggregator.GetEvent<DoorInterlockChangedEvent>()
.Subscribe(isInterlocked =>
{
Console.WriteLine($"联锁状态: {(isInterlocked ? "" : "")}");
}, ThreadOption.UIThread);
// 几何参数更新(每轮询周期触发)| Geometry updated (every polling cycle)
eventAggregator.GetEvent<GeometryUpdatedEvent>()
.Subscribe(data =>
{
Console.WriteLine($"FOD={data.FOD:F2}, FDD={data.FDD:F2}, M={data.Magnification:F3}");
}, ThreadOption.UIThread);
// 运动错误(轴进入 Error/Alarm| Motion error
eventAggregator.GetEvent<MotionErrorEvent>()
.Subscribe(data =>
{
Console.WriteLine($"运动错误: 轴 {data.AxisId} - {data.ErrorMessage}");
}, ThreadOption.UIThread);
// 实体摇杆激活状态变化 | Physical joystick active status changed
eventAggregator.GetEvent<JoystickActiveEvent>()
.Subscribe(isActive =>
{
Console.WriteLine($"实体摇杆: {(isActive ? "" : "")}");
}, ThreadOption.UIThread);
// 几何反算结果填入请求(DebugWindow → MotionControlViewModel
// Geometry apply request (DebugWindow → MotionControlViewModel)
eventAggregator.GetEvent<GeometryApplyRequestEvent>()
.Subscribe(data =>
{
Console.WriteLine($"几何反算请求: SourceZ={data.SourceZTarget}, DetectorZ={data.DetectorZTarget}");
}, ThreadOption.UIThread);
}
}
```
---
## 7. PLC 信号定义 | PLC Signal Definitions
信号名称硬编码在 `MotionSignalNames.cs` 中,信号地址定义在 `PlcAddrDfn.xml`(位于 `XplorePlane/bin/Debug/net8.0-windows/win-x64/`)。
### 7.1 写入信号(WriteCommonDB31
| 信号名 | 类型 | 地址 | 说明 |
|--------|------|------|------|
| `MC_SourceZ_Target` | single | 10 | 射线源Z目标位置(mm |
| `MC_SourceZ_Speed` | single | 14 | 射线源Z运动速度 |
| `MC_SourceZ_JogPos` | byte | 18 | 射线源Z正向Jog |
| `MC_SourceZ_JogNeg` | byte | 19 | 射线源Z反向Jog |
| `MC_SourceZ_Home` | byte | 20 | 射线源Z回零 |
| `MC_SourceZ_Stop` | byte | 21 | 射线源Z停止 |
| `MC_DetZ_Target` | single | 22 | 探测器Z目标位置(mm |
| `MC_DetZ_Speed` | single | 26 | 探测器Z运动速度 |
| `MC_DetZ_JogPos` | byte | 30 | 探测器Z正向Jog |
| `MC_DetZ_JogNeg` | byte | 31 | 探测器Z反向Jog |
| `MC_DetZ_Home` | byte | 32 | 探测器Z回零 |
| `MC_DetZ_Stop` | byte | 33 | 探测器Z停止 |
| `MC_StageX_Target` | single | 34 | 载物台X目标位置(mm |
| `MC_StageX_Speed` | single | 38 | 载物台X运动速度 |
| `MC_StageX_JogPos` | byte | 42 | 载物台X正向Jog |
| `MC_StageX_JogNeg` | byte | 43 | 载物台X反向Jog |
| `MC_StageX_Home` | byte | 44 | 载物台X回零 |
| `MC_StageX_Stop` | byte | 45 | 载物台X停止 |
| `MC_StageY_Target` | single | 46 | 载物台Y目标位置(mm |
| `MC_StageY_Speed` | single | 50 | 载物台Y运动速度 |
| `MC_StageY_JogPos` | byte | 54 | 载物台Y正向Jog |
| `MC_StageY_JogNeg` | byte | 55 | 载物台Y反向Jog |
| `MC_StageY_Home` | byte | 56 | 载物台Y回零 |
| `MC_StageY_Stop` | byte | 57 | 载物台Y停止 |
| `MC_DetSwing_Target` | single | 58 | 探测器摆动目标角度(度) |
| `MC_DetSwing_Speed` | single | 62 | 探测器摆动运动速度 |
| `MC_DetSwing_JogPos` | byte | 66 | 探测器摆动正向Jog |
| `MC_DetSwing_JogNeg` | byte | 67 | 探测器摆动反向Jog |
| `MC_DetSwing_Home` | byte | 68 | 探测器摆动回零 |
| `MC_DetSwing_Stop` | byte | 69 | 探测器摆动停止 |
| `MC_StageRot_Target` | single | 70 | 载物台旋转目标角度(度) |
| `MC_StageRot_Speed` | single | 74 | 载物台旋转运动速度 |
| `MC_StageRot_JogPos` | byte | 78 | 载物台旋转正向Jog |
| `MC_StageRot_JogNeg` | byte | 79 | 载物台旋转反向Jog |
| `MC_StageRot_Home` | byte | 80 | 载物台旋转回零 |
| `MC_StageRot_Stop` | byte | 81 | 载物台旋转停止 |
| `MC_FixRot_Target` | single | 82 | 夹具旋转目标角度(度) |
| `MC_FixRot_Speed` | single | 86 | 夹具旋转运动速度 |
| `MC_FixRot_JogPos` | byte | 90 | 夹具旋转正向Jog |
| `MC_FixRot_JogNeg` | byte | 91 | 夹具旋转反向Jog |
| `MC_FixRot_Home` | byte | 92 | 夹具旋转回零 |
| `MC_FixRot_Stop` | byte | 93 | 夹具旋转停止 |
| `MC_Door_Open` | byte | 94 | 安全门开门 |
| `MC_Door_Close` | byte | 95 | 安全门关门 |
| `MC_Door_Stop` | byte | 96 | 安全门停止 |
| `MC_SourceDetZ_Linkage_Enable` | bool | 101 | 射线源与探测器Z轴联动使能 |
| `MC_Axis_Reset` | byte | 102 | 轴复位命令 |
| `MC_VirtualJoystick_Enable` | bool | 111 | 虚拟摇杆使能 |
### 7.2 读取信号(ReadCommonDB31
| 信号名 | 类型 | 地址 | 说明 |
|--------|------|------|------|
| `MC_SourceZ_Pos` | single | 100 | 射线源Z实际位置(mm |
| `MC_DetZ_Pos` | single | 104 | 探测器Z实际位置(mm |
| `MC_StageX_Pos` | single | 108 | 载物台X实际位置(mm |
| `MC_StageY_Pos` | single | 112 | 载物台Y实际位置(mm |
| `MC_DetSwing_Angle` | single | 116 | 探测器摆动实际角度(度) |
| `MC_StageRot_Angle` | single | 120 | 载物台旋转实际角度(度) |
| `MC_FixRot_Angle` | single | 124 | 夹具旋转实际角度(度) |
| `MC_Door_Status` | byte | 128 | 安全门状态(0:未知,1:开门中,2:已开,3:关门中,4:已关,5:已锁定,6:故障) |
| `MC_Door_Interlock` | byte | 130 | 安全门联锁信号(0:无联锁,10:联锁有效) |
| `MC_Joystick_Active` | bool | 110 | 实体摇杆输入激活 |
| `MC_Axis_ResetDone` | bool | 112 | 轴复位完成 |
### 7.3 信号类型说明 | Signal Type Notes
- **single**32位浮点数(4字节)
- **byte**:8位无符号整数(1字节)
- **bool**:布尔值(1字节,0=false, 非0=true
---
## 8. 安全机制 | Safety Mechanisms
| 机制 | 说明 |
|------|------|
| 边界检查 | 目标位置超出 Min/Max 范围时拒绝移动 |
| 运动中防重入 | 轴处于 Moving 状态时拒绝新的移动命令 |
| 联锁检查 | 联锁信号有效时禁止开门 |
| 禁用轴检查 | Enabled=false 的旋转轴拒绝所有命令 |
| Homing 中拒绝 Jog | 回零过程中不允许 Jog 操作 |
| 多轴原子性检查 | MoveAllToTarget 任意轴越界则全部拒绝 |
| 轮询异常不中断 | 轮询中 PLC 异常被捕获,不影响下一轮 |
| UI 异常保护 | ViewModel 命令通过 SafeRun 包裹,PLC 异常弹窗而非崩溃 |
| PLC 断连安全 | PLC 断开时自动停止所有 Jog 并禁用摇杆,重连后需用户手动恢复 |
| 使能/联动回滚 | 使能切换或联动设置写入 PLC 失败时自动回滚 UI 状态 |
---
## 9. 轮询机制 | Polling Mechanism
`MotionControlService` 使用 `System.Threading.Timer` 以配置的 `PollingInterval`(默认 100ms)周期执行:
1. 检查 PLC 连接状态(通过 `IPlcService.IsConnected`),未连接时跳过
2. 缓存所有轴、门状态和联锁状态的旧值
3. 调用 `IMotionSystem.UpdateAllStatus()` 从 PLC 读取最新状态
4. 重新计算几何参数,发布 `GeometryUpdatedEvent`
5. 检测状态变化,发布 `AxisStatusChangedEvent``DoorStatusChangedEvent``DoorInterlockChangedEvent`
6. 轴进入 Error/Alarm 时额外发布 `MotionErrorEvent`
7. 异常捕获并记录 Error 日志,不中断轮询
通过 `StartPolling()` / `StopPolling()` 控制轮询生命周期。
---
**最后更新 | Last Updated**: 2026-05-11