CANoe编程实现FOTA车端的自动化测试(二)

这篇文章主要讲述在CANoe 11.0环境下,用CAPL编程实现仿真程序。

1. 创建仿真节点

建模之前,首先创建一个.DBC文件,也可以用一个已有的DBC文件修改。新建待仿真的空节点,如下图,只有节点名称无任何信号。然后加载到Setup。
在这里插入图片描述

2. 插入新节点

选择Insert Network Node, 然后右击新建的节点配置该节点属性。
在这里插入图片描述
选择DBC中创建的节点名:
在这里插入图片描述
设置节点属性为OSEK_TP节点(添加osek_tp.dll即可,在canoe安装目录下查找,例如 “C:\Program Files\Vector CANoe 11.0\Exec32”)
在这里插入图片描述
最后整个完整的模型:
在这里插入图片描述

3. 仿真程序实现

这里以某个ECU节点为例讲解,首先是ECU应用层行为仿真
实现了ECU的通信信号仿真,不同的ECU之间的差异在于 信号数量不一样物理请求与功能请求的应答的链路的ECUName不一致诊断ID不一致。其余逻辑上完全一致。所以说二次开发只需要复制代码后 修改此三处即可完成新节点的增加。

/*@!Encoding:936*/
includes
{
  #include "GenericNode.cin"     //此处是一个造好的轮子,可见canoe提供的\OSEK_TP_MultiChannel  Demo
}

variables
{
  msTimer PhysRespTimer;  //物理寻址应答定时器
  msTimer FuncRespTimer;  //功能寻址应答定时器
  msTimer GWMessageTimer;    //ECU外发消息定时器,周期性的往总线发报文
  message 0x111 GW_message;    //此处是随便举例的报文,假设GW的tx报文就是id=0x111
  message 0x222 NWM_message;  //监控唤醒状态
  const int cycPepsTime = 100;    //100ms周期
}

//每100ms发送一帧gw报文到总线,ecu信号仿真
on timer GWMessageTimer
{
  output(GW_message);
  setTimer(GWMessageTimer, cycPepsTime);
}

//模拟按键弹起,物理寻址
on timer PhysRespTimer
{
    //注意此处的系统变量格式, ECUName::链路名::变量名, 本篇章节一介绍的在setup处建立节点时,要求配置选择数据库的节点名将在此处生效
  @sysvar::GW::Conn1::sysSendData = 0;
}

//模拟按键弹起,功能寻址
on timer FuncRespTimer
{
  @sysvar::GW::Conn2::sysSendData = 0;  //注意此处链路名与上一函数不一样,区分物理寻址和功能寻址主要体现在这里
}
//监控一个环境变量,整车电源模式。 备注:环境变量可在DBC中创建
on envVar PEPS_PwrMode
{
  varPowerMode = getValue(PEPS_PwrMode); //先略过此变量的定义位置,全局变量记录电源状态
  GW_message.PEPS_PowerMode = varPowerMode;
  if(varPowerMode==2)
  {
    BCM_ATWS = 2;  //车身安全锁报警状态变量,略过定义处
  }
  if(varPowerMode == 3)//休眠
  {
    InactiveGW();
  }
  else
  {
    ActiveGW();
  }
}

//模拟按键按下,物理寻址
void diagPhysRespMessage()
{
  if(IsResponse){
  @sysvar::GW::Conn1::sysSendData = 1;
  setTimer(PhysRespTimer, N_As);
  }
}

//模拟按键按下,功能寻址
void diagFuncRespMessage()
{
  if(IsResponse){
  @sysvar::GW::Conn2::sysSendData = 1;
  setTimer(FuncRespTimer, N_As);
  }
}

on message NWM_message
{
  if(IsBUSActive == 0)
  {
    GW_message.PEPS_PowerMode = 0;
    ActiveGW();  //设备被唤醒,升级定时器触发后 激活信号
  }
}

//处理来自诊断仪的物理寻址访问GW请求
on message 0x701   //此处是捏造的物理寻址诊断ID,根据产品实际的来变更
{
  diagReqMsg=this;
  writeDbgLevel(level_1, "---physical diagnostic request, id = 0x%x", diagReqMsg.id);
  SetValue(); //获取当前应回复值
  diagParseReqMessage();      //解析请求内容
  diagPhysRespMessage();    //应答请求

}

//处理来自诊断仪的功能寻址访问GW请求
on message 0x7EE    //此处是捏造的功能寻址诊断ID,根据产品实际的来变更
{
  diagReqMsg=this;
  writeDbgLevel(level_1, "---functional diagnostic request, id = 0x%x", diagReqMsg.id);
  diagParseReqMessage();
  diagFuncRespMessage();
}

//初始化仿真的通信信号值
void InitGWValue()
{
  putValue(PEPS_PwrMode, 0);
  GW_message.PEPS_PowerModeValidity = 2;
  GW_message.PEPS_RemoteControlState = 0;
}
//初始化数据
void InitValue()
{
    //以下是从配置文件读取 GW接到诊断请求时的应答的数据
  getProfileString("GW", gEntry_1, gDefautStr, cOEMInfo, gLenEntry_1, gFileName);
  putValue(GWOEMNumber, cOEMInfo); //EPS OEM  NO.
}

//获取ECU的回复参数
void SetValue()
{
  getValue(GWOEMNumber, cOEMInfo);
}

on start
{
  InitGWValue();
  ActiveGW();
}

//停止仿真通信报文
void InactiveGW()
{
  cancelTimer(GWMessageTimer);
  IsBUSActive = 0;
}

//仿真通信报文
void ActiveGW()
{
  setTimer(GWMessageTimer, cycPepsTime);
  IsBUSActive = 1;
}

on preStart
{
  InitValue();
}

//获取实时更新的OEM版本号
on envVar GWOEMNumber
{
  char dest[100];
  getValue(GWOEMNumber, cOEMInfo);
  snprintf(dest, elcount(dest), "\"%s\"", cOEMInfo);
  writeProfileString("GW", gEntry_1, dest, gFileName);
}

//数据对外发送的统一变量,所有ECU发送数据时通过它外传
on envVar varDataToTransmit
{
  getValue(varDataToTransmit, cEnvVarBuffer);
}

通用接口实现:

includes
{
  #include "GenericConn1.cin"
  #include "GenericConn2.cin"  //造好的轮子  建立链路,分别实现物理寻址与功能寻址
  #include "Common.cin"   //通用接口封装在此处
}

variables
{
  char gECU[10] = "%NODE_NAME%";  //此变量是获取当前通信节点的名称,此处与通信链路中的ECUName很自然的关联起来了
  enum AddressModes { kNormal = 0,
                      kExtendedBased = 1,
                      kNormalFixed = 2,
                      kMixed = 3,
  //......略去下面很多代码
}

报文解析函数的实现:

/***********************************************************
* description  : 解析收到的报文
* creation date: 2018/11/13
* author       : XXX
* revision date:
* revision log :
* modifier     :
***********************************************************/
void diagParseReqMessage()
{
  byte fBValue;
  byte hNibble; //高四位
  byte lNibble; //低四位
  byte sid = 0x0;
  byte reserveSid = 0x0; //针对多帧请求的服务有效,特别预留

  int remainderBLen;     //剩余未传输字节
  int remainderFrameCnt=0;
  int consecutiveFrameCnt=0;
  //获取首字节信息
  fBValue = diagReqMsg.byte(0);
  writeDbgLevel(level_1, "---The First Byte: 0x%02x", fBValue);
  hNibble = (fBValue>>4) & 0xf;
  lNibble = fBValue & 0xf;
  //writeDbgLevel(level_1, "high 4 bits=%d, low 4 bits=%d", hNibble, lNibble);
  IsResponse= 0; //初始化时默认不发送应答,需要发送应答时置位1
  //解析高字节信息
  if(0x0 == hNibble) //单帧
  {
    SF_DL = lNibble;
    sid = diagReqMsg.byte(1);
    writeDbgLevel(level_1, "SF: SF_DL=%d, sid=0x%x", SF_DL, sid);
    if(0x2e==sid){//写入服务
      subServiceId = ((diagReqMsg.byte(2)<<8)&0xffff)+diagReqMsg.byte(3);
      writeDbgLevel(level_1, "---SF:sid=0x%02x, ssid=0x%x---", sid, subServiceId);
    }
    else if(0x31==sid) //擦写 05 71 01 FF 01 04 AA AA
    {
      checkSum = (diagReqMsg.byte(2)<<24) | (diagReqMsg.byte(3)<<16)
      |(diagReqMsg.byte(4)<<8) | diagReqMsg.byte(5);
       writeDbgLevel(level_1, "---SF:crc or flush, 0x%x---", checkSum);
    }
    diagProcessSFRequest(sid); //根据实际服务回复应答内容
  }
  else if(0x1 == hNibble) //多帧首帧
  {
    FF_DL = ((lNibble<<8)&0xfff) + diagReqMsg.byte(1);
    reserveSid = diagReqMsg.byte(2);
    remainderFrameCnt = 0; //回复0值
    consecutiveFrameCnt = 0;  //置0连续帧
    remainderBLen = (FF_DL - 6);
    writeDbgLevel(level_1, "---MF:sid=0x%02x", reserveSid);
    if(reserveSid==0x2e){
      subServiceId = ((diagReqMsg.byte(3)<<8)&0xffff)+diagReqMsg.byte(4);
      writeDbgLevel(level_1, "---MF:ssid=0x%x---", subServiceId);
    }
    else if(reserveSid==0x36) //经验, 将数据放置在左边,可避免少写=的异常
    {
      transferDataSN = diagReqMsg.byte(3);
      writeDbgLevel(level_1, "---MF:data sn=0x%x---", transferDataSN);
    }
    else if(reserveSid==0x31) //校验
    {
      checkSum = (diagReqMsg.byte(3)<<24) | (diagReqMsg.byte(4)<<16)
      |(diagReqMsg.byte(5)<<8) | diagReqMsg.byte(6);
      writeDbgLevel(level_1, "---MF:crc or flush, 0x%x---", checkSum);
      IsCRCDone = 1; //已校验过 刷写完成
    }

    if(remainderBLen%7 == 0)
    {
      remainderFrameCnt = remainderBLen/7;
    }
    else
    {
      remainderFrameCnt = remainderBLen/7 + 1;
    }
    writeDbgLevel(level_1, "MF: FF_DL=%d,remainder frame count=%d", FF_DL, remainderFrameCnt);
  }
  else if(0x2 == hNibble) //连续帧
  {
    SN = lNibble;
    consecutiveFrameCnt += 1;
    writeDbgLevel(level_1, "CF: SN=%x, current count=%d", SN, consecutiveFrameCnt);
    sid = 0x0;
  }
  else if(0x3 == hNibble) //流控帧
  {
    FS = lNibble;
    BS = diagReqMsg.byte(1);
    STmin = diagReqMsg.byte(2);
    writeDbgLevel(level_1, "FC: FS=%d, BS=%d, ST min=%d", FS, BS, STmin);
    sid = 0x0;
  }
  else
  {
    writeDbgLevel(level_1, "error frame");
  }

  //响应多帧请求
  if(remainderFrameCnt!=0)
  {
    if(remainderFrameCnt == consecutiveFrameCnt)
    {
      diagProcessMFRequest(reserveSid); //封装具体的应答逻辑,可以根据诊断协议获知
      IsResponse= 1;
      consecutiveFrameCnt = 0;
    }
  }
}

完成了车内ECU的仿真,启动CANoe后,仿真的ECU就可以验证TBOX的FOTA流程正确性,但是本方案只模拟了正向刷写的过程,实际刷写过程中,会有很多异常场景出现,完整的方案还需做更多的开发工作。

  • 6
    点赞
  • 51
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
FOTA(Firmware Over-The-Air)自动化测试主要包括以下几个步骤: 1. 确定测试环境:搭建仿真测试环境,包括内网络的建模和仿真设备的配置。测试环境需要包括12V B+供电源、必需的ECU(根据TBOX的功能设计确定)、仿真设备(如CANoe)和工作站PC。 2. 编写测试用例:根据FOTA的功能需求和测试目标,编写相应的测试用例。测试用例应包括FOTA下载过程的验证、内网络通信信号的仿真和TBOX作为诊断仪在总线上诊断请求的实现等。 3. 配置仿真设备:使用仿真设备(如CANoe)配置内网络节点的建模和ECU逻辑的实现。根据测试用例的需求,配置相应的仿真设备参数,如发送和接收的CAN消息、ECU的状态和响应等。 4. 执行测试用例:在测试环境中执行编写好的测试用例。通过仿真设备模拟FOTA下载过程,验证下载的正确性和稳定性。同时,检查内网络通信信号的仿真和TBOX作为诊断仪在总线上诊断请求的实现是否符合要求。 5. 分析测试结果:根据测试执行的结果,分析FOTA自动化测试的结果。如果测试用例通过,说明FOTA下载过程和内网络仿真都正常工作。如果测试用例未通过,需要进一步分析问题原因并进行修复。 总结起来,FOTA自动化测试主要包括确定测试环境、编写测试用例、配置仿真设备、执行测试用例和分析测试结果等步骤。通过这些步骤,可以验证FOTA下载过程的正确性和稳定性,以及内网络通信信号的仿真和诊断请求的实现是否符合要求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值