
本文面向初学者带你在不迷失于协议细节的前提下通读一个真实的 EtherCAT 主站工程——基于 SOEM (Simple Open EtherCAT Master) 移植到 STM32F407 的代码并最终给出向 STM32H7FPGA 平台移植的完整路线。一、为什么从源码读起很多初学者上手 EtherCAT 时会被协议规范、XML 配置文件、各种寄存器地址吓退觉得它太重。但当你真正打开一个能跑通的主站工程会发现它的代码结构异常清晰自上而下就是五层每一层职责单一层与层之间靠明确接口耦合。一旦看懂了这五层你会得到一个关键认知——移植到任何新平台真正要改的代码不到工程总量的 5%。这不是夸张。下面我们就来验证这句话。二、五层架构一张图看清边界整个工程自上而下分为五层对初学者最关键的一点移植分界线就画在 nicdrv.c 与 eth.c 之间。nicdrv.c 负责帧缓冲管理与收发配对保留在 STM32eth.c 里的 bfin_EMAC_send() / bfin_EMAC_recv() 是真正打交道的硬件收发函数交给 FPGA 替换。记住这两个函数名移植之路就豁然开朗了。各层职责与对应源码应用层 —— main.c 负责 OLED/按键显示在 1ms SysTick 中调用 ecat_loop()ecatuser.c 封装了 ecat_init() 启动序列、ecat_loop() 周期收发、以及 PDO 结构体指针 outputs1/inputs1。SOEM 核心库 —— ethercatmain.c 是状态机与过程数据交换ethercatconfig.c 是从站扫描与 IOmap 映射ethercatbase.c 是以太网帧/数据报组包。HAL 层 —— nicdrv.c 用 16 个收发缓冲 索引号把发出的帧和返回的帧配对osal.c 提供微秒级定时器供超时判断。网卡驱动/时基 —— eth.c 的 bfin_EMAC_send/recv 是 SOEM 与硬件的唯一接口tim.c 用 TIM2(µs)TIM3(秒) 给 osal 提供时间。三、主站是怎么跑起来的状态机四步走EtherCAT 从站有四个核心状态INIT → PRE_OP → SAFE_OP → OP。只有进入 OP主从之间才会周期性交换过程数据。ecat_init() 按固定顺序完成整套握手1.ec_init(“YS-PWM”) —— 绑定网卡初始化 16 组收发缓冲。2.ec_config_init(FALSE) —— 扫描发现从站发广播读(BRD)清点从站数量再对每个从站自增读(APRD) EEPROM填充全局数组 ec_slave[] 的厂商ID、产品ID、名字等信息。3.配置从站 DC IOmap 映射 —— 注册 PO2SOconfig 回调在从站进 SAFE_OP 前通过 SDO 写 PDO 映射参数 0x1C12/0x1C13调用 ec_configdc() 配置分布式时钟ec_config_map(IOmap) 根据 FMMU 和同步管理器把输入输出数据映射到一块连续内存。4.ec_statecheck(0, EC_STATE_SAFE_OP) —— 推动所有从站进入安全运行。5.收发一帧 ec_writestate → EC_STATE_OPERATIONAL —— 进入运行态开启 1ms 周期循环。这里有两个初学者容易忽略的重点重点一PO2SOconfig 回调函数指针。 SOEM 在从站进入 SAFE_OP 前会自动调用它这是 EtherCAT 协议规定的「PRE_OP 阶段配置点」——你只能在这个时机通过 SDO 写 PDO 映射因为 SAFE_OP 后从站就锁定映射了。重点二WKC工作计数器。 expectedWKC (outputsWKC * 2) inputsWKC。每个从站处理一次数据报WKC 自增逻辑读写(LRW)对输出从站 2读写、输入从站 1。主站每周期比对实际 WKC 与期望值判断通信是否正常。这是 EtherCAT 最精巧的健壮性设计。关键数据结构 ec_slavet记住几个字段outputs/inputs 指向 IOmap 里的输出/输入区主站直接读写这两块内存即等于与从站通信、Obytes/Ibytes 是字节长度、configadr 是配置地址、SM[]/FMMU[] 是同步管理器与内存映射单元四、周期过程数据交换核心中的核心进入 OP 后每 1ms 在 SysTick 中断里调用 ecat_loop()里面就两句ec_send_processdata();ec_receive_processdata();这对函数构成了「一帧走完整个从站链」的总线式读写。它的机制是 EtherCAT 区别于普通以太网的根本所在——以太网帧是一帧走到底的总线拓扑1.构造 LRW 帧主站用逻辑地址读写命令(LRW)一条数据报同时读写整段逻辑内存效率最高。第一个帧里还追加一条 DC 时间戳数据报(EC_CMD_FRMW)读写 DC 主从站的系统时间寄存器这是分布式时钟同步的来源。2.帧穿越从站链每个从站的 ESCEtherCAT Slave Controller看到帧经过就按 FMMU 映射把自己的输入写进帧、把帧里的输出读出来并让 WKC 自增。帧绕一圈回到主站。3.帧返回接收这是 nicdrv.c 的精华。SOEM 维护 16 组收发缓冲每发一帧分配一个 index发时置 TX 态收时按帧里的 index 找回对应缓冲——即使收发乱序也能正确配对。4.复制输入 WKC 校验收完后把帧里的输入数据 memcpy 回 IOmap 的输入区。主站直接读 inputs1-xxx 就拿到了从站数据写 outputs1-xxx 就发给了从站。为什么 STM32 只需保留这部分 因为状态机、配置、SDO/邮箱通信都在 INIT→OP 阶段一次性做完进入 OP 后每周期只跑构造帧→穿越→收帧匹配这一闭环逻辑全在 SOEM 核心层与具体硬件完全解耦——这正是 FPGA 接管硬件收发的可行性所在。五、移植到 STM32H7FPGA改两个函数就够了理解了上面的机制移植方案就水到渠成。目标架构STM32H7 只跑过程数据逻辑FPGA 负责以太网帧收发与 PHY 接口。不用改 / 直接复用约 80% 代码Ethercat/soem/* 整个 SOEM 核心库 —— 协议逻辑与硬件无关原样移植到 H7 工程。osal.c —— 仅把里面的 GetSec()/GetUSec() 指向 H7 的定时器即可。必须改写移植的真正工作量集中在这 2 个函数eth.c 里的 bfin_EMAC_send/recv原实现是直接操作 STM32F4 ETH DMA移植后改为操作 FPGA FIFO/* 原实现操作 STM32F4 ETH MAC DMA */intbfin_EMAC_send(void*packet,intlength){memcpy(Tx_Buff[0][0],packet,length);low_level_output(Tx_Buff[0],length);// ← F4 HAL_ETH_TransmitFramereturn0;}/* 移植后把帧写入 FPGA 的 Tx FIFO通过 FSMC 访问 FPGA 寄存器 */intbfin_EMAC_send(void*packet,intlength){FPGA_WriteTxFifo(packet,length);// FIFO 满/超时处理return0;}intbfin_EMAC_recv(uint8_t*packet,size_tsize){/* 原实现HAL_ETH_GetReceivedFrame 读 F4 DMA 描述符 *//* 移植后从 FPGA Rx FIFO 读一帧 */intlenFPGA_ReadRxFifo(packet,size);// 非阻塞无帧返回 -1returnlen;}nicdrv.c 的 ecx_outframe / ecx_recvpkt 调用这两个函数所以只要这两个函数语义不变send 非阻塞发送、recv 非阻塞返回帧长或 -1上层 SOEM 完全无感。删除 / 不需要lan8720.c —— PHY 初始化由 FPGA 端处理。MX_ETH_Init / HAL_ETH_MspInit —— F4 ETH 外设初始化H7 端用 FSMC/SPI 初始化 FPGA 接口替代。FPGA 端需要实现的逻辑六、几个实操提醒1.STM32H7 与 FPGA 的总线选型过程数据帧不大通常 1500 字节用 FSMC 并行总线速度快或 SPI引脚少均可。FSMC 更适合 EtherCAT 的实时性要求。2.FIFO 深度至少容纳 2~4 个完整帧SOEM 允许多帧同时在途EC_MAXBUF16避免丢帧。3.DMA 描述符可去掉F4 用了 DMA 描述符链FPGA 方案里 FIFO 直接替代了描述符逻辑更简单。4.先用原工程跑通移植前建议先在 F4 平台把通信调通确认从站能进 OP、PDO 收发正常再切到 H7FPGA便于隔离问题。5.DC 同步不能省ec_dcsync0(1, TRUE, 1000000, 0) 让从站每 1ms 产生 SYNC0 信号使所有从站同步采样——这是 EtherCAT 微秒级同步的根基移植时务必保留这段配置逻辑。七、收尾把调用链刻进脑子里最后把过程数据一帧的完整调用链记住你就真正读懂了这个工程只有两个 ★ 标记的函数需要改写其余全部保留。这就是5%的由来。EtherCAT 看似复杂但它的设计哲学极其清晰协议逻辑与硬件解耦、帧索引配对容忍乱序、WKC 自检保证健壮。当你把分界线bfin_EMAC_send/recv这一点想通整个移植就变成了换底座的工程问题而非协议重写。读懂这套代码后建议你先精读 ecatuser.c 的 ecat_init()主站怎么跑起来和 nicdrv.c 的 ecx_inframe()帧怎么配对——这两个分别是「主站初始化」和「帧收发配对」的核心吃透它们你的开发进度会快很多。祝你移植顺利。