CAPL 编程系列教程 - 第二十二期:事件之 on message (报文接收)

CAPL 编程系列教程的第二十二期内容 (视频中口误称为第 20 期,继续讲解 CAPL 中的事件 (Events),本期重点是极其重要和常用on message 事件,即报文接收事件。

CAPL 编程系列教程 - 第二十二期:事件之 on message (报文接收)

一、 课程回顾与本期目标

  • 回顾: 前面几期学习了 CAPL 的事件驱动模型、系统测量事件 (on start 等)。
  • 本期目标: 详细学习 on message 事件,理解其触发机制、语法,以及如何在事件处理程序中访问接收到的报文信息。

二、 on message 事件详解

  1. 触发时机:
    • 当 CANoe/CANalyzer 等测量工具在总线上探测到(接收到) 一个特定的 CAN 报文时,每次接收到就会触发一次 on message 事件。
    • 如果一个报文周期性发送(如每 100ms 一次),那么对应的 on message 事件处理程序也会以相同的频率被触发执行。
  2. 基本语法:
    Code snippet
    on message <MessageSpecifier> {
        // Code to execute when the specified message is received
    }
    
  • MessageSpecifier (报文指定符): 用于指定要响应该哪个或哪些报文。可以是:
    • 报文 ID: on message 0x123 { ... }on message 291 { ... } (十六进制或十进制 ID)。
    • DBC 报文名称: on message EngineState { ... } (前提是已加载包含该名称的 DBC 文件)。
    • ID 范围: on message 0x100-0x200 { ... } (使用连字符 - 指定一个 ID 范围,包含边界)。
    • 通配符 *: on message * { ... } (响应所有接收到的报文)。
  1. this 关键字 (核心):
    • on message 事件处理程序的代码块内部,关键字 this 是一个特殊的预定义变量
    • this 代表当前刚刚接收到的那一帧报文
    • this 本身就是一个 message 类型的数据。
    • 可以通过 this 加上点运算符 (.) 来访问当前接收到的报文的各种属性和数据。

三、 通过 this 访问报文信息

  • 访问报文属性:
    • ID: this.id - 获取报文的 ID (返回值为十进制整数)。
    • DLC: this.dlc - 获取报文的数据长度码 (Data Length Code, 0-8 for CAN, 0-15 for CAN FD in some contexts)。
    • 数据字节数: this.dataLength - 获取报文数据域实际包含的字节数。对于标准 CAN,通常等于 DLC;对于 CAN FD,可能不同(DLC 值 9-15 映射到更大的字节数)。
  • 访问信号 (需加载 DBC):
    • 语法: this.SignalNamethis.SignalName.phys
    • 如果 CANoe 工程加载了包含该报文信号定义的 DBC 文件,可以直接用 this. 信号名称来访问信号。
    • 获取的是物理值,CAPL 会自动根据 DBC 进行解析(位提取、字节序处理、系数偏移量计算等)。
    • 示例: engineRpm = this.EngineSpeed; status = this.OnOff;
  • 访问原始字节 (无需 DBC 或需要原始数据时):
    • 按字节: this.byte(index) - 获取指定索引 index (0-N) 的字节值。
    • 按字: this.word(index) - 获取从字节索引 index 开始的两个字节组成的一个字 (word) 的值。需要注意字节序(CAPL 通常按定义的字节序处理,但获取原始 word 时可能需要手动处理或理解其表示方式)。
    • 其他: 可能还有 .dword(), .qword() 等。
    • 用途: 在没有 DBC 文件,但知道信号在字节中的位置时,或者需要对原始字节数据进行位操作等底层处理时使用。
    • 示例 (手动解析信号):
      • 获取 OnOff 信号 (假设在 byte 0 的 bit 0): status = this.byte(0) & 0x01; (使用位与操作提取最低位)。
      • 获取 EngineSpeed 信号 (假设在 word 0 中,需要右移 1 位): rawSpeed = this.word(0) >> 1; (需要根据具体位域和字节序进行精确计算)。

四、 示例与演示

  • 准备工作: 为了演示 on message,需要有一个报文源。教程中创建了另一个 CAPL 节点 (EMS.can) 来模拟 ECU,周期性发送 ID 为 0x123 (EngineState) 的报文。
  • 测试代码 (capl学习-22.can):
    Code snippet
    variables { /* ... */ }
    
    // 响应特定 ID 或名称的报文
    on message EngineState { // 或 on message 0x123 或 on message 0x100-0x200
        long id_dec;
        int dlc_val;
        int len_val;
        int engineRpm;
        int onOffStatus;
        byte b0, b1;
        word w0;
    
        id_dec = this.id; // 获取十进制 ID
        dlc_val = this.dlc;
        len_val = this.dataLength;
    
        write("收到报文: ID=0x%X (%d), DLC=%d, Length=%d", id_dec, id_dec, dlc_val, len_val);
    
        // 通过信号名访问 (需要 DBC)
        engineRpm = this.EngineSpeed;
        onOffStatus = this.OnOff;
        write("  信号: EngineSpeed=%d rpm, OnOff=%d", engineRpm, onOffStatus);
    
        // 通过原始字节访问 (示例)
        b0 = this.byte(0);
        b1 = this.byte(1);
        w0 = this.word(0); 
        write("  原始字节: Byte0=0x%X, Byte1=0x%X, Word0=0x%hX", b0, b1, w0); 
        // 注意:%hX 用于输出 word (通常16位) 的十六进制
    
        // 手动解析示例
        onOffStatus = this.byte(0) & 0x01; // 假设 OnOff 在 byte0, bit0
        engineRpm = (this.word(0) >> 1);   // 假设 EngineSpeed 在 word0, bit 1-15 (简化示例)
        write("  手动解析: OnOff=%d, EngineSpeed (raw shifted)=%d", onOffStatus, engineRpm);
    
        write("--------------------------------"); 
    }
    
  • 运行验证: 启动 CANoe 工程,观察 Write 窗口。由于 EMS.can 节点周期性发送 0x123 报文,on message EngineState 会被反复触发,每次触发都会打印出接收到的报文的 ID、DLC、字节数、通过信号名解析出的物理值、原始字节值以及手动解析(示例)的值。

五、 总结与后续

  • 本期深入学习了 CAPL 中处理报文接收的核心事件 on message
  • 重点掌握了 on message 的触发条件、多种指定报文的方式,以及如何使用 this 关键字访问接收报文的各种属性(ID, DLC, Length)和数据(通过信号名或原始字节/字)。
  • 理解通过信号名访问的便捷性(依赖 DBC)与通过原始字节访问的灵活性(不依赖 DBC 但需手动解析)。
  • on message 是编写需要响应总线通信的 CAPL 脚本(如监控、记录、仿真响应、测试验证等)的基础。
  • 后续将继续学习与信号相关的事件 (on signal, on signal_update) 等。