从零开发 EtherCAT 主站(七):SOEM PDO 映射源码解析,深入理解 SyncManager、FMMU 与 IOMap
文章目录
- 从零开发 EtherCAT 主站(七):SOEM PDO 映射源码解析,深入理解 SyncManager、FMMU 与 IOMap
- 一、从 `simple_ng.c` 看 PDO 映射入口
- 二、为什么需要 PDO Mapping?
- 三、SOEM PDO 映射整体流程
- 四、EtherCAT 从站内部结构:ESC、SyncManager 与 FMMU
- 五、Logical Address:EtherCAT 为什么不用设备地址通信?
- 六、FMMU:从 Logical Address 到 ESC RAM
- 七、`ecx_config_map_group()` 源码分析
- 八、`outputs` 和 `inputs` 指针到底是什么?
- 九、完整流程总结
- 十、本章总结
上一篇文章中,我们分析了 SOEM 主站初始化流程,了解了 EtherCAT 主站如何完成网卡初始化、从站扫描、设备信息读取等步骤,并最终进入 PRE-OP 状态。
但是,主站虽然知道了网络中有哪些从站,却还不知道如何与它们交换实时数据。
以伺服驱动器为例,我们希望周期性地发送控制字、目标位置、目标速度,同时读取状态字、实际位置等信息。这些数据如何进入 EtherCAT 通信帧?从站又如何知道应该读取哪些数据?
这就是PDO(Process Data Object)映射需要解决的问题。
本章将结合 SOEM 源码(ec_config.c、simple_ng.c),深入分析从 PDO 映射到最终形成IOMap的完整过程。
一、从simple_ng.c看 PDO 映射入口
在simple_ng.c的初始化流程中:
ecx_init()↓ecx_config_init()// 扫描从站,读取 EEPROM↓ecx_config_map_group()// ⬅️ PDO 映射的入口↓ecx_configdc()// 配置分布式时钟ecx_config_map_group()是 PDO 映射的入口函数。它的原型如下:
intecx_config_map_group(ecx_contextt*context,void*pIOmap,uint8 group);这个函数执行完成后,会输出类似下面的日志:
mapped 8O+12I bytes from 1 segmentsObytes和Ibytes分别表示输出过程数据和输入过程数据的总字节数。这些数据已经被映射到主站的IOMap中。
之后,周期通信函数:
ecx_send_processdata(context);ecx_receive_processdata(context,EC_TIMEOUTRET);直接操作的就是这块IOMap内存。
二、为什么需要 PDO Mapping?
很多开发者会问:为什么不能像 CANopen 的 SDO 那样,每次通过索引和子索引去读写对象字典?
实际上 EtherCAT确实提供这种方式——就是 SDO。但 SDO 的问题在于实时性不足。
假设控制周期是 1kHz,每 1ms 主站都需要发送目标位置、读取实际位置和状态。如果每次都通过 SDO,通信过程是:
主站请求 → 等待 → 从站响应 → 解析每个参数都需要一次完整的请求-响应往返,效率极低。
PDO 的核心思想是:提前规定好哪些数据需要周期交换,以及它们在通信帧中的固定位置。
例如一个伺服驱动器:
| 方向 | 对象 | 大小 |
|---|---|---|
| RxPDO | 0x6040 Control Word | 16 bit |
| RxPDO | 0x607A Target Position | 32 bit |
| RxPDO | 0x60FF Target Velocity | 32 bit |
| TxPDO | 0x6041 Status Word | 16 bit |
| TxPDO | 0x6064 Position Actual | 32 bit |
| TxPDO | 0x606C Velocity Actual | 32 bit |
RxPDO 总长 80 bit = 10 Byte,TxPDO 同样 10 Byte。主站只需要周期性地发送 10 字节、接收 10 字节,无需关心对象索引解析和数据转换。
这就是 PDO 高效率的根本原因。
三、SOEM PDO 映射整体流程
打开ec_config.c,SOEM 的 PDO 配置流程如下:
ecx_config_map_group() | v ecx_config_find_mappings() // ① 遍历从站,统计 PDO 大小 | v 遍历每个从站,配置 FMMU // ② 为每个从站建立逻辑地址映射 | v 生成 IOMap // ③ 形成主站可直接操作的连续内存最终形成的数据通路是:
Master IOMap (主站内存) | | EtherCAT Frame (逻辑地址寻址) v Slave FMMU (逻辑地址 → 物理地址) | v Slave ESC RAM (SyncManager 管理) | v Slave Application Data四、EtherCAT 从站内部结构:ESC、SyncManager 与 FMMU
为了理解源码,需要先了解 EtherCAT 从站控制器(ESC)的内部结构:
EtherCAT Frame | v +----------+ | ESC | +----------+ | +---------+---------+ | | v v SyncManager FMMU | | v v ESC RAM ←─── 逻辑地址映射SyncManager(同步管理器):管理 ESC 内部的数据缓冲区,保证数据一致性,防止读写冲突。典型配置如下:
| SyncManager | 方向 | 用途 |
|---|---|---|
| SM0 | 主站 → 从站 | Mailbox 输出 |
| SM1 | 从站 → 主站 | Mailbox 输入 |
| SM2 | 主站 → 从站 | RxPDO(过程数据输出) |
| SM3 | 从站 → 主站 | TxPDO(过程数据输入) |
SM2 和 SM3 就是 PDO 数据的物理存储区域。
FMMU(现场总线内存管理单元):负责将 EtherCAT 的逻辑地址转换为从站 ESC 的物理地址。
FMMU 实现逻辑地址到物理地址的映射,使主站可以用统一的逻辑地址空间访问所有从站的数据,无需关心每个从站的物理内存地址。
五、Logical Address:EtherCAT 为什么不用设备地址通信?
传统总线(如 CAN)使用节点 ID 寻址:主站知道要访问哪个节点,然后发送数据。
EtherCAT 则采用逻辑地址寻址。主站将所有从站的物理内存映射到一个4GB 的虚拟逻辑空间中。主站只需发送一个报文,如同操作本地内存一样,一次性读写所有从站的过程数据。
例如,整个网络的逻辑地址布局可能是:
Logical Address Space (4GB) | +--- 0x0000 ← Slave 1 Output (SM2) | +--- 0x0010 ← Slave 2 Output (SM2) | +--- 0x0020 ← Slave 1 Input (SM3) | +--- 0x0030 ← Slave 2 Input (SM3)主站只需要维护这一整块连续的逻辑地址空间,不需要逐个访问每个从站。这是 EtherCAT 能够实现微秒级实时通信的关键原因之一。
六、FMMU:从 Logical Address 到 ESC RAM
那么,逻辑地址0x1000如何知道对应哪个从站的哪块内存?
答案就是FMMU。
每个 FMMU 通道包含以下配置参数:
| 参数 | 说明 |
|---|---|
| 逻辑起始地址 | 在 4GB 逻辑空间中的起始位置 |
| 数据长度 | 映射的数据大小(字节) |
| 物理起始地址 | ESC 内部的物理内存地址 |
| 方向 | 读(输入)或写(输出) |
FMMU 和 SM 的协作关系如下:
主站发送帧 (逻辑地址 LRW) ↓ FMMU: 逻辑地址 → 物理地址(ESC 内部) ↓ SM: 物理地址 → 缓冲区管理(保证数据一致性) ↓ 从站应用层读写数据简单来说:FMMU 负责“地址翻译”,SM 负责“数据缓冲”。
七、ecx_config_map_group()源码分析
回到 SOEM 源码。ecx_config_map_group()的核心逻辑如下:
intecx_config_map_group(ecx_contextt*context,void*pIOmap,uint8 group){uint32 LogAddr=0;// 逻辑起始地址// 1. 查找映射并设置 SyncManagerecx_config_find_mappings(context,group);// 2. 遍历所有从站,配置 FMMUfor(slave=1;slave<=*(context->slavecount);slave++){// 为每个从站分配逻辑地址// 配置 FMMU:逻辑起始地址 → ESC 物理地址// 更新 LogAddr 指针}// 3. 返回 IOMap 总大小return(int)LogAddr;}第一步:ecx_config_find_mappings()
这个函数遍历所有从站,根据 EEPROM 中的 PDO 配置信息,统计每个从站的输入/输出数据大小,并计算出整个 Group 需要的总内存大小。
第二步:配置 FMMU
遍历从站,为每个从站的输出(SM2)和输入(SM3)分别创建 FMMU 映射:
- 分配一段逻辑地址空间
- 将逻辑地址映射到从站 ESC 的物理地址
- 更新
LogAddr指针,为下一个从站预留空间
八、outputs和inputs指针到底是什么?
这是 SOEM 中容易误解的地方。
在simple_ng.c中,grp->outputs和grp->inputs本质是指针,它们指向IOMap中的特定位置。
例如IOMap布局如下:
uint8 map[4096] | +--- 0x0000 ← Slave 1 Output (grp->outputs 指向这里) | +--- 0x0010 ← Slave 2 Output | +--- 0x0020 ← Slave 1 Input (grp->inputs 指向这里)因此,修改*slave.outputs = value实际上就是修改map[]数组。下一次调用ecx_send_processdata()时,这块内存就会被封装成 EtherCAT 帧发送出去。
九、完整流程总结
ecx_config_init() // 扫描从站,读取对象字典 | v ecx_config_map_group() | +-------------------+ | | v v ecx_config_find_mappings() 遍历从站,配置 FMMU | | +---------+---------+ | v 生成 IOMap // 主站可直接操作的连续内存 | v 进入 OP 状态 | v ecx_send_processdata() // 周期发送 IOMap ecx_receive_processdata() // 周期接收 IOMap十、本章总结
通过分析 SOEM 的ec_config.c,我们理解了 EtherCAT 主站最核心的一步:如何把设备对象转换成实时过程数据。
| 概念 | 作用 |
|---|---|
| PDO | 定义周期交换的数据(控制字、目标位置、状态字等) |
| SyncManager | 管理 ESC 内存区域(SM2=RxPDO,SM3=TxPDO) |
| FMMU | 逻辑地址 → 从站物理内存的映射 |
| IOMap | SOEM 主站最终操作的连续内存 |
配置完成后,周期通信函数ecx_send_processdata()和ecx_receive_processdata()本质上就是交换IOMap中的数据。
到这里,我们已经完成了从EtherCAT 网络初始化 → 扫描 Slave → 配置 PDO → 生成 IOMap → 周期通信的完整链路。
下一篇将继续深入 EtherCAT 最核心的实时环节:
《从零开发 EtherCAT 主站(八):SOEM 周期通信源码解析,ecx_send_processdata() 到底发送了什么?》
届时将分析 EtherCAT Frame 结构、LRW/LRD/LWR 命令、SOEM 如何组织过程数据,以及 Working Counter(WKC)的完整工作机制。