一、工作模式(设备怎么干活?)
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;
}
}
}
}
