6.1EtherCAT工业架构:软主站,分布式时钟DC,PDO实时通信

EtherCAT 工业架构

EtherCAT 工业架构:工业界的 "高铁通信系统"(大白话版)

核心一句话:EtherCAT 是专为工业控制设计的实时以太网总线,像高铁调度系统一样,用主站指挥 + 从站执行 + 飞传数据的方式,实现微秒级响应和亚微秒级同步,完美适配你的 X 光检测设备运动控制需求!

一、核心架构:主站+从站(绝对的"一言堂")

EtherCAT采用单主多从的严格主从架构,和你设备里的控制逻辑完全一致:

角色 定位 类比 你的X光设备对应
主站(Master) 唯一指挥者,主动发指令 高铁调度中心 工控机/PLC(运行你的C# WPF程序)
从站(Slave) 被动执行者,实时响应 各节高铁车厢 XY轴驱动器、X光控制器、传感器、IO模块

关键规则:只有主站能主动发数据帧,从站只能"边传边处理",不能主动说话------这是实时性的根本保障!


二、革命性技术:On-the-Fly(飞读飞写)------为什么比传统以太网快100倍?

传统以太网:快递式"存储-转发"

  1. 主站发包裹给从站1 → 从站1拆开看不是自己的 → 重新打包发给从站2
  2. 从站2拆开看不是自己的 → 重新打包发给从站3...
  3. 最后一个从站再原路返回
    问题:每站都要拆包打包,延迟大、周期长(毫秒级)

EtherCAT:高铁式"直通车"(On-the-Fly)

  1. 主站发一个超大信封(包含所有从站的指令和数据空间)
  2. 信封经过从站1:不拆封,直接从窗口拿走自己的指令,同时把数据塞进自己的位置
  3. 信封继续高速通过从站2、3... 每个从站都"飞读飞写"
  4. 最后一个从站把信封原路返回给主站

效果 :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光检测设备完美适配

典型控制流程(结合你的代码)

  1. 主站(工控机)运行你的C#程序,点击"开始扫描"
  2. 主站生成EtherCAT数据帧:包含XY轴移动指令、X光曝光参数
  3. 数据帧"飞传"到各从站:
    • XY轴驱动器:读取移动指令,执行_scanAxis运动
    • X光控制器:读取曝光参数,执行StartXray()
  4. 从站实时反馈:轴位置、X光状态、图像数据
  5. 主站更新界面状态(Status)、显示图像
  6. 点击急停
    • 主站发送急停指令(通过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主站功能,省成本(不用买专用主站卡)
  • 核心组件
    1. 标准以太网口(Intel/Realtek都能用,最好选带实时性优化的网卡)
    2. 主站软件栈(如倍福TwinCAT、CODESYS、开源SOEM/IgH)
    3. C# WPF程序(通过API调用主站功能)
2. 软主站优势(设备价值)
优势 通俗解释 具体价值
低成本 不用买昂贵的专用主站卡 降低设备BOM成本,适合量产
高灵活 可在Windows/Linux上运行 适配你的C# WPF开发环境
易扩展 软件升级即可支持更多从站 未来加视觉系统、多轴联动无压力
强集成 控制+视觉+数据处理在同一台电脑 你的_scanAxisHalcon图像无缝联动

三、线型拓扑关键细节:"飞传数据"的秘密

1. 数据传输流程(像高铁过站不停车)

  1. 主站发帧:生成一个超大"信封",包含所有设备的指令和数据空间
  2. 伺服站:"飞读"移动指令,"飞写"当前位置,信封继续传
  3. IO站:"飞读"急停状态,"飞写"指示灯信号,信封继续传
  4. X光站:"飞读"曝光参数,"飞写"管压/管流数据,信封继续传
  5. 探测站:"飞读"采集指令,"飞写"图像数据,信封原路返回
  6. 主站收帧 :解析所有数据,更新界面Status、显示图像
2. 接线铁律(错一个就不通)
  1. IN/OUT口别搞反 :每个从站都有"IN"(入)和"OUT"(出)口,必须上一级OUT→下一级IN
  2. 最后一个设备处理
    • 方法1:接终端电阻(通常是一个小开关,拨到"ON")
    • 方法2:直接悬空(部分设备支持自动终端)
  3. 网线选对 :用工业屏蔽网线(CAT5e/CAT6),长度≤100米/段,减少干扰
  4. 供电分开 :控制网和动力网分开供电,防止电机启动干扰通信

四、代码深度联动:急停/停止全流程

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配置关键(软主站实战步骤)
  1. 导入ESI文件:从站设备描述文件(XML),包含对象字典
  2. 映射PDO条目 (核心操作):
    • 伺服RxPDO映射:0x6040(控制字)、0x607A(目标位置)
    • 伺服TxPDO映射:0x6041(状态字)、0x6064(实际位置)
    • X光RxPDO映射:0x2000(曝光控制)、0x2001(管压)、0x2002(管流)
  3. 设置同步管理器:SM2(RxPDO)、SM3(TxPDO),绑定DC同步信号
  4. 配置通信周期 :建议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操作铁律(避坑指南)
  1. 先配置SDO,再启用PDO:参数没调好,实时控制会出问题
  2. 关键参数加校验:写SDO后立刻读回,确认配置成功
  3. 运行中谨慎用SDO:避免频繁写SDO,可能影响实时性
  4. 用索引/子索引精准定位:每个参数都有唯一地址(如伺服控制字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光检测设备)
  1. 实时性+灵活性平衡:PDO保证运动/射线/拍照微秒级同步,SDO提供参数调整自由度
  2. 带宽优化:PDO只传关键数据(如位置/开关),大图像数据走独立通道
  3. 故障快速定位:SDO读取详细诊断信息,PDO实时监控状态
  4. 开发效率高:软主站API直接支持PDO/SDO操作,和你的C# WPF代码无缝集成

五、极简总结(背会这3句就够)

  1. PDO是实时"神经传导" :每周期自动传控制/状态,保证运动、射线、拍照微秒级同步
  2. SDO是离线"基因编程":按需配置参数,负责设备初始化、校准、诊断
  3. 两者配合是黄金组合 :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));
        }
    }
}
相关推荐
czhc11400756631 天前
531 扫描模式
mvvm·运控
czhc11400756633 天前
529: XYScanAxis()类
mvvm·视觉·运控
czhc11400756634 天前
528:Halcon图像控件 启动轴状态实时监控
mvvm·视觉·运控
czhc11400756635 天前
5.27 :工业设备检测模式与安全防护详解
安全·mvvm·视觉·运控
czhc11400756637 天前
525:检测完善
mvvm·视觉
学不懂飞行器7 天前
【电赛保姆级教程】电赛视觉怎么选?怎么调?从OpenMV到边缘计算硬核避坑指南(附高鲁棒通信源码)
人工智能·stm32·边缘计算·电赛·视觉
czhc11400756639 天前
523:MVVM 检测业务
视觉
czhc114007566310 天前
ICommand 视觉、运控522
视觉
czhc114007566311 天前
视觉521 halcon24.05 测试
视觉