:SOEM PDO 映射源码解析,深入理解 SyncManager、FMMU 与 IOMap)
从零开发 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 AddressEtherCAT 为什么不用设备地址通信六、FMMU从 Logical Address 到 ESC RAM七、ecx_config_map_group() 源码分析八、outputs 和 inputs 指针到底是什么九、完整流程总结十、本章总结上一篇文章中我们分析了 SOEM 主站初始化流程了解了 EtherCAT 主站如何完成网卡初始化、从站扫描、设备信息读取等步骤并最终进入 PRE-OP 状态。但是主站虽然知道了网络中有哪些从站却还不知道如何与它们交换实时数据。以伺服驱动器为例我们希望周期性地发送控制字、目标位置、目标速度同时读取状态字、实际位置等信息。这些数据如何进入 EtherCAT 通信帧从站又如何知道应该读取哪些数据这就是PDOProcess 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 8O12I 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 的核心思想是提前规定好哪些数据需要周期交换以及它们在通信帧中的固定位置。例如一个伺服驱动器方向对象大小RxPDO0x6040 Control Word16 bitRxPDO0x607A Target Position32 bitRxPDO0x60FF Target Velocity32 bitTxPDO0x6041 Status Word16 bitTxPDO0x6064 Position Actual32 bitTxPDO0x606C Velocity Actual32 bitRxPDO 总长 80 bit 10 ByteTxPDO 同样 10 Byte。主站只需要周期性地发送 10 字节、接收 10 字节无需关心对象索引解析和数据转换。这就是 PDO 高效率的根本原因。三、SOEM PDO 映射整体流程打开ec_config.cSOEM 的 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 AddressEtherCAT 为什么不用设备地址通信传统总线如 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 LogAddr0;// 逻辑起始地址// 1. 查找映射并设置 SyncManagerecx_config_find_mappings(context,group);// 2. 遍历所有从站配置 FMMUfor(slave1;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 内存区域SM2RxPDOSM3TxPDOFMMU逻辑地址 → 从站物理内存的映射IOMapSOEM 主站最终操作的连续内存配置完成后周期通信函数ecx_send_processdata()和ecx_receive_processdata()本质上就是交换IOMap中的数据。到这里我们已经完成了从EtherCAT 网络初始化 → 扫描 Slave → 配置 PDO → 生成 IOMap → 周期通信的完整链路。下一篇将继续深入 EtherCAT 最核心的实时环节《从零开发 EtherCAT 主站八SOEM 周期通信源码解析ecx_send_processdata() 到底发送了什么》届时将分析 EtherCAT Frame 结构、LRW/LRD/LWR 命令、SOEM 如何组织过程数据以及 Working CounterWKC的完整工作机制。