将Feature/XP.Common和Feature/XP.Hardware分支合并至Develop/XP.forHardwareAndCommon,完善XPapp注册和相关硬件类库通用类库功能。
This commit is contained in:
@@ -0,0 +1,322 @@
|
||||
# 运动控制模块集成指南 | 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
|
||||
Reference in New Issue
Block a user