# 运动控制模块集成指南 | 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. 获取轴状态和位置 | 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(); } } ``` --- ## 4. 几何计算 | 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); } } ``` --- ## 5. 事件订阅 | 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); } } ``` --- ## 6. PLC 信号定义 | PLC Signal Definitions 信号名称硬编码在 `MotionSignalNames.cs` 中,信号地址定义在 `PlcAddrDfn.xml`。 每个轴包含以下信号(以 SourceZ 为例): | 信号名 | 方向 | 类型 | 说明 | |--------|------|------|------| | `MC_SourceZ_Pos` | Read | single | 实际位置 | | `MC_SourceZ_Target` | Write | single | 目标位置 | | `MC_SourceZ_Speed` | Write | single | 运动速度 | | `MC_SourceZ_JogPos` | Write | byte | 正向 Jog | | `MC_SourceZ_JogNeg` | Write | byte | 反向 Jog | | `MC_SourceZ_Home` | Write | byte | 回零 | | `MC_SourceZ_Stop` | Write | byte | 停止 | 安全门信号: | 信号名 | 方向 | 说明 | |--------|------|------| | `MC_Door_Open` | Write | 开门 | | `MC_Door_Close` | Write | 关门 | | `MC_Door_Stop` | Write | 停门 | | `MC_Door_Status` | Read | 门状态(int: 0-6) | | `MC_Door_Interlock` | Read | 联锁信号 | --- ## 7. 安全机制 | Safety Mechanisms | 机制 | 说明 | |------|------| | 边界检查 | 目标位置超出 Min/Max 范围时拒绝移动 | | 运动中防重入 | 轴处于 Moving 状态时拒绝新的移动命令 | | 联锁检查 | 联锁信号有效时禁止开门 | | 禁用轴检查 | Enabled=false 的旋转轴拒绝所有命令 | | Homing 中拒绝 Jog | 回零过程中不允许 Jog 操作 | | 多轴原子性检查 | MoveAllToTarget 任意轴越界则全部拒绝 | | 轮询异常不中断 | 轮询中 PLC 异常被捕获,不影响下一轮 | | UI 异常保护 | ViewModel 命令通过 SafeRun 包裹,PLC 异常弹窗而非崩溃 | --- ## 8. 轮询机制 | 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-04-14