导读:CAN 总线所有交互都是依靠不同类型的帧完成,很多初学者只会调用库函数收发报文,看不懂原始帧每一位代表什么,遇到抓包报文时无法定位问题。本章完整讲解 CAN2.0A / CAN2.0B,五种帧类型,逐位拆解报文结构,配合真实报文样例,最后给出报文解析示例代码。

CAN2.0 分为两个版本:

  • CAN2.0A:标准帧,ID 长度 11 位,日常车载、工业设备最常用。
  • CAN2.0B:扩展帧,ID 长度 29 位,适合大量设备组网、ID 资源紧张的场景。

注意:控制器支持 CAN2.0B 的硬件,可以兼容接收 CAN2.0A 标准帧;但是只支持 2.0A 的控制器不能解析扩展帧。

3.1 CAN 的 5 种帧类型总览

CAN 协议一共定义 5 种帧,各自用途完全不同:

  1. 数据帧:最常用,节点向外发送有效业务数据。
  2. 远程帧 (RTR):请求别的节点返回对应 ID 的数据,不带数据段。
  3. 错误帧:任意节点检测通讯错误时主动发出,通知全网发生错误。
  4. 过载帧:接收节点来不及处理报文,通知发送方暂缓发送下一帧。
  5. 帧间隔:两帧报文之间的分隔段,不属于报文本体。

日常开发 95% 场景只使用数据帧远程帧;错误帧、过载帧由硬件控制器自动产生,软件一般不需要手动构造。

3.2 标准数据帧完整逐位拆解(CAN2.0A,11‑bit ID)

一帧标准数据帧,从左到右传输顺序分为 7 个段: 帧起始SOF → 仲裁段 → 控制段 → 数据段 → CRC段 → ACK应答段 → 帧结束EOF

3.2.1 帧起始 SOF(1 bit,显性 0)

1 位显性电平,标记一帧报文的开始。 总线处于空闲(隐性 1)时,发送节点拉低总线发出 SOF,通知所有节点:即将开始传输一帧报文。

3.2.2 仲裁段(12bit)

标准帧仲裁段组成:11 位 ID + RTR 位

  • 11 位 ID:报文标识符,决定报文优先级,ID 数值越小优先级越高
  • RTR 位:0 代表数据帧;1 代表远程帧。

仲裁就发生在这个段。多个节点同时发包,硬件对比每一位 ID,遇到显性 0 和隐性 1 冲突,发 1 的节点退出发送。

实例:标准 ID=0x123 二进制 ID:0001 0010 0011,一共 11bit。ID 越小,在总线竞争中抢占能力越强。

3.2.3 控制段(6 bit)

  • IDE 位:标识符扩展位。标准帧 IDE=0;扩展帧 IDE=1。用来区分当前是标准帧还是扩展帧。
  • r0:保留位,硬件置 0。
  • DLC:4 位数据长度码,取值范围0~8。代表后面数据段有多少字节。

重点:CAN2.0 数据段最大只能 8 字节,DLC 写 9‑15 属于非法,硬件不支持。

3.2.4 数据段(0‑8 字节)

业务有效载荷,DLC 规定多少字节就传输多少字节。 DLC 可以等于 0,也就是不带任何数据的数据帧,工程上可以用来做心跳包。

示例:DLC=3,数据为0x11 0x22 0x33,那么这一帧携带 3 个字节业务数据。

3.2.5 CRC 校验段(16bit)

包含 15 位 CRC 校验值 + 1 位 CRC 界定符(隐性 1)。 发送端根据 SOF 到数据段所有 bit 计算 CRC;接收端本地重新计算 CRC 对比。不一致则判定报文损坏,触发错误处理。

CRC 全部硬件运算,软件不需要手动计算。

3.2.6 ACK 应答段(2bit)

  • ACK 槽:1 位。发送节点输出隐性 1;如果有至少一个节点正确收到本帧,接收节点会把 ACK 槽拉为显性 0。
  • ACK 界定符:隐性 1。

非常重要故障点:如果发送完成 ACK 槽仍然保持隐性 1,代表没有任何节点接收到该报文,产生 ACK 错误。常见原因:对方设备没上电、CAN_H CAN_L 接反、波特率不一致。

3.2.7 EOF 帧结束(7 bit,全部隐性 1)

7 个连续隐性位,标志本帧传输结束。之后总线回到空闲状态,允许其它节点发送报文。

3.3 扩展数据帧简要讲解(CAN2.0B,29bit ID)

扩展帧仲裁段变长:基础 ID 11bit + SRR 位 + IDE 位 + 扩展 ID 18bit + RTR 位,总 ID 29 位。

  • IDE 位此时为 1,用来告诉控制器:这是扩展帧。
  • 扩展帧 ID 空间极大,适合大量传感器设备。

工程注意:抓包工具看到 StdId 和 ExtId,不要混淆;STM32 HAL 库中,IDE = CAN_ID_STD代表标准帧;IDE = CAN_ID_EXT代表扩展帧。

3.4 远程帧 RTR 详解

远程帧用来请求其他节点上报数据,没有数据段,DLC 可以为 0。

  • RTR 位置 1;数据段不存在。 举例:节点 A 发送远程帧 ID=0x200,RTR=1;总线上 ID=0x200 对应的节点 B 收到远程请求,回复一条 ID=0x200 的数据帧携带业务数据。

现实项目中远程帧使用较少,绝大多数项目直接使用定时主动上报数据帧。很多新设备厂商甚至不实现远程帧响应逻辑。

远程帧常见坑:写代码时误把 RTR 置 1,明明想发数据帧,结果发出远程帧,接收端收不到有效数据,调试很久找不到原因。

3.5 错误帧、过载帧

错误帧

当任意节点检测 CRC 错误、位错误、填充错误、格式错误,立刻发送错误帧。 错误帧由 6 个显性错误标志 + 错误界定符组成。全网节点收到错误帧,知道当前报文传输出错,发送方会硬件自动重发报文。

节点内部维护两个计数器:发送错误计数器 TEC、接收错误计数器 REC

  • TEC/REC 0‑99:正常工作;
  • 100‑127:错误警告;
  • 128‑255:错误主动;
  • 大于 255:进入 Bus‑Off 总线离线状态,断开总线,不再参与通讯,必须软件恢复。

BusOff 就是第二章代码检测的总线离线,是 CAN 调试高频故障。

过载帧

接收控制器内部缓冲区满,来不及接收新报文,发出过载帧,通知发送设备暂缓发送。硬件自动处理,软件不用干预。

3.6 真实报文实例拆解(标准帧)

抓包工具拿到报文:ID:0x100 STD,DLC=4,DATA:0x01 0x02 0x03 0x04

逐段解析:

  1. SOF:显性 0,帧起始。
  2. 仲裁段:ID=0x100(二进制0001 0000 0000),RTR=0 → 数据帧。
  3. 控制段:IDE=0(标准帧),DLC=4,代表 4 字节数据。
  4. 数据段:0x01,0x02,0x03,0x04
  5. CRC:硬件自动计算校验。
  6. ACK 槽:接收设备拉低应答。
  7. EOF:7 位隐性,帧结束。

业务含义举例:ID0x100 定义为电机状态报文,字节 0 电机状态、字节 1 转速高 8 位、字节 2 转速低 8 位、字节 3 故障码。

3.7 CAN 报文解析示例代码(HAL 库接收回调)

STM32 HAL 库 CAN 接收中断回调,把收到原始报文拆解打印,方便对接 CAN 抓包日志。

#include "can.h"
#include <stdio.h>

/**
 * @brief CAN接收回调函数,中断中触发
 */
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan)
{
    CAN_RxHeaderTypeDef rx_header;
    uint8_t rx_data[8] = {0};
    if(HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &rx_header, rx_data) != HAL_OK)
    {
        return;
    }

    // 判断标准帧/扩展帧
    if(rx_header.IDE == CAN_ID_STD)
    {
        printf("[CAN RX STD] ID:0x%03X  ", rx_header.StdId);
    }
    else if(rx_header.IDE == CAN_ID_EXT)
    {
        printf("[CAN RX EXT] ID:0x%08X  ", rx_header.ExtId);
    }

    // 判断远程帧还是数据帧
    if(rx_header.RTR == CAN_RTR_REMOTE)
    {
        printf("Remote Frame DLC:%d\r\n",rx_header.DLC);
    }
    else
    {
        printf("Data Frame DLC:%d Data:",rx_header.DLC);
        for(uint8_t i = 0; i < rx_header.DLC; i++)
        {
            printf("0x%02X ", rx_data[i]);
        }
        printf("\r\n");
    }
}

打印输出示例

[CAN RX STD] ID:0x100  Data Frame DLC:4 Data:0x01 0x02 0x03 0x04 

开发提示:中断回调函数中不要做复杂业务运算,只做报文拷贝,解析逻辑放到主循环处理,避免中断阻塞。

拓展:远程帧发送示例代码片段

uint8_t CAN_Send_RemoteFrame(uint16_t std_id)
{
    CAN_TxHeaderTypeDef tx_header;
    uint32_t tx_mailbox;
    tx_header.StdId = std_id;
    tx_header.IDE = CAN_ID_STD;
    tx_header.RTR = CAN_RTR_REMOTE;  // RTR置1,远程帧
    tx_header.DLC = 0;
    tx_header.TransmitGlobalTime = DISABLE;
    //远程帧无数据缓冲区
    if(HAL_CAN_AddTxMessage(&hcan, &tx_header, NULL, &tx_mailbox) == HAL_OK)
    {
        return 0;
    }
    return 1;
}

3.8 本章工程易错点汇总

  1. DLC 最大为 8,超过 8 字节不能直接发送;需要自己做分包。CAN2.0 本身不支持大于 8 字节单帧传输。
  2. RTR 位写反:想发数据帧却设置远程帧,对方收不到数据。
  3. IDE 设置错误:发送扩展帧却配置 CAN_ID_STD,ID 错乱。
  4. ACK 错误:发送成功但是没有应答,优先排查硬件,不是协议问题。
  5. Bus‑Off:TEC/REC 计数器溢出,硬件故障居多,需要软件主动恢复总线。

第三章到此结束。

 

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐