[EtherCAT]从站运行过程中报错(错误码:0x1A\0x1B\0x2C)的代码分析
一、背景
本文将以SSC从站协议栈代码为例,分析EtherCAT从站运行过程中报错(错误码:0x1A\0x1B\0x2C),说明协议栈的检测逻辑。
该类错误为运行时错误,是从站能成功进入OP状态以后,在运行过程中检测到错误,状态从OP退回到更低级状态时报出的错误码。对于从站无法成功进入OP状态的初始化错误不在此分析中。
当从站发生错误时,会切换自身的状态从OP到更低状态,并将错误代码写入从站的0x134寄存器报告AL状态码。

| 错误码 | 说明 |
|---|---|
| 0x1A | Synchronization error(网络的抖动导致从站的同步丢失) |
| 0x1B | Syncmanager watchdog(从站未接收到周期性数据的时间,持续超过了watchdog阈值) |
| 0x2C | Fatal SYNC error(ESC再未收到SYNC硬件中断) |
完整的AL Status Code定义在ecatslv.h中。
二、 SSC协议栈代码分析
不同参数配置下,SSC Tool生成的协议栈代码会有一定不同,本例使用的参数选项包括如下设置:
| 参数 | 说明 |
|---|---|
| ESC_SM_WD_SUPPORTED = 1 | 使用硬件同步管理器看门狗 |
| ECAT_TIMER_INT = 1 | 在软件1ms中断函数中执行ECAT_CheckTimer() |
| AL_EVENT_ENABLED = 1 | 使用PDI中断 |
| DC_SUPPORTED = 1 | 支持DC功能 |
分析协议栈代码,可以发现在运行过程中错误码0x1A\0x1B\0x2C是在CheckIfEcatError()函数中报出,
该函数在mainloop()中被调用,会周期性的执行,负责检查通信和同步变量,并设置AL status(Reg0x130)和AL status code(状态错误码 Reg0x134)。
/////////////////////////////////////////////////////////////////////////////////////////
/**
\brief Checks communication and synchronisation variables and update AL status / AL status code if an error has occurred
*////////////////////////////////////////////////////////////////////////////////////////
void CheckIfEcatError(void)
{
/*if the watchdog is enabled check the process data watchdog in the ESC
and set the AL status code 0x1B if the watchdog expired*/
if (EcatWdValue != 0) // 在从站初始化过程中赋值,默认是100ms
{
/*watchdog time is set => watchdog is active*/
UINT32 WdStatusOK = 0;
HW_EscReadDWord(WdStatusOK, ESC_PD_WD_STATE); // 读0x440寄存器获取硬件WatchDog的状态
WdStatusOK = SWAPDWORD(WdStatusOK);
if (!(WdStatusOK & ESC_PD_WD_TRIGGER_MASK) && (nPdOutputSize > 0)) // 当WatchDog溢出时
{
/*The device is in OP state*/
if (bEcatOutputUpdateRunning
)
{
AL_ControlInd(STATE_SAFEOP, ALSTATUSCODE_SMWATCHDOG); //错误码 0x1B
return;
}
else
{
bEcatFirstOutputsReceived = FALSE;
}
}
}
if(bDcSyncActive)
{
if(bEcatOutputUpdateRunning)
{
/*Slave is in OP state*/
if(!bDcRunning) //判断条件
{
AL_ControlInd(STATE_SAFEOP, ALSTATUSCODE_FATALSYNCERROR); //错误码 0x2C
return;
}
else if(!bSmSyncSequenceValid) // 判断条件
{
AL_ControlInd(STATE_SAFEOP, ALSTATUSCODE_SYNCERROR); //错误码 0x1A
return;
}
}
}
}
2.1 同步管理器看门狗错误码 ALSTATUSCODE_SMWATCHDOG(0x1B)
该错误码是利用ESC内部的硬件看门狗监控主站发给从站的过程数据是否按时被接收到。设定时间内没有接收到主站发来的PDO数据帧,看门狗就会溢出触发报错。
同步管理器看门狗(SM WatchDog)监控同步管理器的状态,默认配置SM2(主站发过程数据给从站)事件触发看门狗。
当从站接收到主站发来的过程数据时触发硬件喂狗,看门狗不会超时。但当从站长时间接收不到主站发来的过程数据帧时,看门狗(默认阈值100ms)就会计数超时,读0x440寄存器获取当前看门狗超时情况进行报错,代码分析见上文CheckIfEcatError()函数。
硬件同步管理器看门狗(SM WatchDog)的阈值和触发设置如下:

出错原因:同步管理器看门狗错误其默认阈值是100ms,远大于常见的主站通信周期,在如此长时间没有收到主站发来的过程数据帧,基本上是物理链路出了问题,比如链路中PHY出现断联导致主站和该从站之间断开造成的。
2.2 致命同步错误码 ALSTATUSCODE_FATALSYNCERROR(0x2C)
该错误码是利用MCU提供的1ms软件定时器监控的SYNC0中断服务函数的执行情况。当SYNC0中断服务函数Sync0_Isr()在规定的时间内得不到执行时触发报错。
由上文CheckIfEcatError()函数可知,在函数中错误码ALSTATUSCODE_FATALSYNCERROR(0x2C)根据bDcRunning变量状态实现检测。
查找bDcRunning变量的赋值情况,其在DC_CheckWatchdog()中赋值,该函数的调用路径是:
MCU提供的1ms定时中断服务函数(生成SSC代码时设置ECAT_TIMER_INT = 1) -> ECAT_CheckTimer() -> DC_CheckWatchdog() -> bDcRunning变量赋值。
/////////////////////////////////////////////////////////////////////////////////////////
/**
\brief This function checks the current Sync state and set the local flags
The analyse of the local flags is handled in "CheckIfEcatError"
*////////////////////////////////////////////////////////////////////////////////////////
void DC_CheckWatchdog(void)
{
if(bDcSyncActive && bEcatInputUpdateRunning)
{
/*If Sync0 watchdog is enabled and expired*/
if((Sync0WdValue > 0) && (Sync0WdCounter >= Sync0WdValue)) //判断计数器是否超出阈值
{
/*Sync0 watchdog expired*/
bDcRunning = FALSE; //超出阈值,设置bDcRunning为假
}
else
{
if(Sync0WdCounter < Sync0WdValue) // 计数器未超出阈值
{
Sync0WdCounter ++; // 计数器递增
}
bDcRunning = TRUE;
}
//省略代码
}
}
从DC_CheckWatchdog()代码可知,该函数负责检查DC同步状态并设置flag,然后由CheckIfEcatError()根据相应flag进行报错。
为设置标志bDcRunning,引入变量Sync0WdValue和Sync0WdCounter,可以将其机制理解为软件实现的看门狗。
Sync0WdValue是阈值,Sync0WdCounter是计数器值。计数器Sync0WdCounter在DC_CheckWatchdog()中每1ms递增1,当Sync0_Isr()执行时对Sync0WdCounter清零(相当于喂狗),如果没有执行Sync0_Isr()(相当于没有喂狗),Sync0WdCounter会计数到超出阈值Sync0WdValue,触发报错逻辑。
PROTO UINT16 Sync0WdCounter; /**< \brief Sync0 watchdog counter*/
PROTO UINT16 Sync0WdValue; /**< \brief Sync0 watchdog value*/
阈值Sync0WdValue在从站初始化过程中由函数StartInputHandler()赋值,其赋值规则为阈值Sync0WdValue = 2 * Sync0周期且大于等于1ms(因为软件定时器的时间精度是1ms),阈值为0时表示禁用。
UINT16 StartInputHandler(void)
{
//省略代码
/* calculate the Sync0/Sync1 watchdog timeouts */
if ( (dcControl & ESC_DC_SYNC0_ACTIVE_MASK) != 0 )
{
/*calculate the Sync0 Watchdog counter value the minimum value is 1 ms
if the sync0 cycle is greater 500us the Sync0 Wd value is 2*Sycn0 cycle */
if(cycleTimeSync0 == 0)
{
Sync0WdValue = 0;
}
else
{
UINT32 Sync0Cycle = cycleTimeSync0/100000; //获取Sync0周期,单位100us
if(Sync0Cycle < 5)
{
/*Sync0 cycle less than 500us*/
Sync0WdValue = 1; // 最小阈值1ms
}
else
{
Sync0WdValue = (UINT16)(Sync0Cycle*2)/10; // 阈值 = 2 * Sync0Cycle, 结果换算为ms
}
}
//省略代码
}
//省略代码
}
计数器Sync0WdCounter在Sync0_Isr()中清零。
void Sync0_Isr(void)
{
Sync0WdCounter = 0; // 计数器清零
//省略代码
}
综上,致命同步错误ALSTATUSCODE_FATALSYNCERROR(0x2C)的检查逻辑为:根据Sync0的周期设置阈值Sync0WdValue,在MCU提供的1ms定时器中断中对计数器值Sync0WdCounter+1,在Sync0中断服务函数中对计数器值Sync0WdCounter清零。当Sync0的中断服务函数长时间不被执行时,Sync0WdCounter就会累加超出阈值,DC_CheckWatchdog()将设置bDcRunning为假,最后CheckIfEcatError()依据bDcRunning报出错误。
该错误较少出现。因为SYNC0中断请求是ESC自身的定时器(分布时钟:Distributed Clocks)周期性产生的中断请求。当分布时钟DC被激活之后,Sync0中断请求的产生与通信情况无关(不考虑通信过程对DC时钟进行的校准),即使拔掉网线,SYNC0中断请求依然周期性产生,MCU接收到Sync0中断请求之后就会去执行Sync0_Isr()中断服务函数实现软件定时器喂狗。
理论出错原因:
- 主站在未更改从站同步模式的情况下,异常关闭从站的分布时钟DC功能,导致其不再产生SYNC0中断请求。
- 从站MCU错误关闭Sync0中断响应,即虽然Sync0中断请求产生了但中断响应被关闭了或其他原因导致的不按时执行SYNC中断服务函数。
2.3 同步错误码 ALSTATUSCODE_SYNCERROR(0x1A)
该错误码检测PDI_Isr()和Sync0_Isr()中断服务函数的执行顺序。
由上文CheckIfEcatError()函数可知,错误码ALSTATUSCODE_SYNCERROR(0x1A)是根据bSmSyncSequenceValid变量状态实现的检测。
PROTO BOOL bSmSyncSequenceValid; /**< \brief Set to true if SM/Sync0 sequence is valid*/
从bSmSyncSequenceValid标志的名称上可以看出,这是监控SyncManager事件(PDI_Isr中断函数)和Sync0事件(Sync0_Isr中断函数)的执行顺序。
查看标志bSmSyncSequenceValid的赋值过程,可知其同样在函数DC_CheckWatchdog()中赋值:
/////////////////////////////////////////////////////////////////////////////////////////
/**
\brief This function checks the current Sync state and set the local flags
The analyse of the local flags is handled in "CheckIfEcatError"
*////////////////////////////////////////////////////////////////////////////////////////
void DC_CheckWatchdog(void)
{
if(bDcSyncActive && bEcatInputUpdateRunning)
{
// 省略代码
if(bDcRunning)
{
/*ECATCHANGE_START(V5.13) ESM4*/
if((sErrorSettings.u16SyncErrorCounterLimit == 0) || (sSyncManOutPar.u16SmEventMissedCounter < sErrorSettings.u16SyncErrorCounterLimit))
/*ECATCHANGE_END(V5.13) ESM4*/
{
bSmSyncSequenceValid = TRUE;
/*Wait for PLL is active increment the Pll valid counter*/
if (i16WaitForPllRunningTimeout > 0)
{
i16WaitForPllRunningCnt++;
}
}
else if (bSmSyncSequenceValid) // 条件1: ((sErrorSettings.u16SyncErrorCounterLimit != 0) && (sSyncManOutPar.u16SmEventMissedCounter >= sErrorSettings.u16SyncErrorCounterLimit))
{
bSmSyncSequenceValid = FALSE; // 报错
/*Wait for PLL is active reset the Pll valid counter*/
if (i16WaitForPllRunningTimeout > 0)
{
i16WaitForPllRunningCnt = 0;
}
}
}
else if(bSmSyncSequenceValid) // 条件2: (!bDcRunning), 该条件是在ALSTATUSCODE_FATALSYNCERROR(0x2C)时产生的
{
bSmSyncSequenceValid = FALSE; // 报错
}
}
}
标志bSmSyncSequenceValid的赋值过程使用到对象字典参数中的sSyncManOutPar.u16SmEventMissedCounter和sErrorSettings.u16SyncErrorCounterLimit。
阈值sErrorSettings.u16SyncErrorCounterLimit的值为4(赋值细节不展开):
#define MAX_SM_EVENT_MISSED 4 /**< \brief threshold of max missed counter value (0x1C32.11 / 0x1C33.11)*/
计数值sSyncManOutPar.u16SmEventMissedCounter在Sync0_Isr(void)中增加:
sSyncManOutPar.u16SmEventMissedCounter = sSyncManOutPar.u16SmEventMissedCounter + 3;
计数值sSyncManOutPar.u16SmEventMissedCounter在PDI_Isr(void)中减小:
sSyncManOutPar.u16SmEventMissedCounter--;
为了管理sSyncManOutPar.u16SmEventMissedCounter的计数,引入u16SmSync0Counter和u16SmSync0Value变量,实现对PDI_Isr()和Sync0_Isr()执行顺序的检测。
PROTO UINT16 u16SmSync0Counter; /**< /brief Incremented by one on every Sync0 event and reset to 0 on every SM event. It is used to check the SM/Sync0 sequence */
PROTO UINT16 u16SmSync0Value; /**< /brief Allowed Sync0 events within one SM cycle. If 0 the Sequence check is disabled */
valid*/
阈值u16SmSync0Value同样在初始过程中由StartInputHandler赋值,其根据同步类型进行赋值,在使用Sync0不使用Sync1的情况下,其被赋值为1。
UINT16 StartInputHandler(void)
{
//省略代码
if(bSubordinatedCycles == TRUE)
{
// 省略代码
}
else
{
if(SyncType0x1C32 == SYNCTYPE_DCSYNC0) // 判断同步模式
{
/* if SyncType of 0x1C32 is 2 the Sync0 event is trigger once during a SM cycle */
u16SmSync0Value = 1; // 赋值为1
}
if(SyncType0x1C33 != SYNCTYPE_DCSYNC1)
{
LatchInputSync0Value = 1;
}
}
//省略代码
}
查看计数器u16SmSync0Counter的赋值过程:
在Sync0_Isr()中u16SmSync0Counter递增,当u16SmSync0Counter递增超出阈值时sSyncManOutPar.u16SmEventMissedCounter会+3。
void Sync0_Isr(void)
{
// 省略代码
if(u16SmSync0Value > 0)
{
/* Check if Sm-Sync sequence is invalid */
if (u16SmSync0Counter > u16SmSync0Value) // 当计数器值超出阈值
{
if ((nPdOutputSize > 0) && (sSyncManOutPar.u16SmEventMissedCounter <= sErrorSettings.u16SyncErrorCounterLimit))
{
sSyncManOutPar.u16SmEventMissedCounter = sSyncManOutPar.u16SmEventMissedCounter + 3; // 对象字典参数+3
}
if ((nPdInputSize > 0) && (nPdOutputSize == 0) && (sSyncManInPar.u16SmEventMissedCounter <= sErrorSettings.u16SyncErrorCounterLimit))
{
sSyncManInPar.u16SmEventMissedCounter = sSyncManInPar.u16SmEventMissedCounter + 3;
}
} // if (u16SmSync0Counter > u16SmSync0Value)
if ((nPdOutputSize == 0) && (nPdInputSize > 0))
{
// 省略代码
}
else if (u16SmSync0Counter <= u16SmSync0Value) // 条件 (nPdOutputSize != 0)
{
u16SmSync0Counter++; // 计数器递增
}
}//SM -Sync monitoring enabled
// 省略代码
}
在PDI_Isr()中u16SmSync0Counter清零,并在sSyncManOutPar.u16SmEventMissedCounter > 0的情况下对其-1。
void PDI_Isr(void)
{
// 省略代码
if(bEscIntEnabled)
{
if ( ALEvent & PROCESS_OUTPUT_EVENT )
{
if(bDcRunning && bDcSyncActive)
{
/* Reset SM/Sync0 counter. Will be incremented on every Sync0 event*/
u16SmSync0Counter = 0; // 计数器清零
}
if(sSyncManOutPar.u16SmEventMissedCounter > 0)
{
sSyncManOutPar.u16SmEventMissedCounter--; // 对象字典参数-1
}
sSyncManInPar.u16SmEventMissedCounter = sSyncManOutPar.u16SmEventMissedCounter;
}
}
// 省略代码
}
综上,u16SmSync0Counter在Sync0_Isr()中+1,在PDI_Isr()中清零。Sync0中断由从站本地DC时钟周期性触发,PDI(SM2事件)中断由主站发PDO数据帧到从站,从站接收后触发。
出错原因:
正常情况下,每一个Sync0周期内,从站都能接收到主站发来的一次PDO数据帧。如果因为某种原因,在一个Sync0周期中,从站没有接收到主站的PDO数据帧,那么就不会触发PDI中断,u16SmSync0Counter就不会清零,计数值就会超出阈值,造成sSyncManOutPar.u16SmEventMissedCounter+3。依此类推直到DC_CheckWatchdog()函数依据sSyncManOutPar.u16SmEventMissedCounter设置标志bSmSyncSequenceValid为假,最后CheckIfEcatError()函数依据bSmSyncSequenceValid进行报错。
主站发出PDO数据帧周期的抖动或物理链路断开导致主站数据帧无法到达从站都能造成在一个Sync0周期中,从站接收不到主站的PDO数据帧。如果是物理链路问题导致的数据帧无法到达从站,由于物理链路的断开和恢复时间,远超出100ms,从站会有超出100ms时间,无法接收到主站的PDO数据帧,这会最终导致报出同步管理器看门狗错误码ALSTATUSCODE_SMWATCHDOG(0x1B)。如果最终产生的错误码只有同步错误码 ALSTATUSCODE_SYNCERROR(0x1A),这就说明是由主站发出PDO数据帧的周期抖动问题造成。
对于主站发出PDO数据帧的抖动,可以适当在主站中配置Sync Shift Time,来增大对主站PDO数据帧周期抖动的容许,该办法并不完全解决问题(细节此处不展开)。
更改阈值sErrorSettings.u16SyncErrorCounterLimit(值:MAX_SM_EVENT_MISSED)可以增大对该类错误的容许。
补充说明:
需要说明的是该过程中sSyncManOutPar.u16SmEventMissedCounter会在Sync_Isr()中满足出错条件时+3,在PDI_Isr()中满足条件时-1,其数值增加和减少的幅度不同,个人理解可以用于更有效的监控一段时间内的出错情况。如果一段时间内连续出现Sync_Isr()和PDI_Isr()执行顺序问题,则会造成计数值累加到较大值(单次+3),即使后续被减小(单次-1);如果一段时间内只是出现一两次Sync_Isr()和PDI_Isr()执行顺序问题,计数值累加后,会在其后的PDI_Isr()中-1,等DC_CheckWatchdog()函数执行时sSyncManOutPar.u16SmEventMissedCounter可能已经被减小到阈值之下了不报错了(sSyncManOutPar.u16SmEventMissedCounter的增减机制为个人理解,如有错误欢迎指正)。
总结
上文CheckIfEcatError()函数实现的理解可以看出其监控了3种错误状态,3种错误监控的时间阈值不同, 每个错误后都会有return语句提前返回,造成3种错误的报告优先级/严重等级也不同。
| 错误码 | 默认阈值 | 优先级/严重等级 |
|---|---|---|
| 同步管理器看门狗ALSTATUSCODE_SMWATCHDOG(0x1B) | 100ms | 高 |
| 致命同步错误码ALSTATUSCODE_FATALSYNCERROR(0x2C) | 2 * sync0 cycle 且 >= 1ms | 中 |
| 同步错误码ALSTATUSCODE_SYNCERROR(0x1A) | 与sync0周期相关, 连续2个sync0周期没有PDO数据或一段时间内频繁出现单个sync0周期没有PDO数据也可能触发 | 低 |
致命同步错误码ALSTATUSCODE_FATALSYNCERROR(0x2C)和同步错误码ALSTATUSCODE_SYNCERROR(0x1A)是在启用DC同步功能时才可能出现的错误码。
假设在使用DC功能的情况下,某一时刻t拔掉主站和从站之间的网线,那么在t + 2 * Sync0 cycle后,同步错误ALSTATUSCODE_SYNCERROR(0x1A)能够被报出,在t + 100ms后,同步管理器看门狗ALSTATUSCODE_SMWATCHDOG(0x1B) 错误发生,其优先级最高,之后报出的错误码就是ALSTATUSCODE_SMWATCHDOG(0x1B) 。
当出现同步错误码ALSTATUSCODE_SYNCERROR(0x1A)时,认为与主站发出的PDO数据帧周期抖动相关。可以调大DC同步周期进行对比,或使用带准确时间戳的以太网数据抓包功能进行数据帧周期分析佐证,PC安装TwinCAT软件作为主站,其实时性一般较差,可以换成可靠的PLC设备进行小DC周期情况下的测试。
当出现同步管理器看门狗错误ALSTATUSCODE_SMWATCHDOG(0x1B),其默认阈值为100ms,一般会多倍于主站的数据帧周期,此时这种问题不会是主站的数据帧周期抖动造成,基本肯定是物理链路发生断联造成,可以肉眼观察网线的Link指示灯(短时间可复现情况下)或使用wireshark抓包分析确认物理链路断联问题。