第2章 物理层与数据链路层:CAN总线基础、CAN帧结构、诊断报文格式(N_PDU)、网络层定时参数

好,咱们直接进入正题。上一章聊了UDS的整体框架,这一章得把地基打牢。你想想看,诊断指令在车上是怎么跑起来的?总得有个物理通道吧。对,就是CAN总线。

我个人习惯,讲CAN总线之前,先让大家明白一个道理:物理层决定了你能不能通,数据链路层决定了通得顺不顺。这两层搞不明白,后面写再多应用层代码也是白搭。

2.1 CAN总线物理层基础

CAN总线,说白了就是两根线——CAN_H和CAN_L。差分信号传输,抗干扰能力强。我最早接触CAN时,总觉得这玩意儿挺玄乎,后来自己拿示波器抓波形,才真正理解。

几个关键点你得记住:

  • 显性电平(Dominant):逻辑0,CAN_H比CAN_L高2V左右
  • 隐性电平(Recessive):逻辑1,CAN_H和CAN_L电压接近
  • 总线终端电阻:120Ω,两端各一个,别省这个
注意: 我曾经在项目里见过有人把终端电阻焊成了12Ω,结果总线反射得一塌糊涂,通讯时断时续。查了两天才找到原因。嗯,这种坑踩过一次就忘不了。

CAN总线的速率,常见的有125kbps、250kbps、500kbps。乘用车诊断一般用500kbps,商用车可能低一些。为什么?线束长度不一样,速率得匹配。

2.2 CAN帧结构——你得看懂报文

CAN帧有好几种:数据帧、远程帧、错误帧、过载帧。咱们做诊断,最关心的是数据帧。来,看一个标准数据帧的结构:

字段 长度 说明
SOF 1 bit 帧起始,显性电平
ID 11 bit (标准帧) / 29 bit (扩展帧) 标识符,决定优先级
RTR 1 bit 远程帧标志,数据帧为显性
DLC 4 bit 数据长度,0~8字节
Data 0~8 bytes 实际数据
CRC 15 bit 循环冗余校验
ACK 2 bit 应答位
EOF 7 bit 帧结束

这里我特别想强调一下ID。诊断报文一般用扩展帧(29位ID),为什么?因为标准帧的11位ID太容易冲突了。你想想看,一辆车上几十个ECU,都挤在11位ID里,不乱才怪。

小技巧: 实际开发中,诊断请求的ID通常是0x7DF(功能寻址)或具体的物理寻址ID。响应ID一般是请求ID + 0x8。比如请求ID是0x7E0,响应就是0x7E8。这个规律记好了,抓包时一眼就能看出来。

2.3 诊断报文格式——N_PDU

好,CAN帧我们看懂了。但诊断数据怎么塞进这8个字节里?这就引出了网络层的概念。ISO 15765-2(也就是常说的TP层)定义了N_PDU(Network Protocol Data Unit)。

N_PDU的结构是这样的:

+----------------+----------------+------------------+
|  N_AI (1字节)   |  N_PCI (1字节)  |  N_Data (0-7字节) |
+----------------+----------------+------------------+

其中N_PCI又分几种类型:

  • 单帧(SF):数据长度 ≤ 7字节,直接发
  • 首帧(FF):数据长度 > 7字节,告诉对方总长度
  • 连续帧(CF):后续数据,带序列号
  • 流控帧(FC):接收方告诉发送方“慢点发”或“继续发”

举个例子,你要发一个8字节的诊断请求:

// 单帧格式(假设数据长度=8,但单帧最多7字节,所以这里用双帧)
// 首帧:N_PCI = 0x10 | 数据长度(高4位)
// 实际项目中,8字节数据会拆成首帧+连续帧

// 首帧示例(假设总长度=8):
0x10 0x08 [数据1] [数据2] [数据3] [数据4] [数据5] [数据6]
// 连续帧(序列号=1):
0x21 [数据7] [数据8]

我刚开始做这个的时候,老是把N_PCI的格式搞混。后来自己画了个流程图,每次写代码前看一眼,再也没出过错。

2.4 网络层定时参数——别让ECU等太久

网络层定时参数,说白了就是超时和重传的规则。ISO 15765-2里定义了几个关键参数:

参数 默认值 说明
N_As 50 ms 发送方从请求发送到帧开始的时间
N_Ar 50 ms 接收方从帧结束到开始响应的时间
N_Bs 1000 ms 发送方等待流控帧的时间
N_Br 1000 ms 接收方发送流控帧的时间
N_Cs 1000 ms 连续帧之间的间隔
N_Cr 1000 ms 接收方等待连续帧的时间
重点: 这些参数不是随便设的。我曾经在一个项目中,把N_Bs设成了500ms,结果某个ECU处理慢了点,老是超时重传。后来查了规格书,发现那个ECU的流控帧响应时间就是800ms。嗯,参数得跟ECU匹配,不能想当然。

实际开发中,我建议你这样做:

  1. 先查ECU的规格书,确认它支持的定时参数
  2. 测试工具(比如CANoe)里把这些参数配好
  3. 跑一轮压力测试,看看有没有超时
  4. 如果有,适当放宽参数,但别超过ISO标准的上限

2.5 避坑指南——我踩过的几个雷

做CAN诊断开发,有些坑是绕不开的。我分享几个亲身经历:

  • 终端电阻忘焊:总线波形乱七八糟,通讯时好时坏。后来我每次画板子都加个跳线,方便调试。
  • ID冲突:两个ECU用了同一个诊断ID,结果响应乱套。解决方案是统一ID分配表,谁改谁签字。
  • 流控帧丢失:发送方发了首帧,接收方没回流控帧,导致超时。后来我在代码里加了重传机制,三次没收到就报错。
  • 连续帧序列号错乱:接收方收到的序列号不连续,直接丢包。这个得检查发送方的代码,确保序列号递增。

好了,这一章的内容就到这儿。物理层和数据链路层是基础,但也是容易出问题的地方。下一章我们聊应用层——UDS服务怎么在CAN上跑起来。到时候你会看到,今天讲的这些N_PDU和定时参数,全都会用上。

记住一句话:底层搞扎实了,上层才能稳