323 lines
10 KiB
Markdown
323 lines
10 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. 获取轴状态和位置 | 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
|