把 EtherCAT 初始化从 FPGA 搬到 ARM:命令通道的接口设计与避坑
摘要:在 ARM+FPGA 架构的 EtherCAT 主站中,把从站初始化序列从 FPGA 状态机搬到 ARM,是"换伺服不用重综合"的关键一步。但这不等于可以拆掉 FPGA 侧的帧传输逻辑。本文讲清楚:搬迁之后 FPGA 还剩什么职责、命令通道的接口该怎么设计、以及实践中容易踩的坑。本文不贴源码,只讲设计思路与接口约定。
一、为什么要搬:状态机 + 模板 ROM 的困境
一种常见的 EtherCAT 主站 FPGA 实现,初始化部分长这样:
- 一个庞大的状态机,每个状态对应一条配置命令
- 一个报文模板 ROM,把每条命令的字节预先烧进去
- 一套逐轴递推逻辑,遍历链路上的所有从站
- 一个仲裁器,决定"现在该发初始化帧还是周期帧"
这套设计能用,但有个硬伤:从站型号相关的信息被烧进了 FPGA。
换个品牌的伺服,PDO 映射内容变了、邮箱缓冲地址变了、SDO 序列变了、SM/FMMU 配置值变了------全在 ROM 和状态机里。结果就是换一次伺服重综合一次 FPGA。
于是很自然的想法是:把这些"语义"搬到 ARM,用 C 常量表描述,FPGA 只保留通用的传输能力。
二、搬走之后,FPGA 还剩什么
这是最容易想岔的地方。搬走初始化流程不等于搬走帧传输。
ARM 手里握着配置流程,但它离 MII 引脚之间还隔着五个物理上绕不过去的坎:
| # | 职责 | 为什么 ARM 做不到 |
|---|---|---|
| 1 | 字节序列化 | ARM 通过外部总线写一次寄存器的耗时也在 几十 ns 量级。光喂字节就吃掉大量的 CPU |
| 2 | CRC32 | 必须逐字节在 FPGA 时钟域流水线计算 |
| 3 | 帧序号生命周期 | 序号要与应答配对,且必须钳在安全区间 |
| 4 | 微秒级超时 | ARM 软件定时通常只到毫秒级,且漏检"帧根本没回来"这种最需要重试的情况 |
| 5 | 与周期帧争总线 | 帧级互斥,ARM 不应感知总线占用状态 |
这五条与"谁实现配置流程"完全无关。FPGA 侧保留的模块,本质上是一个"网卡驱动"------ARM 是应用程序,决定发什么包;驱动负责把包送上线路。
它明确不管的事:不知道某个寄存器偏移代表什么、不实现设备行规状态机、不数轴数、不参与周期过程数据帧生成、不做 CRC、不加前导码。
三、帧格式:先把字节位置搞清楚
接口时序建立在一个基准上:发送组装模块有一个字节计数器,逐字节驱动发送。
| 计数器区间 | 内容 |
|---|---|
| 前 7 个 | 前导码 |
| 第 8 个 | 帧起始定界符 SFD |
| 接下来 12 个 | MAC(点对点直连时通常不校验) |
| 再 2 个 | EtherType |
| 再 2 个 | EtherCAT 帧头(含长度字段) |
| 之后 | 数据报(datagram)区 |
具体工程中 datagram 的起始计数值、接收侧计数器的换算关系,需要用逻辑分析仪实测确认。建议把这类数字全部做成参数,实测后改一个数字即可,不用改 RTL。
3.1 周期帧里的固定开销
周期帧长度公式里通常有一项"固定开销",指的是几条固定 datagram:一条空操作、一条用于 DC 时间心跳的地址映射写、一条用于读取错误计数。它们的长度与轴数无关,每轴再挂一条逻辑读写 datagram。
3.2 两个"长度"别混
这是最容易出错的概念。以一条写 2 字节数据的配置命令为例:
| 概念 | 决定什么 |
|---|---|
| 发送字节数 | CRC 起点、实际吐多少字节 |
| 长度字段 | 帧头里填的值,ESC 用它判断帧边界 |
发送字节数来自以太网最小帧约束:MAC + EtherType + 载荷 + FCS 至少 64 字节,反推载荷下限。所以短命令也要补零凑够。
而长度字段按规范应是真实数据报长度(头部 + 数据 + 工作计数占位)。填含填充的值也能工作(ESC 把填充区当空操作解析),但会多花解析时间。
建议做成参数可切换,用抓包实测确认哪种能通过从站校验。
四、接口设计:上下游信号
命令通道模块的定位是:与周期帧数据源并列,挂在发送组装模块的多路选择器上,不直连发送模块。
4.1 上游(谁驱动它)
| 来源 | 信号类别 | 说明 |
|---|---|---|
| ARM | 模式选择 | 参数模式(FPGA 拼帧)/ 原始模式(ARM 填字节) |
| ARM | 命令码 | EtherCAT 命令码原样透传,不做译码 |
| ARM | 站地址 / 寄存器偏移 | 均为小端 |
| ARM | 数据长度 / 数据 | 参数模式下有字节数上限 |
| ARM | 触发信号 | 置位触发,完成后必须清除 |
| 仲裁器 | 授权信号 | 本帧获准发送 |
| 发送模块 | 字节计数 / 发送使能 | 当前发到第几个字节 |
| 接收模块 | 接收字节流与有效指示 | 用于捕获应答 |
4.2 下游(它驱动谁)
| 目的 | 信号类别 | 说明 |
|---|---|---|
| 仲裁器 | 发送请求 / 预计占用时间 | 供调度判断能否插入 |
| 发送模块 | 数据字节流 / CRC 起点 | 与同步帧字节流同级进选择器 |
| 接收模块 | 帧序号 | 应答配对标签 |
| ARM | 忙 / 完成 / 工作计数 / 数据 | 握手与结果 |
4.3 两种模式的分工
| 模式 | 谁组装帧 | 覆盖场景 |
|---|---|---|
| 参数模式 | FPGA | 状态切换、DC 配置、单条同步管理器配置、FMMU 配置 等高频短写 |
| 原始模式 | ARM 填字节,FPGA 只覆写帧序号 | 邮箱长帧、一帧多数据报、非标长度 |
为什么参数模式的数据上限要能容纳一个完整 FMMU 条目 :FMMU 配置条目在 ESC 里占 16 个连续字节(逻辑起始地址 4 + 映射长度 2 + 起始位 1 + 结束位 1 + 物理起始地址 2 + 物理起始位 1 + 类型 1 + 激活 1 + 保留 3)。这是设备行规的硬规定。如果数据寄存器只开 8 字节,FMMU 配不了,从站进不了安全运行态。
原始模式存在的意义是兜底:参数模式的上限是从现有配置序列反推的,不是协议上限。换一个型号若冒出超长配置写,参数模式直接废掉,只能回炉重综合。原始模式是那张底牌,成本只是一些触发器。
五、仲裁:两个时间域之间的隔离层
只有一条以太网、一个 PHY,同一时刻只能发一帧。而两个请求源活在完全不同的时间观里:
| 周期节拍发生器 | 命令通道 | |
|---|---|---|
| 时间观 | 绝对时间域(算好的发帧时刻) | 事件域(ARM 什么时候写寄存器) |
| 节奏 | 严格等间隔 | 完全异步 |
| 被打断的后果 | 参考时钟不刷新,超时后同步告警 | 只是慢一点 |
仲裁器的核心价值不是"公平分配带宽",而是透明性------让周期发生器完全不知道命令帧的存在。不做的后果,按隐蔽程度排序:
- 两路同时驱动选择器 → 乱码帧,但 CRC 是对的(实时算的)。从站收到自洽但内容错误的帧,工作计数异常但不报错。这是最难查的故障类型。
- 请求脉冲丢失 → 请求是单周期脉冲,命令帧在途时没人接,整个周期被吞掉。
- 命令帧的应答去驱动了周期帧的完成信号 → 节拍发生器以为自己的帧回来了,下次发帧时刻推进错乱,表现为周期抖动、锁定反复丢失。
核心逻辑只有三件事:帧级互斥(不可抢占)、请求脉冲锁存、完成回执的正确路由。
顺带一提:如果被推迟,会不会造成周期累积漂移?取决于周期发生器的实现。若它用"上次目标时刻 + 周期"累加,则推迟只影响一拍、不累积;若用"当前时刻 + 周期",则每次推迟都带来偏移。前者才能让简单的帧级互斥成立,动手前先看清楚。
六、实践中容易踩的坑
坑 1:接收侧偏移换算多算一轮
接收侧的计数器与数据报内偏移之间只有一个换算基准。常见错误是把"数据域起点"和"数据报内偏移"叠加使用,等于把报头长度数了两遍。
后果极隐蔽 :工作计数抓到填充区的零,ARM 读到工作计数等于 0,判定"命令没执行"------每条命令都报失败,但从站其实执行成功了。
对策:确立唯一换算基准,用几个已知锚点(如帧序号位置、数据域起点)交叉验证一遍。
坑 2:脉冲型完成信号在状态切换中丢失
若仲裁器是"收到应答后才释放总线"的设计,那么应答脉冲必然发生在发送状态期间。而状态机在发送态只看"总线释放",不看应答脉冲------等进入等待态时脉冲早已消失。
后果:数据其实抓到了,但 ARM 看到的是"超时 + 工作计数为零"。
对策 :把脉冲锁存成电平,状态机在请求/发送/等待任一状态都能看到。
坑 3:应答没有按帧序号过滤
周期帧一直在跑,若接收侧只判断"帧结束"就捕获数据,周期帧的字节会混进命令的结果寄存器。对策:模块自己在接收流里比对接受到的帧序号,不匹配就不写入。这样接收模块一行都不用改。
坑 4:结果寄存器跨命令残留
新命令开始时若不清空结果寄存器,本帧没抓到工作计数时,ARM 读到的是上一条命令的值 ------把失败判成成功。对策:在触发的那一拍清空,而不是在空闲态每拍清(后者会让 ARM 先撤触发再读数据时读到全零)。
坑 5:帧序号必须钳位
周期帧的序号通常占用高段区间。命令帧的序号若越过边界,就会撞进周期帧区间------周期帧的应答会被误判成命令应答。多轴场景下命令总数达数千,单字节序号必然回绕,钳位是必须的。建议加仿真断言防越界。
坑 6:完成信号只维持一个时钟
若完成标志只拉高一个时钟就清零,而 ARM 读一次外部总线要好几个时钟,永远读不到 。更糟的连带后果:ARM 读不到完成标志就不撤触发信号,模块回到空闲态后立即重新触发,帧一帧发下去,忙标志永久为 1,ARM 彻底挂死。
对策:完成态停在原地等 ARM 撤触发信号,形成完整握手。
坑 7:帧头长度字段不该由 ARM 生成
最硬的理由:周期帧根本没有 ARM 参与------长度取决于上电时测出的从站个数,还会随从站进入运行态逐个增长。这条路必须 FPGA 算。
次级理由是避免两处真相源:长度是数据报长度的派生量,让 ARM 再算一遍写进另一个寄存器,两者可能不一致,表现为"某些命令偶发整帧丢失"。
对策:由 FPGA 从已锁存的数据报长度派生,两者物理上不可能不一致。
坑 8:周期配置的单位陷阱
节拍发生器的周期输入单位可能是纳秒 ,而旧设计的周期寄存器可能是时钟计数值。沿用旧值会让节拍差一个时钟周期对应的倍数。写入前确认单位。
坑 9:周期发生器的复位时机
若周期发生器在 DC 同步完成前就开始跑,而参考时间恒为零,超过阈值会锁存一个同步告警 。而帧有效标志的条件里通常包含"无告警"------即使后来同步好了,帧有效标志仍可能保持无效,只能靠软件清告警。
更硬的:周期发生器一跑起来就发请求,会与同步帧抢同一个选择器,直接打断同步过程。
对策:接"同步完成后才放开"的复位,不要接全局复位。代价只是预热晚一点。
坑 10:结果寄存器、同步管理器地址的字节序
同步管理器地址常以 16 位整体拼进报文(高字节先出),而 EtherCAT 地址字段是小端。填反的表现是初始化卡在同步管理器配置步或进不了运行态。
坑 11:M 位与多数据报
数据报头里有一位表示"后面还有数据报"。单数据报必须清零;周期帧有多条数据报,这一位要正确串联,最后一条清掉。
原始模式多数据报时:只有第一个数据报的序号由 FPGA 托管(它是应答配对的会话标签且必须钳位),后续数据报的序号和 M 位由 ARM 自己填。
七、诚实的能力边界
这个架构能实现什么、不能实现什么,必须说清楚:
| 维度 | 举例 | 归谁 | 重综合 |
|---|---|---|---|
| 从站品牌/型号 | 换品牌、改 PDO 映射内容、改邮箱地址、改 SDO 序列 | C 常量表 | 不用 |
| 拓扑/规模 | PDO 总长度变了、轴数超上限 | FPGA 参数 | 要 |
第一维是这个架构真正的收益:FPGA 里不再有任何一条"某个牌子特有"的信息。
第二维做不到,因为周期帧结构压根不在命令通道里,而在发送组装模块。PDO 长度是编译期常量,同时决定周期帧长度、每轴数据报布局、接收侧解析偏移。
一个好消息 :逻辑地址步长通常留有余量(比如步长 32 而实际只用到十几字节),所以PDO 长度小幅增长时,逻辑地址不用动,改动集中在发送模块的帧长计算与接收模块的解析偏移两处。把 PDO 长度做成寄存器即可一次改到位。
轴数倒是自动识别的------硬件通过广播写的工作计数数出从站个数,加几个轴 FPGA 一行不用改。真正的上限来自内部位宽;要扩到更大规模时注意计数器位宽,其中某个窄位宽计数器在接满时可能读出零,表现为"上电完全无反应"。
容量也要算:帧长 = MAC + EtherType + 帧头 + 数据报区 + FCS,轴数多、PDO 长时会逼近 MTU。建议软件初始化时算一次帧长,超了就报配置错误,别让它静默失败。
八、总结
回看整个设计,正确的分工是:
ARM 拥有:帧的全部语义(命令、地址、数据、几个数据报、多长)
FPGA 拥有:字节上线路、CRC 起点、帧序号、超时、与周期帧交错
最后给一条上板建议:先做影子验证。保留原状态机不动,新模块的输出只接逻辑分析仪,让 ARM 发几条典型命令,与硬件状态机同一步的帧逐字节比对。确认帧偏移、CRC 起点、工作计数位置这几个参数后再切换选择器。这三个数都做成参数的话,实测后改一个数字就行。
(本文整理自一个 ARM+FPGA EtherCAT 主站项目的重构实践。文中所有字节偏移、计数器基准、缓冲区尺寸等均为示意,需根据具体工程实测确认。)