在网络管理相关的测试中,无论是对于OESK还是AutoSar网管,都需要检测网络管理报文有没有停发,总线上的节点是否掉线等。
这里实现了一段简单的代码用来检测总线上的报文周期(此段代码仅适用于周期为固定周期的情况,无法计算AutoSar中报文快发周期与Nos下的正常网管周期)
思路讲解:
在总线上收到同一个ID的第一帧CAN帧时,记录下此刻的时间节点t1,等待收到第二帧时,记录下此刻的时间节点t2,继续等待收到第三帧时,记录下此刻的时间t3。
令z1 = t2-t1;
令z2 = t3-t2;
如果z2与z1的比值在一定范围内,便将(z1+z2)/2,记作此报文的周期,若不在此范围内,认为该三帧之间的间隔时间相差太大,不是连续报文,不记录周期,继续接收3帧报文,直到收到连续的三帧,计算出周期T。
如果将比值的范围设定为0.95~1.05之间,那么只要z1与z2的差距不超过10%,就视为两个差值有效,将二者求和之后取平均值即可计算出一个相对来说准确的周期(如果报文的周期都是整百的,可以将此周期值取整,找到最近的整百)。
随后在需要检测报文丢失的地方,取出当前的时间t4,使用t4 - 报文上一次出现时的时间,如果这个时间大于周期的一定的倍数,则认为这个报文已经丢失了。
实现过程:
variables
{
struct CANMessage {
word NmFrID;
dword NmFrLastTime;//上次出现的时间,单位ms
dword NmFrCycTime;//周期
byte NmIdExistsFlag;//是否已存在标志位
};
struct CANMessage gNmFrArray[256];//声明NM结构体数组变量,0x500~0x5ff共256帧
enum _bool{
_false,
_true
};
dword t1[256],t2[256],t3[256];//三个时间数组,用于存放周期相关的时间
}
enum _bool is_message_timed_out(struct CANMessage Msg) //检测CAN报文是否已经超时
{
dword current_time;//获取当前的时间
current_time = (timeNow() / 100);
current_time = current_time - Msg.NmFrLastTime;//得到当前时间和报文上一次出现的时间差
if(current_time > 5 * Msg.NmFrCycTime)
{
return _true;
}
else
{
return _false;
}
}
byte check_for_timed_out_messages(word id)//此函数可以传入指定的ID,返回当前指定ID的离线状态。若已离线返回1,在线返回0
{
byte index;
index = id - 0x500;
if(gNmFrArray[index].NmIdExistsFlag && is_message_timed_out(gNmFrArray[index]))//如果报文已经出现过,并且报文超时了
{
gNmFrArray[index].NmIdExistsFlag = _false;
return _true;
}
else
{
return _false;
}
}
void CelargVar()
{
int i;
for ( i = 0; i < elcount(gNmFrArray); i++) //清空结构体
{
gNmFrArray[i].NmIdExistsFlag = _false;
gNmFrArray[i].NmFrID = 0;
gNmFrArray[i].NmFrCycTime = 0;
gNmFrArray[i].NmFrLastTime = 0;
}
}
on start
{
CelargVar();
}
on key'A'
{
//check_for_timed_out_messages();
int i;
for(i=0;i<elcount(gNmFrArray);i++)
{
if(check_for_timed_out_messages(i + 0x500))
{
write("CAN ID = 0x%lx 已经离线",i+0x500);
}
}
}
on message 0x500-0x5ff
{
byte Address;
Address = this.id - 0x500;//把ID转化为数组索引
if(gNmFrArray[Address].NmIdExistsFlag == _true)//如果已经出现了2次此报文
{
double tempZ;
tempZ = 0;
t1[Address] = (timeNow() / 100);//计算出当前的时间
t2[Address] = abs(t1[Address] - gNmFrArray[Address].NmFrLastTime);
tempZ = (double)t3[Address] / (double)t2[Address];
write("tempZ = %lf",tempZ);
//如果是第一次执行到这里,t3-t2是无法满足条件的,即要第二次执行到这个代码,也就是收到了三帧,才会生效
if(tempZ >= 0.95 && tempZ <= 1.05 && gNmFrArray[Address].NmFrCycTime == 0)//赋值一次周期之后,周期有了数值,条件也不成立了,即只更新一次周期
{
if(t3[Address] != 0)
{
gNmFrArray[Address].NmFrCycTime = (t3[Address] + t2[Address]) / 2;//计算周期
write("报文0x%LX的周期是%d ms",this.id,gNmFrArray[Address].NmFrCycTime);
}
}
t3[Address] = t2[Address];
}
if (!gNmFrArray[Address].NmIdExistsFlag || gNmFrArray[Address].NmFrID == this.id) //ID没有存储过,或者传过来的ID已经存储了,则更新数据
{
gNmFrArray[Address].NmFrID = this.id;
gNmFrArray[Address].NmFrLastTime = (timeNow() / 100);//取出时间
gNmFrArray[Address].NmIdExistsFlag = _true;
//write("更新ID = %LX信息",id);
}
//add_or_update_message(this.id);
}
接着在IG上添加几个报文测试下周期检测准不准。
可以看到,检测到周期和IG中的发送周期一致。
如果我改为手动发送,制造较大的报文间隔,周期将不会检测成功
当z1与z2相差过大时,不会计算周期
当z1与z2的比值在给定范围内时,求出周期。
接着我们看看检测报文丢失的功能。
此时,我停掉前两个报文,过会儿后按下按键A查询报文丢失情况
成功检测到了离线的报文。
当然,这里是用按键来触发检测的,实际使用中我们应该要知道报文的实时状态,可以略微修改一下代码将检测挪到on message中去检查或者开个定时器来检查,这里就不再演示了。如果有需要的话,可以评论区或者后台联系我,我这里有完善之后的源码,适用于OESK和AutoSar网络管理(可以检测快慢周期)。