3、数据链路层基础:CAN帧结构、J1939的帧格式、数据帧与远程帧的区别

各位工程师朋友,大家好。今天我们聊聊数据链路层。这一层,说白了就是CAN总线通信的“交通规则”。你想想看,物理层只负责把0和1变成电信号,但怎么让这些信号有序地在总线上跑,不打架、不丢包,这就是数据链路层的活儿了。

我个人习惯把这一层比作“信封”。物理层是纸和墨水,数据链路层就是信封上的地址、邮编、邮票。没有这个信封,你的信息根本不知道往哪儿送。

3.1 CAN标准帧结构:11位标识符的江湖

我们先从最基础的CAN标准帧说起。J1939协议用的是CAN 2.0B扩展帧,但理解标准帧是基本功。

一个标准CAN数据帧,由这么几部分组成:

  • SOF(帧起始):1个显性位,告诉总线“我要开始说话了”。
  • 仲裁场:11位标识符 + RTR位 + IDE位 + r0位。这里决定了谁有优先权。
  • 控制场:4位DLC(数据长度代码),告诉别人我这包里装了几个字节。
  • 数据场:0~8字节,真正的数据内容。
  • CRC场:15位CRC校验 + 1位CRC界定符。用来检查数据有没有被干扰。
  • ACK场:2位。接收节点如果正确收到,会拉低ACK位回应。
  • EOF(帧结束):7个隐性位,表示帧结束。

嗯,这里要注意:仲裁场是CAN总线最精妙的设计。为什么?因为多个节点同时发送时,标识符数值越小,优先级越高。我当年调试一个混合动力系统,就遇到过两个ECU同时抢总线,结果一个0x0CF00400的报文总是把0x18FF1234的报文给“挤掉”。后来一查,原来是标识符设计时没考虑优先级冲突。

核心要点:CAN总线仲裁是非破坏性的。发送节点在发送标识符时,会同时监听总线电平。如果自己发送的是隐性位(1),但总线上是显性位(0),说明有更高优先级的节点在发数据,自己就乖乖退出发送。整个过程不会破坏任何数据。

3.2 J1939的帧格式:29位标识符的秘密

J1939用的是CAN 2.0B扩展帧。扩展帧和标准帧最大的区别,就是标识符从11位变成了29位。为什么需要这么多位?因为商用车网络太复杂了——发动机、变速箱、ABS、仪表盘、车身控制器……每个节点都要有自己独特的“身份证号”。

J1939把这29位标识符拆成了几个部分:

位域 位数 说明
优先级 3位 0最高,7最低。我一般建议关键报文用3或4,别用0,太霸道了。
数据页 1位 0或1,用来扩展参数组编号。
PDU格式 8位 决定是点对点通信还是广播通信。
PDU特定 8位 如果是广播,这里放组扩展;如果是点对点,这里放目标地址。
源地址 8位 发送节点的地址,0~253可用。

举个例子,发动机转速报文通常用PGN 0xF004(也就是61444)。它的29位标识符长这样:

0x0CF00400
二进制拆解:
优先级: 011(3)
数据页: 0
PDU格式:11110000(0xF0)
PDU特定:00000100(0x04)
源地址: 00000000(0x00,发动机控制器地址)

你看,从标识符一眼就能看出:这是优先级3的广播报文,PGN是0xF004,来自发动机控制器。这就是J1939的优雅之处——标识符本身就包含了路由信息

我的经验:刚开始学J1939时,我总记不住PGN和标识符的换算关系。后来我养成了一个习惯:用Excel做个换算表,把常用的PGN(比如0xF004发动机、0xF005车速、0xF006水温)都列出来,换算成29位标识符。调试时直接查表,效率高很多。

3.3 数据帧与远程帧的区别:别搞混了

这个问题,我面试过不少工程师,十个人里有八个答不全。数据帧和远程帧,虽然长得像,但用途完全不同。

数据帧:就是“我这里有数据,你收一下”。它带着8个字节的数据内容,是总线上的主力。

远程帧:就是“你那边有没有数据?发给我看看”。它不带数据场,但DLC字段会告诉对方期望的数据长度。

区别在哪?我列个表:

对比项 数据帧 远程帧
RTR位 显性(0) 隐性(1)
数据场 有,0~8字节
用途 主动发送数据 请求对方发送数据
仲裁优先级 高于远程帧(RTR显性优先) 低于数据帧

为什么会这样?因为RTR位在仲裁场里。数据帧的RTR是显性(0),远程帧是隐性(1)。所以如果数据帧和远程帧同时发送,数据帧会赢。这设计很合理——你想想看,人家都带着数据来了,你一个“请求”当然得让路。

我曾经踩过的坑:有一次做ABS系统测试,发现某个节点总是收不到车速信号。查了半天,发现是那个节点发的是远程帧请求车速,但车速传感器节点只响应特定ID的数据帧,不响应远程帧。后来我改成了定时发送数据帧,问题就解决了。所以,在J1939协议里,远程帧用得很少。大多数情况下,节点都是主动周期性发送数据帧。远程帧更多用于诊断或配置场景。

3.4 实际项目中的帧设计建议

讲完理论,我分享几个实战经验:

  1. 优先级别乱用:0留给最紧急的报文(比如制动、安全气囊),1~3给关键控制报文(发动机、变速箱),4~6给状态信息(车速、水温),7给诊断和配置。我见过有人把所有报文都设成优先级0,结果总线仲裁时低优先级报文根本发不出去。
  2. DLC要精确:J1939报文的数据长度通常是8字节,但有些报文只需要2~3个字节。DLC一定要写对,否则接收方可能读到垃圾数据。
  3. 源地址别冲突:每个ECU的源地址必须是唯一的。我建议在项目初期就做一个地址分配表,贴在墙上。否则两个ECU用同一个地址,总线会乱套。
  4. 远程帧慎用:除非你有明确的请求-响应场景,否则尽量用周期性数据帧。远程帧会增加总线负载,而且响应时间不可控。

一句话总结:数据帧是“我说你听”,远程帧是“你问我答”。在J1939的世界里,大部分时间都是“我说你听”。

好了,这一章的内容就到这里。数据链路层是CAN通信的基石,理解帧结构、标识符格式、数据帧与远程帧的区别,是后续学习J1939协议栈、诊断协议、网络管理的基础。下一章我们会聊到网络层——也就是J1939的传输协议,看看超过8字节的数据怎么在CAN总线上传输。