☰
汽车UDS诊断协议学习笔记:0x10/0x27/0x34等核心服务详解与实战避坑
2026/10/15 10:19:56 网站建设 项目流程

简介:这份PDF版学习笔记面向汽车电子、嵌入式测试及诊断开发方向的初学者与工程师,系统梳理ISO 14229标准下的UDS(统一诊断服务)协议知识,帮助读者建立从服务分类到请求响应机制的整体认知。内容按功能单元组织,涵盖诊断和通信管理(0x10会话控制、0x11复位、0x27安全访问、0x3E保持连接等)、数据传输(0x22、0x2E、0x2C等)、已存储数据传输(0x14、0x19)、输入输出控制、例程控制以及上传下载(0x34~0x37)等常用服务,并说明“问-答”模式下请求与响应的对应关系。资源包为1个PDF文件,大小约10.49MB,便于离线阅读与查阅。目前已有1911人学习,适合作为UDS入门速查与协议框架梳理的参考材料。

1. 从一次刷写失败说起:这份 UDS 笔记到底能帮你省多少时间

去年帮一个朋友排查一台模拟 ECU 刷写失败的问题,现象很典型:诊断仪发 0x34 请求下载,ECU 回了 7F 34 33,SecurityAccess 没通过。他翻了一晚上 ISO 14229 原文,愣是没定位到问题出在 0x27 的 seed-key 配对逻辑上。后来我把手头一份整理好的 UDS 学习笔记 PDF 丢给他,第二天早上他说搞定了——问题出在 27 01 和 27 02 的子功能配对关系搞反了。

这件事让我意识到,UDS 协议本身不算复杂,但服务多、参数碎、NRC 码散落在各个章节里,没有一个系统性的整理,排查效率会非常低。这份《汽车 UDS 诊断协议学习笔记 PDF 版》就是干这个的:它把 ISO 14229 里诊断和通信管理、数据传输、已存储数据传输、输入输出控制、例程控制、上传下载六大功能单元的服务定义、请求/响应格式、子功能参数、NRC 码全部梳理成册,适合做汽车诊断协议开发、ECU 测试、诊断仪固件开发的从业者当案头速查用。不管你是刚接触 UDS 的新手,还是已经写过 CAPL 脚本的老手,这份笔记都能帮你少翻几次标准原文。

2. 诊断会话与安全访问:0x10、0x11、0x27 的请求构造与参数拆解

2.1 0x10 诊断会话控制:所有诊断命令的前置条件

0x10 服务是整个 UDS 诊断的入口。DefaultSession 下很多服务不支持,你必须先切到扩展会话或编程会话才能执行后续操作。请求格式很简单:10 xx,其中 xx 是会话类型。ISO 14229 定义了几个通用值,主机厂也可以自定义保留位。

正响应格式为50 xx yy,其中 xx 是请求子功能的回显,yy 是 sessionParameterRecord,通常包含 P2 和 P2* 时间参数。P2 是服务器收到请求后到发出响应之间的最大时间,P2* 是收到 NRC 0x78(responsePending)后延长的时间窗口。这两个参数直接决定你诊断仪的超时设置,设短了会误判超时,设长了会拖慢自动化测试节奏。

负响应格式固定为7F 10 xx,xx 是 NRC 码。0x10 服务下常见的 NRC 包括 0x12(subFunctionNotSupported)、0x13(incorrectMessageLengthOrInvalidFormat)、0x22(conditionsNotCorrect)。如果你发 10 03 收到 7F 10 12,说明这个 ECU 不支持编程会话,不是通信问题。

注意:从 DefaultSession 切到其他会话后,如果一段时间内没有 TesterPresent(0x3E)保活,ECU 会自动回落到 DefaultSession。这个超时时间一般在 5 秒左右,具体看项目定义。

2.2 0x11 ECU 重置:复位类型与自动化中的坑

0x11 服务用于请求 ECU 执行复位。请求格式11 xx,xx 是 resetType。ISO 定义了多个子功能:0x01 是 hardReset,0x02 是 keyOffOnReset,0x03 是 softReset,0x04 是 enableRapidPowerShutDown,0x05 是 disableRapidPowerShutDown。

正响应格式为51 xx,当子功能为 0x04 时,还会跟一个 powerDownTime 参数,取值范围 00~FE,FF 表示时间不可用。这个参数在自动化测试里容易被忽略——如果你发了 11 04 但没等 powerDownTime 结束就发下一条请求,ECU 可能还没完成下电流程,后续通信直接超时。

关键点:ECU 复位成功后会自动回到 DefaultSession。这意味着你在扩展会话里做了一堆配置,一个 0x11 下去全部清零。自动化脚本里如果 0x11 后面紧跟其他服务请求,必须重新走一遍 0x10 切会话的流程。

2.3 0x27 安全访问:seed-key 配对逻辑与 NRC 35/36/37 的触发条件

0x27 是 UDS 里最容易翻车的服务之一。流程分两步:先请求 seed,再发送 key。子功能参数有个硬性规则——奇数子功能是 requestSeed,偶数子功能是 sendKey。27 01 请求 seed,对应的 key 用 27 02 发送;27 03 请求 seed,对应的 key 用 27 04 发送。配对关系是「相邻且大于 seed 请求的最小偶数」。

请求 seed 的格式:27 01,securityAccessDataRecord 是可选项,一般不赋值。正响应为67 01 [seed...],seed 长度由项目定义,常见 4 字节或 8 字节。

发送 key 的格式:27 02 [key...]。key 是根据 seed 经过 ECU 内部算法计算得到的,seed 变了 key 就变。正响应只有67 02,不带额外数据。

NRC 方面,0x27 服务下有三个需要特别关注的错误码:0x35(invalidKey)、0x36(exceedNumberOfAttempts)、0x37(requiredTimeDelayNotExpired)。典型触发逻辑是:第一次发错误 key 返回 0x35;等待 5 秒后再发错误 key 返回 0x36;再等 5 秒发第三次错误 key 返回 0x37。这个延迟时间和最大尝试次数由主机厂定义,不同项目不一样。

下面是一段 CAPL 脚本中安全解锁的核心逻辑,展示了 seed 获取、key 计算和发送的完整流程:

void Diag_SecurityLevel_1_Unlock() { diagRequest ECU.RequestSeed_Request SeedReq_1; diagRequest ECU.SendKey_Send KeySend_1; const dword SENDING_TIMEOUT = 2000; // 请求发送超时,单位 ms const dword RESPONSE_TIMEROUT = 1500; // 响应等待超时,单位 ms byte seedArray[8]; byte keyArray[8]; dword actualLevel; char variant[12]; char ipOption[2]; dword KeyActualSize; long status; int i; diagSetTarget("ECU"); // 设置诊断目标 ECU 名称 actualLevel = 1; // 安全等级 1 ipOption[0] = 'A'; ipOption[1] = 0; KeyActualSize = 8; // key 长度 8 字节 // 第一步:发送 seed 请求 diagSendRequest(SeedReq_1); if (testWaitForDiagRequestSent(SeedReq_1, SENDING_TIMEOUT) == 1) { testStepPass("000", "Request Seed Successfully"); } else { testStepFail("000", "Request send failed"); } // 第二步:等待响应并提取 seed if (testWaitForDiagResponse(SeedReq_1, RESPONSE_TIMEROUT) == 1) { status = diagGetLastResponseCode(SeedReq_1); if (status != -1) { testStepFail("000", "Seed request failed"); } // 从响应 byte 2 开始提取 seed,byte 0/1 是 SID 和子功能 for (i = 0; i < elCount(seedArray); i++) { seedArray[i] = DiagGetRespPrimitiveByte(SeedReq_1, i + 2); } } // 第三步:通过 DLL 计算 key status = diagGenerateKeyFromSeed(seedArray, elCount(seedArray), actualLevel, variant, ipOption, keyArray, elCount(keyArray), KeyActualSize); if (status != 0) { testStepFail("000", "diagGenerateKeyFromSeed failed"); } else { testStepPass("000", "diagGenerateKeyFromSeed success"); } // 第四步:将 key 写入请求参数并发送 diagSetParameterRaw(KeySend_1, "SecurityKey", keyArray, elCount(keyArray)); diagSendRequest(KeySend_1); }

这段脚本里几个参数需要根据项目实际调整:SENDING_TIMEOUT和RESPONSE_TIMEROUT要匹配 ECU 的 P2/P2* 时间;actualLevel对应安全等级,不同等级 seed-key 算法可能不同;variant和ipOption是diagGenerateKeyFromSeed的入参,variant 是 ECU 名称,ipOption 从diagGetCurrentEcu获取。最容易出错的是diagSetParameterRaw里的参数名"SecurityKey",这个名字必须和 CDD 文件里定义的一致,写错了请求发出去的数据就不是你算出来的 key。

3. 数据传输与故障码读取:0x22、0x2E、0x19、0x14 的实操要点

3.1 0x22 按标识符读数据:DID 寻址与多 DID 批量读取

0x22 是日常诊断中用得最多的服务。请求格式22 [DID_H] [DID_L],DID 是 2 字节的数据标识符。正响应为62 [DID_H] [DID_L] [data...]。一个请求可以带多个 DID,ECU 会按顺序返回所有 DID 的数据。

DID 的分配有标准化的部分,比如 0xF186 是 ActiveDiagnosticSession,0xF187 是 VehicleManufacturerSparePartNumber,0xF190 是 VIN 码。主机厂自定义的 DID 一般从 0xFD00 以上开始分配。读 VIN 就是发22 F1 90,返回62 F1 90 [17 字节 VIN]。

实操中容易踩的坑是 DID 长度不匹配。有些 ECU 对某个 DID 返回的数据长度是固定的,你按最大长度去解析会多读垃圾数据。建议在 CDD 或 ODX 文件里确认每个 DID 的实际长度,别靠猜。

3.2 0x2E 按标识符写数据:写入前的会话与安全条件

0x2E 用于向 ECU 写入数据。请求格式2E [DID_H] [DID_L] [data...],正响应6E [DID_H] [DID_L]。写操作通常需要满足两个前提:处于非 DefaultSession,且通过了对应安全等级的 SecurityAccess。

常见 NRC 包括 0x13(长度不对)、0x22(条件不满足,比如没切会话)、0x31(请求超出范围)、0x33(安全访问被拒)。如果你发 2E 收到 7F 2E 33,说明安全访问没做或者等级不够。

写入的数据长度必须和 DID 定义的长度严格一致。比如某个 DID 定义是 4 字节,你发 5 字节数据,ECU 直接回 0x13。这个在自动化脚本里可以用 CDD 文件自动校验,别手动拼。

3.3 0x19 读 DTC 与 0x14 清除 DTC:子功能选择与状态掩码

0x19 服务用于读取故障码信息,子功能很多。最常用的是 0x02(reportDTCByStatusMask),请求格式19 02 [statusMask],statusMask 是 1 字节的状态掩码,用于过滤特定状态的 DTC。正响应格式为59 02 [statusAvailabilityMask] [DTC_H] [DTC_L] [status] ...,每个 DTC 占 4 字节:3 字节 DTC 编号加 1 字节状态。

状态字节的 8 个 bit 分别代表 testFailed、confirmedDTC、pendingDTC、testNotCompletedSinceLastClear 等。如果你只关心已确认的故障码,statusMask 设为 0x08。

0x14 清除诊断信息,请求格式14 [groupOfDTC_H] [groupOfDTC_M] [groupOfDTC_L]。groupOfDTC 设为 0xFFFFFF 表示清除所有组。正响应54。清除后 DTC 状态位会复位,但有些 ECU 需要几秒到几十秒才能完成清除,清除后立刻读可能还能读到残留状态。

注意:0x14 清除操作通常需要安全访问解锁,且只能在扩展会话下执行。DefaultSession 下发 14 会直接收到 7F 14 22。

4. 上传下载与例程控制:0x34/0x36/0x37 刷写流程与 0x31 例程调用

4.1 0x34 请求下载:内存地址与数据格式的协商

0x34 是刷写流程的起点。请求格式为34 [dataFormatIdentifier] [addressAndLengthFormatIdentifier] [memoryAddress...] [memorySize...]。

dataFormatIdentifier 指定压缩和加密方式,0x00 表示无压缩无加密,0x10 表示无压缩有加密,0x01 表示有压缩无加密。addressAndLengthFormatIdentifier 的高 4 位表示 memorySize 占几个字节,低 4 位表示 memoryAddress 占几个字节。比如 0x24 表示地址 4 字节、长度 2 字节。

正响应74 [lengthFormatIdentifier] [maxNumberOfBlockLength...]。maxNumberOfBlockLength 告诉诊断仪每次 TransferData 最多能发多少字节。这个值直接决定你的分块策略,设小了刷写慢,设大了 ECU 缓冲区可能溢出。

4.2 0x36 传输数据与 0x37 退出传输:块序号与超时处理

0x36 请求格式36 [blockSequenceCounter] [data...]。blockSequenceCounter 从 0x01 开始,每发一包递增,到 0xFF 后回绕到 0x00。ECU 靠这个序号检测丢包和重复包。正响应76 [blockSequenceCounter]。

0x37 请求格式37,无参数。正响应77。发送 0x37 之前必须确保所有数据块都已成功传输,否则 ECU 可能回 0x24(requestSequenceError)。

刷写过程中最常见的翻车场景是 0x36 超时。ECU 在接收数据块时需要写入 Flash,这个时间可能超过默认的 P2 超时。ECU 会先回 NRC 0x78(responsePending),诊断仪收到 0x78 后必须把超时延长到 P2*。如果你的诊断仪不支持 0x78 处理,刷写必挂。

4.3 0x31 例程控制:startRoutine、stopRoutine 与 requestRoutineResults

0x31 服务用于控制 ECU 内部的例程执行。请求格式31 [subFunction] [routineIdentifier_H] [routineIdentifier_L] [routineControlOptionRecord...]。子功能有三个:0x01 启动例程,0x02 停止例程,0x03 请求例程结果。

正响应71 [subFunction] [routineIdentifier_H] [routineIdentifier_L] [routineStatusRecord...]。routineStatusRecord 的内容由具体例程定义,常见的是 1 字节状态码。

刷写流程中,0x31 常用于擦除 Flash、校验编程依赖、检查编程前提条件。比如发31 01 FF 00执行擦除例程,ECU 返回71 01 FF 00 00表示擦除成功。如果返回71 01 FF 00 01,说明擦除失败,后续 0x34 请求下载会直接收到 NRC 0x22。

例程控制的一个关键点是:routineControlOptionRecord 是可选的,但有些例程需要传入参数(比如擦除地址范围)。这个必须查 CDD 或 ODX 文件确认,不能凭经验猜。

5. 避坑与排查:UDS 诊断中五个高频翻车场景

5.1 现象:发 0x22 读 DID 返回 7F 22 31

原因:requestOutOfRange。DID 不存在,或者当前会话不支持该 DID。有些 DID 只在扩展会话下可读,DefaultSession 下发直接拒绝。

解决:先确认 DID 是否在 CDD 文件里定义,再确认当前会话模式。如果是会话问题,先发 10 03 切扩展会话再读。

5.2 现象:0x27 安全访问第一次就返回 7F 27 36

原因:exceedNumberOfAttempts。ECU 内部有失败计数器,之前的错误尝试还没被清除。这个计数器通常掉电不清零,或者需要等待延迟时间到期。

解决:等待延迟时间(一般 10 秒)后重试。如果还是 0x36,可能需要给 ECU 断电重启,或者用特定例程复位失败计数器。

5.3 现象:0x36 传输数据过程中突然收到 7F 36 73

原因:wrongBlockSequenceCounter。blockSequenceCounter 不连续,ECU 检测到丢包或序号跳变。

解决:检查发送端的计数器逻辑,确保从 0x01 开始且每包递增。如果中间有重发,重发时序号必须和原包一致,不能递增。

5.4 现象:0x11 ECU 复位后后续请求全部超时

原因:ECU 复位需要时间,期间不响应任何诊断请求。如果你在复位后立刻发下一条请求,物理层可能还没初始化完成。

解决:0x11 正响应收到后,等待至少 2 秒再发下一条请求。更稳妥的做法是循环发 3E 00 直到收到响应,确认 ECU 恢复通信。

5.5 现象:0x19 读 DTC 返回的 DTC 数量比预期少

原因:statusMask 设置过严,过滤掉了部分 DTC。或者 ECU 的 DTC 存储区已满,新 DTC 覆盖了旧 DTC。

解决:先用 statusMask 0xFF 读全部 DTC,确认总数。如果确实少了,检查 ECU 的 DTC 存储容量和覆盖策略。有些 ECU 在 DTC 数量达到上限后会停止记录新故障。

6. 从笔记到实战:用 Python 脚本验证 UDS 服务响应的技巧

笔记看完了,怎么验证自己真的理解了?我的习惯是写一个小脚本,用 python-udsoncan 库模拟诊断仪,对模拟 ECU 发几个典型服务请求,看响应是否符合预期。下面这段代码演示了 0x10、0x22、0x27 三个服务的串联调用:

import udsoncan from udsoncan.connections import IsoTPSocketConnection from udsoncan.client import Client from udsoncan.services import DiagnosticSessionControl, ReadDataByIdentifier, SecurityAccess # 配置连接参数,根据实际项目调整 config = dict(udsoncan.configs.default_client_config) config['p2_timeout'] = 5 # P2 超时,单位秒 config['p2_star_timeout'] = 10 # P2* 超时,收到 0x78 后生效 config['security_algo'] = None # 安全算法由外部 DLL 或自定义函数提供 conn = IsoTPSocketConnection('127.0.0.1', 13400) # DoIP 连接示例 with Client(conn, config=config) as client: # 第一步:切换到扩展会话 resp = client.change_session(DiagnosticSessionControl.Session.extendedDiagnosticSession) print(f"会话切换响应: {resp.service_data.session_echo}") # 第二步:读取 VIN 码(DID 0xF190) vin_resp = client.read_data_by_identifier(0xF190) print(f"VIN: {vin_resp.service_data.values[0xF190].decode('ascii')}") # 第三步:安全访问(需要自定义算法函数) # seed = client.request_seed(0x01) # key = calculate_key(seed) # 替换为实际算法 # client.send_key(0x02, key)

这段代码的关键参数是p2_timeout和p2_star_timeout,必须和 ECU 实际定义一致。security_algo需要替换成项目对应的 seed-key 算法,通常是一个 DLL 或 Python 函数。DoIP 连接地址和端口也要按实际项目改。

验证的时候按这个顺序走:先确认 0x10 能切会话,再确认 0x22 能读到已知 DID,最后测 0x27 的 seed-key 流程。每一步的响应都打印出来,和笔记里的格式定义对照。如果某一步返回 NRC,直接查笔记里对应服务的 NRC 列表,定位速度比翻标准原文快得多。

从那以后我每次拿到新的 ECU 或者新的诊断需求,都会先跑一遍这个三步验证流程,确认基础通信和安全访问没问题,再往下做刷写或标定。这个习惯帮我省了很多「以为是协议问题、其实是会话没切」的无效排查时间。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询