3、UDS服务基础:服务标识符(SID)分类、请求/响应机制、物理寻址与功能寻址

好,我们进入第三章。这一章讲的是UDS协议里最基础、也最核心的几个概念。说白了,你搞懂了SID怎么分类、请求响应怎么玩、寻址方式怎么选,后面看任何诊断流程都会觉得「哦,原来就这么回事」。

我个人习惯把这一章叫做「UDS的ABC」。因为不管你以后做Bootloader、做DTC处理、还是做安全访问,都绕不开这些东西。我记得刚入行那会儿,被一个功能寻址的bug折腾了两天,最后发现是SID分类理解错了。嗯,咱们今天就把这个坑填上。

3.1 服务标识符(SID)分类

SID,全称Service Identifier,就是一个字节。范围从0x00到0x3E,再往上0x3F到0xFF是保留或者OEM自定义的。你想想看,一个字节就256种可能,UDS官方定义的就那么几十个,剩下的留给各家车厂自己玩。

UDS把服务分成了六大类,我列个表给你看,这样最直观:

类别 SID范围 典型服务 我常用的场景
诊断与通信管理 0x10 - 0x3E 0x10(诊断会话控制)、0x11(ECU复位)、0x28(通信控制) 刷写前切会话、复位ECU
数据传输 0x22 - 0x2E 0x22(读取数据)、0x2E(写入数据) 读VIN码、写配置参数
存储数据传输 0x34 - 0x3D 0x34(请求下载)、0x36(传输数据) 刷写固件时传数据块
输入输出控制 0x2F 0x2F(输入输出控制) 强制驱动某个执行器动作
远程激活 0x31 0x31(例程控制) 启动自检、擦除内存
上传/下载 0x34 - 0x37 0x35(请求上传) 从ECU读日志文件

这里有个细节要注意:SID的最高位(bit 7)决定了是请求还是响应。请求SID的最高位是0,响应SID的最高位是1。比如0x22是请求读数据,那它的正响应就是0x62(0x22 | 0x40)。

核心记忆法:请求SID + 0x40 = 正响应SID。这个规律贯穿整个UDS协议,你记牢了。

我在项目中遇到过一个问题:有个同事写诊断栈,正响应SID直接写死了0x62,但没注意请求SID可能是0x22的变种(比如某些OEM自定义的)。结果测试的时候,ECU收到请求后根本不认这个响应。排查了半天,发现是SID的bit 7没处理好。所以啊,千万别手动拼SID,用位运算最靠谱

3.2 请求/响应机制

UDS的通信模型很简单:Tester发请求,ECU给响应。但这里分两种情况:正响应和负响应。

正响应:请求SID + 0x40,后面跟着具体数据。比如你发0x22 0xF1 0x90(请求读VIN),ECU回0x62 0xF1 0x90后面跟着17个字节的VIN码。

负响应:固定用0x7F开头,后面跟着请求SID和错误码。比如你发0x22,ECU不支持这个服务,就回0x7F 0x22 0x11(0x11表示服务不支持)。

我画个简单的流程图给你看:

Tester发送: 0x22 0xF1 0x90
    |
    v
ECU处理:
    ├── 支持该服务且条件满足 → 正响应: 0x62 0xF1 0x90 [数据]
    └── 不支持或条件不满足 → 负响应: 0x7F 0x22 [NRC]

这里有个容易踩的坑:负响应的NRC(Negative Response Code)必须紧跟在请求SID后面。我曾经见过一个协议栈实现,把NRC和请求SID的顺序搞反了,结果Tester那边解析出来全是乱码。排查了整整一个下午,最后发现是代码里一个memcpy的偏移量算错了。

避坑指南:我曾经在项目里遇到过,某些ECU在特定会话下不支持某个服务,但负响应里返回的NRC是0x22(条件不正确),而不是0x11(服务不支持)。这两个NRC的区别很大:0x11说明ECU压根不认识这个服务,0x22说明ECU认识但当前条件不允许。诊断仪收到0x11就直接放弃了,收到0x22还会尝试切会话再试。所以写诊断栈的时候,NRC一定要选对。

3.3 物理寻址与功能寻址

这是UDS里一个非常有意思的设计。说白了,就是「一对一」和「一对多」的区别。

物理寻址:Tester发给某一个特定的ECU。CAN报文的目标地址就是那个ECU的物理地址。比如你只想读发动机ECU的VIN,就用物理寻址。

功能寻址:Tester发给总线上所有支持该功能的ECU。CAN报文的目标地址是一个功能地址(比如0x7DF),所有ECU都会收到并处理。比如你想让所有ECU同时进入扩展会话,就用功能寻址。

我列个对比表,你一看就明白:

特性 物理寻址 功能寻址
目标 单个ECU 多个ECU
CAN ID ECU的物理地址(如0x7E0) 功能地址(如0x7DF)
响应 目标ECU必须响应 ECU可以选择不响应(避免总线冲突)
典型场景 读数据、写配置、刷写 广播请求、同步操作

你想想看,为什么功能寻址的ECU可以选择不响应?因为如果总线上有10个ECU同时响应,CAN总线就冲突了。所以UDS规定:功能寻址的请求,ECU可以静默处理,不发送响应。但物理寻址的请求,ECU必须响应(除非有NRC)。

我的经验:在实际项目中,功能寻址最常用的场景是「诊断会话控制」(0x10)。比如你要刷写整个网络,先发一个功能寻址的0x10 0x03(请求扩展会话),所有ECU同时切到扩展会话。然后你再挨个用物理寻址去刷写。这样效率最高。

但这里有个坑:功能寻址的请求,ECU不能发送正响应,但可以发送负响应。比如你发功能寻址的0x28(通信控制),某个ECU不支持这个服务,它可以回0x7F 0x28 0x11。但如果有多个ECU都不支持,总线就会同时出现多个负响应,造成冲突。所以很多ECU实现里,功能寻址的负响应也是静默的——干脆不响应。

我记得有一次,客户反馈说某个ECU在功能寻址下不回任何响应,怀疑是协议栈bug。我查了半天,发现其实是客户自己把功能寻址的响应使能位给关了。嗯,有时候问题不在代码,在配置。

3.4 小结

这一章的内容,说白了就是三个核心点:

  • SID分类:记住六大类,记住请求和响应的关系(+0x40),记住NRC的格式
  • 请求/响应机制:正响应和负响应的区别,NRC的选择要谨慎
  • 寻址方式:物理寻址一对一,功能寻址一对多,功能寻址的响应要小心处理

下一章我们会讲具体的诊断服务实现,比如0x10(诊断会话控制)和0x22(读取数据)。到时候我会拿实际项目中的代码片段来演示,保证你看完就能上手写。