3. CAPL编程基础与测试脚本骨架

CAPL(Communication Access Programming Language)是CANoe平台内嵌的类C事件驱动脚本语言。它不像普通C程序那样从main函数顺序执行,而是由总线事件、定时器事件和测量系统事件触发回调函数。理解这一机制,是写出可靠自动化测试脚本的前提。本节围绕事件驱动、变量作用域、定时器、消息收发和测试用例骨架五个要点展开,并给出可直接套用的脚本骨架。

一、事件驱动机制:脚本何时被执行

CAPL脚本由若干事件处理程序(event handler)组成,每个处理程序以关键字开头,例如 on start、on message、on timer、on key、on signal。CANoe在测量运行期间监听这些事件,一旦条件满足就调用对应函数体。脚本中未放入任何事件处理程序的语句不会被执行,这是初学者最常见的错误。

常见事件类型及其触发时机如下:

  • on start:测量开始时执行一次,用于初始化变量、启动定时器、设置初始报文。
  • on message <Msg>:总线上收到指定报文时触发,用于响应式逻辑。
  • on timer <T>:定时器到期时触发,用于周期发送或超时判断。
  • on signal <Sig>:指定信号值更新时触发,适合做信号级监控。
  • on key 'x':按下键盘按键时触发,便于手动干预调试。

需要特别注意:on message 与 on signal 的触发依赖总线实际收到报文,若节点未发送或通道配置错误,处理程序永远不会被调用。因此调试时应先用Write窗口打印确认事件是否触发。

二、变量作用域:三类变量的选择

CAPL变量按作用域分为全局变量、局部变量和系统变量。全局变量在 variables 块中声明,整个脚本可见,生命周期贯穿整个测量;局部变量在函数或事件处理程序内部声明,仅在该块内有效;系统变量(System Variable)由CANoe数据库定义,可通过 @sysvar::命名空间::变量名 访问,用于面板、测试模块和脚本之间共享数据。

选择原则:跨事件处理程序共享的状态(如计数器、当前测试步骤)用全局变量;临时计算用局部变量;需要与Panel或Test Module交互的数据用系统变量。全局变量在测量重启时会被重新初始化,若需保持状态应写入系统变量或使用 write/read 持久化。

三、定时器:周期发送与超时控制

CAPL定时器通过 msTimer 或 timer 声明,使用 setTimer() 启动、cancelTimer() 取消。定时器到期后触发对应的 on timer 处理程序。典型用法有两种:一是周期发送报文,在 on timer 中发送后再次 setTimer 实现循环;二是超时监控,发送请求后启动定时器,若在超时前收到响应则取消定时器,否则判定超时。

variables
{
  msTimer tSend;
  msTimer tTimeout;
  int     gRespFlag = 0;
}

on start
{
  setTimer(tSend, 100);   // 100ms后首次发送
}

on timer tSend
{
  message 0x123 msg;
  msg.dlc = 8;
  msg.byte(0) = 0x01;
  output(msg);
  setTimer(tSend, 100);   // 重新装载,形成100ms周期
}

on message 0x456
{
  gRespFlag = 1;
  cancelTimer(tTimeout);  // 收到响应,取消超时
}

on timer tTimeout
{
  write("响应超时,测试失败");
}

四、消息收发:报文与信号操作

发送报文使用 output(),接收报文通过 on message 处理。CAPL中报文对象可直接访问 dlc、byte(i)、id 等属性。若使用数据库(DBC)定义,还可通过信号名直接读写,例如 msg.EngineSpeed = 1500;,CAPL会自动完成信号打包与字节序处理。信号级操作比手工拼字节更安全,推荐优先使用。

接收侧若需读取信号,可在 on message 中直接访问 this.EngineSpeed 或对应报文变量。注意信号更新与报文接收是同一时刻,若需做滤波或去抖,应在处理程序中自行实现计数逻辑。

五、测试用例结构:从脚本到可判定结果

自动化测试脚本的核心是“激励—等待—判定”。一个完整的测试用例通常包含:初始化条件、发送激励、等待响应或超时、比较实际值与期望值、输出判定结果。CAPL中可用 testWaitForMessage()、testWaitForSignal() 等测试函数简化等待逻辑,并用 testStepPass()/testStepFail() 输出结果到Test Report。

下面给出一个可直接套用的测试用例骨架,以“验证0x123请求后0x456在200ms内响应”为例:

testcase TC_ResponseCheck()
{
  message 0x123 req;
  message 0x456 resp;
  int     result;

  // 1. 初始化
  req.dlc = 8;
  req.byte(0) = 0x01;

  // 2. 发送激励
  output(req);

  // 3. 等待响应,超时200ms
  result = testWaitForMessage(0x456, 200);

  // 4. 判定
  if (result == 1)
  {
    testStepPass("收到0x456响应");
  }
  else
  {
    testStepFail("200ms内未收到0x456响应");
  }
}

该骨架中,testWaitForMessage 返回1表示在超时前收到报文,返回0表示超时。实际项目中还应校验响应报文的信号值是否在期望范围内,例如 resp.byte(1) == 0x02,只有报文和内容都正确才判定通过。

建议在 on start 中统一初始化全局变量和定时器,避免在多个测试用例中重复初始化导致状态污染。测试用例之间应通过系统变量或全局变量传递上下文,而不是依赖隐式顺序。

六、事件驱动与测试用例的配合关系

事件驱动机制与测试用例结构并非对立,而是互补。测试用例负责“主动发起激励并判定”,事件处理程序负责“被动响应总线变化”。在复杂场景中,常用做法是:测试用例发送请求后,由 on message 处理程序记录响应状态到全局变量,测试用例再通过轮询或等待函数读取该变量完成判定。这样既保留了事件驱动的实时性,又让测试逻辑保持线性可读。

CAPL事件驱动测试脚本骨架 事件源 on start 测量启动初始化 on timer 周期/超时触发 on message 报文到达触发 on signal 信号更新触发 on key 手动按键触发 共享状态 全局变量 跨事件共享状态 系统变量 Panel/Test共享 msTimer 周期/超时控制 局部变量 函数内临时计算 gRespFlag 响应标志位 测试用例结构 ① 初始化条件 设置报文/信号初值 ② 发送激励 output(req) ③ 等待响应/超时 testWaitForMessage(0x456,200) ④ 比较判定 报文ID + 信号值校验 ⑤ 输出结果 testStepPass / testStepFail 测试结果反馈至事件处理程序,形成闭环

上图展示了CAPL脚本中事件源、共享状态与测试用例三者的关系:左侧事件源触发处理程序,中间通过全局变量、系统变量和定时器维护共享状态,右侧测试用例按“初始化—激励—等待—判定—输出”五步执行,并通过全局变量与事件处理程序交换数据,最终形成闭环。实际开发时,建议先画出这样的关系图,再逐块填充代码,可显著减少事件遗漏和状态竞争问题。

常见错误:在 on message 中直接调用 testStepPass 等测试函数,可能导致测试报告步骤与用例结构错乱。正确做法是在事件处理程序中只更新状态变量,由测试用例统一判定和输出。