CANoe programming realizes automated testing of FOTA vehicle end (2)

This article mainly describes how to use CAPL programming to realize the simulation program under the environment of CANoe 11.0.

1. Create a simulation node

Before modeling, first create a .DBC file, or modify an existing DBC file. Create a new empty node to be simulated, as shown in the figure below, only the node name without any signal. Then load into Setup.
insert image description here

2. Insert a new node

Select Insert Network Node, then right-click the newly created node to configure the node properties.
insert image description here
Select the node name created in DBC:
insert image description here
set the node attribute to OSEK_TP node (just add osek_tp.dll, find it in the canoe installation directory, for example "C:\Program Files\Vector CANoe 11.0\Exec32") Finally, the entire complete model
insert image description here
:
insert image description here

3. Realization of the simulation program

Here we take an ECU node as an example to explain, the first isECU application layer behavior simulation:
The communication signal simulation of ECU is realized. The difference between different ECUs is that the number of signals is different , the ECUName of the link of the response link between the physical request and the function request is inconsistent , and the diagnostic ID is inconsistent . The rest are logically identical. Therefore, secondary development only needs to copy the code and modify these three places to complete the addition of new nodes.

/*@!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);
}

Common interface implementation:

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,
  //......略去下面很多代码
}

Implementation of message parsing function:

/***********************************************************
* 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;
    }
  }
}

After completing the simulation of the ECU in the car and starting CANoe, the simulated ECU can verify the correctness of the FOTA process of TBOX. However, this solution only simulates the process of forward flashing. During the actual flashing process, there will be many abnormal scenarios. The complete solution still needs more development work.

Guess you like

Origin blog.csdn.net/u014157109/article/details/120360152