- 前言
- 确认模拟节点
- 发送机制
- 节点排序
- 节点加入并建环
- 节点丢失
- 节点跳过
- 测试例子
- 总结
————————————————
闲暇之际,听到不知名网友呼我关于OSEK的建环机制用CAPL怎么做?
我会的有以下三种:
-
利用.dll调用,这得看函数解析,对新手不太友好。
-
建立network node仿真节点,每个仿真节点都有一个完整的OSEK机制,即相互独立运作建环,睡眠唤醒等;编写测试用例简单;这得有开发基础及逻辑清晰的头脑,再花费亿点点时间(小七用的这种)。
-
只满足基本的建环与丢失,代码量少,需要迎合测试用例(推荐)。
今天讲第3种。
新建工程,Insert CAPL Test Module > osek.can
确认模拟节点
- 以0x604为ECU节点,0x601,0x605,0x603,0x650,0x67F为模拟节点。
variables{
dword ecu_node_id = 0x604;//ECU节点
dword node_id[5] = {0x601,0x605,0x603,0x650,0x67f};//模拟节点5个
}
发送机制
- 初步模拟出ECU节点运行,直到跛行状态持续
variables{
message 0x001 g_Tx = {dlc=0x08};
dword ecu_node_cycle = 0;//周期
msTimer TsingleNode;//模拟osek管理发送机制
int rxLimit_sum = 4,rxLimit_n = 0;
}
on timer TsingleNode//模拟ECU节点的定时器
{
if(ecu_node_cycle == 0)
{
ecu_node_cycle = 100;
g_Tx.byte(1) = 0x01;
}
else if(ecu_node_cycle == 100)
{
ecu_node_cycle = 260;
g_Tx.byte(1) = 0x02;
}
else if(ecu_node_cycle == 260 || ecu_node_cycle == 1000)
{
rxLimit_n++;
if(rxLimit_n >= rxLimit_sum + 1)
{
ecu_node_cycle = 1000;
g_Tx.byte(1) = 0x04;
}
else
{
if(rxLimit_n == rxLimit_sum)
{
ecu_node_cycle = 1000;
g_Tx.byte(1) = 0x01;
}
else
{
ecu_node_cycle = 100;
g_Tx.byte(1) = 0x01;
}
}
}
g_Tx.id = ecu_node_id;
g_Tx.byte(0) = ecu_node_id - node_id_min;
output(g_Tx);
setTimer(TsingleNode,ecu_node_cycle);
}
- 效果
节点排序
- 声明变量数组按ID大小,从小到大存储当前模拟节点及ECU 节点
void alignNodes(byte sim_node_n, byte node_behavior)//排列节点id大小,只有加入和丢失两种操作。
{
dword value;
int Tempi,Tempj,Tempk;
if(node_behavior != gSim_on && node_behavior != gSim_out)//加入和丢失两种操作
return;
if(sim_node_n != 0xFF)//单个节点
{
if(node_behavior == gSim_on)//模拟节点加入,要重新排列发送顺序
{
for (Tempi = 0; Tempi < cur_node_id_sum; Tempi++)
{
if(cur_node_id[Tempi] == node_id[sim_node_n - 1])//加入时,要排除当前节点数组是否有待加入的ID
return;
}
// writeEx(-3,0,"0x%x节点加入",node_id[sim_node_n-1]);
cur_node_id[cur_node_id_sum++] = node_id[sim_node_n - 1];
if(cur_node_id_sum == 1)//有一个节点时就先增加进去,防止每次增加节点时都增加一个ECU ID
cur_node_id[cur_node_id_sum++] = ecu_node_id;//获取当前所有节点ID
}
else if(node_behavior == gSim_out)//丢失,要重新排列发送顺序
{
for (Tempi = 0; Tempi < cur_node_id_sum; Tempi++)
{
if(cur_node_id[Tempi] == node_id[sim_node_n - 1])//丢失时判断,找到要丢失模拟节点在cur_node_id里位置
{
// writeEx(-3,0,"0x%x节点丢失",node_id[sim_node_n-1]);
for(Tempj = Tempi+1; Tempj < cur_node_id_sum; Tempj++)//遍历Tempi后面的模拟节点
{
cur_node_id[Tempi] = cur_node_id[Tempj];//把后一位模拟节点向前移
}
cur_node_id[cur_node_id_sum - 1] = 0x00;//最后一位置0
cur_node_id_sum--;
if(cur_node_id[Tempi] < ecu_node_id)//前节点,发送时要往左移。后节点不用管。
cur_node_id_n--;
if(cur_node_id_sum == 1)//最后只剩下ECU的节点时,清空cur_node_id和当前节点总个数
{
cur_node_id_sum = 0;
cur_node_id_n = 0;
}
break;
}
}
}
for (Tempi = 0; Tempi < cur_node_id_sum; Tempi++) //冒泡排序,ID从小到大排序
{
for (Tempj = Tempi+1; Tempj < cur_node_id_sum; Tempj++)
{
if (cur_node_id[Tempi] > cur_node_id[Tempj])
{
value = cur_node_id[Tempi];
cur_node_id[Tempi] = cur_node_id[Tempj];
cur_node_id[Tempj] = value;
}
}
}
}
else//所有节点都需要操作
{
for (Tempk = 1; Tempk <= elCount(node_id); Tempk++)
alignNodes(Tempk,node_behavior);
}
}
void nodeBehavior(byte sim_node_n, byte node_behavior)//模拟节点行为
{
//sim_node_n:node_id数组里的节点;以1为起始,1表示第一个节点;0xFF表示所有节点
//node_behavior:代表模拟节点的行为
int Tempi,Tempk;
if(sim_node_n != 0xFF)//单个节点
{
if(node_behavior == gSim_on)//加入
{
sendAlive(sim_node_n);//单个节点加入,先声明,发送Alive
sendRing(sim_node_n,gNMStateCode_Ring);//再发送ring
}
else if(node_behavior == gSim_out)//丢失
{
if(cur_node_id_sum <= 1)//丢失完以后,停止发送
cancelTimer(TringOperation);
}
else if(node_behavior == gSim_sleep)
{
gNMStateCode = gNMStateCode_sleepInd;//建环时,满足睡眠条件时ind置1
}
else if(node_behavior == gSim_awake)
{
gNMStateCode = gNMStateCode_Ring;//建环时,满足睡眠条件->不满足睡眠条件
}
}
else//所有节点
{
for (Tempk = 1; Tempk <= elCount(node_id); Tempk++)
nodeBehavior(Tempk,node_behavior);
}
}
void nodeSwitch(byte sim_node_n, byte node_behavior)//模拟节点开关
{
int Tempi;
alignNodes(sim_node_n,node_behavior);//重组模拟节点
if(cur_node_id_sum >= 2)//包含ECU 在内的节点
{
write("当前所有节点[%d] %x",cur_node_id_sum,cur_node_id[0]);
for (Tempi = 1; Tempi < cur_node_id_sum; Tempi++)
writeEx(1,0," %x",cur_node_id[Tempi]);
}
else
write("当前所有节点[0] ");
nodeBehavior(sim_node_n,node_behavior);
}
- 效果
- CAPL声明:
- CAPL打印:
节点加入并建环
- 先声明模拟节点存在,发送Alive报文
- 按排序后从小到大依次发送
- 发送最大节点ID时,后继节点变为最小节点ID
节点建环图示:
void sendAlive(byte sim_node_n)//alive报文
{
//sim_node_n:node_id数组里的节点;以1为起始,1表示第一个节点;0xFF表示所有节点
g_Tx.id = node_id[sim_node_n-1];
g_Tx.byte(0) = node_id[sim_node_n-1] - node_id_min;
g_Tx.byte(1) = gNMStateCode_Alive;
output(g_Tx);
}
void sendRing(byte sim_node_n, byte msg_type)//ring报文
{
gNMStateCode = msg_type;
if(msg_type == gNMStateCode_sleepAck)//模拟节点发送置1的ack位/32
{
cancelTimer(TringOperation);
g_Tx.id = node_id[sim_node_n-1];
g_Tx.byte(0) = desNodeAddress(node_id[sim_node_n-1]);
g_Tx.byte(1) = gNMStateCode;
output(g_Tx);
}
else//02/12/
{
if(cur_node_id_sum <= 2)//已建环时不能重启定时器,周期会出错
setTimer(TringOperation,100);
}
}
on timer TringOperation//ring报文 模拟节点周期发送
{
if(cur_node_id_n >= cur_node_id_sum)//后继节点移位
cur_node_id_n = 0;
g_Tx.id = cur_node_id[cur_node_id_n];//id
g_Tx.byte(0) = desNodeAddress(cur_node_id[cur_node_id_n]);//后继节点
g_Tx.byte(1) = gNMStateCode;//ring报文类型
outputNode();
cur_node_id_n++;
setTimer(TringOperation,100);
}
- 效果
节点丢失
- 效果
节点跳过
- 根据OsekVDX NM 2.5.2规范,节点需要具有监测自身是否被跳过的能力,如果检测到被跳过,则重新发送alive报文
如下图:
- 效果
测试例子
- 关注公众号【总线网络】代码自取
void MainTest ()
{
NewTestCase0();//ECU节点发送机制
NewTestCase1();//所有节点建环并丢失
NewTestCase2();//单个节点建环并丢失
NewTestCase3();//三个节点建环并丢失后继节点
NewTestCase4();//三个节点建环跳过ECU节点
testWaitForMeasurementEnd(10);
stop();
}
总结
选择合适自己的写脚本的方式,能事半功倍!祝福各位学习CANoe的同学能早日毕业!
有不完善的地方欢迎留言或加我vx补充。
请输入公众号:总线网络。关注我,获取汽车网络开发及测试方面资料,更新干货!
分享总线开发知识
分享CAN/CANFDLIN/ETH等网络资料
分享CANoe/TSMaster/PCAN等设备工具使用
分享UDS/NM/Bootloader测试用例等
一起来学习,进步,交流吧!