简介SEMI E87-0301 是半导体 GEM300 体系下的 Carrier Management SystemCMS临时规范面向负责 300mm 晶圆厂自动化物料搬运的工艺集成工程师、设备工程师与 MES 厂商。标准以详细状态模型和场景定义了主机视图与生产设备在运载器传输协调、执行、完成阶段的行为要求明确覆盖 AMHS 车辆与负载端口之间的传输、设备内部缓冲空间的运载器进出、设备与负载端口访问模式切换、运载器到负载端口关联、Carrier ID 验证及槽位映射验证等关键机制为自动化与手动搬运提供统一通信准则。资源为单文件 PDF 原版标准压缩包约 542KB便于查阅、打印与归档。文档同时指出其作为 provisional 标准的限制涉及与 SEMI E15.1 300mm 负载端口的衔接以及集成设备配合 AMHS 时尚未完全定义的异常处理和错误恢复场景有助于读者更准确理解标准适用范围和未来演进方向。目前已有 2130 人学习下载适合需要深入掌握 CMS 运载器传输管理细节的半导体自动化从业者。1. 为什么设备都跑通了载具还在“乱跑”SEMI E87-0301 到底管什么某晶圆厂做自动化联调时设备已经能自动加工但载具在搬运轨道上不停报错MES 显示载具在等待区实际已经被机械手抓走主控下发搬运指令设备侧又因为“载具状态冲突”直接拒绝。整个团队查了三天最后发现所有问题都指向同一个地方设备侧和主控侧对“载具现在什么状态、能不能动”根本没有共同语言。这就是 SEMI E87-0301 要解决的事。E87-0301 是一套关于载具管理的通信规范它统一了载具 ID 的表示、载具状态的迁移、位置上报和搬运请求的交互方式让设备、搬运系统和工厂主控能用同一种语言描述“这个载具在哪、能不能用、正在做什么”。它不解决工艺问题解决的是载具全生命周期管理的信息一致性问题。适合设备软件工程师、AMHS 集成工程师、MES 接口开发以及所有被“状态对不上”折腾过的现场人员。2. E87-0301 核心模型载具状态机与消息交互很多工程师第一次接触 E87-0301 时容易把它当成一份“报文格式文档”直接去找消息模板。实际上真正撑起这套标准的是一张状态机和一套交互规则。不理解这两样东西就算把报文抄得一字不差联调时还是会乱。2.1 载具生命周期状态机把“乱跑”变成可预期E87-0301 把载具从进入设备区域到离开设备区域的整个过程拆成了几个稳定状态。常见状态包括 AVAILABLE空闲可用、IN_USE已被设备占用、IN_PROCESS内部晶圆正在加工、HOLD暂停、NOT_AVAILABLE不可用。这些状态在设备侧和主控侧必须使用同一个名字、同一个含义。比如 IN_PROCESS 指的是“载具内的晶圆正在被设备加工”此时任何搬运请求都应被拒绝而不是定义为“设备正在忙”。状态之间不是自由跳转。一个载具不能从 AVAILABLE 直接跳到 IN_PROCESS因为中间必须经历“装载到端口”这个动作同样IN_PROCESS 结束后不能直接跳到 IN_USE因为加工完必须先完成卸载确认。下面是一张我在设备软件里常用的基础迁移表当前状态触发事件下一状态允许条件AVAILABLE载具装载到设备端口IN_USE端口已确认载具 ID 已读取IN_USE开始加工IN_PROCESS载具内晶圆批次已绑定IN_PROCESS加工完成AVAILABLE卸载完成端口已释放IN_PROCESS工艺异常HOLD异常码已记录HOLD人工恢复AVAILABLE恢复确认完成任意状态设备下线维护NOT_AVAILABLE设备已锁定这张表看起来简单但现场大量翻车都出现在“跳状态”。我见过某设备的软件直接把载具从 IN_PROCESS 改成 AVAILABLE理由是“加工完了”但主控侧还没收到“卸载完成”事件于是主控认为载具还在设备里搬运系统自然不敢动它。标准里明确要求状态迁移必须伴随对应的事件上报而不是本地改一个变量就结束。这里还有一个新手容易误解的点本地状态和主控状态不必实时一样但最终必须收敛到一致。E87-0301 给出的模型是“旧状态 → 新状态”的事件上报主控收到后自己判断合法性。设备侧不用提前向主控查询“你现在的状态是什么”只要按照真实发生的动作上报即可。反向来看如果主控侧发现上报的迁移不合法应拒绝并报警而不是默默接受。2.2 消息交互模型事件上报、命令下发与事务确认E87-0301 的交互模型非常接近“订阅/上报”设备侧是状态源主控侧是决策方。设备发现载具状态变化主动上报一条事件消息例如载具状态变化、载具到达端口、载具离开端口主控需要搬运时下发一条搬运请求或传送请求设备侧执行后返回完成或错误。整个过程中双方都需要维护同一份载具状态表任一次上报漏了后续就全对不上。消息通常走 SECS/GEM 通道例如用 HSMS 传输 SECS-II 数据消息。E87 本身更关心的是消息内容而不是底层传输。消息体一般由消息头、载具 ID、旧状态、新状态、位置标识、事件时间戳组成。下面是我在设备端常用的一种字段结构实际实现可以按设备平台调整字段类型说明MESSAGE_ID字符串全局唯一消息 ID用于去重和确认CARRIER_ID字符串载具 ID全局唯一OLD_STATE枚举迁移前状态NEW_STATE枚举迁移后状态PORT_ID字符串端口或存储区标识TIMESTAMPISO8601事件发生时间建议统一 UTC这里最常见的误解是设备上报时必须携带当前主控侧的值。其实协议允许上报“旧状态 新状态”主控自己判断是否合法。因此设备侧只要按真实发生的情况上报即可不需要先查询主控的当前状态。命令下发的核心是“事务确认”。主控发起的搬运请求不能只说一句“搬过去”而必须要求设备侧在限定时间内先返回“已收到”再返回“已完成”或“已拒绝”。这个确认机制是用来区分“请求丢了”和“请求失败”的。很多现场只做了完成回报没做收到回报结果超时后主控根本不知道设备是在执行还是已经忘了这条命令。2.3 选型理由为什么是 E87 而不是自己定一套 JSON有人会问我的设备只有一台MES 也是自己写的直接定一个“载具状态 位置”的 JSON 接口不行吗短期看可以但一旦要接 AMHS、接第二个设备、接第三家搬运系统私有协议就会让每一条通道都要单独写转换层而且状态定义很容易出现“同一状态两种含义”。E87-0301 的价值在于把状态名、迁移规则、事件字段都统一了。你不需要一次性实现标准里所有消息但至少状态图和事件字段建议按它来建模。另一个理由是它与相邻标准天然配套E84 负责端口和载具的物理握手E90 负责设备内搬运控制E87 负责更上层的载具跟踪。使用同一套概念联调时沟通成本会低很多。此外E87-0301 这个版本虽然发布较早但至今仍然被大量设备商作为默认接口。很多设备控制软件已经把 E87 消息集成到标准 SECS/GEM 组件里这意味着你选 E87往往能直接复用成熟 SDK选私有协议几乎每个新设备都要从零写适配。我做项目时通常建议即便你的系统只需要“载具在哪”这一个功能也把状态机按 E87 建好未来接 AMHS 时不用返工。3. 按 E87-0301 落地最小载具管理设备端状态机与主控端消息这一章给出一个最小可用的落地路径。不需要一次性实现全部消息先把状态上报和搬运请求跑通后面的功能都是在这个骨架上加。整个落地路径分三步确定 ID 读取方式与消息通道、实现状态机与上报骨架、处理命令时序。3.1 第一步确定载具 ID 读取方式、消息通道与读取时机载具 ID 是 E87 的“主键”。设备在载具装载时通过条码扫描、RFID 读取或人工录入获得 ID之后所有消息都必须携带这个 ID。这里的关键不是“读什么技术”而是“什么时候读”。常见做法是在载具被放置到设备端口并完成物理握手后设备主动读取一次 ID并把结果缓存在本地。后续所有上报都用这个缓存避免在加工过程中反复读标签也避免读到一半标签被遮挡。读取失败时不能把载具放进流程。至少做三次重读每次间隔 500ms连续失败后把端口置为 HOLD并上报 NOT_AVAILABLE。有些 RFID 读头在金属环境下不稳定这时候要用条码扫码枪做人工兜底。我在现场遇到过读头对某个载具位置先天不识别的情况最后是靠“RFID 为主、条码辅助”双通道解决的问题。消息通道的选择上老设备通常用 HSMS 连接主控新设备如果已经有 E84 或 E90消息通道可以直接复用同一套 HSMS 链路。我比较顺的方案是把 E87 消息映射成 SECS-II 的 S6F11 事件上报和 S2F41 命令执行。这样主控侧只要有一个标准 GEM 连接就能同时处理 E84 和 E87不需要额外开端口。如果现场不让走 SECS也可以用 XML over TCP但要注意自己实现消息确认工作量大不少。3.2 第二步实现状态迁移与事件上报的最小骨架下面是一个极简的设备侧状态机骨架用 Python 表达思路实际项目中可以套到 C 或 C#。它不是标准参考实现而是帮助你理解“状态迁移 事件上报”的最小代码结构CARRIER_STATES { AVAILABLE, IN_USE, IN_PROCESS, HOLD, NOT_AVAILABLE } # 只允许标准迁移键是 (旧状态, 新状态) ALLOWED_TRANSITIONS { (AVAILABLE, IN_USE), (IN_USE, IN_PROCESS), (IN_PROCESS, AVAILABLE), (IN_PROCESS, HOLD), (IN_USE, HOLD), (HOLD, AVAILABLE), (AVAILABLE, NOT_AVAILABLE), (IN_USE, NOT_AVAILABLE), } def change_state(carrier_id, old_state, new_state, port_id): if old_state not in CARRIER_STATES or new_state not in CARRIER_STATES: raise ValueError(funknown state: {old_state} - {new_state}) if (old_state, new_state) not in ALLOWED_TRANSITIONS: raise ValueError(fdisallowed transition: {old_state} - {new_state}) # 上报前先持久化本地状态避免上报失败后丢失 local_db.update(carrier_id, new_state) # 发送 E87 事件载具状态变化 send_event( event_codeCARRIER_STATUS_CHANGE, carrier_idcarrier_id, old_stateold_state, new_statenew_state, port_idport_id, timestampnow_utc(), )这段代码的逻辑先检查状态和迁移合法性再持久化本地状态最后发送事件。注意顺序很重要——先落库再上报这样即使网络报错了本地也能查到实际状态后续主控来查询时还能恢复。很多设备侧翻车都是先上报后落库消息发出去了本地还是旧值后续命令一来就乱。这里有两个参数需要特别说明。port_id在 E87 消息里不是可选项它决定载具在设备内的物理位置必须由设备软件传入真实端口号。timestamp也要统一建议都用 UTC否则跨时区或跨系统联调时主控按时间戳排序会把事件顺序搞反。另一个容易被忽略的点是change_state()只负责状态迁移不负责搬运动作。搬运动作应放在命令处理函数里等物理动作完成后再调change_state()。3.3 第三步处理搬运请求的命令时序与去重状态上报只是“说”真正执行搬运还要接“听”。主控下发搬运请求后设备侧需要按顺序执行先检查载具当前状态是否允许搬运再执行物理动作最后上报状态变化。下面是一张用于联调的基本时序表步骤调用方动作成功响应1主控下发搬运请求携带载具 ID 和目标端口请求收到返回处理中2设备锁定载具执行搬运动作动作完成3设备上报载具状态变化旧端口→新端口主控确认4主控更新载具位置记录完成实际联调时我遇到过设备在第 2 步和第 3 步之间直接跳过上报导致主控一直认为载具还在旧位置。所以我在代码里会在执行完物理动作后强制调用一次change_state()哪怕状态值没有变化也要补一条“位置变更”事件。这是最小系统里最容易漏的一环。如果现场要求 XML 报文可以把 E87 消息映射成下面的结构。这只是常见的映射方式不建议当成标准定义关键是字段语义一致CarrierStatusChange MessageId20241115-123456/MessageId CarrierIdFOUP-0001/CarrierId OldStateIN_USE/OldState NewStateIN_PROCESS/NewState PortIdP1/PortId Timestamp2024-11-15T12:34:56Z/Timestamp /CarrierStatusChangeMessageId用于去重PortId是设备内端口Timestamp统一用 UTC。这里要注意设备重发同一事件时必须带同一个MessageId而不是重新生成一个。否则主控会认为是新事件进而把它当成一次新的状态迁移处理造成状态重复或回滚。4. E87-0301 联调必调参数Carrier ID、超时重传与并发优先级这个章节是联调时最耗时间的部分。标准没有把所有数值写死但现场必须提前约定否则双方工程师会在“该等几秒”“该信谁的”这些问题上反复扯皮。4.1 Carrier ID长度、字符集、校验位与统一转换Carrier ID 是整套系统的主键最容易出问题。典型约定是上限 32 个字符使用 ASCII 可打印字符不允许空格和中文条码/RFID 读取时建议统一为十六进制字符串或原样字符串。注意同一工厂内不同设备可能读出不同格式有的读条码会带校验位有的不带。解决方式是接入主控时做一次规则转换并把转换规则写死到设备配置文件。下面是我在项目里常写的字段约定表参数推荐值说明最大长度32超长必须告警不能静默截断字符集ASCII 0x21-0x7E排除空格、控制字符校验CRC16 或末尾校验位用于 RFID 读取误码检测显示格式大写避免大小写不一致导致匹配失败联调时建议在报文里打印每个字符的十六进制很多“乱码”其实是 0x20 空格混进去了。还有一次某个读头把标签里的十六进制字符串直接当成 ASCII 文本发送结果主控收到的是一串乱码。后来约定RFID 读出的原始数据一律转为大写十六进制再作为 Carrier ID两边都按 hex 处理问题才消失。4.2 事件上报的超时与重传确认、退避与去重窗口E87 的事件上报在实际链路里可能丢包所以需要一个确认机制。常见做法是设备发送状态变化后若在 N 秒内未收到主控确认则重发重发次数超过 M 次就报警并置本地 HOLD。不要无限重发否则主控会收到重复消息进一步造成状态冲突。参数经验值适用场景首次超时5s本地以太网重发次数3 次一般 HSMS 链路重试间隔2s / 5s / 10s递增退避超时后动作载具置 HOLD避免带错误状态流动如果你发现链路抖动比较多超时值可以放宽到 15s但重发次数不要增加因为主控侧往往有 3 次去重窗口。重发时一定要带上原消息 ID这样主控可以直接丢弃重复包。主控侧的确认消息也要包含原消息 ID否则设备不知道确认的是哪一条。这个“确认必须带上下文”的规则经常被两边各自写成“反正就是确认一下”结果一丢包就乱套。4.3 并发冲突时的优先级规则谁先谁后必须写进规范多台设备同时操作同一个载具时E87 的“旧状态→新状态”模型能发现冲突但不能自动解决。你需要提前约定优先级。我常用的规则是设备侧物理动作完成后上报的状态优先于主控侧逻辑推测的状态。主控下发搬运请求后设备必须在 5 秒内返回“已收到”或“拒绝”不能沉默。人工干预HOLD优先级最高任何自动流程遇到 HOLD 都必须停止并上报。把这个规则写进接口规范而不是写在代码注释里。因为现场联调时双方工程师往往各执一词有了白纸黑字才能快速定位问题。在并发场景下我遇到过主控的搬运请求和设备的状态上报同时到达设备先执行了搬运再上报状态但主控认为搬运还没开始就拒绝了请求。解决思路是把“命令确认”和“状态上报”串行化设备收到命令后先发“已收到”再执行执行完发“完成”和“状态变化”主控只有在收到“已收到”后才认为命令生效。这个约定必须在联调前定好否则两边各写一套线程模型很难排查。5. E87-0301 实施避坑5 个让生产停线的状态同步现场这一章是血泪经验汇总。每一条都是现场真实遇到过的现象、原因、解决对应写清楚方便你复现时直接对照。这些问题单独看都不复杂但它们叠加起来足以让一条线停摆半天。5.1 现象载具被移走了系统还标记 IN_PROCESS有一次机械手完成加工后把载具放回端口但设备软件没有先上报“加工完成”而是直接进入空闲状态。主控侧收到的还是旧的 IN_PROCESS于是搬运系统一直等着产能停了一个多小时。原因设备软件把“本地状态更新”和“上报主控”分成了两个动作本地完成了但上报失败失败后也没有重试。解决把状态持久化和上报封装成同一个事务。在代码里change_state()必须先落库再上报上报失败时启动重试线程同时在本地状态上打标记“待同步”。主控侧也可以加一个轮询接口发现载具状态长时间不变就主动查询设备。关键教训不能因为上报失败就假装没发生过状态变化。5.2 现象搬运请求被重复执行载具来回搬动主控因为超时重发了搬运请求设备侧收到两次结果载具被来回搬动了一次。幸好没有撞坏硬件但晶圆批次被打乱最后只能全批重查。原因设备侧命令处理没有做幂等。E87 消息里有消息 ID 或参考号但设备侧没有保存已处理的 ID 列表。解决在命令接收入口处维护一个“最近已处理消息 ID”集合长度保留 1000 条。收到重复消息 ID 时直接返回上一次的成功响应不重复执行物理动作。注意集合本身要加锁多线程下容易出并发问题。这个集合不需要永久保存保留最近半小时的会话足够覆盖超时重发窗口。5.3 现象Carrier ID 读到乱码字符集对不上RFID 读取显示正常但主控侧看到的是“0x0A”等字符载具匹配失败。用串口抓包发现设备软件把 RFID 标签的二进制数据直接当成字符串组装进报文导致控制字符混入。原因标签厂商返回的数据本身是二进制没有做 ASCII 安全转换。解决在设备侧把二进制数据统一转成十六进制字符串再作为 Carrier ID 使用。同时约定主控侧也按十六进制解析。上线前在测试环境用全量标签样本跑一遍把每个字符的 hex 值打印出来核对。尤其注意标签里可能带换行符 0x0A 和回车符 0x0D这两个字符进了报文就是隐形炸弹。5.4 现象E84 握手和 E87 状态查询互相死锁设备端口正在做 E84 载具装载握手同时主控发来 E87 状态查询。设备软件是单线程处理消息结果握手流程被卡住状态查询超时主控认为设备已经死了又触发了系统级复位。原因E84 和 E87 共享同一消息解析线程而 E84 握手要求在较短时间内完成响应不能被长耗时操作阻塞。解决把消息解析做成多线程E84 相关报文在优先线程处理E87 的查询响应走普通线程。更保守的做法是在没有彻底理清线程模型之前设备侧先对 E84 消息采用中断级处理E87 消息全部排队避免互相阻塞。这里要特别小心不要让 E87 的日志打印阻塞 E84 的响应否则还是死锁。5.5 现象设备重启后双方状态不一致设备断电重启后本地数据库里的载具状态停留在重启前但实际载具可能已经被人为移走。主控侧重新连接设备后双方都认为自己的数据是对的结果同一个载具出现两个版本。原因设备没有在启动时做状态同步主控也没有主动查询设备的所有载具。解决在设备上线时增加一个“全量载具状态同步”流程。设备重启后先扫描端口上的实际载具再上报全部载具当前状态主控收到后与自己的数据比对不一致的置 HOLD 并报警。这个流程在 E87 实施里属于非常基础的环节但很多团队都会忘。不要依赖“重启后设备会自己恢复”一定要主动扫一遍物理端口的实际载具。6. E87-0301 进阶技巧用状态日志回溯替代“黑匣子”联调到最后你会发现最难的不是写代码而是问题发生后怎么快速定位。E87 相关的状态不一致问题往往只在特定时序下出现现场没有复现条件时就只能靠日志回溯。这里分享一个我一直在用的习惯把每个载具 ID 的状态迁移当作一条不可变记录统一写到独立日志文件并附带完整的消息上下文。我通常会让设备端在每次change_state()时输出一行结构化日志包含时间戳、载具 ID、旧状态、新状态、端口、消息 ID、是否上报成功。同时把主控侧的接收日志也按同一格式落盘。这样联调时只要把两份日志按载具 ID 排序就能看出哪一边漏了消息、哪一边发了旧状态。前一阵遇到一个载具“凭空消失”的问题最后就是靠日志发现设备侧某次上报用了旧的 CARRIER_ID主控侧把它当成另一个载具处理了导致匹配混乱。进阶一点可以在设备端做个启动自检把日志里的状态迁移按 E87 规则重新播放一遍遇到非法跳转直接打印告警。这个自检不需要额外平台支持写个小脚本就能跑。但它的价值很大能让“状态冲突”在变成产线事故之前就被发现。我现在每完成一个 E87 项目都会顺手做两件事把双方日志格式统一并写一个状态回放脚本。说实话这个脚本救过我很多次比在现场抓包省力得多。如果你刚接触 E87-0301先从状态机开始再把超时、ID 编码和启动同步这三件套配好就已经避开了大部分常见的坑。剩下的就是用日志把自己武装到牙齿出了问题不靠猜靠数据回放。希望这套经验能帮到正在与载具状态较劲的你。本文还有配套的精品资源点击获取