41e056f85c
[XP.Hardware.MotionControl] README 文档目录树补齐全部文档条目并加入 CoordinateCompensation.md
630 lines
27 KiB
Markdown
630 lines
27 KiB
Markdown
# 运动控制模块集成指南 | 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<MotionControlModule>();
|
||
}
|
||
```
|
||
|
||
模块启动时自动完成:
|
||
- 从 App.config 加载配置(轴范围、几何原点、轮询周期)
|
||
- 注册 `IMotionSystem`、`IMotionControlService`、`GeometryCalculator` 为单例
|
||
- 注册多语言资源到 Fallback Chain
|
||
- 启动 PLC 状态轮询(100ms 周期)
|
||
|
||
---
|
||
|
||
## 2. 界面集成 | UI Integration
|
||
|
||
### AxisControlView 控件使用 | AxisControlView Usage
|
||
|
||
`AxisControlView` 是运动控制模块的核心用户控件,提供完整的轴控制和调试功能。
|
||
|
||
**XAML 引用 | XAML Reference**
|
||
|
||
```xml
|
||
<UserControl x:Class="YourView"
|
||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||
xmlns:motion="clr-namespace:XP.Hardware.MotionControl.Views;assembly=XP.Hardware.MotionControl">
|
||
|
||
<Grid>
|
||
<!-- 基本使用 | Basic usage -->
|
||
<motion:AxisControlView />
|
||
|
||
<!-- 设置最小宽度 | Set minimum width -->
|
||
<motion:AxisControlView MinWidth="400" />
|
||
</Grid>
|
||
</UserControl>
|
||
```
|
||
|
||
**自动 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, double>
|
||
{
|
||
{ 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, double>
|
||
{
|
||
{ 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<AxisStatusChangedEvent>()
|
||
.Subscribe(data =>
|
||
{
|
||
Console.WriteLine($"轴 {data.AxisId} 状态变为 {data.Status}");
|
||
}, ThreadOption.UIThread);
|
||
|
||
// 门状态变化 | Door status changed
|
||
eventAggregator.GetEvent<DoorStatusChangedEvent>()
|
||
.Subscribe(status =>
|
||
{
|
||
Console.WriteLine($"门状态: {status}");
|
||
}, ThreadOption.UIThread);
|
||
|
||
// 门联锁状态变化 | Door interlock status changed
|
||
eventAggregator.GetEvent<DoorInterlockChangedEvent>()
|
||
.Subscribe(isInterlocked =>
|
||
{
|
||
Console.WriteLine($"联锁状态: {(isInterlocked ? "已联锁" : "未联锁")}");
|
||
}, ThreadOption.UIThread);
|
||
|
||
// 几何参数更新(每轮询周期触发)| Geometry updated (every polling cycle)
|
||
eventAggregator.GetEvent<GeometryUpdatedEvent>()
|
||
.Subscribe(data =>
|
||
{
|
||
Console.WriteLine($"FOD={data.FOD:F2}, FDD={data.FDD:F2}, M={data.Magnification:F3}");
|
||
}, ThreadOption.UIThread);
|
||
|
||
// 运动错误(轴进入 Error/Alarm)| Motion error
|
||
eventAggregator.GetEvent<MotionErrorEvent>()
|
||
.Subscribe(data =>
|
||
{
|
||
Console.WriteLine($"运动错误: 轴 {data.AxisId} - {data.ErrorMessage}");
|
||
}, ThreadOption.UIThread);
|
||
|
||
// 实体摇杆激活状态变化 | Physical joystick active status changed
|
||
eventAggregator.GetEvent<JoystickActiveEvent>()
|
||
.Subscribe(isActive =>
|
||
{
|
||
Console.WriteLine($"实体摇杆: {(isActive ? "激活" : "未激活")}");
|
||
}, ThreadOption.UIThread);
|
||
|
||
// 几何反算结果填入请求(DebugWindow → MotionControlViewModel)
|
||
// Geometry apply request (DebugWindow → MotionControlViewModel)
|
||
eventAggregator.GetEvent<GeometryApplyRequestEvent>()
|
||
.Subscribe(data =>
|
||
{
|
||
Console.WriteLine($"几何反算请求: SourceZ={data.SourceZTarget}, DetectorZ={data.DetectorZTarget}");
|
||
}, ThreadOption.UIThread);
|
||
|
||
// 载物台坐标系补偿变化(记录/设置/清零时触发)| Stage compensation changed
|
||
eventAggregator.GetEvent<StageCompensationChangedEvent>()
|
||
.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
|
||
<!-- 直线轴安全点(mm)| Linear axis safe position (mm) -->
|
||
<add key="MotionControl:SourceZ:SafePosition" value="0" />
|
||
<add key="MotionControl:DetectorZ:SafePosition" value="600" />
|
||
<add key="MotionControl:StageX:SafePosition" value="0" />
|
||
<add key="MotionControl:StageY:SafePosition" value="0" />
|
||
<!-- 旋转轴安全点(度)| Rotary axis safe position (degrees) -->
|
||
<add key="MotionControl:DetectorSwing:SafePosition" value="0" />
|
||
<add key="MotionControl:StageRotation:SafePosition" value="0" />
|
||
<add key="MotionControl:FixtureRotation:SafePosition" value="0" />
|
||
```
|
||
|
||
**调用方式 | 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, double>
|
||
{
|
||
{ AxisId.StageX, 100 }, { AxisId.StageY, 50 }
|
||
}, applyCompensation: true);
|
||
|
||
// 换工件清零
|
||
_mc.ResetStageCompensation();
|
||
```
|
||
|
||
补偿值变化时发布 `StageCompensationChangedEvent`(见第 6 节)。补偿功能复用现有几何配置(`DetectorPixelSizeX/Y`、`PixelDirectionX/Y`),无需新增 App.config 参数。
|
||
|
||
---
|
||
|
||
**最后更新 | Last Updated**: 2026-07-28
|