2.18 负响应码(NRC)——当ECU说“不
被拒之门外的诊断仪
你第一次连上一台陌生的ECU。你发了条0x2E——想用DID 0xF40C写入一个新的发动机转速限制。ECU回你三个字节:0x7F 0x2E 0x33。
如果你不知道NRC编码体系,你看到的只是一串十六进制数字。如果你知道——你看到的是一个完整的句子:“我收到了你的0x2E写入请求——但拒绝执行——因为安全访问被拒绝了——请先解锁。
三层信息,三个字节。
这就是NRC(Negative Response Code,负响应码)的威力——它把“不行“这种无用的回复变成了可执行的指令。没有NRC的“不行“等于没有回答——诊断仪既不知道自己错在哪,也不知道该怎么改。有了NRC,诊断仪可以实现自动化自愈——被拒后自己调整、自己重试。
NRC不是装饰——它是UDS协议的自我纠正机制。它是让诊断仪从“盲人摸象“升级为“自动化诊断“的关键基础设施。
NRC的编码结构——三字节里承载一个对话
负响应帧结构:
┌──────┬──────────┬──────────┐
│ 0x7F │ SID │ NRC │
├──────┼──────────┼──────────┤
│NR_SI │ 请求回声 │ 拒绝原因 │
│1字节 │ 1字节 │ 1字节 │
└──────┴──────────┴──────────┘
三个字节分别回答了三个问题:
- 0x7F — “这是一个负响应”(不是正响应)
- SID回声 — “我拒绝的是哪个请求”(上下文——诊断仪可能发了多个请求)
- NRC — “为什么拒绝”(可执行的原因编码)
为什么用0x7F而不是0xFF?
正响应SID的编码规则是请求SID + 0x40,覆盖0x40~0x7E。0x7F是此范围之外紧邻的下一个值——bit6=1(标识为响应而非请求),同时不与任何正响应SID冲突。0xFF虽然技术上也可行,但选择0x7F保持了编码的紧凑性:所有响应(正/负)集中在0x40~0x7F区间,诊断栈只需检查bit6即可区分请求与响应,无需单独处理0xFF这个离群值。
全量NRC速查表——每个“不“背后的含义
| NRC | 缩写 | 全称 | 含义 | 医院类比 | 诊断仪动作 |
|---|---|---|---|---|---|
| 0x10 | GR | General Reject | 一般性拒绝——ECU无法给出更具体的原因 | “这个治疗我不能做,理由复杂不细说” | 无法自动恢复——日志记录、人工分析 |
| 0x11 | SNS | Service Not Supported | 此SID本ECU完全不支持 | “我们科室不提供这项检查” | 永久跳过此SID——此ECU永远不会支持 |
| 0x12 | SFNS | Sub-Function Not Supported | 服务支持但此子功能不支持 | “我们有心电图机,但不做动态心电图” | 换子功能或用其他SID实现类似目的 |
| 0x13 | IMLOIF | Incorrect Message Length Or Invalid Format | 报文长度或格式与标准/配置不匹配 | “化验单格式错——拒收” | 检查请求字节数、检查DID长度定义 |
| 0x14 | RTL | Response Too Long | 响应数据超过传输层MTU | “报告太长打印不出来——分段给” | 拆分请求——减少DID数量或分批发 |
| 0x21 | BRR | Busy Repeat Request | ECU忙——稍后重发相同请求 | “医生正在手术——几分钟后再来” | 等待后原样重试——等P2*周期过后 |
| 0x22 | CNC | Conditions Not Correct | 条件不满足——ECU状态不正确 | “先挂急诊科的号——这个科现在处理不了” | 检查并调整会话/状态——切换到正确会话 |
| 0x24 | RSE | Request Sequence Error | 请求序列错误——步骤顺序不对 | “先叫挂号→再就诊→再开药——你跳步了” | 从正确序列第一步开始:建会话→解锁→操作 |
| 0x25 | NRFSC | No Response From Subnet Component | 子网组件无响应 | “拍了CT但CT室没回复” | 检查网关、子网络连接——重试 |
| 0x26 | FPEORA | Failure Prevents Execution Of Requested Action | 已存在的故障阻止了请求执行 | “你有心脏病史——不能用这个麻醉药” | 先处理已存在的故障——清除或修复 |
| 0x31 | ROOR | Request Out Of Range | 请求参数超出有效范围 | “没有第439号床位——我们楼就三层” | 查ODX确认有效的DID/地址/例程ID |
| 0x33 | SAD | Security Access Denied | 安全等级不足——未解锁或解锁失败 | “你没权限看这份病历——先出示授权” | 执行0x27解锁——requestSeed→sendKey |
| 0x35 | IK | Invalid Key | SecurityAccess密钥计算错误 | “授权码不对——请重新生成” | 重新请求种子→重新计算密钥→重新发送 |
| 0x36 | ENOA | Exceeded Number Of Attempts | 尝试次数超限——触发延迟锁定 | “输错密码太多次——30分钟后重试” | 等待延迟定时器到期后重试 |
| 0x37 | RTDNE | Required Time Delay Not Expired | 延迟时间未到——还在锁定waiting period内 | “还得等20分钟——急也没用” | 继续等待——使用TesterPresent维持会话 |
| 0x38-0x4F | RBEDLSD | Reserved By Extended Data Link Security | 扩展数据链路安全保留 | (加密通信相关——超出本书范围) | — |
| 0x70 | UDNA | Upload/Download Not Accepted | 上传下载操作未接受——可能因为前面对话未完 | “上次手术没做完——不能开始新手术” | 完成或正确终止前序下载/上传流程 |
| 0x71 | TDS | Transfer Data Suspended | 数据传输已暂停 | “手术中途暂停——需要确认继续” | 发送0x36继续传输或0x37退出传输 |
| 0x72 | GPF | General Programming Failure | 刷写操作一般性失败 | “手术过程中发生了意外” | 检查Flash状态——可能需要擦除后重新编程 |
| 0x73 | WBSC | Wrong Block Sequence Counter | 块序列号错误——下载传输的数据块序号不对 | “药品批次号对不上——送错了” | 重新传输当前块——BlockSequenceCounter校验失败 |
| 0x78 | RCRRP | Request Correctly Received, Response Pending | 请求已收到、处理中——最终响应稍后给出 | “化验正在做——需要等一会出结果” | 等待——扩展P2*时间窗口——这是唯一的“正常中的异常“ |
| 0x7E | SFNSIAS | Sub-Function Not Supported In Active Session | 当前会话不支持此子功能 | “这个检查只能在住院部做——门诊做不了” | 切换到正确的会话→重试 |
| 0x7F | SNSIAS | Service Not Supported In Active Session | 当前会话不支持此服务 | “这个治疗只能在手术室做——你得先住院” | 发送0x10切换到指定会话→重试 |
| 0x81 | RPMTOH | RPM Too High | 发动机转速太高——操作被拒绝 | “心跳太快——不能打这个针” | 请求降低转速或等待怠速状态→重试 |
| 0x82 | RSO | RPM Too Low | 发动机转速太低——操作被拒绝 | “血压太低——不适合做负荷测试” | 提高发动机转速→重试 |
| 0x83 | EIR | Engine Is Running | 发动机正在运转——此操作不能在运转时执行 | “不能边跑边换零件——先熄火” | 先停止发动机→重试 |
| 0x84 | EINR | Engine Is Not Running | 发动机未运转——此操作需要发动机在运转状态 | “这一项需要发动机转起来才能测” | 先启动发动机→重试 |
| 0x85 | ERTTL | Engine Run Time Too Low | 发动机运行时间太短——温度/状态未稳定 | “刚启动——水温还没上来——不能测” | 等待发动机暖机→重试 |
| 0x86 | TEMPTOH | Temperature Too High | 温度太高——条件不满足 | “发烧40度——不能做剧烈活动” | 等待降温或主动散热→重试 |
| 0x87 | TEMPTOL | Temperature Too Low | 温度太低——条件不满足 | “体温过低——不适合进行代谢测试” | 等待升温或主动加热→重试 |
| 0x88 | VSTL | Vehicle Speed Too Low | 车速太低——条件不满足 | “原地不动——没法测ABS” | 必须行驶到一定速度→重试 |
| 0x89 | VSTH | Vehicle Speed Too High | 车速太高——条件不满足 | “速度太快——没法执行精确的转向角校准” | 减速到允许范围→重试 |
| 0x8A | TPTOH | Throttle/Pedal Too High | 节气门开度/踏板位置太高 | “油门踩太深——原地测不了怠速数据” | 松开油门→重试 |
| 0x8B | TPTOL | Throttle/Pedal Too Low | 节气门开度/踏板位置太低 | “没踩油门——无法测试加速响应” | 补油到正确开度→重试 |
| 0x8C | TVINR | Transmission Not In Neutral | 变速箱不在空档 | “挂挡了——不能做静态测试” | 挂到空档或P档→重试 |
| 0x8D | TVING | Transmission Not In Gear | 变速箱未挂挡 | “空档——测试不了档位切换” | 挂到D档或R档→重试 |
| 0x8F | BSCNE | Brake Switch(es) Not Closed | 刹车开关未闭合——未踩刹车 | “没踩刹车——没法测试刹车系统” | 踩下刹车→重试 |
| 0x90 | SLNOIP | Shifter Lever Not In Park | 换挡杆不在P档 | “不在P档——有些设置不能改” | 切换到P档→重试 |
| 0x91 | TCCL | Torque Converter Clutch Locked | 液力变矩器锁止——某些测试不能做 | “离合器锁住了——先解锁” | 解锁变矩器→重试 |
| 0x92 | VTH | Voltage Too High | 电压太高——环境条件不满足 | “供电异常——不适合精密操作” | 检查电源系统→调整→重试 |
| 0x93 | VTL | Voltage Too Low | 电压太低——环境条件不满足 | “电池快没电了——不能做要求稳定的检查” | 给电池充电或更换电源→重试 |
这个表看起来很枯燥——但它是你写一个能自我修复的诊断仪程序的必备知识。每一个NRC都不是终点——它是一个信号,告诉你“应该调整什么条件才能让这个操作在下次被接受“。
深度专题:NRC 0x78——唯一的“正-负混合响应”
在所有NRC中,0x78 RCRRP(Request Correctly Received, Response Pending)是独一无二的存在。从帧格式上看,它是负响应:0x7F + SID + 0x78。从语义上看,它传达的却是正向信息:“你的请求没有问题——我受理了——只是需要更多时间。
为什么不用正响应? 因为正响应一旦发出,就是最终结果。而0x78的正响应还没有产生——ECU需要时间——它不能谎报结果。所以它必须在“还没结果“时给诊断仪一个回应——而UDS里唯一的“未完成但正常“的信号通道就是NRC 0x78。
0x78触发的协议行为链:
诊断仪发送请求(假设是0x22读一个复杂DID)
│
▼ P1定时器开始——诊断仪等待
│
ECU收到请求 → 确认格式正确 → 确定需要额外处理时间
│
▼ 在P2超时前返回
诊断仪 ← 0x7F 0x22 0x78
│
▼ 诊断仪收到0x78 → 切换到P2*(增强型超时——典型值5000ms)
│
ECU继续处理(例如:NvM异步读取未完成、等待传感器采集窗口)
│
▼ 处理完成
诊断仪 ← 0x62 ...(真正的正响应)
0x78可以出现多次! 如果ECU在P2周期内仍无法完成,它可以继续发送第二个、第三个0x78——每次重置诊断仪的P2等待计时器。在固件刷写场景中,ECU可能持续发送数分钟的0x78直到刷写完成。
核心洞察:0x78是UDS协议设计者面对“异步计算现实“的妥协方案。在理想世界里,所有ECU都能在毫秒内完成任意DID的计算。在真实世界里,有些操作需要等待外部事件(NvM异步写入、CAN消息窗口、传感器数据就绪)。0x78让UDS支持异步处理而不破坏同步的请求-响应模型——这是一个优雅的hack,用负响应格式包装了一个正向的等待信号。
功能寻址时NRC的抑制规则——为什么要假装听不见
功能寻址(Functional Addressing)是诊断仪同时向总线上的所有ECU发送同一请求的能力。例如:诊断仪通过功能ID发送0x10 0x01(切换到默认会话)——所有ECU同时切换。这很高效——但也带来了一个问题:
如果50个ECU中有45个不支持某个SID——它们应该同时回复NRC 0x11吗?
如果它们都回复——CAN总线上会瞬间堆积45条负响应帧——造成总线拥塞和仲裁混乱。更致命的是:有些ECU在CAN ID的低位不同——它们会在不同的仲裁时隙中挤占总线——最终到达诊断仪的数据可能错乱。
解决方案:功能寻址时,某些NRC被静默抑制。 ECU收到功能寻址请求时,如果判断NRC属于特定类别,它不发送任何响应——假装没有收到。这不是bug——这是协议设计的刻意行为。
| 被抑制的NRC | 原因 |
|---|---|
| 0x11 (SNS) | 此ECU不支持该服务——但功能寻址的目标可能不包含此ECU——不回复 |
| 0x12 (SFNS) | 子功能不支持 |
| 0x31 (ROOR) | DID/地址/例程不在此ECU中 |
| 0x7E (SFNSIAS) | 子功能不在当前会话下支持 |
| 0x7F (SNSIAS) | 服务不在当前会话下支持 |
哪些NRC在功能寻址时仍然发送? 那些“服务存在、但当前状态不适合执行“的错误:
- 0x22 (CNC) — 我认识你要我做的手术——但我现在的状态不允许
- 0x33 (SAD) — 我知道你想读的数据——但安全访问没解锁
- 0x24 (RSE) — 请求序列错误——步骤没走对
这些NRC之所以保留——是因为它们告诉诊断仪:“不是ECU不支持——而是操作步骤需要调整。“这对诊断仪的自愈逻辑有价值。
从NRC到自愈——一个成熟诊断仪的内在机制
生产级别的诊断仪不会在收到NRC时就把控制权交还给操作员——它会根据NRC自动调整策略。以下是一个自愈引擎的简化工作流程:
收到负响应帧:
│
├─ 0x11 (SNS) → 记录日志 → 永久标记此ECU不支持该服务 → 继续下一个任务
│
├─ 0x7F (SNSIAS) → 提取该服务要求的会话类型 → 发送0x10切换会话 → 等待正响应 → 重试原请求
│
├─ 0x7E (SFNSIAS) → 同上——但仅为子功能切换会话
│
├─ 0x33 (SAD) → 提取该DID/操作要求的安全等级 → 发0x27 requestSeed → 计算密钥 → sendKey → 等待正响应 → 重试原请求
│
├─ 0x35 (IK) → 重新请求种子 (0x27 requestSeed) → 重新计算密钥 → sendKey → 重试
│
├─ 0x36 (ENOA) → 查询该ECU的延迟定时器时长 → 启动局部等待 → 到期后重试
│
├─ 0x37 (RTDNE) → 等待剩余延迟时间 → 重试
│
├─ 0x78 (RCRRP) → 切换到P2*等待 → 扩展接收窗口 → 等待最终响应
│
├─ 0x24 (RSE) → 重置会话 → 重新建会话 → 重新解锁 → 重新执行整个操作序列
│
├─ 0x22 (CNC) → 检查ECU当前状态 → 尝试调整条件 → 重试
│ (某些CNC需要人工判断——例如"发动机未运转"——诊断仪不能自动启动发动机)
│
└─ 0x10 (GR) → 无法自动恢复 → 记录完整上下文 → 提示操作员人工介入
这种自愈机制可以让诊断仪在后台自动完成“会话切换→解锁→重试“这条完整的恢复链——把几十毫秒的自动恢复变成无需人工介入的流畅过程。
本篇小结
- NRC不是装饰——它是UDS协议的自我纠正机制。每一个负响应都携带三层信息:“这是负响应(0x7F)” + “我拒绝了哪个请求(SID回声)” + “为什么(NRC)”。
- 0x78 RCRRP 是UDS中唯一的“正-负混合响应“——格式是负响应,语义是正向的“处理中,请等待“。它是诊断协议对ECU内部异步计算现实的妥协。
- 功能寻址时SNS/SFNS/ROOR/SFNSIAS/SNSIAS五类NRC被静默抑制——防止不支持某功能的ECU同时回复NRC淹没总线。仅保留“服务存在但条件不满足“类NRC(如0x22 CNC、0x33 SAD)。
- 生产级诊断仪依赖NRC自愈引擎——收到NRC不意味着“人工介入“——多数情况下诊断仪可以自动完成“会话切换→解锁→重试“的恢复链。
- NRC从0x11到0x93覆盖了从“服务不支持“到“换挡杆不在P档“的几乎每一种拒绝场景——它是对ECU内部状态的一个完整“原因反馈“机制。
【下集预告】: 你已经读完了ISO 14229-1:2013负响应码的完整体系——从NRC的报文格式、分类表、到诊断仪的自愈策略。下一节进入2.19周期读取(0x2A),继续探索UDS的更多服务。