# 运动控制模块集成指南 | 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(); } ``` 模块启动时自动完成: - 从 App.config 加载配置(轴范围、几何原点、轮询周期) - 注册 `IMotionSystem`、`IMotionControlService`、`GeometryCalculator` 为单例 - 注册多语言资源到 Fallback Chain - 启动 PLC 状态轮询(100ms 周期) --- ## 2. 界面集成 | UI Integration ### AxisControlView 控件使用 | AxisControlView Usage `AxisControlView` 是运动控制模块的核心用户控件,提供完整的轴控制和调试功能。 **XAML 引用 | XAML Reference** ```xml ``` **自动 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 | 摇杆使能开关(默认 false) | | `SwapMouseButtons` | bool | 摇杆左右键功能切换(默认 false) | | `SZDZLock` | bool | 射线源/探测器Z轴锁定移动(默认 false) | | `HasSavedPositions` | bool | 是否有已保存的位置快照 | **交互方式 | Interaction Modes** 1. **手动输入位置** - 直接编辑 RadNumericUpDown 控件 - Enter 键确认并移动轴 - Escape 键取消修改,恢复原值 2. **单轴摇杆(SourceZ/DetectorZ)** - 左键拖拽:正向 Jog - 右键拖拽:反向 Jog - 松开鼠标:停止 Jog 3. **双轴摇杆(StageX/Y + Rotation)** - 默认模式(SwapMouseButtons=false): - 左键拖拽:控制 StageX/Y(X轴=左右,Y轴=上下) - 右键拖拽:控制 StageRotation(X轴=旋转) - 切换模式(SwapMouseButtons=true): - 左键拖拽:控制 StageRotation(X轴=旋转) - 右键拖拽:控制 DetectorSwing(X轴=摆动,Y轴=摆动) 4. **快捷按钮** - **使能开关**:启用/禁用摇杆控制(图标切换) - **摇杆模式**:切换左右键功能(图标切换) - **SZDZ锁定**:锁定 SourceZ/DetectorZ 同步移动(图标切换) - **保存位置**:保存当前所有轴位置到快照 - **恢复位置**:恢复到上次保存的位置快照 --- ## 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; // ... } } } ``` --- ## 3. 下发运动控制命令 | 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; public YourController(IMotionControlService motionControlService) { _mc = motionControlService; } // 单轴移动 | 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.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(); } } ``` --- ## 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() .Subscribe(data => { Console.WriteLine($"轴 {data.AxisId} 状态变为 {data.Status}"); }, ThreadOption.UIThread); // 门状态变化 | Door status changed eventAggregator.GetEvent() .Subscribe(status => { Console.WriteLine($"门状态: {status}"); }, ThreadOption.UIThread); // 门联锁状态变化 | Door interlock status changed eventAggregator.GetEvent() .Subscribe(isInterlocked => { Console.WriteLine($"联锁状态: {(isInterlocked ? "已联锁" : "未联锁")}"); }, ThreadOption.UIThread); // 几何参数更新(每轮询周期触发)| Geometry updated (every polling cycle) eventAggregator.GetEvent() .Subscribe(data => { Console.WriteLine($"FOD={data.FOD:F2}, FDD={data.FDD:F2}, M={data.Magnification:F3}"); }, ThreadOption.UIThread); // 运动错误(轴进入 Error/Alarm)| Motion error eventAggregator.GetEvent() .Subscribe(data => { Console.WriteLine($"运动错误: 轴 {data.AxisId} - {data.ErrorMessage}"); }, ThreadOption.UIThread); } } ``` --- ## 7. PLC 信号定义 | PLC Signal Definitions 信号名称硬编码在 `MotionSignalNames.cs` 中,信号地址定义在 `PlcAddrDfn.xml`(位于 `XplorePlane/bin/Debug/net8.0-windows/win-x64/`)。 ### 7.1 写入信号(WriteCommon,DB31) | 信号名 | 类型 | 地址 | 说明 | |--------|------|------|------| | `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_VirtualJoystick_Enable` | bool | 111 | 虚拟摇杆使能 | ### 7.2 读取信号(ReadCommon,DB31) | 信号名 | 类型 | 地址 | 说明 | |--------|------|------|------| | `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 | 实体摇杆输入激活 | ### 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 异常弹窗而非崩溃 | --- ## 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-08