
1. 项目缘起与整体设计思路半导体制造车间里的晶圆搬移和石墨岛转运听起来像是两个八竿子打不着的工序但在化合物半导体、功率器件以及部分先进封装的产线上它们经常出现在同一个自动化工作站里。晶圆需要从卡匣Cassette里一片片取出经过对准、检测、缓存再送回或流转到下一站石墨岛则作为承载盘或者散热基板需要在不同工位之间精准取放。这两类物料的共同点是价值高、易碎、对颗粒污染和位置精度极其敏感人工搬运基本等于报废率上升。我接到的这个项目就是给一条中小尺寸产线做一套搬移上位机系统。需求方给的原话很朴素“要能控制机械手把晶圆和石墨岛从A点搬到B点能看状态能报警能记录。”但真正落地的时候你会发现这句话背后藏着一整套运动控制、视觉定位、通讯协议、数据追溯和异常处理的工程问题。这套系统我选的是C# WPF的技术栈。为什么不用 WinFormWinForm 做工业上位机确实快拖控件、写事件、跑起来就能用但一旦界面涉及多轴状态实时刷新、动画示意、复杂数据绑定和高分屏适配WinForm 的控件刷新机制和布局能力就会拖后腿。WPF 的数据绑定、依赖属性、模板化控件和硬件加速渲染在工业 HMI 场景里优势非常明显。尤其是 MVVM 模式下界面和逻辑解耦后期改需求不用把代码翻个底朝天。整体架构我分成四层设备通讯层、运动控制层、业务逻辑层、界面展示层。设备通讯层负责和 PLC、机械手控制器、视觉相机、RFID 读写器打交道运动控制层封装轴运动、点位管理、速度规划和安全互锁业务逻辑层处理搬移流程、状态机、配方管理和数据记录界面展示层就是 WPF 做的操作台包含手动调试、自动运行、参数配置、报警查询和日志追溯几个大模块。这个分层不是为了好看而是因为半导体设备的上位机经常要面对“今天加一个传感器明天换一种机械手”的情况。如果通讯和业务揉在一起改一处动全身。分层之后只要接口定义清楚底层换品牌、换协议上层业务基本不用动。提示工业上位机项目里“能跑”和“能稳定跑三个月”是两回事。前期架构上多花两天做解耦后期维护能省两个月。2. 核心硬件选型与通讯方案拆解2.1 为什么选西门子 PLC 做底层控制这套系统里PLC 负责的是实时性要求最高的部分气缸动作、真空吸盘开关、安全门锁、急停链路、轴使能和限位信号。上位机再快也是 Windows 系统调度延迟不可控把安全相关的东西交给 PLC 是基本原则。我选的是西门子 S7-1200 系列原因很实际产线现有设备大多是西门子生态备件通用电气工程师熟悉调试配合成本低。上位机通过S7 通讯协议和 PLC 交换数据读写 DB 块里的状态字和控制字。C# 这边连接西门子 PLC常用的库有 S7.NetPlus 和 HslCommunication。我最终用的是S7.NetPlus轻量、开源、API 直观读写 DB 块和 M 区都很方便。核心代码大概长这样using S7.Net; private Plc _plc; public bool ConnectPlc(string ip, int rack, int slot) { _plc new Plc(CpuType.S71200, ip, rack, slot); _plc.Open(); return _plc.IsConnected; } public ushort ReadDbWord(int db, int startByte) { var bytes _plc.ReadBytes(DataType.DataBlock, db, startByte, 2); return (ushort)(bytes[0] 8 | bytes[1]); } public void WriteDbBit(int db, int byteAddr, int bitAddr, bool value) { _plc.Write(DataType.DataBlock, db, byteAddr * 8 bitAddr, value); }这里有个坑S7 协议的数据是大端序而 C# 在 x86 平台是小端序。读 16 位、32 位数据的时候必须手动做字节序转换否则读出来的速度值、位置值全是乱的。我一开始没注意调试的时候发现轴速度显示成 65535查了半天才发现是字节序问题。2.2 机械手和视觉的通讯方式机械手用的是 EtherCAT 总线控制器上位机不直接控制关节而是通过TCP Socket 发送点位指令和动作命令。控制器那边定义了一套简单的 ASCII 协议比如MOV P1, SPEED50表示以 50% 速度移动到 P1 点GRIP ON表示吸盘开启。上位机收到OK或者ERR,code作为应答。视觉相机负责晶圆圆心定位和缺口Notch角度检测。相机厂商提供了 C# SDK回调里返回图像和计算结果。这里我遇到过一个典型问题回调线程和 UI 线程冲突。相机 SDK 的回调是在非 UI 线程触发的如果直接在回调里更新 WPF 界面会抛InvalidOperationException。解决办法是用Dispatcher.Invoke或者Dispatcher.BeginInvoke把更新操作切回 UI 线程。private void OnVisionResult(VisionResult result) { Application.Current.Dispatcher.BeginInvoke(new Action(() { WaferCenterX result.CenterX; WaferCenterY result.CenterY; NotchAngle result.Angle; })); }RFID 读写器用于识别卡匣和石墨岛托盘的身份走的是串口通讯。WPF 里串口操作我封装了一个SerialPortService打开、关闭、发送、接收事件都在里面处理业务层只订阅解析后的数据。2.3 通讯异常的处理策略工业现场最怕的不是设备坏而是通讯断了但界面还显示正常。我的做法是给每个通讯通道加心跳检测和超时重连。PLC 每 500ms 读一次心跳字连续三次读不到就标记为断线界面变灰并弹报警。Socket 连接设置ReceiveTimeout超时后自动重连重连成功前不允许下发新指令。注意重连逻辑一定要加退避策略。我见过有人写while(true) { try connect }结果网络故障时 CPU 直接跑满。正确做法是失败后等待 1s、2s、4s、8s 递增上限 30s。3. WPF 界面架构与 MVVM 落地细节3.1 为什么工业 HMI 也要认真做 MVVM很多人觉得工业界面能看就行MVVM 是互联网那套花架子。但实际项目里工业界面的状态复杂度一点不比业务系统低轴状态、IO 状态、报警列表、配方参数、流程步骤、日志信息全部要实时刷新。如果全写在代码后台里一个按钮事件里改十几个控件属性后期加一个状态就得翻遍所有事件处理函数。我用的是CommunityToolkit.Mvvm以前叫 MVVM Toolkit轻量、源生成器写起来舒服不用像 Prism 那样引入一大堆依赖。核心 ViewModel 大概有这几个MainViewModel主窗口管理导航和全局状态ManualViewModel手动调试轴点动、气缸控制、吸盘开关AutoViewModel自动运行流程启动、暂停、停止、状态显示RecipeViewModel配方管理晶圆尺寸、速度、延时参数AlarmViewModel报警查询和处理LogViewModel操作日志和搬移记录每个 ViewModel 通过构造函数注入服务接口比如IPlcService、IRobotService、IVisionService。这样单元测试的时候可以 mock 掉硬件直接测业务逻辑。3.2 数据绑定中的实时刷新与性能平衡工业界面里最典型的需求是轴当前位置每 100ms 刷新一次报警状态变化立即刷新配方参数只在加载时刷新。如果所有数据都用INotifyPropertyChanged全量通知界面会卡。我的做法是分频处理数据类型刷新频率实现方式轴位置/速度100ms定时器批量更新UI 线程合并IO 状态200ms位运算打包一次通知报警状态事件驱动PLC 报警字变化触发配方参数手动加载只在切换配方时通知日志列表追加式ObservableCollection 增量添加轴位置刷新我用了一个DispatcherTimer每 100ms 从 PLC 读一次所有轴的位置然后一次性更新 ViewModel 里的属性。注意不要在定时器里直接await异步读否则定时器会重入。我的做法是加一个_isReading标志位上一次没读完就跳过这一次。private bool _isReading; private async void OnTimerTick(object sender, EventArgs e) { if (_isReading) return; _isReading true; try { var positions await _plcService.ReadAllAxisPositionsAsync(); AxisXPosition positions.X; AxisYPosition positions.Y; AxisZPosition positions.Z; } finally { _isReading false; } }3.3 界面布局与 HandyControl 的使用WPF 原生控件做工业界面样式确实朴素。我引入了HandyControl它提供了一套现代化的控件库按钮、文本框、卡片、步骤条、消息提示都有现成的工业风格样式。安装方式很简单NuGet 搜HandyControl安装然后在App.xaml里合并资源字典Application.Resources ResourceDictionary ResourceDictionary.MergedDictionaries ResourceDictionary Sourcepack://application:,,,/HandyControl;component/Themes/SkinDefault.xaml/ ResourceDictionary Sourcepack://application:,,,/HandyControl;component/Themes/Theme.xaml/ /ResourceDictionary.MergedDictionaries /ResourceDictionary /Application.Resources主界面布局我分成三块左侧导航栏、中间主操作区、右侧状态面板。中间主操作区用ContentControl配合 DataTemplate根据当前 ViewModel 自动切换视图。这样手动、自动、配方、报警几个页面切换时不需要手动创建和销毁控件。状态面板常驻显示当前登录用户、设备模式手动/自动、PLC 连接状态、机械手连接状态、相机连接状态、当前配方名称、系统时间。这些信息用颜色区分状态绿色正常、黄色警告、红色故障操作员扫一眼就知道设备能不能动。3.4 字体图标与视觉细节工业界面里图标比文字更直观。我用了FontAwesome的 WPF 封装通过 NuGet 安装FontAwesome.WPF然后在 XAML 里直接使用fa:ImageAwesome IconPlay ForegroundGreen Width24 Height24/ fa:ImageAwesome IconStop ForegroundRed Width24 Height24/按钮上配合图标和文字操作员不用记哪个按钮是干什么的。另外深色主题在工业现场更实用因为车间灯光复杂浅色界面反光严重。HandyControl 支持主题切换我在设置里留了一个选项默认用深色。实操心得WPF 界面在高分屏上如果没做 DPI 适配字会糊。在app.manifest里加上dpiAwaretrue/dpiAware和dpiAwarenessPerMonitorV2/dpiAwareness然后在App.xaml.cs里设置RenderOptions.ProcessRenderMode基本能解决大部分模糊问题。4. 搬移流程的状态机设计与实现4.1 为什么必须用状态机晶圆和石墨岛的搬移流程表面上是“取-移-放”三步但实际要考虑卡匣有没有料、吸盘真空是否建立、机械手是否在安全位、目标工位是否空闲、视觉定位是否成功、放料后真空是否释放、是否要记录追溯数据。这些条件组合起来用if-else写会变成几百行嵌套改一个条件就提心吊胆。我用的是有限状态机FSM每个状态只关心自己的进入条件、执行动作和退出条件。状态之间通过事件驱动切换比如StartCommand、VacuumOk、VisionDone、PlaceComplete、ErrorOccurred。核心状态定义public enum TransferState { Idle, CheckingCassette, PickingWafer, VacuumChecking, MovingToVision, VisionAligning, MovingToTarget, PlacingWafer, ReleasingVacuum, Returning, Completed, Error, Paused }状态机引擎我写了一个简单的StateMachineTState, TEvent泛型类支持注册状态转换和进入/退出动作。这样业务逻辑里只需要配置状态图不用写一堆switch-case。4.2 晶圆搬移的完整流程拆解以晶圆从卡匣搬到检测工位为例完整流程如下检查卡匣状态读卡匣 RFID确认型号和批次读 PLC 的卡匣在位信号和料检测信号。机械手取片机械手移动到卡匣取片位下降真空开启等待真空压力传感器达到阈值通常 -60kPa 以上。真空确认如果 500ms 内真空未建立判定取片失败机械手退回报警提示“取片失败请检查吸盘或晶圆位置”。移动到视觉工位机械手携带晶圆移动到相机上方触发拍照。视觉定位相机计算晶圆圆心偏移和缺口角度上位机把偏移量转换为机械手的补偿坐标。移动到目标工位机械手根据补偿后的坐标移动到放置位下降真空关闭吹气释放部分场景需要。放片确认读目标工位的在位传感器确认晶圆已放下。机械手返回安全位完成一次搬移记录日志状态回到 Idle。石墨岛的搬移流程类似但多了重量检测和放置平整度检测。石墨岛比晶圆重吸盘选型不同真空建立时间更长释放时可能需要延时吹气确保脱离。4.3 状态机的异常处理与恢复工业现场最怕的是流程卡在中间状态。比如机械手取片到一半真空建立了但通讯断了恢复通讯后机械手不知道自己在哪。我的做法是每个状态进入时把当前状态和关键参数写入 PLC 的保持寄存器断电重启后上位机先读这些寄存器判断是否需要回原点或者继续。异常处理分三级一级异常可自动恢复比如真空建立慢了一点重试一次。二级异常需要操作员确认比如视觉定位失败界面弹出对话框让操作员选择重试或跳过。三级异常必须停机比如安全门打开、急停按下、机械手碰撞报警。注意状态机里不要做耗时操作。我见过有人在状态执行动作里直接Thread.Sleep(5000)结果整个界面卡死。正确做法是用async/await加Task.Delay或者把耗时操作放到后台线程状态机只负责发指令和等回调。5. 数据追溯与日志系统的落地5.1 为什么半导体设备必须做数据追溯半导体行业对追溯的要求近乎苛刻。一片晶圆从进站到出站谁操作的、什么时候、用了什么配方、视觉结果是什么、有没有报警、最终放到哪个位置全部要记录。出了问题要能倒查到具体环节否则整批产品都可能被客户拒收。我用的是SQLite做本地数据库轻量、免安装、单文件适合工控机环境。表结构大概有这几张表名用途关键字段TransferRecord搬移记录Id, WaferId, Source, Target, StartTime, EndTime, ResultAlarmRecord报警记录Id, AlarmCode, AlarmText, OccurTime, AckTime, AckUserOperationLog操作日志Id, User, Action, Detail, TimeRecipeData配方数据Id, RecipeName, Parameter, ValueVisionResult视觉结果Id, WaferId, CenterX, CenterY, Angle, Time写入用SQLite 的事务批量提交不要每条记录都开一次连接。我封装了一个DataRepository内部维护一个队列后台线程每 500ms 或队列满 100 条时批量写入。这样既保证数据不丢又不会因为频繁写库影响界面响应。5.2 日志分级与界面展示日志分四个级别Debug、Info、Warn、Error。Debug 只在调试模式记录Info 记录正常流程节点Warn 记录可恢复异常Error 记录故障。界面上的日志列表用ObservableCollection支持按级别过滤、按时间排序、关键字搜索。日志文件同时写一份到本地文本文件按天分割保留 30 天。这样即使数据库损坏也能从文本文件里查。文件路径放在D:\Logs\下按yyyyMMdd建文件夹。public void Log(LogLevel level, string message) { var entry new LogEntry { Level level, Message message, Time DateTime.Now }; Application.Current.Dispatcher.BeginInvoke(() { LogCollection.Insert(0, entry); if (LogCollection.Count 1000) LogCollection.RemoveAt(LogCollection.Count - 1); }); _fileLogger.Write(entry); _dbQueue.Enqueue(entry); }5.3 数据导出与报表操作员和工程师经常需要把搬移记录导出成 Excel 或 CSV 做分析。我用的是ClosedXML库直接生成.xlsx文件支持格式化和公式。导出时让用户选择时间范围后台线程生成文件完成后提示保存路径。using ClosedXML.Excel; public void ExportToExcel(ListTransferRecord records, string filePath) { using var workbook new XLWorkbook(); var worksheet workbook.Worksheets.Add(搬移记录); worksheet.Cell(1, 1).Value 晶圆ID; worksheet.Cell(1, 2).Value 来源; worksheet.Cell(1, 3).Value 目标; worksheet.Cell(1, 4).Value 开始时间; worksheet.Cell(1, 5).Value 结束时间; worksheet.Cell(1, 6).Value 结果; for (int i 0; i records.Count; i) { worksheet.Cell(i 2, 1).Value records[i].WaferId; worksheet.Cell(i 2, 2).Value records[i].Source; worksheet.Cell(i 2, 3).Value records[i].Target; worksheet.Cell(i 2, 4).Value records[i].StartTime; worksheet.Cell(i 2, 5).Value records[i].EndTime; worksheet.Cell(i 2, 6).Value records[i].Result; } workbook.SaveAs(filePath); }实操心得导出大量数据时不要在主线程做。我试过导出 5 万条记录主线程直接卡了十几秒操作员以为死机了。后来改成Task.Run后台导出界面显示进度条体验好很多。6. 常见问题与排查技巧实录6.1 通讯类问题速查现象可能原因排查方法解决方式PLC 连接失败IP 不通、机架槽号错ping 测试、核对硬件组态修正 IP 和 rack/slot读到的数据乱码字节序问题对比 PLC 监控值和上位机值手动做大小端转换通讯间歇性断开网线干扰、交换机问题看断线频率、换网线加屏蔽、换工业交换机Socket 收不到应答机械手忙、协议格式错抓包看发送内容加超时重发、核对协议串口打开失败被占用、波特率错设备管理器看端口关闭占用程序、核对参数6.2 WPF 界面类问题界面卡顿是最常见的抱怨。原因通常有三个一是 UI 线程做了耗时操作二是数据绑定刷新太频繁三是控件数量太多。我的排查顺序是先看有没有在按钮事件里直接读 PLC改成异步再看定时器频率降到 100ms 以上最后看列表控件有没有虚拟化VirtualizingStackPanel.IsVirtualizingTrue。内存泄漏在长时间运行的工业软件里很致命。WPF 里常见的是事件订阅没取消、定时器没停、大对象没释放。我习惯在 ViewModel 的Dispose里统一取消订阅和停止定时器窗口关闭时调用。界面假死但后台还在跑通常是Dispatcher被阻塞了。检查有没有在 UI 线程里Thread.Sleep或者Wait()。所有等待改成await Task.Delay。6.3 运动控制类问题机械手取片失败先看真空压力值。如果压力没到阈值可能是吸盘老化、晶圆位置偏、真空管路漏气。我遇到过吸盘用了半年后弹性下降换新吸盘就好了。视觉定位偏差大先看相机标定有没有过期再看光源亮度是否稳定。车间灯光变化、相机镜头污染都会影响。我一般建议每班次开机做一次标定验证。轴运动超时检查限位开关、伺服使能、急停回路。有时候是 PLC 程序里轴使能没给上位机发指令机械手不动等超时报警。注意调试阶段一定要把速度降到 10% 以下。我见过新手直接全速跑机械手撞到夹具维修花了两天。低速验证流程正确后再逐步提速。6.4 数据类问题SQLite 写入慢通常是没加事务或者没建索引。批量写入用BEGIN TRANSACTION和COMMIT查询字段建索引速度能提升几十倍。数据库文件损坏一般是断电时正在写入。解决方式是启用 WAL 模式并且关键数据同时写文本日志。WAL 模式下 SQLite 的并发和抗损坏能力都更好。using var connection new SQLiteConnection(Data Sourcedata.db); connection.Open(); using var command connection.CreateCommand(); command.CommandText PRAGMA journal_modeWAL;; command.ExecuteNonQuery();导出 Excel 乱码检查编码和字体。ClosedXML 默认 UTF-8一般没问题。如果是 CSV记得加 BOM 头否则 Excel 打开中文会乱。7. 部署与现场调试经验7.1 工控机环境准备工控机我一般选研华或西门子的无风扇机型i5 以上、8GB 内存、SSD 硬盘。系统装 Windows 10 LTSC不要装家庭版LTSC 没有商店和一堆后台服务稳定得多。装完系统后关闭自动更新、关闭休眠、设置高性能电源计划。.NET 运行时我选.NET 6 或 .NET 8不要用 .NET Framework 4.x 了新项目没必要。发布用自包含发布把运行时打包进去现场不用单独装 .NET减少环境问题。dotnet publish -c Release -r win-x64 --self-contained true -p:PublishSingleFiletrue7.2 现场调试的节奏现场调试最忌讳一上来就全自动跑。我的节奏是单点测试每个 IO、每个轴、每个通讯通道单独测。手动流程操作员手动一步步走流程确认每个动作正确。单步自动自动模式下每步暂停确认后再继续。连续自动小批量跑观察稳定性。批量验证满负荷跑记录节拍和异常。每一步都要有记录出问题能回退。调试期间我习惯在界面加一个“调试模式”开关打开后显示更多中间变量和日志关闭后界面干净给操作员用。7.3 交付文档与培训交付不是把软件装上就完了。我一般准备这几份文档操作手册图文并茂操作员能看懂、维护手册常见问题排查、通讯协议表和 PLC、机械手、相机的接口定义、备份恢复指南数据库和配置怎么备份。培训分两场操作员培训讲怎么用、怎么处理报警工程师培训讲架构、怎么改配方、怎么查日志。培训时一定要让学员自己动手操作一遍光看是记不住的。实操心得现场一定要留远程协助通道。我试过半夜被叫起来处理问题如果现场能远程连上看日志十分钟解决不能远程开车过去两小时。当然远程通道的安全策略要严格只允许特定 IP、特定账号、操作留痕。8. 后续扩展方向这套系统跑稳之后可以往上加的东西不少。比如对接 MES 系统把搬移记录和批次信息上传实现全厂追溯加 SPC 统计对视觉定位偏差、真空建立时间做趋势分析提前发现设备劣化多工位并行用多个机械手或者双工位结构提升产能配方版本管理不同产品对应不同参数切换时自动校验。界面方面可以加大屏看板用 WPF 或者单独做个 Web 页面实时显示产量、良率、设备状态。如果产线有多台设备还可以做个集中监控客户端一台电脑看所有设备状态。代码层面我建议把通讯层和业务层进一步抽象成可配置的插件式架构。不同品牌的 PLC、机械手、相机实现同一套接口配置文件里切换。这样以后接新设备不用改核心代码加个插件就行。最后分享一个我在这个项目里体会最深的一点工业软件的价值不在于功能多炫而在于稳定、可追溯、好维护。操作员不需要花哨的动画他们需要的是按钮按下去有反应、报警能看懂、出了问题能查到原因。把这三件事做好比加一百个功能都管用。