半导体设备现场有一条不成文的规矩:设备可以暂时不动,但上位机界面一定不能乱。尤其是晶圆和石墨岛这类高价值载具的搬移系统,操作的每一个动作都得有据可查、有迹可循。这篇就聊聊我用 C# + WPF 从零搭建一套半导体晶圆与石墨岛搬移上位机系统的完整过程,包括架构思路、通信选型、核心功能模块实现、防呆设计,以及现场调试中踩过的坑。无论你是刚转行做上位机开发的工程师,还是已经在和 PLC/运动控制器打交道的软件岗,这篇都能给你一些能直接落地的参考。
先交代一下场景。这套系统服务于一条半导体后道工序中的上下料设备,核心任务就两个:把晶圆从晶圆盒里取出来,放到石墨舟(岛)的指定槽位里;工艺结束后再把晶圆从石墨舟取回,放回晶圆盒。听起来简单,但晶圆是精密易碎件,石墨舟本身也价值不菲,任何一次位置偏移、气压不足、传感器误判都可能造成碎片或刮伤。所以上位机要做的不只是发号施令,更要把每一个环节的状态盯死,把每一次动作的前因后果记下来。
1. 项目背景与整体架构拆解
1.1 晶圆与石墨岛搬移的业务逻辑
先把这个场景的物理链路理清楚。整套设备大致由这几个部分组成:晶圆盒装载区、石墨舟装载区、搬移机械手(或者由气缸、伺服模组组成的机械臂系统)、预对准/校正工位、以及各类传感器和真空系统。上位机不直接控制伺服电机的电流环,它处在整个控制链路的最上层,和 PLC、运动控制器配合工作。
在下发一个“取片”动作时,上位机需要做的事情包括:
- 确认目标晶圆盒槽位是否有片(通过传感器或 PLC 映射状态)。
- 确认机械手当前位置、抓取机构是否就绪。
- 向运动控制器发送目标槽位坐标或索引。
- 等待机械手完成取片,期间实时监控真空压力、吸盘状态。
- 判断当前取到的晶圆是否存在叠片、歪斜。
- 将晶圆搬运到预对准工位进行角度校正。
- 最终将晶圆放入石墨舟的指定槽位,并确认放置到位。
这里面每一步都是串行甚至并行交错的状态流。如果把逻辑全部写死在事件回调里,后期维护会非常痛苦。所以我从一开始就决定采用“状态机 + 配方驱动”的方式,上层定义流程,底层只做执行和反馈解析。
1.2 上位机系统的技术选型与架构设计
选型这块其实没什么悬念:C# + WPF 是 Windows 平台工业上位机的主流组合。C# 的开发效率高,和 PLC、MES、数据库对接都方便;WPF 做复杂交互界面比 WinForms 灵活得多,数据绑定、样式模板、动画支持都是为现代桌面应用准备的。
架构上我分了四层:
- 界面层:WPF + Prism,负责操作界面、监控界面、配方编辑、报警展示。
- 业务逻辑层:负责流程控制、状态机、配方解释、数据校验。
- 通信层:Modbus TCP 客户端、Socket、OPC UA(预留)等,统一封装成独立服务。
- 数据层:本地数据库(SQLite)存储配方、日志、报警记录、操作履历。
这样的分层带来的直接好处,就是界面卡顿和通信阻塞不会互相影响。比如通信层挂在单独的后台线程里,界面只管订阅数据变化事件;数据库写入放在异步队列里,不阻塞操作流程。
注意,选 Prism 不是因为“酷”,而是因为项目往后一定会面临界面复杂度增长。Prism 的模块化、Region、导航机制,在设备状态页、参数配置页、操作员日志页之间来回切换时非常合适,比自己写一堆用户控件切换逻辑干净得多。
2. 通信链路:上位机与 PLC/运动控制器的对话方式
2.1 Modbus TCP 与设备的连接配置
这套设备用的是支持 Modbus TCP 的 PLC。上位机作为 Modbus 客户端,PLC 作为服务端。连接参数就那么几个:IP、端口、超时时间、重连策略。
IP 和端口通常由电气工程师给定,默认端口 502。重点在于超时和重连机制。生产现场网络环境并不可靠,偶尔会有电磁干扰导致报文丢失,所以通信层必须做超时重试和掉线重连。我封装了一个 ModbusService,核心逻辑大致是:
public class ModbusService { private ModbusTcpClient _client; private readonly object _lockObj = new object(); private CancellationTokenSource _cts; public async Task<bool> ConnectAsync(string ip, int port) { try { var tcpClient = new System.Net.Sockets.TcpClient(); await tcpClient.ConnectAsync(ip, port); _client = new ModbusTcpClient(tcpClient); return _client.Connected; } catch (Exception ex) { Logger.Error($"Modbus connect failed: {ex.Message}"); return false; } } public ushort[] ReadHoldingRegisters(byte slaveId, ushort startAddress, ushort length) { lock (_lockObj) { if (_client == null || !_client.Connected) { Reconnect(); } return _client.ReadHoldingRegisters(slaveId, startAddress, length); } } }注意,这里加了 lock 是因为现场出现过两个线程同时读写寄存器导致数据错乱的问题。Modbus 的请求-响应模型本身不是并发的,并发请求会让响应无法对应上请求,所以老老实实串行化。
2.2 数据交互协议与报文设计
PLC 和上位机之间的数据交互,不能想到哪写到哪,一定要提前规划地址映射表。我一般把寄存器区域分成几块:
- 状态区(只读):设备当前模式、各工位传感器状态、真空压力值、报警代码。
- 控制区(只写):启动搬移任务、暂停、复位、选择流程号、选择配方号。
- 参数区(读写):工位坐标、速度设定、气压阈值等。
- 心跳区:上位机和 PLC 每 500ms 互相写一个递增计数,超过 3 个周期没有变化就判定通信异常。
这里有个容易踩的坑:PLC 的程序扫描周期一般只有几十毫秒,但上位机别一股脑高频读数据。我实测过,100ms 的轮询周期足够满足大多数状态监控需求,CPU 占用率也不会因为通信库内部转换而飙升。需要实时性更高的数据,就走运动控制器的专用接口,不走 PLC。
2.3 WPF 前台与后台数据流设计
通信层拿到原始数据之后,不能直接扔给界面更新。我所有的外部数据都先放到一个共享的 DataCache 中,界面通过属性变更通知去订阅。
比如晶圆盒 1 号槽位的“有片/无片”状态,底层从 Modbus 寄存器位解析出来后,写入一个 ConcurrentDictionary,然后触发对应属性。
public class WaferSlotStatus : INotifyPropertyChanged { private bool _hasWafer; public bool HasWafer { get => _hasWafer; set { if (_hasWafer != value) { _hasWafer = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(HasWafer))); } } } }这样界面上的指示灯、槽位图、报警提示都会自动更新,不需要手动刷新控件。这也是 WPF 相比 WinForms 最舒服的地方之一——只要绑定关系理清楚,界面更新维护成本极低。
3. WPF 界面设计:从“能用”到“好用”
3.1 基于 MVVM 和 Prism 的框架搭建
界面层如果只是做出来能点点按钮,那随便写都行。但工业设备软件面向的是操作员、工艺工程师、维护工程师三类人,界面必须兼顾高效操作和状态可读性。
我用 Prism 搭建了模块化结构,每个功能区域独立成 Module。主界面左侧是导航栏,右侧 Region 承载不同的页面。页面之间不直接引用,通过导航请求切换。
启动时初始化:
protected override void RegisterTypes(IContainerRegistry containerRegistry) { containerRegistry.RegisterForNavigation<MonitorPage, MonitorViewModel>(); containerRegistry.RegisterForNavigation<RecipePage, RecipeViewModel>(); containerRegistry.RegisterForNavigation<AlarmPage, AlarmViewModel>(); containerRegistry.RegisterForNavigation<HistoryPage, HistoryViewModel>(); }导航切换用:
_regionManager.RequestNavigate("MainRegion", "MonitorPage");这种结构下,新增一个页面只需要加一个 Module 和一次注册,不影响已有功能。
3.2 实时状态监控与图形化展示
界面不能只显示一堆“True/False”。我用自定义控件把设备状态画成俯视示意图,晶圆盒槽位、石墨舟槽位、机械手位置都图形化展示。晶圆用圆形色块表示,颜色代表状态:绿色表示有片且正常,灰色表示无片,红色表示报警,蓝色表示正在搬运。
石墨舟的槽位管理是这套系统的一个核心交互点。每个舟位有一个二维坐标(行、列),对应石墨舟上的物理槽。界面点击某个槽位可以锁定/解锁该位置,锁定后流程调度不会往该槽位放置晶圆。
绘制图形用的就是 WPF 自带的 Shape 和 Canvas,绑定了 ViewModel 中的 ObservableCollection。性能方面控制在一屏不超过 200 个图形元素,实测刷新毫无压力。
3.3 操作权限与操作记录
设备上电后默认进入“操作员模式”,只能执行启动、暂停、复位等标准操作。工艺参数修改需要登录“工程师模式”,配方编辑则需要“管理员模式”。权限验证放在命令入口处统一拦截。
我做了三个等级的账号类型:
- 操作员:执行已选配方、查看状态和报警。
- 工程师:可修改工艺参数、校准传感器、手动控制单个轴动作。
- 管理员:可编辑配方、删除日志、修改系统配置。
所有关键操作(登录、登出、启动流程、修改参数、切换配方)都会写入操作日志表,记录操作人、时间、动作内容。这样出了问题能追溯到底是谁、什么时候、改了什么。
4. 核心功能模块的实验:晶圆搬移流程与配方管理
4.1 流程状态机与步骤执行器
搬移流程不能写成一个大循环,得用状态机拆解。我定义了一个典型的取放片流程状态枚举:
public enum TransferState { Idle, WaitingHome, HomeCompleted, PickingWafer, VerifyingWafer, AligningWafer, MovingToBoat, PlacingWafer, VerifyingPlacement, Completed, Aborted }每一个状态对应一个执行器方法,由流程控制类统一调度。只有上一步返回成功,才会进入下一步。
拿“PickingWafer”举例,这个状态内部执行的动作是:
- 向 PLC 发送取片指令。
- 等待机械手移动到目标位置。
- 启动真空泵,等待真空压力达到设定阈值。
- 如果真空压力不足,触发报警并中断流程。
整个流程采用异步执行,不阻塞 UI 线程。状态切换时通过事件通知界面更新当前步骤显示。
4.2 配方(Recipe)体系设计
配方是现场工艺的核心。不同产品批次,晶圆尺寸、石墨舟槽位间隔、搬移速度、真空延迟时间都不一样。我的配方结构大致如下:
- 配方名称、版本号、创建时间。
- 晶圆直径(4寸/6寸/8寸/12寸)。
- 晶圆盒类型和起始槽位。
- 石墨舟行数、列数、起点坐标、槽间距。
- 搬移速度、加速度、真空建立时间和释放时间。
- 预对准角度补偿值。
配方保存为 JSON 文件并同步写入 SQLite,界面提供可视化编辑表格,工程师可以直接在界面上修改参数并另存为新版本。生产时操作员只需选中配方,系统自动从配方库加载所有参数。
4.3 日志数据记录与追溯
设备现场的日志分几类:
- 操作日志:登录、操作、配方切换等。
- 运行日志:每个流程步骤的开始、结束、耗时、结果。
- 报警日志:报警代码、发生时间、恢复时间、处理人。
- 工艺数据:每片晶圆搬移时的真空压力曲线、位置偏差、搬移耗时。
工艺数据这个很有意思,我用了 OxyPlot 把真空压力曲线实时显示在界面上,同时把原始采样点写入数据库。后期如果出现某批产品良率问题,可以通过批次号和槽位号码拉出对应的时间曲线,快速判断是否因为搬移过程气压波动导致晶圆损伤。
5. 防呆设计与异常处理
5.1 晶圆位置和姿态检测的互锁逻辑
晶圆是高价值易碎品,任何一次误动作都可能造成不小损失。防呆逻辑是这套软件的重中之重。
最基本的防呆包括:
- 重复放置检查:放置前必须先读到该槽位“无片”,放置后必须读到“有片”。
- 叠片检测:取片后通过光电传感器或反射面积判断是否一次吸起多片。
- 歪斜检测:晶圆边缘到吸盘中心的距离偏差超过阈值时报警。
- 真空压力监控:建立真空后压力值必须在设定区间内,压力突变立即中断。
把这些互锁逻辑封装成独立的 RuleEngine 类,每次执行动作前先跑一遍当前状态的所有规则,任何一条不满足就禁止执行。这样做比在流程代码里散落 if 判断可靠得多。
5.2 报警分级和恢复策略
报警不能全是同一个级别,不然操作员会麻木。我分了三级:
Level 1:提示类,不影响流程,比如“某槽位无片,跳过”。 Level 2:操作类,需要操作员介入,比如“晶圆歪斜,请检查”。 Level 3:设备类,需要维护工程师处理,比如“真空压力过低,系统停机”。
报警触发后,系统自动记录当前流程位置和设备状态快照。恢复处理也分级:Level 1 报警点击确认即恢复;Level 2 报警需要在排除故障后执行复位;Level 3 报警必须重新走一次完整回零流程才能继续。
特别强调一点:任何报警恢复后,流程都不能从断点直接继续,而是必须回到一个明确的安全状态。比如正在取片途中报警停机,恢复后必须先把机械手抬升到安全高度,再重新执行取片流程。这不是为了省事,而是防呆设计的基本要求。
6. 常见问题与现场排查实录
6.1 通信超时与数据丢包的排查
现场最容易出现的问题就是通信超时。我第一次调试时,界面经常提示“PLC 无响应”,排查流程大致是:
先看物理链路:交换机的哪个口对应哪条网线,IP 地址是否有冲突。这一步用 ping 能解决大部分连接性问题。
再看通信频率:排查是不是上位机报文发送太频繁,导致 PLC 响应不过来。Modbus 协议本身对并发报文的支持很弱,如果上位机循环里同时发了多个请求,很容易出现响应错位甚至超时。
最后检查 PLC 程序里的看门狗:很多 PLC 程序里根本没人写通信看门狗,上位机长时间不发请求,PLC 里的数据区就一直是旧值。我们后来在上位机里固定 500ms 发一次心跳读写,问题才彻底解决。
6.2 WPF 界面卡顿和内存泄漏的问题
界面卡顿这个坑,几乎所有做 WPF 上位机的人都会踩。最常见的原因是后台线程直接操作了 UI 控件,或者事件订阅没有注销,导致内存只增不减。
我的处理办法是:
- 后台任务只触发事件,界面在事件中通过 Dispatcher 更新绑定数据。
- 所有定时器都使用 async/await 替代 DispatcherTimer,避免 UI 线程频繁被占用。
- ViewModel 实现 IDisposable,在页面关闭时取消事件订阅。
还有一点:不要在一个 ObservableCollection 里塞成百上千个对象还频繁全量刷新。WPF 的集合刷新是按变化通知走的,如果每次采样都 Clear 再 Add,界面必然会闪。我用的是固定长度的 Buffer 循环覆盖,尽量减少集合变更次数。
6.3 版本兼容和部署打包的问题
热词里有人问“VS2019 开发的 C# 上位机源码能用 VS2015 打开吗”,这个问题在现场也常有。我的建议很直接:没有特殊原因不要回退编译器版本。
我们项目一开始定的目标框架是 .NET Framework 4.8,开发工具用 VS2022。结果现场有一台工控机只装了 VS2015 运行库,部署时发现缺依赖。后来统一改成了 .NET Framework 4.7.2 作为目标框架,再用 VS2022 自带发布功能打出来独立部署包,把运行库一起带上工控机,问题才算解决。
打包这块建议直接用 Visual Studio 自带的 InstallShield 或者简单的文件夹复制 + 批处理脚本。工业现场不像互联网公司有自动发布流水线,很多时候就是一个安装包打天下,所以尽量别依赖开发机的环境。
还有一个小细节:Prism、NModbus4 这些库的版本号在部署前一定要锁定。有次 NuGet 自动还原把 Prism 从 7.2 升到了 8.x,导致 Region 注册时接口签名对不上,界面直接白屏。后来所有外部库都在 packages.config 里固定版本,杜绝了这种问题。
6.4 Prism Region 注册失败的排查
有个操作员登录后弹窗页面一直显示白色,排查半天发现是 Prism 动态注册的 Region 没有在页面加载前完成初始化。Prism 的 Region 注册时机非常敏感,如果在导航发生时才去注册 Region Manager,页面里的 Region 是找不到的。
我的做法是在 View 的构造函数里先创建 Region,再设 ViewModel 的 DataContext,确保区域在数据绑定前已经存在。如果用了自定义的 RegionAdapter,要检查是否在 App 启动时完成了注册,否则后续弹窗的用户控件内容不会显示。
7. 过程中的一些心得和补充提醒
最后聊点实在的,这套系统从开始写代码到最后产线验收,花了大概三个月。回头复盘,有几点值得新入行的朋友注意。
第一,上位机开发,沟通需求比写代码重要。和电气工程师、机械工程师坐下来把信号列表一条一条过,比后期软件改起来省事十倍。信号地址表必须和电气确认至少两遍,否则现场改起来牵一发动全身。
第二,晶圆搬移系统的调试一定要从模拟开始。先用空跑模式把整个流程走通,再放入废片试跑,最后才能用真正的晶圆跑。现场调试时千万别着急一次跑到位,宁可每个步骤单独验证,也不要让软件带着设备盲跑。
第三,日志能多记就多记,存错不存对也没关系。之前有一次良率波动,我靠数据库里存的真空压力曲线定位到一批晶圆在搬移过程中真空延迟长了 200 毫秒,这就是平时多记日志的价值。数据存了不占地方,没存即使绞尽脑汁也猜不出来。
第四,界面操作逻辑一定要站在操作员的角度考虑。接触不良的鼠标、油污的手套、戴着护目镜的视线,决定了按钮不能太小,颜色不能太花,关键操作必须有二次确认。工业软件的美观不是第一位,可用性才是。
这套系统的后续计划,我准备在通信层增加 OPC UA 支持,方便未来接入更上层的制造执行系统。同时把配方管理改造成数据库版本化控制,避免多个工程师同时编辑时互相覆盖。工具链和技术栈本身是成熟的,真正考验人的,是对设备工艺的理解和对异常边界的掌控。