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

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.

相关推荐
热点推荐
张雪一句“鄙视你”,泰康秒赔8万:他不是突然良心发现,而是突然发现“不要脸的成本”太高了

张雪一句“鄙视你”,泰康秒赔8万:他不是突然良心发现,而是突然发现“不要脸的成本”太高了

迷世书童
2026-09-27 10:52:03
医院老护士含泪揭秘:人临终前的10个残酷真相,很多子女不懂

医院老护士含泪揭秘:人临终前的10个残酷真相,很多子女不懂

蝉吟槐蕊
2026-09-13 18:15:04
25岁女子吐槽河北婆婆做“糊弄饭”,老公不耐烦了:爱吃吃不吃滚

25岁女子吐槽河北婆婆做“糊弄饭”,老公不耐烦了:爱吃吃不吃滚

许三岁
2026-09-28 02:00:08
小布披露绝望输球内幕下周取代吴易昺,王左亚运血案含金量引热议

小布披露绝望输球内幕下周取代吴易昺,王左亚运血案含金量引热议

网球之家
2026-09-28 10:27:54
低价“0公里二手车”连环骗局爆雷,上千车主或面临钱车两空

低价“0公里二手车”连环骗局爆雷,上千车主或面临钱车两空

凤凰网财经
2026-09-27 22:01:48
4次扑救哈兰德+非常规手型!科斯塔9.4分,葡萄牙换代成功了

4次扑救哈兰德+非常规手型!科斯塔9.4分,葡萄牙换代成功了

赵或是个热血青年
2026-09-28 11:28:49
西方不服气,为何中国没发动任何战争,却成为世界一流经济强国

西方不服气,为何中国没发动任何战争,却成为世界一流经济强国

纪史行者
2026-09-28 08:33:10
刘欢临终细节被曝!含泪离世,老婆女儿都守在身旁,母女俩太痛苦

刘欢临终细节被曝!含泪离世,老婆女儿都守在身旁,母女俩太痛苦

观察鉴娱
2026-09-27 10:19:27
张杰谢娜一家五口出游,仨女儿都坐推车不带腿,有专属摄影师摄像

张杰谢娜一家五口出游,仨女儿都坐推车不带腿,有专属摄影师摄像

阿废冷眼观察所
2026-09-28 00:02:06
喜讯!U23国足进入亚运会四强之后,足协就可能跟多位强援完成续约

喜讯!U23国足进入亚运会四强之后,足协就可能跟多位强援完成续约

振刚说足球
2026-09-28 10:05:42
亚运会女单金牌得主诞生!王曼昱4-2险胜孙颖莎,摘得生涯第2冠

亚运会女单金牌得主诞生!王曼昱4-2险胜孙颖莎,摘得生涯第2冠

乒谈
2026-09-27 20:58:07
彻底崩盘!曼联 6000 万水货混日子!名宿怒喷卡里克赶紧清洗

彻底崩盘!曼联 6000 万水货混日子!名宿怒喷卡里克赶紧清洗

澜归序
2026-09-28 09:11:57
人无法共情以前自己买的那些东西。网友:三个垃圾桶1700。

人无法共情以前自己买的那些东西。网友:三个垃圾桶1700。

天气观察站
2026-09-27 16:03:26
气愤!刘翔师弟夺冠后怒斥:日本电视台没把我当回事 日本选手无视我

气愤!刘翔师弟夺冠后怒斥:日本电视台没把我当回事 日本选手无视我

念洲
2026-09-27 20:49:20
李谷一不再隐瞒,揭晓宋祖英走上高位的真相,观众:原来如此!

李谷一不再隐瞒,揭晓宋祖英走上高位的真相,观众:原来如此!

林轻吟
2026-07-02 19:19:19
中秋假期游客在秦皇岛乘坐海上索道被淋成落汤鸡,景区:下雨后暂停了一会索道,淋到雨的游客是在下雨前上去的

中秋假期游客在秦皇岛乘坐海上索道被淋成落汤鸡,景区:下雨后暂停了一会索道,淋到雨的游客是在下雨前上去的

台州交通广播
2026-09-27 19:31:25
一投提前下班!严子怡大幅提升赛会纪录,夺亚运会女子标枪金牌

一投提前下班!严子怡大幅提升赛会纪录,夺亚运会女子标枪金牌

全景体育V
2026-09-27 21:01:43
如果莉莉贝特真长这样!丝毫不输夏洛特公主,皮肤还更白嫩!

如果莉莉贝特真长这样!丝毫不输夏洛特公主,皮肤还更白嫩!

艺利森
2026-09-28 09:07:46
美方同意放行伊朗总统,专机起飞后怕生变故,全程未在第三国停靠

美方同意放行伊朗总统,专机起飞后怕生变故,全程未在第三国停靠

你是我心中最美星空
2026-09-27 20:36:09
案例:湖南女护士被执行死刑,行刑时回头质问武警,让他当场崩溃

案例:湖南女护士被执行死刑,行刑时回头质问武警,让他当场崩溃

星宇共鸣
2025-09-17 17:58:03
2026-09-28 12:08:49
新能源自动驾驶 incentive-icons
新能源自动驾驶
专注于半导体行业资讯
1015文章数 348关注度
往期回顾 全部

头条要闻

媒体:俄杜马选举普京获压倒性胜利 俄乌冲突有2大走向

头条要闻

媒体:俄杜马选举普京获压倒性胜利 俄乌冲突有2大走向

体育要闻

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

娱乐要闻

好汉刘欢:一副好嗓子,一颗赤子心

财经要闻

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

科技要闻

智能体惹了多少祸,OpenAI自己没查清!

汽车要闻

神龙科技与Momenta达成全球战略合作 2027年起推新车

态度原创

时尚
艺术
健康
房产
公开课

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

艺术要闻

致敬,刘欢硬笔书法欣赏

鼻子三角区的痘,千万别乱挤!

房产要闻

全年霸榜TOP1!南海・叁號院周年官宣:新作即将登场

公开课

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

无障碍浏览 进入关怀版