EtherCAT 作为工业自动化领域的主流现场总线协议,因其高实时性和高带宽被广泛应用。而 SOEM(Simple Open EtherCAT Master)则是开源社区中最受欢迎的 EtherCAT 主站协议栈之一。本文将以 SOEM 2.0 最新源码为例,详细介绍其在嵌入式 Linux 平台下的移植与编译流程,并结合实际问题给出解决方案,助力开发者高效上手 EtherCAT 主站开发。
文章目录
- 一、SOEM 项目结构简介
- 二、samples 目录下的示例程序作用
- 三、嵌入式 Linux 下的移植与编译流程
- 1. 安装交叉编译工具链(如果已有非必须)
- 2. 编写 CMake 工具链文件
- 3. 配置 CMake
- 4. 编译
- 5. 可执行文件部署
- 四、常见编译报错与解决方案
- 1. 找不到 eniconv.py
- 五、跨平台移植建议
- 六、移植到stm32单片机涉及的内容
- 1、移植需要的步骤
- 2、移植需要修改或新增的文件
- 3、移植流程简述
- 4、常见移植注意事项
- 5、移植总结
- 参考资源
一、SOEM 项目结构简介
github地址:https://github.com/OpenEtherCATsociety/SOEM
SOEM 项目结构清晰,便于移植和二次开发。
源码目录结构介绍:
soem/
├── cmake/ # CMake 构建相关脚本和工具
│ ├── AddENI.cmake
│ ├── Linux.cmake
│ ├── Windows.cmake
│ └── tools/
├── contrib/ # 贡献的代码,包含不同操作系统的适配层
│ ├── cmake/
│ ├── osal/ # OS Abstraction Layer(操作系统抽象层)
│ ├── oshw/ # OS Hardware Layer(硬件抽象层)
│ └── test/
├── include/ # 头文件,主要对外接口和数据结构定义
│ └── soem/
├── osal/ # OS Abstraction Layer 的主目录
│ ├── linux/
│ ├── rtk/
│ ├── win32/
│ └── osal.h # OSAL 的主头文件
├── oshw/ # OS Hardware Layer 的主目录
│ ├── linux/
│ ├── rtk/
│ ├── win32/
│ └── win32/wpcap/ # Windows 下的 WinPcap 相关头文件和库
├── samples/ # 示例程序,演示 SOEM 的用法
│ ├── ec_sample/
│ ├── eepromtool/
│ ├── eni_test/
│ ├── eoe_test/
│ ├── firm_update/
│ ├── simple_ng/
│ └── slaveinfo/
├── scripts/ # 辅助脚本(如 ENI 文件转换)
├── src/ # SOEM 核心源代码
├── CMakeLists.txt # CMake 主构建脚本
├── CMakePresets.json # CMake 构建预设
├── LICENSE.md # 许可证
├── README.md # 项目说明
主要目录说明如下:
- cmake/:跨平台构建脚本,支持多操作系统和工具链。
- contrib/:第三方贡献的适配代码,包括不同操作系统的 OSAL(操作系统抽象层)和 OSHW(硬件抽象层)。
- include/soem/:SOEM 的公共头文件,定义主要数据结构和 API。
- osal/、oshw/:分别为操作系统和硬件抽象层的具体实现,按平台分类(如 linux、win32)。
- samples/:丰富的示例程序,涵盖 EtherCAT 主站开发的常见场景。
- src/:SOEM 协议栈核心源代码。
- scripts/:实用脚本,如 ENI 文件转换工具
eniconv.py
。
src目录下为SOEM2.0核心的协议栈源码。
各个文件的功能介绍:
文件名 | 作用简介 | 主要内容说明 |
---|---|---|
ec_base.c | EtherCAT 基础功能实现 | 提供帧的发送/接收、底层数据处理等基础操作,供其他模块调用 |
ec_coe.c | CoE(CANopen over EtherCAT)协议实现 | 实现 SDO/PDO 通信、对象字典访问、参数配置等 CANopen 协议相关功能 |
ec_config.c | EtherCAT 网络配置相关功能 | 负责从站扫描、初始化、配置、分布式时钟同步等主站自动化配置流程 |
ec_dc.c | 分布式时钟(DC)功能实现 | 实现主站与从站之间的高精度时钟同步、调整和管理 |
ec_eoe.c | EoE(Ethernet over EtherCAT)协议实现 | 支持以太网帧在 EtherCAT 网络中的透传,实现以太网数据通信 |
ec_foe.c | FoE(File over EtherCAT)协议实现 | 支持主站与从站之间的文件传输,如固件升级、配置文件下发等 |
ec_main.c | SOEM 主流程与核心控制逻辑 | 负责主站初始化、主循环、状态机管理,是协议栈的核心调度中心 |
ec_print.c | 调试与信息打印功能实现 | 提供数据包、状态、错误等信息的格式化输出,便于开发调试 |
ec_soe.c | SoE(Servo over EtherCAT)协议实现 | 支持伺服驱动器参数访问和控制,适用于需要 SoE 协议的 EtherCAT 设备 |
这些文件共同组成了 SOEM 协议栈的核心功能模块,各自负责 EtherCAT 协议的不同部分。
二、samples 目录下的示例程序作用
samples
目录下每个子目录都是一个独立的示例程序,覆盖了 EtherCAT 主站开发的常见需求:
- ec_sample:基础 EtherCAT 主站示例,适合新手快速入门。
- eepromtool:EEPROM 操作工具,用于从站底层参数的读写。
- eni_test:ENI 文件解析与测试,适合基于 ENI 文件的网络配置。
- eoe_test:Ethernet over EtherCAT 协议测试,实现以太网帧透传。
- firm_update:从站固件升级示例,适合远程维护场景。
- simple_ng:极简新一代主站示例,便于快速集成。
- slaveinfo:从站信息查询工具,常用于调试和设备信息采集。
samples 目录总结
samples 目录下的每个子目录都是一个独立的示例程序,覆盖了 EtherCAT 主站开发的常见场景:
- 基础通信(ec_sample、simple_ng)
- 从站信息查询(slaveinfo)
- EEPROM 操作(eepromtool)
- ENI 文件支持(eni_test)
- EOE 协议(eoe_test)
- 固件升级(firm_update)
这些示例有助于开发者快速上手 SOEM,并根据实际需求进行二次开发。
三、嵌入式 Linux 下的移植与编译流程
1. 安装交叉编译工具链(如果已有非必须)
以 ARM 平台为例:
sudo apt-get install gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf
如果已经有了交叉编译工具链,则此步骤非必须。
2. 编写 CMake 工具链文件
在项目根目录新建 toolchain-arm.cmake
:
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR arm)
set(CMAKE_C_COMPILER arm-linux-gnueabihf-gcc)
set(CMAKE_CXX_COMPILER arm-linux-gnueabihf-g++)
我的交叉编译工具链toolchain.cmake文件如下:
# 交叉编译工具链配置文件
# 用于嵌入式Linux系统和RISC-V MCU的交叉编译# 设置系统名称
set(CMAKE_SYSTEM_NAME Linux)# 设置处理器架构变量,可以通过命令行参数传入
# 例如: cmake -DTARGET_ARCH=arm ..
if(NOT DEFINED TARGET_ARCH)set(TARGET_ARCH "arm" CACHE STRING "Target architecture (arm or riscv)")
endif()# 设置ARM工具链路径
set(ARM_TOOLCHAIN_PATH "/opt/fsl-imx-x11/4.1.15-2.1.0/sysroots/x86_64-pokysdk-linux/usr/bin/arm-poky-linux-gnueabi")
# 设置RISC-V工具链路径
set(RISCV_TOOLCHAIN_PATH "/home/zh1an/tronlong/tina5.0_v1.0/rtos/lichee/rtos/tools/riscv64-elf-x86_64-20201104")# 根据目标架构设置主工具链
if(${TARGET_ARCH} STREQUAL "arm")# ARM Linux工具链配置set(CMAKE_C_COMPILER ${ARM_TOOLCHAIN_PATH}/arm-poky-linux-gnueabi-gcc)set(CMAKE_CXX_COMPILER ${ARM_TOOLCHAIN_PATH}/arm-poky-linux-gnueabi-g++)set(CMAKE_FIND_ROOT_PATH /opt/fsl-imx-x11/4.1.15-2.1.0/sysroots/cortexa7hf-neon-poky-linux-gnueabi/)set(CMAKE_SYSTEM_PROCESSOR arm)# 设置额外的编译标志set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -march=armv7-a -mfloat-abi=hard -mfpu=neon" CACHE STRING "" FORCE)set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=armv7-a -mfloat-abi=hard -mfpu=neon" CACHE STRING "" FORCE)# 设置链接器set(CMAKE_LINKER ${CMAKE_C_COMPILER})set(CMAKE_AR ${ARM_TOOLCHAIN_PATH}/arm-poky-linux-gnueabi-ar)set(CMAKE_RANLIB ${ARM_TOOLCHAIN_PATH}/arm-poky-linux-gnueabi-ranlib)elseif(${TARGET_ARCH} STREQUAL "riscv")# RISC-V工具链配置 (C906核心)set(CMAKE_C_COMPILER ${RISCV_TOOLCHAIN_PATH}/bin/riscv64-unknown-elf-gcc)set(CMAKE_CXX_COMPILER ${RISCV_TOOLCHAIN_PATH}/bin/riscv64-unknown-elf-g++)set(CMAKE_FIND_ROOT_PATH ${RISCV_TOOLCHAIN_PATH}/riscv64-unknown-elf)set(CMAKE_SYSTEM_PROCESSOR riscv)# 设置RISC-V特定的编译标志 (C906核心)set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -march=rv64gcv0p7 -mabi=lp64d" CACHE STRING "" FORCE)set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=rv64gcv0p7 -mabi=lp64d" CACHE STRING "" FORCE)# 设置链接器set(CMAKE_LINKER ${CMAKE_C_COMPILER})set(CMAKE_AR ${RISCV_TOOLCHAIN_PATH}/bin/riscv64-unknown-elf-ar)set(CMAKE_RANLIB ${RISCV_TOOLCHAIN_PATH}/bin/riscv64-unknown-elf-ranlib)else()message(FATAL_ERROR "不支持的目标架构: ${TARGET_ARCH}. 请使用 'arm' 或 'riscv'")
endif()# 定义ARM工具链变量,供CMakeLists.txt使用
set(ARM_C_COMPILER ${ARM_TOOLCHAIN_PATH}/arm-linux-gnueabi-gcc)
set(ARM_CXX_COMPILER ${ARM_TOOLCHAIN_PATH}/arm-linux-gnueabi-g++)
set(ARM_AR ${ARM_TOOLCHAIN_PATH}/arm-linux-gnueabi-ar)
set(ARM_RANLIB ${ARM_TOOLCHAIN_PATH}/arm-linux-gnueabi-ranlib)
set(ARM_C_FLAGS "-march=armv7-a -mfloat-abi=hard -mfpu=neon")
set(ARM_CXX_FLAGS "-march=armv7-a -mfloat-abi=hard -mfpu=neon")# 定义RISC-V工具链变量,供CMakeLists.txt使用
set(RISCV_C_COMPILER ${RISCV_TOOLCHAIN_PATH}/bin/riscv64-unknown-elf-gcc)
set(RISCV_CXX_COMPILER ${RISCV_TOOLCHAIN_PATH}/bin/riscv64-unknown-elf-g++)
set(RISCV_AR ${RISCV_TOOLCHAIN_PATH}/bin/riscv64-unknown-elf-ar)
set(RISCV_RANLIB ${RISCV_TOOLCHAIN_PATH}/bin/riscv64-unknown-elf-ranlib)
set(RISCV_C_FLAGS "-march=rv64gcv0p7 -mabi=lp64d")
set(RISCV_CXX_FLAGS "-march=rv64gcv0p7 -mabi=lp64d")# 设置查找规则
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)# 禁用系统库路径
set(CMAKE_SKIP_RPATH TRUE)# 设置交叉编译环境的库和头文件搜索路径
set(CMAKE_SYSROOT ${CMAKE_FIND_ROOT_PATH})
3. 配置 CMake
mkdir build
cd build
cmake ../ -DCMAKE_TOOLCHAIN_FILE=../toolchain1.cmake -DTARGET_ARCH=arm -DSOEM_BUILD_SAMPLES=ON -DENICONV=../scripts/eniconv.py -DCMAKE_INSTALL_PREFIX=${HOME}/arm_install
如在目标板上直接编译,可省略 -DCMAKE_TOOLCHAIN_FILE
。
4. 编译
make -j$(nproc)
5. 可执行文件部署
编译完成后,samples
目录下的各示例程序会在 build/samples/xxx/
生成对应可执行文件,拷贝到目标设备即可运行。
我编译构建输出的内容如下:
四、常见编译报错与解决方案
原因:CMake 没找到 scripts/eniconv.py 脚本。因为上述构建,启用了构建自带的samples,其中有个ENI工具的测试(sample-eni.c)需要用到这个脚本。
解决方法:
将 scripts 目录加入 PATH:
export PATH=$PATH:/path/to/soem/scripts
或在 CMake 配置时指定脚本路径:
cmake .. -DENICONV=/path/to/soem/scripts/eniconv.py
1. 找不到 eniconv.py
报错:
Could not find ENICONV using the following names: eniconv.py
原因:CMake 没找到 scripts/eniconv.py
脚本。
解决方法:
- 将
scripts
目录加入 PATH:export PATH=$PATH:/path/to/soem/scripts
- 或在 CMake 配置时指定脚本路径:
cmake .. -DENICONV=/path/to/soem/scripts/eniconv.py
五、跨平台移植建议
- SOEM 2.0 结构清晰,适合嵌入式 Linux 平台移植。
- 编译前务必理清依赖关系,尤其是 Python 脚本和 ENI 工具的路径问题。
- 遇到 CMake 报错时,仔细检查路径设置和环境变量,绝大多数问题都能快速定位解决。
- 推荐优先参考
samples
目录下的示例程序,结合实际需求进行裁剪和二次开发。
六、移植到stm32单片机涉及的内容
将 SOEM 2.0 移植到 STM32 等嵌入式平台,主要涉及操作系统适配、硬件驱动适配和编译环境配置。下面详细说明移植步骤和需要修改的文件:
1、移植需要的步骤
- 操作系统抽象层(OSAL)适配
- SOEM 通过 OSAL 屏蔽不同操作系统的差异。你需要为 STM32 平台实现一套 OSAL(如基于裸机、FreeRTOS、RT-Thread 等)。
- 硬件抽象层(OSHW)适配
- SOEM 通过 OSHW 屏蔽不同网卡/硬件平台的差异。你需要为 STM32 的以太网 MAC(如 RMII/GMII)实现底层收发驱动。
- 编译环境与工具链配置
- 配置适合 STM32 的交叉编译工具链(如 arm-none-eabi-gcc)。
- 编写或修改 Makefile/CMakeLists.txt 以适配 STM32 工程结构。
- 内存与性能优化
- 根据 STM32 的 RAM/Flash 资源,裁剪不必要的功能,优化内存分配。
2、移植需要修改或新增的文件
目录/文件 | 说明与操作 |
---|---|
contrib/osal/ | 新增 stm32/ 目录,实现 STM32 平台的 osal.c 和 osal_defs.h |
contrib/oshw/ | 新增 stm32/ 目录,实现 STM32 平台的 nicdrv.c、nicdrv.h、oshw.c、oshw.h |
osal/ | 如需全局适配,可在此新增或修改 osal.h |
oshw/ | 如需全局适配,可在此新增或修改 oshw.h |
CMakeLists.txt 或 Makefile | 增加对 STM32 平台的编译选项、源文件路径、交叉编译工具链等配置 |
samples/ | 可根据 STM32 资源,移植或精简示例程序 |
3、移植流程简述
-
实现 OSAL(操作系统抽象层)
- 主要包括互斥锁、延时、内存分配等函数。
- 参考
contrib/osal/linux/
或contrib/osal/win32/
,仿照实现contrib/osal/stm32/
。
-
实现 OSHW(硬件抽象层)
- 主要包括以太网帧的收发、网卡初始化等。
- 参考
contrib/oshw/linux/
或contrib/oshw/win32/
,仿照实现contrib/oshw/stm32/
。 - 需要调用 STM32 HAL/LL 库的以太网驱动(如 ETH DMA、PHY 配置等)。
-
配置编译环境
- 新建适合 STM32 的 Makefile 或 CMake 工程,指定交叉编译器和目标架构。
- 配置头文件和源文件路径,确保编译时能找到 STM32 适配的 OSAL/OSHW。
-
裁剪和优化
- 移除不需要的协议模块(如 EoE、FoE、SoE 等),只保留核心 EtherCAT 功能。
- 优化内存分配,避免动态分配,适应 STM32 的内存限制。
-
移植和测试示例程序
- 选择
samples/simple_ng
或samples/ec_sample
进行移植,作为移植验证入口。
- 选择
4、常见移植注意事项
- STM32 以太网驱动需支持“原始帧”收发(Raw Ethernet),不能走 TCP/IP 协议栈。
- 需保证中断/轮询方式下的实时性,避免丢包。
- 可能需要移植 lwIP 的 RAW API 或直接操作 ETH HAL/LL 驱动。
- 调试时建议先用 PC 端抓包工具(如 Wireshark)配合分析。
5、移植总结
SOEM 2.0 移植到 STM32 的主要步骤:
- 新增并实现 STM32 平台的 OSAL 和 OSHW 适配层(
contrib/osal/stm32/
、contrib/oshw/stm32/
)。 - 配置交叉编译环境,修改编译脚本。
- 移植并测试示例程序,逐步完善和优化。
结语
SOEM 2.0 的移植和编译并不复杂,关键在于理解其目录结构和构建流程。希望本文能帮助你顺利在嵌入式 Linux 平台上部署 EtherCAT 主站应用,开启高效、稳定的工业自动化之路!如有更多问题,欢迎留言交流。
参考资源
https://github.com/lipoyang/SOEM4Mbed
https://os.mbed.com/users/EasyCAT/code/EasyCAT_LAB/
https://openethercatsociety.github.io/doc/soes/tutorial_8txt.html