EtherCAT主站开发(学习笔记五)
目录
前言
一、ecx_init_context:
参数说明
函数功能
二、ecx_detect_slaves:
参数说明
函数功能
三、ecx_set_slaves_to_default / ecx_lookup_prev_sii:
3.1参数说明
3.1函数功能
3.2参数说明
3.2函数功能
四、主要函数接口:
1.ecx_config_init:
参数说明
函数功能
2.ecx_config_map_group:
参数说明
函数功能
3.ecx_recover_slave:
参数说明
函数功能
参数说明
函数功能
总结
前言
上文针对SOEM中ec_base.c文件进行了系统的学习,总结了底层数据通信接口的代码解读,今天就针对ec_config.c文件进行学习,首先进行一个大致的了解,ec_config.c主要负责EtherCAT网络配置与初始化的核心实现文件,封装了协议中与“网络扫描、从站配置、通信参数初始化”相关核心逻辑,是主站与从站建立通信的桥梁。简单来说,主站启动后,必须通过此文件函数接口完成“发现从站–配置凑够站–建立通信链路”全过程,否则无法与从站交换数据。下面开始学习函数接口:
一、ecx_init_context:
void ecx_init_context(ecx_contextt *context)
{
int lp;//循环计数器,用于遍历通信组
//初始化从站数量
context->slavecount = 0;
//清空从站列表
memset(context->slavelist, 0x00, sizeof(context->slavelist));
//清空通信组列表
memset(context->grouplist, 0x00, sizeof(context->grouplist));
//清空从站EEPROM缓存
ecx_siigetbyte(context, 0, EC_MAXEEPBUF);
//初始化所有通信组,
for (lp = 0; lp < EC_MAXGROUP; lp++)
{
//设置默认的逻辑起始地址
//逻辑地址用于PDO数据映射(数据交互的内存地址)
//这里按照组号进行偏移,确保地址不重叠
//EC_LOGGROUPOFFSET默认偏移量0x1000
context->grouplist[lp].logstartaddr = lp << EC_LOGGROUPOFFSET;
//初始化邮箱队列,用来交互非实时性数据
ecx_initmbxqueue(context, lp);
}
}
参数说明
ecx_contextt,上下文结构体,里面存储从站列表、通信组配置、邮箱队列、分布式时钟参数等信息,所有SOEM核心操作都需要通过这个结构体访问和修改主站状态
函数功能
这个接口主要是针对上下文结构体做初始化,确保整个工作流程开始前有一个干净的“核心配置容器”。
二、ecx_detect_slaves:
int ecx_detect_slaves(ecx_contextt *context)
{
uint8 b;//存储单字节数据
uint16 w;//存储双字节数据
int wkc;//工作计数器
b = 0x00;
//广播写,在ECT_REG_DLALIAS(别名计数器)写入0x00,让从站忽略别名计数器,使用默认地址响应,避免地址冲突
ecx_BWR(&context->port, 0x0000, ECT_REG_DLALIAS, sizeof(b), &b, EC_TIMEOUTRET3);
//从站状态值 初始状态|确认状态
w = htoes(EC_STATE_INIT | EC_STATE_ACK);
//广播写,在所有从站别名控制寄存器写入初始状态
ecx_BWR(&context->port, 0x0000, ECT_REG_ALCTL, sizeof(w), &w, EC_TIMEOUTRET3);
//再次发送相同命令,确保老旧的芯片从站正确响应
ecx_BWR(&context->port, 0x0000, ECT_REG_ALCTL, sizeof(w), &w, EC_TIMEOUTRET3);
//广播读,读取所有从站的(ECT_REG_TYPE)类型计数器,统计从站数量
wkc = ecx_BRD(&context->port, 0x0000, ECT_REG_TYPE, sizeof(w), &w, EC_TIMEOUTSAFE);
if (wkc > 0)
{
//EC_MAXSLAVE从站最大数量,主站通常被认为是“slave0”,所以这里是小于,最大从站数EC_MAXSLAVE-1
if (wkc < EC_MAXSLAVE)
{
context->slavecount = wkc;//从站有效,记录到上下文结构体
}
else
{
//超出数量,打印错误信息
EC_PRINT("Error: too many slaves on network: num_slaves=%d, max_slaves=%d
",
wkc, EC_MAXSLAVE);
//返回错误码
return EC_SLAVECOUNTEXCEEDED;
}
}
return wkc;//返回从站数量
}
参数说明
上下文结构体,更新数据
函数功能
该接口主要实现监测EtherCAT总线上已连接的从站数量,通过广播,重置从站到初始状态,并读取反馈回的总站数量,记录到上下文结构体中,wkc=0,无从站;wkc=-1,从站数量超出限制。
三、ecx_set_slaves_to_default / ecx_lookup_prev_sii:
static void ecx_set_slaves_to_default(ecx_contextt *context)
{
uint8 b; // 8位单字节变量(用于单个字节寄存器操作)
uint16 w; // 16位双字节变量(用于双字节寄存器操作)
uint8 zbuf[64]; // 64字节全零缓冲区(用于批量重置多字节寄存器)
// 1. 初始化全零缓冲区:将zbuf所有字节置0,后续用于"清零重置"寄存器
memset(&zbuf, 0x00, sizeof(zbuf));
// 2. 禁用手动循环模式(DLPORT寄存器)
b = 0x00;
ecx_BWR(&context->port, 0x0000, ECT_REG_DLPORT, sizeof(b), &b, EC_TIMEOUTRET3);
// 作用:EtherCAT从站支持"循环模式"(用于总线拓扑扩展),这里禁用手动控制,让从站按默认方式工作
// 3. 设置中断掩码(IRQMASK寄存器)
w = htoes(0x0004); // 0x0004是默认中断掩码(只允许特定中断触发)
ecx_BWR(&context->port, 0x0000, ECT_REG_IRQMASK, sizeof(w), &w, EC_TIMEOUTRET3);
// 作用:限制从站的中断触发条件,避免无关中断干扰总线通信
// 4. 重置CRC错误计数器(RXERR寄存器,8字节)
ecx_BWR(&context->port, 0x0000, ECT_REG_RXERR, 8, &zbuf, EC_TIMEOUTRET3);
// 作用:清零从站的接收错误统计(如CRC校验失败次数),避免历史错误状态影响新配置
// 5. 重置FMMU(现场总线内存管理单元,3个FMMU,每个16字节,共48字节)
ecx_BWR(&context->port, 0x0000, ECT_REG_FMMU0, 16 * 3, &zbuf, EC_TIMEOUTRET3);
// 关键:FMMU负责将从站的物理内存(PDO数据区)映射到EtherCAT逻辑地址
// 重置后清除之前的映射配置,后续需要重新配置FMMU才能进行数据交互
// 6. 重置同步管理器(Sync Manager,4个SM,每个8字节,共32字节)
ecx_BWR(&context->port, 0x0000, ECT_REG_SM0, 8 * 4, &zbuf, EC_TIMEOUTRET3);
// 关键:同步管理器是从站的数据收发缓冲区(如SM0用于邮箱通信,SM1用于PDO实时数据)
// 重置后所有SM的配置(如缓冲区地址、大小、类型)被清空,后续需重新配置
// 7. 重置分布式时钟(DC)激活寄存器(DCSYNCACT)
b = 0x00;
ecx_BWR(&context->port, 0x0000, ECT_REG_DCSYNCACT, sizeof(b), &b, EC_TIMEOUTRET3);
// 作用:禁用DC同步功能的激活状态,后续需单独配置DC并激活
// 8. 重置DC系统时间和偏移量(DCSYSTIME,4字节)
ecx_BWR(&context->port, 0x0000, ECT_REG_DCSYSTIME, 4, &zbuf, EC_TIMEOUTRET3);
// 作用:清零从站的DC时间计数器和偏移量,确保所有从站时间起点一致
// 9. 设置DC速度启动参数(DCSPEEDCNT)
w = htoes(0x1000); // 0x1000是默认的DC同步速度参数(控制时间同步的收敛速度)
ecx_BWR(&context->port, 0x0000, ECT_REG_DCSPEEDCNT, sizeof(w), &w, EC_TIMEOUTRET3);
// 10. 设置DC时间滤波器参数(DCTIMEFILT)
w = htoes(0x0c00); // 0x0c00是默认的滤波器参数(减少DC时间抖动,提升同步精度)
ecx_BWR(&context->port, 0x0000, ECT_REG_DCTIMEFILT, sizeof(w), &w, EC_TIMEOUTRET3);
// 11. 忽略别名寄存器(DLALIAS)
b = 0x00;
ecx_BWR(&context->port, 0x0000, ECT_REG_DLALIAS, sizeof(b), &b, EC_TIMEOUTRET3);
// 作用:和之前detect_slaves函数逻辑一致,让从站使用默认物理地址,避免地址冲突
// 12. 重置所有从站到INIT+ACK状态(ALCTL寄存器)
w = htoes(EC_STATE_INIT | EC_STATE_ACK);
ecx_BWR(&context->port, 0x0000, ECT_REG_ALCTL, sizeof(w), &w, EC_TIMEOUTRET3);
// 关键:EtherCAT从站的状态机初始化,INIT是初始状态,ACK表示确认命令,确保从站就绪
// 13. 强制从PDO读取EEPROM配置(EEPCFG寄存器)
b = 2;
ecx_BWR(&context->port, 0x0000, ECT_REG_EEPCFG, sizeof(b), &b, EC_TIMEOUTRET3);
// 作用:让从站先从自身PDI(过程数据接口)读取EEPROM中的默认配置(兼容性处理)
// 14. 恢复为主站控制EEPROM(EEPCFG寄存器)
b = 0;
ecx_BWR(&context->port, 0x0000, ECT_REG_EEPCFG, sizeof(b), &b, EC_TIMEOUTRET3);
// 关键:最终将EEPROM控制权交给主站,后续主站可通过ESI文件配置从站(如PDO映射)
}
3.1参数说明
上下文结构体
3.1函数功能
这个接口实现所有从站关键寄存器重置为默认值,相当于进行了“出厂设置”,清除之前的配置残留,统一初始化关键功能模块(中断,FMMU,同步管理器,分布式时钟等)
static int ecx_lookup_prev_sii(ecx_contextt *context, uint16 slave)
{
int i, nSM; // i:循环计数器(遍历已配置从站);nSM:同步管理器(SM)的索引
// 1. 前置条件判断:
// - slave>1:当前从站编号必须大于1(编号1是第一个从站,没有前辈可复用);
// - context->slavecount>0:总线上已检测到从站(避免空指针或无效遍历)
if ((slave > 1) && (context->slavecount > 0))
{
i = 1; // 从第一个从站(编号1)开始遍历查找
// 2. 循环查找“前辈从站”:
// 遍历范围:从站1 ~ 当前从站slave的前一个(i < slave)
// 匹配条件:厂商ID(eep_man)、产品ID(eep_id)、版本号(eep_rev)完全一致
while (((context->slavelist[i].eep_man != context->slavelist[slave].eep_man) ||
(context->slavelist[i].eep_id != context->slavelist[slave].eep_id) ||
(context->slavelist[i].eep_rev != context->slavelist[slave].eep_rev)) &&
(i < slave))
{
i++; // 不匹配则继续查找下一个从站
}
// 3. 找到匹配的前辈从站(i < slave 说明在遍历范围内找到)
if (i < slave)
{
// 4. 复用前辈从站的SII配置数据(核心逻辑:直接拷贝字段)
// 4.1 复用各类通信细节(CoE/FoE/EoE/SoE协议相关配置)
context->slavelist[slave].CoEdetails = context->slavelist[i].CoEdetails;
context->slavelist[slave].FoEdetails = context->slavelist[i].FoEdetails;
context->slavelist[slave].EoEdetails = context->slavelist[i].EoEdetails;
context->slavelist[slave].SoEdetails = context->slavelist[i].SoEdetails;
// 4.2 复用LRW块传输配置(LRW:Logical Read Write,逻辑读写,用于高效数据传输)
if (context->slavelist[i].blockLRW > 0)
{
context->slavelist[slave].blockLRW = 1; // 当前从站启用LRW
context->slavelist[0].blockLRW++; // 主站(slave0)统计启用LRW的从站数
}
// 4.3 复用从站总线电流参数(Ebuscurrent:从站消耗的总线电流,用于电源规划)
context->slavelist[slave].Ebuscurrent = context->slavelist[i].Ebuscurrent;
context->slavelist[0].Ebuscurrent += context->slavelist[slave].Ebuscurrent; // 主站累加总电流
// 4.4 复用从站名称(如"EtherCAT Slave XXX")
memcpy(context->slavelist[slave].name, context->slavelist[i].name, EC_MAXNAME + 1);
// EC_MAXNAME是从站名称最大长度,+1是为了拷贝字符串结束符''
// 4.5 复用所有同步管理器(SM)的配置(EC_MAXSM默认是4个SM)
for (nSM = 0; nSM < EC_MAXSM; nSM++)
{
context->slavelist[slave].SM[nSM].StartAddr = context->slavelist[i].SM[nSM].StartAddr; // 起始地址
context->slavelist[slave].SM[nSM].SMlength = context->slavelist[i].SM[nSM].SMlength; // 缓冲区长度
context->slavelist[slave].SM[nSM].SMflags = context->slavelist[i].SM[nSM].SMflags; // 工作模式标志(如读写、实时性)
}
// 4.6 复用FMMU(现场总线内存管理单元)的功能配置(4个FMMU的功能类型)
context->slavelist[slave].FMMU0func = context->slavelist[i].FMMU0func;
context->slavelist[slave].FMMU1func = context->slavelist[i].FMMU1func;
context->slavelist[slave].FMMU2func = context->slavelist[i].FMMU2func;
context->slavelist[slave].FMMU3func = context->slavelist[i].FMMU3func;
// 打印日志:提示当前从站复用了哪个前辈从站的SII配置
EC_PRINT("Copy SII slave %d from %d.
", slave, i);
return 1; // 返回1:表示成功复用SII配置
}
}
return 0; // 返回0:表示未找到匹配的前辈从站,需要后续手动解析SII
}
3.2参数说明
slave:当前需要配置的从站编号(从站地址,从1开始)
context->slavelist[ ]:存储所有从站信息的数组,每个元素对应一个从站的完整配置
eep_man/eep_id/eep_rev:从站EEPROM中的厂商ID、产品ID、版本号(三者唯一标识一个从站型号)
3.2函数功能
该接口核心作用是查找总线上已经配置过的,与当前从站型号一致,并直接复用其从站配置数据,避免重复读取和解析从站配置信息,大幅度提高相同从站网络配置效率。
后续的一些基本函数不再进行总结,在其他时间可继续研读源码,下面主要针对核心接口做一个详细学习。
四、主要函数接口:
1.ecx_config_init:
int ecx_config_init(ecx_contextt *context)
{
uint16 slave, ADPh, configadr, ssigen;
uint16 topology, estat;
int16 topoc, slavec, aliasadr;
uint8 b, h;
uint8 SMc;
uint32 eedat;
int wkc, nSM;
uint16 val16;
//打印日志,标志配置开始
EC_PRINT("ec_config_init
");
//初始化上下文结构体
ecx_init_context(context);
//检测总线上从站数量
wkc = ecx_detect_slaves(context);
if (wkc > 0)//检测到从站后继续进行配置
{
//重置所有从站到默认状态
ecx_set_slaves_to_default(context);
//嵌套循环,将所有从站进行逐一配置
for (slave = 1; slave <= context->slavecount; slave++)
{
ADPh = (uint16)(1 - slave);
//读取从站接口类型
val16 = ecx_APRDw(&context->port, ADPh, ECT_REG_PDICTL, EC_TIMEOUTRET3); /* read interface type of slave */
context->slavelist[slave].Itype = etohs(val16);
//给从站分配唯一节点地址
ecx_APWRw(&context->port, ADPh, ECT_REG_STADR, htoes(slave + EC_NODEOFFSET), EC_TIMEOUTRET3); /* set node address of slave */
if (slave == 1)
{
b = 1; /* kill non ecat frames for first slave */
}
else
{
b = 0; /* pass all frames for following slaves */
}
//读取并存储从站配置信息
ecx_APWRw(&context->port, ADPh, ECT_REG_DLCTL, htoes(b), EC_TIMEOUTRET3); /* set non ecat frame behaviour */
configadr = ecx_APRDw(&context->port, ADPh, ECT_REG_STADR, EC_TIMEOUTRET3);
configadr = etohs(configadr);
context->slavelist[slave].configadr = configadr;
//读取从站别名地址、EEPROM状态
ecx_FPRD(&context->port, configadr, ECT_REG_ALIAS, sizeof(aliasadr), &aliasadr, EC_TIMEOUTRET3);
context->slavelist[slave].aliasadr = etohs(aliasadr);
ecx_FPRD(&context->port, configadr, ECT_REG_EEPSTAT, sizeof(estat), &estat, EC_TIMEOUTRET3);
estat = etohs(estat);
if (estat & EC_ESTAT_R64) /* check if slave can read 8 byte chunks */
{
context->slavelist[slave].eep_8byte = 1;//标记从站EEPROM支持8字节读取
}
ecx_readeeprom1(context, slave, ECT_SII_MANUF); /* Manuf */
}
//循环读取厂商ID eep_man
for (slave = 1; slave <= context->slavecount; slave++)
{
eedat = ecx_readeeprom2(context, slave, EC_TIMEOUTEEP); /* Manuf */
context->slavelist[slave].eep_man = etohl(eedat);
ecx_readeeprom1(context, slave, ECT_SII_ID); /* ID */
}
//循环读取产品ID eep_id
for (slave = 1; slave <= context->slavecount; slave++)
{
eedat = ecx_readeeprom2(context, slave, EC_TIMEOUTEEP); /* ID */
context->slavelist[slave].eep_id = etohl(eedat);
ecx_readeeprom1(context, slave, ECT_SII_REV); /* revision */
}
//循环读取版本号 eep_rev
for (slave = 1; slave <= context->slavecount; slave++)
{
eedat = ecx_readeeprom2(context, slave, EC_TIMEOUTEEP); /* revision */
context->slavelist[slave].eep_rev = etohl(eedat);
ecx_readeeprom1(context, slave, ECT_SII_SER); /* serial number */
}
//循环读取序列号 eep_ser
for (slave = 1; slave <= context->slavecount; slave++)
{
eedat = ecx_readeeprom2(context, slave, EC_TIMEOUTEEP); /* serial number */
context->slavelist[slave].eep_ser = etohl(eedat);
ecx_readeeprom1(context, slave, ECT_SII_RXMBXADR); /* write mailbox address + mailboxsize */
}
循环读取邮箱配置(写邮箱地址、长度,读邮箱地址、长度)
for (slave = 1; slave <= context->slavecount; slave++)
{
eedat = ecx_readeeprom2(context, slave, EC_TIMEOUTEEP); /* write mailbox address and mailboxsize */
context->slavelist[slave].mbx_wo = (uint16)LO_WORD(etohl(eedat));
context->slavelist[slave].mbx_l = (uint16)HI_WORD(etohl(eedat));
if (context->slavelist[slave].mbx_l > 0)
{
ecx_readeeprom1(context, slave, ECT_SII_TXMBXADR); /* read mailbox offset */
}
}
for (slave = 1; slave <= context->slavecount; slave++)
{
if (context->slavelist[slave].mbx_l > 0)
{
eedat = ecx_readeeprom2(context, slave, EC_TIMEOUTEEP); /* read mailbox offset */
context->slavelist[slave].mbx_ro = (uint16)LO_WORD(etohl(eedat)); /* read mailbox offset */
context->slavelist[slave].mbx_rl = (uint16)HI_WORD(etohl(eedat)); /*read mailbox length */
if (context->slavelist[slave].mbx_rl == 0)
{
context->slavelist[slave].mbx_rl = context->slavelist[slave].mbx_l;
}
ecx_readeeprom1(context, slave, ECT_SII_MBXPROTO);
}
configadr = context->slavelist[slave].configadr;
val16 = ecx_FPRDw(&context->port, configadr, ECT_REG_ESCSUP, EC_TIMEOUTRET3);
if ((etohs(val16) & 0x04) > 0) /* Support DC? */
{
context->slavelist[slave].hasdc = TRUE;
}
else
{
context->slavelist[slave].hasdc = FALSE;
}
//读取从站端口状态(查看哪些端口已连接)
topology = ecx_FPRDw(&context->port, configadr, ECT_REG_DLSTAT, EC_TIMEOUTRET3); /* extract topology from DL status */
topology = etohs(topology);
h = 0;
b = 0;
if ((topology & 0x0300) == 0x0200) /* port0 open and communication established */
{
h++;
b |= 0x01;
}//port0连接
if ((topology & 0x0c00) == 0x0800) /* port1 open and communication established */
{
h++;
b |= 0x02;
}//port1连接
if ((topology & 0x3000) == 0x2000) /* port2 open and communication established */
{
h++;
b |= 0x04;
}//port2连接
if ((topology & 0xc000) == 0x8000) /* port3 open and communication established */
{
h++;
b |= 0x08;
}//port3连接
/* ptype = Physical type*/
val16 = ecx_FPRDw(&context->port, configadr, ECT_REG_PORTDES, EC_TIMEOUTRET3);
context->slavelist[slave].ptype = LO_BYTE(etohs(val16));
context->slavelist[slave].topology = h;//连接的端口数,1:终端从站,2:中间从站
context->slavelist[slave].activeports = b;//活跃端口掩码
/* 0=no links, not possible */
/* 1=1 link , end of line */
/* 2=2 links , one before and one after */
/* 3=3 links , split point */
/* 4=4 links , cross point */
//查找父节点
context->slavelist[slave].parent = 0; //默认父节点为主站
if (slave > 1)
{
topoc = 0;
slavec = slave - 1;
do
{
topology = context->slavelist[slavec].topology;
if (topology == 1)
{
topoc--; /* endpoint found */
}
if (topology == 3)
{
topoc++; /* split found */
}
if (topology == 4)
{
topoc += 2; /* cross found */
}
if (((topoc >= 0) && (topology > 1)) ||
(slavec == 1)) /* parent found */
{
context->slavelist[slave].parent = slavec;
slavec = 1;
}
slavec--;
} while (slavec > 0);
}
(void)ecx_statecheck(context, slave, EC_STATE_INIT, EC_TIMEOUTSTATE); //* check state change Init */
//初始化邮箱和同步管理器SM
if (context->slavelist[slave].mbx_l > 0)
{
// 配置SM类型(SM0=写邮箱,SM1=读邮箱,SM2=过程数据输入,SM3=过程数据输出)
context->slavelist[slave].SMtype[0] = 1;
context->slavelist[slave].SMtype[1] = 2;
context->slavelist[slave].SMtype[2] = 3;
context->slavelist[slave].SMtype[3] = 4;
// 配置SM0(主站→从站:写邮箱)
context->slavelist[slave].SM[0].StartAddr = htoes(context->slavelist[slave].mbx_wo);
context->slavelist[slave].SM[0].SMlength = htoes(context->slavelist[slave].mbx_l);
context->slavelist[slave].SM[0].SMflags = htoel(EC_DEFAULTMBXSM0);
// 配置SM1(从站→主站:读邮箱)
context->slavelist[slave].SM[1].StartAddr = htoes(context->slavelist[slave].mbx_ro);
context->slavelist[slave].SM[1].SMlength = htoes(context->slavelist[slave].mbx_rl);
context->slavelist[slave].SM[1].SMflags = htoel(EC_DEFAULTMBXSM1);
eedat = ecx_readeeprom2(context, slave, EC_TIMEOUTEEP);
context->slavelist[slave].mbx_proto = (uint16)etohl(eedat);
}
//复用SII或解析SII
if (!ecx_lookup_prev_sii(context, slave))//找不到就解析
{
//解析通用配置
ssigen = ecx_siifind(context, slave, ECT_SII_GENERAL);
/* SII general section */
if (ssigen)
{
context->slavelist[slave].CoEdetails = ecx_siigetbyte(context, slave, ssigen + 0x07);
context->slavelist[slave].FoEdetails = ecx_siigetbyte(context, slave, ssigen + 0x08);
context->slavelist[slave].EoEdetails = ecx_siigetbyte(context, slave, ssigen + 0x09);
context->slavelist[slave].SoEdetails = ecx_siigetbyte(context, slave, ssigen + 0x0a);
if ((ecx_siigetbyte(context, slave, ssigen + 0x0d) & 0x02) > 0)
{
context->slavelist[slave].blockLRW = 1;
context->slavelist[0].blockLRW++;
}
context->slavelist[slave].Ebuscurrent = ecx_siigetbyte(context, slave, ssigen + 0x0e);
context->slavelist[slave].Ebuscurrent += ecx_siigetbyte(context, slave, ssigen + 0x0f) << 8;
context->slavelist[0].Ebuscurrent += context->slavelist[slave].Ebuscurrent;
}
//解析从站名称
if (ecx_siifind(context, slave, ECT_SII_STRING) > 0)
{
ecx_siistring(context, context->slavelist[slave].name, slave, 1);
}
/* no name for slave found, use constructed name */
else
{
//未找到就用厂商ID+产品ID默认组合
sprintf(context->slavelist[slave].name, "? M:%8.8x I:%8.8x",
(unsigned int)context->slavelist[slave].eep_man,
(unsigned int)context->slavelist[slave].eep_id);
}
解析同步管理器SM配置
nSM = ecx_siiSM(context, slave, &context->eepSM);
if (nSM > 0)
{
context->slavelist[slave].SM[0].StartAddr = htoes(context->eepSM.PhStart);
context->slavelist[slave].SM[0].SMlength = htoes(context->eepSM.Plength);
context->slavelist[slave].SM[0].SMflags =
htoel((context->eepSM.Creg) + (context->eepSM.Activate << 16));
SMc = 1;
while ((SMc < EC_MAXSM) && ecx_siiSMnext(context, slave, &context->eepSM, SMc))
{
context->slavelist[slave].SM[SMc].StartAddr = htoes(context->eepSM.PhStart);
context->slavelist[slave].SM[SMc].SMlength = htoes(context->eepSM.Plength);
context->slavelist[slave].SM[SMc].SMflags =
htoel((context->eepSM.Creg) + (context->eepSM.Activate << 16));
SMc++;
}
}
/* SII FMMU section */
if (ecx_siiFMMU(context, slave, &context->eepFMMU))
{
if (context->eepFMMU.FMMU0 != 0xff)
{
context->slavelist[slave].FMMU0func = context->eepFMMU.FMMU0;
}
if (context->eepFMMU.FMMU1 != 0xff)
{
context->slavelist[slave].FMMU1func = context->eepFMMU.FMMU1;
}
if (context->eepFMMU.FMMU2 != 0xff)
{
context->slavelist[slave].FMMU2func = context->eepFMMU.FMMU2;
}
if (context->eepFMMU.FMMU3 != 0xff)
{
context->slavelist[slave].FMMU3func = context->eepFMMU.FMMU3;
}
}
}
if (context->slavelist[slave].mbx_l > 0)//容错处理,一般不存在
{
if (context->slavelist[slave].SM[0].StartAddr == 0x0000) /* should never happen */
{
EC_PRINT("Slave %d has no proper mailbox in configuration, try default.
", slave);
context->slavelist[slave].SM[0].StartAddr = htoes(0x1000);
context->slavelist[slave].SM[0].SMlength = htoes(0x0080);
context->slavelist[slave].SM[0].SMflags = htoel(EC_DEFAULTMBXSM0);
context->slavelist[slave].SMtype[0] = 1;
}
if (context->slavelist[slave].SM[1].StartAddr == 0x0000) /* should never happen */
{
EC_PRINT("Slave %d has no proper mailbox out configuration, try default.
", slave);
context->slavelist[slave].SM[1].StartAddr = htoes(0x1080);
context->slavelist[slave].SM[1].SMlength = htoes(0x0080);
context->slavelist[slave].SM[1].SMflags = htoel(EC_DEFAULTMBXSM1);
context->slavelist[slave].SMtype[1] = 2;
}
/* program SM0 mailbox in and SM1 mailbox out for slave */
/* writing both SM in one datagram will solve timing issue in old NETX */
ecx_FPWR(&context->port, configadr, ECT_REG_SM0, sizeof(ec_smt) * 2,
&(context->slavelist[slave].SM[0]), EC_TIMEOUTRET3);
}
//让从站将EEPROM配置加载到PDI(过程数据接口)
ecx_eeprom2pdi(context, slave);
/* User may override automatic state change */
if (context->manualstatechange == 0)
{
/* request pre_op for slave */
ecx_FPWRw(&context->port,
configadr,
ECT_REG_ALCTL,
htoes(EC_STATE_PRE_OP | EC_STATE_ACK),
EC_TIMEOUTRET3); /* set preop status */
}
}
}
return wkc;
}
参数说明
上下文结构体
函数功能
这个接口是核心初始化接口,在工程实际应用中主要是调用这个接口来进行从站初始化,ecx_config_init()是SOEM网络配置的“总入口”核心逻辑是“所有从站逐个精细化配置”:地址分配–身份读取–拓扑建立–通信模块配置–状态切换,最终让所有从站进入PRE_OP状态,为后续PDO映射和运行做准备。
2.ecx_config_map_group:
int ecx_config_map_group(ecx_contextt *context, void *pIOmap, uint8 group)
{
//判断主站是否启用“重叠模式”
if (context->overlappedMode)
{
//调用对应的重叠模式PDO映射函数
return ecx_config_overlap_map_group(context, pIOmap, group);
}
//普通模式:调用标准的PDO映射函数
return ecx_main_config_map_group(context, pIOmap, group);
}
参数说明
context:主站上下文
pIOmap:主站侧I/O映射缓冲区指针(最终映射后的PDO数据会通过该缓冲区读写)
group:目标通信组编号
函数功能
这个接口主要是根据主站的工作模式,将PDO分组映射任务分发给对应的具体实现函数
| 模式类型 | 对应实现函数 | 核心特点 |
| 普通模式 | ecx_main_config_map_group() | 单线程同步执行映射,逻辑简单、稳定;映射过程中总线暂时无法进行其他数据交互 |
| 重叠模式 | ecx_config_overlap_map_group() | 异步重叠执行映射,映射过程中可并行处理其他总线操作(如从站状态查询);逻辑更复杂 |
无论是哪种模式,最中都完成相同的核心PDO映射工作:
遍历指定组内所有从站;分配PDO逻辑地址(确保连续不冲突);配置从站FMMU(建立逻辑地址->物理内存的映射);初始化pIOmap缓冲区(记录PDO数据的地址偏移,长度等);返回映射结果(EC_OK表示成功,其他表示失败)
3.ecx_recover_slave:
int ecx_recover_slave(ecx_contextt *context, uint16 slave, int timeout)
{
int rval;//返回值(标记恢复结果)
int wkc;//工作计数器
uint16 ADPh, configadr, readadr;
rval = 0;//初始化为0,失败
//读取故障从站原始配置地址
configadr = context->slavelist[slave].configadr;
//计算特殊地址偏移(用于定位故障从站)
ADPh = (uint16)(1 - slave);
//初始化地址为无效值
readadr = 0xfffe;
//向故障从站发送寻址读命令,读取当前配置地址
wkc = ecx_APRD(&context->port, ADPh, ECT_REG_STADR, sizeof(readadr), &readadr, timeout);
if (readadr == configadr)//若读到的地址==原始配置地址,说明从站未故障,直接返回成功
{
return 1;
}
//总线操作成功且读到的地址为0时才进行后续操作
if ((wkc > 0) && (readadr == 0))
{
//清除可能占用临时地址的其他设备
ecx_FPWRw(&context->port, EC_TEMPNODE, ECT_REG_STADR, htoes(0), 0);
//给故障从站分配临时地址,保障通信
if (ecx_APWRw(&context->port, ADPh, ECT_REG_STADR, htoes(EC_TEMPNODE), timeout) <= 0)
{
//若分配失败清除临时地址
ecx_FPWRw(&context->port, EC_TEMPNODE, ECT_REG_STADR, htoes(0), 0);
return 0; /* slave fails to respond */
}
//分配成功,更新上下文结构体中从站地址,后续使用临时地址进行通信
context->slavelist[slave].configadr = EC_TEMPNODE; /* temporary config address */
ecx_eeprom2master(context, slave); //将EEPROM控制权交还给主站
验证从站信息,是否是要恢复的从站
if ((ecx_FPRDw(&context->port, EC_TEMPNODE, ECT_REG_ALIAS, timeout) ==
htoes(context->slavelist[slave].aliasadr)) &&
(ecx_readeeprom(context, slave, ECT_SII_ID, EC_TIMEOUTEEP) ==
htoel(context->slavelist[slave].eep_id)) &&
(ecx_readeeprom(context, slave, ECT_SII_MANUF, EC_TIMEOUTEEP) ==
htoel(context->slavelist[slave].eep_man)) &&
(ecx_readeeprom(context, slave, ECT_SII_REV, EC_TIMEOUTEEP) ==
htoel(context->slavelist[slave].eep_rev)))
{
//验证成功,将临时地址切换为原始配置地址
rval = ecx_FPWRw(&context->port, EC_TEMPNODE, ECT_REG_STADR, htoes(configadr), timeout);
//更新上下文
context->slavelist[slave].configadr = configadr;
}
else
{
/* slave is not the expected one, remove config address*/
ecx_FPWRw(&context->port, EC_TEMPNODE, ECT_REG_STADR, htoes(0), timeout);
context->slavelist[slave].configadr = configadr;
}
}
return rval;//返回恢复结果
}
参数说明
context:主站上下文结构体
slave:待恢复的从站编号
函数功能
该接口的核心作用是恢复“地址丢失或通信中断”的故障从站,当从站因干扰、离线等原因丢失原配置地址时,通过“临时地址绑定-身份验证-恢复原地址”的流程,让从站重新接入网络,无需重新解析SII或重构PDO,实现快速恢复。
4.ecx_reconfig_slave:
int ecx_reconfig_slave(ecx_contextt *context, uint16 slave, int timeout)
{
int state, nSM, FMMUc;//从站当前状态,sm索引,FMMU索引
uint16 configadr;//从站配置地址
//读取从站配置地址
configadr = context->slavelist[slave].configadr;
//强制将从站状态设置为INIT
if (ecx_FPWRw(&context->port, configadr, ECT_REG_ALCTL, htoes(EC_STATE_INIT), timeout) <= 0)
{
return 0;
}
state = 0;
//将从站的EEPROM控制权交给PDI
ecx_eeprom2pdi(context, slave); /* set Eeprom control to PDI */
//等待从站切换到INIT状态
state = ecx_statecheck(context, slave, EC_STATE_INIT, EC_TIMEOUTSTATE);
if (state == EC_STATE_INIT)
{
//配置所有已启用的sm同步管理器
for (nSM = 0; nSM < EC_MAXSM; nSM++)
{
//只配置已启用的
if (context->slavelist[slave].SM[nSM].StartAddr)
{
//将上下文中的SM配置写入从站
ecx_FPWR(&context->port, configadr, (uint16)(ECT_REG_SM0 + (nSM * sizeof(ec_smt))),
sizeof(ec_smt), &context->slavelist[slave].SM[nSM], timeout);
}
}
/* small delay to allow slave to process SM changes */
osal_usleep(5000);
//将从站状态更新到PRE_OP(预操作状态)
ecx_FPWRw(&context->port, configadr, ECT_REG_ALCTL, htoes(EC_STATE_PRE_OP), timeout);
//等待从站状态切换
state = ecx_statecheck(context, slave, EC_STATE_PRE_OP, EC_TIMEOUTSTATE); /* check state change pre-op */
if (state == EC_STATE_PRE_OP)
{
//若启用了ENI配置文件,执行相应命令
//ENI是预定义的网络配置文件,可存储复杂的从站配置命令
if (context->ENI)
{
(void)ecx_mbxENIinitcmds(context, slave, ECT_ESMTRANS_PS);
}
/* execute slave configuration hook Pre-Op to Safe-OP */
if (context->slavelist[slave].PO2SOconfig) /* only if registered */
{
context->slavelist[slave].PO2SOconfig(context, slave);
}
//将从站状态更新到SAFE_OP(安全操作状态)
ecx_FPWRw(&context->port, configadr, ECT_REG_ALCTL, htoes(EC_STATE_SAFE_OP), timeout); /* set safeop status */
//等待从站状态切换
state = ecx_statecheck(context, slave, EC_STATE_SAFE_OP, EC_TIMEOUTSTATE); /* check state change safe-op */
//配置所有已启用的FMMU
for (FMMUc = 0; FMMUc < context->slavelist[slave].FMMUunused; FMMUc++)
{
ecx_FPWR(&context->port, configadr, (uint16)(ECT_REG_FMMU0 + (sizeof(ec_fmmut) * FMMUc)),
sizeof(ec_fmmut), &context->slavelist[slave].FMMU[FMMUc], timeout);
}
}
}
return state;
}
参数说明
context:主站上下文结构体
slave:待重置的从站编号
返回值:从站最终到达的状态(如EC_STATE_SAFE_OP=成功,EC_STATE_INIT=中途失败,0=完全无响应)
函数功能
该接口作用是对单个从站执行“完整的二次初始化”,忽略从站当前状态,强制将其重置到初始状态,然后按照标准流程重新配置SM,调用配置钩子函数等操作,最终将进入到SAFE_OP安全操作状态,为切换到运行态做准备。
总结
本章针对SOEM的ec_config.c文件里的API接口进行了学习,主要是做了从站各项初始化配置以及一些故障从站恢复和重置等操作,通过这些函数接口的学习,实现了主从站通信框架的搭建,之后将针ec_main.c文件进行学习总结,今天就写到这吧。