CAPL 编程系列教程 - 第二十三期:事件之 on signal 与 on signal_update

CAPL 编程系列教程的第二十三期内容 ,继续讲解 CAPL 中的事件 (Events),本期重点是与信号 (Signal) 相关的两个事件:on signalon signal_update,以及多种获取信号值的方法。

CAPL 编程系列教程 - 第二十三期:事件之 on signal 与 on signal_update

一、 课程回顾与本期目标

  • 回顾: 前面学习了 CAPL 的事件驱动模型、系统测量事件、报文接收事件 (on message)。
  • 本期目标: 学习专门用于响应信号变化的两种事件:on signalon signal_update,理解它们的区别、用法,以及如何在事件处理程序或其他地方获取信号的值。
  • 前提条件: 使用这两个事件必须在 CANoe 工程中加载了包含相应信号定义的 DBC 文件。如果没有 DBC 文件定义该信号,这两个事件将无意义。

二、 信号事件详解

  1. on signal <SignalName>

    • 触发时机: 仅当指定的信号 <SignalName> 在总线上出现,并且其与上一次接收到的该信号的值发生变化时,才会触发一次。
    • 逻辑: 如果信号周期性发送,但其值保持不变,则只有在第一次从无到有出现该信号时,或者后续某次值发生改变时才会触发。值不变的后续接收不会触发。
    • 用途: 主要用于捕获和响应信号值的有效改变,执行与状态变化相关的逻辑。
    • 语法:
      Code snippet
      on signal EngineSpeed { // 当 EngineSpeed 信号的值发生变化时执行
          // ... 处理代码 ...
      }
      
  2. on signal_update <SignalName>

    • 触发时机: 只要指定的信号 <SignalName> 在总线上被接收到(更新)无论其值是否发生变化,都会触发一次。
    • 逻辑: 如果信号周期性发送(如 100ms 一次),那么这个事件处理程序也会以 100ms 的周期被触发执行。
    • 用途: 当需要每次信号出现都执行某些操作时使用(例如,每次都记录该信号的出现,或基于每次出现做一些计算),即使值没有变化。相对 on signal 用得可能较少。
    • 语法:
      Code snippet
      on signal_update EngineSpeed { // 每当收到 EngineSpeed 信号时执行
          // ... 处理代码 ...
      }
      
  • 对比: on signal 关注变化on signal_update 关注每次出现/更新

三、 在信号事件中获取信号值 (this 关键字)

  • this 的含义: 在 on signalon signal_update 事件处理程序的代码块内部,关键字 this 代表当前触发事件的那个信号的值
  • this 的类型: 直接使用 this 时,它代表的是信号的物理值 (Physical Value),并且其数据类型始终是浮点数 (floatdouble),即使信号在 DBC 中定义为整数。
  • 获取物理值:
    • this: 直接使用 this 获取物理值 (浮点数)。
    • this.physical (或 this.phys): 使用 .physical 选择器明确获取物理值 (浮点数),效果与直接用 this 相同,但更清晰。
  • 获取原始值:
    • this.raw: 使用 .raw 选择器获取信号的原始值 (Raw Value) (即根据位域提取出来,但未经过系数和偏移量转换的整数值),返回值类型也是浮点数 (虽然其值通常是整数)。
  • 类型转换: 由于 thisthis.physicalthis.raw 返回的都是浮点数,如果需要将其作为整数使用(例如用 %d 输出或进行整数运算),需要进行强制类型转换 (int)
    • 示例: write("当前物理值: %d", (int)this);write("当前原始值: %d", (int)this.raw);
  • 物理量与原始量:
    • 物理量是经过 DBC 中定义的系数 (Factor) 和偏移量 (Offset) 转换后的、具有实际物理意义的值(如 2000 rpm)。
    • 原始量是信号在报文字节中直接对应的数值(通常是整数),未进行转换。
    • 仅当信号的系数为 1 且偏移量为 0 时,物理量才等于原始量。

四、 在其他地方获取信号值的多种方法 (不局限于信号事件)

  • 场景: 有时需要在非信号事件处理程序中(如 on key, on timer, on message 或自定义函数)主动获取某个信号的当前值。此时不能使用 this 来代表信号值。

  • 方法:

    1. 美元符号 ($) + 信号名:
      • 语法: $SignalName
      • 作用: 直接获取信号 <SignalName>当前物理值
      • 类型: 返回值是浮点数
      • 选择器: 同样可以使用 .physical (效果同不加) 或 .raw 获取物理量或原始量(仍为浮点数)。
        • $SignalName 等同于 $SignalName.physical
        • $SignalName.raw 获取原始值。
      • 优点: 语法简洁。
      • 注意: 信号名前必须加 $ 符号。
    2. getSignal() 函数:
      • 语法: getSignal(SignalName)getSignal("SignalName")
      • 作用: 获取信号 <SignalName>当前物理值
      • 参数: 可以是信号的符号名(如 EngineSpeed)或信号名称的字符串(如 "EngineSpeed")。
      • 类型: 返回值是浮点数
      • 限制: 只能获取物理量。
    3. getRawSignal() 函数:
      • 语法: getRawSignal(SignalName)getRawSignal("SignalName")
      • 作用: 获取信号 <SignalName>当前原始值
      • 参数: 同 getSignal()
      • 类型: 返回值是浮点数(即使原始值是整数)。
      • 限制: 只能获取原始量。
  • 命名空间/冲突解决 (适用于方法 1 和方法 2/3): 如果信号名在不同报文或节点间存在冲突,可以在信号名前加上限定符,用双冒号 :: 分隔:

    • NodeName::SignalName (节点限定)
    • MessageName::SignalName (报文限定)
    • NodeName::MessageName::SignalName (节点和报文限定)
    • (视频中还尝试了 BusName::...DatabaseName::... 但似乎当前版本不支持或语法有误)
    • 通常,直接使用信号名即可;若有冲突,优先考虑用报文名限定。

五、 示例与演示

  • 教程使用上一期的仿真 ECU (EMS.can) 周期性发送 EngineState (ID 0x123) 报文,并在其中加入了随机逻辑,使得 EngineSpeed 信号的值在 500 和 800 之间随机变化。
  • 在测试脚本 (capl学习-23.can) 中:
    • 编写 on signal EngineSpeed 事件,在其值变化时打印变化信息和当前值 (使用 this)。
    • 编写 on signal_update EngineSpeed 事件,在每次收到信号时打印接收信息和当前值 (使用 this)。
    • 通过运行对比两个事件的输出频率,清晰地展示了它们的区别(_update 频繁触发,on signal 仅在 500/800 切换时触发)。
    • 演示了在 on signal 事件中使用 this, this.physical, this.raw 获取物理量和原始量的区别(通过修改 DBC 中信号的系数和偏移量来体现差异)。
    • 演示了使用 $EngineSpeed, $EngineSpeed.raw, getSignal(EngineSpeed), getRawSignal(EngineSpeed) 这几种方式获取信号值,并验证其结果与 this 方式获取的一致性。

六、 总结与后续

  • 本期详细学习了 CAPL 中响应信号的两个核心事件 on signal(值变化时触发)和 on signal_update(每次更新时触发)的区别与用法。
  • 掌握了在信号事件中使用 this 关键字获取信号物理值 (thisthis.physical) 和原始值 (this.raw) 的方法,并注意其返回类型为浮点数。
  • 学习了在任意代码位置主动获取信号值的其他常用方法:$<SignalName>getSignal()getRawSignal()
  • 这些事件和函数是编写需要监控和响应特定信号变化的 CAPL 脚本的基础。