第三章:CAN2.0 协议帧结构精讲
导读: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 种帧,各自用途完全不同:
- 数据帧:最常用,节点向外发送有效业务数据。
- 远程帧 (RTR):请求别的节点返回对应 ID 的数据,不带数据段。
- 错误帧:任意节点检测通讯错误时主动发出,通知全网发生错误。
- 过载帧:接收节点来不及处理报文,通知发送方暂缓发送下一帧。
- 帧间隔:两帧报文之间的分隔段,不属于报文本体。
日常开发 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
逐段解析:
- SOF:显性 0,帧起始。
- 仲裁段:ID=0x100(二进制
0001 0000 0000),RTR=0 → 数据帧。 - 控制段:IDE=0(标准帧),DLC=4,代表 4 字节数据。
- 数据段:
0x01,0x02,0x03,0x04。 - CRC:硬件自动计算校验。
- ACK 槽:接收设备拉低应答。
- 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 本章工程易错点汇总
- DLC 最大为 8,超过 8 字节不能直接发送;需要自己做分包。CAN2.0 本身不支持大于 8 字节单帧传输。
- RTR 位写反:想发数据帧却设置远程帧,对方收不到数据。
- IDE 设置错误:发送扩展帧却配置 CAN_ID_STD,ID 错乱。
- ACK 错误:发送成功但是没有应答,优先排查硬件,不是协议问题。
- Bus‑Off:TEC/REC 计数器溢出,硬件故障居多,需要软件主动恢复总线。
第三章到此结束。
更多推荐


所有评论(0)