3. 从站扫描与识别:ec_slave结构体、从站信息读取、厂商ID与产品代码匹配

好,咱们继续。上一节我们把SOEM的初始化流程跑通了,网卡也绑定了。但EtherCAT总线上一堆从站,你得知道它们是谁、是什么型号、能不能干活。这就像你进到一个车间,先得清点一下设备,看看铭牌上写的啥。

这一节,我们就来聊聊怎么“点名”。核心就是三个东西:ec_slave结构体从站信息读取、以及厂商ID与产品代码匹配

3.1 ec_slave结构体:从站的“身份证”

在SOEM里,每个从站的信息都存到一个叫 ec_slave 的结构体里。你可以把它想象成从站的“身份证”。

这个结构体定义在 ethercat.h 里,我挑几个最常用的字段给你看:

typedef struct {
    uint16    state;          // 从站当前状态
    uint16    ALstatuscode;   // 状态码,出错了看这个
    uint16    configadr;      // 配置地址,一般用这个寻址
    uint16    aliasadr;       // 别名地址,可以自己设
    uint32    eep_man;        // 厂商ID,从EEPROM读出来的
    uint32    eep_id;         // 产品代码,也是从EEPROM读的
    uint32    eep_rev;        // 版本号
    uint8     name[EC_MAXNAME]; // 从站名字,字符串形式
    int       mbxproto;      // 邮箱协议类型
    // ... 还有很多,但上面这些最常用
} ec_slave;

我个人习惯,拿到一个从站后,第一件事就是看 eep_maneep_id。这俩字段决定了这个从站是谁家的、什么型号。你想想看,如果厂商ID对不上,后面的PDO映射、SDO配置全白搭。

核心要点:

  • eep_man:厂商ID,比如Beckhoff是0x00000002,松下是0x00000123(具体值去官网查)
  • eep_id:产品代码,每个型号唯一
  • eep_rev:版本号,有时候不同版本PDO不一样,要注意

嗯,这里要注意:eep_maneep_id 是从从站的EEPROM里读出来的,不是从XML文件里来的。有些初学者会混淆,以为从站描述文件(ESI)里的ID就是实际值。其实不一定,EEPROM里的才是“硬编码”。

3.2 从站信息读取:怎么把数据拉回来?

知道了结构体长啥样,下一步就是怎么把数据填进去。SOEM提供了一个非常方便的函数:ec_readstate()

这个函数会遍历总线上的所有从站,把每个从站的状态、ID、名字等信息,全部更新到全局数组 ec_slave[] 里。调用一次,所有从站的信息就都拉回来了。

用法很简单:

// 假设已经 ec_init() 和 ec_config() 过了
// 调用 ec_readstate() 读取所有从站信息
if (ec_readstate() > 0) {
    // 成功读取,ec_slave[] 里已经有数据了
    printf("从站数量: %d\n", ec_slavecount);
    for (int i = 1; i <= ec_slavecount; i++) {
        printf("从站 %d: 厂商ID=0x%08X, 产品代码=0x%08X\n",
               i, ec_slave[i].eep_man, ec_slave[i].eep_id);
    }
} else {
    printf("读取从站状态失败!\n");
}

我在项目中遇到过一个问题:有时候 ec_readstate() 返回的从站数量是对的,但某些字段是0。后来发现是因为从站还没进入OP状态,EEPROM没完全加载。所以我的建议是:在调用 ec_readstate() 之前,先确保从站至少处于PREOP状态。你可以用 ec_statecheck() 等一等。

小技巧:

如果你只想读某个特定从站的信息,可以用 ec_slave[slave_index] 直接访问。索引从1开始,0是主站自己。

3.3 厂商ID与产品代码匹配:怎么确认“找对了人”?

好了,现在数据都读回来了。但你怎么知道这个从站就是你想要的那个?比如你项目里用的是松下A6伺服,结果总线上挂了个别的牌子,那后面配置PDO肯定出错。

匹配的逻辑其实很简单:把你读到的 eep_maneep_id,跟你的期望值做比较

我一般会写一个匹配函数:

// 检查从站是否匹配指定的厂商ID和产品代码
int check_slave_match(int slave_index, uint32 expected_man, uint32 expected_id) {
    if (slave_index < 1 || slave_index > ec_slavecount) {
        return 0; // 索引无效
    }
    if (ec_slave[slave_index].eep_man == expected_man &&
        ec_slave[slave_index].eep_id == expected_id) {
        return 1; // 匹配成功
    }
    return 0; // 不匹配
}

然后你在初始化代码里这样用:

// 假设我们期望的松下A6伺服:厂商ID=0x00000123, 产品代码=0x000000A1
#define EXPECTED_MAN 0x00000123
#define EXPECTED_ID  0x000000A1

for (int i = 1; i <= ec_slavecount; i++) {
    if (check_slave_match(i, EXPECTED_MAN, EXPECTED_ID)) {
        printf("从站 %d 是目标伺服!\n", i);
        // 这里可以记录下索引,后面配置PDO用
    } else {
        printf("从站 %d 不匹配,厂商ID=0x%08X, 产品代码=0x%08X\n",
               i, ec_slave[i].eep_man, ec_slave[i].eep_id);
    }
}

我曾经在一个产线上遇到过坑:同一批次的伺服,产品代码居然有两个版本,一个带后缀A,一个不带。EEPROM里读出来的ID不一样。后来查资料才发现是固件版本不同导致的。所以匹配的时候,最好也把 eep_rev 版本号考虑进去,或者至少打印出来留个日志。

避坑指南:

我曾经因为厂商ID写错了一个字节,导致整条产线动不了。排查了整整一天才发现是0x00000123写成了0x00000122。所以建议你把厂商ID和产品代码定义成宏,并且从官方文档里复制粘贴,不要手打。

3.4 知识体系梳理:一张图看懂

说了这么多,我画张图帮你理一理。从站扫描与识别的核心流程,其实就是三步:

从站扫描与识别核心流程 步骤1:调用ec_readstate() 遍历总线,读取所有从站信息 步骤2:解析ec_slave[]数组 提取eep_man、eep_id、eep_rev 步骤3:匹配验证 与期望值比较 关键字段说明 • eep_man(厂商ID): 标识设备制造商,如Beckhoff=0x00000002 • eep_id(产品代码): 标识具体型号,每个型号唯一 • eep_rev(版本号): 固件版本,不同版本PDO可能不同 匹配成功后才进行后续的PDO映射和SDO配置

这张图把流程串起来了。说白了就是:先读、再解析、最后匹配。匹配上了,才能放心地往下走。

3.5 小结

这一节我们干了三件事:

  • 认识了 ec_slave 结构体,知道了从站的“身份证”长啥样
  • 学会了用 ec_readstate() 把从站信息拉回来
  • 掌握了厂商ID和产品代码的匹配方法,确保找对了设备

嗯,这些内容看起来简单,但实际项目中80%的通信问题都出在“没找对人”上。你想想看,如果厂商ID都匹配错了,后面的PDO配置、SDO读写全是白费功夫。所以这一步,值得你多花点心思。

下一节,我们会深入PDO映射,看看怎么把伺服的位置、速度、转矩这些数据,跟主站的内存对上号。到时候你会回来感谢今天认真匹配ID的自己的。


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