第二章:诊断协议栈——OSI七层模型与诊断、物理层与CAN总线、数据链路层与CAN帧、网络层与传输协议

各位工程师朋友,咱们接着聊。上一章我们把诊断流程的骨架搭起来了,这一章要深入到底层——协议栈。说白了,就是诊断数据在车上到底是怎么“跑”起来的。

很多刚入行的朋友,上来就写诊断代码,结果报文发出去没响应,或者响应乱码。为什么?因为底层协议没吃透。我当年也踩过这个坑,后来花了整整一周把CAN总线时序捋了一遍,才明白问题出在哪。

好,咱们一层一层剥开看。

2.1 OSI七层模型与诊断

OSI七层模型,学计算机的都知道。但诊断里用到的,其实就下面四层:

OSI层 诊断中的角色 常见协议/标准
应用层(第7层) 诊断服务本身(读故障码、刷写等) UDS (ISO 14229)
网络层(第3层) 数据分包、重组、流控制 ISO 15765-2 (DoCAN)
数据链路层(第2层) CAN帧的封装、仲裁、错误检测 CAN 2.0 (ISO 11898-1)
物理层(第1层) 电平信号、位时序、总线拓扑 ISO 11898-2 (高速CAN)

你想想看,诊断仪发一条“读取故障码”的请求,这条指令从应用层一路往下,每层都给它加点“料”,最后变成CAN总线上的电平信号。接收方再一层层拆包,还原出原始指令。这个过程,我习惯叫它“协议栈的舞蹈”。

核心要点:诊断开发人员至少要把下面四层搞明白。应用层决定“做什么”,网络层决定“怎么做”,数据链路层和物理层决定“能不能做”。

2.2 物理层与CAN总线

物理层,就是最底层的“硬家伙”。CAN总线用的是差分信号,两条线——CAN_H和CAN_L。为什么用差分?抗干扰能力强。车上的电磁环境有多恶劣,做过实车测试的都知道。

我记得有一次在台架上调试,报文总是时不时丢一帧。查了半天,发现是CAN收发器的共模电感虚焊了。嗯,这种问题示波器一看就知道,但新手往往先怀疑软件。

物理层几个关键参数:

  • 总线电平:显性(Dominant)对应逻辑0,隐性(Recessive)对应逻辑1。显性电平会覆盖隐性电平——这就是CAN总线仲裁的基础。
  • 位时序:一个位由同步段、传播段、相位缓冲段1、相位缓冲段2组成。采样点通常设在87.5%左右。我个人习惯把采样点设在90%,留点余量。
  • 终端电阻:120Ω,两端各一个。少了反射,多了信号幅值不够。

小技巧:用示波器量CAN_H和CAN_L的差分电压。显性时约2V,隐性时约0V。如果看到波形圆角或者幅值偏低,先检查终端电阻和共模电感。

2.3 数据链路层与CAN帧

数据链路层,负责把物理层的电平信号组织成“帧”。CAN帧有四种:数据帧、远程帧、错误帧、过载帧。诊断里最常用的是数据帧。

一个标准CAN数据帧长这样:

SOF | 11位ID | RTR | IDE | r0 | DLC | 数据段(0-8字节) | CRC | ACK | EOF

这里重点说几个:

  • ID(标识符):决定优先级。数值越小,优先级越高。诊断报文通常用功能寻址(0x7DF)和物理寻址(比如0x7E0、0x7E8)。
  • DLC(数据长度码):表示数据段有几个字节。诊断报文DLC通常是8,但实际有效数据可能只有3个字节。
  • 数据段:最多8字节。诊断数据超过8字节怎么办?交给网络层处理。

我曾经遇到过一个奇葩问题:某ECU发出来的诊断响应,DLC总是填0。按理说这是违规的,但那个ECU的工程师说“反正接收方看数据段就行”。结果我们的诊断仪死活不认——因为我们的协议栈严格按DLC来解析。后来两边一沟通,改了固件才解决。所以,协议栈的实现一定要严谨,别给自己挖坑。

避坑指南:我曾经因为CAN帧的ID过滤配置错误,导致诊断仪收到了大量非诊断报文,CPU负载飙到90%。后来在硬件过滤层加了白名单,只放行诊断相关的ID(0x7DF-0x7FF),问题解决。记住,过滤越早做越好

2.4 网络层与传输协议

网络层,这是诊断协议栈里最容易被忽视的一层。为什么?因为很多工程师觉得“CAN帧一次能发8字节,够用了”。但诊断服务的数据往往超过8字节——比如刷写一个Bootloader,数据包可能几百KB。

ISO 15765-2(也叫DoCAN)就是干这个的。它定义了两种传输方式:

  • 单帧(SF):数据≤7字节时,一帧搞定。第一个字节的高4位是0,低4位表示数据长度。
  • 多帧(FF+CF):数据>7字节时,先发一个首帧(FF),告诉对方“我要发N个字节”。然后发连续帧(CF),直到数据发完。

举个例子,发一个10字节的诊断请求:

首帧(FF):10 0A [数据前6字节]
连续帧1(CF):21 [数据第7-13字节]
连续帧2(CF):22 [数据第14-20字节]
...

注意首帧的第一个字节:高4位是1(表示首帧),低4位是0(保留)。后面两个字节表示总数据长度。连续帧的第一个字节:高4位是2(表示连续帧),低4位是帧序号(从1开始)。

这里有个坑:流控制(Flow Control)。接收方可能处理不过来,它会发一个FC帧告诉发送方“慢点发”或者“暂停”。我见过一个项目,发送方不管FC直接狂发连续帧,结果接收方缓冲区溢出,整个诊断会话崩溃。嗯,这种问题查起来特别费劲,因为错误日志里只显示“超时”。

关键点:网络层实现时,一定要处理好流控制机制。发送方要等收到FC帧后再发下一帧,接收方要合理设置块大小(Block Size)和间隔时间(STmin)。我个人习惯把STmin设为0x10(1ms),块大小设为8,这样既保证效率又不会丢帧。

好了,这一章的内容就到这。协议栈这东西,光看理论不够,一定要动手抓包分析。下一章我们讲应用层——UDS协议,到时候会结合实际的诊断服务来演示。有什么问题,欢迎在评论区交流。