UDS 诊断的“审判法则”:ISO 14229-1:2013 中否定响应顺序的终极解密

想象你是一位法官,坐在庄严的法庭上。一位律师(诊断仪)向你的书记员(ECU 的 DCM 模块)提交了一份申诉状(诊断请求)。你并不会直接阅读申诉状的内容,而是先让书记员按照一本厚厚的《法庭规程》逐项核查:这份申诉状的格式是否符合规定?当事人的身份是否真实?是否在本法庭的管辖范围内?申诉是否超过时效?只要有一条不满足,书记员就会立即喊停,并给出一个标准化的“驳回理由码”(否定响应码)。
这本《法庭规程》的顺序,在汽车诊断领域中,就是 ISO 14229-1:2013(UDS 统一诊断服务) 标准中隐含的 否定响应判定顺序。它规定了 ECU 在收到诊断请求时,必须按照什么顺序检查哪些条件,以及在哪个条件不满足时返回哪个否定响应码。这个顺序不是随意制定的,而是国际标准化组织(ISO)专家们基于“效率、安全、明确性”精心设计的。
今天,我们将从 ISO 14229-1:2013 标准出发,深入剖析否定响应码的优先级顺序。我们会结合故事、流程图、实战案例和可运行的模拟代码,让你彻底理解为什么诊断仪会收到 7F 31 22 而不是 7F 31 33,以及如何根据这个顺序快速定位诊断问题。无论你是刚接触 UDS 的初学者,还是正在为“诡异 NRC”头疼的工程师,这篇文章都将成为你的“解码器”。


目录

  1. 引子:法庭上的“驳回艺术”
  2. UDS 否定响应码基础:什么是 NRC?
  3. ISO 14229-1:2013 中的隐式顺序:从服务处理流程中提取
    • 3.1 服务处理的一般步骤
    • 3.2 否定响应码的优先级顺序表
  4. 为什么是这个顺序?—— 设计哲学与安全考量
  5. 否定响应码详解(按优先级)
    • 5.1 0x11 – serviceNotSupported
    • 5.2 0x12 – subFunctionNotSupported
    • 5.3 0x13 – incorrectMessageLength
    • 5.4 0x22 – conditionsNotCorrect
    • 5.5 0x33 – securityAccessDenied
    • 5.6 其他常见的否定响应码(0x31, 0x72, 0x78 等)
  6. 判定顺序的 Mermaid 流程图
  7. 实战案例:一个 $31 例程控制请求的完整“审判”过程
    • 7.1 场景描述
    • 7.2 正常通过
    • 7.3 会话错误(返回 0x22)
    • 7.4 安全访问未解锁(返回 0x33)
    • 7.5 环境条件不满足(返回 0x22)
    • 7.6 用户回调失败(返回 0x31)
  8. 模拟代码:一个遵循 ISO 14229-1 否定响应顺序的诊断处理模拟器
    • 8.1 代码结构设计
    • 8.2 完整 C 代码(含 Doxygen 注释)
    • 8.3 Makefile 与编译运行
    • 8.4 运行结果解读
  9. 常见误区与澄清
  10. 总结:顺序即法规

1. 引子:法庭上的“驳回艺术”

在汽车诊断中,诊断仪(Tester)通过 CAN 总线发送 UDS(Unified Diagnostic Services)请求给 ECU,ECU 内部的诊断通信管理器(DCM)会像一位严格的法官,按照固定的顺序对请求进行“审查”。一旦某个条件不满足,就会立即驳回请求并给出一个标准化的否定响应码(NRC)。这种“审查顺序”并不是 AUTOSAR 或某个厂商随意发明的,而是来自国际标准 ISO 14229-1:2013(Road vehicles — Unified diagnostic services (UDS) — Part 1: Specification and requirements)

ISO 14229-1:2013 并没有在一张表格中直接列出“第一优先 NRC、第二优先 NRC……”,而是在描述每个服务的行为时,通过状态机或处理流程隐含了否定响应的优先级。例如,它规定:ECU 应首先检查服务是否支持,然后检查子功能,再检查消息长度,等等。业界通过对标准的解读,总结出了一个通用的优先级顺序。这个顺序已被所有符合 UDS 标准的 ECU 实现所采纳,并被 AUTOSAR 等规范明确引用(例如 AUTOSAR SWS_Dcm 中明确写着“Update to ISO 14229-1:2013 (Order of NRCS)”)。

本文将基于 ISO 14229-1:2013 的核心思想,结合行业通用实践,系统性地阐述否定响应码的判定顺序。


2. UDS 否定响应码基础:什么是 NRC?

NRC(Negative Response Code)是一个 1 字节的数值,用于指示为什么 ECU 无法执行某个诊断请求。每个否定响应消息的格式为:

7F <SID> <NRC>

其中:

  • 7F 表示否定响应的服务 ID(Service ID 的补码)。
  • <SID> 是原始请求的服务 ID(例如 0x31)。
  • <NRC> 是一个 8 位的否定响应码。

例如,如果诊断仪发送了一个不存在的服务 0x99,ECU 可能返回 7F 99 11,其中 0x11 表示 serviceNotSupported

ISO 14229-1:2013 的第 7 章定义了各种否定响应码,数量多达几十种。但并非所有 NRC 都会出现在同一个服务中,且某些 NRC 的优先级更高。下面我们将列出与大多数服务相关且具有明确优先级顺序的 NRC。


3. ISO 14229-1:2013 中的隐式顺序:从服务处理流程中提取

虽然标准没有单独列出一张“优先级顺序表”,但通过分析每个服务的处理流程,可以总结出以下普遍接受的顺序。

3.1 服务处理的一般步骤

当 ECU 收到一个 UDS 请求时,典型的处理步骤(与 ISO 14229-1:2013 中描述的逻辑一致)是:

  1. 检查服务 ID 是否支持。如果不支持,立即返回 NRC 0x11。
  2. 如果服务有子功能,检查子功能是否支持。如果不支持,返回 NRC 0x12。
  3. 检查请求消息的长度是否符合该服务的规定。如果长度不正确(过短或过长),返回 NRC 0x13。
  4. 检查当前诊断会话是否允许执行该服务。如果不允许,返回 NRC 0x22。
  5. 检查服务所需的安全访问级别是否已解锁。如果未解锁,返回 NRC 0x33。
  6. 检查其他运行时条件(如电压、车速、发动机状态等)是否满足。如果不满足,返回 NRC 0x22(有些服务可能返回其他 NRC,例如 0x31 requestOutOfRange)。
  7. 执行服务逻辑(读取数据、执行例程等)。如果服务逻辑本身失败(如参数无效、资源不足),返回相应的 NRC(如 0x31, 0x72, 0x22 等)。

这个顺序确保了“最基础”的检查优先,避免浪费资源。

3.2 否定响应码的优先级顺序表

结合行业共识和 AUTOSAR 等规范的明确引用,否定响应的优先级顺序如下(从高到低):

优先级 NRC 名称 说明
1 0x11 serviceNotSupported 服务 ID 不被支持
2 0x12 subFunctionNotSupported 子功能不被支持
3 0x13 incorrectMessageLength 消息长度错误
4 0x22 conditionsNotCorrect 条件不正确(包括会话不允许、环境条件不满足等)
5 0x33 securityAccessDenied 安全访问拒绝
6 其他 NRC(如 0x31、0x72、0x78 等) 与具体服务相关 在服务逻辑执行过程中产生

重要说明

  • 0x22 出现在两个地方:一是会话不允许,二是其他环境条件不满足。但从优先级上来说,这两个情况都位于安全访问检查之前还是之后?标准实际上规定:会话检查在安全访问检查之前,因此 0x22(会话不允许)的优先级高于 0x33。而环境条件的检查可能在安全访问之后(取决于服务设计),但为了简化,许多实现将环境条件检查放在安全访问之后。因此上表中我们将 0x22 列在 0x33 之前,是为了体现会话检查的优先级。
  • 实际上,0x22 是一个“垃圾桶” NRC,可以用于多种情况,但它的优先级位置因检查点不同而不同。更精确的说法是:会话不允许的 0x22 优先级高于 0x33;而环境条件不满足的 0x22 优先级低于 0x33。因此,一个综合的顺序应该是:服务/子功能/长度 → 会话检查 → 安全访问检查 → 环境条件检查 → 服务逻辑。这个顺序是行业公认的。

4. 为什么是这个顺序?—— 设计哲学与安全考量

4.1 快速失败(Fail Fast)

服务 ID 和子功能检查只需要查表,不需要任何运行时状态。因此放在最前面,可以快速拒绝不合法的请求,避免后续更复杂的处理。

4.2 从静态到动态

先检查静态配置(服务支持、长度),再检查动态状态(会话、安全级别、环境条件)。这样既高效又安全。

4.3 安全优先于业务逻辑

安全访问检查(0x33)放在业务逻辑之前,确保只有已授权的请求才能进入敏感操作。会话检查放在安全访问之前,是因为安全级别是会话内的概念——必须先确定会话才能判断安全级别是否足够。

4.4 用户自定义条件放在最后

环境条件和业务逻辑中的错误处理放在最后,因为这些需要调用用户代码或读取实时信号,开销较大,且容易产生副作用。


5. 否定响应码详解(按优先级)

5.1 0x11 – serviceNotSupported

  • 含义:请求的服务 ID 不被 ECU 支持。
  • 典型场景:诊断仪发送 0x9A(某个不存在的服务)。
  • 为什么优先级最高:如果服务 ID 都不支持,后续任何检查都是浪费。

5.2 0x12 – subFunctionNotSupported

  • 含义:服务支持,但请求的子功能(如 $31 的 0x01/0x02/0x03)未被配置为支持。
  • 典型场景:$31 0x04(子功能 0x04 未配置)。
  • 为什么优先级第二:子功能检查同样轻量,且应该在检查长度之前。

5.3 0x13 – incorrectMessageLength

  • 含义:请求消息的长度与标准规定或 ECU 配置的范围不符。
  • 典型场景:$31 请求只有 3 字节(缺少例程 ID),应至少 4 字节。
  • 为什么优先级第三:长度错误可能导致后续解析越界,因此应在解析参数之前检查。

5.4 0x22 – conditionsNotCorrect

  • 含义:条件不满足。这是一个通用的 NRC,可表示多种情况,如:
    • 当前诊断会话不允许该服务。
    • 环境条件(如车速不为 0、发动机正在运转)不满足。
  • 优先级位置:会话检查的 0x22 在安全访问检查之前;环境条件检查的 0x22 在安全访问之后。
  • 注意:虽然码相同,但触发位置不同。调试时应根据上下文区分。

5.5 0x33 – securityAccessDenied

  • 含义:服务要求的安全访问级别尚未解锁。
  • 典型场景:诊断仪试图写入关键配置,但未通过 $27 服务解锁。
  • 优先级:位于会话检查之后,环境条件之前(通常)。

5.6 其他常见的否定响应码(0x31, 0x72, 0x78)

  • 0x31 – requestOutOfRange:请求参数超出有效范围(例如 DID 不支持、例程 ID 无效)。
  • 0x72 – generalProgrammingFailure:编程失败(例如刷写过程中校验错误)。
  • 0x78 – requestCorrectlyReceived-ResponsePending:请求已正确接收,但处理需要额外时间,ECU 先返回此 NRC 告知等待,后续再发送最终响应。此 NRC 特殊,它不是一个“失败”响应,而是“延后”响应。
  • 这些 NRC 通常在服务逻辑执行过程中产生,优先级最低。

6. 判定顺序的 Mermaid 流程图

成功

失败

收到诊断请求

服务ID支持?

返回 NRC 0x11

子功能支持?

返回 NRC 0x12

报文长度正确?

返回 NRC 0x13

诊断会话允许?

返回 NRC 0x22

安全级别满足?

返回 NRC 0x33

环境条件满足?

返回 NRC 0x22

执行服务逻辑

返回肯定响应

返回相关NRC(如0x31, 0x72等)


7. 实战案例:一个 $31 例程控制请求的完整“审判”过程

7.1 场景描述

  • 服务:$31 例程控制,子功能 0x01(startRoutine),例程 ID 0x1001(“自检例程”)。
  • ECU 配置
    • 服务 0x31 支持,子功能 0x01 支持。
    • 最小请求长度 = 4 字节(SID + subfunc + 2 字节 routine ID)。
    • 该服务仅在扩展会话(0x03)下允许。
    • 所需安全级别:Type III(等级 3)。
    • 环境条件:车速 < 5 km/h。
  • 初始状态
    • 当前会话:默认会话(0x01)
    • 安全级别:未解锁(0)
    • 车速:0 km/h

7.2 正常通过

诊断仪发送 31 01 10 01

步骤 检查 结果
1 服务 ID 支持
2 子功能支持
3 长度正确
4 会话允许? 当前默认会话不允许 → ❌ 返回 NRC 0x22

ECU 返回 7F 31 22

7.3 会话错误(返回 0x22)

先切换会话:10 03 进入扩展会话。重新发送 31 01 10 01

步骤 检查 结果
1-3
4 会话允许 扩展会话允许 ✅
5 安全级别 当前级别 0 < 3 → ❌ 返回 NRC 0x33

ECU 返回 7F 31 33

7.4 安全访问未解锁(返回 0x33)

通过 $27 服务解锁 Type III 级别。假设解锁成功后,重新发送 31 01 10 01

步骤 检查 结果
1-4
5 安全级别
6 环境条件 车速 0 < 5 ✅
7 服务逻辑 例程 ID 有效,自检执行成功 → 肯定响应

ECU 返回 71 01 00(假设例程返回状态 0x00)。

7.5 环境条件不满足(返回 0x22)

如果车速为 10 km/h,即使会话和安全级别都满足,也会在步骤 6 被驳回,返回 7F 31 22。注意这里的 0x22 与步骤 4 的 0x22 码相同,但原因不同。

7.6 用户回调失败(返回 0x31)

如果例程 ID 无效(如 0x2000),服务逻辑执行失败,返回 NRC 0x31(requestOutOfRange)。


8. 模拟代码:一个遵循 ISO 14229-1 否定响应顺序的诊断处理模拟器

为了让你直观感受这个顺序,我们编写一个运行在 Linux 上的模拟程序。它模拟了 DCM 按照上述顺序处理 $31 请求的过程。

8.1 代码结构设计

  • iso_uds_sim.h / .c:核心处理函数,包含顺序检查逻辑。
  • main.c:测试不同场景。
  • 使用标准 C 语言,无外部依赖。

8.2 完整 C 代码(含 Doxygen 注释)

/**
 * @file iso_uds_sim.c
 * @brief 模拟 ISO 14229-1:2013 否定响应判定顺序
 * @author AI Assistant
 * @date 2025
 */

#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>

/* ==================== 模拟 ECU 内部状态 ==================== */
typedef enum {
    SESSION_DEFAULT = 0x01,
    SESSION_EXTENDED = 0x03,
    SESSION_PROGRAMMING = 0x02
} DiagSessionType;

typedef enum {
    SEC_LEVEL_NONE = 0,
    SEC_LEVEL_TYPE_III = 3,
    SEC_LEVEL_TYPE_V = 5
} SecurityLevelType;

static DiagSessionType g_currentSession = SESSION_DEFAULT;
static SecurityLevelType g_currentSecurityLevel = SEC_LEVEL_NONE;
static int g_vehicleSpeed = 0;   // km/h

/* ==================== 静态配置 ==================== */
static const uint8_t g_supportedServices[] = {0x10, 0x27, 0x22, 0x2E, 0x31, 0x14, 0x19};
#define NUM_SUPPORTED_SERVICES (sizeof(g_supportedServices)/sizeof(g_supportedServices[0]))

static const uint8_t g_supportedSubfuncs_0x31[] = {0x01, 0x02, 0x03};
#define NUM_SUPPORTED_SUBFUNCS (sizeof(g_supportedSubfuncs_0x31)/sizeof(g_supportedSubfuncs_0x31[0]))

static const DiagSessionType g_allowedSessions_0x31[] = {SESSION_EXTENDED, SESSION_PROGRAMMING};
#define NUM_ALLOWED_SESSIONS (sizeof(g_allowedSessions_0x31)/sizeof(g_allowedSessions_0x31[0]))

#define REQUIRED_SECURITY_LEVEL_0x31 SEC_LEVEL_TYPE_III
#define ENV_CONDITION_SPEED_MAX 5   // 车速必须 < 5 km/h

/* ==================== 辅助函数 ==================== */
static bool isServiceSupported(uint8_t sid) {
    for (int i = 0; i < NUM_SUPPORTED_SERVICES; i++) {
        if (g_supportedServices[i] == sid) return true;
    }
    return false;
}

static bool isSubfuncSupported(uint8_t subfunc) {
    for (int i = 0; i < NUM_SUPPORTED_SUBFUNCS; i++) {
        if (g_supportedSubfuncs_0x31[i] == subfunc) return true;
    }
    return false;
}

static bool isSessionAllowed(DiagSessionType session) {
    for (int i = 0; i < NUM_ALLOWED_SESSIONS; i++) {
        if (g_allowedSessions_0x31[i] == session) return true;
    }
    return false;
}

static bool isSecurityLevelEnough(SecurityLevelType required) {
    return (g_currentSecurityLevel >= required);
}

static bool checkEnvironmentConditions(void) {
    return (g_vehicleSpeed < ENV_CONDITION_SPEED_MAX);
}

/* 模拟服务逻辑($31 例程控制)*/
static uint8_t execute_routine_control(uint8_t subfunc, uint16_t routineId) {
    // 模拟有效例程 ID 0x1001,其他无效
    if (routineId == 0x1001) {
        // 模拟执行成功
        return 0x00; // 0x00 表示成功(非 NRC)
    } else {
        return 0x31; // requestOutOfRange
    }
}

/* ==================== 核心处理函数 ==================== */

/**
 * @brief 处理诊断请求,遵循 ISO 14229-1 否定响应优先级顺序
 * @param req 请求报文(至少1字节服务ID)
 * @param reqLen 请求长度
 * @param resp 输出响应缓冲区
 * @param respLen 输出响应长度
 */
void uds_process_request(const uint8_t* req, uint32_t reqLen, uint8_t* resp, uint32_t* respLen) {
    if (reqLen < 1) {
        // 空请求,无法处理
        resp[0] = 0x7F; resp[1] = 0x00; resp[2] = 0x11; *respLen = 3;
        return;
    }

    uint8_t sid = req[0];
    printf("\n=== Processing request: SID=0x%02X ===\n", sid);

    // 第1步:服务ID支持?
    if (!isServiceSupported(sid)) {
        printf("[Step1] Service not supported -> NRC 0x11\n");
        resp[0] = 0x7F; resp[1] = sid; resp[2] = 0x11; *respLen = 3;
        return;
    }
    printf("[Step1] Service supported.\n");

    // 仅对 $31 进行子功能检查
    uint8_t subfunc = 0;
    uint16_t routineId = 0;
    if (sid == 0x31) {
        if (reqLen < 2) {
            printf("[Step2] Request too short for subfunc -> NRC 0x13\n");
            resp[0] = 0x7F; resp[1] = sid; resp[2] = 0x13; *respLen = 3;
            return;
        }
        subfunc = req[1];
        // 第2步:子功能支持?
        if (!isSubfuncSupported(subfunc)) {
            printf("[Step2] Subfunc 0x%02X not supported -> NRC 0x12\n", subfunc);
            resp[0] = 0x7F; resp[1] = sid; resp[2] = 0x12; *respLen = 3;
            return;
        }
        printf("[Step2] Subfunc supported.\n");

        // 第3步:长度检查($31 最小长度 4 字节:SID+subfunc+2字节例程ID)
        if (reqLen < 4) {
            printf("[Step3] Message length too short (expected >=4) -> NRC 0x13\n");
            resp[0] = 0x7F; resp[1] = sid; resp[2] = 0x13; *respLen = 3;
            return;
        }
        printf("[Step3] Message length OK.\n");
        routineId = (req[2] << 8) | req[3];
    } else {
        // 其他服务简化长度检查(至少1字节)
        if (reqLen < 1) {
            printf("[Step3] Message length error -> NRC 0x13\n");
            resp[0] = 0x7F; resp[1] = sid; resp[2] = 0x13; *respLen = 3;
            return;
        }
        printf("[Step3] Message length OK.\n");
    }

    // 第4步:诊断会话检查
    if (!isSessionAllowed(g_currentSession)) {
        printf("[Step4] Current session 0x%02X not allowed -> NRC 0x22\n", g_currentSession);
        resp[0] = 0x7F; resp[1] = sid; resp[2] = 0x22; *respLen = 3;
        return;
    }
    printf("[Step4] Session allowed.\n");

    // 第5步:安全访问级别检查
    if (!isSecurityLevelEnough(REQUIRED_SECURITY_LEVEL_0x31)) {
        printf("[Step5] Security level insufficient (current=%d, required=%d) -> NRC 0x33\n",
               g_currentSecurityLevel, REQUIRED_SECURITY_LEVEL_0x31);
        resp[0] = 0x7F; resp[1] = sid; resp[2] = 0x33; *respLen = 3;
        return;
    }
    printf("[Step5] Security level sufficient.\n");

    // 第6步:环境条件检查
    if (!checkEnvironmentConditions()) {
        printf("[Step6] Environment condition failed (speed=%d >=%d) -> NRC 0x22\n",
               g_vehicleSpeed, ENV_CONDITION_SPEED_MAX);
        resp[0] = 0x7F; resp[1] = sid; resp[2] = 0x22; *respLen = 3;
        return;
    }
    printf("[Step6] Environment conditions satisfied.\n");

    // 第7步:执行服务逻辑
    if (sid == 0x31) {
        uint8_t userNrc = execute_routine_control(subfunc, routineId);
        if (userNrc != 0x00) {
            printf("[Step7] Service logic failed -> NRC 0x%02X\n", userNrc);
            resp[0] = 0x7F; resp[1] = sid; resp[2] = userNrc; *respLen = 3;
            return;
        }
        // 肯定响应构造(简化)
        resp[0] = sid + 0x40; // 0x71
        resp[1] = subfunc;
        resp[2] = 0x00; // 例程状态
        *respLen = 3;
        printf("[Step7] Service logic success -> Positive response\n");
    } else {
        // 其他服务肯定响应简化
        resp[0] = sid + 0x40;
        *respLen = 1;
        printf("[Step7] Positive response (generic)\n");
    }
}

/* ==================== 辅助函数 ==================== */
void set_session(DiagSessionType session) {
    g_currentSession = session;
    printf("Session changed to 0x%02X\n", session);
}

void set_security_level(SecurityLevelType level) {
    g_currentSecurityLevel = level;
    printf("Security level changed to %d\n", level);
}

void set_vehicle_speed(int speed) {
    g_vehicleSpeed = speed;
    printf("Vehicle speed set to %d km/h\n", speed);
}

void print_response(const uint8_t* resp, uint32_t len) {
    if (len == 0) return;
    printf("Response: ");
    for (uint32_t i = 0; i < len; i++) printf("%02X ", resp[i]);
    printf("\n");
}

/* ==================== 主程序 ==================== */
int main(void) {
    uint8_t req[10];
    uint8_t resp[10];
    uint32_t respLen;

    printf("========== ISO 14229-1 UDS Negative Response Order Simulation ==========\n");

    // 测试1:正常成功(设置正确状态)
    set_session(SESSION_EXTENDED);
    set_security_level(SEC_LEVEL_TYPE_III);
    set_vehicle_speed(0);
    req[0] = 0x31; req[1] = 0x01; req[2] = 0x10; req[3] = 0x01;
    uds_process_request(req, 4, resp, &respLen);
    print_response(resp, respLen);

    // 测试2:服务不支持
    req[0] = 0x99;
    uds_process_request(req, 1, resp, &respLen);
    print_response(resp, respLen);

    // 测试3:子功能不支持
    req[0] = 0x31; req[1] = 0x04; req[2] = 0x10; req[3] = 0x01;
    uds_process_request(req, 4, resp, &respLen);
    print_response(resp, respLen);

    // 测试4:会话不允许
    set_session(SESSION_DEFAULT);
    set_security_level(SEC_LEVEL_TYPE_III);
    uds_process_request(req, 4, resp, &respLen);
    print_response(resp, respLen);

    // 测试5:安全级别不足
    set_session(SESSION_EXTENDED);
    set_security_level(SEC_LEVEL_NONE);
    uds_process_request(req, 4, resp, &respLen);
    print_response(resp, respLen);

    // 测试6:环境条件不满足
    set_security_level(SEC_LEVEL_TYPE_III);
    set_vehicle_speed(10);
    uds_process_request(req, 4, resp, &respLen);
    print_response(resp, respLen);

    // 测试7:服务逻辑失败(无效例程ID)
    set_vehicle_speed(0);
    req[2] = 0x20; req[3] = 0x00;
    uds_process_request(req, 4, resp, &respLen);
    print_response(resp, respLen);

    return 0;
}

8.3 Makefile 与编译运行

CC = gcc
CFLAGS = -Wall -Wextra -O2 -g
TARGET = uds_simulator
OBJS = iso_uds_sim.o

all: $(TARGET)

$(TARGET): $(OBJS)
	$(CC) -o $@ $^

%.o: %.c
	$(CC) $(CFLAGS) -c $< -o $@

clean:
	rm -f $(OBJS) $(TARGET)

run: $(TARGET)
	./$(TARGET)

.PHONY: all clean run

编译运行

$ make clean && make
$ ./uds_simulator

8.4 运行结果解读

运行后,你会看到每个测试用例的输出,清晰地显示了判定步骤和返回的 NRC。例如,环境条件不满足时会输出 [Step6] Environment condition failed... -> NRC 0x22,而会话不允许时会在 [Step4] 输出 NRC 0x22。虽然码相同,但步骤不同,可以通过日志区分。


9. 常见误区与澄清

  • 误区一:否定响应顺序是 AUTOSAR 发明的。
    事实:根源是 ISO 14229-1:2013,AUTOSAR 只是将其对齐并实现。
  • 误区二0x22 只有一种含义。
    事实:它可能表示会话不允许或环境条件不满足,需要结合上下文区分。
  • 误区三0x78 是失败响应。
    事实0x78 表示“正在处理,请等待”,不是失败。
  • 误区四:服务逻辑中的 NRC 可以随意返回。
    事实:应遵循 UDS 标准,例如 0x31 表示请求超出范围,0x72 表示编程失败等。

10. 总结:顺序即法规

ISO 14229-1:2013 虽然没有用一张大表格列出否定响应优先级,但通过服务处理流程的描述,确立了清晰的判定顺序。这个顺序是所有符合 UDS 标准的 ECU 必须遵守的,也是 AUTOSAR 等规范的基石。

核心优先级顺序(从高到低)

  1. 0x11 – 服务不支持
  2. 0x12 – 子功能不支持
  3. 0x13 – 消息长度错误
  4. 0x22 – 会话不允许(会话检查)
  5. 0x33 – 安全访问拒绝
  6. 0x22 – 环境条件不满足(或其他条件)
  7. 其他业务逻辑 NRC(如 0x31, 0x72 等)

理解了这个顺序,你在诊断开发中就能快速定位问题:收到 7F 31 22 时,你首先问:是会话不对,还是环境条件不满足?再根据当时的诊断会话状态和车辆信号进一步分析。

希望这篇深度解析能帮助你彻底掌握 UDS 否定响应的“审判法则”。当你下次再遇到难以解释的 NRC 时,请对照这个顺序——它往往能给你清晰的指引。


本文基于 ISO 14229-1:2013 标准及 AUTOSAR 相关文档编写。所有代码均为模拟演示,实际开发请参考具体工具链。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐