4、UDS请求与响应机制:单帧与多帧传输、物理寻址与功能寻址、肯定响应与否定响应(NRC)

好,咱们今天聊点实在的。UDS诊断的核心,说白了就是「你问一句,我答一句」。但这一问一答背后,藏着不少门道。我在项目里见过太多工程师,协议栈调通了,但就是卡在「为什么ECU不理我」或者「为什么发了多帧对方没反应」这种问题上。

嗯,这一章咱们就把请求与响应的机制彻底捋清楚。你想想看,一个诊断会话,从你发出一条请求,到ECU给你回复,中间经历了什么?

4.1 单帧传输:最基础的「一问一答」

单帧,就是一条CAN报文搞定一次请求或响应。它的数据场长度不超过8字节(CAN标准帧)。

我个人习惯把单帧分成两类:

  • 请求单帧:诊断仪发给ECU,比如发个「读取DID 0xF190」
  • 响应单帧:ECU回给诊断仪,比如「数据是0x01 0x02 0x03」

举个例子,读取VIN码(17个ASCII字符)时,如果只用单帧,那肯定装不下。但如果是读取一个2字节的DID,单帧就绰绰有余。

单帧格式(N_PCI字节)

第一个字节的高4位是0x0,低4位表示数据长度(0~7)。

比如:0x03 0x22 0xF1 0x90 —— 第一个字节0x03表示后面有3个有效数据字节。

我记得有一次,客户反馈说「ECU不响应读取请求」。我排查了半天,发现是诊断仪发的单帧里,N_PCI长度写错了。明明只有3个字节,却写了个0x05。ECU直接忽略,连NRC都不回。嗯,这种低级错误,调试时最容易忽略。

4.2 多帧传输:数据量大了怎么办?

当你要传输的数据超过7个字节(CAN标准帧),单帧就装不下了。这时候就得用多帧传输。

多帧传输分三个阶段:

  1. 首帧(FF):第一个字节高4位是0x1,低4位+第二个字节组成总数据长度(12位,最大4095字节)。
  2. 流控帧(FC):接收方告诉发送方「你慢点发」或「你可以发了」。流控帧有三个参数:流控状态(CTS/WAIT/OVFLW)、块大小(BS)、最小间隔时间(STmin)。
  3. 连续帧(CF):发送方按流控帧的要求,一帧一帧地把剩余数据发完。

避坑指南

我曾经在一个项目里,ECU的接收缓冲区只有64字节,但诊断仪一次性发了200字节的下载请求。ECU收到首帧后,发现总长度超过缓冲区,直接回了NRC 0x78(请求正确接收,但响应待定)。然后...就没有然后了。诊断仪一直等,ECU一直忙。最后我加了个流控帧的WAIT状态,才把问题解决。

多帧传输的时序,说白了就是「你发一帧,我回一个流控,你再发下一帧」。但要注意,流控帧的BS参数决定了连续帧的最大数量。比如BS=5,表示发送方最多连续发5帧,然后必须等下一个流控帧。

帧类型 N_PCI字节 说明
首帧(FF) 0x10 + 长度高4位 第二个字节是长度低8位
连续帧(CF) 0x20 + 序列号 序列号从1开始,循环0~15
流控帧(FC) 0x30 + 流控状态 后跟BS和STmin

你想想看,如果STmin设得太小,ECU处理不过来,就会丢帧。我建议STmin至少设10ms,除非你确认ECU的CAN驱动足够快。

4.3 物理寻址与功能寻址:找谁说话?

物理寻址,就是点对点。诊断仪发一条CAN报文,只有指定的ECU会响应。CAN ID通常是0x7E0(请求)和0x7E8(响应)。

功能寻址,就是广播。诊断仪发一条CAN报文,总线上所有支持该功能的ECU都会响应。CAN ID通常是0x7DF。

这里有个关键点:功能寻址的响应,ECU不能发NRC。为什么?因为如果多个ECU同时发NRC,总线会冲突。所以功能寻址只用于「无响应」或「只有肯定响应」的场景。

注意

我在项目中遇到过一个问题:用功能寻址发了一个「读取DID」的请求,结果总线上三个ECU同时响应,CAN总线直接报错。从那以后,我规定:功能寻址只用于「诊断会话控制」或「ECU复位」这类不需要数据回传的服务。

物理寻址和功能寻址的选择,其实取决于你的需求:

  • 想单独控制某个ECU?用物理寻址。
  • 想同时唤醒所有ECU?用功能寻址。
  • 想读取某个ECU的故障码?用物理寻址。

4.4 肯定响应与否定响应(NRC)

ECU收到请求后,要么回肯定响应,要么回否定响应。肯定响应的格式很简单:SID + 0x40 + 数据。比如请求是0x22,肯定响应就是0x62。

否定响应就复杂了。格式是:0x7F + SID + NRC。NRC就是否定响应码,告诉诊断仪「为什么不行」。

常见的NRC有:

  • 0x11:服务不支持。你发了个ECU不认识的SID。
  • 0x12:子功能不支持。比如你发了个0x10 0x03,但ECU不支持扩展会话。
  • 0x13:报文长度或格式错误。数据长度不对,或者N_PCI写错了。
  • 0x22:条件不满足。比如你没进入编程会话就想刷写。
  • 0x31:请求超出范围。比如DID不存在,或者数据值非法。
  • 0x78:请求正确接收,但响应待定。ECU正在忙,稍后再问。

NRC处理原则

ECU收到请求后,必须按顺序检查:

  1. SID是否支持?不支持回0x11。
  2. 子功能是否支持?不支持回0x12。
  3. 报文格式是否正确?不正确回0x13。
  4. 安全访问是否通过?没通过回0x33。
  5. 条件是否满足?不满足回0x22。
  6. 数据是否有效?无效回0x31。

一旦检查通过,就执行服务,然后回肯定响应。

我记得有一次,客户说「ECU总是回0x22,但我觉得条件都满足了」。我查了半天,发现是ECU在收到请求前,刚执行完一个耗时操作,状态还没恢复。嗯,这种时序问题,用CANoe的触发功能一抓就现形了。

4.5 实战中的常见问题

说了这么多,咱们总结几个实战中容易踩的坑:

  • 多帧超时:连续帧之间的间隔不能超过STmin,否则ECU会超时退出。
  • 流控帧丢失:发送方没收到流控帧,会一直等。我建议加个超时重传机制。
  • NRC 0x78滥用:有些工程师一遇到复杂操作就回0x78,但ECU不能一直「待定」。我建议设个最大等待时间,超时就回0x10(通用拒绝)。
  • 功能寻址的响应冲突:前面说了,功能寻址不要用于需要数据回传的服务。

我的调试习惯

我一般会在CANoe里写个CAPL脚本,自动记录所有诊断请求和响应。一旦发现NRC,就高亮显示。这样调试效率高很多。另外,我建议在ECU的代码里加个诊断日志,把每次收到的请求和回的NRC都打印出来。嗯,这招帮我省了不少时间。

好了,这一章的内容就到这儿。单帧与多帧、物理寻址与功能寻址、肯定响应与否定响应,这三块是UDS诊断的基石。你把这些搞懂了,后面学会话管理、安全访问、DID读写,就会轻松很多。

下一章,咱们聊聊「诊断会话控制」—— 怎么让ECU进入不同的工作模式。到时候我会分享一个我踩过的坑,关于会话切换时NRC 0x22的诡异问题。