【CANoe使用】CAPL基础脚本和实例
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 简单数据类型
- 整型:byte(0-255),word, dword, gword, int, long, int64
- 字符:char, 可以和byte类型直接转换
- 浮点型
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)
- 使用关键字message来声明一个报文变量,默认是CAN报文
- 声明报文变量,有数据库支撑时,完整的声明应包含message ID or message name(Message ID以x结尾的ID表示扩展帧)
- "*"表示这条报文在声明时还不含有CAN ID(表示一个不确定CANID的message变量)
- 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);
}
诊断报文
- 通过诊断请求和诊断响应两个对象来实现和ECU之间的诊断服务交互
- 在声明诊断对象时进行初始化
diagRequest ServiceQualifier request;
diagResponse ServiceQualifier response;
注:1)以上语句声明了对象request 和 response,通过给出诊断服务ServiceQualifier进行初始化;2)使用*代替ServiceQualifier,可用于初始化未添加诊断描述的空对象,但是在发送之前对象的数据必须完成具体设置
系统变量
- 系统变量用来描述某种特殊状态(eg.某种事件的触发)或者记录测量数据
- 可通过系统定义和用户自定义系统变量
- 系统变量的作用域在其命令空间内
定时器
- CAPL提供了两种定时器变量:timer和msTimer
- timer基于秒,msTimer基于毫秒
2.3 常见运算和流程控制语言
CAPL中的常见、关系运算符,流程控制语言与C语言没什么差别,这里就不一一赘述。以下说一下特殊的情况:
- 函数返回值未定义时则默认为void;
- 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 定时器事件
- 定时器变量用来创建一个定时事件
- SetTimer函数用来设定时间间隔
- 定时器运行到达设定时间间隔触发定时事件 on timer {}
- 周期性触发需要每次触发结束后使用SetTimer复位
- 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);//重置计时器;
}
原文地址:https://blog.csdn.net/Gagaaaaaa/article/details/140361576
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!