资讯动态

半导体SECS/GEM通讯平台实战:从HSMS报文解析到设备接入

发布时间:2026/10/15 4:35:32 来源:尧图企业网站定制
简介半导体制造行业在国产替代与智能排产双轮驱动下SECS/GEM协议已成为设备与MES/EAP系统之间事实上的通讯语言。PDF面向半导体设备工程师、工厂自动化工程师及产线信息化管理者围绕SEMI标准体系中的SECS/GEM、GEM300、Interface-A等协议结合金南瓜等国产解决方案的典型实现讲清从底层API调用、PLC地址映射到EAP上层集成的完整链路并给出湿法设备、注胶设备、退火设备等落地方案中的配置思路与排错要点内容涉及E4、E5、E30、E37等标准族谱的适用范围和选型建议可应用于新建产线规划、老设备升级改造及MES对接验收等场景。压缩包共1个PDF文件大小1.1MB主线凝练适合作为部署前快速扫盲或项目中期回头翻阅的手册。已有370人学习下载读完可快速建立对设备联调、数据采集和标准合规的整体认知降低因协议理解偏差造成的集成返工。1. 半导体SECS/GEM通讯平台从设备说方言到统一翻译层在半导体产线碰过设备对接的人大多经历过这种场面一台新设备拉到现场供应商留下一本几百页的协议文档和一个脚本demo你要在两周内让它和MES/EAP对上话。设备说的不是HTTP也不是Modbus而是SECS/GEM——半导体行业设备自动化的事实标准。所谓secsgem通讯平台就是把SECS/GEM从字节流、消息编解码、设备状态模型到业务告警的一整套通讯能力收敛成一个可复用的平台层让上位系统不用关心每台设备底层的“方言”。它解决的是“设备怎么连、消息怎么收、事件怎么上报、报警怎么翻译”这一串问题。本文写给要实际落地这套平台的设备工程师和系统集成工程师目标是让你从协议名词走到能跑通的最小实现并避开最常见的坑。2. SECS/GEM协议栈到底在传什么HSMS与SECS-II的核心机制与选型理由2.1 为什么上下游都用SECS/GEM而不是普通TCP/Modbus很多第一次接触SECS/GEM的开发者都会问设备明明支持TCP为什么不直接在Socket上定一套私有协议答案是成本。一条产线有几十种设备来自不同供应商如果每家的设备都用私有协议那每种设备就要写一套通讯驱动设备换型号就要改代码主机侧维护成本直接爆炸。SECS/GEM的价值在于“约定”哪条消息用来建联、哪条消息用来上报事件、设备状态怎么定义、变量怎么组织都由标准固定下来。设备供应商只要按标准实现主机侧就能用同一套平台接入不同设备。注意SECS/GEM不是一个取代TCP/IP的协议而是在TCP/IP之上定义了会话、消息和语义三层约定。所以“用SECS/GEM还是用TCP”这个问法本身不准更准确的问法是在TCP之上跑裸报文还是跑SECS/GEM语义层。裸报文的问题在于没有会话管理、没有消息分层、没有应答关联而SECS/GEM提供了设备ID、W-bit应答、System Bytes关联、HSMS控制消息等一整套机制。这也是为什么半导体行业的MES对接几乎绕不开它设备种类太多只有统一语义才能让平台侧只写一套代码。2.2 HSMS消息头与SECS-II编码一个S1F13消息的字节级拆解现在的SECS/GEM设备绝大多数走HSMSHigh-Speed SECS Message Services也就是TCP之上的传输层。一个HSMS消息由三部分构成4字节的消息长度、10字节消息头、以及可选的正文。消息头只有10字节但每个字段都有讲究下面用S1F13举例拆解。字段字节偏移含义Message ID0-1低15位是Device ID最高位是W-bitW1表示需要回复Stream2消息流号如S1F13就是Stream1Function3消息功能号如S1F13就是Function13PType4协议类型SECS-II填0SType5会话类型数据消息填0Select/Linktest等控制消息填非0System Bytes6-9请求与应答的关联ID回复时必须带回原值S1F13是“Establish Communications Request”主机用它向设备发起通信确认。最简单的S1F13正文是一个空列表用SECS-II编码就是两个字节0x01 0x00。下面我用Python手工拼一个完整的HSMS报文方便看清楚每个字节。import struct def build_s1f13(device_id: int, system_bytes: int) - bytes: # W-bit 置1要求设备回复S1F14 msg_id (device_id 0x7FFF) | 0x8000 # 10字节消息头MessageID Stream Function PType SType SystemBytes header struct.pack( HBBBB4s, msg_id, 1, # Stream1 13, # Function13 0, # PType0 0, # SType0 system_bytes.to_bytes(4, big) ) # S1F13正文空列表SECS-II编码为 01 00 body b\x01\x00 payload header body # 消息长度为消息头加正文总长度 return struct.pack(I, len(payload)) payload if __name__ __main__: msg build_s1f13(device_id1, system_bytes0x1001) print(msg.hex())这段代码把关键字段都落到了字节上。struct.pack(HBBBB4s, ...)里H是2字节无符号短整形四个B分别是Stream、Function、PType、SType最后的4s是4字节System Bytes。很多新手在这犯的错是把System Bytes当2字节用导致后面的字段错位设备端解析出来的消息头全是乱的。另外PType和SType在数据消息里必须填0如果你在控制消息里也填0设备会把Select请求当成数据消息处理连接永远建立不起来。2.3 通讯库选型自研、开源、商业的边界动手写平台之前先回答一个问题通讯层是自己写还是用现成的库我的建议是分层看。传输层和消息编解码——也就是上面这段拼报文拆报文的工作可以自己写但要尽可能小只处理“字节流到消息对象”的转换。GEM业务层也就是状态机、事件采集、报警管理这部分不要自己造直接用经过验证的协议栈或开源库因为这里面的状态转换和边界情况极多自研容易漏。我见过三种典型做法。第一种是小团队验证Demo自己用SocketGEM状态机模型拼一个最小实现优点是代码透明缺点是遇到非标设备会花大量时间调字节。第二种是中型设备集成项目用开源SECS库做底层自己做业务映射这是最常见也最划算的路径。第三种是整线自动化项目采购商业协议栈因为要过SEMI认证审计商业库的合规性文档更全。选择时看团队对字节级的掌控能力和项目周期但有一条底线底层字节解析必须自己能看懂否则出问题就是黑匣子。路线可控性维护成本适用场景自研高高小规模Demo、架构验证开源库中中设备种类多、业务逻辑复杂商业协议栈低低需要认证审计的整线项目3. 搭建一个SECS/GEM通讯平台从单机通讯到多设备接入3.1 平台总体架构设备接入层、消息路由层、业务处理层平台化不是把每个设备通讯写成一个独立脚本而是把共性能力抽出来。我一般会把平台拆成三层。设备接入层负责TCP连接生命周期包括监听、重连、心跳保活、粘包拆包消息路由层负责把SECS消息分发到对应的处理函数按Stream/Function定位业务业务处理层负责GEM状态模型、事件上报、数据采集、告警翻译。三层必须解耦否则设备型号一多每加一种设备就要改一遍接入逻辑。设备接入层要做的一件事是“把每个设备的连接抽象成一个会话对象”。会话对象里保存设备ID、Socket、帧缓冲、最近活跃时间。对上层来说它不关心设备是通过Active模式连进来还是通过Passive模式连出去只提供一个send(device_id, msg)接口。消息路由层拿到消息后先拆出Stream/Function再交给注册好的处理器。比如S6F11是事件上报S5F4是报警清除S2F41是主机命令各路由到独立模块。分层之后新设备的接入工作就收窄成“配置设备参数 编写业务映射”通讯层不用再动。3.2 用Python实现一个最小HSMS客户端先看最常见的场景主机主动连设备发S1F13收S1F14。这里以一个HSMS Active模式的客户端为例。import socket import struct def recv_exactly(sock, n: int) - bytes: buf b while len(buf) n: chunk sock.recv(n - len(buf)) if not chunk: raise ConnectionError(connection closed) buf chunk return buf def send_hsms(sock, device_id: int, stream: int, function: int, system_bytes: int, body: bytes, wbit: bool True) - None: msg_id (device_id 0x7FFF) | (0x8000 if wbit else 0) header struct.pack(HBBBB4s, msg_id, stream, function, 0, 0, system_bytes.to_bytes(4, big)) payload header body sock.sendall(struct.pack(I, len(payload)) payload) def recv_hsms(sock): length struct.unpack(I, recv_exactly(sock, 4))[0] data recv_exactly(sock, length) header data[:10] msg_id, stream, func, ptype, stype, sysbytes struct.unpack( HBBBB4s, header) return { device_id: msg_id 0x7FFF, wbit: bool(msg_id 0x8000), stream: stream, function: func, ptype: ptype, stype: stype, system_bytes: int.from_bytes(sysbytes, big), body: data[10:], } sock socket.create_connection((192.168.1.20, 5000), timeout5) send_hsms(sock, device_id1, stream1, function13, system_bytes0x1001, bodyb\x01\x00) resp recv_hsms(sock) print(freceive S{resp[stream]}F{resp[function]}, sys0x{resp[system_bytes]:04x}) sock.close()这段代码里真正要记的是两个函数recv_exactly和recv_hsms。recv_exactly解决TCP粘包问题确保长度不够就继续收。recv_hsms先读4字节长度再按长度读完整消息。注意Socket的recv返回长度不一定是你要的长度直接recv(1024)然后解析极大概率会在生产环境翻车。send_hsms里我特意把W-bit置1因为S1F13需要设备回S1F14如果你发的是Notify消息比如S6F11给主机上报事件W-bit要置0否则设备会一直等不存在的回复。3.3 配置参数设备ID、IP端口、连接模式、超时重连设备接入时下面这些参数必须在配置中心里预先定好不能散落在代码常量里。参数典型值说明Device ID1-32767每个设备在产线中的唯一ID消息头低15位Host IP / Port192.168.0.10:5000Passive模式下主机监听地址Connect ModeActive / Passive由谁发起TCP连接主机通常被动等待设备Connect Timeout5-30sTCP建连超时超过即重连Linktest Interval90-120sHSMS心跳保活和探测半开连接Message Retry Count3-5无响应消息重发次数连接模式是最容易搞错的参数。很多后道设备的默认行为是“开机主动连主机”主机侧要开一个TCP Server等设备连但有些老设备需要主机主动去连也就是Active模式。如果平台配置和设备实际行为不匹配现象就是设备一直显示“等待连接”平台侧却报连接被拒绝。我一般会在配置界面把两种模式都做成开关并埋一个连通性日志方便现场对调。重连逻辑要加退避不能每1秒重连一次否则设备侧会被连接风暴打崩。常见做法是5秒、10秒、30秒逐级退避连续失败3次后置为离线。4. GEM行为建模状态机与事件报文是平台的灵魂4.1 GEM的五大要素状态模型、事件、变量、采集、报警GEM不是一种报文格式而是一套设备行为规范。它规定了设备必须具备的状态模型、事件报告机制、数据变量、配方管理和报警上报能力。平台侧建模时至少要把五类对象建出来状态模型、事件、数据变量、设备常量、报警。状态模型描述设备当前处于什么状态比如IDLE、PROCESSING、DOWN事件描述状态变化或异常发生比如“开始加工”“加工结束”“发生报警”数据变量描述要采集的值比如腔体温度、真空度报警则是可上报给上位系统的设备异常。很多团队只做消息收发不做GEM状态建模结果就是S6F11事件能收到但不知道设备当前到底处于什么状态业务侧每次都要反查设备。正确做法是平台内部维护一张设备实时状态表消息到达时先更新状态机再触发业务事件。这一步做扎实之后MES的Display、Alarm、Trace功能才能落地否则平台只是一个“报文转发器”价值大打折扣。4.2 设备状态机建模与状态变更上报下面用一个简化的状态机演示设备有IDLE、PROCESSING、DOWN三个状态状态变更时平台侧触发回调。注意这里的状态机建模是业务侧的事和SECS报文不矛盾——GEM本身要求设备有状态机这个Demo表示平台侧如何同步设备状态。class EquipmentState: def __init__(self, device_id, on_event): self.device_id device_id self.state IDLE self.on_event on_event def start_process(self): old self.state self.state PROCESSING self.on_event(self.device_id, EVT_PROCESS_START, {old: old, new: self.state}) def process_end(self): old self.state self.state IDLE self.on_event(self.device_id, EVT_PROCESS_END, {old: old, new: self.state}) def alarm(self, code): old self.state self.state DOWN self.on_event(self.device_id, EVT_ALARM, {old: old, new: self.state, code: code})回调on_event里做的事就是组装S6F11消息并发送。S6F11的正文包含事件ID和数据变量列表大致按下面的方式组织先建一个List填入事件ID再填入一组变量。设备端如果启用了事件报告每次状态迁移就会触发S6F11平台侧拿到后先更新自己的状态表再决定是否要通知MES或产生告警。这里要注意事件上报通常不是简单发一条消息而是要走“事件优先级判定”和“重复事件过滤”避免设备频繁上报把消息总线打爆。4.3 自定义SECS消息映射把S6F11事件报文翻译成业务告警收到S6F11不等于告警可用还要把消息里的Event ID翻译成业务侧能读懂的事件。每个设备厂商的事件ID定义都不一样比如设备A的1001可能是“腔体温度过高”设备B的1001可能是“门锁异常”。平台里必须有一张映射表。EVENT_MAP { EQ_A: {1001: temperature_high, 1002: door_open}, EQ_B: {1001: door_open, 1002: vacuum_loss}, } def handle_s6f11(msg): # body是SECS-II列表这里简化解析第一个int是EventID event_id parse_secs2_list(msg[body])[0] device msg[device_id] biz_event EVENT_MAP.get(device, {}).get(event_id, unknown) if biz_event ! unknown: notify_upstream(device, biz_event) else: log_warning(funmapped event {event_id} from device {device})这里有一个容易被忽略的点S6F11里的Event ID可能不是整数而是ASCII字符串也可能带二级子参数所以解析函数不能写死。我习惯先抓一台真实设备的事件报文存成二进制样例再做解析器回归测试避免厂商升级固件后事件结构变了导致平台静默丢事件。映射表之外还要保留原始消息体方便事后回溯。告警翻译完之后建议在事件里带一台设备的当前状态快照这样MES侧不需要再发消息反查设备状态。5. 通讯平台落地避坑指南从握手失败到消息乱序5.1 现象连接建立后立刻被设备断开现象TCP握手成功但几秒后设备主动断开日志只有连接被重置。原因HSMS层没有完成Select会话流程设备不接受数据消息。很多设备要求建立TCP后先做Select然后才能发SECS消息。解决在连接建立后先发送Select.requestSType1收到Select.response后再进入数据阶段。这条坑在模拟器上可能不出现但真实设备上非常普遍因为模拟器往往放宽了对HSMS会话状态的要求。5.2 现象S1F13发送后收不到S1F14现象消息发出去了设备没有回复重发几次后连接被断开。原因W-bit没置1设备认为这是不需要回复的Notify或者System Bytes被错误复用设备无法关联应答。解决确认S1F13的W-bit为1每次请求用独立的System Bytes不要一直用同一个值。如果设备仍然不回抓包看设备是否发了RejectReject里带的原因码能定位是头部问题还是正文问题。真实项目里我见过一个设备端固件Bug如果连续收到相同System Bytes的S1F13它会直接丢弃这种只能靠随机化System Bytes来规避。5.3 现象S6F11多条事件消息乱序业务侧告警错乱现象设备连续上报“开始加工”“结束加工”“开始加工”平台收到的顺序和实际发生顺序不一致告警和状态表错位。原因平台在多个worker线程里并发处理同一设备的消息S6F11的解析和状态更新互相竞争。解决同一设备的消息处理按设备做队列串行化状态更新和事件上报放同一个线程消息解析可以并行但到了状态机这层必须串行。串行化之后就算报文顺序和设备实际发生顺序有偏差至少平台侧不会因为竞态条件产生错乱。5.4 现象副机模式Host切换导致设备连接风暴现象主备切换后设备同时连接新主机和旧主机两边都在收消息甚至互相顶掉连接。原因设备配置里保留了多个Host地址主机切换时没有先发送Separate消息关闭旧会话。解决切换前由旧主机发SeparateSType7通知设备断开平台启动时清理半开连接设备侧只配置一个当前生效Host。这条在高可用产线尤其重要否则主备切换一次设备侧会反复重连甚至把网口流量打满。5.5 现象日志里出现“Invalid Message Length”现象解析帧缓冲时长度字段出现几百MB甚至负数。原因TCP粘包把下一条消息的前4字节和上一条消息的正文拼在一起解析器没有按4字节长度精确取帧。解决使用recv_exactly方式的帧缓冲先收满4字节长度再收对应长度字节对长度字段做上限校验超过比如64MB就判定非法防止异常数据把内存打满。这是所有HSMS平台都该内置的第一道防线千万不要直接调用recv(1024)后就开始解析。6. 进阶模拟器驱动、报文回放与通讯平台压测技巧6.1 搭建一个轻量设备模拟器没有真实设备时我习惯写一个能跑在PC上的设备模拟器。它监听一个端口收到S1F13就回S1F14收到Linktest就回Linktest并且每隔一段时间主动发一条S6F11事件。模拟器不是玩具它能让平台开发在设备到厂前就把通讯和业务逻辑验证完。关键是模拟器也要实现帧缓冲和消息头解析否则验证不出平台的拆包能力。6.2 报文级回放与断点调试现场抓包是避坑的终极大招。把一台真实设备的通讯抓下来保存成十六进制文件本地回放给平台解析。回放时在解析器每个关键分支埋日志能直观看到哪一步解析结果和预期不符。我建议在平台里做一个“诊断模式”逐帧打印收发报文帧长度、消息头字段、正文原始字节。线上出了怪问题先开诊断模式抓现场再回放基本都能定位到是设备端行为异常还是平台解析异常。6.3 压测与异常注入平台上线前压测关注三个指标最大连接数、单连接吞吐、异常恢复能力。用模拟器开几十个连接同时发消息观察平台CPU和内存再让其中几个连接随机断开看平台能否在重连后恢复消息收发。还有一个容易忽略的点Linktest心跳要压因为空闲连接长时间不发消息会被防火墙或设备踢掉心跳间隔不合理会导致频繁断连。压测时把心跳间隔调到30秒、60秒、90秒各跑一遍找到当前网络环境下最稳的值。我之前在一条线体上吃过心跳间隔设太长的亏设备隔一小时掉一次后来用压测把间隔从300秒调到90秒才稳定。平台搭起来不难难的是把“会话管理、消息解析、状态同步、异常恢复”这四件事做扎实。希望这篇笔记里的代码和踩坑经验能帮你少走几段弯路也祝你的通讯平台在产线上一跑就是几年不出乱子。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑