3. CAPL语言基础与测试脚本结构
CAPL(Communication Access Programming Language)是Vector为CANoe量身打造的类C事件驱动脚本语言。它不像通用编程语言那样从main函数顺序执行,而是围绕"事件"组织代码:总线报文到达、定时器超时、按键触发、测量开始或结束,都会调用你预先写好的事件处理函数。理解CAPL的变量、数据类型、运算符、流程控制、函数定义以及事件驱动机制与程序生命周期,是写出可靠自动化测试脚本的前提。
变量与数据类型
CAPL的变量按作用域分为全局变量和局部变量。全局变量在variables块中声明,整个测量期间持续存在,可被多个事件处理函数共享;局部变量在函数或事件块内部声明,只在本次调用中有效。CAPL是静态类型语言,声明时必须给出类型,常用类型如下:
int:16位有符号整数,范围约 -32768~32767,适合计数器、状态码。long:32位有符号整数,适合报文ID、时间戳、累加量。dword:32位无符号整数,常用于原始报文数据。float/double:浮点数,用于电压、温度、物理量计算。byte:8位无符号整数,适合单字节数据。char:字符类型,配合字符串使用。message:报文对象,如message 0x123 msg1;声明一条ID为0x123的报文。timer:定时器对象,用于周期或延时触发。msTimer:毫秒级定时器,最常用。
数组声明形如int buf[8];,字符串用char name[32];。CAPL还支持结构体struct,可把多个相关字段打包,例如把一条诊断请求的SID、子功能、数据长度组合成一个结构体,便于在多个函数间传递。
运算符与流程控制
CAPL运算符与C基本一致:算术运算符+ - * / %,关系运算符== != > < >= <=,逻辑运算符&& || !,位运算符& | ^ ~ << >>,以及赋值和复合赋值= += -=。处理报文数据时位运算尤其重要,例如从数据字节中提取某几位:value = (data[0] >> 4) & 0x0F;。
流程控制包括if/else、switch/case、while、for、do...while。CAPL没有goto,但支持break和continue。在测试脚本中,switch常用于根据报文ID或诊断服务ID分派处理逻辑,for常用于遍历数组或循环发送多帧报文。需要注意:CAPL事件处理函数中应避免长时间阻塞循环,否则会拖慢整个测量系统的实时响应。
函数定义
CAPL函数用void或具体返回类型定义,例如:
int Add(int a, int b)
{
return a + b;
}
void SendTestFrame(long id, byte data0)
{
message *msg;
msg.id = id;
msg.dlc = 8;
msg.byte(0) = data0;
output(msg);
}
函数可以带参数和返回值,支持递归调用。CAPL还提供大量内置函数,如output()发送报文、write()输出日志、setTimer()启动定时器、cancelTimer()取消定时器、testWaitForMessage()等待报文、testStep()记录测试步骤。自定义函数应尽量短小、职责单一,把"构造报文""发送并等待响应""校验结果"拆开,便于复用和排错。
事件驱动机制与程序生命周期
CAPL程序的生命周期由CANoe测量控制:测量开始(Start)时,CAPL程序被加载并初始化,on start事件块执行;测量运行期间,各种事件按发生顺序触发对应处理函数;测量停止(Stop)时,on stop事件块执行,程序结束。常见事件块包括:
on start:测量启动时执行一次,适合初始化变量、启动定时器、发送首帧。on stop:测量停止时执行一次,适合保存结果、释放资源。on message 0x123:收到指定ID报文时触发,是总线测试最核心的事件。on timer myTimer:定时器超时触发,用于周期发送或超时判断。on key 'a':按下键盘按键触发,便于手动干预测试。on signal UpdateSignal:信号值变化时触发,适合基于信号层的测试。
下面这张图展示了CAPL程序从测量开始到结束的事件驱动执行路径,以及全局变量、事件处理函数与内置函数之间的调用关系。
一个可运行的测试脚本骨架
把上述要素组合起来,下面是一个典型的CAPL测试脚本结构,用于周期发送报文并统计接收次数:
variables
{
msTimer tSend;
int rxCount = 0;
int txCount = 0;
}
on start
{
write("测试开始");
setTimer(tSend, 100);
}
on timer tSend
{
message 0x123 msg;
msg.dlc = 8;
msg.byte(0) = txCount & 0xFF;
output(msg);
txCount++;
setTimer(tSend, 100);
}
on message 0x456
{
rxCount++;
if (rxCount >= 10)
{
cancelTimer(tSend);
write("已收到10帧响应,停止发送");
}
}
on stop
{
write("发送帧数=%d,接收帧数=%d", txCount, rxCount);
}
这段脚本中,variables块声明全局定时器和计数器;on start启动100ms周期定时器;on timer每100ms发送一帧0x123并递增发送计数;on message 0x456收到响应后累加接收计数,达到10帧时取消定时器;on stop输出统计结果。整个脚本没有main函数,执行完全由事件驱动。
testWaitForMessage()、testWaitForTimeout()等测试函数,或通过定时器分步推进,否则会阻塞CANoe的实时事件处理,导致报文丢失或测量异常。