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

18 KiB
Raw Blame History

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
    └── 依赖注入 → ISignalDataServiceXP.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 布局设计(宽 350pxUserControl

┌─────────────────────────────────────┐  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 + MM = 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() 等待归零
可选轴隐藏 StageRotationFixtureRotation 通过配置 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 多语言资源文件