第2章:SOEM库入门
好,咱们正式开始接触SOEM。说实话,我第一次看到这个库的时候,心里想的是:「这玩意儿到底怎么跟我的从站设备对上话?」后来啃了几个月源码,踩了不少坑,才慢慢摸清楚它的脾气。今天我就把这点经验分享给你。
2.1 SOEM库的功能与架构
SOEM,全称是Simple Open EtherCAT Master。名字里带「Simple」,但别被它骗了——它一点都不简单。它是一个纯C语言实现的开源EtherCAT主站库,支持Linux和Windows。
它的核心功能,说白了就三件事:
- 扫描总线:发现总线上挂了哪些从站设备
- 配置从站:给每个从站分配地址、配置PDO映射
- 周期通信:以固定周期(比如1ms)跟所有从站交换过程数据
我刚开始做项目时,以为SOEM就是个黑盒子,调几个API就能跑。结果发现,它的架构其实分三层:
SOEM架构三层模型:
- 底层(osal/oshw):操作系统抽象层,负责网络套接字、线程、定时器
- 核心层(ethercat):EtherCAT协议栈,处理数据帧的收发、状态机管理
- 应用层(用户代码):你写的业务逻辑,调用SOEM API完成配置和通信
嗯,这里要注意:底层那部分,Linux和Windows的实现完全不同。我当年在Windows上调试得好好的,一移植到Linux就崩了,最后发现是套接字超时设置的问题。
2.2 SOEM在Linux/Windows下的编译与安装
这部分我踩过的坑最多。先说说Linux下的编译,其实挺简单的:
# 克隆SOEM源码
git clone https://github.com/OpenEtherCATsociety/SOEM.git
cd SOEM
# 创建构建目录
mkdir build && cd build
# 配置并编译
cmake ..
make
# 安装到系统
sudo make install
我个人习惯用cmake,因为跨平台方便。但如果你在嵌入式Linux上交叉编译,记得设置好工具链:
cmake -DCMAKE_TOOLCHAIN_FILE=../toolchain-arm.cmake ..
小技巧:编译时加上 -DETHERCAT_DEBUG=ON,可以开启调试日志。我第一次调试从站通信时,全靠这个日志定位问题。
Windows下编译稍微麻烦点。我建议用Visual Studio 2019或2022,打开SOEM目录下的 SOEM.sln 直接编译。如果你用MinGW,也可以:
mkdir build && cd build
cmake -G "MinGW Makefiles" ..
mingw32-make
嗯,这里有个坑:Windows下SOEM默认使用WinPcap或Npcap抓包。我曾经因为没装Npcap,折腾了半天才发现网卡驱动不对。记得先装好Npcap,并且勾选「Install in WinPcap API-compatible Mode」。
2.3 SOEM核心数据结构解析
这部分是重点中的重点。你想想看,SOEM内部怎么管理那么多从站?靠的就是几个核心数据结构。我当年啃源码时,就是先把这几个结构体搞明白,后面看代码就顺畅多了。
2.3.1 ecx_context:主站上下文
这是SOEM最顶层的结构体,一个主站对应一个context。它包含了所有全局状态:
typedef struct {
ecx_portt port; // 网络端口
ecx_slavet slavelist; // 从站列表
ecx_redt elist; // 冗余配置
ecx_idxstackT idxstack; // 索引栈
ecx_ecatt ecat; // EtherCAT状态
// ... 还有其他成员
} ecx_contextt;
我刚开始写代码时,以为一个程序只能有一个主站。后来做冗余设计才发现,SOEM支持多个context,每个context可以独立控制一条EtherCAT总线。
2.3.2 ec_slave:从站信息结构体
这个结构体描述了一个从站的全部信息。每次调用 ec_config() 后,SOEM会把总线上所有从站的信息填充到这个结构体数组中:
typedef struct {
uint16 state; // 从站当前状态
uint16 eep_man; // 厂商ID
uint16 eep_id; // 产品ID
uint16 eep_rev; // 版本号
uint16 Obits; // 输出位长度
uint16 Ibits; // 输入位长度
uint32 configadr; // 配置地址
uint32 aliasadr; // 别名地址
ecx_sdo_requestt sdo; // SDO请求
// ... 还有很多
} ec_slavet;
注意: ec_slave 数组的下标就是从站的站号(从1开始)。我曾经犯过一个低级错误:以为从站0是第一个设备,结果访问越界了。
2.3.3 ecx_portt:网络端口结构体
这个结构体封装了底层网络操作。SOEM通过它来发送和接收EtherCAT数据帧:
typedef struct {
ecx_redportt redport; // 冗余端口
int sockhandle; // 套接字句柄
int lastSDO; // 最后一次SDO操作
ecx_frameT frame; // 当前数据帧
// ...
} ecx_portt;
我记得有一次在Linux上调试,发现 sockhandle 总是-1,排查了半天才发现是权限问题——普通用户没有原始套接字权限。加上 sudo 就好了。
2.3.4 数据结构的关联关系
这几个结构体是怎么串起来的?我画个图你就明白了:
实际编程中,你只需要定义一个 ecx_contextt 变量,然后调用 ec_init() 初始化,SOEM内部会自动关联好这些结构体。但理解它们的内部关系,对调试绝对有帮助。
核心要点总结:
- SOEM架构分三层:应用层、核心层、底层
- Linux编译用cmake,Windows用VS或MinGW
- 核心数据结构:ecx_contextt(主站)、ec_slave(从站)、ecx_portt(端口)
- 从站数组下标从1开始,别搞错了
好了,SOEM的入门就聊到这儿。下一节我们会真正动手写代码,用SOEM扫描总线上的从站。到时候你会发现,理解了这些数据结构,写代码就跟搭积木一样简单。
公众号:蓝海资料掘金营,微信deep3321