做半导体晶圆搬移这类项目,最怕的不是运动控制写不出来,而是上位机软件在产线上三天两头出幺蛾子。我在重庆某家设备厂从头到尾跟完了一套晶圆与石墨岛搬移系统的上位机开发,从最初的原型验证到产线稳定跑料,中间踩过的坑、推翻重来的设计、调试到凌晨的现场,都值得好好复盘一遍。这篇文字就当是给自己留个档,也顺便给准备入坑工控上位机、尤其是准备碰半导体设备的朋友一些参考。
这套系统的核心任务并不复杂,说白了就是让晶圆自动从料盒出来,经过定位校正,再由机械臂放到石墨岛上,最后把装满晶圆的石墨岛整个搬走。但“不复杂”这三个字,放在半导体行业里往往意味着“容错率极低”:晶圆薄、脆、贵,石墨岛娇气,任何一次磕碰、抖动、位置偏差,轻则破片,重则整批报废。设备厂留给软件的调试时间又短,所以上位机这部分,从一开始就不能按普通工控项目来写。
1. 项目背景与需求拆解:这套系统到底在干什么
1.1 工位与工艺:晶圆怎么流到石墨岛
整个设备我们分成了四个工位。第一个工位是晶圆料盒上料区,操作员把装满晶圆的料盒推到位,扫码枪读取料盒条码,上位机确认批次信息后开始动作。第二个工位是晶圆定位校正,晶圆从料盒被吸盘取出来后,先放到一个旋转对位台上,通过光纤传感器检测晶圆边缘缺口实现角度校正。第三个工位是石墨岛装载区,机械臂带着校正好的晶圆移动到石墨岛上方,精准放入对应的槽位。第四个工位是石墨岛搬移区,装满晶圆的石墨岛由底部平移机构连同舟托一起送到缓存台,等待下一道工序取走。
这套流程看着顺序简单,实际运行时有两个非常麻烦的地方。一是晶圆在同一个设备里要经历“抓取、旋转、平移、放入”四个动作,每一步的上位机状态都要和PLC严格同步,差一个拍子就可能撞机构。二是石墨岛本质上是碳素材料,表面粗糙且容易掉粉,晶圆放进去的时候如果垂直速度控制不好,晶圆边缘会蹭到舟壁,直接产生颗粒污染。所以上位机不仅要管动作顺序,还要管每个动作的速度参数、缓冲距离和到位判定方式。
1.2 上位机到底管哪些事
这套系统的上位机不是简单的人机界面,更像是整个设备的“指挥官”。它要承担几类工作:配方管理(不同尺寸晶圆对应不同运动参数)、动作流程编排(与PLC联合执行Step by Step的流程)、数据追溯(每片晶圆的上料时间、操作人员、良品状态都进数据库)、报警/日志管理(任何异常都要有记录可查),还要与MES系统做对接,把产量和批次数据上传。
很多从别的行业转过来的工程师容易有个误区,觉得上位机就是“做界面”,把状态和按钮画出来就行。真在半导体设备上干过一轮就清楚了:界面只是最表层的东西,底层的数据一致性、状态同步、异常恢复才是灵魂。比如设备突然急停之后,上位机必须知道当前晶圆在哪一步、机械臂在什么位置、石墨岛还剩多少个空槽,这些信息一旦丢失,恢复生产的时候就会出大问题。
1.3 硬指标拆解:节拍、精度与安全
项目立项时客户给的硬指标有三个。节拍方面,整机要求单晶圆搬移循环时间不超过18秒,这意味着每个动作都要掐着秒算,上位机逻辑响应不能有明显的额外延迟;定位精度方面,晶圆放入石墨岛槽位的重复定位精度正负0.1毫米以内,虽然实际执行靠机械结构和运动控制卡保证,但上位机必须在错误的动作时序前提前拦住;安全方面,设备任意安全门打开或安全光幕触发时,整机必须在100毫秒内停止所有动作,同时上位机要记录触发点对应的流程步骤。
这三个指标放在一起,就决定了上位机架构不能是简单的“串行查询”,必须走事件驱动加状态机校验的路子。节拍要求我们用异步通信、批量数据交换,而不是一条条指令慢慢问;精度要求我们引入位置反馈比对和硬限位前的减速校验;安全要求我们把急停、光幕的联动响应做到比PLC扫描周期还要快,甚至在软件层做冗余判断。
2. 技术选型与整体架构设计:为什么C# WPF适合干这个活
2.1 对比过一轮之后还是选了C# WPF
评审技术方案时,我们把C# WPF、WinForms、Qt C++、LabVIEW都过了一遍。WinForms开发速度最快,但界面做不出现代化效果,后续客户要求看趋势曲线、3D模拟效果会非常吃力;Qt C++跨平台好、性能强,但项目周期摆在那里,团队又都是C#栈出身,硬切C++风险太高;LabVIEW做采集和简单逻辑很好,但做复杂流程管理、数据库交互、MES对接非常难受。
最后选的C# WPF,核心原因有三个。第一,WPF的MVVM模式非常适合这种“状态多、数据多、界面还要实时刷”的设备控制场景,数据绑定之后界面代码量能少一半,逻辑也好做单元测试。第二,C#在上位机生态里资源太丰富了,PLC通信库、串口库、数据库驱动、OPC UA库、条码扫描SDK、日志框架全都现成,不需要造轮子。第三,我们还要处理晶圆位置数据、报警记录、历史曲线的可视化,WPF在这方面确实比WinForms高出一个时代。
2.2 上位机程序的分层策略
整个上位机我按设备层、业务层、界面层来分,三层之间用接口隔离,谁也不要直接调用谁的内部实现。设备层负责和PLC、运动控制卡、扫码枪、传感器打交道,把外设全部封装成“可查状态、可发指令、可订阅事件”的服务;业务层负责核心流程编排,比如装舟流程、卸舟流程、手动调试流程,这些流程以状态机的形式存在;界面层只做两件事——向操作员展示状态,以及把操作员的指令转发给业务层。
这套分层在开发期可能感觉多绕了几层,但到了现场调试和后期维护的时候优势就出来了。比如PLC换了个IP地址,只需要改设备层配置;用户要求新增一个“晶圆翻面”流程,只需要在业务层加一个状态机,界面层几乎不用动。而且因为我们所有跨层调用都走接口,团队可以并行开发,我写业务逻辑的同时,UI设计师那边拿假数据先把界面做起来,最后联调合入,效率高得多。
2.3 通信方案:与PLC、运动控制卡怎么对接
这个设备的底层执行机构分成两部分:伺服轴和气缸动作归PLC管,但精密位置路径规划归运动控制卡管。上位机跟PLC走的是以太网,用的S7协议,通过S7.Net库做读写,周期约50毫秒;跟运动控制卡走的则是厂商提供的TCP/IP指令,单独建立一条长连接,指令和反馈通过异步线程处理。
通信写得多了一定会意识到一个问题:上位机不能把PLC和运动控制卡当成“一定会正确响应”的黑盒。物流行业丢一包数据无所谓,重传一次就行,但晶圆搬移设备丢一条“到位信号”,机械臂就可能带着片子直接拍下去。所以在通信层我做了三层保障:第一层是心跳机制,高频率检查连接状态,断开立即报警并触发逻辑锁;第二层是CRC校验和序列号,每条写入指令都带上自增序号,回复序号不匹配就判定通信异常;第三层是响应超时重试,但重试次数严格控制,超过3次直接暂停流程并转人工介入,绝不无限重发。
2.4 关键库与依赖取舍
项目里我最终留下的核心依赖其实不多。UI层面用了HandyControl做基础控件风格统一,省去大量控件样式定制时间;通信层面用S7.Net和自封装的一个Socket库;持久化用了SQLite加类似仓储模式的封装,单机场景不折腾SQL Server了;日志用了NLog,分文件按天滚动,同时输出到数据库一份方便报警溯源;MVVM框架用的是CommunityToolkit.Mvvm,轻量、干净,没有引入Prism那么大一坨。
这里特别想提醒一下:上位机项目里引入依赖一定得克制。有人觉得DevExpress界面漂亮就装一套,觉得Prism高大上也装一套,结果项目还没写两行,光模板生成和配置就把人绕晕了。工控现场要的是稳定和可控,第三方库越多,版本冲突、打包体积、运行期野毛病就越多。够用就好,这是我在几个项目里拿时间换回来的经验。
3. 核心功能模块实现要点:搬运动作怎么写成稳定代码
3.1 用显式状态机收编动作流程
装舟流程这种多步骤动作,最忌讳用“写在按钮点击事件里的串行代码”来实现。第一步等传感器、第二步发指令、第三步延时、第四步再等传感器……这种写法调试时没问题,但一旦中间插入一个人工干预、一次报警复位,流程位置就完全乱了。
我采用的是显式状态机,把装舟流程拆成十几个状态:初始化、扫描料盒、取片、去定位台、角度校正、取片-回机械臂、移动到舟上方、找槽位、下片、回原点、判断是否换舟等等。每个状态对应一个枚举值,状态流转由一个后台线程的状态机引擎负责。引擎每次都先判断当前状态下的前置条件,条件满足才执行动作并进入下一个状态;条件不满足就停在原地,等超时或者等外部事件。这个设计很老套,但在工业场景里就是最可靠的。
你可能会问,为什么不直接用PLC写梯形图?当然可以,PLC做顺序控制是本职。但对于多配方、多尺寸晶圆切换、有复杂数据记录的场合,逻辑放在上位机的可维护性远高于在大几百行的梯形图里翻找。我们在两者之间划定了边界:单步动作和硬安全逻辑放PLC,流程编排、参数计算和状态判断放上位机。边界清楚之后,两边调试都不互相拖累。
3.2 晶圆位置与角度校正逻辑
晶圆从料盒出来的时候,位置和角度都有偏差,直接往石墨岛槽位里放肯定会蹭片。校正依靠定位台的旋转运动和传感器检测:晶圆放到定位台上后,旋转机构让晶圆慢速旋转,安装在侧方的光纤传感器检测晶圆边缘的缺口(notch)位置,传感器信号经过PLC高速计数采集后,得到一个对应的角度偏移量,再把这个偏移量回传给上位机,上位机计算出目标补偿角度,控制旋转台反转到0度位置。
这部分逻辑的关键点在于“传感器信号的判定窗口”。晶圆边缘有缺口,但夹持位置、表面获取的光纤反射信号可能因为晶圆材质和薄膜颜色不同而变化。第一批做测试的时候,因为窗口阈值没调好,传感器把正常边缘误判成缺口,导致角度校正误差直接偏了十几度,放舟的时候差点把一片价值不菲的测试片刮了。后来我们把判定逻辑改成连续三个采样点满足缺口特征才确认触发,并且在校正完成后,又用下游对射传感器的通光状态做二次验证,才算是稳住了。
3.3 安全互锁:软件层面绝对不能省
半导体设备的客户对安全审查非常严格,光栅、安全门、急停这些外部硬互锁全部走PLC硬接线,但上位机软件层面也要做对应的软互锁。我们在软件里维护了一份“当前设备状态”全局对象,任何流程步骤在动作前都要查询该对象的状态标志:安全门是否关闭、光幕是否正常、是否有人工干预模式标志位、各轴是否已回原点。
我最深的一个教训是,软件互锁不是只做一次检查就够了,而是要在动作的每个关键节点反复检查。因为气缸动作有延迟,机械臂从未到位到到位之间可能跨越几百毫秒,在这期间如果有人推开安全门,PLC已经让机构停了,但上位机流程可能还傻站在“等到位信号”的状态。所以后来我们规定:所有等待外部响应的超时循环里,必须连带检查安全标志位和急停标志位,只要安全条件不满足,状态机立刻从当前流程退出到安全暂停状态,并保留现场以支持人工恢复。
3.4 配方管理与参数追溯
设备会跑不同尺寸的晶圆,4英寸、5英寸、6英寸都可能换线生产。每次更换要调整的参数包含取片高度、放片速度、校正角度范围、石墨岛槽距、超时阈值等十几项。直接用代码写死肯定不行,我们做了配方表,存到SQLite里,每个配方之间有独立的编号和版本号。
配方导入和切换必须做到“切换后第一片不能崩”。我是这么设计的:配方切换操作不直接改动运行参数区,而是先写入预载区,操作员点“应用配方”后系统做一轮参数范围合法性校验(比如取片高度是否在机械行程内、速度是否超过允许上限),校验通过才整体拷贝到运行参数区。这样做的好处是,即使操作员在配方里填了一个荒唐的数值,系统也能在运行前拦住,而不是让机械臂直接撞到下极限。
另外,每个配方在修改时都记录修改人、修改时间和变更说明,每次生产启动时把配方版本号、参数快照与晶圆批号一起存入生产记录表。这样做最大的好处是出问题的时候能回溯:如果某批次晶圆出现了划伤,我能查到当时用的是哪一版配方、谁改的、具体参数是多少,省掉了很多“说不清、赖不掉”的麻烦。
3.5 日志体系:从开发到运维都靠它
日志这个事我一开始没太上心,直到现场调试时电机偶尔抖一下、偶尔没反应,问题极其隐蔽,才体会到日志就是上位机的黑匣子。我用了NLog做文件日志,同时按级别往数据库里写报警记录。但真正起作用的是“流程步骤日志”——状态机每进行一次状态切换,都往日志里写一条:当前步骤、目标步骤、触发条件、相关传感器值、当前配方号、时间戳精确到毫秒。
这套流程日志在联调期间帮了大忙。比如机械臂明明已经到位置了,但上位机一直不往下走,翻日志发现是“等到位信号”状态超时,再往下查通信报文,发现PLC返回的到位位地址跟我们程序里读的偏移差了1个字节。这种问题如果不靠详尽的日志,光靠眼睛盯着看,真的可能查一整天都查不出来。
4. 上位机界面、线程与实时性优化:让界面不卡、数据不丢
4.1 界面布局与交互逻辑
半导体设备的上位机界面风格和普通软件不一样,现场操作员穿着手套,在设备旁边站着操作,界面必须大、暗色、高对比、远距离能看清状态。整体布局我分成了五个区域:顶部是设备总状态栏,显示自动/手动模式、设备告警、安全门状态、当前配方号;中间主区域是工位状态卡片,四工位各占一块,每块里有晶圆有无、当前位置、动作状态、传感器信号等关键信息;右下是操作按钮区,按钮的启用和禁用严格跟随当前流程状态绑定;右上是报警信息列表,按时间倒序,未确认报警高亮;底部是数据统计栏,显示本班次产量、当前节拍耗时、良品率。
MVVM模式下,这些状态全部通过属性绑定来更新,前端代码里几乎看不到手动赋值文本框的操作。比如工位状态卡片里的“当前位置”直接绑定到ViewModel里的CurrentPosition属性,后台线程更新属性值,界面自动刷新。这样写的好处不仅仅是代码量少,更重要的是状态展示和实际逻辑只有一个数据源,不可能出现界面显示和程序实际状态不一致的情况。
4.2 多线程设计与UI更新的正确姿势
上位机里的线程绝对不能随便写,无节制的Thread和Task会让程序变成一锅粥。我按用途划分了几类线程:主UI线程只管界面渲染;一个后台通信线程负责与PLC周期轮询数据;一个后台线程跑状态机引擎;一个线程池处理运动控制卡指令和响应。所有线程之间不直接共享数据,而是通过ConcurrentDictionary、Channel或者事件消息来传递。
WPF的UI更新还有个大坑:只有UI线程才能修改控件属性,后台线程改了会抛异常或者界面直接崩掉。早期我图省事,在通信线程里直接写界面的TextBlock,结果过一阵就偶发崩溃。后来统一改成两种方式:一种是数据绑定,ViewModel实现通知接口,后台线程修改属性,界面自动更新;另一种是必须直接操作UI元素时,用Application.Current.Dispatcher.BeginInvoke把操作封送到UI线程。这里要记得用BeginInvoke而不是Invoke,BeginInvoke是异步的,不会把后台线程阻塞在等待UI执行上。
4.3 大数据量下的实时曲线与历史曲线
设备有一个页面专门显示晶圆搬移过程的实时数据,包括各轴位置、速度、IO状态变化。这部分数据如果直接刷新到UI里,一秒钟几十次更新足够让界面卡成PPT。我采用的方案是“UI渲染降频+数据存储全量”:底层数据采集线程全量保存到内存环形缓冲区,同时以500毫秒一次的频率发布给前端刷新;查询历史时再从数据库读取。
曲线绘制用的OxyPlot库,它属于轻量级且性能不错,WPF里集成很顺。但OxyPlot也不是拿来即用就完了,大量数据点一次性画出来照样卡。做法是做了抽稀处理,比如画一个小时的历史曲线,屏幕上大概只有一千个像素宽,我就在读取数据时按显示宽度做等比例抽样,保证喂给图表的点数在一千左右,这样缩放、拖动都非常流畅。抽稀算法对趋势图影响很小,但性能差异是数量级的。
4.4 配方与数据库读写不能阻塞主流程
上位机里有个容易忽略的性能点:数据库读写。假设每片晶圆都要插入一条生产记录,机械动作周期才18秒,如果某次插入因为磁盘繁忙或数据库锁而耗时两三秒,整条流程就被拖住了。我的处理方式是把所有数据库写操作放入一个独立的写队列,后台线程按顺序消费,即使某次写失败了也只是记录到内存日志,不影响设备动作。设备机械流程上确实需要查数据库的场景,比如校验批次是否存在,则采用异步查询加超时兜底,查询失败时给出默认结果并提醒操作员。
内存里我也专门维护了几个常用数据集,比如配方列表、报警字典、当前批次是否已校验过等,这些数据首次加载后一直驻留内存,避免频繁查库。毕竟上位机不是高并发网站,实时性比数据新鲜度重要得多。
5. 调试阶段的高频问题与避坑实录
5.1 PLC通信偶尔掉线,表现为数据卡住不动
联调第三天遇到一个诡异问题:上位机界面上的所有PLC点位数据时不时全部冻结,过几秒又自己恢复,期间操作员点任何按钮都没反应。排查过程非常折磨,一开始怀疑S7.Net的读写线程卡死,加了超时和多线程重连也没解决;后来打开PLC诊断发现,通信偶尔中断的原因是上位机请求太频繁,PLC的通信负载接近上限,偶发拒绝连接。
解决方法是把原来的“每50毫秒读一次全部点位”改成“分层读取”:高频关键点位(安全门、急停、轴到位)保持50毫秒周期,中频点位(传感器、气缸状态)降到200毫秒,低频点位(计数器、报警明细)500毫秒一次。同时写入操作不走周期轮询,改用事件触发。这一改之后通信掉线再也没出现过。这也算一个通用经验:上位机和PLC通信,不是读得越快越好,读得巧才是关键。
5.2 晶圆定位偶发偏移,最后发现是光源污染
定位校正模块打样时一切正常,上了产线后却开始偶发偏移超差,而且毫无规律。起初怀疑机械振动影响传感器固定座,紧了一遍螺丝、加了防松垫圈没用;怀疑晶圆薄膜材质影响反射信号,但换了几款料问题依旧。后来一次偶然观察发现,设备旁边有工人在进行焊接作业,弧光辐射正好打在传感器上,导致传感器误触发。
这个问题的根治方案是给光纤传感器加装遮光罩,并且把信号判定从“边沿触发”改成“窗口内持续有效”,同时让上位机在收到角度信号后做合理性校验,如果校正结果跟上一片差值超过设定阈值,就自动暂停并提示操作员确认。这件事给我留下的经验是:半导体现场环境干扰因素非常多,传感器信号必须从机械、电气、软件三层同时设防,任何单层防护都有被击穿的可能。
5.3 WPF界面卡顿与内存缓慢增长
设备持续运行几天后,内存占用会从最初的300多兆缓慢爬到两三个G,最终只能重启软件。定位到两个问题:一是事件订阅没有解除,状态机引擎给界面层发送事件时,界面订阅了但页面关闭时没有退订,造成对象无法释放;二是有个定时器在后台调用了一个高频率的本地数据库查询,返回的DataTable没有及时清理。
处理方式很朴素:所有事件订阅都在ViewModel的OnDeactivated时解除;数据库中查询改成只读的DataReader并立即释放;定时器也统一改成Microsoft.Toolkit.HighPerformance里的周期任务封装,方便统一取消和释放。改完之后跑了三天三夜压力测试,内存稳定在一个合理范围内。关于WPF上位机,我想多说一句:内存泄漏是最不容易在开发期被发现的问题,一定要在项目交付前安排一次长时间的老化测试。
5.4 设备报警恢复后的流程继续问题
设备触发报警后,操作员排除故障、复位报警,然后希望流程能接着跑而不是从头再来。这个需求很简单,但实现起来容易踩坑。最初我把报警复位设计成“清空报警标志位后就从暂停状态继续执行”,结果有几次在步骤条件已经不满足的情况下(比如晶圆已经被操作员手动拿走了),流程还傻傻地往下走,差点发生机构空夹。
后来我改成“报警恢复后的强制状态校核”:设备复位后,上位机先发起一次全量状态扫描,比对当前各传感器位置与状态机预期是否一致;不一致时给操作员列出差异,让操作员选择是“按当前实际继续”还是“回安全原点重新开始”。虽然操作员多了一步操作,但这一步能避免很多不可预料的机械动作。在我的经验里,自动化设备安全领域,宁可流程走慢一点,也不要让机器在状态不确定时乱动。
5.5 部署与版本管理的几条心得
上位机程序的部署,我建议直接用安装包(Inno Setup或WiX都行),把运行环境、依赖库、数据库初始化脚本、配置文件模板全部打进去,装到工控机上之后一键初始化。配置文件里不存明文通信密码,至少做个简单的加密处理,避免设备间互相拷贝配置导致混乱。
版本管理更是血泪教训。现场调试期间一天可能改三四版,如果不做好版本管理,出了bug根本不知道是哪一版代码跑在现场。我们用Git做版本管理,并且严格要求每次发版打Tag、写Release Notes。另外,上位机启动时把版本号显示在标题栏和维护页面里,让现场人员一截图就能反馈是哪个版本,排查效率直线上升。
我在这个项目里最深的感受是:C# WPF写上位机,技术难度不是最高的,难的是对整个设备流程、现场环境和操作习惯有足够的敬畏心。很多问题看起来是软件问题,根子却在机械结构或者电气接线上;很多需求看上去只是加个按钮,背后却牵动整个状态机的流转。上位机开发者的价值,不只是把界面画出来、把通信调通,而是要把整个设备的逻辑吃透,用代码把设备的行为约束在安全、稳定、高效的轨道上。
最后分享一个小技巧:在状态机引擎里加一个调试模式,可以在界面上强制修改某个传感器的模拟值,这样很多流程逻辑不用频繁手动顶传感器、拨气缸,直接在软件里就能闭环测试。这个小工具在我后续好几个项目里都在用,省下来的调试时间相当可观。