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

323 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 运动控制模块集成指南 | 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. 获取轴状态和位置 | 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;
// ...
}
}
}
```
---
## 3. 下发运动控制命令 | 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;
public YourController(IMotionControlService motionControlService)
{
_mc = motionControlService;
}
// 单轴移动 | 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(); // 所有已启用轴回零
}
// 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();
}
}
```
---
## 4. 几何计算 | 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);
}
}
```
---
## 5. 事件订阅 | 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);
}
}
```
---
## 6. PLC 信号定义 | PLC Signal Definitions
信号名称硬编码在 `MotionSignalNames.cs` 中,信号地址定义在 `PlcAddrDfn.xml`
每个轴包含以下信号(以 SourceZ 为例):
| 信号名 | 方向 | 类型 | 说明 |
|--------|------|------|------|
| `MC_SourceZ_Pos` | Read | single | 实际位置 |
| `MC_SourceZ_Target` | Write | single | 目标位置 |
| `MC_SourceZ_Speed` | Write | single | 运动速度 |
| `MC_SourceZ_JogPos` | Write | byte | 正向 Jog |
| `MC_SourceZ_JogNeg` | Write | byte | 反向 Jog |
| `MC_SourceZ_Home` | Write | byte | 回零 |
| `MC_SourceZ_Stop` | Write | byte | 停止 |
安全门信号:
| 信号名 | 方向 | 说明 |
|--------|------|------|
| `MC_Door_Open` | Write | 开门 |
| `MC_Door_Close` | Write | 关门 |
| `MC_Door_Stop` | Write | 停门 |
| `MC_Door_Status` | Read | 门状态(int: 0-6 |
| `MC_Door_Interlock` | Read | 联锁信号 |
---
## 7. 安全机制 | Safety Mechanisms
| 机制 | 说明 |
|------|------|
| 边界检查 | 目标位置超出 Min/Max 范围时拒绝移动 |
| 运动中防重入 | 轴处于 Moving 状态时拒绝新的移动命令 |
| 联锁检查 | 联锁信号有效时禁止开门 |
| 禁用轴检查 | Enabled=false 的旋转轴拒绝所有命令 |
| Homing 中拒绝 Jog | 回零过程中不允许 Jog 操作 |
| 多轴原子性检查 | MoveAllToTarget 任意轴越界则全部拒绝 |
| 轮询异常不中断 | 轮询中 PLC 异常被捕获,不影响下一轮 |
| UI 异常保护 | ViewModel 命令通过 SafeRun 包裹,PLC 异常弹窗而非崩溃 |
---
## 8. 轮询机制 | 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()` 控制轮询生命周期。
---
**最后更新 | Last Updated**: 2026-04-14