Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

2.4 TesterPresent(0x3E)——医生还在吗

协议里最短的一条服务,也是最关键的一条

UDS有26种SID。其中最短的那一条,报文只有两个字节。它的全部作用就是让诊断仪向ECU说一句:“我还在。

诊断仪 → ECU:  0x3E 0x00
ECU → 诊断仪:   0x7E 0x00

仅此而已。不读数据、不写数据、不执行任何操作、不改变任何状态。这一对请求/响应比你我日常呼吸还轻——但是它保证了你挂的号不被自动注销。

如果这个两字节报文在一段时间内没有到达ECU——S3server超时,ECU自动切回默认会话。你花20秒建立的全部诊断上下文——会话、安全等级、周期读取配置、事件响应注册——全部归零。

核心洞察:0x3E不是诊断操作。它是 诊断会话的生命线。S3server定时器是由0x3E喂养的——不是你发了多少条请求,而是你任何时候都必须发这一条特定的保活命令。


为什么是诊断仪主动报活,而不是ECU主动探测

这里有一个设计选择,答案异常清晰。两种可能的做法:

方案A(UDS的选择):诊断仪定时发0x3E,ECU重置定时器。 方案B(被否决):ECU主动发探测包,诊断仪回复。ECU根据是否收到回复判断诊断仪是否还在。

为什么是A?

  • A的单向负担小。 诊断仪只需发一个无作用的、两字节的信号——ECU收到后只需重置定时器然后回复(两字节回音)。这两字节在CAN总线上占完一个8字节帧,总线负载接近零。
  • B会让总线被打满。 如果有30个ECU都需要主动向诊断仪发探测包——每秒一次——30 × (探测帧+响应帧) = 每秒60帧只为了回答“你在不在“。在刷写期间总线带宽对传输数MB固件至关重要——每多一帧被动检测都是浪费。
  • B的可靠性更低。 如果ECU的探测包在路上被碰撞(CAN仲裁也可能碰撞),诊断仪收不到就不会回复——ECU会认为诊断仪断了。但实际上诊断仪正常——只是那一个探测分组的帧抖撞了一次。

0x3E 的天才在于反向强绑定:诊断仪不想掉出诊断会话就必须持续说话;ECU只需要接收和重置定时器。这样一来,可靠性和责任都放在最需要维持会话的那一方——诊断仪。


请求报文

Byte 0: 0x3E (SID)
Byte 1: sub-function
        bit7  = suppressPosRsp (1=不需要正响应)
        bit6-0= 0x00 (zeroSubFunction)

唯一允许的子功能码是0x00(zeroSubFunction)。其他子功能都会导致负响应(NRC 0x12=subFunctionNotSupported)。因为TesterPresent不需要分级——“活着“就是“活着”,不需要特别区分是哪种活法。

suppressPosRsp(bit7)在TesterPresent的用例上是特殊的一个场景:在功能寻址发送中非常重要。你同时在和30个ECU保活——你只需要向每一个ECU证明“我活着“就够——不需要每个ECU都回话浪费总线和ECU的CPU处理时间。

功能寻址 + suppressPosRsp=1:
诊断仪 → CAN总线:  0x3E 0x80   (bit7=1, 不需要响应)
→ 30个ECU都收到,全部重置S3定时器,没有一个回话。
物理寻址 + suppressPosRsp=0:
诊断仪 → 特定ECU:  0x3E 0x00
ECU → 诊断仪:      0x7E 0x00   (确认收到)

核心洞察:suppressPosRsp在0x3E上的作用比其他任何SID都更频繁被使用——因为在功能寻址中,所有ECU都需要一次保活,不需要每个都回复。


正响应:0x7E + 0x00

Byte 0: 0x7E (SID+0x40)
Byte 1: 0x00 (子功能回声)

当你收到正响应,发生的事只有一件:ECU的S3定时器被重置到你进入会话时约定的值。仅此而已。

负响应不应该出现在这个服务——如果真的出现了(NRC 0x12=subFunctionNotSupported)——意味着ECU不完全兼容UDS最低标准。任何UDS兼容的ECU都必须支持0x3E。


S3超时时间从哪来

S3server超时时间——这个值在UDS标准中不是由0x3E协商的。它由以下方式提供:

  1. ECU实现内部配置。 通常Bsp配置值在2000ms-5000ms范围。
  2. 生产者运行手册。 ECU的OEM供货规格通常会写明“此ECU的S3超时时间”。
  3. 诊断仪基于这个已知值定时发送0x3E,确保发送间隔小于S3超时的80%——留余量防止CAN仲裁抖动和ECU处理误差。

这里有个真实的生产坑:典型的S3值为5000ms。如果你的诊断仪每4秒(4000ms)发一次0x3E——看似安全(4000<5000)——但CAN帧的发送到ECU收到之间有延迟(不一定在同次CAN任务调度里立即处理),每帧可能额外消耗数十毫秒。如果再赶上ECU的CAN任务在另一个核、刷写期间CPU占用高——发周期给0x3E可能超时。 所以最安全的做法——你的发送间隔应≤ S3的50%(5000ms S3 → 每2000ms发一次0x3E)。


0x3E与0x10的相互作用

这两条服务构成了UDS会话生命周期的最基本回路:

               ┌────→  N秒后   ────┐
               │ (S3超时)         │
               │  ECU切换          │ 发送0x3E重置定时器
               │  默认会话          │ 医生还在
               │                   │
          ┌──────────┐      ┌──────────┐
          │ 非默认会话 ├──────┤ 定时器重设│←─┐
          └────┬─────┘      └──────────┘  │
               │        0x3E保活          │
               └──────────────────────────┘
                   每 N 秒一次 (N < S3/2)
初始状态: defaultSession
    │
    ├─ 诊断仪发: 0x10 0x03
    │  ECU → 0x50 0x03 + timing → 进入扩展会话
    │
    ├─ 诊断仪发: 0x3E 0x00 (t=0)
    │  ECU → 0x7E 0x00 → S3定时器重置
    │
    ├─ 诊断仪发: 0x19 0x01 0x4F (读DTC数量)
    │  ECU → 0x59... → S3定时器重置
    │
    ├─ ... 一段时间工作中 ...
    │
    ├─ 诊断仪发: 0x3E 0x00 (t=2000ms)
    │  ECU → 0x7E 0x00 → S3定时器重置
    │
    └─ 诊断仪太久没发任何请求 (t>S3)
       ECU S3 → 0
       ECU 自动切回 defaultSession
       session = 0x01
       securityLevel = LOCKED
       注册的周期读和事件响应 → 全部停止

保活避坑指南

错误1:只发送0x22/0x19等正常请求以为也能保活

“我不是一直在问ECU数据吗——每200毫秒发一个0x22读DID——这不能代替0x3E吗?

不能。 0x22/0x19这类请求会穿过诊断协议栈进入应用层——ECU需要调度CPU去查表、读传感器、编码数据、构建响应。而0x3E只在诊断通信层(DCM/DSL)内部处理——不触及任何应用模块、不调用任何业务函数,处理路径比0x22短得多。

不过有两层澄清:第一,在Flash擦写这类极端场景下,任何报文(包括0x3E)都可能被阻塞——0x3E也不是万能的,只是被阻塞的概率低于需要进入应用层的请求。第二,0x22的负载取决于读什么DID——读VIN这类静态常量与查表返回即可,未必比0x3E重多少;但读传感器实时值需要经过ADC采样和标定转换,确实有负载。核心区别在于:0x22无论轻重都进入了应用层,而0x3E只在协议栈底层——路径不同,职责不同,不能互换。

正确做法:永远用0x3E单独做保活,不用正常请求代替它。

错误2:suppressPosRsp=1但ECU出错时无负响应

在物理寻址下——suppressPosRsp=1只抑制正响应。ECU有权发回负响应。但诊断仪必须准备好收两个字节的NRC(0x7F + 0x3E + NRC)——虽然不常见,但可能的:某些非默认会话内部状态出错时0x3E请求可能会被拒绝。

错误3:S3超时后在诊断仪不知情下会话已变

ECU自动切回默认会话后,不再向诊断仪主动通知。你的诊断仪认为自己还在扩展会话——发了一个0x27请求解锁——ECU回复0x7F + 0x27 + 0x22(服务在默认会话不支持)——于是诊断仪错误日志里写了一大堆“ECU不支持安全访问“。但实际上——会话超时了、你还在用旧状态。

解决方法:每次在你的诊断仪收到“会话中不支持“或类似的负响应后,先回退到重新建立会话(发0x10)再重试后面的全套。现代商业诊断仪的逻辑通常都带有这个“收到NRC 0x7F后重建会话“的自愈逻辑。


本篇小结

  1. 0x3E TesterPresent是UDS里最短、最“无意义“的SID——它的字节内容不包含任何诊断数据。但它是维持非默认诊断会话的唯一生命线。
  2. 为什么是诊断仪主动报活而不是ECU探测——责任放在需要维持会话的一方(诊断仪),避免了被动探测在多ECU场景下产生的海量冗余流量。
  3. S3定时器是静的——它不是由ECU通知给诊断仪的——它写在ECU的供应商规格里。诊断仪的保活间隔应小于S3的一半以留足抖动余量。
  4. suppressPosRsp在0x3E上的应用非常普遍——功能寻址时一次保活所有ECU,为免每一个ECU再回话,应置bit7=1。
  5. 不要用正常请求(0x22/0x19/…)替代0x3E——只有纯粹的诊断心跳不需要触发任何应用层业务,保证诊断会话在最轻的负载下维持。

【下集预告】:保活的过程中,你可能还需要让ECU自己做一件事——‘重启一下’。但重启分三种——彻底断电(hardReset)、模拟熄火(keyOffOnReset)、只重启程序(softReset)。它们之间的区别不是参数的细微差异——而是对ECU的物理状态、学习值、RAM是否保持、非易失存储是否重新初始化三种完全不同的态度。为什么正响应必须在复位之前发送?这是UDS协议的’临别确认’设计——我们进入0x11。