2、诊断请求与响应:请求报文格式、肯定响应与否定响应、NRC详解、物理寻址与功能寻址
好,咱们今天聊点实在的。诊断请求和响应,说白了就是ECU和诊断仪之间的对话规则。你发一句,它回一句,但怎么发、怎么回,这里头门道不少。我刚开始做UDS开发那会儿,就因为在报文格式上栽过跟头,后来才明白,这些基础东西不搞清楚,后面调试起来真要命。
2.1 请求报文格式:你说话得有个规矩
诊断请求报文,结构其实挺简单。它由三部分组成:
- 服务标识符(SID):1个字节,告诉ECU你想干啥。比如0x10是诊断会话控制,0x22是读取数据。
- 子功能(Sub-function):可选,1个字节。有些服务需要它,比如0x10服务后面跟着0x01就是默认会话,0x02是编程会话。
- 数据参数(Data):可变长度,具体看服务定义。
举个例子,一个读取VIN码的请求报文:
请求:22 F1 90
解释:
0x22 - 服务ID(读取数据)
0xF1 - 数据标识符高字节
0x90 - 数据标识符低字节(组合起来0xF190就是VIN码的DID)
嗯,这里要注意。子功能字节的最高位(bit 7)有特殊含义。如果置1,表示这是一个抑制肯定响应的请求。什么意思?就是告诉ECU:你处理完就行,不用回我“收到”了。我在项目中遇到过,批量刷写时用这个能省不少总线时间。
关键点:请求报文长度最小2字节(SID + 1字节数据),最大取决于诊断协议栈配置,一般不超过4095字节。
2.2 肯定响应与否定响应:ECU的两种回答
ECU收到请求后,要么说“行”,要么说“不行”。
肯定响应的格式是:请求SID + 0x40 + 响应数据。比如上面那个读取VIN码的请求,肯定响应就是:
响应:62 F1 90 4C 46 56 33 30 31 32 33 34 35 36 37 38 39
解释:
0x62 - 肯定响应SID(0x22 + 0x40)
0xF1 0x90 - 回显请求中的DID
后面17个字节 - VIN码数据
否定响应就有点意思了。格式是:0x7F + 请求SID + NRC。NRC就是否定响应码,告诉你为什么不行。
否定响应:7F 22 12
解释:
0x7F - 否定响应标志
0x22 - 回显请求的服务ID
0x12 - NRC,表示“子功能不支持”
我个人习惯,调试时先看否定响应。它直接告诉你问题出在哪,比瞎猜快多了。
2.3 NRC详解:ECU的“拒绝理由”大全
NRC(Negative Response Code)是UDS协议里最实用的部分。我整理了一份常用NRC表,你收好:
| NRC值 | 名称 | 含义 | 常见场景 |
|---|---|---|---|
| 0x10 | General Reject | 一般性拒绝 | 请求格式错误,但说不清具体原因 |
| 0x11 | Service Not Supported | 服务不支持 | ECU根本不认识这个SID |
| 0x12 | Sub-function Not Supported | 子功能不支持 | SID认识,但子功能没实现 |
| 0x13 | Incorrect Message Length | 报文长度错误 | 多发了或少发了字节 |
| 0x22 | Conditions Not Correct | 条件不满足 | 当前会话下不允许该操作 |
| 0x31 | Request Out of Range | 请求超出范围 | DID不存在或数据值非法 |
| 0x33 | Security Access Denied | 安全访问被拒绝 | 没解锁就尝试写操作 |
| 0x78 | Request Correctly Received - Response Pending | 请求已收到,正在处理 | ECU需要长时间处理,让你等等 |
避坑指南:我曾经遇到过一个ECU,每次发诊断请求都返回0x78。一开始我以为程序死循环了,后来才发现是ECU在处理NVM写入,需要几百毫秒。0x78其实是告诉你“别急,我在忙”。正确的做法是收到0x78后,等一段时间再重新发请求。
为什么0x78这么特殊?因为它不是真正的否定。ECU说“我收到了,但还没处理完,你先等着”。这时候诊断仪应该等待,而不是直接报错。你想想看,如果ECU正在擦写Flash,你催它也没用。
2.4 物理寻址与功能寻址:找一个人还是找一群人
这是UDS里一个很巧妙的设计。说白了就是:你发的请求,是给一个ECU,还是给一群ECU?
物理寻址:一对一。请求报文的目标地址是某个特定ECU的物理地址。只有这个ECU会响应。比如你想读取发动机ECU的故障码,就用物理寻址。
功能寻址:一对多。请求报文的目标地址是一个功能组地址。总线上所有支持该功能的ECU都会收到并处理,但只有符合条件的ECU才会响应。比如你想让所有ECU同时进入扩展会话,就用功能寻址。
在CAN总线上,这两种寻址体现在CAN ID上。举个例子:
物理请求:0x7DF → 目标ECU的物理地址是0x7DF
功能请求:0x7DF → 但这里的0x7DF是功能地址,所有ECU都认
物理响应:0x7E8 → 源ECU用自己的物理地址回复
功能响应:一般不响应,或者用物理地址单独回复
嗯,这里有个坑。功能寻址的请求,ECU通常不会发肯定响应。为什么?你想想看,如果10个ECU同时响应,总线不就乱套了?所以功能寻址一般用于广播类操作,比如“所有ECU,请进入编程会话”。每个ECU收到后自己处理,但不回复。
重要提醒:功能寻址的否定响应是可以发的。如果某个ECU不支持该功能,它会用物理地址回复一个否定响应。我曾经调试时发现,一个ECU收到功能寻址的0x10服务请求后,返回了0x7F 10 0x22。排查了半天,原来是它当前处于编程会话,不允许重复进入。这个0x22(条件不满足)就是通过物理地址发回来的。
我个人习惯,在项目初期先用物理寻址把每个ECU单独调通。等所有ECU都稳定了,再测试功能寻址。这样出问题时,能快速定位是哪个ECU在捣乱。
好了,这一章的内容就这些。请求报文怎么拼、肯定否定怎么区分、NRC怎么查、寻址方式怎么选,都是UDS开发的基本功。下一章我们聊聊诊断会话管理,那可是ECU的“工作模式切换器”。