十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

高速SECS/GEM源码解析:半导体设备接入与HSMS传输实战

高速SECS/GEM源码解析:半导体设备接入与HSMS传输实战 简介SECS半导体设备通信标准与GEM通用设备模型是半导体制造设备与fab信息系统之间的核心通信协议广泛应用于设备端EAP对接与自动化集成。这套名为JngHightSpeedSecs的源码工程面向设备自动化开发人员提供了可编译运行的MFC示例工程SecsExampleJng便于从消息收发、HSMS高速通信到GEM交互流程逐层理解协议落地方式压缩包共31个文件、约722KB包含h/cpp源码、sln/vcxproj工程配置以及SuperHighSpeedHSMS.dll、Communication.dll等已编译库和可执行示例程序另附ReadMe.txt帮助上手可直接对照学习或用Visual Studio打开工程二次开发。已有1102人学习浏览。代码在SECS消息处理模块划分、事件回调、错误处理与日志记录等方面提供了具体落地参考可帮助工程师掌握SEMI通信细节开发出能在7×24小时生产环境中稳定运行的设备接口减少通信中断对产线的影响示例程序也能快速验证通信效果提升开发效率。1. 高速 SECS/GEM 源码包解决的是产线设备接入的第一公里半导体封测设备的自动化接口绕不开 SECS/GEM而“JngHightSpeedSecs_SECSGEM_SECS_SECS源代码”这类标题指向的就是一套能把设备端和主机端跑起来的 SECS/GEM 源代码实现。接调机测机时最怕的不是消息不会发而是协议栈黑匣子化连接时好时坏、事件上报丢帧、心跳一抖整条线断。这套源码要解决的是“连得上、扛得住、查得清”三件事适用对象是设备厂商的嵌入式工程师、EAP 系统的现场实施和运维人员以及做 MES 集成的开发。先把协议分层和边界想明白再动手写代码现场才不至于反复翻车。2. 从传输层到应用层SECS/GEM 协议的职责边界与选型理由2.1 HSMS 传输层是性能起点端口 5000、会话 ID 与三种连接状态SECS/GEM 能跑高速前提是传输层选对。老设备走 SECS-I即 RS-232C 半双工一帧数据最多 244 字节S2F42 这类带配方或 Trace 数据的大消息经常发到超时。新设备基本都走 HSMSSEMI E37也就是 TCP/IP 之上的 SECS 消息服务。HSMS 没有串口那堆流控和奇偶校验双方建立 TCP 连接后用“4 字节长度 10 字节消息头”做分帧速率跟网卡走这才是“高速”二字的底气。设备端和主机端的连接建立有两种角色。常见做法是设备做 HSMS-Server在 5000 端口监听主机作为 HSMS-Client 主动连过来也有设备做 Client 反向拨号到主机的场景多见于设备在防火墙后面、只能出站的工厂。两种角色在源码实现上差别很小只是把 accept 换成 connect但现场网络配置差别很大实施前要先确认设备支持哪种。HSMS 的会话状态分为未选择、选择和通信中。TCP 连接建立后双方先交换 Select.req/Select.rsp成功后进入已选择状态之后才能收发 SECS-II 的数据消息。设备 ID 放在消息头前两个字节的低 14 位主机靠它区分同一端口后面的多台设备。一台设备可以同时与多个 Host 建多条 TCP 连接也可以只连一个主机这是高速场景里做并发设计的基础后面第 4 章会讲到它怎么变成坑。2.2 SECS-II 报文结构消息头十个字节和 SxFy 消息语义SECS-IISEMI E5定义的是“消息内容”的统一格式。一条消息由 10 字节消息头和后面的数据项组成数据项按格式码嵌套成树形结构类似 TLV 但更灵活。消息头的字段分布如下偏移长度字段说明02会话 IDbit15 为 W 位低 14 位是设备 ID21流号Stream1~12731功能号Function0~25542头号/块号HSMS 下一般填 064系统字节事务 ID用于配对请求和响应SxFy 是 SECS 里的核心寻址方式。S1F1 是“主机问设备是谁”S1F2 是设备应答身份信息S1F13/S1F14 用于建立通信S6F11 是事件上报S2F41/S2F42 是远程命令收发。数据项的类型由格式码标识常见的有 List、Binary、ASCII、Boolean、U1/U2/U4 等可以层层嵌套例如 S6F11 的 Data 字段经常是L B 事件ID L 子项...。很多人容易把 SECS 理解成“主机下发指令给设备”的单向协议实际不是。SECS 是双向的设备可以主动上报事件和告警主机也可以随时请求数据。后面做数据采集时很多业务逻辑本质是两边约定“谁来发哪个 SxFy、回哪个 SxFy”再配合数据项格式做解析理解了双向性源码才不会写偏。2.3 GEM 补上的标准行为状态机、事件收集和远程命令只有 SECS-II 还不够接产线因为语法之外还有“行为”需要统一。SEMI E30/GEM 把这套行为模板化包括设备状态机、事件收集、告警管理、远程命令、配方管理和数据采集。标题里的“GEM 源代码”指的就是实现这一整套状态转换和回调逻辑的代码而不是单纯收发消息。GEM 状态机是重点。设备有一个“控制状态”Control StateOFFLINE、ONLINE、PAUSED还有一个“设备状态”Device StateDISABLE、ENABLE。设备在 ENABLE 下才能正常处理主机命令离线状态收到上线请求要能自动转入 Online。事件收集则要求设备支持主机动态配置主机用 S2F37 启停事件设备按 S6F11 主动上报。远程命令走 S2F41 收参数、S2F42 返回执行结果。选型时注意 SECS/GEM 的实现深度差异。有些设备只做了 SECS-II 消息收发没做 GEM 状态机这种设备接 MES 时行为逻辑全写在 Host 端项目后期痛不欲生。我更倾向在源码层面把 GEM 状态做成固定枚举而不是现场临时补否则状态一跳错连日志都无从查起。3. 手写一套可复现的 SECS/GEM 最小源码连接、编解码和心跳3.1 从零起 HSMS 服务端监听、握手和设备 ID 校验用 Python 写最小 HSMS 服务端不算复杂。先监听 5000 端口收到连接后处理 Select.req回 Select.rsp把连接状态标记为已选择。HSMS 控制消息的类型码放在 10 字节消息头的字节 2-3Select.req 是 0x0001Select.rsp 是 0x0002Linktest.req 是 0x0007Linktest.rsp 是 0x0008。import socket import struct import threading # HSMS 控制消息类型码 CTL_SELECT_REQ 0x0001 CTL_SELECT_RSP 0x0002 CTL_LINKTEST_REQ 0x0007 CTL_LINKTEST_RSP 0x0008 # HSMS 消息头: 会话ID(2) 类型/流功能(2) 保留(2) 系统字节(4) HEADER struct.Struct(HHHI) def recv_exact(sock, n): buf b while len(buf) n: chunk sock.recv(n - len(buf)) if not chunk: raise ConnectionError(connection closed) buf chunk return buf def handle_client(conn, expected_dev_id): while True: # 先收 4 字节长度, 再收完整消息体 length struct.unpack(I, recv_exact(conn, 4))[0] body recv_exact(conn, length) # 解析消息头 session_raw, msg_type, _rsv, sys_bytes HEADER.unpack(body[:10]) session_id session_raw 0x3FFF # 低 14 位是设备 ID # 设备 ID 不匹配直接断开, 防止主机串线连错设备 if session_id ! expected_dev_id: conn.close() return if msg_type CTL_SELECT_REQ: rsp HEADER.pack(0x0000, CTL_SELECT_RSP, 0x0000, 0x00000000) conn.sendall(struct.pack(I, len(rsp)) rsp) elif msg_type CTL_LINKTEST_REQ: rsp HEADER.pack(0x0000, CTL_LINKTEST_RSP, 0x0000, 0x00000000) conn.sendall(struct.pack(I, len(rsp)) rsp) def main(): srv socket.socket(socket.AF_INET, socket.SOCK_STREAM) srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) srv.bind((0.0.0.0, 5000)) srv.listen(8) while True: conn, addr srv.accept() threading.Thread(targethandle_client, args(conn, 0x0001), daemonTrue).start() if __name__ __main__: main()这段代码只处理控制消息不解析 SECS-II 数据体但已经覆盖了 HSMS 通信建立的骨架。recv_exact 是必要的TCP 是流式协议不按长度读满就解析粘包和半包问题能把人搞疯。Select.rsp 的消息头要按标准结构回只填类型码、其余字段补零很多初学者把后面的字段随意填成设备信息主机端会直接拒收。设备 ID 参数按工厂规划写同一端口多台设备时这里就是第一道防线。如果设备做 HSMS-Client 主动连主机代码差别只在连接方向把 accept 换成 connect建连后同样先处理 Select 流程。我一般建议能用 Client 就尽量 Client 模式这样可以少在防火墙上给每台设备开入站端口现场部署省很多事。3.2 SECS-II 编解码字节流和嵌套数据结构怎么互转SECS-II 的数据项是带格式码和长度的嵌套结构。每个数据项由一个字节的格式码、宽度可变的长度字段和内容组成。长度字段的宽度由格式码的高位两位决定常见规则是 0 对应 1 字节1 对应 2 字节2 对应 4 字节。List 的长度字段表示子项数量而不是字节数这点特别容易写错。# 常用格式码(完整字节值) FMT_LIST 0x01 # List, 长度字段是子项个数 FMT_BINARY 0x25 # Binary, 高位两位指示 4 字节长度, 适合大块数据 FMT_ASCII 0x41 # ASCII 字符串 FMT_BOOL 0x09 # Boolean def read_item(data, pos): fmt data[pos] pos 1 # 长度字段宽度由格式码高位决定, 0-1, 1-2, 2-4 width 1 ((fmt 6) 0x03) length int.from_bytes(data[pos:pos width], big) pos width if fmt FMT_LIST: items [] for _ in range(length): item, pos read_item(data, pos) items.append(item) return items, pos elif fmt FMT_ASCII: return data[pos:pos length].decode(ascii, errorsreplace), pos length elif fmt FMT_BINARY: blob data[pos:pos length] return blob, pos length elif fmt FMT_BOOL: return data[pos] ! 0, pos length else: return data[pos:pos length], pos length参数说明里最关键的是 length 字段的宽度。有些设备固件实现不规范长度字段不按格式码高位走统一用一字节这种设备碰到超过 255 字节的 Binary 数据就会解析错位表现为主机收到的字段全部错位但又不报错排查极其痛苦。ASCII 解码建议加上 errorsreplace老设备偶尔会在字符串里塞控制字符直接 decode 抛异常会把整个接收线程打死。写完解码器必须马上写对应的编码器。我用 S1F1/S1F2 做第一个验证S1F2 的响应体是L A 设备名 A 软件版本 ...如果编码器里 List 的嵌套顺序不对主机解析出来的字段全部对应错位。这种错位在报文日志里很难看出来因为每个字段类型都合法。3.3 心跳与重传网络抖动下怎么保证链路不假死HSMS 在 TCP 层之上没有业务保活链路是否活着全靠 Linktest 消息。主机定期发 Linktest.req设备在超时时间内回 Linktest.rsp收不到就判定断线并重连。心跳包重传的源码逻辑不难难在参数连得太频浪费带宽连得太疏链路假死时间过长现场表现为“连接还在但消息都发不出去”。import socket import time import threading class LinktestManager: def __init__(self, conn, interval45, timeout10): self.conn conn self.interval interval self.timeout timeout self._last_ok time.time() self._alive True def send_linktest(self): # Linktest.req: 类型码 0x0007, 10 字节头 req HEADER.pack(0x0000, 0x0007, 0x0000, 0x00000000) try: self.conn.sendall(struct.pack(I, len(req)) req) self.conn.settimeout(self.timeout) resp self.conn.recv(1024) if resp: self._last_ok time.time() except socket.timeout: self._alive False def loop(self): while self._alive: time.sleep(self.interval) self.send_linktest() def is_alive(self): return self._alive心跳间隔的常见设置是 45 秒对应很多 EAP 系统的默认表网络稳定时没问题。但如果现场交换机或安全设备把空闲连接老化时间设成 30 秒45 秒心跳就会周期性断链。我的习惯是把 interval 放进配置文件首次上线先用 20 秒跑半天再根据日志调整到 30 秒或 45 秒。检测到断线后不要做复杂的指数退避重传直接关闭旧 socket重新走 Select 握手更简单可靠。SECS-II 的事务重传是另一层逻辑发了一个 SxFy 请求后规定时间内没收到响应要重发相同系统字节的消息。实现时用一个字典把系统字节和发送时间、重发次数存起来超时后取出重发。这里最容易犯的错误是重发时重新生成系统字节导致设备端把重发当成新事务响应也对不上。4. 高速场景的四个避坑点并发、解析、心跳和事件上报4.1 设备只允许最小连接数并发的第一个坑现象把服务端代码写成能接受几千个 TCP 连接到现场发现设备固件只允许两个连接。第三个连接要么被设备直接踢掉要么一直挂在 SYN 状态主机端来回报错。原因设备端的 SECS 实现往往不是通用 TCP 服务器而是按固定会话数分配的。常见设计是一个连接给 EAP 主机另一个预留给人机界面或工程诊断并没有开放无限连接的能力。看似源码里 accept 随便调实则是设备资源的硬限制。解决实施前先查设备参数里的连接数上限通常标注为会话数或连接槽位。EAP 只维持一条主连接需要临时诊断时复用这条会话不要另开连接去跑 S2F41。源码里的连接管理写成有限连接池超出上限直接拒绝并记录告警比无限 accept 在线上更可控。4.2 解析性能陷阱大 Binary 和字符串转码拖慢收包现象设备每秒上报几十个事件或者 Trace 数据里带几百 KB Binary代码处理一帧要上百毫秒。主机端消息积压最后 TCP 接收缓冲溢出日志里出现“收到半个包”的异常。原因大量 SECS 源码对 Binary 用逐字节 append对字符串反复 decode再把整个 List 反复拷贝。我做过基准测试一个 1MB 的 Binary 负载逐字节处理比整块切片慢二十倍以上。问题不在协议在写法。解决解析时对 Binary 直接整块切片返回 bytes 或内存视图后续计算不要二次拷贝。事件上报的 S6F11 如果嵌套很深只解到需要的层级就停不要递归到底。解析线程只做入队和 ACK真正的业务计算放到消费线程避免一条大消息卡住整个接收循环。4.3 高频采集下的事件丢帧缓冲溢出和优先级倒置现象设备端把 Trace 采集频率调到 10ms 一组主机处理不过来事件开始丢。而且不是丢后面的是最早的事件先丢趋势分析里缺了头一段。原因TCP 反压最终作用在设备侧发送队列。设备端的队列设计往往是最新覆盖最旧这对高频 Trace 数据来说方向就反了越新的数据越重要旧的丢了反而不影响实时监控。解决把设备端事件队列拆成两类。高频 Trace 队列允许丢旧保新只用环形缓冲存最近一批低频告警和数据采集结果必须可靠上报走独立队列并支持重传。主机端对应给 S6F11 开独立接收线程快速入队后立即回 ACK业务解析放慢一点没关系别把设备端发送堵死。4.4 心跳参数是现场玄学间隔不对链路时好时坏现象联调时一切正常上线后每隔固定时间断一次。重连后又正常过一会又断。TCP 层看得到 RSTSECS 层没有任何报错。原因网络设备比如交换机、工业防火墙空闲老化时间比 Linktest 间隔短导致链路先被中间设备杀掉。很多 EAP 默认 45 秒心跳但现场交换机老化时间设成 30 秒这个冲突在代码层面完全看不出来只能算时间参数的巧合。解决先把 Linktest 间隔降到 20 秒试再不行就在 TCP 层开 KeepAlive 兜底链路两侧都设置。现场如果发现“每隔 N 分钟必断”的规律优先把 N 和心跳间隔、交换机老化时间放在一起算很多是周期共振。我遇到过 N 正好是心跳间隔三倍的案例光看代码一辈子找不到原因。5. 用模拟器和压测脚本站稳再上产线验证方法与现场习惯5.1 标准模拟器互通验证对接测机前不要拿真实设备练手先跑通 SECS/GEM 模拟器。把源码里的设备角色起成模拟器主机用现成的 SECS/GEM 模拟器连接先跑 S1F13 建立通信再手动触发一个 S6F11 事件确认消息头、会话 ID 和系统字节都正确。互通测试通过后把模拟器切到主机侧验证设备端收到 S2F41 远程命令时状态机转换是否正常。做完这两轮再上真实设备问题会少一大半。5.2 压测怎么抓真实吞吐用脚本模拟主机高频请求或者让设备循环上报 S6F11# 快速压测: 连续发 1000 帧 S6F11, 统计 RTT for i in range(1000): msg build_secs2_message(device_id1, stream6, func11) t0 time.time() send_and_wait(msg) # 内部按系统字节配对响应 rtt.append(time.time() - t0) print(avg_rtt_ms, sum(rtt) / len(rtt) * 1000) print(p99_rtt_ms, sorted(rtt)[int(len(rtt) * 0.99)] * 1000)把平均 RTT 和 p99 RTT 记下来作为这条产线的基线。跑压测时把日志级别调到 WARNING避免高 IO 的日志写入把结果带偏。测完清空模拟器的仿真事件别让积压消息在正式联调时混进来这些都是血泪经验换来的。5.3 现场实施的两个习惯第一所有 SxFy 消息都落日志日志里必须带系统字节这样请求和响应能配对查。第二心跳间隔、重传超时、设备 ID、连接上限这些参数全部外置到配置文件不要写死在代码里。我吃过亏现场改一个超时参数要重新编译换到另一台设备忘同步最终线上参数和代码对不上排查时连后悔药都没有。这套源码方向值不值得长期投入如果公司持续有封测或晶圆设备对接需求值得把 SECS/GEM 源码吃透并沉淀成内部公共库后续 EAP 系统的现场实施、部署及日常运维工作会轻松非常多如果只是单台设备一次性项目用成熟模拟器加最小源码验证就够。我的习惯是先把最容易出问题的三处也就是心跳间隔、长度字段解析、连接上限全部写成带默认值的配置项再交到现场。希望帮到你。本文还有配套的精品资源点击获取
返回列表