第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就崩了,最后发现是套接字超时设置的问题。

应用层(用户代码) 调用SOEM API:ec_init()、ec_config()、ec_send_processdata() 核心层(ethercat) EtherCAT协议栈:状态机、数据帧收发、CoE/Sdo处理 底层(osal/oshw) 操作系统抽象:套接字、线程、定时器、内存管理 API调用方向 用户代码 → SOEM核心 → 操作系统

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 主站上下文 包含port、slavelist等 ecx_portt 网络端口 管理套接字和数据帧 ec_slave[] 从站信息数组 下标=站号,从1开始 port slavelist 一个ecx_context管理一个port和一组从站

实际编程中,你只需要定义一个 ecx_contextt 变量,然后调用 ec_init() 初始化,SOEM内部会自动关联好这些结构体。但理解它们的内部关系,对调试绝对有帮助。

核心要点总结:

  • SOEM架构分三层:应用层、核心层、底层
  • Linux编译用cmake,Windows用VS或MinGW
  • 核心数据结构:ecx_contextt(主站)、ec_slave(从站)、ecx_portt(端口)
  • 从站数组下标从1开始,别搞错了

好了,SOEM的入门就聊到这儿。下一节我们会真正动手写代码,用SOEM扫描总线上的从站。到时候你会发现,理解了这些数据结构,写代码就跟搭积木一样简单。


公众号:蓝海资料掘金营,微信deep3321