做设备上位机这行的人应该都有体会:项目交付最怕的不是功能做不出来,而是换一个人接手就完全看不懂流程,现场调试被逼着改源码。最近我把一套C#植板控制系统的源码从v1.4整理到v2.1,核心变化是把Halcon视觉定位、PLC通讯、运动控制和流程编排全部整合进一个框架里,操作端用拖拽编程,现场调流程不用碰代码。这篇文章把架构设计、技术选型、关键实现和踩坑记录完整拆一遍,给正在做视觉引导设备上位机的同行一份可参考的落地版本。
1. 项目整体设计与思路拆解
1.1 植板控制系统到底解决什么问题
植板工序在电路板类自动产线上很常见,本质是把待安装元件或子板精准放置到主板指定位置。传统做法靠机械治具硬定位,换型困难、精度不够。v2.1这套系统做的是视觉引导植板:相机拍摄工作台上的板面定位Mark点,Halcon识别位置偏差,上位机算出补偿量后通过PLC控制运动机构执行抓取与对位动作,最后把结果上报产线系统。
系统从功能上拆成三块:视觉定位模块、PLC动作执行模块、流程调度模块。视觉模块负责“眼睛”,PLC负责“手脚”,流程调度负责“大脑”。其中流程调度是v2.1变化最大的部分,用拖拽编程把视觉、通讯、运动、判断逻辑组合成一条可配置的执行链,设备操作员在工程师指导下就能重新编排动作顺序,不再需要改代码重新编译。
这套系统适合谁参考?简单说三类人:一是做自动化设备上位机开发的工程师,想看看C#里怎么把Halcon和PLC揉到一个框架里;二是做视觉集成项目的朋友,尤其被“视觉定位结果如何准确交给PLC执行”这种问题困扰的;三是设备公司里负责交付和维护的工程师,需要用一套能减少售后返工的上位机架构。
1.2 方案选型:C#、Halcon、PLC三者如何分工
选型之前最该想清楚的是:哪些事放在上位机做,哪些事放在PLC里做。很多项目死在职责不清,上位机把伺服点动也做了,PLC把图像结果显示也接了,两边代码耦合严重,一改就崩。
v2.1的分工原则是:上位机负责一切“需要频繁调整”的智能逻辑,PLC负责一切“必须快速响应且涉及安全”的执行逻辑。图像处理、工艺参数、流程编排这些会随产品频繁变化的,放在上位机;急停、限位、伺服使能、气缸动作这类必须毫秒级响应且和生产安全强相关的,放在PLC里。视觉单独放PLC里不现实,因为Halcon的算法模型需要文件管理和参数调试,这些场景上位机有天然优势。
语言选用C#而不是C++,原因很直接。Halcon本身提供.NET接口,C#调用算子在性能上损失很小,但开发效率高一个数量级。工业设备上位机大量工作是写界面、配参数、做日志、对接MES,这些恰恰是C#的强项。再加上现代C#的异步特性(async/await)处理PLC通讯和相机采集非常顺手,代码写出来也容易维护。v2.1在Visual Studio里保持工程分层清晰,UI层、流程引擎层、视觉封装层、通讯层彼此隔离,后文会详细展开。
2. 核心细节解析与实操要点
2.1 Halcon视觉框架的两种集成路线
Halcon集成到C#项目里有两条主流路线,很多人一上来就选错了。第一条是把Halcon的算法脚本导出成C#代码,编译进工程;第二条是用HDevEngine引擎动态加载.hdev脚本文件,运行时执行。
两者差别很大,选错后期维护很痛苦。
| 对比项 | 导出C#代码 | HDevEngine动态加载 |
|---|---|---|
| 执行性能 | 较高,无解释开销 | 略低,有少量解释开销 |
| 算法修改方式 | 需改代码重新编译 | 直接改.hdev脚本文件,热加载 |
| 部署复杂度 | 低,dll打包即可 | 需带上脚本文件和对应算子库 |
| 现场调参体验 | 差,每次改都得重启软件 | 好,改完脚本即刻生效 |
| 适合场景 | 算法稳定、批量交付 | 现场调试验证、多产品配方 |
我的经验是,量产交付版本用导出C#代码,视觉调试版本用HDevEngine。v2.1实际采用折中方案:正式流程里把核心定位算法导出成C#方法,但给每个产品保留一个.hdev脚本调试入口,现场视觉工程师可以先在脚本里调好参数,再一键同步到C#工程配置里。这里有一个关键细节:Halcon导出C#代码后,算子内部的错误处理不会自动翻译成异常,必须在调用处包一层错误码转换。我遇到过导出代码里某个算子返回负错误码,但C#侧完全感知不到,结果视觉结果全错,排查了一整天。
2.2 拖拽编程的技术实现方案
拖拽编程听起来炫酷,本质上是做了一套可视化的流程表达引擎。v2.1没有用太重的第三方图形框架,而是自研了一个轻量级节点编辑器。每个工艺步骤是一个节点,节点之间用连线定义执行顺序,额外的分支条件用判断节点实现。
节点类型设计了五种,覆盖植板控制的全部场景:
| 节点类型 | 职责 | 关键参数 |
|---|---|---|
| 视觉定位节点 | 触发相机拍照并返回坐标偏移 | 相机编号、模板名称、超时时间、分数阈值 |
| PLC动作节点 | 下发指令给PLC并等待到位 | 指令码、执行轴、到位确认信号名、超时时间 |
| 条件判断节点 | 根据定位结果或PLC状态走不同分支 | 比较对象、运算符、阈值、跳转节点ID |
| 延时节点 | 等待固定时长或等待信号 | 等待时长或信号名称 |
| 结果记录节点 | 把关键数据写入本地日志或上报产线 | 数据源、字段映射、上报地址 |
节点底层的核心结构很简洁,一个抽象基类加上两个公共数据结构:
public abstract class FlowNode { public string NodeId { get; set; } public string NodeType { get; set; } public Dictionary<string, object> Params { get; set; } public string NextNodeId { get; set; } public abstract NodeResult Execute(FlowContext context); } public class FlowContext { public Dictionary<string, object> Variables { get; } = new(); public CeaseFlag Cease { get; } = new(); public FlowLogger Logger { get; } = new(); }拖拽编辑器界面上拖出来的是节点框,保存时序列化成上面的类结构,执行引擎加载这个结构按NextNodeId顺序执行。参数区做成表格控件,不同节点类型显示不同字段。实操中要注意两个坑:一是节点连线极易形成循环引用,执行时会把设备带飞,必须保存前做环路检测;二是节点的Params里的值从界面拿回来都是字符串,执行时要按声明类型做转换并校验,否则用户填错数据类型根本发现不了,等执行到那一步直接抛异常。
2.3 PLC联动与指令下发机制
上位机与PLC通讯,第一原则是“上位机可以请求,但不能直接控动作”。v2.1里上位机所有动作类操作都通过指令握手协议完成:上位机写好一个请求字,PLC看到请求字后解析动作指令,执行完毕后写回完成字和结果码,上位机轮询到完成字后再走下一步。这不是保守,是安全上的硬要求。PLC是执行安全逻辑的最后一道闸门,上位机意外崩溃时PLC必须自动停下来。
通讯协议选型上,实际项目中最常遇到的是S7协议、Modbus TCP和Fins TCP三类,取决于现场PLC品牌。v2.1在上位机里抽象了一个通讯接口层,不直接绑死某一种协议,接口只暴露读写DB块和M区地址的操作。内部用异步轮询机制,通讯库自己维护Socket连接状态,断线自动重连,重连失败时会在上位机主界面弹出明显的红色状态条并自动停止流程执行。
握手三帧结构是这么设计的:请求帧(上位机写指令码+参数)、执行帧(PLC读到后开始执行)、完成帧(PLC写回完成状态+执行结果)。上位机发起请求后计时,超时时间按动作类型区别设置——气缸动作设5秒,伺服走位设30秒。超时之后上位机不是直接报错完事,而是先读取PLC的当前设备状态字,把设备状态、报警码、当前工位信号一并抓回来,这些信息对现场排查非常有帮助。我自己调试时最容易忽略的是通讯数据块的字节对齐问题,比如S7协议里Bool型和Int型交错排在同一个DB块,C#侧解析时位偏移错一位,读出来全是乱的,这种问题没有捷径,只能对照PLC侧变量表逐个对拍。
3. 实操过程与核心环节实现
3.1 环境准备与工程骨架
整套系统的开发环境是Visual Studio 2022,目标框架用的.NET Framework 4.8。为什么不用.NET 6/8?主要考虑现场工控机系统往往是Windows 10 IoT版或者Windows 7(老旧设备还在跑),.NET Framework 4.8兼容性更省心。Halcon用的是当前主流版本的Development和Runtime两套安装包,开发机装全量,部署机只需要Runtime。
工程按四层划分,这个分层在v2.1里是重构过的,之前v1.x把代码全塞在窗口的code-behind里,维护成本高到离谱:
- UI层:WPF界面,负责参数配置、流程节点拖拽、实时状态显示、日志浏览。
- FlowEngine层:流程节点定义、流程加载、执行调度、状态机管理。
- ModuleVision层:Halcon算子封装,对外暴露拍照定位、模板管理、坐标转换、结果返回。
- ModulePLC层:PLC通讯封装,对外暴露读写接口、指令握手、重连管理、状态监听。
分层带来的直接好处是换相机品牌不用动流程引擎,换PLC品牌不用动视觉代码。UI层和业务层之间通过接口解耦,举个实际例子:流程引擎执行视觉节点时,只管调用Vision接口的LocateProduct(imageId, productName)方法,完全不关心底层是Halcon还是其他视觉算法库,也不关心相机品牌是什么。
3.2 视觉定位链路搭建
植板系统的视觉定位链路可以精简成四步:采集图像、标定映射、模板匹配、坐标补偿。v2.1里先做的是相机标定,用Halcon的标准网格板标定算子生成相机内参和畸变系数,再做手眼标定建立像素坐标到机械坐标的转换关系。这两个标定做不好,后面的匹配都是白费。
模板匹配这块,我推荐用形状匹配(Shape-Based Matching)而不是灰度匹配。植板场景的工件有金属反光、背景复杂,灰度匹配非常容易被光照扰动干扰,而形状匹配对灰度变化不敏感,稳定性高一截。核心代码段大概是这样的:
HTuple hv_ModelId = new HTuple(); HOperatorSet.ReadShapeModel("config/mark_model.shm", out hv_ModelId); HObject ho_Image = new HObject(); ho_Image.ReadImage(currentImagePath); HTuple hv_Row, hv_Col, hv_Angle, hv_Score; HOperatorSet.FindShapeModel( ho_Image, hv_ModelId, -0.1, 0.2, // 角度搜索范围 0.6, // 最小匹配分数 1, 0.5, // 匹配数量与亚像素精度 "least_squares", // 贪婪度参数 0, 0.9, // 最大重叠度 out hv_Row, out hv_Col, out hv_Angle, out hv_Score);拿到匹配到的行列坐标和角度后,紧接着做坐标补偿:通过Halcon的VectorAngleToRigid算子计算从模板位置到实际位置的刚性变换矩阵,再把目标植板点坐标变换到当前图像坐标系下,最后叠加之前标定出的手眼矩阵,输出的是PLC能直接用的机械坐标偏差量。
这里有个操作细节容易被忽略:FindShapeModel匹配时搜出来的可能是多角度、多实例,现场植板往往要求唯一目标。我的处理是取分数最高且大于阈值的那个匹配结果,并把第二高分与第一高分的分数差也一并校验——如果两者分数太接近,说明现场可能存在干扰特征,这时候宁可判定定位失败,也不赌一把。这个逻辑救了我很多次,尤其当板面有两颗非常接近的Mark点时。
3.3 流程节点的挂接与联调
流程引擎实现里,最有价值的是“单步执行”和“条件断点”两个调试功能。拖拽流程如果只能一键跑完,出了故障根本没法查。v2.1在流程引擎里内置了调试模式:每条流程节点可以单独执行、跳过、设定异常时自动暂停。联调时我习惯把流程拆成三段来调试:先跑视觉定位节点,看坐标输出是否正确;再跑PLC动作节点,看设备是否按照要求动作;最后串起来跑整条流程,看节点间的数据传递是否流畅。
节点间的数据传递是个重要设计点。v2.1用一个全局变量字典做数据总线,视觉节点输出的Row、Col、Angle写入字典,判断节点从字典里读取这些值做比较,PLC动作节点把补偿量从字典里取出来拼进指令参数。这样做的好处是每个节点都是纯函数式执行,输入输出明确,调试时看到变量字典就一目了然。
联调时踩得最深的一个坑是PLC动作节点的“到位确认”逻辑。最开始设计是PLC完成动作后置位完成信号,上位机检测到信号后进入下一个节点。但现场发现某些气缸动作响应太快,PLC置位完成信号后紧接着气缸就复归了,等到上位机轮询时信号早就没了,整个流程卡死。后面改成了双缓存握手:PLC完成的完成字一直保持到上位机下发“确认收到”指令后才会清零,这样即使轮询有延时也不会丢状态。
3.4 参数配置与安全策略
v2.1的参数管理采用“配方文件”机制。每款产品的视觉模板、定位容差、PLC速度参数、流程节点序列都保存在独立的配方文件夹里,换产品时一键切换,不需要重启软件。配方文件用JSON格式存储,文件名加上版本号,配合上位机的导入导出功能,新项目调试时先在试产机上调出一套配方,再导出拷贝到量产机。
安全策略这块,上位机不能替代PLC安全回路,但可以减少人为误操作。v2.1做了三级权限:操作员只能看状态、启动停止;工程师能编辑工艺参数和配方;管理员才允许修改流程和恢复出厂配置。前面说的急停逻辑全部走PLC硬回路,上位机只做状态显示和记录。
针对安全还有一个容易被忽视的点:上位机软件启动时一定要做“设备状态一致性检查”。我遇到过设备中途断过电,上位机不知情,PLC已经停在某个中间姿态,上位机又自动把流程跑起来了,差点撞机。v2.1现在启动流程前会强制读取PLC的设备状态字和上次停机原因,如果不是正常停机状态,弹出确认框让操作员确认安全后再继续。这个设计成本很低,但真能挡掉事故。
4. 常见问题与排查技巧实录
4.1 视觉定位偶发偏移
这是植板类设备现场反馈频率最高的一个问题。现象是大部分时间定位精准,偶尔一批板子出现偏移,复位后重做又能通过。我排查这类问题的固定套路是按“外部环境、硬件状态、算法参数”三层来筛。
先看外部环境——现场照明是否变动、板面有没有水汽和油污、产线上方有没有其他光源频闪干扰。再看硬件状态——相机固定螺丝有没有松动、镜头有没有脏污、标定板是否被碰过。最后看算法参数——搜索角度范围设得太窄、匹配分数阈值设定过高、ROI区域过大把干扰特征包进去了。v2.1在视觉模块里把每次定位的分数、变换矩阵、Mark点坐标都存了日志,还专门加了一个“保存当前帧图像”的开关按钮,现场出问题时让操作员按一下,把图像发回来比对,大多数情况一眼就能看出问题。
另外一个容易被忽略的是“运动部件回差”。视觉算出来的坐标没问题,但机构执行时因为皮带磨损或者连轴器松动,实际到位位置和指令位置差了几个像素。这种问题靠视觉模块修不好,必须做机械补偿或者定期执行回差校准。我的做法是把视觉补偿后的实际到位结果在设备连续跑空循环时记录下来,画成散点图,看偏移方向是否稳定,稳定偏移直接加固定补偿,随机偏移就要查机械结构了。
4.2 PLC通讯超时与断线重连
通讯超时是上位机与PLC联调时绕不开的坎。现象有几种:周期性报超时但设备没停;大流量读写时偶发超时;长时间运行后彻底断线无法自动恢复。
先说周期性超时——一旦出现就要检查轮询周期。上位机同时读几十个点位,每个点位一次独立请求,通讯周期必然拉长,PLC侧扫描周期被上位机请求串行化,越轮询越乱。我的解决办法是改用批量读写,一个请求读整个DB块,而不是逐点位读取。通讯不用同步阻塞方式,全部改成异步轮询,避免界面线程卡死。
长时间运行后彻底断线,常见原因是网络重连逻辑只做了TCP层重连,没做PLC会话层握手恢复。PLC侧通讯连接断开后,上位机重连需要重新建立会话并且恢复数据订阅。v2.1的做法是重连成功后强制做一次全量状态同步——把所有DB块数据重新读一遍,刷新到上位机内存里,保证界面显示和PLC一致。这个细节非常关键,不然界面显示“运行中”,PLC实际早已停机,操作员看到的全是假数据。
4.3 拖拽流程执行顺序错乱
拖拽流程执行顺序错乱,听起来不可能,但真会出。最典型的是异步回调导致的乱序:视觉定位节点还没执行完成,PLC动作节点就拿到了上一次的旧坐标数据。
排查下来根因是节点执行的并发控制没做好。v2.1执行引擎用了单一调度线程,所有节点都在这个线程里顺序执行,杜绝并发问题。视觉节点和通讯节点内部虽然是异步任务,但外部必须等待异步任务返回,用async/await包装。这里有个用async/await时容易犯的致命错:在节点执行方法里如果用到了共享的HObject或者HTuple对象,异步等待期间对象可能被另一个节点修改,结果执行结果全乱。处理方法是视觉相关对象保持线程隔离,宁可创建局部副本也不能共享。
另一个流程乱序的原因是外部信号触发。比如PLC侧来了一个中断信号,上位机收到后立刻执行某个跳转逻辑,打断了当前流程。v2.1把外部信号处理统一放进信号队列,不直接打断流程,而是等当前节点执行到安全点后再响应跳转。否则现场流程执行到一半,一个急停恢复信号就会让整个状态机错乱。
4.4 v2.1版本升级的一些心得
从v1.4到v2.1,最值得记录的升级不只是功能,而是解决了两个老问题。第一个是旧版流程逻辑散落在窗口事件里,v2.1收拢到流程引擎后,新增工位动作只需要拖节点,不用再写代码。第二个是把视觉参数从代码里抽出来放到配方文件里,之前换产品要改代码、重编译,现在现场工程师就能处理。
升级过程中最麻烦的是配置迁移。v1.4的相机参数存在自己的配置文件里,格式与v2.1不同,直接覆盖会把现场已经调好的视觉标定数据冲掉。最后写了迁移工具,读旧配置转存成新格式,再人工核对关键项。这里给想重构系统的朋友一个建议:配置文件格式升级时一定做兼容读取,就是新版本程序能读旧格式并存一份备份,否则现场升级就是大型灾难现场。
还有一点版本升级的心得,改代码前先把接口冻结。v2.1开发过程中有段时间界面和流程引擎并行开发,接口天天变,两边互相等,进展很慢。后来先花三天把接口定义清楚、写模拟实现,UI侧可以假数据联调,引擎侧用单元测试跑逻辑,并行效率高出一大截。
5. 复盘与扩展思路
v2.1这套版本在几个现场设备上跑了半年,感受最深的不是视觉有多准、PLC通讯有多快,而是“可维护性”带来的交付效率提升。以前售后去现场调一个新产品的工艺,工程师要带电脑、连开发环境、翻代码、改参数、重新部署,动不动一个下午。现在专职调试员用拖拽流程半小时内就能完成一条新工艺的编排,售后的工作量大幅下降。
这个架构还能往两个方向扩展。一个是对接产线MES系统,把每个工位的植板结果、视觉图片、PLC状态数据汇总上报,做全流程追溯,这套系统的数据接口层已经预留了位置;另一个是多机协同,多套设备通过一台调度上位机统一编排任务,流程引擎的节点化设计天然支持远程指令下发,改造成本不高。
最后说一个很多人都会忽视的点:上位机软件的“操作日志”一定要做扎实。不止是软件日志,还包括操作员每一步操作动作的记录,什么时候改了哪个参数、什么时候手动启动了流程、设备报警时谁在场做什么操作,这些都是设备异常追溯的关键证据。v2.1的系统里,日志全部落盘,按天切分,保留三个月。看似不起眼的小功能,在设备交付后的质量争议和工艺追溯中起了大作用。做设备上位机久了就明白,稳定可靠不是哪一套算法带来的,而是把所有细节都抠到位的结果。