CAPL脚本使用介绍

描述

之前断断续续分享过一些Vector工具的使用总结,如下所示,包括CANape、CANoe、CANalyzer。

然而里面还有一项最重要的,也是平时使用过程中,提效很明显的CAPL脚本的使用,覆盖整车各节点的模拟、软件刷写、诊断测试等等,今天就来理一理CAPL脚本。 首先理一理基本语法和常用语句。在打开CAPl编辑界面时,下默认组成部分有:Include、Variable、System、Value Objects,其中Include为需要包含的已存在的头文件,一般不配置;Variable为申明与定义全局变量,需要定义的变量包括需要发送的信号以及其数据类型。CAPL中的数据类型有: 无符号整型:byte(1字节),word(2字节),dword(4字节)

有符号整型:int(2字节),long(4字节)

浮点数:float(8字节),double(16字节)

CAN消息类型:Message;定时器类型:timer(单位为s),msTimer(单位为ms);

单个字符:char(1字节)。

除了界面基础的信息外,在CAPL脚本中,我们大量使用官方定的的一些接口,这些接口通常需要查看help文档或者是CAPL的手册,下面是梳理的一些常用接口。

1、定时器 

CAPL中的定时器的使用相当频繁,比如测试时需要向定时发送某条CAN报文时就需要用定时器; 定时器的声明:

msTimer myTimer1;//声明了一个ms定时器,定时单位是毫秒
timer myTimer2;//声明了一个以秒为单位的定时器;

设置定时器:

setTimer(myTimer1,400);//设置定时器myTimer1为一个400ms定时器;
setTimerCyclic(myTimer2,2);//设置定时器myTimer2为一个2s为周期的循环定时器;

设置定时器定时事件,即当定时器计时时间到时将要执行的操作:

on timer myTimer1
{
.......
}

2、信息的操作和发送

//CAN消息发送:
message 0x7ff Msg;//声明一个message,ID=0x7ff
Msg.dlc=8;//设置其DLC=8;
Msg.id=0x100;//更改其ID=0x100;
Msg.byte(0)=55;//设置数据场的第一个字节为55
output(Msg);//发送Msg


//CANFD消息发送:
  msg1.FDF=1;
  msg1.BRS=1;
  msg1.dlc=8;
  Msg.id=0x100;//更改其ID=0x100;
  msg1.byte(0)=0x44;
  msg1.byte(10)=0x10;
  msg1.byte(11)=0x11;
  output(Msg);//发送Msg

3、节点上下线操作 

节点是在dbc中定义的,如VCU,BMS,MCU等,有时需要将它们离线,离线后不再向总线上发送报文,在线时可以向总线上发送报文。 

节点上线:

void testSetEcuOnline(dbNode aNode);
void testSetEcuOnline(char aNodeName[]);

节点下线:

void testSetEcuOffline(dbNode aNode);
void testSetEcuOffline(char aNodeName[]);

4、检查错误帧 

进行CAN通讯的测试时,检查错误帧是很常见的,要用CAPL脚本实现自动检测错误帧也不困难,它的核心就是调用错误检查函数ChkStart_ErrorFrameOccured(),该函数一旦被调用,CANoe就会从此函数被调用时开始持续检测总线上有没有出现错误帧。 

下面是一个小的例子

dword chechId;
dword numCheckEvents;
checkId=ChkStart_ErrorFrameOccured();//开始检测错误帧
TestAddCondition(checkId);//添加检测条件,如果出现了错误帧,则输出报告中会记录下来
TestWaitForTimeout(5000);//持续检测5s
checkControl_Stop(checkId);//停止检测错误帧
numCheckEvents=ChkQuery_NumEvents(checkId);//对5s内的检测情况进行获取,若函数返回0则没有出现错误帧
if(numCheckEvents>0)
     TestStepFail("Error Frames Occured");

5、添加事件信号 

这种事件信号相当于信号量机制,一般使用在需要等待某个或者是多个条件满足时进行下一步操作。

具体做法是:在一个位置添加需要等待的事件,程序中的其他地方,如果某个事件发生了(如周期超界等),提供该事件的供应,则等待的程序段获得了该事件,继续执行下面的操作。主要使用的函数有以下几个:

//供应text事件
long TestSupplyTextEvent( char aText[] );
//添加text事件
long TestJoinTextEvent(char[]aText);
//等待text事件,有一个出现则程序执行下一步
long TestWaitForAnyJoinedEvent(dword aTimeout);
//等待text事件,所有等待事件都出现则程序执行下一步
long TestWaitForAllJoinedEvents(dword aTimeout);

以下是一个例子:

TestJoinTextEvent("Test finished");
TestJoinTextEvent("Error Frame Occured");
TestWaitForAnyJoinedEvents(20000);
或者:
TestWaitForAllJoinedEvents(20000);
在系统事件on errorFrame中:
on errorFrame
{
   TestSupplyTextEvent("Error Frame occured");
}
在系统的on message 中:
on message 0x400
{
   TestSupplyTextEvent("Test Finished")
}

6、回调函数 

CAPL中也有类似于C语言中的回调函数的机制,如检测报文周期和错误帧的函数中就可以使用,当周期超界或者总线出现错误帧就会自动调用回调函数执行一些操作; 如:

 

 

ErrChkId=ChkStart_ErrorFramesOccured("Callback_ErrorFrameOccured");//检查错误帧,如果发现错误帧就调用回调函数
回调函数设计如下:
void Callback_errorFrameOccured(dword chk_id)
{
  float t;
  t=timeNow()/100000.0;//记录出现错误帧的时间
  testStep("ErrorFrameTimeStamp","%.6f s",t);//打印该事件戳
  TestSupplyTextEvent("ErrorFrameOccured");//供应Text事件
}

 

 

7、监视总线的情况,这一般会用在查看一段时间内,总线上有没有出现通讯异常的情况。需要使用函数ChkStart_NodeBabbling( ). 如,检测一段时间内总线有没有出现停止通讯的情况:

 

 

CheckId=ChkStart_NodeBabbling(CAN::PT_MCU,0);//立即开始检查总线状态
testWaitForTimeout(2000);//延时2s
ChkControl_Stop(CheckId);//停止检测
QueryNumberEvents=ChkQuery_NumEvents(CheckId);//如果在2s内总线停止通讯,则QueryNumberEvents!=0

 

 

8、关于获取关键时间点

(1)CANoe中获取定时器当前计时值的函数为:timerToElapse();该函数原型如下:

 

 

long timerToElapse(timer);
long timerToElpase(msTimer);                                 

 

 

(2)获取等待某个事件的时间,需要使用函数TestGetLastWaitElapsedTimeNS(),其原型如下:

 

 

float TestGetLastWaitElapsedTimeNS();

 

 

(3)获取当前的仿真时间点:

 

 

float timeNowFloat();

 

 

(4)等待指定报文:

 

 

long TestWaitForMessage(dbMessage aMessage,dword aTimeout);
long TestWaitForMessage(dword aMessageId,dword aTimeout);

 

 

若在aTimeout时间内等到了指定ID的报文,函数返回1,否则返回0;

(5)获取报文的数据,等到了报文之后,如果想知道报文的具体内容可以使用函数:

 

 

message msg;
long result;
result=TestGetWaitEventMsgData(msg);
.....处理msg.....

 

 

9、多总线测试 

设置总线背景,一般都总线测试都会有两路及以上的CAN,这时若要通过CAPL脚本获取某个CAN通道上的报文时,就需要先设置好总线背景,即将总线设置为值监听某一路的CAN通道。下面是一个例子:

 

 

void BusContextConfiguration(char yBus[])
{
   yBusContext=GetBusNameContext(yBus);//这里的yBusContext为全局变量
   SetBusContext(yBusContext);
}
//使用:
BusContextConfiguration("CAN1");//将总线监听设为CAN1

 

 

此时等待某一路的CAN报文可是这样实现:

 

 

res=testWaitForMessage(CAN1::NM_IPU,600);//等待CAN1上的名称为NM_IPU的报文,等待事件为600ms

 

 

10、诊断报文的发送和接收

 

 

request_A.SendRequest(); //诊断请求
TestWaitForDiagResponse(request_A, 5000); //诊断接收

 

 

11、

将诊断请求 / 响应写入报告

 

 

TestReportWriteDiagObject (diagRequest req);


TestReportWriteDiagObject (diagResponse resp);


TestReportWriteDiagResponse (diagRequest req);

 

 

12、获取诊断请求 / 响应的原始数据

 

 

long diagGetPrimitiveByte( diagRequest request, DWORD bytePos);


long diagGetPrimitiveByte( diagResponse response, DWORD bytePos);

 

 

13、获取诊断请求 / 响应的参数

 

 

long diagGetParameter (diagResponse obj, char parameterName[], double output[1])


long diagGetParameter (diagRequest obj, char parameterName[], double output [1])


double diagGetParameter (diagResponse obj, char parameterName[])


double DiagGetParameter (diagRequest obj, char parameterName[])


long diagGetParameter (diagResponse obj, long mode, char parameterName[], double output[1])


long DiagGetParameter (diagRequest obj, long mode, char parameterName[], double output [1])


double diagGetParameter (diagResponse obj, long mode, char parameterName[])


double diagGetParameter (diagRequest obj, long mode, char parameterName[])

 

 

最后分享最近刚使用CAPL脚本的一些注意点以及一个示例

第一CAPL 的局部变量是静态局部变量。经过使用发现,在 variables{ }之外,事件或者函数内部定义的局部变量是静态局部变量,其值不会因为退出本事件或者函数,而变为初始值。所以如果真的需要一个局部变量,在每次退出之前,重新使用赋值语句赋为初始值。

第二建议使用系统变量或者环境变量,这样可以跨不同的capl脚本操作,比如检测某个环境变量或者系统变量的变化,来执行一些动作。

 

 

on sysvar sysvar::EngineStateSwitch
{
  $EngineState::OnOff = @this;
if(@this)
  $EngineState::EngineSpeed = @sysvar::Engine::EngineSpeedEntry;
else
  $EngineState::EngineSpeed = 0;
}

 

 

第三个就是以太网转CAN的capl脚本示例:

 

 

/*@!Encoding:1252*/


variables
{
  //
  // Constants
  //
  
  const WORD kPort         = 23; // UDP port number for instance
  const WORD kRxBufferSize = 1500;
  const WORD kTxBufferSize = 1500;
  
  //
  // Structure of UDP payload
  //
  
  _align(1) struct CANData
  {
    BYTE  dlc;
    BYTE  flags; // Bit 7 - Frame type (0 = standard, 1 = extended)
                 // Bit 6 - RTR bit ('1' = RTR bit is set)
    DWORD canId;
    BYTE  canData[8];
  };
  
  //
  // Global variables
  //
  
  UdpSocket gSocket;
  CHAR      gRxBuffer[kRxBufferSize];
  CHAR      gTxBuffer[kTxBufferSize];
  DWORD     gOwnAddress;
  DWORD     gModuleAddress= 0xFFFFFFFF; // default is the broadcast address 255.255.255.255  and the TCP/IP stack will build the Network broadcast address
}


//
// Measurement start handler
//


on start
{
  DWORD addresses[1];
  
  // get own IP address of the Windows TCP/IP stack
  IpGetAdapterAddress( 1, addresses, elcount(addresses) );
  gOwnAddress = addresses[0];
  
  // open UDP socket
  gSocket = UdpSocket::Open( 0, kPort ); 
  
  if (gSocket.GetLastSocketError() != 0)
  {
    write( "<%BASE_FILE_NAME%> Open UDP socket failed, result %d. Measurement stopped!", gSocket.GetLastSocketError() );
    stop();
    return;
  }


  if (gSocket.ReceiveFrom( gRxBuffer, elcount(gRxBuffer) ) != 0)
  {
    if (gSocket.GetLastSocketError() != 997) // ignore pending IO operation
    {
      write( "<%BASE_FILE_NAME%> UDPReceive failed, result %d. Measurement stopped!", gSocket.GetLastSocketError() );
      stop();
      return;
    }
  }


}
//
// On receive UDP data handler using CAPL Callback 
//
void OnUdpReceiveFrom( dword socket, long result, dword address, dword port, char buffer[], dword size)
{
  DWORD          dataOffset;
  struct CANData canData;
  message *      canMsg;
  
  if (address == gOwnAddress) return; // ignore own broadcasts
  //
  // Store IP address of module to reach
  //
  
  if (gModuleAddress == 0)
  {
    gModuleAddress = address;
  }
  
  //
  // Handle received data
  //
  
  dataOffset = 0;
  while (dataOffset + __size_of(struct CANData) <= size)
  {
    memcpy( canData, buffer, dataOffset );
    
    canMsg.id      = (canData.canId & 0x1FFFFFFF) | ((canData.flags & 0x80) ? 0x80000000 : 0);
    canMsg.dlc     = canData.dlc & 0x0f;
    canMsg.rtr     = ((canData.flags & 0x40) ? 1 : 0);
    canMsg.byte(0) = canData.canData[0];
    canMsg.byte(1) = canData.canData[1];
    canMsg.byte(2) = canData.canData[2];
    canMsg.byte(3) = canData.canData[3];
    canMsg.byte(4) = canData.canData[4];
    canMsg.byte(5) = canData.canData[5];
    canMsg.byte(6) = canData.canData[6];
    canMsg.byte(7) = canData.canData[7];
    
    output( canMsg );
   
    dataOffset += __size_of(struct CANData);
  }
  


  //
  // Receive more data
  //
  if (gSocket.ReceiveFrom( gRxBuffer, elcount(gRxBuffer) ) != 0)
  {
    if (gSocket.GetLastSocketError() != 997) // ignore pending IO operation
    {
      write( "<%BASE_FILE_NAME%> UDPReceive failed, result %d. Measurement stopped!", gSocket.GetLastSocketError() );
      stop();
      return;
    }
  }
}


//
// Handler for CAN messages
//


on message *
{
  int i;
  struct CANData canData;
  
  if ((this.dir == RX) && (gModuleAddress != 0))
  {
    canData.canId = this.id & 0x1FFFFFFF;
    canData.flags = ((this.id & 0x80000000) ? 0x80 : 0x00) | ((this.rtr == 1) ? 0x40 : 0x00);
    canData.dlc   = this.dlc;
    
    for( i = 0; i < 8; i++ )
    {
      canData.canData[i] = (i < this.dlc) ? this.byte(i) : 0;
    }
    
    memcpy( gTxBuffer, canData );
    
    gSocket.SendTo( gModuleAddress, kPort, gTxBuffer, __size_of(struct CANData) );
  }
  else if (gModuleAddress == 0)
  {
    write( "<%BASE_FILE_NAME%> Tx not possible. Module to reach must send packets first." ); //Server simulation
  }
}
审核编辑:黄飞

 

 

 

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
评论(0)
发评论
jf_02442554 10-12
0 回复 举报
真的很牛逼 收起回复

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分