网易首页 > 网易号 > 正文 申请入驻

CAN 帧格式详解:数据是怎么打包传输的

0
分享至


控制器局域网络(CAN)总线是一种广泛应用于汽车电子、工业控制等领域的串行通信协议。其核心特点是多主竞争、非破坏性仲裁,这得益于精心设计的帧格式。本文将详细拆解 CAN 数据帧(标准帧 CAN 2.0A)的结构,解释数据如何被打包成比特流并在总线上传输,最后给出 C++ 代码示例,演示打包与解析过程。

一、CAN 的帧类型

CAN 总线共有 5 种帧类型:

  • 数据帧:发送节点向其他节点传输数据(最常用)。

  • 远程帧:请求某个节点发送具有指定 ID 的数据帧。

  • 错误帧:节点检测到总线错误时主动发出的帧。

  • 过载帧:节点尚未准备好接收时,延迟后续数据帧。

  • 帧间隔:用于分隔上述帧。

本文聚焦数据帧,它完整展示了数据打包传输的全过程。

二、标准数据帧(CAN 2.0A)结构

标准数据帧由 7 个不同场(Field)组成,其结构如下图所示:

┌─────┬─────────────┬─────┬───────────┬────────────┬─────────┬─────┬─────────┐
│ SOF │ 仲裁场(12b) │控制场(6b)│ 数据场(0~8B) │ CRC场(16b) │ ACK场(2b) │ EOF(7b) │
│ 1b │ ID(11b)+RTR │IDE+r0+DLC│ │ CRC序列+界定符│ 槽+界定符 │ │
└─────┴─────────────┴─────┴───────────┴────────────┴─────────┴─────┴─────────┘

各字段的详细功能如下:

字段

位数

SOF

(帧起始)

1

显性位(逻辑0),用于同步所有节点的内部时钟,标志着帧的开始。

仲裁场

12

包含11 位标识符(ID)和RTR位。ID 决定帧优先级(数值越小优先级越高);RTR=0 表示数据帧,RTR=1 表示远程帧。

控制场

6

包含 IDE 位(标识符扩展,标准帧为显性0)、保留位 r0(显性0)以及4 位 DLC(数据长度码,指示数据场字节数,取值 0~8)。

数据场

0~64

实际要传输的数据,最多 8 字节。高位字节先发送(MSB first)。

CRC 场

16

15 位循环冗余校验码 + 1 位隐性界定符。发送方根据帧起始到数据场结束计算 CRC,接收方重新计算并比较,以检测传输错误。

ACK 场

2

包含ACK 槽(1 位)和ACK 界定符(1 位隐性)。发送方在 ACK 槽发送隐性位;接收方如果正确接收到帧,则在 ACK 槽发出显性位(覆盖隐性),表示应答。

EOF

(帧结束)

7

7 个连续隐性位,标志帧的结束。


注:实际 CAN 总线使用 NRZ(不归零)编码,并引入位填充(每 5 个相同电平后插入一个相反电平)以保证同步。CRC 计算和位填充通常由 CAN 控制器硬件完成,软件开发者只需关注 ID、DLC 和数据场。
三、扩展数据帧(CAN 2.0B)简介

CAN 2.0B 将标准帧的 11 位 ID 扩展为29 位(最多可标识 5.36 亿个节点),主要用于 CANopen、J1939 等需要大量 ID 的协议。其结构差异在于:

  • 仲裁场中IDE 位为隐性(1),随后增加18 位扩展 ID。

  • 控制场中的r0 位被替换为 RTR 位(扩展帧中称为 SRR,替代远程请求),整体结构更复杂。

扩展帧与标准帧可以共存于同一总线,通过 IDE 位区分。

四、数据打包与传输流程

  1. 应用层准备数据:决定目标 ID、DLC(0~8)和最多 8 字节数据。

  2. 构建帧字段:依次填充 SOF、ID、RTR、控制位、DLC、数据。

  3. 计算 CRC:对 SOF 到数据场末尾的位序列进行多项式除法,得到 15 位 CRC 序列。

  4. 位填充:在 SOF 到 CRC 序列(不含界定符)之间,每连续 5 个相同电平插入一个相反电平。

  5. 生成帧:组装所有字段(包括填充位),依次发送到总线。

  6. 接收与校验:接收节点去除填充位,重新计算 CRC 并与接收到的 CRC 比较;若一致且无错误,则在 ACK 槽位发送显性位应答。

  7. 结束:发送节点检测到 ACK 显性位后,发送 EOF,完成一次通信。

五、C++ 代码示例:标准帧的打包与解析

以下 C++ 代码演示了如何将高层 CAN 帧结构(ID、DLC、数据)打包成符合标准帧格式的比特流(字节数组),并从中解析出原始数据。为简化教学,忽略 CRC 计算和位填充(实际由硬件或专用库完成),重点展示字段的排列与位操作。

#include  

#include
#include
#include

// 高层 CAN 标准帧表示(应用层直接使用的结构)
struct CAN_StandardFrame {
uint32_t id; // 11 位标识符 (0x000 ~ 0x7FF)
uint8_t dlc; // 数据长度码 (0 ~ 8)
uint8_t data[8]; // 数据场,最多 8 字节
};

/**
* 将标准帧打包成字节数组(模拟总线上的比特流,不含位填充和有效 CRC)
* @param frame 输入的高层帧结构
* @return 打包后的字节数组,按 CAN 总线顺序排列(每字节内 MSB 先发送)
*/
std::vector pack_standard_frame(const CAN_StandardFrame& frame) {
// 标准帧最大长度(不含填充):SOF(1)+ID(11)+RTR(1)+IDE(1)+r0(1)+DLC(4)+数据(0~64)+CRC(15)+CRC界定符(1)+ACK槽(1)+ACK界定符(1)+EOF(7) = 44~108位 ≈ 6~14字节
// 我们固定分配 14 字节,未使用的位填 0(隐性)。
std::vector raw(14, 0);
// 辅助函数:在字节数组的指定位位置写入一个比特(MSB first)
auto set_bit = [&raw](int bit_pos, bool value) {
int byte_idx = bit_pos / 8;
int bit_offset = 7 - (bit_pos % 8); // 高位在前
if (value)
raw[byte_idx] |= (1 << bit_offset);
else
raw[byte_idx] &= ~(1 << bit_offset);
};
int bit_pos = 0;
// 1. SOF:显性位(逻辑 0)
set_bit(bit_pos++, false);
// 2. 仲裁场:11 位 ID(高位先发)
for (int i = 10; i >= 0; --i) {
set_bit(bit_pos++, (frame.id >> i) & 0x01);
}
// RTR:数据帧为 0(显性)
set_bit(bit_pos++, false);
// 3. 控制场:IDE(0) + r0(0) + DLC(4位)
set_bit(bit_pos++, false); // IDE = 0(标准帧)
set_bit(bit_pos++, false); // r0 = 0(保留)
for (int i = 3; i >= 0; --i) {
set_bit(bit_pos++, (frame.dlc >> i) & 0x01);
}
// 4. 数据场:按字节发送,每个字节高位先发
for (int byte = 0; byte < frame.dlc; ++byte) {
for (int i = 7; i >= 0; --i) {
set_bit(bit_pos++, (frame.data[byte] >> i) & 0x01);
}
}
// 5. CRC 场(15 位 CRC + 1 位界定符):这里仅占位,实际应由硬件计算
for (int i = 0; i < 15; ++i) set_bit(bit_pos++, false); // 全 0(隐性,实际取决于 CRC 结果)
set_bit(bit_pos++, true); // CRC 界定符(隐性)
// 6. ACK 场:发送方在 ACK 槽发送隐性,界定符隐性
set_bit(bit_pos++, true); // ACK 槽(发送隐性,等待接收方置显性)
set_bit(bit_pos++, true); // ACK 界定符
// 7. EOF:7 个隐性位
for (int i = 0; i < 7; ++i) set_bit(bit_pos++, true);
return raw;
}

/**
* 从字节数组中解析出标准帧(忽略 CRC/ACK 校验)
* @param raw 输入的比特流字节数组(与 pack_standard_frame 格式一致)
* @return 解析出的高层帧结构
*/
CAN_StandardFrame parse_standard_frame(const std::vector& raw) {
CAN_StandardFrame frame = {0};
auto get_bit = [&raw](int bit_pos) -> bool {
int byte_idx = bit_pos / 8;
int bit_offset = 7 - (bit_pos % 8);
return (raw[byte_idx] >> bit_offset) & 0x01;
};
int bit_pos = 0;
// SOF 检查(应为 0)
bool sof = get_bit(bit_pos++);
if (sof != false) {
std::cerr << "警告:无效 SOF(应显性 0)" << std::endl;
}
// 读取 ID(11 位)
uint32_t id = 0;
for (int i = 10; i >= 0; --i) {
id |= (static_cast(get_bit(bit_pos++)) << i);
}
frame.id = id;
// RTR 位(数据帧应 0)
bool rtr = get_bit(bit_pos++);
if (rtr != false) {
std::cerr << "警告:RTR 位不为 0,非数据帧" << std::endl;
}
// IDE(标准帧应为 0)
bool ide = get_bit(bit_pos++);
bool r0 = get_bit(bit_pos++);
// DLC
uint8_t dlc = 0;
for (int i = 3; i >= 0; --i) {
dlc |= (static_cast(get_bit(bit_pos++)) << i);
}
frame.dlc = dlc;
// 数据场
for (int byte = 0; byte < frame.dlc; ++byte) {
uint8_t data_byte = 0;
for (int i = 7; i >= 0; --i) {
data_byte |= (static_cast(get_bit(bit_pos++)) << i);
}
frame.data[byte] = data_byte;
}
// 后续 CRC、ACK、EOF 可忽略(本示例不校验)
return frame;
}

// 辅助函数:打印字节数组的二进制形式(便于观察字段布局)
void print_binary(const std::vector& data) {
for (size_t i = 0; i < data.size(); ++i) {
std::cout << std::bitset<8>(data[i]) << " ";
if ((i + 1) % 4 == 0) std::cout << std::endl;
}
std::cout << std::endl;
}

int main() {
// 示例:发送一个 ID=0x123,DLC=4,数据为 {0xDE, 0xAD, 0xBE, 0xEF} 的标准数据帧
CAN_StandardFrame tx_frame;
tx_frame.id = 0x123; // 二进制 0001 0010 0011
tx_frame.dlc = 4;
tx_frame.data[0] = 0xDE;
tx_frame.data[1] = 0xAD;
tx_frame.data[2] = 0xBE;
tx_frame.data[3] = 0xEF;
// 打包
std::vector bus_stream = pack_standard_frame(tx_frame);
std::cout << "=== 打包后的比特流(14字节,不含填充和有效CRC)===" << std::endl;
print_binary(bus_stream);
// 解析
CAN_StandardFrame rx_frame = parse_standard_frame(bus_stream);
std::cout << "=== 解析结果 ===" << std::endl;
std::cout << "ID: 0x" << std::hex << rx_frame.id << std::dec << std::endl;
std::cout << "DLC: " << static_cast(rx_frame.dlc) << std::endl;
std::cout << "数据: ";
for (int i = 0; i < rx_frame.dlc; ++i) {
std::cout << std::hex << std::setw(2) << std::setfill('0')
<< static_cast(rx_frame.data[i]) << " ";
}
std::cout << std::dec << std::endl;
return 0;
}
代码说明
  • **pack_standard_frame**:按照 CAN 标准帧的位顺序,将 ID、DLC、数据等字段依次写入一个字节数组。每个字节内采用MSB first顺序(与 CAN 总线一致)。CRC 场和 ACK 场使用占位值,实际项目中应调用硬件或 CRC 库计算。

  • **parse_standard_frame**:逆向读取位流,还原出 ID、DLC 和数据。同时进行简单的合法性检查(SOF、RTR 等)。

  • main函数:演示一个完整的打包-解析流程,并打印比特流和解析结果。

运行该程序,你将看到类似下面的输出(比特流因固定占位而包含大量 0,但 ID、DLC 和数据位置正确):

=== 打包后的比特流(14字节,不含填充和有效CRC)===
00000000 00000000 10010001 00110000 ...
...
=== 解析结果 ===
ID: 0x123
DLC: 4
数据: de ad be ef
六、扩展帧的代码差异

若需支持扩展帧(29 位 ID),只需修改打包/解析函数中的位宽和字段顺序:

  • 仲裁场:11 位基本 ID + SRR(1) + IDE(1) + 18 位扩展 ID。

  • 控制场:RTR(1) + r1(1) + r0(1) + DLC(4)。

  • 总长度增加,CRC 计算范围也相应变化。

通常在实际工程中,我们会使用成熟的 CAN 库(如 Linux SocketCAN 的struct can_frame和struct canfd_frame),它们已经封装好了标准帧和扩展帧的打包逻辑。

七、总结

CAN 数据帧通过精心划分的字段,实现了高可靠性的数据打包传输:

  • SOF同步所有节点;

  • ID 仲裁决定总线竞争胜者;

  • DLC灵活指示数据长度;

  • CRC保证数据完整性;

  • ACK实现确认机制。

虽然 CRC 计算和位填充细节较为复杂,但 CAN 控制器硬件已帮开发者屏蔽了底层细节。理解帧格式能帮助我们更好地调试 CAN 通信问题,以及编写正确的应用层协议。

希望本文的讲解和代码示例能帮助你掌握 CAN 数据打包传输的核心原理。在实际开发中,建议直接使用硬件厂商提供的 CAN 驱动或操作系统级的 SocketCAN 接口,以简化开发流程。

特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。

Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

相关推荐
热点推荐
事态已经全面升级!赖清德有可能成为新中国历史上,唯一一位在任台湾地区领导人中出现重大变故的人物

事态已经全面升级!赖清德有可能成为新中国历史上,唯一一位在任台湾地区领导人中出现重大变故的人物

人生录
2026-09-11 00:05:21
严子怡创雨战世界历史第一好成绩,日本电视台:不知道怎么解说了

严子怡创雨战世界历史第一好成绩,日本电视台:不知道怎么解说了

杨华评论
2026-09-28 04:29:40
确认了!她系台湾间谍情报机关人员

确认了!她系台湾间谍情报机关人员

焦作日报
2026-09-27 15:33:27
53岁知名科普作家吴京平告别仪式在上海举办,挚友汪诘:未来或用技术让他继续陪伴听众

53岁知名科普作家吴京平告别仪式在上海举办,挚友汪诘:未来或用技术让他继续陪伴听众

极目新闻
2026-09-27 17:40:22
惊天警报!久明派急速夺权,克里姆林宫权力更迭已进入最后倒计时

惊天警报!久明派急速夺权,克里姆林宫权力更迭已进入最后倒计时

军情观察家
2026-09-27 22:34:40
深夜,突然跳水!特朗普,最新发声

深夜,突然跳水!特朗普,最新发声

中国基金报
2026-09-28 00:11:40
“第一次感觉离死亡这么近!”男子称喝了几口隔夜鱼汤差点窒息,孩子哭着喊“爸爸不要死”……医疗专家解析

“第一次感觉离死亡这么近!”男子称喝了几口隔夜鱼汤差点窒息,孩子哭着喊“爸爸不要死”……医疗专家解析

都市快报橙柿互动
2026-09-28 00:14:52
青年男女疑高铁上不雅举动引热议 女方还有腹肌 网友:肯定练过

青年男女疑高铁上不雅举动引热议 女方还有腹肌 网友:肯定练过

环球趣闻分享
2026-09-27 13:36:44
妻子曝刘欢真正死因!独女是诱因,错过5年抢救期,一度恶化致死

妻子曝刘欢真正死因!独女是诱因,错过5年抢救期,一度恶化致死

风月得自难寻
2026-09-27 18:01:52
这个中秋,三四线相亲市场崩了?

这个中秋,三四线相亲市场崩了?

魔都囡
2026-09-27 11:01:54
孙颖莎:我可能等不到下届亚运会了 王曼昱:莎莎有很多地方值得我学习

孙颖莎:我可能等不到下届亚运会了 王曼昱:莎莎有很多地方值得我学习

中国青年报
2026-09-27 21:38:09
“45秒,全没了!”柏林宣布退出,奥运会从顶级IP沦为财政噩梦

“45秒,全没了!”柏林宣布退出,奥运会从顶级IP沦为财政噩梦

史智文道
2026-09-27 19:19:01
冷门炸裂!0-4,两大名将黯然离场,王楚钦张本智和成最大遗憾!

冷门炸裂!0-4,两大名将黯然离场,王楚钦张本智和成最大遗憾!

军科零零
2026-09-27 11:06:05
耿同学又出手!揭《Nature》一论文造假,24个原始值完全一致,通讯作者已承认并撤稿

耿同学又出手!揭《Nature》一论文造假,24个原始值完全一致,通讯作者已承认并撤稿

材料科学与工程
2026-09-24 11:48:39
震惊!网传杭州商K整治引发连锁反应:大批高奢包急售,二奢市场震动,美女扎堆申硕或相亲

震惊!网传杭州商K整治引发连锁反应:大批高奢包急售,二奢市场震动,美女扎堆申硕或相亲

火山詩话
2026-09-27 17:34:05
河南一技校101名毕业生入职北大,所涉专业系全国唯一,该校曾入职北大的毕业生:有和北大人才中心签约的,也有劳务派遣

河南一技校101名毕业生入职北大,所涉专业系全国唯一,该校曾入职北大的毕业生:有和北大人才中心签约的,也有劳务派遣

极目新闻
2026-09-27 19:00:11
预赛给自己打0分的吴艳妮,在名古屋亚运会收获一枚铜牌

预赛给自己打0分的吴艳妮,在名古屋亚运会收获一枚铜牌

封面新闻
2026-09-27 21:09:39
邓亚萍谈日本男单全军覆没:男团夺冠后兴奋过了头,能战胜国乒出乎他们的意料;张本智和对中国队做了充分的准备,单打他的心态不一样了

邓亚萍谈日本男单全军覆没:男团夺冠后兴奋过了头,能战胜国乒出乎他们的意料;张本智和对中国队做了充分的准备,单打他的心态不一样了

极目新闻
2026-09-27 09:04:26
李克勤演唱会迟到,救场歌手连唱近20首,本人表示:“3万人的演出这还是第一次,之前最多是五六千人”

李克勤演唱会迟到,救场歌手连唱近20首,本人表示:“3万人的演出这还是第一次,之前最多是五六千人”

韩小娱
2026-09-27 16:58:06
20名“双一流”大学生、研究生"翻墙"被处分 ,网传被举报:“一个姐们差一名保研,拿翻墙这事把前面所有人全举报了”

20名“双一流”大学生、研究生"翻墙"被处分 ,网传被举报:“一个姐们差一名保研,拿翻墙这事把前面所有人全举报了”

双一流高校
2026-09-28 00:09:38
2026-09-28 08:36:49
新能源自动驾驶 incentive-icons
新能源自动驾驶
专注于半导体行业资讯
1015文章数 348关注度
往期回顾 全部

头条要闻

13岁少年和老爸"手搓"涡喷发动机 建家庭实验室花20万

头条要闻

13岁少年和老爸"手搓"涡喷发动机 建家庭实验室花20万

体育要闻

114项指控成立,曼城已经完蛋了吗?

娱乐要闻

刘欢去世后谣言四起,韩磊被翻旧账

财经要闻

标价298元核桃油, 实为大豆油冒充

科技要闻

特斯拉人形机器人被曝周产量已达数百台

汽车要闻

MAZDA EZ-60激光版上市 焕新全系11.39万元起

态度原创

时尚
手机
家居
旅游
公开课

女人过了三四十岁,一定要准备一件小西装,大方得体又显瘦

手机要闻

消息称苹果Vision Pro市场遇冷:门店“月销1台就很幸运”

家居要闻

2026建博会(广州) 公装联探展交流活动

旅游要闻

用彩色像素打开临清东宛园,快来坐高铁打卡超美国风景区

公开课

李玫瑾:为什么性格比能力更重要?

无障碍浏览 进入关怀版