SECS-II消息解析实战:从E5-1104标准到S1F13字节拆解
简介SEMI E5-1104 是半导体设备通信标准 SECS-II 的原版高清英文文档面向从事半导体设备与主机通信开发、集成及维护的工程师和技术人员。该标准由全球信息与控制委员会批准详细定义了消息结构、流与函数、事务与对话协议、数据结构并覆盖设备状态、工艺管理、材料控制、异常处理、数据收集、配方管理等18个流的具体消息用途也包含 T1-T4 事务超时机制和错误恢复逻辑。文档特别针对晶圆映射、工艺步骤协调等复杂场景给出了实现指引是理解 SECS/GEM 通信协议、实现设备与主机高效可靠交互的核心参考资料适用于设备集成、协议调试和标准符合性验证等实际工作。压缩包内为1个pdf文件大小约2.66MB高清原版便于阅读和长期保存。目前已有341人学习下载适合需要深入掌握 SECS-II 消息定义与集成实施的开发者。1. SECS-II 消息内容定义为什么 E5-1104 值得逐页读完第一次接手半导体设备通讯程序时设备文档包里通常只有几个示例报文和一个模拟器真正能把线上抓到的十六进制帧逐字节解释清楚的还是那份 SEMI E5 标准。E5-1104 是 SECS-II 消息内容定义的权威版本它规定消息头、功能号、正文编码规则以及每个数据项的类型和边界。SECS-II 不像 HTTP 有现成的文本框架它是一套二进制、动态长度、递归嵌套的消息规则报文里每一个字节该怎么解释都要回到这份文档查证。这篇笔记写给三类人设备端软件开发者、主机端集成工程师、以及要做 SECS 仿真测试的工程师目标是帮你把文档翻译成能用的解析器和稳定跑通的会话。很多线上问题查到最后都像玄学其实只是某一位和文档定义对不上而已。2. 从 S1F13 出发理解 E5-1104流-功能模型与标准文档的正确打开方式SECS-II 的消息不是靠消息名区分而是靠一个两段式编号SxFy其中 x 是 Stream消息流号y 是 Function功能号。选 S1F13 当切入点是因为它是建立通信请求既有请求又有回复而且消息体里带着两个高频数据项能一口气把消息编号、事务、正文编码全部讲清楚。2.1 先记住 SxFySECS-II 的消息编号体系以 S1F13 W 为例S1 表示这个消息属于 Stream 1即设备状态与通信控制族F13 是这个流里的第 13 个功能建立通信请求末尾的 W 表示发送方要求对方回复。对应的回复消息是 S1F14标准把这一来一回定义为一个事务Transaction。一个事务里主动发起且要求回复的消息叫 Primary被动回复的消息叫 Secondary区分这两者靠的就是 W 位。刚接触 SECS 时最容易忽略的是同一个功能号在不同 Stream 里的含义完全不同。比如 F1 在 Stream 1 里是查询在线状态S1F1换个流就是别的用途。所以解析器做路由时必须以 SxFy 整体作为 key不能只按 Function 分发。下表是现场最常用的一组消息建议直接存在项目笔记里消息方向内容说明S1F1 / S1F2Host → 设备 / 回复在线状态查询与应答握手第一件事S1F13 / S1F14Host → 设备 / 回复建立通信请求携带设备型号与软件版本S2F41 / S2F42Host → 设备 / 回复主机命令下发与执行结果回复S5F1 / S5F2设备 → Host / 回复报警上报与主机确认S6F11 / S6F12设备 → Host / 回复事件报告状态或数据变化时主动上报2.2 E5-1104 在 SECS/GEM 标准族里的位置SECS/GEM 不是一份标准而是一族标准。E5-1104 只负责一件事消息内容定义。传输和业务行为在别的标准里。现场联调时很多人把 E30 的状态机规范和 E5 的消息格式混在一起查越查越乱。标准管什么一句话定位E4SECS-I 点对点串行传输老产线 RS-232/RS-422 物理层传输E5SECS-II 消息内容定义消息头、正文格式、数据项定义E30GEM 设备模型与行为状态机、事件、变量、报警的行为规范E37HSMS 高速消息服务以太网上封装 SECS-II 报文简单说E37 把消息搬到以太网E5 规定消息长什么样E30 规定设备在什么状态下允许发什么消息。对接设备时先看 E30 判断该发哪个 SxFy再去 E5 查这个 SxFy 的消息体和数据项最后用 E37 的封包方式发出去。这也是为什么原版高清文档有价值E5-1104 的章节号和表格编号是各方沟通的共同语言。出问题时和别人对齐E5 第 6 节消息头表格第 3 行对方立刻知道你在说什么。二手转载稿经常丢表格编号甚至把格式字节的低半字节含义写错这类错误一旦进了代码排查成本极高。2.3 文档章节怎么导航不是从头读是按图索骥E5-1104 不是拿来通读的教科书是拿来查的工具书。我一般按五步走从 E30 或设备厂商手册拿到需要的 SxFy 编号去 E5 的 Stream/Function 定义表查这个编号的消息是否存在、是 Primary 还是 Secondary、是否允许无正文在数据项Data Item章节查每个字段的定义包括类型、长度、是否必选回到消息定义章节看这些数据项如何组成消息正文最后用格式字节编码规则把正文变成字节流。这个顺序能避免最常见的错误——直接拿网上示例报文当标准。示例报文往往来自某台特定设备字段内容被厂商改过只有 E5 定义的结构和类型才是通用的。提示E5 的数据项表是所有 GEM 变量的源头。MDLN、SOFTREV、COMMACK 这些字段的准确拼写、类型、长度限制都要以这份表为准而不是以某台设备的日志为准。3. 用 E5-1104 把一条 S1F13 消息拆成字节消息头、正文与查询路径有了文档导航方法下一步是实操拆帧。SECS-II 的报文分两层传输层负责把消息搬过去消息层负责表达内容。E5 主要管消息层但消息头里有几个字段和传输层强相关拆帧时得一起看。3.1 消息头 10 字节先分清传输层与消息层SECS-II 的消息头固定 10 字节字段按下面这张表排列。具体位宽和保留位在不同传输方式E4 串行 / E37 网络下略有差异但字段顺序是同一套。字节偏移字段说明0Device ID R/W 位高位是设备地址最低位是 W 位1 表示要求回复1Stream消息流号2Function功能号3块控制字段含 P 位表示当前块是否为最后一块4-7System Bytes事务 ID4 字节大端整数8-9Block Number块号消息被分段传输时用来重组这里面最容易被忽视的是 System Bytes。它相当于消息的事务 IDPrimary 发出时带上一个值Secondary 回复时必须原样带回。现场多任务并发时主机靠它把回复匹配到对应的请求上。如果设备主动上报消息比如 S6F11也占用了同一段 System Bytes 区间匹配逻辑没写好就会出现回复张冠李戴。R/W 位在回复消息里固定为 0在要求回复的 Primary 里为 1。调试模拟器时抓包第一眼就看这个位的值能立刻判断方向对不对。3.2 正文编码规则格式字节 长度 数据的递归展开消息体是 SECS-II 最见功底的地方。它的编码规则可以浓缩成一句话每个数据项由格式字节开头格式字节的高 4 位是数据类型低 4 位是后面长度字段占用的字节数。举个例子格式字节 0x41拆开看高半字节是 0x4低半字节是 0x1含义是数据类型为 ASCII后面用 1 个字节表示数据长度。下一个字节如果是 0x07就说明再往后 7 个字节是真正的文本数据。整个 SECS-II 的正文就是这样按类型-长度-数据不断拼接出来的。LIST 类型稍微特殊它的长度字段表示的是所有子项加起来的字节总数而不是子项的个数。解析时先读 LIST 头再根据总长度划出子项区域在区域内继续按同样的规则递归解析。这也是 SECS-II 被称为动态格式协议的原因——没有固定的结构体定义每个字段的边界都在数据流里现确认。3.3 手工拆解一条 S1F13 请求从 SML 到完整字节序列先用 SECS 消息语言SML描述一条常见的 S1F13 请求正文L2 A DEMO-01 A 1.0.0 含义是一个 LIST 包含两个 ASCII 字符串分别是设备型号和软件版本。按 3.2 的规则编码正文字节是01 10 41 07 44 45 4D 4F 2D 30 31 41 05 31 2E 30 2E 30拆开看01LIST 类型长度字段占 1 字节10LIST 内容总长度 16 字节41 07ASCII 类型长度为 744 45 4D 4F 2D 30 31字符 DEMO-0141 05ASCII 类型长度为 531 2E 30 2E 30字符 1.0.0。验证一下两个字符串项分别占 9 字节和 7 字节合计 16 字节正好等于 LIST 头里的 0x10。数据项文本是我构造的占位值实际设备上 MDLN 和 SOFTREV 的内容以设备厂商手册为准但结构就是这一套。3.4 为什么说 SECS-II 是动态格式协议和 XML、JSON 不同SECS-II 没有独立的 schema 文件也没有定长的结构体定义。长度字段本身是变长的解析器必须先读格式字节才能知道长度字段占几个字节读完长度才知道数据区有多大。这种设计在 RS-232 时代是为了省字节放在今天就是解析器的复杂度来源。写解析器时必须把读一个数据项做成递归函数而不是顺序解析。顺序解析遇到嵌套 LIST 立刻崩。另一个容易漏的点是所有长度字段都是大端整数包括消息头里的 System Bytes 和正文里的每个长度值。这一点不记住后面全是天文数字。4. 照着 E5-1104 写 SECS-II 消息解析器数据项映射与递归解码文档读完最终要落到代码。这里给一套最小可用的递归解析器骨架格式代码表需要你打开 E5-1104 核对后填入我不建议凭记忆写死在代码里。4.1 数据项定义怎么落成代码结构E5 数据项表里每个字段都有规范的名字、类型和长度约束。拿到文档后第一步把常用字段整理成一张映射表作为解析器的期望值DATA_ITEMS { MDLN: {name: Model Number, expected_type: ASCII, max_len: 20}, SOFTREV: {name: Software Rev, expected_type: ASCII, max_len: 20}, COMMACK: {name: Communications Ack, expected_type: BIN, max_len: 1}, } # 注意expected_type 和 max_len 务必对照 E5-1104 数据项表核对这段代码的作用不是解析而是校验。解析器解出一个数据项后拿字段名去查这张表类型对不上、长度超限就记一条结构化日志。线上问题排查时这类日志比 raw hex 好定位得多。写映射表时参数名要和文档保持一致。网上很多代码把 SOFTREV 拼成 SOFT_REV协议是不会认的纯属给自己挖坑。4.2 递归解码核心函数一次读一个 Item下面这段是解析器的心脏一次调用只读一个数据项返回解析结果和新位置# 格式代码按 E5-1104 查表确认后填入 TYPE_LIST 0x0 # LIST 类型 TYPE_ASCII 0x4 # ASCII 类型 def decode_item(buf: bytes, pos: int): fmt_byte buf[pos] pos 1 type_code fmt_byte 4 # 高 4 位数据类型 len_bytes fmt_byte 0x0F # 低 4 位长度字段占几字节 length int.from_bytes(buf[pos:pos len_bytes], big) pos len_bytes if type_code TYPE_LIST: items [] end pos length while pos end: item, pos decode_item(buf, pos) items.append(item) return {type: LIST, value: items}, pos if type_code TYPE_ASCII: text buf[pos:pos length].decode(ascii, replace) return {type: ASCII, value: text}, pos length raw buf[pos:pos length] return {type: fT{type_code:02X}, value: raw}, pos length逻辑说明fmt_byte 4取出高半字节作为类型fmt_byte 0x0F取出低半字节作为长度字段的字节数然后用大端方式读出真正的长度。LIST 分支不直接返回原始字节而是进入一个子循环把end之前的所有数据当成子项递归解析这就是嵌套 LIST 的展开方式。参数说明buf是完整的消息正文pos是当前读取位置。函数返回的pos是下一个数据项的起点这样外层循环才能连续推进。如果长度字段len_bytes为 0int.from_bytes会拿到空切片实际项目里要加防御性判断这里先不展开。4.3 从解出树到听懂消息流-功能分发解析器把正文解成树结构后只是完成了信息的还原。要让消息产生业务行为还需要一张路由表把 SxFy 映射到对应的处理函数HANDLERS { (1, 1): handle_are_you_online, (1, 13): handle_establish_comm, (2, 41): handle_host_command, (6, 11): handle_event_report, } def dispatch(stream, function, tree): handler HANDLERS.get((stream, function)) if handler: handler(tree) else: log.warning(unhandled message: S%dF%d raw%s, stream, function, tree)这一步是把 E5 的消息定义变成工程逻辑的分界线。E5 只告诉你 S6F11 长什么样不告诉你收到后该干嘛业务处理是主机和设备各自的事。实际项目里处理函数内部还会再查一次 4.1 的 DATA_ITEMS 表做类型校验双保险。4.4 给代码装三张保险长度上限、类型校验、错误恢复第一张保险是长度上限。SECS-II 长度字段是动态的理论上能表达很大的数字但真实设备的数据项长度都在数据项表里有上限。解析器必须设一个硬上限超过直接丢弃并告警否则一个异常长度字段就能把内存打满。第二张保险是类型校验。比如 S1F13 的 MDLN 按文档期望是 ASCII结果某台设备用 BIN 发了二进制字节硬解成 UTF-8 会出现一堆乱码。校验规则写在 4.1 的映射表里类型不匹配就按文档和实际差异记录而不是强行解码。第三张保险是错误恢复。推荐在最外层包一层 try出错时记录当前pos和最近一个 LIST 的起始位置并输出整帧 hex。这样拿到线上异常帧能直接对照 E5 文档手工拆到出错位置比猜快得多。5. SECS-II 落地避坑清单事务 ID、大小端、私有功能码与 5 个典型翻车现场下面五条是从设备对接和仿真环境里反复踩出来的按现象 → 原因 → 解决整理。每一条都能对应到 E5 文档里的某个具体定义。5.1 大小端错位U2/U4 全部变成天文数字现象设备上报的 U2 数据0x00 0x28 本应是 40解析出来却是 10240所有多字节整数都变了。原因SECS-II 的消息头、长度字段、整型数据项全部使用大端字节序代码里用了小端去读。解决统一使用int.from_bytes(data, big)。特别注意长度字段也是大端不要只改数据区不改长度区。写完解析器后用 3.3 里那条01 10 41 07 ...的报文做单测长度对不上立刻就能暴露。5.2 事务 ID 复用导致回复张冠李戴现象主机发出 S1F13 等待回复设备同时主动上报一条 S6F11。主机拿 S6F11 的 System Bytes 去匹配 S1F13 的待回复表匹配失败日志刷出一片 TIMEOUT。原因System Bytes 只是事务 ID设备主动消息和主机请求的回复可能落在同一个数值区间仅靠一个 ID 无法区分这是谁的回复。解决只维护本端发出且等待回复的 System Bytes 集合收到消息先查集合命中的才是回复未命中的直接按主动消息分发pending {} # system_bytes - 请求消息 tid 0 def send_primary(stream, function, body): global tid while tid in pending: tid (tid 1) 0xFFFFFFFF # 回绕时跳过未回复的 ID pending[tid] (stream, function, body) send(stream, function, w1, system_bytestid, bodybody) def on_secondary(sysbytes, stream, function, body): req pending.pop(sysbytes, None) if req is None: handle_event(stream, function, body) # 主动消息 else: handle_reply(req, body) # 本端请求的回复这段代码的关键在while tid in pending事务 ID 回绕后可能撞上还没收到回复的旧请求必须跳过不能直接覆盖。5.3 功能号 128 以上不一定是错误帧现象新设备接入后日志里出现 S6F129被当成未知消息丢弃对应的事件数据全丢了。原因SECS-II 把 Function 0-127 定义为标准功能号128-255 留给厂商私有扩展。设备厂商自己定义的消息E5 文档里当然查不到。解决对未知 SxFy 不要直接丢先透传并存完整 raw hex再找设备厂商要补充协议文档。很多场景里这类扩展消息恰恰是设备的核心数据通道。5.4 长消息分段后直接进正文解析器现象大体积的 S6F11 事件报告解析时LIST 长度总是超界解析器报错。原因消息正文超过传输层限制时会被切成多个块Block发送正文解析器拿到的是其中一块而不是完整消息。块的分界信息在消息头的块控制字段和 Block Number 里。解决在进入正文解析前加一层重组逻辑以 System Bytes 为 key 收集分块Block Number 连续收到标记最后一块的块后再把完整缓冲区交给decode_item。重组缓冲区要设超时常见做法是 5 秒收不完整就丢弃并告警防止内存堆积。5.5 时间数据项和字符集别看它是 ASCII 就放心解码现象设备上报的历史时间在报表里排序错乱日文注释的字段解析出来全是问号。原因SECS 标准年代早时间数据项是按文档规定拼装的定长数字字符串不是通用 ISO 格式字符串拆分方式错了整个时间就错了。字符集上E5 还保留着 JIS-8 等多字节字符集的支持直接拿 UTF-8 硬解必乱。解决字符串解码统一指定编码不要依赖系统默认字符集时间字段严格按数据项表里规定的长度和分隔符去拆不存在的字段位不要臆测。6. 让文档变成测试向量回环验证与协议演进后的底线解析器写完验证比实现更重要。我的做法是把手头的标准报文固化成测试向量每个版本跑一遍回环测试def roundtrip(sml_bytes): tree, pos decode_item(sml_bytes, 0) assert pos len(sml_bytes) # 全部消费完没有残留 encoded encode_tree(tree) # 反向编码实现略 assert encoded sml_bytes # 编码前后字节完全一致回环测试的价值在于它同时验证了解码器和编码器任何一端的字节序、长度字段处理错误都会导致断言失败。把 3.3 里的 S1F13 正文、以及 S1F1/S6F11 的常见报文都放进测试用例后续改代码时跑一遍能挡住大部分回归。另一条验证路径是接通用 SECS 模拟器做完整握手。模拟器发起 S1F13你的解析器回 S1F14观察 W 位、System Bytes、正文结构是否对得上。模拟器互通只是第一步真设备才是终检——设备厂商的私有行为和字段填充习惯模拟器模拟不出来。这里说一段自己的教训。有一次模拟器全部跑通换成真设备就卡在 S1F13 建立通信。查了一天发现是设备把 SOFTREV 字段发成了空字符串而我的解析器把它当必填字段做了硬校验直接拒绝。E5 文档定义的是类型和上限字段到底填不填、填什么是设备厂商的实现细节。从那以后我养成了两个习惯一是手头标准 PDF 永远是唯一事实来源网上代码只当线索二是每接入一台新设备先抓包对照文档做一份差异记录再动代码。SECS-II 这套协议并不复杂复杂的是每个厂商都在标准上加了各自的习惯而把这些习惯和标准区分开的能力就是靠逐字段对照文档练出来的。希望帮到你。本文还有配套的精品资源点击获取