# 运动控制模块集成指南 | 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 | 摇杆使能开关(默认 true) | | `SwapMouseButtons` | bool | 摇杆左右键功能切换(默认 false) | | `SZDZLock` | bool | 射线源/探测器Z轴锁定移动(默认 false) | | `HasSavedPositions` | bool | 是否有已保存的位置快照 | **交互方式 | Interaction Modes** 1. **手动输入位置** - 直接编辑 RadNumericUpDown 控件 - Enter 键确认并移动轴 - Escape 键取消修改,恢复原值 - 上下箭头键步进移动(±0.1) 2. **单轴摇杆(SourceZ/DetectorZ)** - 左键拖拽:Y轴方向控制 SourceZ Jog(正/反向由拖拽方向决定) - 右键拖拽:Y轴方向控制 DetectorZ Jog(正/反向由拖拽方向决定) - 当 SZDZLock=true 时,左键同时控制 SourceZ + DetectorZ,右键同理 - 松开鼠标:停止 Jog 3. **双轴摇杆(StageX/Y + Rotation/Swing)** - 默认模式(SwapMouseButtons=false): - 左键拖拽:X轴→StageX Jog,Y轴→StageY Jog - 右键拖拽:X轴→DetectorSwing Jog,Y轴→StageRotation/FixtureRotation Jog - 切换模式(SwapMouseButtons=true):左右键功能互换 - 松开鼠标:停止所有关联轴 Jog 4. **快捷按钮** - **使能开关**:启用/禁用虚拟摇杆控制(同步写入 PLC 信号) - **摇杆模式**:切换左右键功能映射 - **SZDZ锁定**:锁定 SourceZ/DetectorZ 同步移动(同步写入 PLC 联动使能信号) - **保存位置**:保存当前所有轴位置到快照(需 PLC 已连接) - **恢复位置**:恢复到上次保存的位置快照并发送移动命令 --- ## 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; // ... } } } ``` --- ## 4. 下发运动控制命令 | 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; private readonly IMotionSystem _motionSystem; public YourController(IMotionControlService motionControlService, IMotionSystem motionSystem) { _mc = motionControlService; _motionSystem = motionSystem; } // 单轴移动 | 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(); // 所有已启用轴机械回零(驱动轴物理运动到机械零点,建立坐标系) } // 移动到安全点 | Move to safe position // 将所有轴移动到用户在 App.config 中配置的安全点位置坐标,用于设备维护、换件等场景避免碰撞。 // 注意:与 HomeAll()(机械回零)不同,MoveToSafePosition 是移动到用户配置的坐标值。 // Note: Different from HomeAll() (mechanical homing). MoveToSafePosition moves to user-configured safe coordinates. public void MoveToSafe() { MotionResult result = _mc.MoveToSafePosition(); // 所有轴移动到配置的安全点 } // 坐标系补偿 | Coordinate compensation // 主框架坐标匹配得到像素偏移 (dX, dY) 后,一步完成"记录补偿 + 修正移动"(默认覆盖模式) // After coordinate matching, record compensation and perform corrective move in one step (Replace by default) public void ApplyCompensation(double dxPixels, double dyPixels) { // 默认覆盖模式;同一位置迭代修正残差时用 CompensationMode.Accumulate MotionResult result = _mc.ApplyPixelOffsetCompensation(dxPixels, dyPixels); // 仅记录补偿、不移动(预览/仅计算) _mc.RecordPixelOffsetCompensation(dxPixels, dyPixels); // 查询当前补偿值(mm) var (compX, compY) = _mc.GetStageCompensation(); // 换工件/维护时清零补偿(HomeAll 回零不会清补偿) _mc.ResetStageCompensation(); } // 定点移动并叠加补偿(仅 StageX/StageY 生效,补偿后仍过边界校验) // Move to target with compensation (only StageX/StageY, boundary check still applies) public void MoveWithCompensation() { _mc.MoveToTarget(AxisId.StageX, 100.0, applyCompensation: true); var targets = new Dictionary { { AxisId.StageX, 100.0 }, { AxisId.StageY, 50.0 } }; _mc.MoveAllToTarget(targets, applyCompensation: true); } // 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(); } // 轴复位(清除故障)| Axis reset (clear fault) // 注意:Reset 与 Home 是完全不同的操作,不要混淆! // - Home(机械回零):驱动轴物理运动到机械零点,通过零点开关确认位置,建立坐标系。轴必须处于正常状态才能 Home。 // - Reset(轴复位):清除故障/报警状态(Alarm/Error → Idle),不产生物理运动,轴位置不变。用于轴碰限位或驱动器报错后恢复。 // 正确顺序:轴报警时 → 先排除物理故障 → Reset(清除报警)→ Home(建立坐标系) // Note: Reset and Home are completely different operations! // - Home: drives axis physically to mechanical zero (via home switch), establishes coordinate system. Axis must be in normal state. // - Reset: clears alarm/fault status (Alarm/Error → Idle), no physical movement, position unchanged. Used after limit hit or drive error. // Correct sequence: axis alarm → resolve physical issue → Reset (clear alarm) → Home (establish coordinate system) public void AxisReset() { // 通过 IMotionSystem 获取轴复位实例 | Get axis reset instance via IMotionSystem IAxisReset axisReset = _motionSystem.AxisReset; MotionResult result = axisReset.Reset(); // 发送复位命令(写入 MC_Axis_Reset = 10) bool done = axisReset.IsResetDone; // 读取复位完成状态 } } ``` --- ## 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); // 实体摇杆激活状态变化 | Physical joystick active status changed eventAggregator.GetEvent() .Subscribe(isActive => { Console.WriteLine($"实体摇杆: {(isActive ? "激活" : "未激活")}"); }, ThreadOption.UIThread); // 几何反算结果填入请求(DebugWindow → MotionControlViewModel) // Geometry apply request (DebugWindow → MotionControlViewModel) eventAggregator.GetEvent() .Subscribe(data => { Console.WriteLine($"几何反算请求: SourceZ={data.SourceZTarget}, DetectorZ={data.DetectorZTarget}"); }, ThreadOption.UIThread); // 载物台坐标系补偿变化(记录/设置/清零时触发)| Stage compensation changed eventAggregator.GetEvent() .Subscribe(data => { Console.WriteLine($"补偿值: X={data.Xmm:F4}mm, Y={data.Ymm:F4}mm"); }, 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_Axis_Reset` | byte | 102 | 轴复位命令(清除所有轴故障/报警状态,0:缺省,10:触发复位。不产生物理运动,与各轴 Home 不同) | | `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 | 实体摇杆输入激活 | | `MC_Axis_ResetDone` | bool | 112 | 轴复位完成(Reset 操作完成后 PLC 置 true) | ### 7.3 信号类型说明 | Signal Type Notes - **single**:32位浮点数(4字节) - **byte**:8位无符号整数(1字节) - **bool**:布尔值(1字节,0=false, 非0=true) ### 7.4 Home(机械回零)与 Reset(轴复位)的区别 | Difference between Home and Reset | 对比项 | Home(机械回零) | Reset(轴复位) | |--------|-----------------|----------------| | PLC 信号 | 各轴独立信号(如 `MC_SourceZ_Home`) | 全局单一信号 `MC_Axis_Reset` | | 是否产生物理运动 | **是**,驱动轴运动到机械零点 | **否**,轴原地不动 | | 目的 | 建立坐标系(位置寄存器归零) | 清除故障/报警状态(Alarm → Idle) | | 前提条件 | 轴处于正常状态(Idle/Standstill) | 轴处于故障/报警状态 | | 执行结果 | 轴到达机械零位,位置 = 0 | 轴状态恢复正常,位置不变 | | 典型场景 | 设备开机上电后确认轴位置 | 轴碰限位开关或驱动器报错后恢复 | | 调用方式 | `IMotionControlService.HomeAll()` 或 `ILinearAxis.Home()` | `IAxisReset.Reset()` | **正确操作顺序**:轴报警时 → 排除物理故障 → `Reset`(清除报警)→ `Home`(建立坐标系) --- ## 8. 安全机制 | Safety Mechanisms | 机制 | 说明 | |------|------| | 边界检查 | 目标位置超出 Min/Max 范围时拒绝移动 | | 运动中防重入 | 轴处于 Moving 状态时拒绝新的移动命令 | | 联锁检查 | 联锁信号有效时禁止开门 | | 禁用轴检查 | Enabled=false 的旋转轴拒绝所有命令 | | Homing 中拒绝 Jog | 回零过程中不允许 Jog 操作 | | 多轴原子性检查 | MoveAllToTarget 任意轴越界则全部拒绝 | | 轮询异常不中断 | 轮询中 PLC 异常被捕获,不影响下一轮 | | UI 异常保护 | ViewModel 命令通过 SafeRun 包裹,PLC 异常弹窗而非崩溃 | | PLC 断连安全 | PLC 断开时自动停止所有 Jog 并禁用摇杆,重连后需用户手动恢复 | | 使能/联动回滚 | 使能切换或联动设置写入 PLC 失败时自动回滚 UI 状态 | --- ## 9. 安全点配置 | Safe Position Configuration 每个轴可以配置一个安全点位置(`SafePosition`),用于设备维护、换件时将轴移动到安全位置避免碰撞。 **App.config 配置示例 | App.config Configuration Example:** ```xml ``` **调用方式 | Usage:** ```csharp MotionResult result = _motionControlService.MoveToSafePosition(); ``` **注意 | Note:** - 安全点坐标必须在轴的 Min/Max 范围内,否则会被边界检查拒绝 - 与 `HomeAll()`(机械回零)不同,`MoveToSafePosition()` 是移动到用户配置的坐标,不是机械零点 - 禁用的旋转轴会被自动跳过 --- ## 10. 轮询机制 | 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()` 控制轮询生命周期。 --- ## 11. 坐标系补偿 | Coordinate Compensation 用于将主框架坐标匹配得到的图像像素偏移换算为载物台物理补偿,并让后续定点运动自动叠加补偿,保持工件对齐。 ### 11.1 补偿模型 - 补偿值以物理量 mm 存储(Stage 坐标系,放大比无关),初始 `(0,0)` - 仅作用于 `StageX / StageY`,其余轴不补偿 - `HomeAll()` 回零**不会清零补偿**;仅 `ResetStageCompensation()` 显式清零 ### 11.2 记录模式(CompensationMode) | 模式 | 语义 | 适用场景 | |------|------|----------| | `Replace`(默认)| `comp = 本次换算值` | 每次从同一名义参考位置测量、每位置只记录一次 | | `Accumulate` | `comp += 本次换算值` | 在同一位置迭代修正残差 | > `ApplyPixelOffsetCompensation` 的移动为相对移动。覆盖模式假设"同参考位置、每位置一次";同位置迭代修正请用 `Accumulate`,否则物理移动会重复叠加。 ### 11.3 接口一览 | 方法 | 说明 | |------|------| | `ApplyPixelOffsetCompensation(dx, dy, mode = Replace)` | 记录补偿 + 执行相对修正移动 | | `RecordPixelOffsetCompensation(dx, dy, mode = Replace)` | 仅记录补偿,不移动 | | `GetStageCompensation()` | 获取当前补偿值 `(Xmm, Ymm)` | | `SetStageCompensation(xMm, yMm)` | 直接以 mm 设置补偿值 | | `ResetStageCompensation()` | 清零补偿(换工件/维护时调用) | | `MoveToTarget(..., applyCompensation: true)` | 单轴定点移动叠加补偿(仅 StageX/StageY) | | `MoveAllToTarget(targets, applyCompensation: true)` | 多轴定点移动叠加补偿(仅 StageX/StageY) | ### 11.4 调用示例 ```csharp // 主框架坐标匹配后:记录补偿并修正到位(默认覆盖) _mc.ApplyPixelOffsetCompensation(dxPixels, dyPixels); // 后续定点运动叠加补偿 _mc.MoveAllToTarget(new Dictionary { { AxisId.StageX, 100 }, { AxisId.StageY, 50 } }, applyCompensation: true); // 换工件清零 _mc.ResetStageCompensation(); ``` 补偿值变化时发布 `StageCompensationChangedEvent`(见第 6 节)。补偿功能复用现有几何配置(`DetectorPixelSizeX/Y`、`PixelDirectionX/Y`),无需新增 App.config 参数。 --- **最后更新 | Last Updated**: 2026-07-28