ARTICLE DETAIL

资讯详情

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

SecsGem 协议实战:C# 实现 Equip 与 EAP Host 通信及避坑指南

SecsGem 协议实战:C# 实现 Equip 与 EAP Host 通信及避坑指南 简介这份资源面向半导体设备通信方向的C#开发者与自动化工程师提供一套可运行的SecsGem通信实现包含Equip设备端与EAP Host主机端两套程序可用于晶圆厂设备联机调试、协议学习与项目二次开发。压缩包共1317个文件约41.58MB以545个dll依赖库、280个cs源码、16个csproj工程文件与16个exe可执行程序为主另含sml消息定义、config配置、resx资源及log日志等辅助文件工程结构完整便于直接编译运行与逐层剖析。资源围绕Socket通讯建立后的握手流程展开主动端先发送Selected.rsp被动端回复Select.rsp后进入Selected状态随后通过S1F13与S1F14完成SecsGem连接建立读者可据此理解协议状态机与报文交互细节。目前已有90人学习下载适合希望掌握设备端与主机端双向通信、快速搭建联机验证环境的中高级开发者参考。1. 从一台贴片机的报警说起SecsGem 到底在产线里干什么凌晨两点产线上一台贴片机突然报警停机操作员在 MES 上看到的状态还是「运行中」。问题出在设备端和设备管理软件之间那条数据链路上——设备已经报了错但上位机根本没收到。这类「设备状态和系统状态对不上」的问题在半导体、面板、光伏这些行业里几乎每周都会遇到而解决它的核心协议就是 SecsGem。SecsGemSEMI Equipment Communications Standard / Generic Equipment Model是 SEMI 组织定义的一套设备通信标准分两层底层是 SECS-I/HSMS 负责报文传输上层是 SECS-II 定义消息内容GEM 则规定了设备该实现哪些行为。简单说它让不同厂商的设备和管理软件能用同一套「语言」对话。标题里的 Equip 设备端就是设备侧的程序EAP Host 主机端则是上位机侧的程序两者通过 HSMS 建立 TCP 连接后互发消息。这套东西适合谁做 C# 上位机、设备自动化、MES/EAP 对接的工程师。如果你正在被「设备数据采不上来」「不同品牌设备要写不同驱动」折磨那这篇内容就是给你准备的。下面我从协议结构讲到两端程序的落地实现把能抄的代码和踩过的坑都摆出来。2. SecsGem 的报文结构和 HSMS 连接建立先把地基打牢在动手写 Equip 和 Host 之前必须搞清楚 SecsGem 的报文长什么样、连接怎么建。很多人一上来就抄代码结果连「为什么这条消息发出去设备没反应」都排查不了就是因为跳过了这一层。2.1 SECS-II 消息的组成Stream、Function 和消息体SECS-II 消息用 SxFy 表示S 是 Stream功能大类F 是 Function具体功能。比如 S1F1 是「Are You There」握手S1F13 是建立通信请求S6F11 是事件报告。每个消息体由若干数据项Data Item组成数据项有类型和长度常见类型如下格式码类型C# 对应类型说明0o10Booleanbool布尔值0o21Binarybyte[]二进制0o30ASCIIstring字符串0o31JIS-8string日文编码0o34I1sbyte1 字节有符号0o35I2short2 字节有符号0o36I4int4 字节有符号0o44F8double8 字节浮点0o51U1byte1 字节无符号0o52U2ushort2 字节无符号0o54U4uint4 字节无符号消息头固定 10 字节结构是设备 ID2 字节 消息头10 字节含 Stream、Function、W-Bit、System Bytes 等。W-Bit 表示是否需要回复Wait BitSystem Bytes 用于匹配请求和回复。这个设计很关键——Host 发一条 S1F13 给 EquipEquip 必须回一条 S1F14靠的就是 System Bytes 对上号。2.2 HSMS 连接建立Select 流程和心跳机制HSMS 是 SecsGem 跑在 TCP 上的传输层。连接建立不是简单的 TCP 三次握手就完事还要走 Select 流程TCP 连接建立后主动方发 Select.req被动方回 Select.rsp状态码 0 表示成功之后才能发 SECS-II 消息连接期间用 Linktest.req/rsp 做心跳保活下面是一个 HSMS 连接管理的核心代码骨架public class HsmsConnection { private TcpClient _client; private NetworkStream _stream; private Timer _linktestTimer; private ushort _systemBytes 0; // 建立连接并发送 Select.req public async Taskbool ConnectAsync(string ip, int port) { _client new TcpClient(); await _client.ConnectAsync(ip, port); _stream _client.GetStream(); // 构造 Select.reqSessionID 通常为 0xFFFF var selectReq BuildHsmsHeader(0xFFFF, 0x01, 0, 0); await _stream.WriteAsync(selectReq, 0, selectReq.Length); // 等待 Select.rsp超时 5 秒 var buffer new byte[10]; var readTask _stream.ReadAsync(buffer, 0, 10); if (await Task.WhenAny(readTask, Task.Delay(5000)) ! readTask) return false; // 检查 Select.rsp 状态码 return buffer[3] 0x00; } // 构造 HSMS 头部length 为消息体长度 private byte[] BuildHsmsHeader(ushort sessionId, byte pType, uint systemBytes, int length) { var header new byte[14]; header[0] (byte)(sessionId 8); header[1] (byte)(sessionId 0xFF); header[2] (byte)(length 24); header[3] (byte)(length 16); header[4] (byte)(length 8); header[5] (byte)(length 0xFF); header[6] pType; // 0x01Select.req, 0x02Select.rsp header[7] 0x00; header[8] (byte)(systemBytes 24); header[9] (byte)(systemBytes 16); header[10] (byte)(systemBytes 8); header[11] (byte)(systemBytes 0xFF); header[12] 0x00; header[13] 0x00; return header; } }这段代码里几个参数要特别注意SessionID 在 Select 阶段用 0xFFFF正式通信时用设备 IDPType 区分消息类型0x01 是 Select.req0x09 是 Linktest.reqSystem Bytes 每次请求要递增回复时原样带回。心跳定时器一般设 30 秒发一次 Linktest.req连续 3 次没收到 rsp 就判定连接断开。提示HSMS 的 Select 超时不要设太短有些设备启动慢5 秒是底线我一般设 10 秒。3. Equip 设备端实现状态机、事件报告和远程命令设备端程序的核心职责是维护自身状态机、响应 Host 的请求、主动上报事件。GEM 标准里定义了设备必须实现的状态模型这是 Equip 端的骨架。3.1 GEM 状态模型和事件报告机制GEM 设备有三个核心状态Control State设备控制权在谁手里、Equipment State设备运行状态、Processing State是否在处理工件。Control State 分 Online Local、Online Remote、Offline 三种Host 通过 S1F17/S1F15 等消息切换控制权。事件报告是设备主动上报的主要方式。流程是设备定义好事件比如「报警发生」Host 通过 S2F33 定义报告规则设备在事件触发时发 S6F11 上报。下面是一个事件上报的实现public class EquipEventReporter { private readonly HsmsConnection _conn; private readonly Dictionaryint, Listuint _reportDefs new(); // Host 通过 S2F33 下发报告定义这里解析并存储 public void HandleDefineReport(SecsMessage msg) { // 消息体格式DATAID 报告数量 [报告ID VID数量 VID列表]... var reader new SecsItemReader(msg.Body); reader.ReadU4(); // 跳过 DATAID var reportCount reader.ReadU2(); for (int i 0; i reportCount; i) { var reportId reader.ReadU2(); var vidCount reader.ReadU2(); var vids new Listuint(); for (int j 0; j vidCount; j) vids.Add(reader.ReadU4()); _reportDefs[reportId] vids; } } // 事件触发时调用构造 S6F11 上报 public async Task ReportEventAsync(int reportId, Dictionaryuint, object values) { if (!_reportDefs.TryGetValue(reportId, out var vids)) return; var body new SecsItemWriter(); body.WriteU4(0); // DATAID body.WriteU2((ushort)reportId); body.WriteU2((ushort)vids.Count); foreach (var vid in vids) { body.WriteU4(vid); WriteValue(body, values[vid]); } var msg new SecsMessage(6, 11, waitBit: true, systemBytes: _conn.NextSystemBytes(), body.ToArray()); await _conn.SendAsync(msg); } }这里的关键是报告定义和事件上报要严格对应。Host 定义了报告 ID 100 包含 VID 1、2、3设备上报时就必须按这个顺序和数量填值少一个 Host 解析就会错位。我见过有人图省事直接发固定格式结果 Host 端解析全乱套。3.2 远程命令处理和 S1F13 通信建立Host 通过 S2F41 下发远程命令Remote Command设备执行后回 S2F42。命令体包含命令名和参数列表。设备端要维护一个命令处理器字典public class RemoteCommandHandler { private readonly Dictionarystring, FuncDictionarystring, string, (byte, string) _handlers new(); public RemoteCommandHandler() { // 注册命令处理器返回 (HCACK, 错误信息) _handlers[START] p { StartProcess(); return (0, ); }; _handlers[STOP] p { StopProcess(); return (0, ); }; _handlers[PP-SELECT] p { if (!p.ContainsKey(PPID)) return (1, 缺少 PPID); SelectRecipe(p[PPID]); return (0, ); }; } public (byte hcack, string msg) Execute(string cmd, Dictionarystring, string parameters) { if (!_handlers.TryGetValue(cmd, out var handler)) return (1, 不支持的命令); // HCACK1 表示命令不存在 return handler(parameters); } }HCACK 返回值有讲究0 表示成功1 表示命令不存在2 表示命令已存在但参数不对3 表示命令当前无法执行。很多设备端实现只返回 0 和 1Host 端就没法区分「命令不认识」和「参数错了」排查起来很痛苦。S1F13/S1F14 是通信建立握手Host 发 S1F13 带设备信息Equip 回 S1F14 确认。这一步通常在 Select 完成后立即执行确认双方都准备好了。4. EAP Host 主机端实现消息路由、超时重发和日志Host 端比 Equip 端复杂的地方在于它要同时管理多台设备每台设备有自己的连接、状态和消息队列。核心是消息路由和超时管理。4.1 消息路由和 System Bytes 匹配Host 发出去的每条需要回复的消息W-Bit1都要等对应的回复。用 System Bytes 做 key 存一个待回复字典public class HostMessageRouter { private readonly ConcurrentDictionaryuint, TaskCompletionSourceSecsMessage _pending new(); private uint _systemBytes 0; // 发送需要回复的消息等待超时返回 null public async TaskSecsMessage SendAndWaitAsync( HsmsConnection conn, SecsMessage msg, int timeoutMs 10000) { var sysBytes Interlocked.Increment(ref _systemBytes); msg.SystemBytes sysBytes; var tcs new TaskCompletionSourceSecsMessage(); _pending[sysBytes] tcs; await conn.SendAsync(msg); var completed await Task.WhenAny(tcs.Task, Task.Delay(timeoutMs)); _pending.TryRemove(sysBytes, out _); return completed tcs.Task ? tcs.Task.Result : null; } // 收到回复时调用匹配 System Bytes public void OnReplyReceived(SecsMessage reply) { if (_pending.TryRemove(reply.SystemBytes, out var tcs)) tcs.TrySetResult(reply); } }超时时间设置是个经验活。S1 类握手消息 10 秒够用S2F41 远程命令要看设备执行时间我一般设 30 秒。S6F11 事件报告是设备主动发的Host 收到后回 S6F12 确认这条回复不需要等。4.2 多设备连接管理和日志记录一个 Host 通常要连几十台设备每台设备一个 HsmsConnection 实例用一个字典管理public class EapHostManager { private readonly Dictionarystring, DeviceSession _sessions new(); public async Task StartAsync(ListDeviceConfig configs) { foreach (var cfg in configs) { var session new DeviceSession(cfg); _sessions[cfg.DeviceId] session; // 每台设备独立连接失败不影响其他设备 _ Task.Run(async () { while (true) { try { await session.ConnectAndRunAsync(); } catch (Exception ex) { Log.Error(${cfg.DeviceId} 连接异常: {ex.Message}); await Task.Delay(5000); // 5 秒后重连 } } }); } } }日志这块我要多说一句SecsGem 的日志必须记录原始字节流不能只记解析后的消息。因为很多问题出在报文格式上比如长度字段算错了、数据项类型码写错了只看解析后的内容根本发现不了。我一般用十六进制格式记录收发原始数据配合时间戳和方向标识。5. 避坑指南SecsGem 联调中最容易翻车的五个地方这一章是我这些年踩过的坑里挑出来最有代表性的每一条都是真金白银换来的。5.1 现象Select 成功但消息发出去没反应原因HSMS 头部长度字段计算错误。长度字段指的是消息体长度不包括 10 字节的消息头。很多人把整个报文长度填进去对方按这个长度读就会多读 10 字节导致后续消息全部错位。解决发送前打印头部十六进制确认第 2-5 字节的长度值等于消息体字节数。接收端也要校验读到长度异常直接断开重连。5.2 现象S6F11 上报后 Host 收到但解析报错原因数据项类型码和实际值不匹配。比如 VID 定义的是 U4设备端写成了 U2Host 按 U4 读就会多读 2 字节后面全乱。解决在 Equip 端维护一份 VID 类型定义表写入前校验类型。Host 端解析时如果遇到未知类型码记录原始字节并跳过不要直接抛异常导致连接断开。5.3 现象设备频繁断连重连原因Linktest 心跳超时设置太短或者设备端处理 Linktest 的优先级太低被其他任务阻塞。解决心跳间隔设 30-60 秒超时次数设 3 次。设备端把 Linktest 处理放在独立线程或高优先级任务里不要和业务逻辑抢锁。5.4 现象S2F41 远程命令发出后设备执行了但 Host 显示超时原因设备端执行命令耗时较长超过了 Host 的超时时间但设备执行完后还是回了 S2F42此时 Host 已经移除了待回复记录。解决Host 端对耗时命令单独设超时或者设备端先回一个「命令已接受」再异步执行。我一般建议设备端在 5 秒内先回 HCACK0实际执行结果通过事件报告异步通知。5.5 现象多台设备同时上报事件时 Host 丢消息原因Host 端用单线程处理所有设备的接收某台设备消息量大时其他设备的消息被阻塞。解决每台设备一个独立的接收线程和消息队列解析后投递到业务处理线程池。注意消息队列要有上限防止内存暴涨。6. 用 Wireshark 抓包验证和一套可复用的调试习惯联调 SecsGem 最有效的手段是抓包。Wireshark 能直接解析 HSMS 协议但默认不解析 SECS-II 消息体需要手动配置。我的做法是先用 Wireshark 确认 TCP 层和 HSMS 层的交互正常再用自己写的十六进制日志工具看 SECS-II 内容。一个具体的验证流程Host 发 S1F13在 Wireshark 里过滤tcp.port 5000看是否有对应的 Select.req/rsp 和 S1F13/S1F14。如果 Select 成功但 S1F13 没回复检查 Equip 端是否在 Select 后正确启动了消息接收循环。调试习惯上我坚持三点第一所有收发消息落盘按天分文件保留至少 7 天第二每条消息记录 System Bytes 和耗时方便定位是哪条消息卡住了第三写一个模拟设备端和模拟 Host 端的测试工具不依赖真实设备就能验证协议逻辑。最后说一个进阶技巧用 C# 的Spanbyte和Memorybyte重写报文解析部分避免频繁的数组分配。在每秒上千条消息的场景下GC 压力会明显下降。这个优化我是在一台高速固晶机上做的消息处理延迟从平均 8ms 降到了 2ms 以内。希望这些经验帮到你少走点我当年走过的弯路。本文还有配套的精品资源点击获取
返回列表