第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。
2.3 会话层——你的「身份」决定了权限
这一层很多人会忽略,但它特别容易出问题。
会话层管两件事:会话管理和安全访问。
UDS定义了三种默认会话:
- 默认会话(0x01):上电后的状态,只能做基本诊断
- 扩展会话(0x03):可以读写数据、执行例程
- 编程会话(0x02):刷写固件专用
我曾经遇到过一个坑:
客户反馈说ECU无法写入配置。我查了半天,发现测试脚本只发了0x10 01(默认会话),然后直接发0x2E写数据。
ECU当然不理你——默认会话下写操作是被禁止的。
解决方案?先发0x10 03切换到扩展会话,再发0x2E。
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。
说白了,就是寻址方式没对上。
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 协议栈调试的「黄金法则」
说了这么多,总结一下我的调试思路:
- 从应用层开始:确认SID和子功能对不对
- 检查会话层:当前会话是否支持该服务
- 看网络层:多帧报文的分包和重组是否正常
- 查数据链路层:CAN ID、帧格式、寻址方式
- 最后看物理层:线束、电阻、电压
你想想看,如果物理层有问题,你花三天查应用层代码,那不是白费劲吗?
反过来,如果应用层SID写错了,你换十根CAN线也没用。
好了,这一章我们搞清楚了UDS协议栈的每一层在干什么。
下一章,我会带你深入应用层,把26个诊断服务一个一个拆开来看。
到时候你会发现——原来诊断调试,真的没那么玄乎。