ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

半导体设备 SECS/GEM 通讯平台:协议栈、数据模型与联调运维

半导体设备 SECS/GEM 通讯平台:协议栈、数据模型与联调运维 我在半导体产线做过几年设备联网的活最常被问到的一句话是设备自己都能把数据吐出来了为什么还要单独搞一个 SECS/GEM 协议系统通讯平台问这话的人多半刚从通用工业协议那边转过来脑子里装的是 Modbus、OPC UA 那一套觉得 TCP 通了、数据读到了事情就算办完。但半导体产线的规则完全不一样一台刻蚀机、一台量测机台、一台清洗设备想进 fab 的自动化体系就必须开口说 SECS/GEM 这门方言而 Host 侧要同时跟几十上百台不同品牌、不同固件版本的设备对话中间没有一层统一的通讯平台兜底工程根本推不动。这篇就按我的实际经验把这个平台从零搭起来要面对的核心问题捋一遍协议栈怎么分层、数据模型怎么建、超时参数怎么定、联调阶段哪些坑一定会踩、上线之后怎么运维。适合正在做自研决策的设备厂商软件工程师、fab 里负责 EAP 的自动化工程师以及手上已经拿到设备接口手册但不知道怎么落地的人。哪怕你之前只写过 TCP 长连接的业务代码看完也能明白这套东西的工程量到底堆在哪里。1. 先想清楚这个平台站哪一侧SECS/GEM 在产线里的真实位置1.1 设备厂商和 fab 各自缺的是什么SECS/GEM 不是哪家公司拍脑袋定的私有协议它来自 SEMI 的一整套标准文件。E4 定义了串口时代的 SECS-IE37 定义了现在主流的 HSMSE5 定义了消息本体长什么样E30 定义了 GEM 的行为模型再往上还有 E39、E87、E90、E94、E116 这些扩张件分别管对象服务、载具管理、控制作业、设备性能追踪。这套文件加起来几百页写作风格非常标准体——它只告诉你设备应该具备什么行为从不告诉你代码该怎么写。设备厂商的痛点在交付侧。同一台机台卖给不同客户每家 fab 的 Host 问的东西都不一样有的只要几十个状态变量有的要全量事件上报、要远程下配方、要 trace 采样、要接收控制作业。设备厂商不可能为每个客户改一版固件所以必须有一个可配置的通讯平台把标准行为实现一遍把客户差异下沉到配置表和变量映射里。Host 侧的痛点更麻烦。一条产线上几十种机型每种机型的变量命名、事件定义、报警清单都不同同一机型换个固件版本还会改。Host 不可能为每台设备写一套代码所以平台必须做协议适配层把设备私有的东西翻译成 MES 能理解的统一数据模型。说白了这个平台的核心价值就是把N 台设备 × M 个客户的乘积级适配工作量压成一条线性的、可配置的流水线。1.2 三种落地形态边界必须在项目启动前谈清楚实际项目里见得最多的是三种形态。设备内嵌跑在设备工控机上直接跟底层运动控制或 PLC 对接一个进程管一台机延迟低、状态准缺点是升级要停机、要重新验证。边缘代理一台网关管一组设备通过设备私有接口取数再统一转成 SECS/GEM适合老设备改造但取数环节容易丢细节。中央 EAP机房里的服务一台机器连几百台设备统一做数据采集、配方管理、控制作业调度集中运维很方便代价是网络抖动会被放大成业务问题并且必须扛住几百路并发。真实项目里最常见的是设备内嵌 中央 EAP混合设备方交付时自带一套最小 GEM 实现保证能过认证Host 侧再建一个中央平台做统一接入。这个组合的边界一定要在启动会上白纸黑字写清楚——哪些行为由设备负责、哪些由 Host 负责。我至少在三四个项目里见过扯皮事件报告的使能到底谁发起断线重连之后谁负责重建上报链路spooling 补传的数据算谁的这些问题不提前定联调阶段一定会互相甩锅而且甩到最后往往还是要 Host 侧平台兜底因为设备固件改起来成本高得多。2. 把协议栈拆成三层HSMS、SECS-II、GEM 各管什么2.1 HSMSTCP 之上那段固定头部一条 HSMS 报文在 TCP 流上的样子是确定的最前面 4 个字节是长度字段大端序并且不包含这 4 个字节自身。紧接着是头部Session ID 占 2 字节Header Byte 2 和 Header Byte 3 各占 1 字节System Bytes 占 4 字节。头部之后才是 SECS-II 消息体。数据报文和控制报文共用这套头部靠 Header Byte 3 区分。数据报文里Header Byte 2 的最高位是 W-bit低 7 位是 Stream 号Header Byte 3 是 Function 号。控制报文里这两个字节换成 SType常见的取值有1 是 Select.req2 是 Select.rsp3 是 Deselect.req4 是 Deselect.rsp5 是 Linktest.req6 是 Linktest.rsp7 是 Reject.req9 是 Separate.req。Session ID 在 HSMS-SS 单会话模式下高 16 位放 Device ID低 16 位按惯例填 0xFFFF——但这条我踩过坑个别设备的实现填的是 0x0000所以第一版代码里最好把它做成可配置项收到 S9F1未识别 Device ID时第一时间怀疑这里。HSMS 有自己的连接状态机分成 NOT-CONNECTED、CONNECTED/NOT SELECTED、CONNECTED/SELECTED 三个状态。注意TCP 连上了不等于能通信中间还夹着一个 Select 阶段。很多新手代码只判断 socket 是否可写就开始发业务报文结果设备端全部丢弃日志里一片安静。主被动模式上一般 Host 做 Active、设备做 Passive也有反过来的两边不能同时 Active否则会互相连、互相抢端口。2.2 SECS-II格式字节里那个被忽略的低 2 位SECS-II 的每一个数据项都是格式字节 长度字段 内容的三段式。格式字节的高 6 位是格式码低 2 位表示长度字段本身占几个字节取值 0 到 3。低 2 位为 0 的时候长度字段根本不出现直接表示这个项长度为 0。常用的格式码可以记一张表格式码二进制类型含义长度字段的含义000000LList元素个数001000BBinary字节数001001BOOLEAN布尔字节数通常 1010000AASCII字符数010001JIS-8日文字节数011001I11 字节有符号字节数011010I22 字节有符号字节数011100I44 字节有符号字节数100100F44 字节浮点字节数101001U11 字节无符号字节数101010U22 字节无符号字节数101100U44 字节无符号字节数最容易记混的一点L 类型的长度字段写的是元素个数其他类型写的是字节数。举个具体例子A hello编码出来是0x41 0x05 0x68 0x65 0x6C 0x6C 0x6F5 个字节的长度能用 1 字节表示所以格式字节低 2 位是 01如果字符串有 300 字节长度字段就得用 2 字节格式字节变成0x42。见过太多实现把长度字段写死成 1 字节短报文全过一传配方就溢出。还有就是嵌套深度。SECS-II 的 L 可以无限层嵌套写递归解析器一定要设深度上限否则遇到脏数据或者恶意报文直接把栈打穿。这个道理跟 JSON 解析器要看嵌套深度是一回事只不过 SECS-II 没有 JSON 那种现成的库什么都得自己写。2.3 GEM标准里最软也最费工的部分E30 不定义任何一条报文的具体格式它定义的是什么时候应该发什么报文。两套状态机是核心通讯状态有 Disabled 和 Enabled 两个值通过 S1F13/S1F14 建立控制状态有 Equipment Offline、Attempt Online、Host Offline、Online Local、Online Remote 五个值迁移可以由设备本地按钮触发也可以由 Host 发 S1F15 请求离线、S1F17 请求在线触发。这两套状态机没跑对后面的数据采集全是空中楼阁。E30 还定义了数据模型SVID 是状态变量ECID 是设备常量ALID 是报警CEID 是事件RPTID 是报告VID 是报告里引用变量的编号PPID 是配方标识。以及一套能力菜单——不是所有设备都要实现全套 GEM厂商可以声明自己支持哪些功能。平台设计上必须把这些能力做成开关否则你会被迫给一台只支持基础采集的老机台实现一整套 trace 和配方管理纯属浪费。3. 通讯平台的骨架怎么搭连接、会话、路由三件事3.1 事务匹配的唯一凭据是 System Bytes主动发出一笔需要回复的报文时System Bytes 由主动方生成对方回的 Secondary 报文必须原样带回同一组 System Bytes这是唯一的事务匹配依据。Stream 和 Function 只能告诉你这是哪个业务的回包不能告诉你这是哪一笔的回包——同一个 S1F3 可能同时在飞好几笔。这就是为什么并发事务必须有上限一般单连接控制在 8 到 16 笔窗口再大回包匹配就容易乱。System Bytes 是 4 字节无符号几千万笔之后理论会回绕。实际业务量下不用担心回绕但要担心多线程生成重复。我见过一个实现用new Random().Next()生成同一个进程里短时间生成出重复值的概率高得离谱表现就是偶尔有一笔事务匹配到了错误的回包数据串到别的变量上排查了整整两天。正确做法是用单个原子递增计数器或者干脆用时间戳低位加计数器组合。还有一种更恶心的设备它回包时不用你给的 System Bytes自己生成一组。遇到这种只能退化成Stream/Function 时间窗口内最早未完成事务的匹配策略并在配置里为这个机型单独打标。这也是为什么配置驱动的设计很重要。3.2 线程模型IO 线程只做拆包业务绝不阻塞平台的线程模型我建议分三层。IO 线程只管从 socket 读字节、按长度字段切出完整报文、入队然后立刻回去继续读。业务逻辑放到工作线程池每个连接一个状态机实例状态机内部不能有阻塞调用。发送方向要单独排队加锁避免两个线程同时往一个 socket 写把两半报文交织在一起——这种错误现象特别迷惑因为单条报文各自都是合法的拼在一起就是垃圾。回包机制上同步等待和异步回调只能选一种混用是灾难。同步等待的好处是业务代码写起来像普通函数调用坏处是 T3 超时的时候工作线程被占住。异步回调反过来。我的经验是控制类、查询类走同步等待配超时事件上报处理、数据落库走异步队列两边用不同的线程池隔离开。这样即使数据库慢了也不会把状态机卡死。3.3 配置驱动的设备档案是最省时间的一个设计每台设备的接口手册都是独立的变量表、事件表、报警表、常量表各不相同同一机型换个固件版本还会改。平台一定要把这些东西做成外部配置文件YAML、JSON 或者从 Excel 导入而不是硬编码在代码里。配置项至少包含Device ID、Session ID 拼法、连接的主被动角色、超时参数覆盖值、支持的 GEM 能力集合、变量表、事件表、报警表、常量表。更关键的是要写一个离线校验工具。上线前跑一遍报告里引用的 VID 是不是都存在于变量表CEID 是不是都连了至少一个 RPTID变量名有没有重复数据类型跟配置里声明的是否冲突这一个工具就能把联调阶段一半的低级错误挡在门外。我们做过的项目里加了这个校验之后第一次现场联调成功率明显上去了因为大部分问题在办公室里就暴露了。4. 数据模型才是真正的工程量从 CEID 到 RPTID 到 VID 的链路4.1 事件报告链路的三段式定义GEM 的事件上报不是设备想发就发而是要先由 Host 建立起定义链路顺序不能乱用 S2F33 Define Report 定义报告。报文里带 DataID 和一组L[3]每个元素是{RPTID, VID 个数, [VID 列表]}意思是报告 RPTID 里包含这几个变量。用 S2F35 Link Event Report 把事件和报告连起来。报文里带 DataID 和一组L[2]每个元素是{CEID, RPTID 个数, [RPTID 列表]}意思是事件 CEID 发生时把这些报告发给我。用 S2F37 Enable/Disable Event Report 使能。报文里带一个布尔值表示使能还是禁用以及一组 CEID。之后设备端发生对应事件就会发 S6F11内容结构是 DataID CEID 一组L[2]每个元素是{RPTID, [变量值列表]}。请注意后半段变量值是一个按 VID 顺序排列的裸数组不带变量名。也就是说 Host 侧必须持有完全相同的变量表才能解出每个值对应哪个变量一旦设备端和 Host 侧的 VID 顺序不一致采集到的数据就会整体串位而且不会报任何错。这是这套协议里最反直觉、也最容易埋雷的地方我们内部管它叫位置约定。4.2 断线重连后必须重建的软状态设备端的这些定义和使能状态绝大多数实现是易失的掉电或者连接断开就丢。所以平台在连接恢复之后不能只是把 TCP 接上就宣布万事大吉必须走一套完整的重建流程。我的做法是给会话状态机加一个 REBUILD 阶段CONNECTED → SELECTED → REBUILD重放 S2F33/S2F35/S2F37、重设 trace、重设 spooling 参数→ READY。重建的每一步都要有独立的成功判据和重试上限。异步重建整体耗时可能十几秒期间收到的 S6F11 要正常处理但不能作为系统健康的证据。重建失败超过阈值必须告警否则会出现一种很隐蔽的故障连接是活的、心跳是通的、MES 那边看设备在线但事件上报链路其实早就断了产线上堆了一堆货没人知道。这种问题一旦漏到客诉层面解释起来非常被动。另外提醒一句spooling 的补传逻辑要单独设计。设备在断线期间把事件攒在本地重连后用 S6F23 请求把攒的数据捞回来或者由设备主动补发。补传的数据带的是历史时间戳入库时千万别用接收时间覆盖否则时序分析全乱。4.3 报警、常量、限值各有一套脾气报警走的是 S5 流。S5F1 是报警上报必须带三个字段ALCD报警码区分发生还是解除、ALID报警 ID、ALTX报警文本。S5F3 用来使能或禁用某条报警S5F5/S5F6 查报警清单S5F7/S5F8 查已使能的报警。这里有个实操细节报警文本 ALT X 的编码和长度各家差异极大有的只用 ASCII有的塞中文有的截断到 40 字符入库时最好统一做一次清洗和截断否则后面做报表会被脏数据烦死。设备常量走 S2F13/S2F14 读、S2F15/S2F16 写。这里最大的坑是写完之后什么时候生效有的设备立即生效有的要下一个作业周期有的必须重启相关模块。这个必须查设备接口手册不能猜也不能靠试——试错的代价可能是把在跑的批号搞坏。变量限值走 S2F45/S2F46 定义、S2F47/S2F48 查询通常用来做数据有效性判断。我一般把限值也纳入配置表在平台侧做一次前置校验明显超限的值先拦下来做标记再交给 MES 判断这样能减少后端的数据污染。5. 时序参数怎么定T3/T5/T6/T7/T8 不是抄默认值就完事5.1 五个超时的真实含义与建议区间标准里给了默认值但直接照抄往往不合适。下面这张表是我在几个项目里调出来的经验区间参数标准默认实际管什么我的建议T345 s主动发出后等对方回包的最长时间按业务分级查询类 5 到 10 s配方类 30 到 60 sT510 s连接建立后等 Select 完成的间隔保持 10 s重连次数上限设 3 到 5 次T65 s控制事务Select/Deselect/Linktest超时保持 5 sT710 s已连接但未进入 Selected 状态的停留上限保持 10 sT85 s单条报文内部的字符间超时HSMS 下意义弱化但拆包缓冲要有上限重点说说 T3。默认 45 秒是串口时代为了容错留的余量放到以太网上太宽松了。业务上如果真的卡 45 秒产线节奏早就受影响。我的做法是按报文类型分档状态查询给 5 到 10 秒事件定义类给 15 秒配方上传下载这种大报文给 30 到 60 秒。分级之后超时能被更早发现重试也更快整体吞吐会明显改善。T8 在 HSMS 下不太好直接对应——TCP 是流没有字符间的概念。但它提醒我们一件事拆包缓冲必须有上限。我见过设备发一条长度字段写错成 0xFFFFFFFF 的脏报文解析端老老实实等着收 4GB内存直接爆掉。缓冲区设个 1MB 到 4MB 的硬上限超了直接断连并记录比 OOM 崩进程体面得多。5.2 Linktest 与网络中间设备长时间没有数据往返的连接中间经过的防火墙、NAT 设备、负载均衡会把会话表清掉表现就是业务不发就没问题一闲下来再发就超时。解决办法是定期发 Linktest.req 等 Linktest.rsp 保活。间隔的取法各家不同我一般取 T6 的整数倍或者干脆固定 30 秒具体还是看设备手册有没有明确要求。Linktest 用的 System Bytes 要跟业务事务隔离开。常见做法是控制报文走一个独立的高位区间或者用一个固定的约定值只要保证在途事务不冲突就行。曾经有个项目为了省事心跳和业务共用一个计数器结果心跳把正在等回包的业务事务的 System Bytes顶了两台设备的回包互相串了查了半天才发现是这里。5.3 重连退避固定 1 秒重连会把设备打爆设备侧的资源通常比服务端紧张。如果平台用固定的 1 秒间隔重连几十台设备同时掉线时重连风暴能把设备端的连接处理模块直接压垮然后就是掉线、重连、再掉线的死循环。正确做法是指数退避加抖动1、2、4、8、16、32、60 秒每次在上限基础上叠加 ±20% 的随机抖动避免多台设备同时重连。到达最大退避之后进入低频探测模式比如五分钟一次同时把告警推出去让人介入。还有一点断开连接的时候要先发 Separate.req再关 TCP。直接 close socket设备端会认为是异常断开可能触发它自己的保护逻辑甚至标记成通讯故障写进报警历史。礼貌地告别代价只是一条控制报文。6. 联调阶段最耗时间的问题以及我的排查链路6.1 半包粘包从现象到复现现象通常是这样连接建立后能正常跑几分钟偶尔解析出一堆莫名其妙的 Stream 号或者报长度超限然后连接断开重连又好了。原因就是 TCP 是字节流一次 recv 可能只拿到半个头部也可能一次拿到三条半报文。排查方法很直接在 socket 读取的回调里把每次读到的原始字节数和前 16 个字节的十六进制打出来。如果看到某次只有 6 个字节那就百分百是半包。复现手段也简单在发送端故意把一次 write 拆成两次中间 sleep 几毫秒问题立刻就出来。解决办法是缓冲区累积加按长度字段切分绝不能依赖 read 的边界。用现成的框架更省事Netty 有 LengthFieldBasedFrameDecoder.NET 有 System.IO.Pipelines都是为这种场景准备的。我自己更倾向于用框架因为手写的切分逻辑在边界场景长度字段本身也被切断上很容易出 bug而这类 bug 在测试环境很难复现。6.2 卡在 T3 超时先查 W-bit再查 System Bytes业务卡住、日志显示等待超时我一般按这个顺序排查第一步看发出去的报文 W-bit 是不是 1。W0 是单向通知对方压根不会回你等一辈子也等不到。这个错误在事件上报类报文上特别常见因为 S6F11 在 GEM 里多数场景就是 W0。第二步看收到的回包 System Bytes 跟原报文是否一致。不一致就是匹配策略的问题参考前面 3.1 节说的。第三步看 Stream/Function 是否配对。有些设备用 S1F0 表示我收到了但没法回复如果你没处理 S1F0就会一直等到超时。第四步去设备端日志看有没有 S9F9。如果有说明设备认为它自己的事务超时了——往往意味着设备先给你发了请求而你没回。6.3 收到 S9 系列错误报文到底意味着什么S9 流是 SECS-II 层的错误反馈收到它说明对方的解析器认为你的报文有问题这比超时好定位得多。常见的几个报文含义通常的原因S9F1未识别的 Device IDSession ID 或设备 ID 填错S9F3未识别的 StreamStream 号超出对方支持范围S9F5未识别的 FunctionFunction 号不对或该功能未实现S9F7非法数据编码违规比如长度字段写错、格式字节不对S9F9事务超时对方等你的回复等超时了S9F11数据过长超过对方能接受的报文长度上限S9F13会话超时长时间没有有效交互S9 报文里会带一个 MHEAD 字段把出错报文的消息头原样带回来用它就能精确定位是哪一条发出的报文惹的祸。平台必须把 S9 处理当成一等公民收到就落库、就告警、就带上原始报文 hex而不是当成普通报文混在流水里。我见过一个平台把 S9 全丢掉了结果设备端明明一直在抱怨你的编码有问题Host 侧看起来风平浪静最后靠抓包才发现。6.4 字符串与数值的编码陷阱A 类型按标准是 ASCII只能表示 0 到 0x7F。现实中设备的注释文本、报警文本经常带中文这时候要么约定走 JIS-8要么厂家有私有的编码约定。遇到乱码别急着骂设备先确认双方对这段字节的解释是不是同一个。数值类型全部大端I4 和 U4 最容易翻车。有些设备的手册写的是4 字节整数没写字节序新手默认按小端解读出来的值总是离谱的大数。反过来你自己的编码器也必须是大端的网络字节序这一点没有例外。还有一个经典坑是空列表。L[0]的写法是格式字节低 2 位为 0长度字段根本不出现一个字节就完事。有些实现习惯性地补一个 0x00 长度字节结果对方解析器认为这个 L 的长度是 0、但后面多了一个字节直接报 S9F7 非法数据。这个错误在联调初期几乎人人踩过。7. 上线之后日志、压测与版本演进7.1 双份日志hex 和 SML 一个都不能少日志一定要打两份。一份是原始字节流的十六进制一份是解析后的 SML 文本两边都带时间戳、连接标识、方向、设备 ID。定位问题的顺序通常是先翻 SML 看逻辑对不对发现逻辑没问题但对方就是抱怨再翻 hex 看编码对不对。只有 SML 没有 hex编码类问题永远查不清只有 hex 没有 SML看日志能看瞎眼。SML 的文本格式大概长这样方便肉眼读S1F13 W L [2] A EQP001 A 1.0.0 .滚动策略我一般按天 设备分文件保留 7 到 15 天。生产环境千万别开全量 hex 到控制台几百台设备的报文速率能瞬间把磁盘和 IO 打满。做成可以按设备动态开关最好出问题的那台临时打开排查完关掉。7.2 压测该压什么压测不能只压能连上。我通常测这五项连接数目标并发连接数乘 1.5看文件描述符、内存、线程数是否可控。报文速率每秒事务数按峰值的两倍打观察 T3 超时率。长报文几 MB 的配方传输验证拆包缓冲上限和内存峰值。断线重连风暴批量设备同时断开再同时重连看退避策略是否真的生效。长稳至少跑 72 小时盯内存增长曲线。SECS/GEM 平台最常见的长稳问题是每个连接泄漏一点缓冲区跑三天内存翻倍。7.3 接口文档和配置表的版本管理每台设备的接口手册都有自己的编号和版本号配置表必须跟设备档案绑定版本。设备固件升级之后哪怕只是小版本也要跑一次回归变量表有没有变、事件定义有没有变、超时参数有没有变。我们吃过一次亏设备升级固件后静默调整了某个 S6F11 里报告的顺序采集数据整体串位而且没有任何报错最后是靠人工核对原始 hex 才发现的。我个人的习惯是给每个机型的配置表做一份变更日志写明哪一天、哪台设备、哪个固件版本、改了什么字段、谁验证的。这东西在出问题时能救命——尤其是在客户催着要结论、而你需要快速证明不是平台的问题的时候。最后分享一个小技巧在平台里加一个报文回放功能把生产环境抓到的 hex 日志导进来重跑一遍。很多偶发性问题现场根本复现不了但手上有日志就能在办公室反复回放定位效率至少提升一倍。这个功能开发量不大收益却特别高属于我强烈建议第一版就做进去的东西。
返回列表