
简介面向半导体设备通信的 SECS/GEM 协议栈 C 实现完整覆盖 SEMI E4设备、E5消息、E30GEM、E37HSMS等常用标准适合 MES、设备自动化及上位机开发工程师直接集成或学习二次开发。包内共 92 个文件包含 DLL、LIB、头文件、C 源码、配置文件、PDF 标准文档、可执行模拟器及数据表格压缩包仅 5.89MBDLL/LIB 用于运行时集成源码与文档便于理解协议细节模拟器可支撑通讯测试配置文件则方便快速修改设备参数。资源已有 5378 人学习提供可直接复制的 Demo 示例代码且不依赖任何第三方库能显著降低协议实现与排错成本。配套说明涵盖各模块接口、参数配置与二次开发流程适合有 C 基础、需快速实现 SECS/GEM 通讯功能的工程人员参考使用。1. SECS/GEM 看着吓人其实 SDK 已经帮你扛掉了最硬的部分1.1 先搞懂对手SECS/GEM 到底是一族什么标准第一次收到客户发来的《设备联网规格书》我只在目录里看到 SECS/GEM、HSMS、E5/E30/E37 这几个词人就有点犯怵。等拿到厂商提供的集成包文件夹里躺着一个名为 SECS-SDK-DLL-LIB 的压缩包——里面是 .dll、.lib、一个 .h 头文件外加一个满是注释的 Demo 工程这时候才多少有点底。SECS/GEM 在半导体、光伏、面板这些行业里几乎是设备对接产线 MES 的默认标准但协议本身牵扯到消息编解码、状态机、连接管理一堆东西自己从零写根本不现实所以大部分设备厂商会选择用协议栈厂商提供的 SDK把它封装成 DLL/LIB 库交付给设备软件工程师。很多人第一次接触 SECS/GEM 都会误以为它是一个协议实际上它是一族标准的统称。E4 定义了串口传输层的 SECS-IE37 定义了基于 TCP/IP 的 HSMS 传输E5 定义了消息格式层的 SECS-II也就是我们天天打交道的 S1F1、S2F41 这类 Stream/Function 消息E30 才是 GEMGeneric Equipment Model它规定了设备的行为模型、状态机、报警和数据收集的逻辑。越是做设备端软件越需要把这几个层次分清楚传输层负责把数据可靠地搬过去消息层负责把业务数据编码成标准结构GEM 层管设备和上位机之间什么时候该干什么事。1.2 自己实现协议栈的代价远比你想象的大刚入行的工程师往往会有个念头SDK 也不复杂自己写个通讯不就行了我当时也这么想过后来看了一天 E5 规范就放弃了。SECS-II 的消息体是 AS-II 数据项格式一个消息可以嵌套 List、Binary、Boolean、Integer、U4、F8 等各种类型字节序、长度编码、数据项边界都要逐字节处理。光是消息头的 system bytes 管理和请求-应答关联就能让初学者绕晕。更麻烦的是状态机和时序。HSMS 的连接建立要处理 Select 握手消息发送要等待 T3 定时器范围内的应答通讯中断后要按 T5/T7/T8 等多个定时器的规则处理重连和保活。GEM 层更不用说Online、Local、Remote 状态切换、报警上报、配方管理、SPC 数据采集每一块都有对应的消息序列和状态约束。把这些全部实现到能过工厂验收FAT的程度一个熟手至少也要几周而且后续每条客户定制的消息都可能引入新的边界问题。说白了这就像自己造变速箱再去造车能用现成的东西绝对不碰。1.3 DLL/LIB 在交付体系里的真实位置协议栈厂商把自己实现好的 SECS 核心封装成库Windows 平台最常见的就是 DLL 和 LIB。这份交付物里通常包含几个部分一个动态链接库或者静态库提供全部 API一个导入库供编译期链接一个头文件声明函数接口再加上配置文件、SML 模板和 Demo 工程。设备软件工程师要做的是把自己的业务逻辑接到这套 API 上而不是去实现 SECS/GEM 本身。这里有个容易误解的点DLL/LIB 只是载体不同厂商的 SDK 接口风格差异很大。有的 API 是 C 风格函数有的已经是 C 类库有的还额外提供 .NET 封装。但核心的能力边界是一致的——它们都已经帮你处理好了传输层、消息层和大部分 GEM 状态机你要关心的是业务什么时候发送哪条消息、收到某条消息后做什么动作、设备状态变化时要不要上报给主机。2. 拿到 DLL/LIB 包先别急着写代码形态和调用方式决定坑的数量2.1 这个 .lib 到底是静态库还是导入库决定了你的链接方式拿到一个 .lib 文件第一件事不是扔进工程而是先搞清楚它是静态库还是动态库的导入库Import Library。这个判断能直接省掉你后面几小时的排查时间。静态库在编译链接时会被完整地拷贝进 exe最终部署时不需要带着 .lib 和 .dll导入库则只是给链接器用的指向标真正运行时还需要动态加载同目录下的 .dll。区分的办法很简单静态库体积通常比较大而且同目录下一般不会有一个同名 .dll导入库体积很小同目录下必定配套一个 .dll。如果是静态库你的安装包只需要发布 exe如果是动态库exe 同目录下必须放上对应的 DLL少一个都跑不起来。C# 工程则完全不需要关注 .lib直接 DllImport 那个 DLL 即可。2.2 调用约定和导出符号位置不对的 extern C 会让你一脸蒙用 C 调 C 风格的 SECS SDK最常见的问题是名字修饰name mangling。厂商的头文件通常会这样声明extern C { __declspec(dllexport) HANDLE secs_initialize(const SecsConfig* cfg); __declspec(dllexport) int secs_start(HANDLE h); }这里extern C的作用是让导出符号保持 C 风格命名不加就会被 C 编译器修饰成?secs_initializeYAPEAXPEBUSecsConfigZ这种导致链接时找不到符号。你要是自己写一个函数声明去调用 DLL一定得保持一致的调用约定。常见的约定有__cdecl和__stdcall两种如果头文件里是__stdcall而你的调用侧没写程序可能在运行时栈不平衡莫名其妙地崩溃。如果厂商只给了一个 DLL 没有头文件可以用 Visual Studio 自带的 dumpbin 工具查看导出表dumpbin /exports SecsSdk.dll这样能看到所有可用的函数名再自己用LoadLibraryGetProcAddress动态调用或者写一个 .def 文件重新生成导入库。但这类情况极其少见厂商一般都会把头文件、文档、示例工程一起打包。2.3 位数、运行库、部署路径启动期三个隐形杀手DLL 加载失败几乎都逃不开这三件事。首先是位数匹配x64 的 exe 必须加载 x64 的 DLLx86 的 exe 必须加载 x86 的 DLL混着装只能在 64 位系统上迎来一脸黑。C# 项目默认的 AnyCPU 在 64 位机器上会以 64 位进程运行如果厂商给的是 32 位 DLL直接抛 BadImageFormatException。其次是 VC 运行库。很多 SECS SDK 是用 Visual Studio 编译的目标机器如果没装对应版本的 VC RedistributableLoadLibrary 会失败返回的错误码往往是 126找不到指定的模块而不是 127。这类问题最迷惑人因为库文件明明就在 exe 目录下。最后是部署路径DLL 的搜索顺序有讲究最稳的做法就是和 exe 放同一个目录别往 system32 里塞也别指望靠 PATH 环境变量解决。3. 一次完整的集成从初始化到收发消息的代码走读3.1 初始化一条龙配置加载、内核创建、回调注册SECS SDK 的接入流程本质上是一条直线加载配置、创建内核、注册回调、启动通讯。为了让你有直观的体感我用最常见的 C 风格 API 写一段示例实际 API 名称以厂商文档为准但逻辑结构基本一致// 1. 加载配置 SecsConfig cfg; memset(cfg, 0, sizeof(cfg)); cfg.deviceId 0; // 设备ID与主机约定一致 cfg.hostIp 192.168.1.100; // 主机地址 cfg.hostPort 5000; // 主机端口 cfg.localPort 5001; // 本机监听端口 cfg.connectMode SECS_MODE_PASSIVE; // PASSIVE设备端被动等主机来连 cfg.linkTestIntervalMs 10000; // 链路心跳间隔 // 2. 初始化协议栈创建通讯内核 HANDLE hSecs secs_initialize(cfg); if (hSecs NULL) { // 查看日志通常是配置非法、端口被占用或 License 缺失 } // 3. 注册连接状态回调和消息回调 secs_set_connection_handler(hSecs, OnConnectionChange); secs_set_message_handler(hSecs, OnSecsMessage); // 4. 启动通讯 secs_start(hSecs);这里最关键的是 deviceId 和 connectMode。deviceId 是设备在 SECS 网络中的地址主机发来的每条消息头都带这个 ID对不上会被协议栈直接丢弃connectMode 则决定你是等主机连还是主动连主机选错角色通讯永远建立不起来。3.2 消息的发送与应答关联system bytes 不是玄学SECS-II 的消息是一个 Stream/Function 对比如 S1F13 是建立通讯请求S1F14 是建立通讯确认。当收到对端消息时如果对方设置了 Reply Expected你就必须在超时时间内回复一条相同 Stream、Function 加一的应答消息。而最容易被忽略的是 system bytes——它就像快递单号用于把请求和应答关联起来。SDK 内部已经处理了大半但你写回包时必须原样带回void OnSecsMessage(HANDLE h, const SecsMessage* msg) { if (msg-stream 1 msg-function 13) { SecsMessage reply; memset(reply, 0, sizeof(reply)); reply.stream 1; reply.function 14; reply.systemBytes msg-systemBytes; // 应答必须带回原请求的 system bytes reply.replyExpected 0; secs_send_message(h, reply); // 这里可以顺便把设备的软件版本、状态数据填进 body } if (msg-stream 1 msg-function 1) { // S1F1 Are You Online // 回复 S1F2带上 ONLINE 状态 } }SDK 在发送消息时通常会提供一个带等待应答的接口或者通过回调机制把应答消息返回给你。如果你发一条 S2F41 主机命令给设备设备回 S2F42你需要在回调里根据 system bytes 判断这是哪一次请求的应答。所有 SDK 都会做这层关联但你要把哪个请求对应哪个回调的业务逻辑理清楚否则多线程同时发消息时很容易张冠李戴。3.3 C# 调用路径P/Invoke 还是托管封装设备软件用 C# 写 HMI 或上位机的情况非常多。如果厂商提供了 .NET 托管封装直接用封装类是最省心的如果只有原生 DLL就需要用 P/Invoke 手搓一层。典型的写法如下[StructLayout(LayoutKind.Sequential)] public struct SecsConfig { public int deviceId; [MarshalAs(UnmanagedType.LPStr)] public string hostIp; public int hostPort; public int localPort; public int connectMode; public int linkTestIntervalMs; } [DllImport(SecsSdk.dll, CallingConvention CallingConvention.Cdecl)] private static extern IntPtr secs_initialize(ref SecsConfig config); [DllImport(SecsSdk.dll, CallingConvention CallingConvention.Cdecl)] private static extern int secs_start(IntPtr h); [DllImport(SecsSdk.dll, CallingConvention CallingConvention.Cdecl)] private static extern int secs_set_message_handler(IntPtr h, SecsMessageCallback cb);这里有一个特别阴的坑C# 的委托对象如果定义在局部变量里没有保持引用会被 GC 回收然后原生 DLL 回调时直接崩溃。所以消息回调委托一定要挂在类字段上保证存活期覆盖整个 SECS 生命周期。另外消息体里的字符串、字节数组涉及 Marshal 转换最好在封装层里就转成友好的基础类型别把原生结构体直接抛给业务层。4. 设备端和主机端是两套玩法连接模型与状态机4.1 主动连接还是被动监听开局就要定对角色很多第一次做 SECS/GEM 对接的工程师会把精力全花在消息上直到联调才发现连接方向跟对方想的不一样。SECS/GEM 里的主从关系其实有两层TCP 连接层和设备控制层。TCP 连接层上如果设备端配置成被动模式Passive设备就一直监听端口等主机主动发起 TCP 连接配置成主动模式Active设备会按固定间隔去连主机的 IP 和端口。在工厂实际环境里设备有固定 IP 且主机可以访问到它时通常用被动模式如果设备在 NAT 后面或主机无法主动访问设备就得让设备主动外连。最稳妥的做法是开发前直接跟 MES 或主机团队确认一句话——你们连我们还是我们连你们连接方式是什么这句话能避免至少半天无效调试。另外无论哪种模式TCP 建立起来之后还要经历 HSMS 的 Select 握手标志着 SECS 会话正式建立这时才进入可以收发 SECS-II 消息的 Communicating 状态。4.2 断线重连、心跳与 SECS 定时器生产车间的网络环境比办公室恶劣得多交换机重启、网线被踩、Wi-Fi 干扰都可能导致通讯中断。SECS 协议定义了一组定时器SDK 一般都开放了配置项典型值大致如下定时器含义典型值备注T3等待应答超时45s发出去的请求没回复超时报错T5连接请求重试间隔10s主动建连失败后的重试T6控制消息应答超时5sHSMS 层控制消息专用T7连接空闲超时10s长时间无数据可断开部分场合关掉T8网络字符间隔超时5s防止半包/拆包错误挂死连接设备端如果做被动监听断线后要自动回到 Listen 状态重新等待主机连接主动模式则要处理重连间隔。这里我的经验是重连间隔别设得太激进连续失败时做一个简单的退避比如 5 秒、10 秒、20 秒慢慢增加避免网络抖动时所有设备一起疯狂重连把主机打挂。心跳Linktest也同理10 秒左右是比较常见的配置但具体节奏要看产线网络的实际情况。4.3 GEM 状态模型可联机、本地、远程控制的权限边界GEM 标准里设备有一个三态模型Offline、Online-Local、Online-Remote。Offline 下设备和主机虽然可能有 TCP 连接但不接受远程控制只回最基本的 S1F2 类状态查询Online-Local 下操作员在设备端本地操作主机可以采集数据但不可以控制设备Online-Remote 下主机才拥有完整的远程控制权限比如通过 S2F41 下发配方或启停命令。很多设备软件把这几个状态做成了 HMI 上的一个联机/离机按钮切换后通过 SD 接口同步状态给主机。核心的坑在于设备状态和 TCP 连接状态是两回事连接建立不代表设备已经联机联机也不代表主机立刻能远程控制。业务层必须单独维护这个状态模型否则会出现主机下发命令设备却当成本地操作处理的严重事故。SDK 一般会提供状态获取接口但状态切换的业务逻辑和设备端安全控制一定得自己实现。5. 把这套东西跑进生产之前完整排障链路5.1 启动期DLL 加载失败和初始化报错这个阶段的问题特征非常明显程序一开始跑要么直接弹窗找不到 DLL要么初始化函数返回一个错误码。按下面的顺序排查基本能覆盖 90% 的原因。先确认 DLL 和 exe 在同一个目录这是最简单但最常被忽略的一点再确认进程位数任务管理器里看一眼进程名后面有没有带 (32 位)C# 项目直接改编译目标为 x64 或 x86 测试然后用 dumpbin 看 DLL 依赖dumpbin /dependents SecsSdk.dll如果能看到 VCRUNTIME140.dll、MSVCP140.dll 这类依赖说明目标机器必须安装对应版本的 VC Redistributable。还有一类比较隐蔽的问题是初始化时返回特定错误码比如端口被占用、License 文件不存在、SML 配置路径不对。这类问题 SDK 日志里都会写关键是先找到日志输出函数或日志文件位置别让调试变成瞎猜。5.2 连接期TCP 是通的SECS 握手却过不去启动没问题TCP 端口也能用 telnet 连上但通讯状态一直停在 Not Communicating这是联调阶段最磨人的问题之一。先别急着怀疑 SDK按链路逐层确认。先用 Wireshark 抓包看 TCP 三次握手之后有没有 HSMS 的 Select 控制消息。Wireshark 自带 HSMS 解析器能看到消息类型和 Session ID。如果抓包发现设备收到了连接请求但一直没回 Select最常见的原因是设备 ID 不匹配。SECS 消息头里的 Session ID 承载的就是设备 ID主机发来的设备和本机配置的 deviceId 不一致时协议栈会认为这不是发给自己的消息直接丢弃。另一个常见原因是端口配错比如设备端监听的是 5001主机连的却是 5000TCP 层看起来是通的但协议栈根本没收到数据。把两端的 IP、端口、设备 ID 三项拉出来逐行对照绝大多数握手问题都能解决。5.3 交互期消息发出去了应答却迟迟不来连接已经进入 Communicating 状态S1F13 发出去了对方就是没有回应。这种问题最容易让人怀疑 SDK 有 bug但大多数情况下还是业务侧的配置问题。先检查这条消息是否设置了 Reply Expected如果设置了对方必须在 T3 超时内回包再确认对端是否注册了对应的消息处理函数很多设备端 Demo 默认只处理了少数几条消息遇到 S2 系列的查询就直接静默。最后看 log 里的超时错误码一般会明确提示T3 timeout waiting for reply。处理这类问题我强烈建议在封装层加入消息级日志每次发送和接收都记录时间戳、方向、SxFx、system bytes最好再记录消息体摘要。排查时一翻日志就能看出是发了没收到还是收到了没回。另外适当调大 T3 也是一种手段但治标不治本如果网络确实抖动严重45 秒都等不到回包那问题大概率在对端逻辑或网络上。5.4 没有现成 Host 怎么办模拟器和抓包工具双管齐下很多设备软件工程师在开发阶段根本没有真实主机可连这时候别硬等自己先搭一个最小环境。厂商通常会在 SDK 包里附带 Host Simulator 或 Equipment Simulator强烈建议先把这套模拟器跑起来设备端连主机模拟器完成一次握手、一次事件上报、一次报警上报再碰真实业务。如果没有合适的模拟器可以用一个几十行的 Python 脚本验证底层 TCP 连接import socket s socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) s.bind((0.0.0.0, 5000)) s.listen(1) conn, addr s.accept() print(connected from, addr) while True: data conn.recv(1024) if not data: break print(RX:, data.hex())这个脚本不会解析 HSMS但能确认设备端有没有发出数据、发的字节是什么。配合 Wireshark 看完整报文基本就具备了定位所有底层问题的能力。6. 用 SML 配置把消息定义和代码解耦一次加消息不用重编 DLL6.1 SML 是什么为什么协议栈厂商都推荐它SMLSECS Message Language是一种文本格式用来描述 SECS-II 消息的结构。它长这样S1F1 . S1F13 U1:0.SDK 会在初始化时加载 SML 文件之后你发送或接收消息时协议栈自动按照 SML 里的定义完成数据项编码和解码。这带来的好处是质变的客户要求新增一条自定义消息时你只需要在 SML 文件里加上定义然后在业务代码里处理对应的 Stream/Function再也不用手改 DLL、重新编译整个工程。设备型号不同、主机要求的消息字段不同往往也只是换一份 SML 文件的事。6.2 配置、封装层、业务层的三层工程结构基于我个人的经验用 SECS SDK 做设备软件最好把工程拆成三层。最底层是协议适配层ProtocolAdapter负责所有 SDK API 调用、DLL 生命周期管理、日志打点这一层是唯一允许引用厂商头文件的代码模块。中间是业务服务层处理设备状态机、报警逻辑、数据采集这一层不关心 SECS 细节只面向业务概念。最上层是 UI 和业务流程层比如 HMI 上的联机按钮、工艺参数下发界面。SML 文件、配置文件、设备 ID、IP 端口这些全部走配置纳入版本管理不要硬编码在代码里。这样做的好处是将来换一家协议栈厂商或者升级 SDK 版本只需要重写最底层那一个适配层业务逻辑几乎不用动。我踩过最大的坑就是把协议逻辑和业务逻辑写在一起最后加一个报警上报功能都能牵一发动全身。把层拆干净后面所有设备项目都会轻松很多。说句实在话我第一次跑通 SECS/GEM 通讯是在一个周五晚上甲方那边只给了一个模拟器地址我拿着 SDK 的 Demo 工程改配置一条 S1F13 握手消息从设备端发出去看到主机那边返回 S1F14心里的石头才算落地。这一步走通之后后面的一切都顺了。最后给你一个我自己的规矩任何 SECS SDK 集成都先做一次空跑——两端都连上模拟器把握手、事件上报、报警上报三件事跑通再碰真实业务逻辑。这个方法帮我避开了至少三次拿到现场才发现协议理解错了的尴尬。希望这篇文章也能帮你少走几步弯路。本文还有配套的精品资源点击获取