三、CAN数据链路层(一):CAN帧的种类与数据帧结构详解
数据链路层,说白了就是给原始信号“定规矩”的那一层。CAN总线之所以能在汽车上稳定跑几十年,靠的就是这一层把通信规则定得死死的。我个人习惯把数据链路层理解为“交通警察”——它告诉你什么时候该走、什么时候该停、出了事故怎么处理。
这一章我们重点看两件事:CAN帧有哪几种,以及数据帧内部到底长什么样。
3.1 CAN帧的四种类型
CAN总线上跑的“消息包”一共就四种。不多,但各司其职。
| 帧类型 | 用途 | 谁发 | 有没有数据 |
|---|---|---|---|
| 数据帧 | 发送数据 | 发送节点 | 有(0~8字节) |
| 远程帧 | 请求数据 | 接收节点 | 无 |
| 错误帧 | 报告总线错误 | 检测到错误的节点 | 无 |
| 过载帧 | 请求延迟发送 | 接收节点(忙时) | 无 |
实际项目中,数据帧占了95%以上的总线流量。远程帧偶尔用在诊断场景。错误帧和过载帧嘛——我希望你永远别在示波器上看到它们。
3.2 数据帧结构详解
数据帧是CAN通信的“主力军”。它的结构从SOF到EOF,一共七个场。我建议你把这七个场背下来,因为后面讲位时序、采样点、仲裁机制,全都要用到它们。
标准数据帧(11位ID)结构总览:
SOF + 仲裁场(12位) + 控制场(6位) + 数据场(0~64位) + CRC场(16位) + ACK场(2位) + EOF(7位)
总长度:44~108位(不含位填充)
3.2.1 SOF(帧起始)
SOF只有1位,固定为显性(0)。它干两件事:
- 告诉所有节点:“我要发消息了,总线开始同步”
- 作为硬同步的触发边沿
你想想看,如果总线上所有节点都在等别人先开口,那就没人说话了。SOF就是那个“我先来”的信号。我见过一些初学者把SOF当成数据的一部分去解析,结果怎么都对不上——记住,SOF不是数据,它是同步信号。
3.2.2 仲裁场
仲裁场是CAN总线最精彩的设计之一。标准帧里它包含:
- 11位标识符(ID):决定消息优先级,数值越小优先级越高
- 1位RTR位:数据帧为显性(0),远程帧为隐性(1)
为什么说它精彩?因为CAN的仲裁是无损的——多个节点同时发送时,优先级低的节点自动退出发送,优先级高的节点不受任何影响。这比以太网的CSMA/CD优雅太多了。
避坑指南:
我在项目中遇到过一个问题:两个节点用同一个ID发数据,结果总线一直报错。查了半天才发现是ID分配冲突。记住,CAN总线上每个消息ID必须是唯一的——不是说节点ID唯一,而是消息ID唯一。同一个ID只能由一个节点发送。
3.2.3 控制场
控制场共6位,结构如下:
| 位 | 名称 | 说明 |
|---|---|---|
| 1 | IDE位 | 0=标准帧,1=扩展帧 |
| 1 | 保留位r0 | 显性(固定0) |
| 4 | DLC(数据长度码) | 0~8,表示数据场字节数 |
DLC虽然只有4位,但有效值只有0~8。你可能会问:4位不是能表示0~15吗?嗯,CAN协议规定DLC超过8时,数据场长度按8处理。这是历史原因——早期设计时觉得8字节够用了,后来也没改。
3.2.4 数据场
数据场就是真正要传的内容。0~8字节,长度由DLC决定。传输顺序是MSB优先(高位在前)。
举个例子,如果我要发送一个16位的车速值0x1234:
// 假设车速ID = 0x100,DLC = 2
// 数据场排列:
// Byte 0: 0x12 (高8位)
// Byte 1: 0x34 (低8位)
这里有个常见的坑:字节序问题。不同厂商对多字节数据的排列方式可能不同。有的用Motorola格式(高位在前),有的用Intel格式(低位在前)。我在做BCM项目时就吃过这个亏——仪表盘显示的车速一直是0,后来发现是ECU发的车速字节序和仪表解析的不一致。
3.2.5 CRC场
CRC场包含:
- 15位CRC序列:由SOF、仲裁场、控制场、数据场计算得出
- 1位CRC分隔符:隐性位(1)
CAN用的CRC多项式是:x¹⁵ + x¹⁴ + x¹⁰ + x⁸ + x⁷ + x⁴ + x³ + 1。这个多项式能检测出:
- 所有单比特错误
- 所有双比特错误
- 所有奇数个错误
- 所有长度≤15的突发错误
- 99.997%的其他错误
说实话,你不需要记住这个多项式。但你要知道:CRC是CAN总线可靠性的最后一道防线。如果CRC校验失败,接收节点会直接丢弃这帧数据。
3.2.6 ACK场
ACK场只有2位:
- ACK槽(1位):发送节点发隐性(1),接收节点如果正确接收则拉为显性(0)
- ACK分隔符(1位):固定隐性(1)
这个设计很有意思。发送节点在ACK槽期间释放总线(输出隐性),然后监听总线。如果有任何一个节点正确接收了这帧数据,就会把ACK槽拉成显性。发送节点看到显性,就知道“有人收到了”。
3.2.7 EOF(帧结束)
EOF共7位,全部为隐性(1)。它的作用是给总线一个“安静期”,让所有节点知道这帧结束了。EOF之后紧接着是3位的ITM(帧间空间),然后才能发下一帧。
为什么是7位?因为位填充机制最多连续出现6个相同位,7个连续的隐性位是唯一不可能在数据段出现的模式——这样接收节点就能准确判断帧结束。
3.3 数据帧完整结构图
下面这张图把标准数据帧的完整结构画出来了。我建议你对照着图,在脑子里过一遍整个收发流程。
3.4 扩展帧 vs 标准帧
CAN 2.0B协议定义了两种帧格式。区别主要在仲裁场:
| 特性 | 标准帧 | 扩展帧 |
|---|---|---|
| ID长度 | 11位 | 29位 |
| IDE位 | 0(显性) | 1(隐性) |
| 仲裁场长度 | 12位 | 32位 |
| 最大ID数量 | 2048 | 536,870,912 |
| 兼容性 | 所有控制器都支持 | 需控制器支持CAN 2.0B |
实际项目中,乘用车动力系统常用标准帧(11位ID够用了),商用车和诊断协议(如UDS on CAN)经常用扩展帧。我个人习惯:能用标准帧就别用扩展帧——帧短意味着总线负载率低,实时性更好。
3.5 一个完整的收发流程
把上面所有知识点串起来,一个数据帧的完整生命周期是这样的:
- 发送节点检测总线空闲(至少11个隐性位)
- 发送SOF(显性位),所有节点硬同步
- 逐位发送仲裁场,同时监听总线。如果发现总线电平与自己发送的不一致,说明仲裁失败,立即退出发送
- 仲裁胜出的节点继续发送控制场和数据场
- 发送节点计算CRC并发送CRC序列
- 发送节点释放总线,等待ACK。接收节点如果CRC校验通过,在ACK槽发送显性位
- 发送节点发送EOF(7个隐性位),帧结束
- 进入ITM(帧间空间,3个隐性位),准备下一帧
实战建议:
刚开始学CAN的时候,我建议你用CAN分析仪抓一段总线波形,然后对着波形图一个一个场去解析。我自己就是这么学的——把波形放大,从SOF数到EOF,每个位都搞清楚是什么。做过三五次之后,你对CAN帧结构的理解会比看书深刻得多。
数据帧是CAN通信的基石。搞懂了它,后面讲位时序、采样点、同步机制的时候,你就知道那些参数到底在调什么了。