5.27 :工业设备检测模式与安全防护详解

一、工作模式(设备怎么干活?)

1. 点位模式

大白话:定点打卡干活

机器轴走到固定的坐标点 → 稳稳停下 → 不动了 → 让 X 光 / Halcon 采图检测

适用场景:半导体芯片、单个元器件检测(只需要测几个固定位置)

对应代码:走到指定位置 → 暂停 → 执行HalconDetectAlgorithm采图检测

2. 连续模式

大白话:边走边拍,全覆盖扫描

X 轴一直匀速移动,不停车 → 全程持续采图 → 把整个 PCB / 晶圆全部扫一遍

适用场景:大板检测、晶圆全幅扫描(要测一整块区域)

对应代码:循环不中断,持续生成图像 + 实时检测

二、安全保护(设备不会撞坏、不会出事)

3. 硬件急停

大白话:红色紧急按钮,一键保命

按下 → 电机立刻卡死停止 → 整机断电 → 系统自动记录:谁、什么时候按的急停

工业设备标配,防止伤人、撞坏设备

4. 软限位保护

大白话:软件设置的「禁区」

比如规定 X 轴最多走 100mm,一旦超过 100 → 立刻报警 + 强制停机

双重保护:物理有硬限位开关,软件再加一层防护,绝对不会撞机

csharp 复制代码
public bool CheckLimit() => Posx > X_MAX_LIMIT;
csharp 复制代码
public bool CheckLimit()
{
    // 判断:当前X轴位置 > 设定的最大软限位
    return Posx > X_MAX_LIMIT;
}

Bug

普通 RelayCommand 不支持直接绑定 async Task 方法。

核心原因

普通 RelayCommand 的执行委托是 Action(无返回值的同步方法)

StartContinueScan 是 async Task,返回 Task 对象,和 Action 的签名不匹配

编译器要求方法必须是无返回值(void),而不是返回 Task

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 HalconDotNet;

namespace WpfApp6
{
	public class MainViewModel : Inotifybase
	{

		private readonly XYScanAxis _scanAxis;
		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();
				CommandManager.InvalidateRequerySuggested();
			}
		}
		private double rate;

		public double Rate
		{
			get { return rate; }
			set
			{
				rate = value;
				OnPropertyChanged();
			}
		}
		public RelayCommand StartC { get; }
		public RelayCommand StopC { get; }
		private CancellationTokenSource _cts;
		private HWindowControl _halconWinFormsControl;
		//	private HImage _xRayImage;
		private bool _isXrayOpen;

		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 ObservableCollection<string> LogList { get; } = new ObservableCollection<string>();
		public RelayCommand PointScanC { get; }
		public RelayCommand ContinueScanC { get; }
		//public RelayCommand StopC { get; }
		public RelayCommand EmergencyStopC { get; }
		public RelayCommand ResetC { get; }
		public RelayCommand TestImageC { get; }

		public MainViewModel(HWindowControl halconControl)
		{
			_scanAxis = new XYScanAxis();
			_halconWinFormsControl = halconControl ?? throw new ArgumentNullException(nameof(halconControl));
			PointScanC = new RelayCommand(StartPointScan, CanStartScan);
			ContinueScanC = new RelayCommand(StartContinueScan, CanStartScan);
			//StartC = new RelayCommand(Start, CanStart);
			StopC = new RelayCommand(NormalStop, CanStop);
			EmergencyStopC = new RelayCommand(EmergencyStop, () => true);
			ResetC = new RelayCommand(ResetAlarm, CanReset);
			TestImageC = new RelayCommand(TestShowImage);
			Loading();
			StartAxisStatusRefresh();
		}

		private bool CanReset() => Status == STATUS_FAULT || Status == STATUS_LIMIT;
		//{
		//    //throw new NotImplementedException();

		//}

		private bool CanStartScan() => Status == STATUS_READY;
		//{
		//    throw new NotImplementedException();
		//}

		private async Task StartAxisStatusRefresh()
		{
			//throw new NotImplementedException();
			try
			{
				while (true)
				{
					await Task.Delay(200);
					RefreshAxisStatus();
					if (_scanAxis.CurrentStatus == XYScanAxis.AxisWorkStatus.LimitAlarm)
					{
						Application.Current.Dispatcher.Invoke(() =>
						{
							Status = STATUS_LIMIT;
							AddLog($"[限位报警]X坐标超出阈值({_scanAxis.Posx:F1})");
							EmergencyStop();
						});
					}
				}
			}
			catch (Exception ex)
			{
				Application.Current.Dispatcher.Invoke(() =>
				{
					AddLog($"轴状态刷新异常:{ex.Message}");
					Status = STATUS_FAULT;
				});
			}
		}

		private void ResetAlarm()
		{
			// throw new NotImplementedException();
			_scanAxis.ResetAlarm();
			Status = STATUS_READY;
			AddLog("报警已复位,轴已回零");
		}

		private void AddLog(string msg)
		{
			// throw new NotImplementedException();
			Application.Current.Dispatcher.Invoke(() =>
				LogList.Insert(0, $"[{DateTime.Now:HH:mm:ss}]{msg}")
			);
		}

		private void EmergencyStop()
		{
			//throw new NotImplementedException();
			_cts?.Cancel();
			_scanAxis.EmergencyStop();
			CloseXray();
			Status = STATUS_FAULT;
			AddLog("【紧急停止】整机已停机");
		}

		private async void NormalStop()
		{
			//throw new NotImplementedException();
			_cts?.Cancel();
			await Task.Delay(100);
			_scanAxis.NormalStop();
			CloseXray();
			Status = STATUS_STOP;
			AddLog("正常停止");
			CleanupCts();
		}

		private async void StartContinueScan()
		{
			//throw new NotImplementedException();
			await RunScan(async token =>
			{
				AddLog("连续扫描模式:启动全幅扫描+实时采图");
				var scanTask=_scanAxis.StartContinueScanAsync(token);
				while(!token.IsCancellationRequested && 
				_scanAxis.CurrentStatus != XYScanAxis.AxisWorkStatus.LimitAlarm)
				{
					await Task.Delay(200,token);
					//await _scanAxis.StartContinueScanAsync(token);
					double progress = _scanAxis.Posx /XYScanAxis.X_MAX_LIMIT ;
					Application.Current.Dispatcher.Invoke(() => Rate = progress);
                    ShowXrayImage();
                }
				await scanTask;
			});
		}

		private async Task RunScan(Func<CancellationToken, Task> scanLogic)
		{
			//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}");
			}
			finally
			{
				CloseXray();
				CleanupCts();
			}
		}

		private void CleanupCts()
		{
			//  throw new NotImplementedException();
			_cts?.Dispose();
			_cts = null;
		}

		private async void StartPointScan()
		{
			// throw new NotImplementedException();
			await RunScan(async token =>
			{
				AddLog("点位模式:移动到坐标(50,0)");
				await _scanAxis.MoveToAsync(50, 0, token);
				if (_scanAxis.CurrentStatus == XYScanAxis.AxisWorkStatus.Arrived)
				{
					Status = STATUS_ARRIVED;
					AddLog("点位到位,等待采图");
					ShowXrayImage();
					Application.Current.Dispatcher.Invoke(() => Rate = 1.0);	
				}
			});
		}

		private async Task DeviceInitWork(CancellationToken token)
		{
			await _scanAxis.HomeAsync(token);
			if (_scanAxis.CurrentStatus == XYScanAxis.AxisWorkStatus.Fault)
			{
				Status = STATUS_FAULT;
				return;
			}
			Status = STATUS_READY;
			RefreshAxisStatus();
		}
		private void RefreshAxisStatus()
		{
			AxisStatusText = $"轴状态:{_scanAxis.CurrentStatus}|X:{_scanAxis.Posx:F1} Y:{_scanAxis.Posy:F1}";
			//OnPropertyChanged();
		}
		private async void Loading()
		{
			try
			{
				Status = STATUS_INIT;
				AddLog("设备初始化...");
				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;
					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($"Error: {ex.Message}");
				//	MessageBox.Show(ex.Message + STATUS_FAULT);

			}

		}

		private bool CanStop() => Status == STATUS_RUNNING || Status == STATUS_ARRIVED;
		//{
		//	//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 void CloseXray()
		{
			// throw new NotImplementedException();

			_isXrayOpen = false;

		}

		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 (_halconWinFormsControl?.HalconWindow == null)
			{
				AddLog("Halcon窗口未初始化,无法显示图像");
				return;
			}
			HImage xrayImage = null;
			try
			{
				Application.Current.Dispatcher?.Invoke(() =>
				{
					_halconWinFormsControl.HalconWindow.ClearWindow();
					xrayImage = new HImage();
					xrayImage.GenImageConst("byte", 800, 500);
					_halconWinFormsControl.HalconWindow.SetColor("green");
					_halconWinFormsControl.HalconWindow.DispRectangle1(120.0, 120, 380, 680);
					_halconWinFormsControl.HalconWindow.SetLineWidth(2);
					AddLog("X光图像采集+渲染完成");
				});
			}
			catch (Exception ex)
			{
				Application.Current.Dispatcher.Invoke(() =>
				{
					Status = STATUS_FAULT;
					AddLog($"图像显示异常:{ex.Message}");
				});
			}
			finally
			{
				xrayImage?.Dispose();
			}
			
		}
		public void TestShowImage()
		{
			AddLog("===单独测试 Halcon图像显示 ===");
			ShowXrayImage();
		}
	}
}

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,
            Scaning,
            Arrived,
            LimitAlarm,
            Fault
        }

        public const double X_MAX_LIMIT = 100.0;
            public AxisWorkStatus CurrentStatus { get; set; }
            public double Posx { get; private set; }
            public double Posy { get; private set; }
            public XYScanAxis()
            {
                CurrentStatus = AxisWorkStatus.Idle;
                Posx = 0;
                Posy = 0;

            }
            public async Task HomeAsync(CancellationToken token)
            {
                if (CurrentStatus != AxisWorkStatus.Idle) return;
                CurrentStatus = AxisWorkStatus.Homing;
                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;

                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(CancellationToken token)
            {
                if (CurrentStatus != AxisWorkStatus.Idle) return;
                CurrentStatus = AxisWorkStatus.Scaning;
                while (!token.IsCancellationRequested)
                {
                if (CheckLimit()) {  CurrentStatus = AxisWorkStatus.LimitAlarm;
                    break;
                }
                    Posx += 0.5;
                    await Task.Delay(200,token);
                }
                if(CurrentStatus != AxisWorkStatus.LimitAlarm)
                CurrentStatus = AxisWorkStatus.Idle;
            }
            public void NormalStop()
            {
                if(CurrentStatus == AxisWorkStatus.Moving || CurrentStatus==AxisWorkStatus.Scaning||CurrentStatus==AxisWorkStatus.Homing)
                {
                    CurrentStatus = AxisWorkStatus.Idle;
                }
                        }
            public void EmergencyStop()
            {
                CurrentStatus = AxisWorkStatus.Fault;
            }
        public bool CheckLimit() => Posx > X_MAX_LIMIT;
        public void ResetAlarm()
        {
            if(CurrentStatus==AxisWorkStatus.LimitAlarm || CurrentStatus == AxisWorkStatus.Fault)
            {
                Posx = 0;
                CurrentStatus = AxisWorkStatus.Idle;
            }
        }
        }
    }
相关推荐
李白你好1 小时前
[特殊字符] XSS漏洞演示靶场 - 交互式XSS攻击演示平台,包含钓鱼攻击、Cookie窃取演示,适合安全教育教学
前端·安全·xss
星恒讯工业路由器2 小时前
WiFi 安全技术演进全解析:从 WEP 到 WPA3 的迭代与安全蜕变
网络·安全·wifi·信息与通信
BullSmall2 小时前
软件产品【安全通信矩阵】标准文档(合规版)
线性代数·安全·矩阵
Black蜡笔小新2 小时前
私有化部署安全可控,自动化AI算法训练服务器DLTM训推一体工作站赋能产业智能化落地
人工智能·安全·自动化
ylscode2 小时前
微软 Defender 新增自动隔离功能:智能遏制网络攻击的双刃剑
网络·安全
穗余3 小时前
2026 AI x Web3 School共学营笔记-Day9-隐私是需要理解的基础能力
学习·安全·架构
网宿安全演武实验室12 小时前
当AI跑进容器:全链路容器安全检测与智能运营实
人工智能·安全·容器·k8s
老赵聊算法、大模型备案15 小时前
《人工智能应用伦理安全指引1.0》发布
人工智能·安全
Geometry Fu15 小时前
《智能终端与边缘计算》第六章 边缘计算安全平台
人工智能·安全·边缘计算·智能终端