第2章:UDS协议栈架构

聊UDS诊断,很多人一上来就扎进具体服务里。

什么0x22读数据、0x2E写数据,背得滚瓜烂熟。

但真到调试的时候,报文发出去没响应,就傻眼了。

为什么?

因为你没搞懂协议栈是怎么一层层工作的。

我刚开始做UDS开发时也犯过这毛病。后来被一个老工程师点醒:诊断调试,本质就是协议栈的逐层排查。你搞懂了每一层在干什么,问题就藏不住了。

2.1 协议栈长什么样?

UDS协议栈,说白了就是一套通信规则的分层模型。

它参考了OSI七层模型,但汽车诊断场景下,我们只关心五层:

层级 名称 核心职责 我常说的比喻
第7层 应用层 诊断服务定义(读/写/控制) 你要说什么
第4层 会话层 会话管理、安全访问 你用什么身份说
第3层 网络层 报文分包、重组、流控 话太长怎么分段说
第2层 数据链路层 CAN帧收发、错误检测 怎么把话包装好
第1层 物理层 电平信号、线束、收发器 用什么介质传

嗯,这里要注意:UDS没有严格定义传输层和表示层。那些东西被CAN协议栈和DoIP的TCP/IP给包办了。

2.2 应用层——你真正关心的部分

应用层是离我们最近的一层。

你写的诊断需求文档,里面那些「读取VIN码」「写入ECU配置」「执行例程」——全是在这一层定义的。

UDS标准(ISO 14229-1)规定了26个服务,每个服务有固定的SID(服务标识符)。

举个例子:

请求:02 10 03
解释:
02    —— 数据长度(2字节)
10    —— SID,诊断会话控制服务
03    —— 子功能,扩展会话模式

应用层只关心「我要做什么服务」,不关心报文怎么发出去。

我个人习惯,调试时先看应用层:

  • SID对不对?
  • 子功能支持不支持?
  • 数据格式有没有按规范填?

很多新手一上来就怀疑CAN总线坏了,其实只是把0x22写成了0x2E。

我的小技巧: 调试时先抓应用层日志。如果请求和响应的SID能对上,说明上层没问题。再往下查。

2.3 会话层——你的「身份」决定了权限

这一层很多人会忽略,但它特别容易出问题。

会话层管两件事:会话管理安全访问

UDS定义了三种默认会话:

  • 默认会话(0x01):上电后的状态,只能做基本诊断
  • 扩展会话(0x03):可以读写数据、执行例程
  • 编程会话(0x02):刷写固件专用

我曾经遇到过一个坑:

客户反馈说ECU无法写入配置。我查了半天,发现测试脚本只发了0x10 01(默认会话),然后直接发0x2E写数据。

ECU当然不理你——默认会话下写操作是被禁止的。

解决方案?先发0x10 03切换到扩展会话,再发0x2E。

注意: 会话有超时机制。如果一段时间(通常5秒)没有诊断请求,ECU会自动切回默认会话。我见过有人调试时去接杯水,回来发现会话丢了,还以为是ECU死机了。

2.4 网络层——报文太长怎么办?

CAN报文一帧最多传8字节数据。

但诊断请求经常超过8字节。比如读取DTC信息,或者刷写固件,数据量很大。

这时候就需要网络层来分包和重组。

ISO 15765-2(也就是常说的CAN-TP)定义了四种帧类型:

帧类型 PCI字节 用途
单帧(SF) 0x0X 数据≤7字节,一帧搞定
首帧(FF) 0x1X 数据>7字节,告诉对方总长度
连续帧(CF) 0x2X 后续数据,按顺序发
流控帧(FC) 0x3X 接收方告诉发送方:慢点/快点/等等

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

首帧:10 0A [数据前6字节]
连续帧1:21 [数据中间4字节]
连续帧2:22 [数据最后2字节]

嗯,这里有个细节:首帧的PCI字节高4位是0x1,低4位表示总长度。0x0A就是10字节。

我调试时经常用CANoe的Trace窗口看这些帧。如果看到连续帧序号乱了,或者流控帧没收到,那基本就是网络层的问题。

2.5 数据链路层——CAN帧的规矩

这一层由CAN控制器硬件和底层驱动负责。

它管的是:

  • CAN ID的仲裁
  • 帧格式(标准帧/扩展帧)
  • 数据场填充
  • CRC校验
  • 错误帧处理

诊断报文通常使用功能寻址物理寻址两种方式:

  • 物理寻址:一对一的,CAN ID固定,比如0x7E0(请求)和0x7E8(响应)
  • 功能寻址:一对多的,所有ECU都会响应,比如0x7DF

我记得有一次,客户说诊断仪发请求后ECU没反应。

我抓了CAN总线一看,诊断仪用的是0x7DF功能寻址,但ECU只响应物理寻址0x7E8。

说白了,就是寻址方式没对上。

调试要点: 先确认CAN ID对不对。物理寻址和功能寻址的ID不能搞混。这是数据链路层最常见的错误。

2.6 物理层——最容易被忽略的「最后一公里」

物理层就是线束、收发器、终端电阻这些东西。

它出问题的时候,症状往往很诡异:

  • 偶尔能通信,偶尔不能
  • 高速时丢帧,低速时正常
  • 只有某个ECU收不到报文

我遇到过最离谱的一次:

一辆车的OBD接口针脚弯了,导致CAN_H和CAN_L短路。诊断仪发请求,ECU能收到,但响应发不出去——因为总线一直处于显性状态。

查了三天,最后发现是物理层的问题。

所以我的建议是:

  • 先量一下CAN_H和CAN_L之间的电阻,正常是60Ω(两个120Ω终端电阻并联)
  • 再量一下对地电压,CAN_H约2.5V,CAN_L约2.5V
  • 如果电压不对,检查收发器供电和接地

2.7 协议栈调试的「黄金法则」

说了这么多,总结一下我的调试思路:

  1. 从应用层开始:确认SID和子功能对不对
  2. 检查会话层:当前会话是否支持该服务
  3. 看网络层:多帧报文的分包和重组是否正常
  4. 查数据链路层:CAN ID、帧格式、寻址方式
  5. 最后看物理层:线束、电阻、电压

你想想看,如果物理层有问题,你花三天查应用层代码,那不是白费劲吗?

反过来,如果应用层SID写错了,你换十根CAN线也没用。

我的习惯: 调试时在CANoe里同时打开Trace窗口和诊断窗口。Trace看底层报文,诊断窗口看应用层解析。两边一对照,问题在哪一层,一目了然。

好了,这一章我们搞清楚了UDS协议栈的每一层在干什么。

下一章,我会带你深入应用层,把26个诊断服务一个一个拆开来看。

到时候你会发现——原来诊断调试,真的没那么玄乎。