当前位置: 首页 > news >正文

05-CAPL 报文发送与接收

专栏:《CAPL 脚本编写实战指南》第 5 篇
作者:一线汽车电子测试工程师
适合人群:已掌握 CAPL 基础的测试人员、想学习报文操作的工程师


开篇:一次通信问题的排查经历

这是我工作第 2 年遇到的真实案例。

项目背景

  • 客户:某国内车企
  • 问题:VCU 偶发收不到 BCM 报文
  • 现象:路试中偶发通信丢失
  • 影响:车辆功能受限

排查过程

  1. 第 1 天:用 CANoe 抓日志,分析报文
  2. 第 2 天:写 CAPL 脚本,模拟 BCM 发送
  3. 第 3 天:发现是 BCM 节点偶发复位
  4. 第 4 天:修复 BCM 软件,问题解决

关键脚本
就是一个简单的报文发送和接收脚本。

我的体会

报文发送和接收是 CAPL 最核心的功能。
学会了,就能解决 80% 的通信问题。

这篇教程,我会把报文发送和接收的所有知识点都讲清楚。


一、报文发送基础

1.1 报文变量定义

variables { message CANMessage msg_Test; // 定义 CAN 报文变量 }

说明

  • message:CAPL 关键字,声明报文类型
  • CANMessage:CANoe 定义的报文类型
  • msg_Test:变量名,可以自定义

注意

  • 报文变量必须在 variables 块定义
  • 报文名要与 DBC 中的一致(如果使用 DBC)

1.2 配置报文

on start { // 配置报文 ID msg_Test.canID = 0x123; // 配置数据长度(DLC) msg_Test.dlc = 8; // 配置数据字节 msg_Test.byte(0) = 0x10; msg_Test.byte(1) = 0x20; msg_Test.byte(2) = 0x30; msg_Test.byte(3) = 0x40; msg_Test.byte(4) = 0x50; msg_Test.byte(5) = 0x60; msg_Test.byte(6) = 0x70; msg_Test.byte(7) = 0x80; }

说明

  • canID:CAN 报文 ID(11 位或 29 位)
  • dlc:数据长度(0-8)
  • byte(n):第 n 个数据字节(0-7)

注意

  • ID 范围:0x000-0x7FF(标准帧)或 0x00000000-0xFFFFFFFF(扩展帧)
  • DLC 必须是 0-8 的整数
  • 字节索引从 0 开始

1.3 发送报文

on start { // 发送报文 output(msg_Test); write("报文已发送"); }

说明

  • output():发送 CAN 报文
  • 报文会发送到 CAN 总线上
  • 其他节点可以接收

1.4 通过信号发送

如果加载了 DBC 文件,可以通过信号名设置值:

on start { // 通过信号名设置值(需要 DBC) msg_Test.VehicleSpeed = 100; // 100 km/h msg_Test.EngineSpeed = 2000; // 2000 rpm msg_Test.GearPosition = 3; // D 档 // 发送报文 output(msg_Test); write("报文已发送,车速=%d km/h", msg_Test.VehicleSpeed); }

好处

  • 不用关心分辨率和偏移量
  • 代码更可读
  • 自动处理物理值/原始值转换

二、周期发送

2.1 用 outputPeriod() 周期发送

on start { // 配置报文 msg_Test.canID = 0x123; msg_Test.dlc = 8; msg_Test.byte(0) = 0x10; // 设置周期发送(100ms) outputPeriod(msg_Test, 100); write("开始周期发送,周期=100ms"); } on end { // 停止周期发送 outputStop(msg_Test); write("停止周期发送"); }

说明

  • outputPeriod(msg, time):设置周期发送
  • time:周期时间(毫秒)
  • outputStop(msg):停止周期发送

注意

  • 周期发送会自动持续
  • 程序结束时要停止
  • 周期时间不能太短(最小 10ms)

2.2 用定时器周期发送

variables { message CANMessage msg_Test; msTimer sendTimer; long sendCount; } on start { msg_Test.canID = 0x123; msg_Test.dlc = 8; sendCount = 0; // 设置定时器(100ms) setTimer(sendTimer, 100); write("开始定时器周期发送"); } on timer sendTimer { sendCount++; msg_Test.byte(0) = sendCount; output(msg_Test); write("发送第%d个报文", sendCount); // 发送 10 次后停止 if (sendCount >= 10) { write("发送完成"); } else { setTimer(sendTimer, 100); // 重新设置定时器 } }

outputPeriod() vs 定时器

方式优点缺点适用场景
outputPeriod()简单不够灵活固定周期发送
定时器灵活代码稍多需要控制发送内容

2.3 修改周期发送内容

variables { message CANMessage msg_Test; msTimer sendTimer; long counter; } on start { msg_Test.canID = 0x123; msg_Test.dlc = 8; counter = 0; setTimer(sendTimer, 100); } on timer sendTimer { // 每次发送不同的数据 counter++; msg_Test.byte(0) = counter; msg_Test.byte(1) = counter * 2; msg_Test.byte(2) = counter * 3; output(msg_Test); if (counter >= 100) { counter = 0; // 重置计数器 } setTimer(sendTimer, 100); }

三、报文接收

3.1 接收特定报文

on message msg_Test { long data = this.byte(0); write("收到报文,数据=%d", data); }

说明

  • on message:报文接收事件
  • msg_Test:报文名称(要与 DBC 一致)
  • this:指向接收到的报文
  • this.byte(n):读取第 n 个数据字节

3.2 接收任意报文

on message * { write("收到报文:ID=0x%x, DLC=%d", this.canID, this.dlc); }

说明

  • *:接收所有报文
  • 常用于报文监控、日志记录

3.3 接收特定 ID 的报文

on message 0x123 { write("收到 0x123 报文"); long data = this.byte(0); write("数据=%d", data); }

说明

  • 直接用 ID 接收
  • 不需要 DBC 文件

3.4 读取信号值

如果加载了 DBC 文件,可以直接读取信号:

on message VCU_Status { long speed = $VehicleSpeed; // 读取车速信号 long rpm = $EngineSpeed; // 读取转速信号 long gear = $GearPosition; // 读取档位信号 write("车速=%d km/h, 转速=%d rpm, 档位=%d", speed, rpm, gear); }

说明

  • $SignalName:读取信号值
  • 自动处理分辨率和偏移量
  • 返回物理值

3.5 报文计数器

variables { long msgCount; } on message * { msgCount++; // 每 100 个报文输出一次统计 if (msgCount % 100 == 0) { write("已收到%d个报文", msgCount); } } on end { write("总计收到%d个报文", msgCount); }

四、超时检测

4.1 简单超时检测

variables { msTimer timeoutTimer; boolean msgReceived; } on start { msgReceived = false; setTimer(timeoutTimer, 1000); // 1 秒超时 write("开始超时检测"); } on message EngineSpeed { msgReceived = true; resetTimer(timeoutTimer); setTimer(timeoutTimer, 1000); // 重新设置超时 write("收到转速信号"); } on timer timeoutTimer { if (!msgReceived) { write("警告:EngineSpeed 信号超时!"); } msgReceived = false; setTimer(timeoutTimer, 1000); }

说明

  • 用定时器实现超时检测
  • 收到报文时重置定时器
  • 定时器触发时检查是否收到报文

4.2 多信号超时检测

variables { msTimer timeoutTimer; long lastTime_EngineSpeed; long lastTime_VehicleSpeed; long timeout_ms = 1000; // 1 秒超时 } on start { lastTime_EngineSpeed = timeNow(); lastTime_VehicleSpeed = timeNow(); setTimer(timeoutTimer, 100); // 100ms 检查一次 } on message EngineSpeed { lastTime_EngineSpeed = timeNow(); } on message VehicleSpeed { lastTime_VehicleSpeed = timeNow(); } on timer timeoutTimer { long currentTime = timeNow(); // 检查转速信号 if (currentTime - lastTime_EngineSpeed > timeout_ms) { write("警告:EngineSpeed 信号超时!"); } // 检查车速信号 if (currentTime - lastTime_VehicleSpeed > timeout_ms) { write("警告:VehicleSpeed 信号超时!"); } setTimer(timeoutTimer, 100); }

4.3 超时计数器

variables { msTimer timeoutTimer; long lastTime_EngineSpeed; long timeoutCount; long timeout_ms = 1000; } on start { lastTime_EngineSpeed = timeNow(); timeoutCount = 0; setTimer(timeoutTimer, 100); } on message EngineSpeed { lastTime_EngineSpeed = timeNow(); } on timer timeoutTimer { if (timeNow() - lastTime_EngineSpeed > timeout_ms) { timeoutCount++; write("EngineSpeed 超时次数:%d", timeoutCount); if (timeoutCount >= 5) { write("错误:连续 5 次超时,通信故障!"); } } else { timeoutCount = 0; // 收到信号,重置计数器 } setTimer(timeoutTimer, 100); }

五、报文过滤

5.1 按 ID 过滤

on message * { // 只处理特定 ID 的报文 if (this.canID == 0x123) { write("收到 VCU 报文"); } if (this.canID == 0x456) { write("收到 BCM 报文"); } }

5.2 按数据过滤

on message * { // 只处理第 1 个字节为 0x10 的报文 if (this.byte(0) == 0x10) { write("收到特定数据报文"); } }

5.3 按条件过滤

on message VCU_Status { long speed = $VehicleSpeed; // 只处理车速超过 100 km/h 的报文 if (speed > 100) { write("警告:车速超过 100 km/h"); } }

六、实战案例

6.1 报文日志记录

/* * 报文日志记录脚本 * 功能:将所有 CAN 报文记录到文件 */ variables { long logHandle; } on start { // 打开日志文件 logHandle = fileOpen("can_log.txt", FILE_WRITE); // 写入文件头 fileWrite(logHandle, "Time,CanID,DLC,Data0,Data1,Data2,Data3,Data4,Data5,Data6,Data7\n"); write("开始记录 CAN 报文"); } on message * { char buffer[200]; // 格式化报文数据 sprintf(buffer, "%d,0x%x,%d,%d,%d,%d,%d,%d,%d,%d,%d\n", timeNow(), this.canID, this.dlc, this.byte(0), this.byte(1), this.byte(2), this.byte(3), this.byte(4), this.byte(5), this.byte(6), this.byte(7)); // 写入文件 fileWrite(logHandle, buffer); } on end { fileClose(logHandle); write("报文记录完成"); }

6.2 报文统计

/* * 报文统计脚本 * 功能:统计每种 ID 的报文数量 */ variables { long msgCount[2048]; // 存储每种 ID 的计数 msTimer reportTimer; } on start { // 初始化计数 for (int i = 0; i < 2048; i++) { msgCount[i] = 0; } // 设置报告定时器(10 秒) setTimer(reportTimer, 10000); write("开始报文统计"); } on message * { int id = this.canID; if (id < 2048) { msgCount[id]++; } } on timer reportTimer { write("=== 报文统计报告 ==="); for (int i = 0; i < 2048; i++) { if (msgCount[i] > 0) { write("ID=0x%03x: %d 个", i, msgCount[i]); } } write("===================="); setTimer(reportTimer, 10000); } on end { write("统计结束"); }

6.3 报文仿真

/* * 报文仿真脚本 * 功能:模拟 VCU 节点发送报文 */ variables { message CANMessage msg_VCU_Status; msTimer sendTimer; long simSpeed; long simRpm; } on start { // 配置报文 msg_VCU_Status.canID = 0x123; msg_VCU_Status.dlc = 8; // 初始化仿真值 simSpeed = 0; simRpm = 800; // 怠速 // 设置发送定时器(100ms) setTimer(sendTimer, 100); write("开始 VCU 报文仿真"); } on timer sendTimer { // 模拟车速缓慢增加 if (simSpeed < 120) { simSpeed += 10; // 每次增加 10 km/h } // 模拟转速随车速变化 simRpm = 800 + simSpeed * 30; // 设置信号值 msg_VCU_Status.VehicleSpeed = simSpeed; msg_VCU_Status.EngineSpeed = simRpm; // 发送报文 output(msg_VCU_Status); write("仿真发送:车速=%d km/h, 转速=%d rpm", simSpeed, simRpm); setTimer(sendTimer, 100); } on end { outputStop(msg_VCU_Status); write("停止 VCU 报文仿真"); }

6.4 通信质量监测

/* * 通信质量监测脚本 * 功能:监测 CAN 通信质量 */ variables { long totalMsg; long errorFrame; long overloadFrame; msTimer reportTimer; } on start { totalMsg = 0; errorFrame = 0; overloadFrame = 0; setTimer(reportTimer, 60000); // 1 分钟报告一次 write("开始通信质量监测"); } on message * { totalMsg++; } on errorFrame { errorFrame++; write("检测到错误帧"); } on overloadFrame { overloadFrame++; write("检测到过载帧"); } on timer reportTimer { write("=== 通信质量报告 ==="); write("总报文数:%d", totalMsg); write("错误帧数:%d", errorFrame); write("过载帧数:%d", overloadFrame); if (totalMsg > 0) { float errorRate = (float)errorFrame * 100.0 / (float)totalMsg; write("错误率:%.4f%%", errorRate); } write("======================"); setTimer(reportTimer, 60000); } on end { write("监测结束"); }

七、练习题目

练习 1:周期发送

// 要求:每 50ms 发送一个报文 // 数据字节 0 从 0 递增到 255,然后循环

练习 2:报文过滤

// 要求:只记录 ID 为 0x123、0x456、0x789 的报文 // 其他 ID 的报文忽略

练习 3:超时检测

// 要求:检测 3 个信号的超时 // 超时时间分别为 500ms、1000ms、2000ms

练习 4:报文统计

// 要求:统计每种 ID 的报文数量 // 每 30 秒输出一次前 10 名的 ID

练习 5:ECU 仿真

// 要求:仿真一个 ECU 节点 // 发送 3 个报文,周期分别为 10ms、20ms、50ms

八、常见问题

Q1:报文发送失败?

:检查以下几点:

  1. CANoe 是否在仿真模式
  2. 是否加载了正确的 DBC 文件
  3. 报文 ID 和 DLC 是否正确配置
  4. CAN 通道是否正确

Q2:收不到报文?

:检查以下几点:

  1. 是否有节点发送报文
  2. 报文 ID 是否正确
  3. on message 事件名是否正确
  4. CAN 通道是否正确

Q3:周期发送停止不了?

// 在 on end 中停止 on end { outputStop(msg_Test); }

Q4:超时检测不准确?

  1. 检查定时器周期设置
  2. 检查超时阈值是否合理
  3. 确保收到报文时重置定时器

Q5:如何同时处理多个报文?

on message msg1 { // 处理 msg1 } on message msg2 { // 处理 msg2 } on message msg3 { // 处理 msg3 }

九、学习建议

9.1 练习顺序

第 1 天:报文发送基础 第 2 天:周期发送 第 3 天:报文接收 第 4 天:超时检测 第 5 天:报文过滤 第 6 天:实战案例 第 7 天:综合练习

9.2 实战建议

  1. 从简单开始— 先发送单个报文
  2. 逐步复杂— 再添加周期发送
  3. 结合项目— 在实际项目中应用
  4. 建立模板— 复用常用代码

9.3 避坑指南

说明避免方法
忘记初始化变量值不确定在 on start 中初始化
周期发送不停资源泄漏在 on end 中停止
定时器不触发忘记重新设置on timer 中重新 setTimer
信号名错误与 DBC 不一致仔细检查 DBC
ID 配置错误报文发不出去确认 ID 范围

写在最后

报文发送和接收是 CAPL 最核心的功能。

学好了这个

  • 80% 的测试脚本都能写
  • 80% 的通信问题都能解决
  • 可以开始做项目了

建议

这篇教程里的所有代码,都亲手敲一遍。
只有亲手敲过,才是你的知识。

下一篇预告:《CAPL 测试框架设计》

  • 测试框架架构
  • 完整框架代码
  • 使用示例
  • 扩展功能

如果本文对你有帮助,欢迎点赞、收藏、关注专栏,第一时间获取更新!

有任何问题,欢迎在评论区留言,我会逐一回复。

练习答案会在第 6 篇公布,先自己试试看!

http://www.jsqmd.com/news/558870/

相关文章:

  • Qwen1.5-1.8B-Chat-GPTQ-Int4效果展示:中文逻辑推理、多跳问答真实对话截图
  • JAVA 项目教程《苍穹外卖-8》,微信小程序项目,前后端分离,从开发到部署
  • RimSort:专业级RimWorld模组管理解决方案
  • 2026年比较好的地暖塑料管材设备/螺旋管塑料管材设备/挤出塑料管材设备采购指南厂家怎么选 - 行业平台推荐
  • 2026年比较好的少儿编程教具/少儿编程品牌/少儿编程招商可靠供应商推荐 - 行业平台推荐
  • 2026年HENF级板材品牌哪家好?行业品质之选推荐 - 品牌排行榜
  • 2026年知名的圆形电梯/半圆形电梯生产厂家推荐几家 - 行业平台推荐
  • MelonLoader技术解析:Unity游戏模组加载的全方位解决方案
  • 嘉立创EDA专业版安装避坑指南:从下载到第一个STM32原理图实战
  • linux recorder
  • 2026年比较好的奥华油墨/印刷油墨/聚氨酯油墨/里油墨销售厂家哪家好 - 行业平台推荐
  • 告别手动整理!MinerU一键提取学术论文核心观点,效率提升10倍
  • 2026年HENF级板材品牌有哪些?行业品质之选推荐 - 品牌排行榜
  • 2026年比较好的储能变电站/美式变电站工厂直供推荐 - 行业平台推荐
  • 『CesiumJS』初体验
  • 雪女-斗罗大陆-造相Z-Turbo效果展示:基于Transformer架构的动漫风格图像生成
  • 2026年热门的10盘热风旋转炉/32盘推车式热风旋转炉/推车式热风旋转炉/16盘推车式热风旋转炉实力工厂怎么选 - 行业平台推荐
  • Java String
  • 2026年靠谱的交流低压配电柜/河南交流低压配电柜/河南高低压配电柜/配电柜配电箱精选厂家 - 行业平台推荐
  • 2026 HENF级板材品牌如何选择?环保与性能双优指南 - 品牌排行榜
  • 告别原生组件坑!微信小程序里让Canvas乖乖跟着ScrollView滚动的3种实战方案
  • 工业质检新视野:通义千问3-VL-Reranker-8B在缺陷检测中的应用
  • 2026年比较好的广州石锅商用烤箱/面包商用烤箱/石锅商用烤箱/食品商用烤箱制造厂家 - 行业平台推荐
  • NeRF训练太慢?从Blender数据到位置编码,这5个关键细节决定了你的GPU燃烧效率
  • 2026年质量好的ALD技术/ALD设备/光伏ALD/ALD工艺开发供应商怎么选 - 行业平台推荐
  • 视频字幕提取效率提升10倍:本地AI驱动的硬字幕解决方案全指南
  • StructBERT零样本分类-中文-base高性能:ONNX Runtime加速推理延迟降低65%
  • python高校大学生家教平台的设计与开发
  • 前端开发者必看:5个提升AI提示词效果的实战技巧(附代码示例)
  • Fish Speech-1.5语音合成企业标准:WAV采样率/比特率/声道数配置指南