4. 从站扫描与配置:ec_config()函数详解、从站信息读取、PDO映射配置

好,咱们进入正题。这一章聊的是SOEM主站初始化中最核心的一步——ec_config()。很多新手朋友觉得这函数就是点一下,从站就配好了。其实没那么简单。我刚开始用SOEM的时候,也以为它就是个“一键配置”,结果调试时从站死活不工作,折腾了两天才发现是PDO映射没配对。

说白了,ec_config()干的事就是:扫描总线上的从站、读取它们的EEPROM信息、然后根据你的配置去协商PDO映射。它背后是一套完整的状态机切换流程。咱们今天就把这层窗户纸捅破。

4.1 ec_config() 到底干了什么?

先看函数原型:

int ec_config(uint8_t usefoe, uint8_t useeoe);

参数很简单,两个布尔值:是否使用FoE(文件传输),是否使用EoE(以太网 over EtherCAT)。大部分工业现场,这两个参数都传0就行。

但函数内部,它做了这几件大事:

  • 状态机初始化:把所有从站从Init推到Pre-Op状态
  • 读取从站信息:包括厂商ID、产品码、修订号、序列号
  • SM(同步管理器)配置:根据从站EEPROM里的配置,设置邮箱通道
  • FMMU配置:把逻辑地址映射到物理地址
  • PDO映射协商:这是最关键的,后面细说

嗯,这里要注意:ec_config()执行完后,从站处于Pre-Op状态。要进入Op状态,还得手动调用ec_statecheck()ec_writestate()。我见过有人以为配完就能直接发数据,结果一直报错——其实就是状态没切过去。

核心要点:ec_config() 只是完成了配置协商,不会自动切换到Op状态。后续需要显式调用状态切换函数。

4.2 从站信息读取——你拿到的不只是ID

调用完ec_config(),从站信息会存储在全局数组ec_slave[]里。每个从站对应一个结构体。我最常用的几个字段:

// 读取第一个从站的信息
ec_slavet *slave = &ec_slave[1];  // 注意:索引0是主站自己

printf("厂商ID: 0x%04X\n", slave->eep_man);
printf("产品码: 0x%08X\n", slave->eep_id);
printf("修订号: 0x%04X\n", slave->eep_rev);
printf("从站名称: %s\n", slave->name);
printf("当前状态: %d\n", slave->state);

这里有个坑,我踩过好几次:slave->name是从EEPROM里读出来的字符串,有些从站厂商根本没写这个字段,或者写的是乱码。所以别太依赖它做逻辑判断,还是用厂商ID+产品码的组合更靠谱。

另外,ec_slavecount这个全局变量记录了总从站数。调试时我习惯先打印一下:

printf("检测到 %d 个从站\n", ec_slavecount);
for (int i = 1; i <= ec_slavecount; i++) {
    printf("从站%d: 厂商0x%04X, 产品0x%08X\n", 
           i, ec_slave[i].eep_man, ec_slave[i].eep_id);
}

为什么从站索引从1开始?因为索引0是主站自己。这个设计一开始让我挺不习惯的,后来也就接受了。

4.3 PDO映射配置——最容易被忽视的环节

PDO映射,说白了就是告诉从站:你要把哪些数据放到输入输出通道里。比如一个数字量输入模块,它可能把8个通道的状态打包成一个字节;一个模拟量模块,可能把两路AD值各占2个字节。

SOEM里,PDO映射的配置有两种方式:

  • 自动映射:依赖从站EEPROM里的默认配置,ec_config()会自动读取并应用
  • 手动映射:通过ec_config_map()ec_config_overlap_map()覆盖默认配置

我个人的习惯是:先用自动映射跑通,再根据实际需求手动调整。为什么呢?因为有些从站的默认映射根本不对,或者不符合你的应用场景。

举个例子,我之前用过一个伺服驱动器,它的默认PDO映射里包含了位置、速度、电流三个维度的数据,但我只需要位置和速度。如果不去手动修改映射,每次通信都要多传一堆用不上的数据,白白浪费带宽。

4.4 手动配置PDO映射的实战代码

手动配置PDO映射,核心是操作ec_slave[]里的SMPDO结构体。下面是一个典型流程:

// 步骤1:先调用ec_config()完成基础扫描
ec_config(FALSE, FALSE);

// 步骤2:修改第一个从站的输出PDO映射
// 假设我们要映射两个16位数据(共4字节)
ec_slave[1].SM[0].StartAddr = 0x1000;  // 起始地址
ec_slave[1].SM[0].SMlength = 4;        // 长度4字节
ec_slave[1].SM[0].SMtype = 2;          // 输出类型

// 步骤3:配置PDO条目
ec_slave[1].SM[0].PDO[0].PDOnumber = 0x1600;
ec_slave[1].SM[0].PDO[0].PDOassign = 1;  // 使能
ec_slave[1].SM[0].PDO[0].PDOentry[0].index = 0x6040;  // 控制字
ec_slave[1].SM[0].PDO[0].PDOentry[0].subindex = 0;
ec_slave[1].SM[0].PDO[0].PDOentry[0].bitlength = 16;

ec_slave[1].SM[0].PDO[0].PDOentry[1].index = 0x607A;  // 目标位置
ec_slave[1].SM[0].PDO[0].PDOentry[1].subindex = 0;
ec_slave[1].SM[0].PDO[0].PDOentry[1].bitlength = 16;

// 步骤4:应用映射配置
ec_config_map(&ec_slave[1]);

警告:手动修改PDO映射后,必须调用ec_config_map()ec_config_overlap_map()来生效。直接修改结构体数组是不行的,SOEM不会自动检测这些变化。

4.5 避坑指南——我踩过的几个雷

这些年调试SOEM,PDO映射这块我栽过不少跟头。挑几个典型的说说:

  • bitlength别搞错:PDO条目里的bitlength是以位为单位的。16位数据就填16,不是2字节。我一开始老写成2,结果数据死活不对。
  • SM类型别混淆:SMtype=2是输出(主站到从站),SMtype=3是输入(从站到主站)。搞反了的话,数据流方向就错了。
  • PDO映射数量有限制:每个SM通道能映射的PDO条目数是有限的,具体看从站手册。别一股脑往里塞,超过了从站会报错。
  • 映射顺序要一致:主站和从站的PDO映射顺序必须一一对应。我曾经在两个项目里用了同一个配置模板,结果从站型号不同,映射顺序反了,调试了一下午才发现。

小技巧:调试PDO映射时,可以用ec_print_slave_info()函数打印从站的详细信息,包括当前PDO映射表。这比对着手册猜要快得多。

4.6 知识体系总览

为了让你对整个流程有个直观认识,我画了张图。这张图展示了从调用ec_config()到最终进入Op状态的完整路径:

ec_config() 执行流程与PDO映射配置 ec_config() 调用 参数:usefoe, useeoe 扫描总线,读取从站EEPROM 厂商ID、产品码、SM配置、PDO默认映射 状态机推进:Init → Pre-Op 邮箱通信建立,SDO可访问 PDO映射配置(关键决策点) 自动映射(默认) vs 手动映射(ec_config_map) 返回从站数量 ec_slavecount ec_slave[] 数组填充 手动映射需 调用额外函数

这张图里,最关键的节点就是那个黄色高亮的“PDO映射配置”。自动映射省事,但灵活度不够;手动映射麻烦,但能精确控制。怎么选?我的建议是:前期调试用自动映射快速验证通信,后期优化时再切到手动映射。

好了,关于ec_config()和PDO映射,核心内容就这些。记住一点:配置只是第一步,调试才是重头戏。下一节咱们会聊怎么用SOEM的调试工具去抓包分析,到时候你就知道这些配置到底有没有生效了。


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