简介这份资源围绕半导体制造领域通用的SECS/GEM通信协议展开面向半导体设备开发、MES系统集成及自动化测试方向的工程师与学习者帮助理解设备与主机之间的通信机制与行为模型。内容涵盖SECS-I、SECS-II的物理层、链路层与应用层协议以及GEM定义的设备状态模型、数据报告模型和操作命令可用于搭建通信Demo、验证消息交互流程或作为二次开发参考。压缩包共72个文件约296KB以cs源码、dll动态库、json配置、pdb调试符号、exe可执行程序及csproj工程文件为主另含sln解决方案与少量cache、bin等编译产物整体结构紧凑便于直接运行与调试。目前已有604人学习下载。通过该示例工程读者可快速掌握HSMS通信连接、消息收发与设备模型实现思路为半导体设备自动化与MES对接提供可复用的代码基础。1. 半导体设备为什么需要一套“通用语言”SECS/GEM 到底在解决什么问题一条 300mm 晶圆产线上光刻、刻蚀、薄膜、量测加起来可能有上百台设备它们来自不同厂商、不同年代、跑着不同的控制软件。如果每接一台新设备工厂的 MES 都要为它单独写一套驱动那这条线基本没法维护。SECS/GEM 就是在这个背景下被推到台前的它把“设备怎么跟主机对话”这件事标准化让一台设备只要声明自己支持 SECS/GEM主机就能用同一套逻辑去采集状态、下发配方、控制远程命令。很多人第一次接触 SECS/GEM会以为它只是一个通信协议实际上它是一整套语义约定。SECS 管的是“消息怎么在链路上跑”GEM 管的是“这些消息代表什么业务含义、设备该暴露哪些状态和事件”。搞懂这套机制你才能理解为什么半导体工厂的设备接入周期能从几个月压到几天也才能看懂那些看似冗长的状态机到底在防什么。2. SECS 消息层为什么一条报文要拆成 Stream、Function 和 Item 三层2.1 从一条 S1F1 说起链路建立时到底发生了什么设备上电、TCP 连接建立之后主机通常会先发一条 S1F1意思是“你在吗”。设备回 S1F2表示“我在而且我准备好了”。这个交互看起来简单但它背后是 SECS 消息模型的最小闭环Stream 1 负责通信状态Function 1 是“Are You There”Function 2 是“On Line Data”。为什么不用一个简单的 ping因为 SECS 要的不只是“通不通”还要确认对方是否处于可通信状态、是否已经完成初始化。S1F2 的回复里可以带设备型号、软件版本等数据主机拿到之后就能决定后续用哪些功能。常见的实现思路是设备端维护一个通信状态机只有进入“Communications Established”之后才允许处理 S1F1 以外的消息。这个状态机不是可选项它是 GEM 规范里明确要求的。从源码角度看一个典型的 SECS 消息编码器会把 Stream 和 Function 打包成一个字节高 7 位是 Stream低 8 位是 Function再加上一个 W-bit 表示是否需要回复。接收端解析时先读这个头再根据 Stream/Function 去查对应的处理函数。这种设计的好处是消息类型可以扩展到 127 个 Stream、255 个 Function足够覆盖半导体设备的所有业务场景。2.2 Item 的嵌套结构为什么参数不能平铺SECS 消息的载荷不是简单的键值对而是一棵 Item 树。每个 Item 有一个格式码比如 A 表示 ASCIIU4 表示 4 字节无符号整数L 表示 ListList 可以嵌套 List形成任意深度的结构。比如一条 S2F33 定义报告时载荷里会有多个 List每个 List 里又有报告 ID 和变量 ID 列表。为什么这么设计因为半导体设备的业务数据天然是层次化的。一个配方可能包含多个步骤每个步骤有多个参数参数又有自己的单位和上下限。如果平铺成键值对键名会变得极其冗长而且无法表达“这个参数属于哪个步骤”这种归属关系。Item 树用嵌套 List 直接映射了这种层次解析时递归下降即可。常见的实现思路是编码时先写 Item 头格式码 长度字节数再写实际数据List 的长度字段表示子 Item 的数量而不是字节数。解码时先读格式码如果是 List 就递归解析子 Item直到所有子 Item 解析完。这里有个容易翻车的地方长度字节数本身也是变长的1 到 3 个字节都有可能取决于数据长度。很多自研解析器在这里踩坑把长度字段固定成 2 字节结果遇到长消息就解析错位。# 简化的 SECS Item 解码逻辑展示长度字段的变长处理 def decode_item(data, offset): format_byte data[offset] offset 1 format_code format_byte 2 # 高 6 位是格式码 num_length_bytes format_byte 0x03 # 低 2 位表示长度字段占几个字节 if num_length_bytes 0: length 0 else: length int.from_bytes(data[offset:offset num_length_bytes], big) offset num_length_bytes if format_code 0x00: # List items [] end offset length while offset end: item, offset decode_item(data, offset) items.append(item) return {type: list, items: items}, offset else: value data[offset:offset length] offset length return {type: format_code, value: value}, offset这段代码里num_length_bytes来自格式字节的低 2 位取值 0 到 3。如果是 0表示长度为 0后面没有长度字段。这个设计让短消息可以省掉长度字段长消息又能支持到 3 字节长度最大 16MB。参数说明format_code决定后续数据的解释方式length在 List 里表示子 Item 数量在非 List 里表示字节数。理解这一点才能看懂为什么有些 SECS 日志里 List 的长度是 3但后面跟了 3 个完全不同的 Item。2.3 事务与超时T3、T5、T6 这些定时器在防什么SECS 通信里有一组定时器名字很朴素T3 是等待回复的超时T5 是连接确认的超时T6 是发送超时。它们的存在是因为半导体设备不能无限等待。如果主机发了 S2F41 让设备执行远程命令设备因为内部故障没回复主机不能一直挂着否则整条线的调度都会卡住。T3 的典型值是 45 秒但不同设备可以协商。设备在 S1F2 里可以带一个“MDLN”和“SOFTREV”主机根据这些信息决定用哪套超时参数。常见的实现思路是每个事务在发送时启动一个 T3 定时器收到回复就取消超时后触发重试或报错。重试次数一般不超过 3 次因为半导体设备对重复命令很敏感重试可能导致重复执行。这里有个血泪经验T3 超时后不要立刻重发同一条消息尤其是 S2F41 这种带副作用的命令。正确的做法是先发 S1F1 确认链路还活着再决定是否重试。很多自研主机在这里翻车设备其实已经执行了命令只是回复丢了重发导致配方被下两次。3. GEM 状态机设备为什么要维护一套“对外可见的状态”3.1 通信状态与控制状态为什么设备要区分“在线”和“离线”GEM 定义了两个正交的状态机通信状态和控制状态。通信状态管的是“链路通不通”控制状态管的是“设备听不听主机的”。一台设备可以通信在线但控制离线意思是“我能跟你说话但我不接受你的远程命令”。这种区分在产线上很实用设备工程师在本地调试时可以把控制状态切到离线避免主机误发命令。控制状态又分本地和远程。本地模式下操作员在设备面板上操作远程模式下主机可以下发配方、启动流程。状态切换通常通过 S1F17 和 S1F15 这类消息完成。设备端收到请求后要检查当前是否允许切换比如正在跑片时不能切到远程。这个检查逻辑是 GEM 规范里明确要求的不是可选的。从实现角度看设备端一般用一个状态机对象来维护这两个状态每个状态迁移都有对应的消息处理函数。常见的做法是状态迁移前先发一个事件通知主机迁移后再发一个确认。这样主机就能知道设备当前处于什么状态而不是靠猜。3.2 事件与报告设备怎么主动告诉主机“我出事了”GEM 最核心的机制之一是事件报告。设备可以定义一组事件比如“配方开始”“配方结束”“报警发生”每个事件关联一组报告每个报告包含一组变量比如温度、压力、批次号。当事件发生时设备主动发 S6F11 给主机载荷里带报告 ID 和变量值。为什么不用主机轮询因为半导体设备的状态变化很快轮询要么太慢漏掉事件要么太快浪费带宽。事件报告让设备在关键时刻主动推送主机只需要在收到后做业务处理。常见的实现思路是设备端维护一个事件表每个事件有启用/禁用标志主机可以通过 S2F37 来启用或禁用事件。禁用后设备不再发送该事件但事件本身仍然会发生只是不通知主机。这里有个容易忽略的细节S6F11 里的报告 ID 是设备定义的主机需要先通过 S2F33 和 S2F35 来定义报告和链接事件。如果主机没定义就直接启用事件设备可能发不出报告或者发出来的报告主机不认识。很多接入失败都是因为这一步没做对。3.3 变量与数据采集为什么 S1F3 和 S6F11 要并存主机采集设备数据有两种方式主动请求和被动接收。S1F3 是主动请求主机指定一组变量 ID设备返回当前值。S6F11 是被动接收设备在事件发生时推送变量值。两者并存是因为场景不同对于实时性要求高的数据比如温度曲线用 S6F11对于按需查询的数据比如设备配置用 S1F3。变量 ID 是设备定义的主机需要先通过 S1F11 或 S1F13 获取变量列表。设备返回的变量列表里包含变量 ID、名称、单位、最小值、最大值。主机拿到之后才能决定采集哪些变量、怎么解析。常见的实现思路是设备端把变量映射到内部数据源S1F3 请求到来时从数据源读当前值S6F11 触发时从数据源读快照值。数据源可以是 PLC 寄存器、数据库、内存变量取决于设备类型。# 设备端处理 S1F3 的简化逻辑 def handle_s1f3(variable_ids): # variable_ids 是主机请求的变量 ID 列表 result [] for vid in variable_ids: if vid not in variable_map: # 未知变量返回空值而不是报错 result.append({id: vid, value: None}) continue source variable_map[vid] value read_from_source(source) # 从 PLC/数据库/内存读取 result.append({id: vid, value: value}) return build_s1f4(result) # 打包成 S1F4 回复这段代码展示了 S1F3 处理的核心逻辑遍历请求的变量 ID从映射表里找到数据源读取当前值打包回复。参数说明variable_map是设备启动时加载的变量定义表read_from_source根据数据源类型做不同处理。注意未知变量返回空值而不是报错因为主机可能请求了设备不支持的变量报错会导致整个请求失败返回空值让主机自己判断。4. 避坑与排查SECS/GEM 接入时最常见的 5 个翻车现场4.1 现象S1F1 能通但 S1F13 一直超时原因S1F13 是“建立通信”请求设备端需要返回 S1F14里面带设备型号和软件版本。如果设备端的状态机还停留在“Communications Established”之前S1F13 会被丢弃。很多设备实现里S1F1 和 S1F13 的处理函数是分开的S1F1 只检查链路S1F13 才检查业务状态。解决先确认设备端是否已经进入“Online”状态。可以发 S1F17 尝试切换或者检查设备日志里有没有“Control State Change”记录。如果设备端有本地/远程开关确认它处于远程模式。4.2 现象S6F11 报告收不到但事件确实发生了原因主机没有通过 S2F33 定义报告或者没有通过 S2F35 链接事件。设备端在事件发生时会检查事件是否启用、报告是否定义如果没定义就静默丢弃。很多设备实现里这个检查是静默的不会报错。解决在启用事件之前先发 S2F33 定义报告再发 S2F35 链接事件最后发 S2F37 启用事件。顺序不能乱。可以用 S2F34 和 S2F36 确认定义是否成功。4.3 现象S2F41 远程命令执行了两次原因T3 超时后主机重发了 S2F41但设备其实已经执行了第一次只是回复丢了。设备端没有做幂等处理第二次收到又执行了一遍。解决设备端对 S2F41 做幂等可以用命令 ID 时间戳做去重。主机端在 T3 超时后先发 S1F1 确认链路再决定是否重试。如果链路正常但没收到回复发 S1F3 查一下设备状态确认命令是否已经生效。4.4 现象Item 解析错位长消息解析失败原因长度字段的字节数没按格式码的低 2 位处理固定成了 2 字节。短消息碰巧能解析长消息就错位。解决严格按照格式码的低 2 位决定长度字段的字节数。0 表示无长度字段1 到 3 表示长度字段占 1 到 3 字节。写单元测试时构造长度超过 255 和超过 65535 的消息验证解析器是否正确。4.5 现象设备端 CPU 占用高SECS 通信变慢原因设备端在每次 S1F3 请求时都去 PLC 读所有变量而不是只读请求的变量。或者 S6F11 事件触发太频繁没有做合并。解决S1F3 只读请求的变量用缓存减少 PLC 访问。S6F11 事件做合并比如温度变化超过阈值才发而不是每次采样都发。常见的实现思路是设备端维护一个事件队列事件发生时先入队后台线程按固定间隔批量发送。5. 从 SECS/GEM 到 EDA 接口这套机制的设计取舍与延伸思考SECS/GEM 诞生于 1980 年代当时半导体设备还没有现在这么复杂TCP/IP 也还没普及。它的设计取舍很明显用二进制编码换带宽效率用状态机换确定性用事件报告换实时性。这些取舍在今天看来有些过时比如二进制编码不如 JSON 易读状态机不如 REST 灵活但它们在半导体工厂的严苛环境下依然有效。一个值得思考的点是为什么 SECS/GEM 没有演进成更现代的协议因为半导体工厂的设备生命周期很长一台设备可能用 20 年协议变更意味着所有设备都要升级。而且 SECS/GEM 的语义已经足够稳定新需求可以通过扩展 Stream/Function 来满足不需要推翻重来。常见的实现思路是在 SECS/GEM 之上加一层 EDA 接口用 HTTP/JSON 做数据采集但底层还是走 SECS/GEM 的消息通道。如果你要自己实现一套 SECS/GEM 通信栈我建议先做三件事第一把 Item 编解码写对这是所有功能的基础第二把状态机画出来明确每个状态的进入和退出条件第三把事件报告的定义和链接流程跑通这是最容易翻车的地方。验证方法很简单用一台支持 SECS/GEM 的模拟器跑一遍 S1F1、S1F13、S2F33、S2F35、S2F37、S6F11 的完整流程看每一步的回复是否符合预期。从那以后我每次接入新设备都强制走一遍“S1F1 确认链路 → S1F13 建立通信 → S2F33 定义报告 → S2F35 链接事件 → S2F37 启用事件 → 触发事件验证 S6F11”的流程少一步都不行。希望帮到你。本文还有配套的精品资源点击获取