# XP.Hardware.MotionControl 运动控制模块设计规划 > 文档版本:v1.0 > 创建日期:2026-04-10 > 适用项目:XplorePlane 平面CT工业检测系统 --- ## 1. 背景与硬件描述 本模块用于控制平面CT系统中的所有运动轴、旋转轴及安全防护门。 ### 1.1 直线轴(4轴) | 轴标识 | 说明 | 运动方向 | |--------|------|----------| | 轴1 SourceZ | 射线源Z方向运动 | 上下 | | 轴2 DetectorZ | 探测器Z方向运动 | 上下 | | 轴3 StageX | 载物台X方向运动 | 前后 | | 轴4 StageY | 载物台Y方向运动 | 左右 | ### 1.2 旋转轴(3轴) | 轴标识 | 说明 | 特点 | |--------|------|------| | 旋转轴1 DetectorSwing | 探测器在XZ平面的旋转摆动 | 有限角度,旋转中心在射线源Z方向延长线上 | | 旋转轴2 StageRotation | 载物台围绕XY中心的Z方向旋转 | 可选配置 | | 旋转轴3 FixtureRotation | 旋转夹具在载物台XY平面上围绕X方向的旋转 | 可选配置 | ### 1.3 安全门 防护门,具备状态机控制与互锁信号联动。 ### 1.4 几何关系 ``` FOD(焦点到旋转中心距离)= |SourceZ绝对坐标 - 旋转中心绝对Z坐标| FDD(焦点到探测器距离) = |SourceZ绝对坐标 - DetectorZ绝对坐标| 放大倍率 M = FDD / FOD ``` 只有确定了射线源、探测器、载物台旋转中心的绝对坐标,才能准确计算 FOD 和 FDD,进而控制放大倍率。 --- ## 2. 目录结构 ``` XP.Hardware.MotionControl/ ├── Abstractions/ │ ├── ILinearAxis.cs # 直线轴策略接口 │ ├── IRotaryAxis.cs # 旋转轴策略接口 │ ├── ISafetyDoor.cs # 安全门策略接口 │ ├── IMotionSystem.cs # 顶层系统管理接口 │ ├── LinearAxisBase.cs # 直线轴抽象基类 │ ├── RotaryAxisBase.cs # 旋转轴抽象基类 │ ├── SafetyDoorBase.cs # 安全门抽象基类 │ ├── MotionResult.cs # 统一操作结果类型 │ └── Enums/ │ ├── AxisStatus.cs # 轴状态枚举 │ ├── DoorStatus.cs # 门状态枚举 │ └── AxisId.cs # 轴标识枚举 ├── Implementations/ │ ├── PlcLinearAxis.cs # 基于PLC的直线轴实现 │ ├── PlcRotaryAxis.cs # 基于PLC的旋转轴实现 │ └── PlcSafetyDoor.cs # 基于PLC的安全门实现 ├── Services/ │ ├── IMotionControlService.cs # 运动控制业务服务接口 │ ├── MotionControlService.cs # 服务实现(含轮询线程、几何计算) │ └── GeometryCalculator.cs # FOD/FDD/放大倍率计算器 ├── Config/ │ ├── MotionControlConfig.cs # 配置实体(轴范围、原点、PLC信号名) │ └── ConfigLoader.cs # 配置加载器 ├── ViewModels/ │ └── MotionControlViewModel.cs # 操作面板 ViewModel ├── Views/ │ └── MotionControlView.xaml # 操作面板 UserControl(宽350) ├── Module/ │ └── MotionControlModule.cs # Prism 模块注册 ├── Documents/ │ └── MotionControl_Design.md # 本文档 └── Resources/ ├── Resources.resx ├── Resources.zh-CN.resx └── Resources.en-US.resx ``` --- ## 3. 核心抽象层设计 ### 3.1 三类硬件抽象继承关系 ``` ILinearAxis IRotaryAxis ISafetyDoor │ │ │ LinearAxisBase RotaryAxisBase SafetyDoorBase │ │ │ PlcLinearAxis PlcRotaryAxis PlcSafetyDoor ``` ### 3.2 ILinearAxis 接口职责 - 位置读取(实际值) - 目标位置写入 - Jog 正向 / Jog 反向(按住运动,松开停止) - 回零 - 停止 - 限位状态读取(正限位 / 负限位) - 轴状态(Idle / Moving / Homing / Error / Alarm) ### 3.3 IRotaryAxis 接口职责 - 角度读取(实际值) - 目标角度写入 - Jog 正向 / Jog 反向 - 回零 - 停止 - 角度软限位(MinAngle / MaxAngle,来自配置) - 轴状态 ### 3.4 ISafetyDoor 接口职责 - 状态机读取(Unknown / Opening / Open / Closing / Closed / Locked / Error) - 开门指令 - 关门指令 - 互锁信号读取(是否允许开门) ### 3.5 IMotionSystem 顶层接口职责 - 按 `AxisId` 枚举索引各直线轴(SourceZ、DetectorZ、StageX、StageY) - 按 `RotaryAxisId` 枚举索引各旋转轴(DetectorSwing、StageRotation、FixtureRotation) - 持有安全门实例 - 协调运动:开门前联锁检查 - 几何计算委托给 `GeometryCalculator` --- ## 4. 服务层设计 ### 4.1 IMotionControlService 封装业务规则,供 ViewModel 调用: - `StartPolling()` / `StopPolling()` — 启停 PLC 数据轮询 - `MoveToTarget(AxisId, double)` — 移动指定轴到目标位置 - `MoveAllToTarget(targets)` — 多轴联动移动 - `StopAll()` — 停止所有轴 - `HomeAll()` — 全部回零 - `JogStart(AxisId, direction)` / `JogStop(AxisId)` — Jog 控制 - `OpenDoor()` / `CloseDoor()` / `StopDoor()` — 门控制(含联锁前置检查) - `ApplyGeometry(fod, fdd)` — 根据 FOD/FDD 反算并移动 - `GetCurrentGeometry()` — 获取当前 FOD/FDD/M ### 4.2 GeometryCalculator ```csharp // 正算:由轴位置计算几何参数 double CalcFOD(double sourceZAbs, double stageRotCenterZAbs) double CalcFDD(double sourceZAbs, double detectorZAbs) double CalcMagnification(double fdd, double fod) // 反算:由目标几何参数计算轴目标位置 (double sourceZTarget, double detectorZTarget) CalcAxisTargets(double targetFOD, double targetFDD) // 边界检查:目标值是否在轴 Min/Max 范围内 bool ValidateTargets(double sourceZTarget, double detectorZTarget) ``` 绝对坐标计算方式: ``` SourceZ绝对坐标 = SourceZ当前位置 + SourceZOrigin(配置) DetectorZ绝对坐标 = DetectorZ当前位置 + DetectorZOrigin(配置) 旋转中心绝对Z坐标 = StageRotationCenterZ(配置,固定值) ``` ### 4.3 轮询线程 在 `MotionControlService` 内部使用 `System.Threading.Timer`,周期约 100ms,仅在 PLC 已连接时激活。每次轮询通过 `ISignalDataService.GetValueByName()` 读取所有轴的实际位置和状态,更新到 ViewModel 的可绑定属性。 --- ## 5. 与 PLC 模块的依赖关系 MotionControl 通过依赖注入获取 `ISignalDataService`(来自 XP.Hardware.PLC),所有读写操作通过信号逻辑名称进行,不直接操作 PLC 地址。信号名称在配置文件中定义。 ``` XP.Hardware.MotionControl └── 依赖注入 → ISignalDataService(XP.Hardware.PLC) └── 读:GetValueByName("MC_SourceZ_Pos") └── 写:EnqueueWrite("MC_SourceZ_Target", value) ``` --- ## 6. 配置文件设计(App.config) ```xml 100 30 10 ``` --- ## 7. ViewModel 设计 ### 7.1 属性分组 ```csharp // 直线轴(每轴一组实际值 + 目标值) double SourceZActual / SourceZTarget double DetectorZActual / DetectorZTarget double StageXActual / StageXTarget double StageYActual / StageYTarget // 旋转轴(每轴一组,可选轴按配置控制 Visibility) double DetSwingActual / DetSwingTarget // 附带角度限制提示文本 double StageRotActual / StageRotTarget double FixtureRotActual / FixtureRotTarget // 几何信息(实时计算,只读显示) double FOD / FDD / Magnification // 几何反算输入 double TargetFOD / TargetFDD / TargetMagnification // 安全门 DoorStatus DoorStatus bool IsInterlocked // 状态 bool IsMoving bool IsPlcConnected // 调试模式(默认 false,折叠隐藏调试区域) bool IsDebugMode ``` ### 7.2 命令列表 ```csharp // 主操作命令 ICommand MoveCommand // 执行运动到各轴目标位置 ICommand StopCommand // 停止所有轴 ICommand HomeCommand // 全部回零 ICommand OpenDoorCommand ICommand CloseDoorCommand ICommand StopDoorCommand ICommand ApplyGeometryCommand // 根据 FOD/FDD/M 反算并移动 // 调试命令(Jog,按住触发,松开停止) ICommand JogPlusCommand // 参数:AxisId ICommand JogMinusCommand // 参数:AxisId ICommand JogStopCommand // 参数:AxisId ``` --- ## 8. View 布局设计(宽 350px,UserControl) ``` ┌─────────────────────────────────────┐ 350px │ [安全门状态] ● 已关闭 [互锁: 正常] │ ├─────────────────────────────────────┤ │ 直线轴 │ │ 源Z [实际: 123.4mm] [▲目标▼] │ │ 探Z [实际: 456.7mm] [▲目标▼] │ │ 台X [实际: 12.3mm] [▲目标▼] │ │ 台Y [实际: -5.6mm] [▲目标▼] │ ├─────────────────────────────────────┤ │ 旋转轴 │ │ 探摆 [实际: 15.0°] [▲目标▼] │ │ 限制: -45° ~ +45° │ │ 台转 [实际: 180.0°] [▲目标▼] │ │ 夹具 [实际: 0.0°] [▲目标▼] │ ├─────────────────────────────────────┤ │ 几何信息 │ │ FOD: 150.0mm FDD: 450.0mm M: 3.0│ │ [目标FOD▲▼] [目标FDD▲▼] [应用] │ ├─────────────────────────────────────┤ │ [开始运动] [停止运动] [回零] │ │ [开门] [关门] │ ├─────────────────────────────────────┤ │ ▼ 调试模式(默认折叠) │ │ 源Z [+Jog][-Jog] 探Z [+][-] │ │ 台X [+Jog][-Jog] 台Y [+][-] │ │ 探摆 [+Jog][-Jog] 台转[+][-] │ │ 夹具 [+Jog][-Jog] │ └─────────────────────────────────────┘ ``` --- ## 9. FOD/FDD 反算逻辑 用户输入目标 FOD 和 FDD(或放大倍率 M),系统自动计算各轴目标位置: ``` // 已知:StageRotationCenterZ(旋转中心绝对Z坐标,来自配置,固定值) // 输入 FOD + FDD: SourceZ_target = StageRotationCenterZ - FOD DetectorZ_target = SourceZ_target + FDD // 输入 FOD + M(M = FDD/FOD): FDD = M * FOD DetectorZ_target = SourceZ_target + FDD // 边界检查: if SourceZ_target < SourceZ.Min || SourceZ_target > SourceZ.Max → 拒绝并提示 if DetectorZ_target < DetectorZ.Min || DetectorZ_target > DetectorZ.Max → 拒绝并提示 ``` --- ## 10. 关键安全机制 参考旧项目 `ControlDoor.openDoor()` 和 `_130kvMove` 的实现经验: | 机制 | 说明 | 对标旧代码 | |------|------|-----------| | 运动中防重入 | `IsMoving` 标志位,运动中禁止再次触发运动命令 | `fdcRun` 标志位 | | 开门联锁检查 | 开门前通过 `IEventAggregator` 检查是否有扫描进行中 | `ControlDoor.openDoor()` 前置逻辑 | | 射线源联锁 | 开门前需确认射线源已关闭(暂不实现,预留扩展点,见下方说明) | `zeroXray2Correct()` 等待归零 | | 可选轴隐藏 | `StageRotation` 和 `FixtureRotation` 通过配置 `Enabled` 控制 UI 可见性 | 无对应,新增 | | Jog 安全 | Jog 操作通过 `MouseDown/Up` 事件控制,松开立即停止 | `radButton3_MouseDown/Up` | --- ## 11. Prism 模块注册 ```csharp // MotionControlModule.RegisterTypes() containerRegistry.RegisterSingleton(); // 配置(单例) containerRegistry.RegisterSingleton(); // 系统管理(单例) containerRegistry.RegisterSingleton(); // 服务(单例) // MotionControlModule.OnInitialized() // 注册多语言资源到 Fallback Chain // 启动 PLC 轮询(若 PLC 已连接) ``` --- --- ## 12. 关于射线源联锁的扩展说明(暂不实现) ### 12.1 什么是事件聚合器(EventAggregator) Prism 的 `IEventAggregator` 是一个**发布/订阅总线**,用于在模块之间传递消息,而不需要两个模块直接互相引用。 类比理解:就像一个广播电台,`RaySource` 模块是"电台"(发布者),`MotionControl` 模块是"收音机"(订阅者),两者只通过"频道"(事件类型)通信,彼此不知道对方的存在。 ``` XP.Hardware.RaySource XP.Hardware.MotionControl │ │ │ 发布 RaySourceStatusChangedEvent │ └──────────→ EventAggregator ──────→ 订阅并响应 ``` ### 12.2 如何订阅(当需要实现时) **第一步:RaySource 模块已有的事件定义** ```csharp // XP.Hardware.RaySource/Abstractions/Events/RaySourceStatusChangedEvent.cs // 该事件已存在,携带射线源当前状态 public class RaySourceStatusChangedEvent : PubSubEvent { } ``` **第二步:MotionControlService 中注入并订阅** ```csharp public class MotionControlService : IMotionControlService { private readonly IEventAggregator _eventAggregator; private bool _isRaySourceOn = false; // 缓存射线源状态 public MotionControlService(IEventAggregator eventAggregator, ...) { _eventAggregator = eventAggregator; // 订阅射线源状态变化事件 _eventAggregator .GetEvent() .Subscribe(OnRaySourceStatusChanged, ThreadOption.BackgroundThread); } private void OnRaySourceStatusChanged(RaySourceStatus status) { // 根据状态判断射线是否开启 _isRaySourceOn = (status == RaySourceStatus.XRayOn); } public async Task OpenDoor() { // 开门前检查射线源 if (_isRaySourceOn) { _logger.Warn("射线源未关闭,禁止开门 | Ray source is on, door open blocked"); return; } // ... 继续开门逻辑 } } ``` **第三步:注意事项** - 订阅时选择 `ThreadOption.BackgroundThread`,避免在 UI 线程处理硬件状态 - 模块卸载时需要取消订阅,防止内存泄漏:`GetEvent<...>().Unsubscribe(OnRaySourceStatusChanged)` - 两个模块之间**不需要互相引用**,只需要共同引用定义事件的程序集(或在各自模块内定义相同结构的事件) ### 12.3 预留扩展点 当需要实现射线源联锁时,只需在 `MotionControlService.OpenDoor()` 方法开头加入上述订阅逻辑即可,不需要修改任何其他代码。 --- ## 13. 事件定义(Prism EventAggregator) ``` Abstractions/Events/ ├── AxisStatusChangedEvent.cs # 轴状态变化事件(AxisId + AxisStatus) ├── DoorStatusChangedEvent.cs # 门状态变化事件(DoorStatus) ├── GeometryUpdatedEvent.cs # 几何参数更新事件(FOD + FDD + M) └── MotionErrorEvent.cs # 运动错误事件(AxisId + 错误信息) ``` --- ## 13. 实现优先级建议 | 优先级 | 内容 | |--------|------| | P0 | 配置实体、枚举定义、MotionResult | | P0 | ILinearAxis / IRotaryAxis / ISafetyDoor 接口 + 抽象基类 | | P1 | PlcLinearAxis / PlcRotaryAxis / PlcSafetyDoor 实现 | | P1 | GeometryCalculator | | P1 | IMotionControlService + MotionControlService(含轮询) | | P2 | MotionControlViewModel | | P2 | MotionControlView.xaml(宽350 UserControl) | | P3 | MotionControlModule 注册 | | P3 | 多语言资源文件 |