4. CAN总线基础与CAPL编程入门

在网关测试工程中,CAN侧收发与路由验证脚本是最基础也最核心的一类CAPL程序。它的任务不是“发几条报文看看”,而是要在明确CAN帧结构、标识符语义、报文周期和事件触发机制的前提下,构造可复现的输入激励,观察网关输出,并据此判断路由是否正确。本节从CAN帧结构讲起,逐步落到一个可运行的CAPL脚本框架。

CAN帧结构与标识符:脚本要操作的真实对象

标准CAN数据帧由帧起始、仲裁段、控制段、数据段、CRC段、应答段和帧结束组成。对CAPL编程而言,真正需要直接操作的是仲裁段中的标识符、控制段中的DLC以及数据段中的8字节数据。标准帧使用11位标识符,扩展帧使用29位标识符,二者在CAPL中分别对应不同的报文对象声明方式。

标识符不只是“编号”,它同时携带优先级信息:在总线仲裁中,二进制数值越小的标识符优先级越高。例如0x100比0x200优先。网关路由验证时,如果输入报文与输出报文标识符不同,必须明确这是“ID转换路由”还是“透传路由”,否则后续判断会失去依据。

数据段长度由DLC给出,经典CAN中DLC为0~8,对应0~8字节。CAPL中访问数据字节常用 this.byte(0) 或 msg.byte(i),写入时用 msg.byte(i)=value。需要注意,DLC与实际写入字节数必须一致,否则接收端可能按DLC截断或补零,造成数据比对失败。

报文周期与事件处理:CAPL脚本的驱动方式

CAN报文按周期发送时,周期值决定了总线负载和接收端的采样节奏。常见周期有10ms、20ms、50ms、100ms、500ms。网关测试中,输入侧周期与输出侧周期可能相同,也可能被网关重新整形。例如输入报文周期20ms,网关输出周期仍为20ms,但相位可能偏移;若输出周期变为100ms,则说明网关做了周期转换或降频处理。

CAPL是事件驱动的。脚本不会从头到尾顺序执行,而是由事件触发对应处理函数。最常用的三类事件是:on start(测量开始)、on message(收到指定报文)、on timer(定时器到期)。on start用于初始化变量和启动定时器;on message用于接收并解析报文;on timer用于周期性发送激励或执行超时判断。

定时器分为秒级定时器和毫秒级定时器。毫秒级定时器用 msTimer 声明,适合10ms、20ms等短周期任务;秒级定时器用 timer 声明,适合超时监控。定时器到期后必须重新 setTimer 才能继续周期触发,否则只执行一次。这是初学者最容易忽略的一点。

网关CAN侧收发与路由验证脚本结构 输入侧(CAN1) ID: 0x100 DLC: 8 周期: 20ms ID: 0x200 DLC: 4 周期: 100ms ID: 0x300 DLC: 2 周期: 50ms CAPL: on timer 发送 setTimer 周期触发 网关 路由表匹配 ID转换 / 透传 周期整形 数据映射 CAPL: on message 接收并解析输入 输出侧(CAN2) ID: 0x180 DLC: 8 周期: 20ms ID: 0x280 DLC: 4 周期: 100ms ID: 0x380 DLC: 2 周期: 50ms CAPL: on message 校验 比对ID/DLC/数据/周期 验证要点:ID映射关系、DLC一致性、数据字节映射、输出周期与相位、超时与丢失检测

CAPL脚本实现:从声明到收发

下面给出一个可直接参考的CAPL脚本框架,用于验证0x100→0x180、0x200→0x280、0x300→0x380三条路由。脚本包含报文声明、定时器声明、发送逻辑和接收校验逻辑。

variables
{
  msTimer tSend100;
  msTimer tSend200;
  msTimer tSend300;
  message 0x100 msgIn100;
  message 0x200 msgIn200;
  message 0x300 msgIn300;
  int cycle100 = 20;
  int cycle200 = 100;
  int cycle300 = 50;
  int cnt100 = 0;
  int cnt200 = 0;
  int cnt300 = 0;
}

on start
{
  msgIn100.dlc = 8;
  msgIn200.dlc = 4;
  msgIn300.dlc = 2;
  setTimer(tSend100, cycle100);
  setTimer(tSend200, cycle200);
  setTimer(tSend300, cycle300);
  write("测量开始,输入侧周期发送已启动");
}

on timer tSend100
{
  cnt100++;
  msgIn100.byte(0) = cnt100 & 0xFF;
  msgIn100.byte(1) = (cnt100 >> 8) & 0xFF;
  msgIn100.byte(2) = 0x11;
  msgIn100.byte(3) = 0x22;
  msgIn100.byte(4) = 0x33;
  msgIn100.byte(5) = 0x44;
  msgIn100.byte(6) = 0x55;
  msgIn100.byte(7) = 0x66;
  output(msgIn100);
  setTimer(tSend100, cycle100);
}

on timer tSend200
{
  cnt200++;
  msgIn200.byte(0) = cnt200 & 0xFF;
  msgIn200.byte(1) = 0xAA;
  msgIn200.byte(2) = 0xBB;
  msgIn200.byte(3) = 0xCC;
  output(msgIn200);
  setTimer(tSend200, cycle200);
}

on timer tSend300
{
  cnt300++;
  msgIn300.byte(0) = cnt300 & 0xFF;
  msgIn300.byte(1) = 0x5A;
  output(msgIn300);
  setTimer(tSend300, cycle300);
}

on message 0x180
{
  if (this.dlc != 8)
  {
    write("路由错误:0x180 DLC=%d,期望8", this.dlc);
    return;
  }
  if (this.byte(2) != 0x11 || this.byte(3) != 0x22)
  {
    write("路由错误:0x180 数据映射不匹配");
    return;
  }
  write("0x100->0x180 路由正常,计数=%d", this.byte(0));
}

on message 0x280
{
  if (this.dlc != 4)
  {
    write("路由错误:0x280 DLC=%d,期望4", this.dlc);
    return;
  }
  if (this.byte(1) != 0xAA)
  {
    write("路由错误:0x280 数据映射不匹配");
    return;
  }
  write("0x200->0x280 路由正常");
}

on message 0x380
{
  if (this.dlc != 2)
  {
    write("路由错误:0x380 DLC=%d,期望2", this.dlc);
    return;
  }
  write("0x300->0x380 路由正常");
}

这段脚本的关键点有三处。第一,on timer中每次发送后必须重新setTimer,否则定时器只触发一次。第二,on message中通过this访问当前接收到的报文,this.dlc和this.byte(i)分别读取DLC和数据字节。第三,校验逻辑先判断DLC,再判断数据内容,最后输出正常信息,形成“先结构后内容”的判断顺序。

周期与超时判断:从“收到”到“按时收到”

仅判断报文是否收到还不够,网关测试必须判断报文是否按预期周期到达。可以在on message中记录时间戳,计算相邻两帧的时间差。CAPL提供timeNow()或getTime()获取当前时间,单位通常为10微秒或毫秒,具体取决于环境配置。以下片段用于检测0x180的周期是否稳定在20ms附近。

variables
{
  dword lastTime180 = 0;
  dword delta180 = 0;
}

on message 0x180
{
  dword now = timeNow();
  if (lastTime180 != 0)
  {
    delta180 = now - lastTime180;
    if (delta180 < 15 || delta180 > 25)
    {
      write("周期异常:0x180 间隔=%d,期望20ms附近", delta180);
    }
  }
  lastTime180 = now;
}

这里的时间单位需要根据实际工程配置确认。若timeNow()返回的是10微秒单位,则20ms对应2000;若返回毫秒,则对应20。脚本中应先通过一次已知周期报文实测确认单位,再设定阈值。阈值不宜过窄,通常取标称周期的±20%~±30%作为合理波动范围,避免因总线负载和调度抖动造成误报。

建议在on start中先发送一帧“预热”报文,再启动周期定时器,避免第一帧时间戳为0导致周期计算被跳过。同时,所有校验失败信息应包含报文ID、实际值和期望值,便于在Write窗口快速定位。

路由验证的完整判断链

一个完整的网关CAN侧路由验证,应覆盖以下判断链:输入报文是否按周期发出;网关是否在预期时间内输出对应报文;输出报文ID是否符合路由表;输出DLC是否与输入一致或符合映射规则;输出数据字节是否按映射关系正确转换;输出周期是否与输入周期一致或符合整形规则;是否存在丢帧、重复帧或超时。上述脚本框架已经覆盖了前五项,周期稳定性和丢帧统计可以在此基础上增加计数器与超时定时器实现。

例如,为0x180增加一个100ms超时定时器,每次收到0x180时重置该定时器;若定时器到期仍未收到,则判定为路由丢失。这种“接收重置+超时报警”的模式,是网关测试中最常用的在线监控手段。

CAPL中output()发送的报文会进入总线,若输入侧和输出侧挂在同一条总线上,可能造成回环接收。网关测试中应确保输入侧和输出侧分别连接到不同CAN通道,或在脚本中通过通道号区分收发,避免把自身发送的报文误判为网关输出。

掌握以上内容后,就可以在CANoe中建立测量工程,加载该CAPL节点,运行并观察Write窗口输出。若三条路由均输出“路由正常”,且无周期异常和DLC错误提示,则说明网关CAN侧收发与路由功能在本次测试条件下工作正常。后续可在此基础上扩展更多报文、更多路由规则以及故障注入场景。