ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

半导体设备通信核心:SECS/GEM协议栈与HSMS实现深度解析

2026/8/29 8:43:41 拓冰建站 浏览量
半导体设备通信核心:SECS/GEM协议栈与HSMS实现深度解析 简介半导体制造中设备与MES系统的稳定通信是产线连续运行的关键而SECS/GEM协议正是解决设备接口碎片化的行业标准。它通过E37HSMS建立基于TCP/IP的高速传输通道配合E5SECS-II完成消息的编解码同时以E30GEM定义设备状态与行为模型。理解这套协议栈的原理有助于工程师掌握设备与Host之间连接建立、心跳保活、消息帧封装及数据交互的完整链路。在实际应用中无论是配方下发、过程数据采集还是报警上报都依赖这套标准的可靠性。本文基于JngHightSpeedSecs开源实现拆解协议分层结构、建连流程、消息处理机制和高性能设计要点并结合常见故障排查经验为半导体设备通信二次开发提供可落地的工程参考。 做半导体设备软件的朋友对SECS/GEM这几个字母肯定不陌生。设备要跟MES制造执行系统通信跑配方、报数据、传履历绕来绕去都绕不开这套标准协议栈。我最近在梳理一套名为JngHightSpeedSecs的SECS/GEM源代码把E4、E5、E30、E37那套东西用一套高性能内核重新实现了一遍既有完整的SECS-II消息编解码也有GEM状态模型。这篇文章就把这套源码的协议栈结构、建连流程、消息处理机制和常见坑位拆开讲清楚适合正在做设备端通信模块二次开发的工程师也适合想弄懂设备与Host之间到底在传什么的新手。1. 这套源码解决的核心问题为什么SECS/GEM绕不开1.1 半导体制造里设备通信的真实痛点半导体前道、后道设备包括刻蚀、薄膜沉积、光刻、清洗、测试机、分选机几乎都要与工厂的MES或Host系统通信。通信的内容不只是“设备开没开机”而是要把完整的工艺配方下发给设备把每片晶圆的过程数据、测量结果回传给Host还要上报报警、跟踪设备状态。这个通信链条一旦出问题产线上就是大批量停线所以工厂对这个通道的稳定性要求极其苛刻。问题在于设备来自不同厂商每个厂商的软件风格都不一样。如果没有一个统一标准MES厂商就得为一台刻蚀机写一套接口再为一台测试机写另一套接口维护成本完全失控。SECS/GEM协议就是为了解决这个“接口碎片化”而生的行业标准。它定义了设备与Host之间怎么建立连接、消息怎么封装、数据用什么类型表示、设备状态怎么切换让来自不同厂商的设备能用一个标准接口接入同一个工厂系统。JngHightSpeedSecs这套源码本质上就是把这套标准协议栈从零实现了一遍。它不是给业务逻辑用的框架而是通信底座负责把“底层TCP/IP的数据流”翻译成“上层业务看得懂的SECS消息”同时负责心跳、重连、超时、消息分片这些脏活累活。1.2 JngHightSpeedSecs与标准协议栈的对应关系SECS/GEM标准体系由SEMI组织发布涉及的核心标准主要有四个SEMI E4SECS-I基于RS-232的半双工通信协议早期老设备用得比较多。SEMI E5SECS-II定义了消息内容的数据格式和语义也就是消息体怎么编码。SEMI E30GEM定义了设备行为模型包括状态模型、报警管理、配方管理、事件管理、数据收集等。SEMI E37HSMS基于TCP/IP的高速通信协议现代设备基本都走这套替代了老的RS-232方案。JngHightSpeedSecs这个名字里的HighSpeed指的就是它直接以HSMSSEMI E37为传输层而不是老旧的SECS-I。整套源码在处理消息时把E5的SECS-II格式编解码和E37的HSMS传输层都做了模块化封装同时向上层提供了E30 GEM级别的语义支持。从代码层面看这套源码大致可以分为三层。最底层是TCP通信层负责socket管理、数据收发、断线重连中间层是HSMS协议层负责处理Select、Linktest、Separate这些控制消息以及把上层数据消息封装成HSMS帧最上层是SECS-II消息层负责Stream/Function消息的解析和构造同时提供GEM状态的维护入口。我把这套源码从头到尾理了一遍之后发现它的分层思路和商用SDK非常接近适合拿来当底层库集成到自己的设备软件里也可以做协议学习参考。2. 源码层面的核心机制拆解2.1 消息帧格式与编解码链路要读懂SECS/GEM源码第一关就是搞明白消息在网络上到底长什么样。以HSMS为例一条完整的消息由三部分组成消息头、消息体、消息长度。HSMS消息头固定10字节里面包含Session ID、Stream与Function编号、Reply Expected标志、PType、SType和System Bytes。其中System Bytes是一个四字节的自增序号用于把请求和回复配对这个在源码里通常有一个专门的计数器来管理。PType固定为0表示SECS-II消息SType为0时是数据消息为1到9时是控制消息比如Select.req、Linktest.req这些。消息体就是SECS-II格式编码的数据。SECS-II的数据格式是一棵带类型的树可以嵌套包括List列表、ASCII字符串、Binary二进制字节、Boolean布尔、四种整数类型U1/I1/U2/I2/U4/I4/U8/I8、两种浮点类型F4/F8。源码里的编解码器会递归这棵树把内存里的对象转换成字节流接收时再反向解析回来。我在源码里比较关注的是格式头Format Header的处理逻辑。SECS-II规定每个数据项的格式字节由高四位表示数据类型低四位表示长度长度超过255字节时要用长度扩展。如果实现不对解析中文ASCII字符串或者超过长度限制的配方数据时很容易出错。JngHightSpeedSecs这块处理得比较干净它把格式编码单独抽成了工具类读写分开从源头上避免了编解码状态互相污染的问题。2.2 传输层状态机与建连流程HSMS通信不是TCP一连上就能传数据的它有自己的会话状态机。我刚看这套源码的时候花了不少时间才把状态迁移理清楚因为代码里用枚举定义了至少四五个状态并且通过事件驱动在那几个状态之间跳转。HSMS的状态大致包括Disabled未启用、Init初始化、Not Connected未连接、ConnectedTCP已连接但未选择、Selected会话已选择可以传数据。设备端和Host端在建立TCP连接后必须先完成Select流程也就是主机发Select.req从机回Select.rsp双方进入Selected状态之后才允许传输数据消息。这套源码里还有一个Communication Retry状态用于连接中断后按策略自动重连的场景。这里要重点说一下Select.rsp里的结果码。Header Byte 2和Byte 3的二进制位分别表示是否支持该Session ID和是否已建立连接源码里对应的校验逻辑如果写得太粗糙会吞掉合法的Select请求。我在联调时就踩过一次因为对方Host发的消息里携带了附加的Session ID而代码里只比较了低16位导致多会话场景下选不上会话。后面改成按标准注释逐位解析问题才解决。除了Select流程Linktest心跳机制也是源码里必须看明白的部分。Host端通常会按照T7计时器周期性地发送Linktest.req设备端必须在T3时间内回复Linktest.rsp。如果设备端迟迟不响应Host会判定链路异常并断开。源码里Linktest的响应逻辑通常在接收线程里同步处理如果这个线程被阻塞比如业务回调里做了耗时的文件写操作心跳就会超时导致断链。2.3 高性能设计为什么它敢叫HighSpeed既然叫HighSpeed性能上必然有些设计考量。我看了这套源码之后归纳出三点比较关键的设计。第一点是收发分离的双缓冲队列。接收线程只做一件事从socket读取字节流按HSMS帧格式拆包把完整的消息塞进接收队列业务处理线程从队列里取消息做解析和回调。发送侧同样有独立的发送队列业务线程调用发送接口后立即返回不阻塞在socket写操作上。这种设计在批量上传数据、并发请求多的场景下特别有用能避免业务代码里频繁的锁竞争。第二点是内存池复用。SECS消息在频繁通信时会产生大量临时对象如果每次收发都走一次系统内存分配GC压力和堆碎片都会上来。这套源码在数据缓冲区层面做了预分配和复用我在代码里看到了默认缓冲区池的实现长度在几KB范围内的消息基本都能从池里拿内存用完归还。实测下来在高频采集场景下内存分配次数明显减少。第三点是System Bytes的递增管理。每个请求发出时要分配一个同步号源码里用原子变量来递增避免多线程环境下的重复分配。这个号一旦重了请求和回复的匹配关系就乱了业务层会拿到错误的结果。用原子变量比加锁效率高也比随机数可靠因为随机数在极端情况下可能重复自增号的碰撞窗口基本不存在。3. 实操用JngHightSpeedSecs搭一个设备端通信服务3.1 从源码构建与初始化配置先把JngHightSpeedSecs的源码工程打开我拿到的版本是以C/C为主实现对外暴露了类似SDK风格的接口同时提供了一套C#调用示例。如果你要在Windows上跑直接打开工程文件依赖项主要是标准网络库没有额外的第三方组件编译起来很省心。Linux环境下的CMakeLists文件也是配好的只需要把编译器切换到C17标准就行。编译完成之后第一个要配置的是设备通信参数。以设备端为例下面这段初始化配置是这类SDK最常见的用法我以示意代码说明SecsGemDevice device; device.deviceId 0; // 设备ID跟Host端约定 device.ipAddress 192.168.10.20; // 设备端IP device.port 5000; // HSMS默认端口 device.connectMode Passive; // 设备端处于被动监听模式 device.t3Timeout 45; // 回复超时单位秒 device.t6Timeout 5; // 控制消息超时 device.t7Timeout 10; // 链路空闲心跳间隔 device.linktestEnabled true; // 开启心跳保持 device.initialize();设备ID不用多说是设备在工厂里的唯一编号Host端用它来区分链路上的多台设备。connectMode这里要重点说SECS/GEM通信里有Active和Passive两种角色设备软件里通常做成可配置项。主动模式下设备去连接Host的端口被动模式下设备监听自己的端口等Host来连。产线调试时两种模式各有坑后面我会单独讲。配置完成后调用start方法启动服务。源码里这个调用会同时启动接收线程和业务处理线程池服务就绪后会打一条日志类似“HSMS service started, waiting for host connection”之类的信息看到这条日志基本说明底层通信模块已经活了。3.2 实现设备端S7F1/S7F5通信跑通基础连接之后下一步就是让设备能响应Host的请求。我最常用的是配方相关的两类消息S7F1Process Program Load Request和S7F5Process Program Request。S7F1是Host通知设备“有一个配方要下发”设备收到后回复S7F2表示是否接受S7F5是Host请求设备返回某个配方内容设备回复S7F6携带配方数据。这套源码里注册消息处理的模式是典型的观察者风格伪代码大概是这个样子device.registerHandler(7, 1, [](SecsMessage msg, ReplyContext ctx) { // 解析请求数据比如配方ID auto recipeId msg.body().getList().get(0).asString(); // 检查配方是否存在于设备 bool accept checkRecipeExists(recipeId); // 构造S7F2回复 SecsMessage reply(7, 2); reply.body() .addBinary(accept ? 0 : 1); // 0接受非0拒绝 .addList() .addU4(recipeId); ctx.reply(reply); }); device.registerHandler(7, 5, [](SecsMessage msg, ReplyContext ctx) { auto recipeId msg.body().getList().get(0).asString(); RecipeData data loadRecipe(recipeId); SecsMessage reply(7, 6); reply.body() .addList() .addU4(data.id) .addA(recipeId) .addF8(data.param1) .addA(data.params); ctx.reply(reply); });需要注意消息处理器运行在工作线程池里不是在socket接收线程里。这个设计是合理的因为业务回调经常要做文件读取、数据库查询之类的耗时操作不能阻塞接收线程。但对应的代价是如果你的处理器内部访问了共享变量一样得加锁别以为回调里就安全了。另外一个关键点是业务回调里判断是否“需要回复”。Host发来的请求消息带有Reply Expected标志源码解析消息头时会把标记存下来业务回调里通过msg.isReplyExpected()判断。如果Host不期望回复你还硬回一条Host会当成意外消息处理甚至断开连接。我见过有人在S6F11这种事件上报消息上加了回复逻辑结果Host端日志刷了一堆异常排查了很久才发现是这里的问题。3.3 与Host端联调验证的正确姿势设备端代码写完必须找一套Host端模拟器来联调。网上常见的SECS/GEM模拟器工具有好几款比如SECS Simulator、E4/E5/E37协议模拟器之类的。我习惯用一个轻量级Host模拟器来做主测工具它支持基础的Select、Linktest能手工构造任意Stream/Function消息还能记录所有收发消息的内容。联调第一步先用模拟器以Active模式连接设备端的Passive监听端口。连接成功后看模拟器是否自动完成Select交换。正常情况下日志里会依次出现TCP连接建立、Select.req发出、Select.rsp返回、进入Selected状态这几条记录。如果Select没通过先检查设备ID是否一致再检查模拟器和设备端的端口、IP配置是否匹配。第二步手工发送一条S1F1Are You There消息设备端应当回复S1F2携带设备状态和厂商信息。这是最基础的消息也是判断协议栈通不通的试金石。S1F1通了再测S7F5和S7F1这些业务消息。每测一个消息都打开模拟器的原始帧日志对照SECS-II报文字节逐项看一遍确认数据类型和长度都符合标准。联调阶段我建议做一次“断开重连”测试就是让Host端强制断开TCP连接观察设备端能否在T5超时后检测到断连并按重试策略重新监听。JngHightSpeedSecs源码里对断线重连做了状态重置如果没重置好会出现连接恢复了但状态还停留在Selected的错误导致后续消息被静默丢弃。我的经验是每次断线测试后都主动看一遍设备端的运行日志确认状态机重置到了Not Connected再进行下一轮。3.4 配置与调优参数速查不同工厂的Host系统对通信参数要求不太一样我整理了实际项目里经常需要调整的参数和推荐值你可以直接拿去做参考参数推荐值说明T345sSECS消息回复超时设太短容易误判慢速设备T510s建立TCP连接后等待Select的超时时间T65s控制消息Select/Linktest等的响应超时T710s链路空闲时间超过后主动发心跳一般10到20秒T85s连续接收到不完整或异常帧的容忍时间System Bytes起点随机初始化避免设备重启后与Host端已有缓存的值冲突接收缓冲区大小4KB起步配方数据大的话建议调到64KB以上这里特别提醒一下T3和T7的关系。T3管的是数据消息的回复等待时间T7管的是链路空闲心跳。如果设备在收到数据消息后业务处理耗时超过了T3Host那边会判定超时并报错。解决方向有两个一是把T3调大二是把耗时操作改成异步处理先立即回复“收到”再做实际处理。JngHightSpeedSecs源码里没有限制你必须在回调里同步回复所以异步回复是完全可以实现的关键在于ReplyContext的生命周期管理回调结束后它还活着你把上下文缓存到别的地方后续再调用reply方法即可。4. 常见问题与排查技巧实录4.1 连接建不上、链路反复断连接建不上是国产化设备接入SECS/GEM时最常见的故障。我排查这类问题有一个固定套路先确认物理链路再抓包看协议交互最后定位代码逻辑。第一步telnet一下端口确认TCP层通不通。如果telnet能通但Select失败把抓包目标锁定在Select.req/Select.rsp上。最常见的两个原因一是设备ID不匹配设备端配置的Device ID和Host端请求的不一致二是Select.rsp的结果码没有按标准填尤其是“Connection Not Ready”这个状态表示接收方认为当前会话还没有准备好这多半是状态机没有正确进入Connected状态。链路反复断的情况大概率是Linktest响应不及时导致的。设备端接收线程如果被业务回调阻塞在T7间隔内收不到Linktest.reqHost会判断链路死了。我在源码里看到Linktest处理应该放在接收线程里快速完成不要在回调里做任何耗时操作。如果已经这么写了还是断检查一下设备的防火墙策略有些网络环境会静默丢弃长时间空闲的TCP连接这时需要把T7心跳调小到10秒以下。4.2 消息收发超时的排查消息超时在联调阶段太常见了。现象是Host发S7F5设备端不回或者回了但Host在T3时间内没收到。抓包之后通常能看到请求已经到了设备端回复也发出了但客户端收不到或者设备端压根没收到请求。如果请求到了设备端但没回复重点检查消息注册表有没有绑定对应的Stream/Function。JngHightSpeedSecs源码里的注册表是按Stream和Function两维索引的注册错了比如把7F5注册成了7F3收到请求后就找不到处理器直接丢弃Host就会超时。这个坑可以通过日志排查收到未注册消息时源码会打印一条warning级别的日志。如果设备端已经调用了reply但Host始终收不到那要看回复消息构造是否正确。比如回复的Stream/Function填反了S7F6写成了S7F2Host那边虽然有消息进来但校验时会当成异常消息丢掉。最有效的方法是直接用模拟器或者Wireshark看原始帧把消息头10个字节一项项对比标准很快能找到问题。4.3 大块数据比如配方、Trace数据传输慢配方数据动辄上百KBTrace数据在高频采集时更是持续不断这类大块数据传输容易触发两个问题。第一个是消息长度字段溢出的处理。HSMS消息长度字段占4字节对普通配方来说完全够用但如果你把整个Trace数据一次性塞进一条S6F11消息有些实现会出现长度解析异常。JngHightSpeedSecs在这一点上处理得不错它会按实际长度动态扩展缓冲区不需要提前设上限。第二个是TCP粘包拆包。HSMS协议本身不保证一个TCP包就是一条完整消息所以接收端必须有按帧长度拆包的逻辑。如果拆包逻辑写错了小消息看不出问题大消息一传就乱。源码里拆包的核心是先读4字节长度再按这个长度读对应的消息体代码是标准的“读长度-读数据”循环但前提是接收缓冲区要设置得足够大特别是多个消息连续到达时。我在实际场景里把接收缓冲区调到了128KB大块消息传输才稳定。对大流量Trace数据我还要提醒一点避免在SECS消息里传过大的单个数据项。如果一条S6F11里塞了几万个数据点发射端一次malloc大块内存接收端也要解析大List整体效率都会下降。合理的拆分策略是把数据按时间段分成多条消息上报每次不超过2KB到4KB既保证了实时性又降低了单条消息的失败风险。4.4 与标准Host软件兼容性的几个细节很多工厂会用美国、韩国厂商的Host软件这些软件对协议实现的要求非常严格经常会在别人看不出来细节上卡住。我自己碰到过三个典型的兼容性问题。第一个消息头的Device ID字段。虽然大多数场景只用一个设备ID但Host可能会在消息头里携带Session ID信息有些实现忽略高字节有些则要求必须全量匹配。JngHightSpeedSecs源码里对Device ID的比较是全量16位比较如果遇到只比较低7位的旧Host软件需要按对方的实现调整。第二个ASCII字符串的编码规范。SECS-II标准规定ASCII使用可打印字符但设备返回的版本号、设备名称里经常包含不可见字符或者UTF-8编码的中文。虽然很多Host软件能容忍但也有严格的Host会直接拒绝这类消息。我的做法是设备端所有对外ASCII字段统一走英文和数字如果一定要传中文优先用Binary类型并附带编码说明而不是硬塞进ASCII里。第三个S6F11等事件消息上报格式。GEM标准对事件上报有一套严格结构包括DATA_ID、CEID和事件数据列表。有些设备软件把自定义字段直接往上报Host解析不到对应项目就报错。我建议在上报前用标准的GEM验证工具跑一遍消息格式校验确认结构树符合E30规范再联调能省下大量时间。4.5 常见故障快速定位表现象可能原因排查方向TCP能通但Select失败设备ID不匹配对比握手报文中的Device IDSelect.rsp状态异常状态机未正确进入Connected检查连接状态重置逻辑心跳超时断链接收线程被阻塞确认回调里没有耗时操作Host发请求设备端不回消息处理器未注册查看warning级日志回复发出但Host收不到Stream/Function填错用原始帧对比消息头大消息丢帧乱序接收缓冲区过小调大缓冲区并复测中文ASCII字段乱码编码不符合标准改用Binary或英文内容事件消息Host解析失败数据结构不符合GEM用GEM校验工具验证报文5. 从源码二次开发角度补几句话如果你不是只跑通示例就完事而是想把这套源码集成到自己的设备软件里有几个设计上的取舍很重要。第一线程模型要和现有软件匹配。JngHightSpeedSecs内部默认起了接收线程和处理线程池如果你的设备软件本身是单线程事件循环可以改造它的消息分发把SECS消息转换成自己的事件投递而不是让源码自己开线程。这样能避免两套线程模型互相干扰尤其在Qt或者MFC这类有自己的消息循环的框架里直接嵌入多线程SDK很容易出现界面卡顿和回调乱序。第二日志接口要接好。源码里的日志输出默认打到stdout但设备软件通常有自己的日志系统。我建议把日志回调接口接上把SECS通信层的日志统一写到设备日志文件里。遇到产线问题第一件事就是翻通信日志查Select、Linktest、消息收发记录一份完整的通信日志能把问题定位时间压缩到几分钟量级。第三做好配置持久化。设备ID、IP、端口、T3超时这些参数建议做成配置文件或者写入设备注册表不要写死在代码里。工厂机房里的设备移动、网络改段是常有的事如果每次改配置都要重新编译烧录现场工程师会非常崩溃。我在实际项目里把这些参数都做成可以远程修改的配置项配合通知机制动态生效省了很多现场运维的麻烦。收个尾这套源码的价值在我看来说三点最后说点个人心得。做SECS/GEM通信开发最难的不是写出能收发消息的代码而是写出能在产线上稳定跑几个月的代码。JngHightSpeedSecs这套源码给我的最大收获不是它帮我省了多少开发工作量而是它让我看到了一个工业级通信库应该有的样子状态机边界清楚、超时处理完整、线程模型清晰、大块数据有预案。对照标准协议文档逐行读它基本上能把SECS/GEM协议的很多隐藏细节吃透比如Session ID的位域规则、System Bytes的复用策略、心跳与数据消息的优先级关系这些都是标准文档里不会花大篇幅讲、但实际调试时一定会碰到的知识点。如果你也是刚开始接触SECS/GEM我建议手里备三样东西一份SEMI E37和E5的标准文档一台可以用Wireshark抓包的测试环境以及一套能手工构造消息的Host模拟器。有了这三样再配合源码基本就具备了自己动手排查问题的能力了。希望这篇拆解能帮你少走点弯路。本文还有配套的精品资源点击获取