首页 / 驱动算法 / EtherCAT专题

EtherCAT SM / FMMU 详解:从站内存的两层管家

EtherCAT 帧在网络上"飞行"时,每个从站怎么知道自己该拿哪段数据、拿完放哪、什么时候通知应用?答案是 ESC 内部的两层内存管家:FMMU(Fieldbus Memory Management Unit)把帧里的逻辑地址映射到从站本地内存,SM(Sync Manager)管理本地内存缓冲区与 MCU/PDI 的交接和同步。本文讲清 FMMU 的命中检查与寄存器配置、SM 的邮箱/过程数据通道与缓冲模式、两者分工拼出的完整数据路径、PreOp/SafeOp 阶段的配置时机,配"一帧 LRW 命中全网"交互演示与可编译 C 初始化模块。

1. 为什么需要 SM 和 FMMU:帧在飞,数据要有"门牌号"

EtherCAT 数据报是流水线式转发的:一个 LRW 帧从主站出发,依次穿过每个从站,每个从站只有极短的处理时间。两个现实问题必须由硬件解决:

  • 帧里哪一段是我的?——一帧 LRW 携带的逻辑地址区段可能覆盖整条线上所有从站的数据。从站必须快速判断"这段逻辑地址是否命中我的区间",命中就截取/写入。这是 FMMU 的活:逻辑地址 → 本地内存映射 + 命中检查;
  • 拿到的数据放哪、什么时候通知 MCU?——ESC 截下的数据要放进一个缓冲区,再通过 PDI 交给 MCU;MCU 写好的反馈也要从缓冲区被下一帧取走。缓冲区不能乱放、读写不能互相踩。这是 SM 的活:本地内存缓冲区的管理、握手与事件。
✅ 一句话分工

FMMU 管"帧 ⇄ ESC 内存"(网络侧),SM 管"ESC 内存 ⇄ 应用"(本地侧)。两者在 ESC 内部接力,共同完成"一帧数据从主站到 MCU 对象字典"的硬件级搬运——全程不占主站/从站 CPU。

2. FMMU:逻辑地址 → 本地内存的映射器

每个 FMMU 定义一段逻辑地址区间与一段本地内存的对应关系。帧里的逻辑地址落在区间内 → 数据被自动映射进(或读自)对应的本地地址。关键字段:

寄存器字段作用典型值
逻辑起始地址(4B)FMMU 负责的逻辑地址区间的起点由主站按全网规划分配,如 0x10000000
逻辑长度(2B)区间长度(字节)= 该从站过程数据长度(如 10 字节)
逻辑起始位 / 结束位位级偏移(支持非字节对齐)一般 0 / 7(整字节)
物理起始地址(2B)映射到 ESC 本地内存的哪一段= SM2/SM3 的缓冲区地址,如 0x1000
读 / 写使能允许主站读(从站→主站)还是写(主站→从站)输出 PDO:写使能;输入 PDO:读使能
激活位FMMU 是否参与命中检查1=激活(PreOp 后使能)

一个从站通常配 2 个 FMMU:FMMU0 管输出 PDO(主站写:逻辑区间 → SM2 缓冲区)、FMMU1 管输入 PDO(主站读:SM3 缓冲区 → 逻辑区间)。

3. FMMU 命中检查:一帧 LRW 怎么喂全网

主站发一个 LRW 数据报,逻辑地址区间覆盖 N 个从站的数据段。帧经过每个从站时,ESC 并行检查:

命中条件(逐 FMMU 检查)

logical_start ≤ 帧内目标逻辑地址 < logical_start + logical_length

命中 → 按映射把帧数据写入本地物理地址(写使能 FMMU),并把 WKC 加 1;未命中 → 原样转发下一从站。所有从站的 FMMU 区间在逻辑地址空间里不重叠,一帧就能精准喂到每一个

这就是 EtherCAT"一帧控全网"的物理基础:没有 FMMU,每个从站都得等主站逐个点名(节点寻址),数据就飞不快。逻辑寻址 + FMMU = 一次广播,各取所需。

4. SM:本地内存 ↔ 应用的交接管家

SM 管理一段本地内存缓冲区,并保证主站侧写、应用侧读(或反向)互不冲突。它维护写指针、读指针和状态位:

字段作用
物理起始地址 / 长度缓冲区在 ESC 本地内存的位置与大小
通道类型邮箱输入(SM0)/ 邮箱输出(SM1)/ 过程数据输入(SM2)/ 过程数据输出(SM3)
缓冲模式单缓冲 / 三缓冲(过程数据常用三缓冲,见第 5 节)
控制 / 状态写指针、读指针、新数据标志、校验错误、中断使能
事件新数据到达 → SM 事件/中断 → MCU 经 PDI 取走

SM 是"握手套件":主站写完 SM2 置新数据标志,MCU 读到后清标志;反过来 MCU 写完 SM3,主站下一帧读走并确认。握手没做对,就是"偶发丢数据"——最常见的一类从站 bug。

5. 邮箱与过程数据:SM0~SM3 四种通道

SM通道方向内容典型缓冲
SM0邮箱输入主站 → 从站CoE/EoE 请求(SDO 下载、FoE)单缓冲或 3 缓冲
SM1邮箱输出从站 → 主站CoE/EoE 应答(SDO 上传、紧急事件)单缓冲或 3 缓冲
SM2过程数据输入主站 → 从站RxPDO(目标值、控制字)3 缓冲
SM3过程数据输出从站 → 主站TxPDO(实际值、状态字)3 缓冲
✅ 三缓冲(Triple Buffer)为什么是过程数据标配

主站和 MCU 各自独立读写,单缓冲会互相踩(主站覆盖 MCU 未取走的数据)。三缓冲 = 三个缓冲轮转:主站写一个、MCU 读一个、还有一个做交接缓冲区——读写永不冲突、数据永远是最新的完整帧。代价是多一倍内存和一次额外拷贝,但对 125μs 周期的伺服从站,值。

6. FMMU + SM 完整数据路径:一趟 PDO 的硬件旅程

把第 2~5 节串起来,一次输出 PDO(主站 → 从站)的硬件之旅:

步骤谁干动作
1主站把 0x607A 目标位置等写入过程数据缓冲,放进 LRW 帧
2ESC FMMU0帧到达,命中检查:逻辑地址 ∈ FMMU0 区间 → 数据写入 SM2 缓冲区
3ESC SM2置"新数据"标志,产生 SM 事件/中断(若使能)
4MCU(PDI)中断或轮询发现新数据 → 经 SPI/并行读走缓冲 → 按 PDO 映射拆包进对象字典
5MCUFOC 消费目标值,写反馈进 SM3 缓冲
6ESC FMMU1 + 帧返回下一帧(或同一帧返回路径)经 FMMU1 把 SM3 数据映射进逻辑区间,主站读走,WKC +2

注意步骤 2/3 是纯硬件:ESC 在帧经过的几个时钟周期内完成命中、搬运、置标志——这就是 EtherCAT 从站能在一根线上串几十个、每个只引入几十 ns 延时的原因。

7. 配置时机:PreOp / SafeOp 阶段写入

SM 和 FMMU 不是出厂就有的,是主站在状态机迁移时写进 ESC 寄存器的:

状态配置动作
Init从站上电,ESC 读 EEPROM 配置字,等主站
PreOp主站配置邮箱通道 SM0/SM1(地址、长度、模式)+ FMMU(如需);CoE 邮箱开始可用,传 SDO、下 PDO 映射
SafeOp主站配置过程数据 SM2/SM3 与 FMMU(逻辑地址分配、激活)、DC 参数;过程数据开始流动,从站输出保持安全值
OP全速运行:每周期 LRW 交换 PDO

所以"改 PDO 映射"的完整动作链是:改 0x1600/0x1A00 → 改 0x1C10/0x1C11 → 重新走 Init→PreOp→SafeOp→OP,让主站重配 SM 长度和 FMMU 区间(详见《EtherCAT PDO 详解》)。

8. 交互演示:一帧 LRW 命中多个从站

拖动数据报,看 FMMU 命中检查如何"各取所需"——逻辑地址空间被 4 个从站各占一段,一帧覆盖全部:

FMMU 命中检查演示:逻辑地址空间分段与 LRW 广播
命中从站 1(输出 PDO)→ 写入 SM2 缓冲,WKC +1

观察要点:

  • 4 个从站的 FMMU 区间在逻辑地址空间里连续不重叠,各自命中自己的数据段;
  • 同一帧 LRW 的地址区间覆盖全部从站——每站命中一次,一次广播完成全网交换(这就是"一帧控全网");
  • 区间边界处命中判断精确到字节:落在别人的区间里,数据就原样流过,谁也不碰。

9. 代码实现(C语言,可编译模块)

从站栈在 PreOp/SafeOp 时写 SM/FMMU 寄存器(按 ET1100 风格偏移,其他 ESC 改基址宏):

/* esc_cfg.c — SM/FMMU 寄存器初始化模块(精简) */ #include <stdint.h> /* ESC 寄存器偏移(ET1100 风格,以手册为准) */ #define REG_SM_BASE 0x0800u /* SM 配置区:每个 8 字节 */ #define REG_FMMU_BASE 0x0600u /* FMMU 配置区:每个 16 字节 */ /* SM 通道类型(控制寄存器低 4 位) */ #define SM_MBOX_WR 0x01u /* 邮箱输入(主站写) */ #define SM_MBOX_RD 0x02u /* 邮箱输出(从站写) */ #define SM_PDO_WR 0x03u /* 过程数据输入(主站写) */ #define SM_PDO_RD 0x04u /* 过程数据输出(从站写) */ typedef struct { volatile uint16_t* base; } esc_t; static inline void wr16(esc_t* e, uint32_t off, uint16_t v) { e->base[off / 2] = v; } /* 配置一个 SM 通道 */ void sm_cfg(esc_t* e, int idx, uint16_t phys_addr, uint16_t len, uint8_t mode, uint8_t irq_en) { uint32_t b = REG_SM_BASE + idx * 8; wr16(e, b + 0x00, phys_addr); /* 物理起始 */ wr16(e, b + 0x02, len); /* 长度 */ wr16(e, b + 0x04, (uint16_t)mode | (irq_en ? 0x4000u : 0)); /* 控制:类型+中断 */ wr16(e, b + 0x06, 0x0001); /* 激活 */ } /* 配置一个 FMMU:逻辑区间 → 物理段 */ void fmmu_cfg(esc_t* e, int idx, uint32_t logical_start, uint16_t logical_len, uint16_t phys_addr, uint8_t rd_en, uint8_t wr_en) { uint32_t b = REG_FMMU_BASE + idx * 16; volatile uint32_t* p32 = (volatile uint32_t*)(&e->base[b / 2]); *p32 = logical_start; /* 逻辑起始地址(4B) */ wr16(e, b + 0x04, logical_len); /* 逻辑长度 */ wr16(e, b + 0x06, 0x0000); /* 起始位 0 / 结束位 7(整字节) */ wr16(e, b + 0x08, phys_addr); /* 物理起始 */ wr16(e, b + 0x0A, (rd_en ? 0x01 : 0) | (wr_en ? 0x02 : 0)); /* 读/写使能 */ wr16(e, b + 0x0C, 0x0001); /* 类型:物理映射 + 激活 */ } /* SafeOp 阶段:典型伺服从站一次配齐(邮箱+PDO+FMMU) */ void esc_safeop_cfg(esc_t* e) { sm_cfg(e, 0, 0x0200, 128, SM_MBOX_WR, 1); /* SM0 邮箱输入 */ sm_cfg(e, 1, 0x0280, 128, SM_MBOX_RD, 1); /* SM1 邮箱输出 */ sm_cfg(e, 2, 0x0300, 10, SM_PDO_WR, 1); /* SM2 输出 PDO(10B) */ sm_cfg(e, 3, 0x0310, 10, SM_PDO_RD, 1); /* SM3 输入 PDO(10B) */ fmmu_cfg(e, 0, 0x10000000u, 10, 0x0300, 0, 1); /* 主站写 → SM2 */ fmmu_cfg(e, 1, 0x1000000Au, 10, 0x0310, 1, 0); /* SM3 → 主站读 */ }

注:逻辑地址分配由主站统一规划(各从站区间不重叠),从站只需在 SafeOp 阶段把主站下发的值写进寄存器。邮箱 SM0/SM1 在 PreOp 先配,过程数据 SM2/SM3 与 FMMU 在 SafeOp 激活。

10. 工程避坑清单

坑现象对策
SM 长度与映射不匹配主站报过程数据长度错误 / WKC 异常SM2/SM3 长度 = ⌈Σbitlen/8⌉,改映射必同步改 SM
FMMU 区间重叠两个从站收到同一份数据,互相污染逻辑地址由主站统一分配,严禁手写重叠区间
读/写使能反了输入输出数据全错或 WKC 不变输出 PDO(主站写)用写使能 FMMU,输入 PDO(从站写)用读使能
SM 握手没清标志偶发丢数据/重复数据MCU 读走 SM2 后清"新数据"标志;写 SM3 后确认主站读走再覆盖
邮箱用单缓冲被踩SDO 偶发失败/校验错邮箱改 3 缓冲;或保证主站与 MCU 访问窗口不重叠
SafeOp 后改 FMMU 不重启新配置不生效、数据错位SM/FMMU 修改后必须重新走 Init→…→SafeOp 状态机
非字节对齐 FMMU 位偏移数据错位、偶发漂移位级映射仅在特殊场景用,过程数据一律整字节对齐

一句话收尾:FMMU 让"一帧喂全网"成为可能,SM 让"数据安全交接"成为可能——前者解决"谁拿",后者解决"谁用",这两层管家配好,从站才真正跑在 EtherCAT 的硬件实时通道上。