第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Ω,两端各一个,别省这个
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里,不乱才怪。
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 | 接收方等待连续帧的时间 |
实际开发中,我建议你这样做:
- 先查ECU的规格书,确认它支持的定时参数
- 测试工具(比如CANoe)里把这些参数配好
- 跑一轮压力测试,看看有没有超时
- 如果有,适当放宽参数,但别超过ISO标准的上限
2.5 避坑指南——我踩过的几个雷
做CAN诊断开发,有些坑是绕不开的。我分享几个亲身经历:
- 终端电阻忘焊:总线波形乱七八糟,通讯时好时坏。后来我每次画板子都加个跳线,方便调试。
- ID冲突:两个ECU用了同一个诊断ID,结果响应乱套。解决方案是统一ID分配表,谁改谁签字。
- 流控帧丢失:发送方发了首帧,接收方没回流控帧,导致超时。后来我在代码里加了重传机制,三次没收到就报错。
- 连续帧序列号错乱:接收方收到的序列号不连续,直接丢包。这个得检查发送方的代码,确保序列号递增。
好了,这一章的内容就到这儿。物理层和数据链路层是基础,但也是容易出问题的地方。下一章我们聊应用层——UDS服务怎么在CAN上跑起来。到时候你会看到,今天讲的这些N_PDU和定时参数,全都会用上。
记住一句话:底层搞扎实了,上层才能稳。