☰
CAPL 编程入门:从运算符、文件结构到条件编译的完整指南
2026/10/10 14:25:22 网站建设 项目流程

目录

前言

一、运算符

1. 比较运算符

1. 字符串比较

2. 浮点数比较

2. 逻辑运算符和位运算符

1. 逻辑非运算符

2. 按位取反运算符

二、CAPL 的文件结构

1. 文件分类

1. 文件分类

2. 节点类型

3. 文件编码

2. 文件结构

1. includes 模块

2. Variables 模块

3. 事件结构

1. 测量事件

2. 按键事件

3. 定时器事件

4. 值改变事件

5. 报文事件

6. 函数和测试函数

7. 测试用例

三、文件加密

四、CAPL的总线常量

五、条件编译

总结


前言

CAPL(Communication Application Programming Language)是 Vector CANoe 中用于仿真、测试和诊断的脚本语言,广泛应用于汽车电子领域的总线开发与验证。本文从运算符、文件结构、事件机制、总线常量到条件编译,系统梳理 CAPL 的核心语法与常见用法,帮助初学者快速上手,也为有经验的开发者提供一份可查阅的速查参考。


一、运算符

1. 比较运算符

1. 字符串比较

CAPL 语法支持数值类型直接比较,但是不能支持字符串类型的直接比较,实现字符串的比较,需要使用 CAPL 的内置函数;

long strncmp(char s1[], char s2[], long len);

long strncmp(char s1[], char s2[], long s2Offset, long len);

s1 和 s2 是从前往后,逐个字符进行比较,根据 ascii 码表中的字符值比较,如果 s1 中字符比 s2 小,返回 -1,否则返回 1;

如果 s1 和 s2 相等,则函数返回值为 0;如果前面字符都相等,如果 s1 长度小于 s2,返回 -1,否则返回 1;

代码示例:

// 字符串的比较 on key 'a' { char s1[10] = "abcd"; char s2[10] = "abd"; if(strncmp(s1, s2, strlen(s1)) == 0) { write("s1 和 s2 相等!"); } else { write("s1 和 s2 不相等!"); } }

2. 浮点数比较

浮点数的比较需要特别注意:

如果浮点数的精度已知,可以直接比较;

如果浮点数的精度未知(比如浮点数是用除法算出来的,可能出现除不尽的情况),就需要使用减法比较;

浮点数精度已知:

// 浮点数的比较 on key 'b' { float a = 3.14; float b = 3.14; if(a == b) write("a 和 b 相等!"); else write("a 和 b 不相等!"); }

浮点数精度未知:

比如 10 / 3,是无法除尽的,直接和 3.333 比较,就会出现不相等的情况。在实际应用中往往小数点一定位数就忽略了,上述的两个值认为是相等的。这种情况下,就需要使用减法比较:

on key 'c' { float a = 10 / 3.0; float b = 3.333; float diff = 0.01; if(abs(a - b) < diff) write("a 和 b 相等!"); else write("a 和 b 不相等!"); }

2. 逻辑运算符和位运算符

1. 逻辑非运算符

数值 0 为假,非 0 的值都为真;

空字符串为假,非空的字符串都为真,包括空格;

2. 按位取反运算符

小于 32bit 的整型(byte,word,int 类型)在进行位运算的时候,都会触发整型提升,将数据类型暂时提升为 long 类型,进行运算,运算完毕之后,再将结果赋值给原变量,进行截断;

比如 0x3C,进行按位取反:

0x3C(提升) :0000 0000 0000 0000 0000 0000 0011 1100

按位取反 :1111 1111 1111 1111 1111 1111 1100 0011

按位取反之后,得到的数字是负数,负数在内存中是以补码的形式保存的。因此要计算结果,就需要换算成源码再计算;

补码 -> 源码:先减 1,再取反;

减 1:1111 1111 1111 1111 1111 1111 1100 0010

取反:0000 0000 0000 0000 0000 0000 0011 1101

取反之后再阶段,取最后一个字节(0x3D),存到原来的 byte 变量中,结果就是 -61;

快速计算公式:~x = -(x + 1);

位运算最容易出错的地方是优先级,优先级不确定的时候,推荐加括号;

二、CAPL 的文件结构

1. 文件分类

1. 文件分类

CAPL 语言中,文件分为 .can 和 .cin 两种格式的文件,两种文件可以互相调用,变量和函数也可以相互引用。

通常来说,.can 文件中用于编写测试用例,.cin 文件中用于编写全局变量和函数,再在 .can 文件中通过 include 引用 .cin 文件;

2. 节点类型

CANoe 中有三种节点类型:网络节点,测试模块和编程节点;

Simulation Setup 窗口可以添加网络节点和测试节点:

Measurement Setup 窗口可以插入编程节点:

3. 文件编码

CAPL 开头的第一行代码是文件的编码方式,是 CAPL 根据计算机系统的语言设置,自动加上的;

/*@!Encoding:936*/:中文编码格式,支持中英文字符串和注释;

/*@!Encoding:ASCII*/:英文编码格式,不支持中文字;

/*@!Encoding:65001*/:通用的 UTF-8 编码格式,支持中英文字符串和注释;

在 CAPL Browser 的 File -> Options 中可以设置文件的编码方式:

2. 文件结构

1. includes 模块

includes 是 CAPL 文件用来导入文件和库的区域,可以导入 .can,.cin,.dll 等文件;

支持导入绝对路径和相对路径;

相对路径指的是相对于 xxx.cfg 文件的相对路径;

2. Variables 模块

Variables 用于定义 CAPL 的全局变量;

变量分为全局变量和局部变量;

局部变量陷阱:

局部变量的作用域是函数/测试用例代码块的内部,在 CAPL 中,局部变量都是静态变量。代码运行结束后,局部变量不会被释放,当再次调用代码块,局部变量还是上次代码运行结束后的值。

on key '1' { int ret; ret = func(); write("ret = %d", ret); } int func() { int b = 0; b++; return b; }

CANoe 12.0 SP3 之后,要定义真正的局部变量(非静态),可以使用stack关键字;

全局变量陷阱:

全局变量的作用域是整个 CAPL 文件,生命周期是从 CANoe 开始运行到结束;

假设有两个节点 NodeA 和 NodeB,两个节点都引用了 Header.cin,Header.cin 中定义了全局变量 gVar;NodeA 更改了 gVar,NodeB 节点读到的值还是原来的值,并不是 NodeA 改变之后的值;

3. 事件结构

1. 测量事件

on preStart:用于初始化变量,打印信息,读写文件,不可执行总线操作;

on start:用于初始化变量,启动定时器等;

on preStop:用于设置变量,打印信息及输出测试数据;

on stopMeasurement:用于设置变量,关闭定时器等;

测量事件的执行顺序为:on preStart -> on start -> on preStop -> on stopMeasurement;

但是在测试模块和测试单元中,不支持 on start 和 on stopMeasurement 事件,也不支持在 on preStart 和 on preStop 中初始化变量;

2. 按键事件

在 CAPL 中可以定义按键事件,按键之后触发按键事件,通常可以在 on key 语句中发送报文,更改信号,调试代码;

3. 定时器事件

可以在 on timer 中定义周期性事件,周期行进行触发;

4. 值改变事件

分为信号的值改变事件,系统变量的值改变事件,信号/系统变量值改变后进行触发;

5. 报文事件

可以通过 CAPL Browser 的 Filter 过滤总线,CAPL Browser 中只显示选中的总线的事件;

on message 接收到报文的时候触发;

on errorFrame 用于处理错误帧,常见的的错误帧有位错误,格式错误,填充错误,CRC 校验错误,ACK 错误等,可以通过 this.errorCode 判断错误帧类型;

errorCode 占两个字节,每个位从高到低依次为:

  • bit15:1 表示 protocol exception,0 表示正常;
  • bit14:1 表示错误帧是由 CANoe/CANalyzer 发送的;
  • bit13-12:0: RX NAK Error,1: TX NAK Error,2: RX Error, 3: TX Error;
  • bit11-6:0-Bit Error,1-Form Error,2-Stuff Error,3-Other Error,4-CRC Error,5-Ack Del Error,6-not used,7-Ack Error,8-Overload Frame,9-Protocol Exception Event;
  • bit5:1-RX,0-TX;
  • bit4-0:not used;

6. 函数和测试函数

1. 函数

函数是可重复使用的代码块,CAPL 中内置了很多用于仿真和测试的函数库,无需引用也可以使用;用户可以通过 includes 模块导入 dll 文件,来扩展 CAPL 的功能;

函数的返回值只能是基本的数据类型,或者 void 类型。CAPL 中通常使用输出型参数,进行输出;

2. 测试函数

测试函数只能在测试节点中使用,可以通过 testfunction 关键字定义测试函数;

测试函数可以在测试用例和测试序列中使用,并且会将函数的名称和测试结果输出到测试报告中,这是测试函数和函数的区别;

testfunction testFuncDemo() { testStep("", "测试函数 testFuncDemo 被执行!"); } testcase test1() { testFuncDemo(); } void MainTest() { test1(); }

在 XML 测试模块中,可以通过 xml 调用测试函数,但是不能调用普通函数。

7. 测试用例

测试用例是 CAPL 语言的固定功能块,可以通过 testcase 关键字创建测试用例,也可以进行传参和重载。

三、文件加密

CAPL Browser 可以进行文件加密保护源代码。

.can 文件加密后会生成 .canencr 文件,.cin 文件加密后会生成 .cinencr 文件;

加密完成后做好源文件备份,将源文件删除即可,生成的加密文件功能完全相同,但不能被查看和编辑,起到保护源代码的作用。

四、CAPL的总线常量

CAPL 语言内置了一些总线相关的常量,可以通过 %常量名称% 引用,返回值是字符串。可以直接插入字符串,也可以通过 %s 格式化输出到字符串中;

1. %NODE_NAME%

用户获取网络节点名称;

on key 'a' { write("this node name: %NODE_NAME%"); }

2. %CHANNEL%

用于获取网络节点分配的通道;

on key 'b' { write("this channel: %CHANNEL%"); }

3. %NETWORK_NAME%

用于获取仿真节点分配的网络名称;

on key 'c' { write("the network name: %NETWORK_NAME%"); }

4. %BUS_TYPE%

用于获取仿真节点分配的总线类型;

on key 'd' { write("the bus type: %BUS_TYPE%"); }

5. %FILE_NAME%

用于获取源文件名称,%FILE_NAME_NO_EXT% 用于获取不带扩展名的文件名称;

on key 'e' { write("the source file name: %FILE_NAME%"); write("the source file name: %FILE_NAME_NO_EXT%"); }

五、条件编译

条件编译是一种编译指令,可以根据某些条件决定是否编译某段代码。可以根据不同的编译环境,配置选项或者运行条件来启用或者禁用某些代码,提高代码的可移植性和灵活性;

CAPL 中的 includes,variable 模块和函数中都可以使用条件编译,并且支持常用的运算符(||、&&、>、<,==等等);

CAPL 内置的编译条件:

  • MEASUREMENT_SETUP:如果 CAPL 程序是 CANoe Measurement setup 的一部分,该值为 1;
  • SIMULATION_SETUP:如果 CAPL 程序不是 CANoe Measurement setup 的一部分,该值为 1;
  • ANALYSIS:如果 CAPL 程序是 CANoe Measurement setup 的一部分,该值为 1;
  • TEST_NODE:如果 CAPL 程序是 CAPL 测试节点或者测试库,该值为 1;
  • CANOE:如果 CAPL 程序是为 CANoe 编译的,该值为 1;
  • CANOE_LT:如果 CAPL 程序是为 CANoe LT 编译的,该值为 1;
  • CAPL_ON_BOARD:如果 CAPL 程序是为在网络接口上执行 编译的,该值为 1;
  • EXTENDED_REAL_TIME:如果 CAPL 程序是为扩展实时部分执行编译的,该值为 1;
  • TOOL_MAJOR_VERSION:CANoe 的主版本,比如 CANoe 15 SP7 中的 15;
  • TOOL_MINOR_VERSION:CANoe 的子版本,比如 CANoe 15 SP7 中的 7;
  • TOOL_SERVICE_PACK:CANoe 的服务包版本,比如 CANoe 15 SP7 中的 7;
  • DEBUG:如果激活了 CAPL Debug,则为真;
  • X64:如果 CAPL程序位于仿真设置或者测试设置中,并且使用 64 位 RT 内核,则为真;
  • LINUX:如果 CAPL程序位于仿真设置或者测试设置中,并且使用 LINUX 内核,则为真;
on key 'a' { #if(TOOL_MAJOR_VERSION == 15 && TOOL_MINOR_VERSION == 7) { write("CANoe 15.7"); } #endif #if SIMULATION { write("this file is in simulation node"); } #endif }

除了使用内置的编译条件,还可以使用 #define <Condition> 自定义编译条件;

#define 不是宏定义,不能进行赋值,也不能在 #if 和 #elif 之外的代码中使用;

定义时只能在 includes 和 variables 中;

includes { #define TEST_1 } variables { #define TEST_2 } on key 'b' { #if TEST_1 write("定义了 TEST_1"); #endif #if TEST_2 write("定义了 TEST_2"); #endif }

总结

本文系统梳理了 CAPL 的核心语法与常见用法:在运算符方面,重点说明了字符串比较需借助 strncmp 内置函数、浮点数比较需关注精度问题,以及位运算中的整型提升与补码换算;在文件结构方面,介绍了 .can 与 .cin 文件的职责划分、节点类型、文件编码方式,以及 includes 和 Variables 模块的使用要点,并提醒了局部变量静态特性与全局变量跨节点隔离等易踩的陷阱;在事件机制方面,覆盖了测量事件、按键事件、定时器事件、值改变事件和报文事件等常用场景;此外还介绍了文件加密、总线常量引用和条件编译等实用功能。

掌握这些内容后,读者已经具备编写基础 CAPL 脚本的能力。建议在实际项目中多结合 CANoe 的仿真与测试环境动手实践,逐步熟悉各类事件触发时机和调试技巧,从而更高效地完成总线开发与自动化测试任务。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询