EtherCAT 工业架构
EtherCAT 工业架构:工业界的 "高铁通信系统"(大白话版)
核心一句话:EtherCAT 是专为工业控制设计的实时以太网总线,像高铁调度系统一样,用主站指挥 + 从站执行 + 飞传数据的方式,实现微秒级响应和亚微秒级同步,完美适配你的 X 光检测设备运动控制需求!
一、核心架构:主站+从站(绝对的"一言堂")
EtherCAT采用单主多从的严格主从架构,和你设备里的控制逻辑完全一致:
| 角色 | 定位 | 类比 | 你的X光设备对应 |
|---|---|---|---|
| 主站(Master) | 唯一指挥者,主动发指令 | 高铁调度中心 | 工控机/PLC(运行你的C# WPF程序) |
| 从站(Slave) | 被动执行者,实时响应 | 各节高铁车厢 | XY轴驱动器、X光控制器、传感器、IO模块 |
关键规则:只有主站能主动发数据帧,从站只能"边传边处理",不能主动说话------这是实时性的根本保障!
二、革命性技术:On-the-Fly(飞读飞写)------为什么比传统以太网快100倍?
传统以太网:快递式"存储-转发"
- 主站发包裹给从站1 → 从站1拆开看不是自己的 → 重新打包发给从站2
- 从站2拆开看不是自己的 → 重新打包发给从站3...
- 最后一个从站再原路返回
问题:每站都要拆包打包,延迟大、周期长(毫秒级)
EtherCAT:高铁式"直通车"(On-the-Fly)
- 主站发一个超大信封(包含所有从站的指令和数据空间)
- 信封经过从站1:不拆封,直接从窗口拿走自己的指令,同时把数据塞进自己的位置
- 信封继续高速通过从站2、3... 每个从站都"飞读飞写"
- 最后一个从站把信封原路返回给主站
效果 :100个从站的循环周期仅需30μs,抖动<1μs,单帧可控制上百轴运动!
三、硬件组成:极简且高效
1. 主站硬件
- 普通以太网口(无需专用网卡,x86/ARM都能跑)
- 主站软件栈(倍福提供免费库,或第三方实现)
- 负责:网络管理、数据帧生成、分布式时钟同步
2. 从站硬件(核心是ESC芯片)
- ESC(EtherCAT Slave Controller):专用芯片(如ET1100/ET1200),相当于从站的"大脑"
- 不需要操作系统,直接硬件级处理数据
- 负责:实时读写数据、响应主站指令、同步时钟
3. 拓扑结构(超灵活,不用交换机)
- 线型(最常用):从站1→从站2→从站3→...→主站
- 树型/星型:支持分支扩展
- 环形:冗余备份,断一个节点不影响整体
四、关键特性:工业控制的"安全气囊"
1. 分布式时钟(DC):所有设备"分秒不差"
- 主站作为时间基准,从站自动校准
- 同步精度**≤1μs**,保证XY轴同时启动/停止,X光曝光与运动完美同步
- 你的设备双扫描模式(点位/连续)全靠这个!
2. 实时性与确定性:说100μs到就100μs到
- 通信周期可低至100μs以下,抖动<1μs
- 不受网络负载影响,再忙也不耽误控制指令
3. 诊断能力:哪里坏了一眼看穿
- 主站可实时监控每个从站状态
- 从站故障自动上报,定位精确到节点
- 你的
EmergencyStop()急停后,能快速诊断是轴还是X光模块问题
五、和你的X光检测设备完美适配
典型控制流程(结合你的代码)
- 主站(工控机)运行你的C#程序,点击"开始扫描"
- 主站生成EtherCAT数据帧:包含XY轴移动指令、X光曝光参数
- 数据帧"飞传"到各从站:
- XY轴驱动器:读取移动指令,执行
_scanAxis运动 - X光控制器:读取曝光参数,执行
StartXray()
- XY轴驱动器:读取移动指令,执行
- 从站实时反馈:轴位置、X光状态、图像数据
- 主站更新界面状态(
Status)、显示图像 - 点击急停 :
- 主站发送急停指令(通过EtherCAT)
- XY轴驱动器执行
EmergencyStop(),瞬间刹死 - X光控制器执行
CloseXray(),立刻关闭 - 主站更新
Status = STATUS_FAULT,记录日志
六、极简总结:EtherCAT=工业实时通信的"天花板"
| 优势 | 通俗解释 | 对你的价值 |
|---|---|---|
| 快 | 微秒级响应,比传统以太网快100倍 | XY轴高速精准扫描,X光曝光无延迟 |
| 准 | 亚微秒级同步,所有设备时间一致 | 双扫描模式完美执行,图像无畸变 |
| 稳 | 确定性通信,不受网络影响 | 长时间运行不卡顿,设备更可靠 |
| 省 | 不用交换机,布线简单 | 降低设备成本,减少故障点 |
| 强 | 支持千个从站,扩展灵活 | 未来可加视觉系统、多轴联动 |
七、和普通停止/急停的关联
代码里的Stop()和EmergencyStop():
- 都是通过主站发送EtherCAT指令给从站
- 普通停止:主站发送"平稳停止"指令,从站减速停车
- 紧急停止:主站发送"强制刹死"指令,从站瞬间断电抱死,优先级最高
一句话记住:EtherCAT就是工业界的"高铁通信系统",主站是调度中心,从站是车厢,数据像乘客一样"飞"过每个车厢,又快又准又稳!
软主站 + 线型拓扑:主站→伺服→IO→X 光→探测器
核心一句话 :用普通工控机当"大脑"(软主站),按主站→伺服→IO→X光→探测器 顺序串成"糖葫芦"(线型拓扑),不用交换机,靠EtherCAT"飞传数据"实现微秒级同步,完美匹配半导体X光检测设备!
一、架构总览:"一根线串到底"的极简连接
1. 拓扑图(直接对应你的设备)
[工控机(软主站)] ------网线------ [XY伺服驱动器] ------网线------ [IO模块] ------网线------ [X光控制器] ------网线------ [探测器]
- 线型拓扑 :像串糖葫芦一样,每个设备只有一个入线和一个出线,最后一个设备可悬空或接终端电阻
- 无需交换机:数据帧"飞"过每个设备,不拆包不转发,延迟<30μs,比传统以太网快100倍!
2. 角色定位(和代码一一对应)
| 设备 | 角色 | 核心作用 | 你的代码关联 |
|---|---|---|---|
| 工控机(软主站) | 总指挥 | 运行C# WPF程序,发指令、收数据、同步时钟 | _scanAxis运动控制、StartXray()曝光、EmergencyStop()急停 |
| XY伺服驱动器 | 运动执行器 | 接收位置指令,驱动电机精准移动 | _scanAxis.EmergencyStop()瞬间刹死XY轴 |
| IO模块 | 信号中转站 | 处理急停按钮、限位开关、指示灯等数字信号 | StopAxisStatusRefresh()停止状态监控 |
| X光控制器 | 射线开关 | 控制X光管开启/关闭、调节剂量 | CloseXray()立刻关闭X光 |
| 探测器 | 图像采集器 | 将X光转换为数字信号,传输给主站 | _halconWinFormsControl显示图像 |
二、软主站:普通电脑变"工业大脑"
1. 什么是软主站?
- 不是硬件卡 :用软件在普通工控机上实现EtherCAT主站功能,省成本(不用买专用主站卡)
- 核心组件 :
- 标准以太网口(Intel/Realtek都能用,最好选带实时性优化的网卡)
- 主站软件栈(如倍福TwinCAT、CODESYS、开源SOEM/IgH)
- C# WPF程序(通过API调用主站功能)
2. 软主站优势(设备价值)
| 优势 | 通俗解释 | 具体价值 |
|---|---|---|
| 低成本 | 不用买昂贵的专用主站卡 | 降低设备BOM成本,适合量产 |
| 高灵活 | 可在Windows/Linux上运行 | 适配你的C# WPF开发环境 |
| 易扩展 | 软件升级即可支持更多从站 | 未来加视觉系统、多轴联动无压力 |
| 强集成 | 控制+视觉+数据处理在同一台电脑 | 你的_scanAxis与Halcon图像无缝联动 |
三、线型拓扑关键细节:"飞传数据"的秘密
1. 数据传输流程(像高铁过站不停车)
- 主站发帧:生成一个超大"信封",包含所有设备的指令和数据空间
- 伺服站:"飞读"移动指令,"飞写"当前位置,信封继续传
- IO站:"飞读"急停状态,"飞写"指示灯信号,信封继续传
- X光站:"飞读"曝光参数,"飞写"管压/管流数据,信封继续传
- 探测站:"飞读"采集指令,"飞写"图像数据,信封原路返回
- 主站收帧 :解析所有数据,更新界面
Status、显示图像
2. 接线铁律(错一个就不通)
- IN/OUT口别搞反 :每个从站都有"IN"(入)和"OUT"(出)口,必须上一级OUT→下一级IN
- 最后一个设备处理 :
- 方法1:接终端电阻(通常是一个小开关,拨到"ON")
- 方法2:直接悬空(部分设备支持自动终端)
- 网线选对 :用工业屏蔽网线(CAT5e/CAT6),长度≤100米/段,减少干扰
- 供电分开 :控制网和动力网分开供电,防止电机启动干扰通信
四、代码深度联动:急停/停止全流程
1. 紧急停止(EmergencyStop())执行路径
用户按急停按钮 → IO模块检测到信号 → 软主站收到中断 → 执行以下步骤:
1. StopAxisStatusRefresh() → 停止IO站状态刷新
2. _cts?.Cancel() → 软主站发送取消指令给所有从站
3. _scanAxis.EmergencyStop() → 伺服站瞬间刹死XY轴
4. CloseXray() → X光站立刻关闭射线
5. Status = STATUS_FAULT → 主站更新状态,禁用所有按钮
6. AddLog("紧急停止") → 记录日志
2. 关键同步机制(分布式时钟DC)
- 软主站作为时间基准 ,所有从站自动校准,同步精度**≤1μs**
- 保证:
- XY轴同时启动/停止,扫描轨迹不偏移
- X光曝光与探测器采集完美同步,图像无拖影
- 你的双扫描模式(点位/连续)精准执行
DC(Distributed Clocks)分布式时钟 = 给所有设备「强制对表」
让伺服电机、X 光控制器、图像探测器,共用同一个绝对精准的时间,同步精度 ≤ 1 微秒(百万分之一秒)
实现:轴动到哪 → 射线立刻开 → 探测器瞬间拍,三者零误差对齐!
二、DC 分布式时钟:怎么工作?(极简原理)
你的线型拓扑:
软主站 → 伺服(运动) → IO → X光(射线) → 探测器(拍照)
DC 同步规则:
软主站 = 基准时钟(总钟表)
发出唯一的标准时间,全网唯一权威。
所有从站自动校准
伺服、X 光、探测器,全部抛弃自己的时钟,强行对齐主站时间。
统一执行指令
主站下发指令:「第 1000000 微秒时,同时执行」
→ 三个设备同一瞬间动作。
csharp
1. 软主站(DC基准) 下发指令:
"10000μs 时,伺服到位;10001μs 时,X光开启;10001μs 时,探测器拍照"
2. 【伺服】10000μs → 精准停在TargetX/Y
3. 【X光】10001μs → 瞬间发射射线
4. 【探测器】10001μs → 瞬间采集图像
五、避坑指南:90%的人会踩的3个坑
1. 软主站实时性不足
- 症状:轴运动卡顿、X光曝光延迟、同步误差大
- 解决 :
- 用实时操作系统(如Windows 10 IoT企业版、Linux RT)
- 关闭后台程序,给主站软件最高优先级
- 选高性能CPU(i7/Ryzen 5以上),避免资源竞争
2. 线型拓扑断链风险
- 症状:一个设备断电,整个网络瘫痪
- 解决 :
- 关键设备加冗余电源
- 最后一个设备必须接终端电阻
- 定期检查网线接头,避免松动
3. 数据溢出/丢失
- 症状:探测器图像花屏、轴位置数据错误
- 解决 :
- 合理设置通信周期(100μs~1ms,根据设备数量调整)
- 每个从站只传输必要数据(如轴位置、X光状态,不传输大图像)
- 图像数据走独立以太网通道,不占用EtherCAT带宽
六、极简总结:软主站+线型拓扑=你的X光设备最佳选择
| 核心优势 | 设备意义 |
|---|---|
| 成本低 | 不用交换机和专用主站卡,降低设备成本 |
| 速度快 | 微秒级响应,XY轴高速扫描无延迟 |
| 同步准 | 亚微秒级同步,X光与运动完美配合 |
| 布线简 | 一根线串到底,安装维护方便 |
| 易开发 | 软主站+WPF+Halcon,开发效率高 |
一句话记住:这就是为半导体X光检测设备量身定做的"神经中枢",用最省钱的方式,实现最快、最准、最稳的控制!
PDO 实时通信 + SDO 参数配置
核心一句话 :PDO是实时"神经传导" (每周期自动传控制/状态,微秒级),SDO是离线"基因编程" (按需配置参数,毫秒级),两者配合让你的设备运动精准、射线稳定、拍照清晰,完美适配半导体X光检测!
一、PDO vs SDO:分工像"快递+挂号信"(一眼看懂)
| 对比项 | PDO(过程数据对象) | SDO(服务数据对象) |
|---|---|---|
| 本质 | 实时"神经脉冲",周期自动传输(如100μs/次) | 配置"基因密码",按需触发(如开机/调试时) |
| 速度 | 微秒级(100μs~1ms),不耽误运动/射线同步 | 毫秒级(1~100ms),不影响实时控制 |
| 数据量 | 小而精(最多8字节/条目),只传关键实时数据 | 大而全(最多4GB),可传完整参数表 |
| 模式 | 生产者-消费者,无请求无响应(像广播) | 客户端-服务器,请求-响应(像挂号信) |
| 你的设备用途 | 伺服位置/速度、X光开关、探测器触发、急停信号 | 伺服参数(如加速度)、X光剂量、探测器曝光时间、IO配置 |
| 代码关联 | _scanAxis.Move()、StartXray()、EmergencyStop() |
设备初始化、参数校准、故障诊断 |
二、PDO:X光检测的"实时神经中枢"(运动/射线/拍照同步核心)
1. PDO的两种关键类型(和你的设备一一对应)
- RxPDO(主站→从站) :"指令通道"
- 伺服:目标位置、速度、控制字(如0x6040)
- X光:曝光指令、管压/管流设定
- 探测器:采集触发、曝光时间
- TxPDO(从站→主站) :"反馈通道"
- 伺服:实际位置、速度、状态字(如0x6041)
- X光:管压/管流实际值、射线状态
- 探测器:图像数据就绪、采集状态
- IO:急停按钮状态、限位开关信号
2. 设备PDO传输流程(点位扫描实例)
软主站每100μs发一次PDO帧(像心跳):
1. 主站→伺服RxPDO:目标位置X=100.0mm,Y=50.0mm,控制字=0x0006(使能+运动)
2. 主站→X光RxPDO:准备曝光,管压=160kV,管流=0.5mA
3. 主站→探测器RxPDO:准备采集,曝光时间=10ms
同时,各从站返回TxPDO:
1. 伺服→主站:实际位置X=99.8mm,Y=49.9mm,状态字=0x0007(就绪+运动中)
2. X光→主站:就绪,管压=160kV,管流=0.5mA
3. 探测器→主站:就绪,温度正常
4. IO→主站:急停未触发,限位正常
当伺服TxPDO显示到位(状态字=0x0017)→ 主站下一周期PDO:
- 主站→X光RxPDO:曝光开始
- 主站→探测器RxPDO:采集开始
3. PDO配置关键(软主站实战步骤)
- 导入ESI文件:从站设备描述文件(XML),包含对象字典
- 映射PDO条目 (核心操作):
- 伺服RxPDO映射:0x6040(控制字)、0x607A(目标位置)
- 伺服TxPDO映射:0x6041(状态字)、0x6064(实际位置)
- X光RxPDO映射:0x2000(曝光控制)、0x2001(管压)、0x2002(管流)
- 设置同步管理器:SM2(RxPDO)、SM3(TxPDO),绑定DC同步信号
- 配置通信周期 :建议100μs~1ms(设备少用短周期,设备多用长周期)
三、SDO:设备的"基因编程器"(参数配置/调试/诊断)
1. SDO的核心价值(你的设备必用场景)
- 设备初始化:开机时配置伺服加速度、X光管参数、探测器增益
- 参数校准:定期校准X光剂量、探测器灵敏度
- 故障诊断:读取伺服报警代码、X光管温度、探测器错误日志
- 模式切换:从点位扫描→连续扫描时,修改运动参数和曝光模式
2. 你的设备SDO配置实例(代码级)
csharp
// 伺服参数配置(SDO写操作)
sdo.Write(0x6083, 0x00, 1000); // 最大加速度=1000mm/s²
sdo.Write(0x6084, 0x00, 500); // 最大减速度=500mm/s²
sdo.Write(0x6098, 0x00, 0x0001); // 回零模式=主动回零
// X光参数配置
sdo.Write(0x2100, 0x00, 160); // 管压=160kV
sdo.Write(0x2101, 0x00, 0.5); // 管流=0.5mA
sdo.Write(0x2102, 0x00, 10); // 最大曝光时间=10ms
// 探测器参数配置
sdo.Write(0x3000, 0x00, 1); // 采集模式=单帧
sdo.Write(0x3001, 0x00, 8); // 图像位深=8bit
// 故障诊断(SDO读操作)
ushort servoAlarm = sdo.ReadUInt16(0x603F, 0x00); // 读取伺服报警代码
float xrayTemp = sdo.ReadFloat(0x2200, 0x00); // 读取X光管温度
3. SDO操作铁律(避坑指南)
- 先配置SDO,再启用PDO:参数没调好,实时控制会出问题
- 关键参数加校验:写SDO后立刻读回,确认配置成功
- 运行中谨慎用SDO:避免频繁写SDO,可能影响实时性
- 用索引/子索引精准定位:每个参数都有唯一地址(如伺服控制字0x6040:00)
四、PDO+SDO协同:你的X光设备"完美运行组合拳"
完整工作流程(从开机到检测)
1. 【开机初始化】→ 用SDO配置所有设备参数
- 伺服:加速度、减速度、回零模式
- X光:管压、管流、最大曝光时间
- 探测器:采集模式、图像位深
- IO:急停触发方式、限位类型
2. 【PDO启动】→ 软主站启用周期PDO通信(100μs/次)
- 伺服RxPDO:目标位置、控制字
- 伺服TxPDO:实际位置、状态字
- X光RxPDO:曝光指令
- 探测器RxPDO:采集触发
3. 【检测执行】→ PDO实时控制+SDO按需调整
- 点位扫描:PDO传位置→到位后PDO触发X光+探测器
- 连续扫描:PDO传速度+位置→PDO同步X光脉冲+探测器采集
- 异常处理:IO TxPDO传急停信号→主站PDO发停止指令→SDO读故障代码
4. 【关机/维护】→ SDO保存参数+读取诊断数据
核心优势(对你的X光检测设备)
- 实时性+灵活性平衡:PDO保证运动/射线/拍照微秒级同步,SDO提供参数调整自由度
- 带宽优化:PDO只传关键数据(如位置/开关),大图像数据走独立通道
- 故障快速定位:SDO读取详细诊断信息,PDO实时监控状态
- 开发效率高:软主站API直接支持PDO/SDO操作,和你的C# WPF代码无缝集成
五、极简总结(背会这3句就够)
- PDO是实时"神经传导" :每周期自动传控制/状态,保证运动、射线、拍照微秒级同步
- SDO是离线"基因编程":按需配置参数,负责设备初始化、校准、诊断
- 两者配合是黄金组合 :SDO打底,PDO执行,让你的X光检测设备精准、稳定、高效!
mvvm
vm
csharp
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.Mvvm.Messaging.Messages;
using HalconDotNet;
namespace WpfApp6
{
public class MainViewModel : Inotifybase
{
private readonly XYScanAxis _scanAxis;
// private readonly HWindowControlWPF _halconWpfWindow;
private readonly HWindowControl _halconWinFormsControl;
private readonly EtherCATMaster _ecatMaster;
private readonly AXISafeIO _safeIO;
private CancellationTokenSource _cts;
private CancellationTokenSource _axisStatusCts;
private bool _isXrayOpen;
private double _xrayExposeTime=5.0;
public double XrayExposeTime
{
get { return _xrayExposeTime; }
set { _xrayExposeTime= value;
OnPropertyChanged();
}
}
private bool _isDetectorConnected;
public bool IsDetectorConnected
{
get { return _isDetectorConnected; }
set { _isDetectorConnected = value;
OnPropertyChanged();
}
}
private string _productModel="半导体芯片(3D AXI)";
public string ProductModel
{
get { return _productModel; }
set { _productModel = value;
OnPropertyChanged();
}
}
private int _exposureTime=200;
public int ExposureTime
{
get { return _exposureTime; }
set {
if (value >= 10 && value <= 1000)
{ _exposureTime = value;
OnPropertyChanged();
}
}
}
private double _scanSpeed=0.5;
public double ScanSpeed
{
get { return _scanSpeed; }
set {
if(value >= 0.1 && value <= 2.0)
{ _scanSpeed = value; OnPropertyChanged();}
}
}
private double _targetX=50;
public double TargetX
{
get { return _targetX ; }
set {
if(value>=0 && value <= XYScanAxis.X_MAX_LIMIT)
{ _targetX = value; OnPropertyChanged(); }
}
}
private double _targetY=0;
public double TargetY
{
get { return _targetY; }
set {
if (value >= 0) { _targetY = value; OnPropertyChanged(); }
}
}
public bool CanEditParam
=> Status == STATUS_READY || Status == STATUS_STOP
|| Status == STATUS_FAULT
|| Status == STATUS_LIMIT;
private string _axisStatusText;
public string AxisStatusText
{
get { return _axisStatusText; }
set
{
_axisStatusText = value;
OnPropertyChanged();
}
}
private string status;
public string Status
{
get { return status; }
set
{
status = value;
OnPropertyChanged();
OnPropertyChanged(nameof(CanEditParam));
CommandManager.InvalidateRequerySuggested();
}
}
private double rate;
public double Rate
{
get { return rate; }
set
{
rate = value;
OnPropertyChanged();
}
}
private string _busStatus;
public string BusStatus
{
get { return _busStatus; }
set { _busStatus = value;
OnPropertyChanged();
}
}
public ObservableCollection<string> LogList { get; } = new ObservableCollection<string>();
// public RelayCommand StartC { get; }
// private HImage _xRayImage;
private const string STATUS_INIT = "初始化中...";
private const string STATUS_READY = "已就绪";
private const string STATUS_RUNNING = "运行中";
private const string STATUS_STOP = "已停止";
private const string STATUS_FAULT = "故障";
private const string STATUS_FINISH = "检测完成";
private const string STATUS_LIMIT = "限位报警";
private const string STATUS_ARRIVED = "点位已到位";
public RelayCommand PointScanC { get; }
public RelayCommand ContinueScanC { get; }
public RelayCommand StopC { get; }
//public RelayCommand StopC { get; }
public RelayCommand EmergencyStopC { get; }
public RelayCommand ResetC { get; }
public RelayCommand TestImageC { get; }
public RelayCommand SimulateXrayFaultC { get; }
public MainViewModel(HWindowControl halconControl)
{
_ecatMaster = new EtherCATMaster();
_safeIO=new AXISafeIO();
_scanAxis = new XYScanAxis(_ecatMaster);
_halconWinFormsControl = halconControl ?? throw new ArgumentNullException(nameof(halconControl));
//WeakReferenceMessenger.Default.Register<ValueChangedMessage<string>>(this, (s, msg) =>
//{
//})
WeakReferenceMessenger.Default.Register<ValueChangedMessage<string>>(this, (sender, msg) =>
{
if (msg.Value == "XrayScanStart")
{
//CanEditParam = false;
AddLog("参数已锁定,不可修改");
}
if (msg.Value == "XrayScanStop")
{
//CanEditParam = true;
AddLog("参数已解锁");
}
});
TestImageC = new RelayCommand(TestShowImage);
PointScanC = new RelayCommand(StartPointScan, CanStartScan);
ContinueScanC = new RelayCommand(StartContinueScan, CanStartScan);
//StartC = new RelayCommand(Start, CanStart);
StopC = new RelayCommand(NormalStop, CanStop);
EmergencyStopC = new RelayCommand(EmergencyStop);
ResetC = new RelayCommand(ResetAlarm, CanReset);
SimulateXrayFaultC = new RelayCommand(SimulateXrayHardwareFault);
Loading();
StartAxisStatusRefresh();
}
//{
// //throw new NotImplementedException();
//}
//{
// throw new NotImplementedException();
//}
//private async Task DeviceInitWork(CancellationToken token)
//{
// await _scanAxis.HomeAsync(token);
// if (_scanAxis.CurrentStatus == XYScanAxis.AxisWorkStatus.Fault)
// {
// Status = STATUS_FAULT;
// return;
// }
// Status = STATUS_READY;
// RefreshAxisStatus();
//}
//{
// //throw new NotImplementedException();
// return Status ==STATUS_RUNNING;
//}
//private async void Stop()
//{
// try
// {
// _cts?.Cancel();
// await Task.Delay(100);
// CloseXray();
// await Task.Delay(900);
// Application.Current.Dispatcher.Invoke(() =>
// {
// Status = STATUS_STOP;
// Rate = 0;
// _halconWinFormsControl?.HalconWindow?.ClearWindow();
// });
// }
// catch (Exception ex)
// {
// Application.Current.Dispatcher.Invoke(() =>
// {
// Status = STATUS_FAULT;
// MessageBox.Show(ex.Message);
// });
// }
// finally
// {
// _cts?.Dispose();
// _cts = null;
// }
//}
//private bool CanStart()
//{
// //throw new NotImplementedException();
// return Status == STATUS_READY;
//}
//private void Start()
//{
// //throw new NotImplementedException();
// if (Status == STATUS_RUNNING)
// {
// MessageBox.Show("已在运行中");
// return;
// }
// _cts = new CancellationTokenSource();
// var token = _cts.Token;
// Task.Run(async () =>
// {
// try
// {
// Application.Current.Dispatcher.Invoke(() =>
// {
// Status = STATUS_RUNNING;
// Rate = 0;
// });
// _isXrayOpen = true;
// for (int i = 1; i <= 100; i++)
// {
// if (token.IsCancellationRequested) break;
// using (HImage xRayImage = new HImage())
// {
// xRayImage.GenImageConst("byte", 800, 500);
// //HRegion defectRegion = new HRegion(200.0, 300, 300, 400);
// //_xRayImage.SetGrayvalRegion(defectRegion, new HTuple(225));
// //HRegion fullImageRegion = new HRegion(0.0, 0, 800, 500);
// //_xRayImage.PaintRegion(fullImageRegion, new HImage(128, "byte", 1, 1), 0);
// HalconDetectAlgorithm(xRayImage);
// }
// double LRate = i * 0.01;
// Application.Current.Dispatcher.Invoke(new Action(() =>
// {
// Rate = LRate;
// }));
// await Task.Delay(500, token);// Thread.Sleep(500);//
// }
// if (!token.IsCancellationRequested)
// {
// Application.Current.Dispatcher.Invoke((() =>
// {
// Status = STATUS_FINISH;
// Rate = 1.0;
// }));
// }
// }
// catch (TaskCanceledException)
// {
// Application.Current.Dispatcher.Invoke(() => Status = STATUS_STOP);
// }
// catch (Exception ex)
// {
// Application.Current.Dispatcher.Invoke(() =>
// {
// Status = STATUS_FAULT;
// MessageBox.Show(STATUS_FAULT + ex.Message);
// });
// }
// finally
// {
// CloseXray();
// _cts?.Dispose();
// _cts = null;
// }
// });
//}
//private void HalconDetectAlgorithm(HImage image)
//{
// //throw new NotImplementedException();
// if (_halconWinFormsControl?.HalconWindow == null)
// {
// MessageBox.Show("Halcon窗口未初始化", STATUS_FAULT);
// return;
// }
// HImage imageFilter = null;
// HRegion region = null;
// try
// {
// imageFilter = image.MeanImage(3, 3);
// region = imageFilter.Threshold(100.0, 200.0).
// Connection().
// SelectShape("area", "and", 100, 99999);
// int defectCount = region.CountObj();
// Application.Current.Dispatcher.Invoke(() =>
// {
// _halconWinFormsControl.HalconWindow.ClearWindow();
// _halconWinFormsControl.HalconWindow.DispObj(image);
// _halconWinFormsControl.HalconWindow.DispObj(region);
// _halconWinFormsControl.HalconWindow.SetColor("white");
// _halconWinFormsControl.HalconWindow.DispRectangle1(100.0, 100, 400, 700);
// if (defectCount > 0)
// {
// Status = $"检测中:发现{defectCount}个缺陷";
// }
// });
// }
// catch (Exception ex)
// {
// Application.Current.Dispatcher.Invoke(() =>
// {
// Status = STATUS_FAULT;
// MessageBox.Show($"图像处理失败: {ex.Message}", STATUS_FAULT);
// });
// }
// finally
// {
// region?.Dispose();
// imageFilter?.Dispose();
// }
//}
private void ShowXrayImage()
{
if (new Random().Next(1, 80) == 1)
{
AddLog("【硬件故障】图像探测器连接断开");
Status = STATUS_FAULT;
EmergencyStop();
return;
}
if (_halconWinFormsControl?.HalconWindow == null)
{
AddLog("Halcon窗口未初始化,无法显示图像");
return;
}
//HImage rawImage = null;
//HImage smoothImage= null;
//HImage enhanceImage= null;
//HImage xrayImage = null;
try
{
Application.Current.Dispatcher.Invoke(() =>
{
_halconWinFormsControl.HalconWindow.ClearWindow();
using (HImage rawImage = new HImage())
{
rawImage.GenImageConst("byte", 800, 500);
// rawImage.RandNoise(20);
int grayBase = 80 + ExposureTime / 10;
//rawImage.GenImageConst("byte", 800, 500);
using (HImage brightImage = rawImage.ScaleImage(1.0, grayBase - 128))
{
using (HImage smoothImage = brightImage.MeanImage(3, 3))
{
using (HImage enhanceImage = smoothImage.ScaleImage(1.5, 0.0))
{
_halconWinFormsControl.HalconWindow.DispObj(enhanceImage);
_halconWinFormsControl.HalconWindow.SetColor("green");
_halconWinFormsControl.HalconWindow.SetLineWidth(2);
_halconWinFormsControl.HalconWindow.DispRectangle1(120.0, 120, 380, 680);
}
}
}
}
AddLog($"X光图像采集成功|曝光:{ExposureTime}ms");
});
}
catch (Exception ex)
{
Application.Current.Dispatcher.Invoke(() =>
{
AddLog($"图像显示异常:{ex.Message}");
Status = STATUS_FAULT;
EmergencyStop();
});
}
//finally
//{
// rawImage?.Dispose();
// smoothImage?.Dispose();
// enhanceImage?.Dispose();
//}
}
public void TestShowImage()
{
AddLog("===单独测试 x光图像显示 ===");
ShowXrayImage();
}
private async void StartPointScan()
{
// throw new NotImplementedException();
await RunScan(async token =>
{
await DcSyncScanLogic(async t =>
{
// double tarX = TargetX;
//double tarY = TargetY;
//AddLog($"点位模式:移动到坐标X:{tarX:F1} Y:{tarY:F1}");
await _scanAxis.MoveToAsync(TargetX, TargetY, t);
if (_scanAxis.CurrentStatus == XYScanAxis.AxisWorkStatus.Arrived)
{
Status = STATUS_ARRIVED;
AddLog("轴到位,DC同步完成,X光曝光");
ShowXrayImage();
Application.Current.Dispatcher.Invoke(() => Rate = 1.0);
}
}, token);
});
}
private async void StartContinueScan()
{
//throw new NotImplementedException();
await RunScan(async token =>
{
await DcSyncScanLogic(async t =>
{
AddLog($"连续扫描启动|扫描步长:{ScanSpeed:F2}");
var scanTask = _scanAxis.StartContinueScanAsync(ScanSpeed, t);
while (!t.IsCancellationRequested &&
_scanAxis.CurrentStatus != XYScanAxis.AxisWorkStatus.LimitAlarm)
{
await Task.Delay(200, t);
//await _scanAxis.StartContinueScanAsync(token);
double progress = _scanAxis.Posx / XYScanAxis.X_MAX_LIMIT;
Application.Current.Dispatcher.Invoke(() => Rate = progress);
//ShowXrayImage();
}
await scanTask;
Application.Current.Dispatcher.Invoke(() =>
{
Rate = 1.0;
Status = STATUS_FINISH;
AddLog("3D AXI连续扫描完成");
});
// AddLog("连续扫描准备就绪,DC同步完成,开始扫描");
}, token);
//await scanTask;
});
}
private async Task StartAxisStatusRefresh()
{
_axisStatusCts = new CancellationTokenSource();
var token = _axisStatusCts.Token;
//throw new NotImplementedException();
try
{
while (!token.IsCancellationRequested)
{
await Task.Delay(200, token);
//RefreshAxisStatus();
Application.Current.Dispatcher.Invoke(RefreshAxisStatus);
BusStatus=_ecatMaster.BusLog;
if (_scanAxis.CurrentStatus == XYScanAxis.AxisWorkStatus.LimitAlarm && Status != STATUS_LIMIT)
{
Application.Current.Dispatcher.Invoke(() =>
{
Status = STATUS_LIMIT;
AddLog($"[限位报警]X坐标超出阈值({_scanAxis.Posx:F1})");
EmergencyStop();
});
}
}
}
catch (TaskCanceledException)
{
AddLog("轴状态监控已停止");
}
catch (Exception ex)
{
Application.Current.Dispatcher.Invoke(() =>
{
AddLog($"轴状态刷新异常:{ex.Message}");
Status = STATUS_FAULT;
});
}
finally
{
_axisStatusCts?.Dispose();
_axisStatusCts = null;
}
}
private void StopAxisStatusRefresh()
{
_axisStatusCts?.Cancel();
}
private void RefreshAxisStatus()
{
AxisStatusText = $"轴状态:{_scanAxis.CurrentStatus}|X:{_scanAxis.Posx:F1} Y:{_scanAxis.Posy:F1}";
//OnPropertyChanged();
}
private bool CanStartScan() => Status == STATUS_READY;
private bool CanStop() => Status == STATUS_RUNNING || Status == STATUS_ARRIVED;
private bool CanReset() => Status == STATUS_FAULT || Status == STATUS_LIMIT;
private async void Loading()
{
try
{
Status = STATUS_INIT;
AddLog("设备初始化...");
await _ecatMaster.StartBus();
BusStatus= _ecatMaster.BusLog;
//await Task.Delay(3000);
Application.Current.Dispatcher.Invoke(() =>
{
if (_halconWinFormsControl?.HalconWindow == null)
{
AddLog("Halcon窗口未初始化");
Status = STATUS_FAULT;
return;
}
//_halconWinFormsControl.HalconWindow.OpenWindow(0, 0, 800, 500, "visible", "");
Status = STATUS_READY;
Rate = 0;
AddLog("初始化完成,设备就绪");
//Rate = 0;
});
//_halconWinFormsControl.HalconID.OpenWindow(0, 0, 800, 500, "visible", "");
//throw new NotImplementedException();
//Application.Current.Dispatcher.Invoke(() =>
//{
// Status = "设备初始化中...";
// //_halconWinFormsControl.HalconWindow.OpenWindow(
// // 0, 0, 800, 500,
// // "visible", "");
// _xRayImage = new HImage();
// });
//Task.Delay(1000).Wait();
//Task.Delay(1000).Wait();
//Task.Delay(1000).Wait();
// Thread.Sleep(3000);
// Application.Current.Dispatcher.Invoke(() =>
// {
// Status = "已就绪";
// Rate = 0;
// });
//}}
}
catch (Exception ex)
{
Status = STATUS_FAULT;
AddLog($"初始化异常: {ex.Message}");
// MessageBox.Show(ex.Message + STATUS_FAULT);
}
}
private async Task DcSyncScanLogic(Func<CancellationToken, Task> scanLogic, CancellationToken token)
{
if(!_ecatMaster.DcSyncOk)
{
//Status = STATUS_FAULT;
AddLog("DC时钟同步失败,无法开始扫描");
return;
}
if (!_safeIO.AllowXray)
{
AddLog("安全IO状态异常,禁止X光发射");
return;
}
// AddLog("DC时钟同步中...");
//await Task.Delay(1000, token);
//if (token.IsCancellationRequested) return;
//AddLog("DC时钟同步完成,扫描开始");
await scanLogic(token);
ShowXrayImage();
}
private void ResetAlarm()
{
// throw new NotImplementedException();
StopAxisStatusRefresh();
_scanAxis.ResetAlarm();
Status = STATUS_READY;
Rate = 0;
AddLog("报警已复位,轴已回零");
StartAxisStatusRefresh();
}
private void EmergencyStop()
{
//throw new NotImplementedException();
WeakReferenceMessenger.Default.Send(new ValueChangedMessage<string>("XrayScanStop"));
StopAxisStatusRefresh();
_cts?.Cancel();
_scanAxis.EmergencyStop();
CloseXray();
Status = STATUS_FAULT;
AddLog("【紧急停止】整机已停机");
}
private async void NormalStop()
{
//throw new NotImplementedException();
WeakReferenceMessenger.Default.Send(new ValueChangedMessage<string>("XrayScanStop"));
StopAxisStatusRefresh();
_cts?.Cancel();
await Task.Delay(100);
_scanAxis.NormalStop();
CloseXray();
Status = STATUS_STOP;
Rate = 0;
AddLog("正常停止");
CleanupCts();
StartAxisStatusRefresh();
}
private void SimulateXrayHardwareFault()
{
if (Status != STATUS_FAULT)
{
AddLog("请先启动扫描,再模拟故障");
return;
}
AddLog("[硬件故障]x光发射单元异常,停止发射!");
EmergencyStop();
}
private async Task RunScan(Func<CancellationToken, Task> scanLogic)
{
WeakReferenceMessenger.Default.Send(new ValueChangedMessage<string>("XrayScanStart"));
AddLog("3D AXI扫描启动,平板检测器开始采集图像->EtherCAT同步触发");
//throw new NotImplementedException();
if (_cts != null) return;
_cts = new CancellationTokenSource();
var token = _cts.Token;
try
{
Application.Current.Dispatcher.Invoke(() =>
{
Status = STATUS_RUNNING;
Rate = 0;
_isXrayOpen = true;
});
await scanLogic(token);
}
catch (TaskCanceledException)
{
AddLog("任务已取消");
}
catch (Exception ex)
{
Status = STATUS_FAULT;
AddLog($"运行异常:{ex.Message}");
EmergencyStop();
}
finally
{
CloseXray();
CleanupCts();
}
}
private void AddLog(string msg)
{
// throw new NotImplementedException();
Application.Current.Dispatcher.Invoke(() =>
LogList.Insert(0, $"[{DateTime.Now:HH:mm:ss}]{msg}")
);
}
private void CleanupCts()
{
// throw new NotImplementedException();
_cts?.Dispose();
_cts = null;
}
private void CloseXray()
{
// throw new NotImplementedException();
_isXrayOpen = false;
}
public void ShutdownAllResources()
{
AddLog("程序退出,开始释放所有资源...");
EmergencyStop();
StopAxisStatusRefresh();
CleanupCts();
Application.Current.Dispatcher.Invoke(() =>
{
_halconWinFormsControl?.HalconWindow?.ClearWindow();
});
AddLog("所有资源已释放,安全退出!");
}
}
}
v
xml
<Window x:Class="WpfApp6.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp6"
xmlns:winforms="clr-namespace:System.Windows.Forms.Integration;assembly=WindowsFormsIntegration"
xmlns:halcon="clr-namespace:HalconDotNet;assembly=halcondotnet"
mc:Ignorable="d"
Title="x光检测系统" Height="550" Width="900" Closed="OnClosed">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="350"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2"
Orientation="Horizontal" Margin="10">
<TextBlock Text="{Binding Status}" FontSize="16" FontWeight="Bold" Foreground="blue"/>
<TextBlock Text="|" FontSize="16" Margin="5,0"/>
<TextBlock Text="{Binding AxisStatusText}" FontSize="14" Foreground="DarkGreen"/>
</StackPanel>
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding BusStatus}"
Foreground="DarkRed" FontSize="12" Margin="10"/>
<StackPanel Grid.Row="1" Grid.Column="0" Margin="10" Orientation="Vertical">
<Border BorderBrush="Gray" BorderThickness="1" Padding="8">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="30"/>
<RowDefinition Height="30"/>
<RowDefinition Height="30"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="140"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="曝光(ms):" VerticalAlignment="Center"/>
<TextBlock Grid.Row="0" Grid.Column="1"
Text="{Binding ExposureTime,Mode=TwoWay}" IsEnabled="{Binding CanEditParam}"/>
<TextBlock Grid.Row="1" Grid.Column="0" Text="扫描速度:" VerticalAlignment="Center"/>
<TextBox Grid.Row="1" Grid.Column="1"
Text="{Binding ScanSpeed,Mode=TwoWay,StringFormat=F2}"
IsEnabled="{Binding CanEditParam}"/>
<TextBlock Grid.Row="2" Grid.Column="0" Text="目标X坐标:" VerticalAlignment="Center"/>
<TextBox Grid.Row="2" Grid.Column="1"
Text="{Binding TargetX,Mode=TwoWay,StringFormat=F1}"
IsEnabled="{Binding CanEditParam}"/>
</Grid>
</Border>
</StackPanel>
<ProgressBar Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2"
Value="{Binding Rate}" Minimum="0" Maximum="1"
Height="20" Width="400" Margin="10" HorizontalAlignment="Center"/>
<ListBox Grid.Row="3" Grid.Column="0"
ItemsSource="{Binding LogList}" Height="auto" Margin="10">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" Foreground="Black" FontSize="12"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<!--
<TextBlock Text="{Binding Status}" FontSize="16" FontWeight="Bold" Foreground="Blue" Margin="5"/>
<TextBlock Text="{Binding AxisStatusText}" FontSize="14" Margin="5" Height="auto" Foreground="DarkGreen"/>
<ProgressBar Value="{Binding Rate}"
Minimum="0"
Maximum="1"
VerticalAlignment="Top" Height="20" Width="300" Margin="0 60 0 0"/>
<ListBox ItemsSource="{Binding LogList}" Height="150" Width="350"
Margin="0 100 0 0" HorizontalAlignment="Left">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" Foreground="Black"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>-->
<winforms:WindowsFormsHost Grid.Row="3" Grid.Column="1"
Margin="10" Background="Black">
<halcon:HWindowControl x:Name="HalconWinFormsControl"/>
</winforms:WindowsFormsHost>
<StackPanel Grid.Row="4" Grid.Column="0" Grid.ColumnSpan="2"
Orientation="Horizontal" HorizontalAlignment="Center" Margin="0 10">
<Button Command="{Binding PointScanC}" Content="点位扫描"
Width="100" Height="35" Margin="5" FontSize="14"/>
<Button Command="{Binding ContinueScanC}" Content="连续扫描"
Width="100" Height="35" Margin="5" FontSize="14"/>
<Button Command="{Binding StopC}" Content="停止"
Width="100" Height="35" Margin="5" FontSize="14"/>
<Button Command="{Binding EmergencyStopC}" Content="急停"
Width="100" Height="35" Margin="5" Background="Red" Foreground="white" FontSize="14"/>
<Button Command="{Binding ResetC}" Content="复位"
Width="100" Height="35" Margin="5" FontSize="14"/>
<Button Command="{Binding TestImageC}" Content="测试图像"
Width="100" Height="35" Margin="5" Background="Orange" Foreground="White"/>
</StackPanel>
</Grid>
</Window>
csharp
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace WpfApp6
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
private readonly MainViewModel _viewModel;
public MainWindow()
{
InitializeComponent();
_viewModel = new MainViewModel(HalconWinFormsControl);
DataContext = _viewModel;
}
protected override void OnClosed( EventArgs e)
{
base.OnClosed( e );
_viewModel.ShutdownAllResources();
}
private void OnClosed(object sender, EventArgs e)
{
}
//private void MainWindow_Loaded(object sender, RoutedEventArgs e)
//{
// // throw new NotImplementedException();
// this.DataContext = new MainViewModel(HalconWinFormsControl);
//}
}
}
base
csharp
using HalconDotNet;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace WpfApp6
{
public class XYScanAxis
{
public enum AxisWorkStatus
{
Idle,
Homing,
Moving,
Scanning,
Arrived,
LimitAlarm,
XrayFault,
DetectorFault,
Fault
}
public const double X_MAX_LIMIT = 100.0;
public AxisWorkStatus CurrentStatus { get;private set; }
public double Posx { get; private set; }
public double Posy { get; private set; }
private readonly EtherCATMaster _ecatMaster;
public XYScanAxis(EtherCATMaster master)
{
_ecatMaster = master;
CurrentStatus = AxisWorkStatus.Idle;
Posx = 0;
Posy = 0;
}
public async Task HomeAsync(CancellationToken token)
{
if (CurrentStatus != AxisWorkStatus.Idle) return;
CurrentStatus = AxisWorkStatus.Homing;
_ecatMaster.WritePD0(ECATSlaveType.ServoAxis, "轴回零中");
await Task.Delay(1500, token);
if (token.IsCancellationRequested || CheckLimit())
{
CurrentStatus = CheckLimit()?AxisWorkStatus.LimitAlarm: AxisWorkStatus.Fault;
return;
}
Posx = 0;
Posy = 0;
CurrentStatus = AxisWorkStatus.Idle;
}
public async Task MoveToAsync(double targetX, double targetY, CancellationToken token)
{
if (CurrentStatus != AxisWorkStatus.Idle) return;
CurrentStatus = AxisWorkStatus.Moving;
_ecatMaster.WritePD0(ECATSlaveType.ServoAxis, $"移动到X:{targetX}");
await Task.Delay(1000,token);
if (token.IsCancellationRequested||CheckLimit()) {
CurrentStatus=CheckLimit()?AxisWorkStatus.LimitAlarm: AxisWorkStatus.Fault;
return;
}
Posx = targetX;
Posy = targetY;
CurrentStatus = AxisWorkStatus.Arrived;
}
public async Task StartContinueScanAsync(double scanStep,CancellationToken token)
{
if (CurrentStatus != AxisWorkStatus.Idle) return;
CurrentStatus = AxisWorkStatus.Scanning;
_ecatMaster.WritePD0(ECATSlaveType.ServoAxis, "连续扫描启动");
while (!token.IsCancellationRequested)
{
if (CheckLimit()) { CurrentStatus = AxisWorkStatus.LimitAlarm;
break;
}
Posx += scanStep;
await Task.Delay(200,token);
}
if(CurrentStatus != AxisWorkStatus.LimitAlarm)
CurrentStatus = AxisWorkStatus.Idle;
}
public void NormalStop()
{
if(CurrentStatus == AxisWorkStatus.Moving || CurrentStatus==AxisWorkStatus.Scanning||CurrentStatus==AxisWorkStatus.Homing)
{
CurrentStatus = AxisWorkStatus.Idle;
_ecatMaster.WritePD0(ECATSlaveType.ServoAxis, "轴正常停止");
}
}
public void EmergencyStop()
{
CurrentStatus = AxisWorkStatus.Fault;
_ecatMaster.WritePD0(ECATSlaveType.ServoAxis, "轴紧急停止");
}
public bool CheckLimit() => Posx > X_MAX_LIMIT;
public void ResetAlarm()
{
if(CurrentStatus==AxisWorkStatus.LimitAlarm || CurrentStatus == AxisWorkStatus.Fault)
{
Posx = 0;
CurrentStatus = AxisWorkStatus.Idle;
}
}
}
}
csharp
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace WpfApp6
{
public class EtherCATMaster
{
public ECATBusState BusState { get; private set; }
public bool DcSyncOk { get; private set; }
public string BusLog { get; private set; }
public EtherCATMaster()
{
BusState = ECATBusState.未初始化;
DcSyncOk = false;
}
public async Task<bool> StartBus()
{
BusLog = "EtherLog总线启动:主站->伺服轴--->安全IO->X光源->平板探测器";
await Task.Delay(500);
DcSyncOk = true;
BusState = ECATBusState.总线就绪;
BusLog = "EtherCAT总线就绪|DC时钟同步完成(纳秒级)";
return true;
}
public void WritePD0(ECATSlaveType slave, string cmd)
{
BusLog = $"[PDO同步]{slave}->{cmd}";
}
public void WriteSDO(ECATSlaveType slave, string param)
{
BusLog = $"[SDO配置]{slave}->{param}";
}
}
}
csharp
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace WpfApp6
{
public static class EtherCATConfig
{
public const int BUS_CYCLE_MS = 1;
public const int DC_SYNC_SUCCESS = 1;
}
public enum ECATSlaveType
{
ServoAxis,
SafeIO,
XRaySource,
Detector
}
public enum ECATBusState
{
未初始化,
总线就绪,
运行中,
同步故障,
总线断开
}
}
csharp
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace WpfApp6
{
public class AXISafeIO
{
public bool SafetyDoorClosed { get; set; } = true;
public bool EmergencyStop { get; set; } = false;
public bool AllowXray=>SafetyDoorClosed && !EmergencyStop;
}
}
csharp
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Authentication;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
namespace WpfApp6
{
public class RelayCommand : ICommand
{
private readonly Action _execute;
private readonly Func<bool> _canExecute;
public RelayCommand(Action execute, Func<bool> canExecute)
{
_execute = execute ?? throw new ArgumentNullException(nameof(execute)) ;
_canExecute = canExecute ?? (() => true);
}
public RelayCommand(Action execute)
{
_execute = execute ?? throw new ArgumentNullException( nameof(execute)) ;
_canExecute =(() => true);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value;}
}
public bool CanExecute(object parameter) => _canExecute();
//{
// throw new NotImplementedException();
//}
public void Execute(object parameter) => _execute();
//{
// throw new NotImplementedException();
//}
}
}
csharp
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
namespace WpfApp6
{
public class Inotifybase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
