18 KiB
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
// 正算:由轴位置计算几何参数
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<float>("MC_SourceZ_Pos")
└── 写:EnqueueWrite("MC_SourceZ_Target", value)
6. 配置文件设计(App.config)
<MotionControl>
<!-- 直线轴配置(单位:mm) -->
<SourceZ Min="0" Max="500" Origin="0"
PlcSignalRead="MC_SourceZ_Pos" PlcSignalWrite="MC_SourceZ_Target"
PlcSignalJogPos="MC_SourceZ_JogPos" PlcSignalJogNeg="MC_SourceZ_JogNeg"
PlcSignalHome="MC_SourceZ_Home" PlcSignalStop="MC_SourceZ_Stop" />
<DetectorZ Min="0" Max="600" Origin="0"
PlcSignalRead="MC_DetZ_Pos" PlcSignalWrite="MC_DetZ_Target"
PlcSignalJogPos="MC_DetZ_JogPos" PlcSignalJogNeg="MC_DetZ_JogNeg"
PlcSignalHome="MC_DetZ_Home" PlcSignalStop="MC_DetZ_Stop" />
<StageX Min="-150" Max="150" Origin="0"
PlcSignalRead="MC_StageX_Pos" PlcSignalWrite="MC_StageX_Target"
PlcSignalJogPos="MC_StageX_JogPos" PlcSignalJogNeg="MC_StageX_JogNeg"
PlcSignalHome="MC_StageX_Home" PlcSignalStop="MC_StageX_Stop" />
<StageY Min="-150" Max="150" Origin="0"
PlcSignalRead="MC_StageY_Pos" PlcSignalWrite="MC_StageY_Target"
PlcSignalJogPos="MC_StageY_JogPos" PlcSignalJogNeg="MC_StageY_JogNeg"
PlcSignalHome="MC_StageY_Home" PlcSignalStop="MC_StageY_Stop" />
<!-- 旋转轴配置(单位:度) -->
<DetectorSwing Min="-45" Max="45" Origin="0" Enabled="true"
PlcSignalRead="MC_DetSwing_Angle" PlcSignalWrite="MC_DetSwing_Target"
PlcSignalJogPos="MC_DetSwing_JogPos" PlcSignalJogNeg="MC_DetSwing_JogNeg"
PlcSignalHome="MC_DetSwing_Home" PlcSignalStop="MC_DetSwing_Stop" />
<StageRotation Min="-360" Max="360" Origin="0" Enabled="true"
PlcSignalRead="MC_StageRot_Angle" PlcSignalWrite="MC_StageRot_Target"
PlcSignalJogPos="MC_StageRot_JogPos" PlcSignalJogNeg="MC_StageRot_JogNeg"
PlcSignalHome="MC_StageRot_Home" PlcSignalStop="MC_StageRot_Stop" />
<FixtureRotation Min="-90" Max="90" Origin="0" Enabled="false"
PlcSignalRead="MC_FixRot_Angle" PlcSignalWrite="MC_FixRot_Target"
PlcSignalJogPos="MC_FixRot_JogPos" PlcSignalJogNeg="MC_FixRot_JogNeg"
PlcSignalHome="MC_FixRot_Home" PlcSignalStop="MC_FixRot_Stop" />
<!-- 安全门配置 -->
<SafetyDoor PlcSignalOpen="MC_Door_Open"
PlcSignalClose="MC_Door_Close"
PlcSignalStop="MC_Door_Stop"
PlcSignalStatus="MC_Door_Status"
PlcSignalInterlock="MC_Door_Interlock" />
<!-- 几何原点(绝对坐标 mm,用于 FOD/FDD 计算) -->
<Geometry SourceZOrigin="0"
DetectorZOrigin="600"
StageRotationCenterZ="300" />
<!-- 轮询周期(ms) -->
<PollingInterval>100</PollingInterval>
<!-- 运动速度(%,0-100) -->
<DefaultVelocity>30</DefaultVelocity>
<JogVelocity>10</JogVelocity>
</MotionControl>
7. ViewModel 设计
7.1 属性分组
// 直线轴(每轴一组实际值 + 目标值)
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 命令列表
// 主操作命令
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 模块注册
// MotionControlModule.RegisterTypes()
containerRegistry.RegisterSingleton<MotionControlConfig>(); // 配置(单例)
containerRegistry.RegisterSingleton<IMotionSystem, PlcMotionSystem>(); // 系统管理(单例)
containerRegistry.RegisterSingleton<IMotionControlService, MotionControlService>(); // 服务(单例)
// 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 模块已有的事件定义
// XP.Hardware.RaySource/Abstractions/Events/RaySourceStatusChangedEvent.cs
// 该事件已存在,携带射线源当前状态
public class RaySourceStatusChangedEvent : PubSubEvent<RaySourceStatus> { }
第二步:MotionControlService 中注入并订阅
public class MotionControlService : IMotionControlService
{
private readonly IEventAggregator _eventAggregator;
private bool _isRaySourceOn = false; // 缓存射线源状态
public MotionControlService(IEventAggregator eventAggregator, ...)
{
_eventAggregator = eventAggregator;
// 订阅射线源状态变化事件
_eventAggregator
.GetEvent<RaySourceStatusChangedEvent>()
.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 | 多语言资源文件 |