做半导体后端设备的上位机绕不开晶圆搬送这个场景。晶圆从料盒取出经过定位、预对准再送进工艺腔石墨岛作为高温工艺的载具也需要在不同工位之间往复搬移。这套设备里每一个动作的协调、每一项数据的落库都压在上位机系统上。我前阵子用 C# WPF 落地了一台晶圆/石墨岛搬移设备的上位机从西门子 PLC 通讯、运动控制状态机到 MVVM 界面、本地数据库追溯踩了不少坑也沉淀了一套可以复用的写法。如果你正准备做半导体设备上位机或者想把手里的 WinForm 老项目往 WPF 迁移这篇实战记录应该能给你一点参考。这套系统听起来像是“控制几个轴动一动”但真正深入进去就会发现它考验的是数据一致性、状态机设计、异常恢复和防呆逻辑。晶圆和石墨岛都属于“怕磕碰、怕污染”的物料任何一次不到位、超时、误动作都可能造成碎片或者返工。所以上位机不只是发指令它更像产线上的调度员必须知道设备当前在哪一步、传感器反馈是否对得上、历史批次有没有记录可查。1. 项目拆解晶圆与石墨岛搬移系统的控制真相1.1 产线场景与上位机职责这套设备典型的布局是这样的料盒端有晶圆承载台机械手负责把晶圆从料盒取到预对准台对准之后搬运至工艺腔而石墨岛这个角色比较特殊它一般作为高温热处理的载具本身比单片晶圆重得多表面有时带着槽位或者涂层搬移时不仅要保证位置精度还要注意速度曲线不能太激进否则惯性冲击容易导致石墨岛边缘崩裂。上位机在中间扮演什么角色简单说PLC 负责硬实时逻辑轴使能、原点回归、限位处理这些都放在 PLC 里上位机负责流程编排和状态感知什么时候该取料、取料成功后要更新哪个批次记录、当前配方参数是什么、要不要等一下工艺腔门的互锁信号。这种分工在半导体设备里比较常见PLC 保证安全上位机保证“看得见、管得住、查得到”。我见过不少项目想用 PLC 把所有流程都写完结果代码量大到后期维护困难交互界面更是一塌糊涂。换到上位机之后流程逻辑可以随时更新不需要反复刷 PLC 程序日志和追溯也更顺手。所以我一直认为晶圆搬移类设备真正的主脑放在上位机是合理的关键是把边界划清楚。1.2 选型逻辑与框架规划为什么选 C# WPF 而不是传统的 WinForm也不是 LabVIEW 或者 Python当时团队主要考虑三点。第一是生态C# 在工业上位机领域积累了大量开源的通讯库、控件库和案例你需要的功能大概率有现成方案开发效率高。第二是界面表现力WPF 的数据绑定、模板和动画天生适合做动态监控界面设备状态、实时曲线、报警列表都能做得比较专业不输商用 HMI 软件。第三是团队技术栈C# 上手门槛比 C 低招人和交接都容易而且 .NET 程序部署在 Windows 工控机上非常方便。框架层面我直接采用了 MVVM 模式。ViewModel 负责把 PLC 数据映射成界面字段View 只做显示和交互Model 则定义设备、批次、配方这些数据实体。这样做的最大好处是当产线需求变化时只需要改 ViewModel 和模型XAML 界面基本不用重构。再加上 WPF 自带的命令绑定我可以把“启动搬移”“停止搬移”“回原点”这类操作统一封装成 ICommand界面上多个入口共用同一套逻辑逻辑不会散乱。1.3 功能清单与模块边界最终交付的系统大概包含下面几个模块设备通讯通过 S7 协议或者 OPC UA 与西门子 PLC 交换数据支持断线重连和实时订阅。运动控制给 PLC 下发送轴目标位置、速度、加速度读取轴状态和报警。流程调度维护晶圆取放、石墨岛搬移、工艺腔门互锁等状态机。配方管理保存不同工艺参数组合加载时下发到 PLC。数据追溯记录每片晶圆、每个石墨岛的搬移历史关联配方和操作员。报警管理集中显示报警、确认、复位并且写入数据库。用户权限区分操作员、工程师、管理员限制关键操作和配方修改。模块边界这件事必须在前置设计时定好否则后期肯定互相纠缠。我习惯把“通讯层”独立成一个服务内部用事件对外广播数据变化View/ViewModel 不直接触碰 Socket 或者 PLC 对象。运动控制和流程调度虽然相关但也要分离状态机只负责任务编排不关心具体轴参数轴的加减速、位置补偿都放到一个独立的 MotionService 里。这样无论后面替换机械结构还是更换 PLC 型号改动面都能控制住。2. 通讯层设计PLC、OPC 与 Modbus 的数据中转站2.1 西门子 PLC 通讯选型S7 直连还是 OPC UA热搜词里能看到“C#连接西门子OPC”这类高频词说明这是很多人的第一道坎。西门子 PLC 做上位机通讯常用路线有这么几条第一条是使用厂商协议直连比如 S7comm。C# 里常见的库有 Sharp7、S7netplus它们不需要在 PLC 侧额外配置直接访问 DB 块和 I/O实时性很好。对于单台上位机控制一台 PLC 的场景我非常推荐这种方案几百个点位轮询一次只有几十毫秒足够搬移设备用。第二条是走 OPC UA 或 OPC DA。OPC UA 适合多系统集成比如上面还要接 MES、SCADA或者存在不同品牌的 PLC 混用。最近西门子官网对 .NET 的 OPC UA 支持也越来越好可以用 Opc.Ua.Client 或者一些封装库。但 OPC UA 在网关配置和证书管理上比较繁琐单一设备场景没必要一开始就上。第三条是走 Modbus TCP这是网红词“wpf modbus 大屏”里常出现的方式。如果下位机不是西门子而是国产 PLC、仪表或者 IO 模块Modbus TCP 是性价比最高的选择。不建议直接裸写报文可以用 NModbus4 或者基于 NModbus 的库把线圈、寄存器、离散输入分开封装。我当时用的方案是西门子 S7-1500 用 Sharp7 走 S7 协议另外留了一条 OPC UA 作为后续 MES 对接的备用通道。实际联调下来S7 协议底层的 PDU 长度、机架号、槽号这些参数必须确认清楚任何一项错了都会连接超时这是很多人第一次调试时卡住的地方。2.2 读写下位机数据的工程化封装不要一上来就到处写 PLC 读写代码。我习惯把“点位表”单独做成配置在程序里用一个字典或者 DataTable 管理。每个点位包含变量名、数据块号、偏移量、数据类型、读写权限。这样现场新增一个传感器只需在配置里加一行不用改代码重新发布。实际通讯层我用了一个单例服务类内部维护连接状态和读写线程。读方面把需要监控的点位放进一个轮询列表每 50ms 批量读取一次写方面用一个队列把外部请求串行化PLC 通讯报文本身有长度和频率限制不管界面上点得多快底层都按顺序执行。这样写的好处是流量可控、不容易踩 PLC 通讯资源冲突。对于数据订阅我不建议用事件在低层一层层传递。我在服务里统一维护一个内存中的“数据快照”比如DeviceDataSnapshot每次轮询更新快照然后对外触发一个DataUpdated事件。ViewModel 订阅这个事件后把需要的字段拷贝到绑定属性。这里有个关键细节WPF 绑定属性必须在 UI 线程更新所以从通讯线程触发事件后需要App.Current.Dispatcher.BeginInvoke或者使用DispatcherTimer再做一次汇总刷新。否则大概率会看到“调用线程无法访问此对象”的异常。2.3 多线程与委托让 UI 永远不卡凡是用过上位机的人几乎都被“程序运行起来界面就冻结”这个问题折磨过。根本原因是把耗时工作放在了 UI 线程。C# 里解决这个问题我主要靠三层手段Task 异步、事件委托、线程安全容器。长时间阻塞任务比如 PLC 重连、数据库备份、文件导出一律用Task.Run放到后台线程。但异步回调回到界面时要注意上下文切换。比如await Task.Run(() _plcService.Connect()); UpdateConnectStatus(_plcService.Connected);这段代码中await之后的UpdateConnectStatus会自动回到 UI 线程因为它是从 UI 上下文发起的。这比手工Dispatcher.Invoke省心很多。但要注意Connect()里如果内部用了同步阻塞还是要设计超时不能无限等。委托在通讯、日志、报警这些场景特别顺手。底层把“数据刷新”“报警产生”“状态变化”抽象成委托或事件上层只需要订阅不用关心是谁发起的。比如public delegate void AlarmRaisedHandler(AlarmInfo alarm); public event AlarmRaisedHandler AlarmRaised;这样报警列表、声音提示、数据库写入可以各做各的松耦合。现场经常会有“新增一个信号量到界面”的需求有了委托和事件模式通常只需要在 ViewModel 里增加一个订阅不动通讯层。3. 运动控制与搬移流程状态机3.1 轴控制与位置比对从脉冲当量到坐标换算石墨岛和晶圆搬移设备一般有 X/Z/R 轴或者龙门式双轴伺服或者步进电机驱动。上位机不是直接发脉冲而是把目标位置、速度下发到 PLC再由 PLC 的轴控模块执行。这里首先要把单位统一。机械上如果电机每转 10000 个脉冲丝杆导程是 5mm那 1mm 对应 2000 个脉冲。如果减速比是 1:2还要再除以 2。坐标换算这块我在程序里建了一个“轴配置”类PulsesPerRev电机每转脉冲数ReductionRatio减速比LeadScrew导程Direction方向补偿保存配方时只写用户单位毫米或者微米下发前换算成内部单位。调试的时候经常发现机械装配导致原点位置和正负方向不一致所以方向补偿应该做成可配置而不是改代码。实践下来轴参数配置化是第一天就要做的事不然后面调机台会很痛苦。位置校验比位置设定更重要。上位机每次搬移动作完成后必须读回编码器反馈位置和目标位置比对偏差超过允许范围就要报警。晶圆搬移一般允许正负 0.1mm 内石墨岛搬移可以放宽一些但也不能超过 0.5mm否则腔体门关闭时可能撞到。3.2 搬移流程状态机设计与防呆逻辑搬移流程表面上是“取料-搬运-放料”三步但实际要考虑各种条件和异常。我推荐用显式状态机管理不要用散落的if else。实现方式可以是这样定义一个枚举MoveState包含Idle、Home、PickWafer、WaitAlign、PickIsland、PlaceIsland、Complete、Alarm等。流程引擎是一个循环根据当前状态执行对应动作动作结束后根据条件切换到下一个状态。switch (_state) { case MoveState.PickWafer: _plcService.WriteBoolean(PickStart, true); _state MoveState.WaitPickDone; break; case MoveState.WaitPickDone: if (_plcService.ReadBoolean(PickDone)) _state MoveState.AlignWafer; else if (_plcService.ReadBoolean(PickTimeout)) RaiseAlarm(取晶圆超时); break; }关键是每个状态都要有超时监控和异常出口。不能出现“等一个信号等到天荒地老”。我在状态机外层放了一个全局看门狗每次状态切换时记录时间戳超过配方设定的超时时间就进入Alarm状态并且停止当前轴动作。防呆逻辑也要做在状态机里目标位置没有到位、真空压力没有建立、门互锁未闭合绝不执行下一动作。另外要特别注意“手动模式”和“自动模式”的状态切换。手动模式下的动作通常只走单步不应触发完整状态机。如果手动和自动共用一套状态变量很容易发生“状态回不到 Idle”的情况。我的做法是维护两套状态变量手动动作完成后把自动状态机强制重置到Idle并做一次原点回归验证。3.3 传感器互锁与安全光栅联动半导体设备环境里传感器互锁不是可有可无的软逻辑。晶圆检测传感器、料盒内有无晶圆、预对准台真空压力、石墨岛到位传感器、腔体门开闭状态这些信号必须在 PLC 和上位机两侧都做校验。上位机侧校验的意义和价值在于快速定位故障。比如机械手已经在预对准台上方结果真空压力没建立这时候上位机可以通过日志立刻看出是哪一路传感器反馈异常而不是让维护人员去 PLC 程序里翻梯形图。安全光栅的联锁必须放在 PLC 硬逻辑中这个不能依赖上位机。上位机要做的是在收到“安全光栅遮挡”信号时把当前流程上下文完整记录下来包括正在搬移的物料批次、当前轴坐标、传感器状态。这样复位之后操作员能知道设备停在哪一步再手动恢复。千万不要在上位机侧把安全联锁做成可选功能工程上这是底线问题。4. 界面与交互WPF MVVM 实操4.1 用 MVVM 组织生产界面WPF 界面我最终分了三块主监控区、流程控制区、参数与日志区。主监控区用设备三维示意图加上状态颜色标识模型变化靠数据绑定驱动流程控制区放置“启动自动”“暂停”“复位”“手动操作”等按钮参数与日志区展示实时配方、轴位置、报警和操作日志。MVVM 核心是INotifyPropertyChanged。我通常写一个ViewModelBase实现属性通知和命令绑定。每个设备模块对应一个 ViewModel比如MoveViewModel、RecipeViewModel、AlarmViewModel。数据绑定方向要明确ViewModel 订阅通讯层数据快照把原始点位值转换为界面上可读的字符串、颜色、可见性等属性View 里只通过{Binding Xxx}显示。要注意别把 PLC 点位直接暴露给 View。PLC 读到的可能是布尔量、整数、浮点数界面要的是“位置正常”“门已关闭”“真空压力 2.5kPa”这样的语义信息。转换逻辑放在 ViewModel 或 Converter 中可以但最好放在 ViewModel方便单元测试。4.2 数据绑定与实时曲线晶圆搬移过程中操作员最关注的是轴跟随误差和真空压力曲线。WPF 里实时曲线有好几种方案用 OxyPlot 或者 ScottPlot 是成熟路线自己画Polyline也可以但性能和交互差距明显。我实际选的是 OxyPlot简单配置即可。实时刷新的一个坑是“高频刷新导致整个 UI 卡顿”。绑定属性每 50ms 更新一次再叠加曲线点、报警闪烁CPU 占用会很高。解决思路是把界面刷新频率降下来比如采集数据和通讯层保持 50ms但曲线控件只每 200ms 刷新一次数据用环形缓冲区暂存。这样视觉上不会卡CPU 也降下来了。private void AddCurvePoint(double value) { _buffer.Add(value); if (_buffer.Count _maxPoints) _buffer.RemoveAt(0); }WPF 数据绑定还有一个经典烦恼日志控件里如果直接用RichTextBox它的Document属性不是依赖属性没法直接绑定。网上搜“wpf 如何在richtextbox.document上使用binding”会看到很多答案我的做法是自己写一个附加属性把FlowDocument与日志集合绑定并用Document.Blocks.Clear()和Paragraph追加。这样日志内容可以同时进数据库列表和界面显示。4.3 HandyControl 与自定义控件快速搭建WPF 默认控件虽然够用但做设备上位机界面的质感还是差一点。我用了 HandyControl这个开源控件库对工控风格很友善。它的Card容器可以用来做功能分区Growl可以用来做全局消息弹窗DateTimePicker可以带时分秒正好满足历史记录查询需求。很多人搜“wpf 日期选择器控件带时分秒”其实就是找这个控件。HandyControl 引入方式不复杂NuGet 安装后在App.xaml里添加资源字典即可。但要注意版本和主题资源顺序如果不小心覆盖了默认样式原有Button的宽度、高度会变得和预期不一致需要花一点时间调整。我的习惯是只在局部页面使用 HandyControl 的样式不全局替换避免影响自定义的监控页面布局。自定义控件方面我封装了一个“状态指示灯”控件构造函数接收颜色、闪烁频率和文字说明内部用一个DispatcherTimer控制闪烁。这种控件在设备状态显示里特别常用而且代码量很小。界面自定义灵活正是 WPF 相对于传统 WinForm 最有竞争力的地方一旦切换到 WPF你会发现做监控画面更像做“产品”而不是拼控件。5. 数据库与追溯模块5.1 本地存储与历史查询追溯是半导体设备的硬指标。生产批次、物料编号、操作员、配方参数、轴位置、报警内容都需要留痕。现场工控机一般不保证能稳定连接外网数据库所以本地存储是不可或缺的。我用的是 SQLite部署简单单文件即可不需要安装数据库服务。用 Microsoft.Data.Sqlite 访问ORM 用 EF Core 或者 Dapper 都可以。我倾向于 Dapper轻量、 SQL 可控性强。数据库表设计一般分为几类MoveRecord每次搬移动作的完整记录包含物料类型、料盒槽位、目标工位、轴终点坐标、结果。ProcessBatch一个批次对应一批晶圆或石墨岛关联多条搬移记录。AlarmLog报警时间、报警代码、报警描述、确认时间和操作员。Recipe配方参数版本、下发时间、下发到 PLC 的原始数据。查询界面用 WPF 的DataGrid展示历史记录。这里有一个性能问题数据量大了以后一次性加载几千行会卡需要做分页或者延迟加载。我通常用“按时间范围 物料ID 结果”做组合筛选不提供查全表的入口。5.2 批次追溯与数据导出追溯的关键是“批次内可以正向查也可以反向查”。正向查是输入批次号能看到这一批里所有晶圆/石墨岛的搬移过程每步时间、坐标、操作员。反向查是输入晶圆ID能看到它属于哪个批次、经过了多少次搬移、用了哪一版配方。实现上要保证 ID 连贯。每片晶圆从料盒取出的那一刻就生成一个工件上下文后面所有动作都往这个上下文里追加记录。搬移记录表里保存WoferId、CassetteSlot、ProcessBatchNo等字段。石墨岛也要有自己的编号因为石墨岛会多次复用每次工艺结束之后要记录它的累计使用次数和状态超过次数上限就要提示维护。导出方面现场工程师最常用的是 CSV 和 Excel。CSV 很简单用CsvHelper库几行代码就能导出来。Excel 如果只是为了查看导出 CSV 完全够用没必要纠结 EPPlus 的授权问题。导出操作放在后台线程完成后用Growl提示下载路径。5.3 数据完整性批量写入与备份策略现场写入频率最高的就是搬移记录和报警记录。如果每动作一条INSERT立即提交SQLite 在高频率写入下性能不会太差但并发写字时也可能出现database is locked。我的做法是使用事务批量提交连接打开后在事件循环里积攒 20 条记录或者 1 秒定时一次性提交到数据库。这样写入吞吐量比逐条提交高很多也不容易锁库。如果你们现场对接的是 SQL Server并且用了SqlBulkCopy也注意“表变动有影响”这个热搜问题只要目标表的列顺序和结构一改批量复制就可能报错。所以用SqlBulkCopy前要保证列映射明确不要依赖序号。SQLite 没有 SqlBulkCopy但它的事务机制已经够用。备份策略上我用了两套办法一套是每天定时把数据库文件复制到指定目录另一套是在批次开始时把当前数据库路径记录下来批次结束后做一次完整性检查。SQLite 开启 WAL 模式后读写并发更强但备份时需要连同-wal文件一起处理否则可能丢最近日志。单机上我比较保守凌晨备份时先把应用挂起停止写库再复制文件这样备份出来的文件一定是最干净的。6. 上线调试中的坑与排查技巧6.1 通讯不稳定先排查协议栈整个项目联调阶段花时间最多的就是通讯不稳。现象是界面连接上了但数据时不时刷新停顿或者 PLC 那边偶尔报错。第一步是看网线、IP、机架号和槽号不要觉得这些基础参数不可能错现场最常出的就是 PLC 槽号填错导致连接断断续续。第二步是用小工具单独 ping PLC看是不是网络丢包如果 ping 都丢包就要查交换机端口或者电磁干扰。第三步是抓包或者开启库的调试日志确认 PDU 长度是否匹配。西门子 S7 的 PDU 协商很关键Sharp7 默认值一般能自动适应但如果你手工改过连接参数很容易在数据量大的时候触发分帧异常。OPC UA 路线的问题往往是证书。第一次连接时UA 客户端证书需要和服务端建立信任关系。很多开发环境里的“匿名访问”和 UAC 权限纠缠在一起会让人误以为代码有问题。我的经验是先在 UaExpert 里完成一次成功连接确认服务器地址、端点、安全策略再回到 C# 代码里配置能少走很多弯路。6.2 坐标偏差与机械误差补偿石墨岛搬移时坐标精度是一个反复折磨人的点。第一次装完机上电后机械手走到目标位置实际偏差很可能有几十个毫米这是机械装配误差不是软件 bug。要先用激光对刀或者千分表找基准点记录机械原点偏移量写进轴配置。我建议把“原点偏移”和“工位坐标校准”做成两个独立配置项前者是全局的后者是每个工位单独微调。还有一种情况是双向定位误差轴从左边走和从右边走到位后的停靠位置不一样这是丝杆反向间隙造成的。如果精度要求高需要在软件里做方向补偿或者让每次搬移都从同一方向逼近目标位置。晶圆搬移我基本都用方向统一的方式比复杂的补偿表简单可靠。另外轴运动曲线也不要一味追求快加减速时间至少给到 200ms 到 500msS 曲线比梯形曲线对石墨岛更友好。6.3 常见问题速查表现象可能原因排查建议上位机连不上 PLCIP、机架号、槽号错误用测试工具单独连接确认基础参数连接正常但读取偶尔闪烁网络丢包或 PDU 分帧异常检查交换机线缆查看库日志UI 界面卡顿高频绑定或者 UI 线程阻塞降低刷新频率耗时候用 Task.Run轴实际位置和目标差很多脉冲当量或方向补偿配置错误检查轴配置参数并重新标定原点石墨岛搬移时振动大加减速过猛或 S 曲线参数不合适延长加减速时间检查机械间隙数据库报 locked写入没有走事务或备份不干净改为批量事务提交备份时暂停写库报警列表丢失报警事件未做数据库持久化统一在报警服务里写日志表WPF 页面样式错乱HandyControl 全局资源覆盖默认样式只在局部使用控件库样式这套速查表更像是我整个项目踩坑的浓缩版。真正到了产线调试很多问题都是交叉的不是一张表能完全覆盖但先把这些高频问题排除掉剩下的大概率就是机械或者工艺层面的事了。做完整套系统后我个人体会最深的就是C# 和 WPF 只是工具真正撑住项目的是模块划分和数据一致性设计。通讯层把点位剥离开状态机把流程关进笼子数据库把每一步都记下来界面只是最后把这一切可视化的结果。即使后面换设备、换 PLC、换机械结构只要骨架搭对了改动量始终是可控的。最后再分享一个细节正式上线前务必做一次“模拟故障演练”从通讯断线、安全光栅触发、坐标超差到数据库满盘把每个场景的恢复路径都验证一遍。机器跑得越久你会越感谢当时把这些异常处理得足够完整。
阅读完成 · 觉得有帮助?