【CANoe使用】CAPL基础脚本和实例

1. CAPL的编写界面

CAPL脚本是CANoe中一种类C的语言,需要与网络节点关联,也可以利用其加强测量分析功能,以及搭建高效的自动化测试模块。
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

2. CAPL基础

2.1 CAPL概述

CAPL的主要用途:

  • 仿真节点或模块
  • 仿真时间报文、周期报文或者附加条件的重复报文
  • 使用PC键盘模拟操作按钮等人工操作事件
  • 仿真节点的定时或网络事件
  • 仿真多个时间事件,每个事件都有自己特定行为
  • 仿真普通操作、诊断或生产操作
  • 仿真物理参数或报文的变化
  • 生成错误帧,评估模块和网络软件处理机制
  • 仿真模块或网络错误来评估相关的防错机制
  • 提供网络测试、诊断等功能测试库函数

CAPL语言有以下特性:

  • 集成在CANoe中的开发环境,是CAN总线访问编程语言(CAN Access Programming Language);
  • 类C语言,但也有一些C++特性(this指针,事件等);
  • 像C++一样允许函数重载;
  • CAPL提供一些自带很强大的库函数;
  • 可以更方便访问dbc文件和系统变量;
  • 通过事件驱动的语言

这里先看一下CAPL语言的程序结构,主要包含了:头文件、全局变量、事件函数、自定义函数
在这里插入图片描述

2.2 CAPL数据类型

2.2.1 变量的声明与定义

变量的命名的规则和C语言差不多,只不过要避开CAPL之中的关键字。
注:CAPL关键字分类(只是一部分):

  • 数据类型关键字:int, byte, word, dword.
  • 控制语句关键字:if, else
  • 存储类型关键字:static, extern
  • 其他关键字:const, message,timer

全局变量

在variables部分声明全局变量
可通过直接赋值进行初始化,如果没有初始化,编译器自动初始化为0
全局变量的作用域:整个CAPL文件以及与此文件有链接的其他CAPL文件

局部变量

局部变量被静态地创建,初始化只在程序体启动时执行,再次进入程序,局部变量被假定是上一次跳出程序时的值。
局部变量的作用域仅限于当前函数体范围内

2.2.2 简单数据类型

  1. 整型:byte(0-255),word, dword, gword, int, long, int64
  2. 字符:char, 可以和byte类型直接转换
  3. 浮点型

2.2.3 复合数据类型

结构体
4. 结构名在程序中必须唯一
5. 简单类型、枚举类型或者其他的结构都可以作为结构的成员
6. 结构体可以作为函数的入参,但是不能作为函数的返回值。

枚举
7. 枚举的成员名必须唯一(否则将有可能代替隐藏数据库中同名的报文和信号)
8. 如果没有在声明枚举的同时对成员进行赋值,编译器将按照成员声明的顺序对成员进行初始化(第一个为0,往后依次加1)

数组
9. CAPL直接用字符串初始化字符数组的行为
10. CAPL也支持多维数组
11. CAPL通过内建函数elCount(数组名)来获得数组成员的个数
12. CAPL中必须显式的定义数组的长度
13. 可以将数组作为函数的参数(例如: MyFunction(array[][]))

2.2.3 特殊数据类型


报文(message)

  1. 使用关键字message来声明一个报文变量,默认是CAN报文
  2. 声明报文变量,有数据库支撑时,完整的声明应包含message ID or message name(Message ID以x结尾的ID表示扩展帧)
  3. "*"表示这条报文在声明时还不含有CAN ID(表示一个不确定CANID的message变量)
  4. message可以作为函数的入参使用,具体代码如下:
   /*标准帧*/
   message 0x1AD msg1;//声明变量,can消息   十六进制
   message 100 m2;           //用消息id声明can消息   十进制
   message *msg;       //声明一个不确定CANID的message变量;
   message EngineData m3;    //用总线上加载的dbc数据库(EngineData)里消息名称声明can消息
   message 0x100 msg1={DLC=8};  //定义消息数据长度
   
   /*扩展帧*/ 
   message 100x msg1;         //消息id后面加x,表示扩展帧   十进制
   message 0x123456x msg2;    //消息id后面加x,表示扩展帧   十六进制 
   
   /*报文发送至总线上*/
  message * msg1;
  msg1.dlc = 8;
  message.ID = 0x100;
  output(msg1);          //报文发送至总线上必须要有ID

   on message 0x1AD
   {
       message *msg1;
       msg1 = this;
   }
   byte CalcMessageData(message *msg)  //message 作为函数的入参
   {
       return msg.byte(0);
   }

诊断报文

  1. 通过诊断请求和诊断响应两个对象来实现和ECU之间的诊断服务交互
  2. 在声明诊断对象时进行初始化
diagRequest ServiceQualifier request;
diagResponse ServiceQualifier response;

注:1)以上语句声明了对象request 和 response,通过给出诊断服务ServiceQualifier进行初始化;2)使用*代替ServiceQualifier,可用于初始化未添加诊断描述的空对象,但是在发送之前对象的数据必须完成具体设置


系统变量

  1. 系统变量用来描述某种特殊状态(eg.某种事件的触发)或者记录测量数据
  2. 可通过系统定义和用户自定义系统变量
  3. 系统变量的作用域在其命令空间内

定时器

  1. CAPL提供了两种定时器变量:timer和msTimer
  2. timer基于秒,msTimer基于毫秒

2.3 常见运算和流程控制语言

CAPL中的常见、关系运算符,流程控制语言与C语言没什么差别,这里就不一一赘述。以下说一下特殊的情况:

  1. 函数返回值未定义时则默认为void;
  2. CAPL中并不支持c语言指针的使用。更多使用的是数组和其他数据结构。

2.4 CAPL事件

常用的CAPL事件如下图所示:
在这里插入图片描述

2.4.1 系统事件

on preStart   	  			/*系统事件,初始化时执行*/
{ 
   
 	resetCan();      			/*CAPL接口函数,用于复位CAN控制器*/
}

on start        			/*系统事件,工程开始时执行*/
{ 
   
 	write(“Just A Try”);    	/*write()函数将字符串信息在”write”窗口输出*/
}


on preStop    				/*系统事件,工程预备停止时执行;发生在stopMeasurement事件前面*/
{ 
   
  	write("The Project Will Stop!”);
}

on stopMeasurement  		/*系统事件,工程停止时执行*/
{ 
   
  	write("The End!\n");
}

2.4.2 CAN控制器事件

	on busOff       /*CAN控制器事件:硬件检测到BusOff时执行*/
	{ 
   
	  	write("BusOff Error!");
	}

2.4.3 CAN消息事件

通过”on message [optional]”定义消息事件,该事件会在指定的报文消息被接收时被调用。关于消息事件的定义格式示例如下:

on message 123         		/*接收到123(10进制)这个ID的报文时执行*/
on message 0x441       		/*接收到0x441(16进制)这个ID的报文时执行*/
on message BCM 	       		/*接收到BCM(工程dbc文件中的报文名)这个报文时执行*/
on message *	      			/*接收到任意报文时都执行*/
on message 0x300-0x444	 	/*接收到这个范围内的ID报文时执行*/
on message CAN1.123        
{ 
 
	write(“Received %x”,this.id);	 /*打印接收到的报文id*/
	write(“Received Message %d in total!”,count);
}
on message 0x189
{

write("this.id = %x",this.id);//获取报文ID
write("this.name = %s",this.name);//获取报文名字
write("this.can = %d",this.can);//获取当前报文在哪路can上
write("this.dir = %d",this.dir);//获取当前报文是TX还是RX
write("this.dlc = %d",this.dlc);//获取当前报文的报文长度
write("this.dlc = %x",this.Byte(0));//获取当前报文的第一个字节
write("this.dlc = %x",this.QWord(0));//获取当前报文的第一个QWord(8个字节)
}

2.4.4 定时器事件

  1. 定时器变量用来创建一个定时事件
  2. SetTimer函数用来设定时间间隔
  3. 定时器运行到达设定时间间隔触发定时事件 on timer {}
  4. 周期性触发需要每次触发结束后使用SetTimer复位
  5. cancelTimer函数用来在定时器运行中需要取消计时
variables
{
  message 0x555 msgl;  //将报文0x555起名为msgl
  msTimer myTimer;     //声明一个毫秒计时器
}
on start
{
  setTimer(myTimer,100);    //将定时值设置为100ms并启动
}

//定时器超时,触发以下事件
on timer myTimer
{
  serTimer(myTimer, 100);  //复位定时器
  msgl.byte(0) = msgl.byte(0) +1;  //报文0x555的第一位上数据增加1
  output(msgl);    //发送报文0x555
} 

2.4.5 键盘事件

通过”on key”定义键盘事件,该事件会在我们按下指定按键时执行;关于键盘事件的定义格式示例如下:

on key ‘a’      	/*在小写输入法下,按下键盘的’A’键时执行*/
on key ‘A’      	/*在大写输入法下,按下键盘的’A’键时执行*/
on key ‘ ’      	/*按下键盘的空格键时执行,注意单引号中间是有空格的*/
on key 0x20     	/*按下键盘的空格键时执行*/
on key F2      	    /*按下键盘的’F2’键时执行*/
on key CtrlF3      	/*同时按下键盘的’Ctrl’键和’F3’键时执行*/
on key*      		/*按下键盘的任意键时都会执行(注意*与key之间没有空格) */
{ 
   
  	write(“The Key Is Press”);
} 

2.4.6 错误帧事件

错误帧由工具的CAN硬件检测,可以通过使用on errorFrame事件进行处理。当收到一个错误报文或者过载报文的时候触发这个事件。用于统计错误帧的数量和时间。
在这里插入图片描述

on errorFrame
{
  write("this.time = %fs",this.time/100000.0);//获取时间戳,时间单位是秒
  write("this.id = 0x%x",this.ID);
  write("this.ErrorPosition_Bit = %d",this.ErrorPosition_Bit);
  write("this.ErrorCode = 0x%x",this.ErrorCode);
}

2.4.7 环境变量事件

通过”on envVar”定义环境变量事件;该事件会在指定的环境变量值产生变化的响应(环境变量常常用于关联上一个面板控件,当我们对控件进行操作时,对应改变关联上的环境变量值;而此时我们在CAPL中关于该环境变量的事件就会被调用;以此完成交互操作)。关于环境变量事件的定义格式示例如下:

on envVar BCM_HightBeamAlarm    /*环境变量事件:指定的环境变量值有输入时执行*/
{ 
   
	  byte num=0;
	  num = getValue(this);     /*可以使用getValue(环境变量名/this关键字)获取指定的环境变量的值*/
	  if(num == 1)
	  { 
   
	    write("The envVar is %d",@BCM_HightBeamAlarm);  
	  }
	  else
	  { 
   
	    putValue(this,1);/*使用putValue(环境变量名/this关键字,设定的值)设置环境变量的值;直接赋值的话,格式是@BCM_HightBeamAlarm = 1; */
	    write("Change envVar to %d",@BCM_HightBeamAlarm);
	  }
}

2.4.8 系统变量事件

该事件是对系统变量发生变化的响应,我们写面板的时候这个语句用的最多

//以下语句定义了系统变量DI_O改变时,触发以下事件
on sysVar IO::DI_O
{
  $Gateway::IOValue1 = @this;  
}

//以下语句定义了系统变量DI_O更新时,触发以下事件
on sysvar_update IO::DI_O
{
  $Gateway::IOValue2 = @this;
}

2.4.9 CAN信号事件

CAN信号事件指在CAN总线上有指定的或任意的信号出现时产生的事件(需要配合DBC文件使用),关键字是:on signal xxx(指信号产生变化时)或on signal_update xxx(信号每次接收到)

on signal LightSwitch :: OnOff
{
    SAT1 = this;
}
on signal_update LightSwitch :: OnOff
{
    SAT2 = this;
}

2.4.10 诊断事件

诊断事件是在诊断请求或诊断响应发生时产生,常用诊断事件:
在这里插入图片描述

on diagRequest ECU.DefaultSession_Start
{
    //发生诊断请求事件
    write("Default Session switch request received");
}
on diagRequestSedn ECU.HardReset
{
    //诊断请求发送完成
    write("Hard reset service sent completely");
}
on diagReponse ECU.VehicleIdentification_Number_Read
{
    //收到响应的诊断请求
    write("VehicleIdentification_Number_Read Reponse received");
}

2.5 函数

CAPL其实自身有一个很强大的函数库,几乎可以满足用户绝大部分的需求,包括一些:

  • 计算函数
  • 字符串函数
  • CAN总线函数
  • LIN总线函数
  • 诊断函数
  • 通用函数

同时,用户可可以根据自己的需求,自定义一些满足自己独特需要的函数

CAPL函数致力于定义接口,形成模块化的代码以提高代码的重用性,它的语法和C语言很相似,但也与C的区别有以下几点:1)当返回类型省略时,被默认解释为void类型;
2)允许函数包含一个空的形参列表;
3)允许重载函数(即同一个函数名,但每个函数的形参列表不同);

byte CalcResult( byte c);
void CalcResult( char src[], char dst[], dword[]);

4)函数可用实参进行类型检查,如果类型不同则检查是否能够通过隐式类型转换,如不能,则无法通过编译;
5)任意维度或大小的数组都可被作为函数参数传递;

void printMatrix(int m[][])
{
    int i , j;
    for(i = 0; i < elcount(m), i++)
    { 
         for(j = 0; j < elcount(m[0]); j ++)
         { 
               write("%d", m[i][j]);     
           }
     }
}

6)大部分CAPL支持的数据类型都可以直接声明为函数参数,例如普通简单数据类型和符合类型,但有些类型不能被直接声明,而需要加上 * 号(注:该符号在这里不是C语言中指针的意思)。

注:例如signal *s 、envvarInt * ev、sysvarFloat *sv、diagRequest * dr、diagReponse * dr以及所有来自于database中的变量,均需要加上 * 号声明。另message类型比较特殊,如果该变量是用户自定义,那么函数参数声明时,message和message *都可以,但是如果变量来自dbc,那么只有message * 可用。下面的例子以dbc中信号为例:

foo ( signal * s)
{
    write("Signal name : %s", s.name);
}

2.6 总线数据库的使用

2.6.1 信号的访问

除了以报文作为事件,也可以将特定的信号作为事件:

// on signal + <signal name> : 当信号值发生改变时触发
 on signal SignalA
 { 
     write("1, signal value is updated");
     sigvalue = getSignal(SignalA); //内置函数getSignal获得信号物理值
     write("sig value is %f", sigvalue)
     write("sig value is %f", $SignalA)//符号$也能访问信号值或者给信号赋值
     write("sig value is %f", $SignalA.raw)//符号$也能访问信号的raw数据
 }
// on signal_update + <signal name> : 当信号发送至总线上时触发
 on signal_update SignalB
 { 
     write("2, signal is send to the bus");
 }
//信号的赋值可以通过符号$和内置函数setSignal实现
on start
{
   setSignal(SignalA, 5);
   $SignalA = 10;
}

在一个仿真工程中,信号的定义可能有重复,为了区别不同的信号,CAPL中需要加入channel,network,node, message信息限定修饰,即:[(channel | network)::] [[dbNode::]node::] [[dbMsg::]message::][[dbSig::]signal]

$LightSwitch::OnOff                       // node + signal
$LightSwitch::Lightstate::OnOff           // node + message + signal
$CAN1::Gateway::status                    // channel + node + signal
$PowerTrain::Gateway::status              // network + node + signal
$CAN1::Status                             //channel + signal

2.6.2 访问系统变量

可以通过库函数来访问系统变量,可以通过**@namespace::Variable**这个语法格式访问如下:

on key *
{
   //sysSetVariable<VarDataType>(sysvar, value);
   sysSetVariableInt(sysvar::SysDemo::SysVar_1,3);
   sysGetVariableInt(sysvar::SysDemo::SysVar_1,varvalue);
   write("var value is %d, varvalue");
}
on key 'd'
{
    @SysDemo::sysVar_1 = 5;
    write("sysvar value is %d",  @SysDemo::sysVar_1);
}

2.6.3 访问环境变量

与系统变量类似,可以使用语法访问,只是不需要加入namespace了,或者使用getvalue(),putvalue访问。

2.7 CAPL实例

那么如何使用CAPL脚本去发送一帧自定义的CAN、CANFD报文呢?

on key 'a'
{
    message 0xab msg1; //定义一帧CANID为0xab的报文
    msg1.dlc = 4;      //报文长度为4
    msg1.byte(0) = 0x11; //定义数据场内容
    msg1.byte(0) = 0x22;
    output(msg1); // 通过内置函数output()发送该报文
}

在编译完CAPL脚本之后,开始canoe工程,之后按下按键“a”,则触发事件并发送0xab报文;

除此之外,也可以通过CAPL脚本去发送一帧导入的dbc文件中的报文

on key 'b'
{
    message EngineState msg2; //定义dbc中以后的报文
    msg2.EngineSpeed = 100;      //定义信号值
    msg2.OnOff = 1; 
    output(msg2); // 通过内置函数output()发送该报文
}

那周期性地发送报文呢?这时我们则需要定时器以实现功能。

variables
{
   msTimer mytimer;
   message EngineState msg2; 
}

on start
{
   setTimer(mytimer, 20);//设定20ms的计时器;
}

on timer mytimer
{
     msg2.EngineSpeed = 100;      //定义信号值
     msg2.OnOff = 1; 
     output(msg2); // 通过内置函数output()发送该报文
     setTimer(mytimer, 20);//重置计时器;
}

相关推荐

  1. Linux:shell脚本基础使用(9)《数组》

    2024-07-12 19:48:05       54 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-07-12 19:48:05       67 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-07-12 19:48:05       71 阅读
  3. 在Django里面运行非项目文件

    2024-07-12 19:48:05       58 阅读
  4. Python语言-面向对象

    2024-07-12 19:48:05       69 阅读

热门阅读

  1. 如何部署本地dockers镜像源

    2024-07-12 19:48:05       19 阅读
  2. CAD二次开发(12)- 块的定义和使用

    2024-07-12 19:48:05       17 阅读
  3. MySQL在Windows系统上的详细安装指南

    2024-07-12 19:48:05       16 阅读
  4. ubuntu 换源

    2024-07-12 19:48:05       23 阅读
  5. Elasticsearch进阶学习

    2024-07-12 19:48:05       15 阅读
  6. 面向对象进阶基础练习

    2024-07-12 19:48:05       21 阅读
  7. RGB树-美团2023笔试(codefun2000)

    2024-07-12 19:48:05       25 阅读
  8. python输出/sys/class/power_supply/BAT0/电池各项内容

    2024-07-12 19:48:05       19 阅读
  9. ArcGIS Pro SDK (八)地理数据库 6 版本控制

    2024-07-12 19:48:05       23 阅读
  10. SpringBoot使用手册

    2024-07-12 19:48:05       18 阅读
  11. ROS2-humble学习

    2024-07-12 19:48:05       20 阅读