4、CAPL脚本入门:CAPL语言基础、事件函数(on message, on key)、简单路由逻辑实现
好,咱们进入第四讲。前面几章我们把CANoe的界面、工程配置、网关路由的基础概念都过了一遍。从这章开始,要动真格的了——写代码。
别紧张,CAPL其实没那么可怕。它不像C语言那么底层,也不像Python那么自由。它就是个专门为汽车总线测试设计的脚本语言。说白了,就是让CANoe按照你的想法去干活。
4.1 CAPL语言基础:先认识几个关键点
我第一次接触CAPL的时候,觉得它长得挺像C语言。确实,语法上有很多相似之处。但有几个地方,你得特别注意。
4.1.1 变量声明
CAPL里声明变量,跟C语言一样,要指定类型。常用的有这些:
| 类型 | 说明 | 示例 |
|---|---|---|
| int | 32位有符号整数 | int counter = 0; |
| dword | 32位无符号整数 | dword msgId = 0x123; |
| word | 16位无符号整数 | word length = 8; |
| byte | 8位无符号整数 | byte dataByte = 0xFF; |
| float | 浮点数 | float voltage = 12.5; |
| char | 字符 | char ch = 'A'; |
嗯,这里要注意:CAPL里没有全局变量和局部变量的说法,只有全局变量和事件函数内的变量。全局变量要声明在variables块里。
关键点:所有全局变量,必须写在 variables { } 这个大括号里。这是CAPL的规矩。
variables
{
int g_counter = 0;
dword g_lastMsgId = 0;
byte g_routingTable[10];
}
4.1.2 数组和结构体
数组用法跟C一样。结构体稍微有点不同,你得先定义再使用。
// 定义结构体
struct CanMessage
{
dword id;
byte dlc;
byte data[8];
};
// 声明结构体变量
struct CanMessage g_rxMsg;
我个人习惯把结构体定义放在variables块之前,这样清晰一些。
4.2 事件函数:CAPL的灵魂
CAPL是事件驱动的。什么意思呢?就是平时它闲着,只有特定事件发生了,它才会执行对应的代码。这些事件函数,就是CAPL的入口。
4.2.1 on message:最常用的事件
on message 是CAPL里最核心的事件函数。当CAN总线上有报文过来时,它就会被触发。
on message 0x123
{
// 当收到ID为0x123的报文时,执行这里的代码
write("收到报文: 0x%x", this.id);
}
这里有个关键字 this,它代表当前收到的报文对象。通过 this.id、this.dlc、this.byte(0) 可以访问报文的各个字段。
小技巧:如果你想让所有报文都触发,可以用 on message *。但要注意,这样性能开销会大一些。我在项目中一般只在调试阶段用 *,正式代码里都会指定具体的ID。
4.2.2 on key:键盘交互
这个函数用来响应键盘按键。调试的时候特别方便。
on key 'a'
{
write("你按下了 'a' 键");
}
on key 'F1'
{
write("你按下了 F1 功能键");
}
我曾经在测试网关路由时,就用 on key 来手动触发不同的路由策略。按 '1' 走路径A,按 '2' 走路径B,调试效率高了不少。
4.2.3 其他常用事件
| 事件函数 | 触发时机 |
|---|---|
| on start | 测量开始时执行一次 |
| on preStart | 测量开始前执行 |
| on stopMeasurement | 测量停止时执行 |
| on timer | 定时器到期时执行 |
| on errorFrame | 检测到错误帧时执行 |
4.3 简单路由逻辑实现:从理论到代码
好了,基础语法和事件函数都认识了。咱们来写点真正有用的东西——一个简单的网关路由逻辑。
假设场景是这样的:
- CAN1总线上有报文0x123,需要转发到CAN2总线
- 转发时,报文ID要改成0x456
- 数据内容保持不变
这个需求很典型吧?我在实际项目中遇到过无数次。下面就是实现代码:
variables
{
// 定义两个CAN通道的句柄
message 0x123 g_msgFromCan1;
message 0x456 g_msgToCan2;
}
on message 0x123
{
// 收到CAN1上的报文
if (this.can == 1) // 确认来自CAN1
{
// 复制数据到目标报文
g_msgToCan2.dlc = this.dlc;
for (int i = 0; i < this.dlc; i++)
{
g_msgToCan2.byte(i) = this.byte(i);
}
// 发送到CAN2
output(g_msgToCan2);
write("路由成功: 0x%x (CAN1) -> 0x%x (CAN2)", this.id, g_msgToCan2.id);
}
}
注意:这里有个容易踩的坑。如果你在 on message 里直接修改 this.id 然后 output(this),是不行的。因为 this 是只读的。你必须创建一个新的报文对象来发送。
3.3.1 用键盘控制路由开关
调试的时候,我们经常需要临时开启或关闭路由。用 on key 来实现就很简单:
variables
{
int g_routingEnabled = 1; // 1:开启, 0:关闭
}
on key 'r'
{
g_routingEnabled = !g_routingEnabled;
if (g_routingEnabled)
{
write("路由已开启");
}
else
{
write("路由已关闭");
}
}
on message 0x123
{
if (g_routingEnabled)
{
// 执行路由逻辑
// ... 代码同上
}
}
你看,这样在测试时按一下 'r' 键,就能控制路由的启停。我曾经在实车测试时,就用这个方法来模拟网关的故障模式,省了不少事。
4.4 本章小结
这一章我们学了CAPL的变量声明、事件函数,还写了一个简单的路由逻辑。你想想看,其实核心就三点:
- 变量声明:全局变量放
variables块里 - 事件函数:
on message处理报文,on key处理键盘 - 路由实现:收到报文 -> 创建新报文 -> 复制数据 -> 发送
下一章,我们会把这个简单的路由逻辑扩展成更复杂的场景,比如多ID路由、数据映射、信号级别的路由。嗯,到时候你会看到,CAPL的威力远不止于此。
好,今天就到这里。动手试试吧,在CANoe里建个新工程,写个 on message 看看效果。相信我,亲手跑通一次,比看十遍教程都管用。