ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

UDS诊断服务0x2E按标识符写数据:测试用例设计与NRC排查实战

UDS诊断服务0x2E按标识符写数据:测试用例设计与NRC排查实战 做车载诊断测试这几年0x2E 服务WriteDataByIdentifier按标识符写数据是用得最多、也是最容易踩坑的诊断服务之一。很多测试同学拿到需求文档后第一反应是“这不就是往 DID 里写个值吗”结果一写用例就发现漏了安全解锁、条件约束、数据长度校验、NRC 优先级这些关键点。这篇文章从协议基础开始结合需求文档拆解 0x2E 服务的用例设计方法给出完整的测试用例模板、自动化脚本思路和常见问题排查清单。不管是刚入行的诊断测试工程师还是负责 UDS 协议栈开发的同学都可以直接参考。1. 0x2E 服务是什么为什么用例难设计1.1 从“写数据”这个动作说起0x2E 服务是 UDSUnified Diagnostic Services统一诊断服务协议中用于写入数据的服务。它允许诊断仪向 ECU电子控制单元的某个 DIDData Identifier数据标识符写入一段数据。通俗点说0x22 服务是“读数据”从 ECU 里把数据读出来。0x2E 服务是“写数据”把数据写进 ECU 里。0x2F 服务是“按地址写数据”通过内存地址直接写入。0x2E 和 0x2F 看起来都和数据写入相关但使用场景完全不同。0x2E 面向的是逻辑数据标识符比如“车辆识别码 VIN”“软件版本号”“配置字”0x2F 面向的是物理内存地址通常用于 Bootloader 或底层标定。在诊断测试中0x2E 服务常用于写入 VIN、生产日期等车辆身份信息。写入配置参数如车型代码、变速箱类型。写入标定值或学习值如转向角传感器零点。写入测试模式开关如生产线末端的 EOLEnd of Line配置。1.2 为什么 0x2E 的用例容易漏0x2E 不是简单的“下发数据 - 收到响应”就结束了。它背后涉及一整套安全机制和条件约束安全访问大多数可写 DID 都受安全等级保护必须先通过 0x27 服务解锁。会话切换部分写入操作只能在扩展会话或编程会话下执行。条件校验车辆状态、电源模式、车速等条件不满足时ECU 会拒绝写入。数据校验DID 数据长度、数据范围、校验和、格式必须符合定义。写入策略有些 DID 支持“立即写”有些需要先写入 RAM 再执行“存储”操作。总线时序写入过程中总线掉线、会话超时、重复请求都会影响结果。所以0x2E 的用例设计难点不在于“怎么写正例”而在于把需求文档里隐含的约束条件一条条挖出来转成可执行的测试用例。2. 0x2E 服务协议细节拆解2.1 请求与响应帧格式0x2E 服务的请求格式如下字节名称值说明Byte 0SID0x2E服务标识符Byte 1-2DID0xXXXX数据标识符如 0xF190 表示 VINByte 3...NdataRecord变长要写入的数据长度由 DID 定义正响应格式字节名称值说明Byte 0SID0x400x6E正响应服务标识符Byte 1-2DID0xXXXX与请求中的 DID 一致负响应格式字节名称值说明Byte 00x7F0x7F负响应服务标识符Byte 1SID0x2E原服务标识符Byte 2NRC0xXX负响应码一个最简单的 CAN 报文示例发送CAN ID 0x7E0: 02 2E F1 90 31 32 33 34 35 36 37 38 39 30 31 32 33 34 35 36 37 接收CAN ID 0x7E8: 04 6E F1 90 00 00 00 00这里0x02是 CAN 帧里第一个字节表示后续还有 2 个字节因为采用 ISO-TP 单帧传输时第一个字节的高四位表示帧类型低四位表示后续数据长度。2E是 SIDF1 90是 VIN 的 DID后面的 ASCII 字符就是 VIN 内容。如果 ECU 拒绝写入负响应示例发送CAN ID 0x7E0: 02 2E F1 90 31 32 33 34 35 36 37 38 39 30 31 32 33 34 35 36 37 接收CAN ID 0x7E8: 03 7F 2E 33这里0x33表示安全访问被拒绝SecurityAccess Denied说明没有先执行解锁操作。2.2 常见 NRC 及含义NRC名称含义是否常见0x13IncorrectMessageLengthOrInvalidFormat报文长度错误或格式无效高频0x14ResponseTooLong响应太长极少0x22ConditionsNotCorrect条件不满足高频0x24RequestSequenceError请求序列错误中频0x31RequestOutOfRange请求超出范围DID 不存在或数据值非法高频0x33SecurityAccessDenied安全访问被拒绝高频0x36ExceededNumberOfAttempts超过尝试次数低频与 0x27 配合0x72GeneralProgrammingFailure一般编程失败中频0x73WrongBlockSequenceCounter块序列计数器错误极少与 0x36 配合不同 ECU 对 NRC 的优先级处理可能不同比如“未解锁 DID 不存在”时有的 ECU 返回 0x31有的返回 0x33。这种差异正是用例设计时要重点验证的点。2.3 与 0x22、0x2F 的边界区分用例设计之前要明确 0x2E 与相邻服务的边界避免把“读”的用例和“写”的用例混在一起0x22读取 DID 数据不影响 ECU 存储内容。0x2E写入 DID 数据可能立即生效也可能需要重启或执行存储操作后生效。0x2F通过内存地址写入不关注 DID 概念通常用于底层开发。实际测试过程中0x2E 写入后通常会用 0x22 回读来验证写入结果。这个“写入后回读”的习惯是诊断测试的基本操作也是用例设计中不可缺少的一环。3. 根据需求设计 0x2E 用例的准备工作很多测试同学拿到 0x2E 相关的需求文档第一反应是“直接照着 DID 列表写正反例”结果写完发现漏了安全条件、漏了时序测试。正确步骤是先做需求拆分再提取测试要点最后设计用例。3.1 拆解需求文档中的 DID 属性一份合格的诊断需求文档针对每个可写 DID 至少会给出这些信息属性说明示例DID16 位十六进制标识0xF190数据名称功能含义车辆识别码 VIN数据长度固定长度或变长17 字节数据类型ASCII / 数值 / 位映射ASCII取值范围合法数据范围0x00 - 0x0F读写属性只读 / 只写 / 读写读写安全等级是否需要解锁需要安全等级 1会话要求默认会话 / 扩展会话 / 编程会话扩展会话条件约束车速、挡位、电源状态等车速为 0电源 ON写入生效方式立即生效 / 重启生效 / 需存储指令立即生效写入次数限制是否有次数限制最多 5 次错误码定义边界值对应 NRC超范围返回 0x31把需求文档里的 DID 属性整理成上面的表格是设计用例前的第一步。不要跳过这一步因为很多需求文档的信息是分散的不整理容易漏。3.2 提取隐含条件与约束除了 DID 本身的信息还需要从整车级需求中提取隐含条件安全解锁流程解锁后是否有时间窗口在窗口内才能写。会话状态切换写入后是否需要切回默认会话写入时序是否要求连续写多个 DID中间是否有间隔要求条件状态机比如 EOL 模式下可写下线后不可写。重复写入策略重复写同一 DID 是否允许是否需要先擦除写失败恢复写失败后数据是保持原值还是变成无效值举个实际例子某 ECU 的“转向角传感器零点标定”DID要求车速为 0、方向盘在中间位置、电源电压在 9V 到 16V 之间。如果你只看 DID 属性表可能会漏掉“方向盘在中间位置”这个条件。而恰恰是这个条件不满足时ECU 会返回 0x22。3.3 确定用例设计方法0x2E 服务用例推荐组合以下方法方法适用场景等价类划分合法数据范围、非法数据范围边界值分析数据最小/最大长度、数值上下界状态转换法默认会话/扩展会话/编程会话切换场景法生产线 EOL 配置、售后维修写入错误推断法未解锁写入、重复写入、总线中断4. 0x2E 服务用例设计完整实战下面以“VIN 写入”和“配置字写入”两个典型 DID 为例演示完整用例设计过程。示例需求如下属性VIN0xF190配置字0xF1A0长度17 字节 ASCII2 字节十六进制取值范围合法 VIN 字符0x0000 - 0xFFFF安全等级需要解锁需要解锁会话扩展会话默认会话写入次数最多 10 次无限制生效方式重启后生效立即生效条件约束车速为 0电源 ON4.1 功能正例设计功能正例的核心目标是验证“在合法条件下写入合法数据ECU 返回正响应”。用例编号测试步骤预期结果TC_0x2E_001进入扩展会话完成安全解锁发送 17 字节合法 VIN返回 0x6E F1 90TC_0x2E_002用 0x22 回读 VIN读到的 VIN 与写入一致TC_0x2E_003重启 ECU再次回读 VINVIN 保持不变说明已持久化TC_0x2E_004写入配置字 0x1234返回 0x6E F1 A0TC_0x2E_005回读配置字读回 0x1234正例用例要注意一点写入后必须回读验证不能只看响应帧。有的 ECU 正响应正常但数据没有真正写入 Flash重启后数据丢失。回读和重启验证是 0x2E 正例的必备步骤。4.2 功能反例设计反例用于验证异常输入时的 NRC 响应是否符合需求。用例编号测试步骤预期结果TC_0x2E_101未解锁直接写 VIN返回 0x7F 2E 33TC_0x2E_102写不存在的 DID如 0xF1FF返回 0x7F 2E 31TC_0x2E_103VIN 数据长度只有 10 字节返回 0x7F 2E 13TC_0x2E_104VIN 数据长度 18 字节返回 0x7F 2E 13TC_0x2E_105写入非法 VIN 字符如中文、特殊符号返回 0x7F 2E 31TC_0x2E_106配置字写入 0x1FFFF超出范围返回 0x7F 2E 31TC_0x2E_107默认会话下写 VIN返回 0x7F 2E 22TC_0x2E_108车速不为 0 时写 VIN返回 0x7F 2E 22反例设计有一个容易忽略的点NRC 的优先级。比如“未解锁 DID 不存在”时不同 ECU 返回的 NRC 可能不同。需求文档如果没写清楚测试用例就要把这种组合场景列出来实际测试时记录 ECU 的真实行为再和开发确认是否符合规范。4.3 边界值用例设计边界值用例是 0x2E 测试的重头戏因为很多隐藏 bug 都出在长度边界和数值边界上。用例编号测试步骤预期结果TC_0x2E_201VIN 写入 17 字节全 0x20空格返回正响应回读为 17 个空格TC_0x2E_202VIN 写入 17 字节全 0x7Az返回正响应TC_0x2E_203配置字写入 0x0000返回正响应TC_0x2E_204配置字写入 0xFFFF返回正响应TC_0x2E_205配置字写入 0x00FF返回正响应TC_0x2E_206VIN 数据最后少 1 字节16 字节返回 0x7F 2E 13TC_0x2E_207VIN 数据多 1 字节18 字节返回 0x7F 2E 13TC_0x2E_208配置字写入 0xFF 00高字节合法低字节越界返回 0x7F 2E 31边界值用例这里要特别说明一下 0x13 和 0x31 的区别长度不对返回 0x13长度正确但值不在范围内返回 0x31。有的同学会把“长度为 10 字节的 VIN”写成预期 0x31这是不对的长度错误按协议应返回 0x13。4.4 时序与状态转换用例诊断测试最容易出错的地方就是时序。0x2E 的时序用例要覆盖这些场景用例编号测试步骤预期结果TC_0x2E_301完成解锁后等待超过超时时间再写 VIN返回 0x7F 2E 33TC_0x2E_302解锁后在超时时间内写 VIN返回正响应TC_0x2E_303扩展会话下写 VIN然后切回默认会话写入成功数据保持TC_0x2E_304重复写 VIN 超过 10 次第 11 次返回 0x7F 2E 72TC_0x2E_305写入过程中 ECU 复位ECU 重启后VIN 保持原值TC_0x2E_306先写配置字再写 VIN然后重启两个数据都生效TC_0x2E_307合法写入后立即发送下一条写入请求连续两次写入均返回正响应时序用例中最常见的失败场景是超时窗口。有的 ECU 解锁后允许写入的时间窗口只有 5 秒测试脚本如果解锁后没有及时发送 0x2E 请求就会出现偶发性的 0x33。这种问题在手工测试时很难复现但在自动化测试中很容易暴露。4.5 安全与并发用例安全用例重点关注写入操作与安全机制的交互用例编号测试步骤预期结果TC_0x2E_401安全等级 1 解锁后写安全等级 2 的 DID返回 0x7F 2E 33TC_0x2E_402安全等级 2 解锁后写安全等级 1 的 DID返回正响应TC_0x2E_403错误解锁 3 次后写 VIN返回 0x7F 2E 36TC_0x2E_404解锁后切换到默认会话再写 VIN返回 0x7F 2E 33TC_0x2E_405学习值写入与 0x22 回读并发执行回读结果稳定无总线错误安全用例这里有一个概念需要区分0x27 服务返回正响应表示解锁成功但这个成功可能只是“当前诊断会话内有效”。一旦切换会话安全状态会复位。所以“解锁后切会话再写”这个用例预期结果是 0x33而不是正响应。5. 0x2E 服务自动化测试脚本示例实际项目里0x2E 服务用例通常通过 CAPL、Python配合 CAN 盒或 vTestStudio 来执行。这里给出两个最常用的脚本示例。5.1 CAPL 脚本示例CANoe 环境CAPL 是 CANoe 的脚本语言常用于诊断测试。下面是一个简单的 0x2E 写入 VIN 的示例脚本/* CAPL 脚本0x2E 写入 VIN 示例 */ variables { // 诊断报文的 TX/RX CAN ID const int DIAG_REQ_ID 0x7E0; const int DIAG_RES_ID 0x7E8; // 请求数组 byte reqData[24]; byte resData[24]; int resLen; } void WriteVIN() { int i; // 构造 0x2E 请求SID DID(F1 90) 17 字节 VIN reqData[0] 0x2E; reqData[1] 0xF1; reqData[2] 0x90; // VIN 内容测试用 VIN LSVAM4187C2188888 reqData[3] L; reqData[4] S; reqData[5] V; reqData[6] A; reqData[7] M; reqData[8] 4; reqData[9] 1; reqData[10] 8; reqData[11] 7; reqData[12] C; reqData[13] 2; reqData[14] 1; reqData[15] 8; reqData[16] 8; reqData[17] 8; reqData[18] 8; reqData[19] 8; // 发送诊断请求 DiagRequestRaw(DIAG_REQ_ID, reqData, 20); resLen DiagGetResponseRaw(DIAG_RES_ID, resData, 24); // 验证响应 if (resLen 3 resData[0] 0x6E) { write(0x2E Write VIN: Positive Response); } else { write(0x2E Write VIN: Negative Response NRC0x%02X, resData[2]); } }这个脚本是一个核心片段实际使用时需要根据你的 CANoe 工程里的诊断层 API 调整。关键点是把 VIN 的 ASCII 码逐字节填入请求数组然后通过诊断请求函数发送。5.2 Python 脚本示例CAN 盒调用如果用 Python 配合 CAN 盒如 PCAN、CANoe 的 Python 接口核心代码如下# 0x2E 写入 VIN 示例 import can def write_did(bus, vid, did, data): 发送 0x2E 服务请求 :param bus: CAN 总线实例 :param vid: 诊断请求 CAN ID :param did: 数据标识符如 0xF190 :param data: 要写入的数据bytes 类型 # 使用 python-can 的 send 方法发送原始 CAN 帧 # 这里假设 ISO-TP 层已经由上层处理 req bytes([0x2E, (did 8) 0xFF, did 0xFF]) data msg can.Message(arbitration_idvid, datareq, is_extended_idFalse) bus.send(msg) # 使用示例 if __name__ __main__: bus can.interface.Bus(channelPCAN_USBBUS1, bustypepcan) vin bLSVAM4187C2188888 write_did(bus, 0x7E0, 0xF190, vin)这个示例简化了 ISO-TP 层实际工程中需要用isotp库来分包和组包。Python 的好处是方便和测试报告、Excel 用例集集成适合批量跑回归用例。5.3 自动化用例集的组织建议实际项目中0x2E 的自动化用例应该直接对应手工用例编号一个用例一个脚本函数。推荐结构如下test_0x2e/ ├── common/ │ ├── security.py # 安全解锁封装 │ ├── session.py # 会话切换封装 │ └── bus.py # CAN 总线封装 ├── test_vin_write.py # VIN 写入用例集 ├── test_config_write.py # 配置字写入用例集 └── test_0x2e_negative.py # 反例用例集用例函数内部通过assert或测试框架的断言机制来校验响应 NRC用例执行完毕后生成 HTML/Excel 测试报告。这样可以做到手工用例和自动化用例一一对应可追溯性好。6. 常见问题与排查思路6.1 写入返回 0x33可能原因排查步骤解决思路未进行安全解锁检查是否发送 0x27 服务并收到正响应按安全等级解锁后再发送 0x2E解锁超时检查解锁到写入的时间间隔缩短时序在超时窗口内发送切换了会话检查是否从扩展会话切到了默认会话保持解锁会话不变安全等级不够检查 DID 要求的安全等级确认所用密钥对应的等级0x33 是 0x2E 测试中最常见的 NRC。遇到这个问题先别急着查 0x2E 代码优先查测试脚本里 0x27 的解锁时序以及解锁后是否发生了会话切换。6.2 写入返回 0x22可能原因排查步骤解决思路条件不满足检查车速、挡位、电源状态调整车辆状态到需求条件会话错误确认当前会话是否为需求要求的会话切换正确会话DID 写入使能未开启确认 ECU 是否允许当前状态下写入检查 EOL 或生产模式标志0x22 表示“条件不满足”但具体是哪个条件不满足协议没有定义。排查时需要结合需求文档逐项核对车辆状态。6.3 写入返回 0x13可能原因排查步骤解决思路数据长度错误核对 DID 定义长度调整数据长度ISO-TP 分包错误检查连续帧发送是否正确用 CANoe 的诊断层发送请求格式错误检查 SID 或 DID 字节顺序确认 DID 高位在前0x13 在 0x2E 里通常意味着请求长度和 DID 定义不符。要特别注意某些 ECU 对 0x2E 请求要求“数据长度必须精确匹配”多一个字节都不行。6.4 写入返回正响应但数据未生效可能原因排查步骤解决思路数据写入 RAM 未持久化重启 ECU 后回读检查是否还需要执行存储指令写入后需要复位查看需求文档的生效方式执行 ECU 复位写入校验失败但未报错对比回读数据检查 ECU 内部校验逻辑这个问题最隐蔽。ECU 返回 0x6E 不代表数据已经持久化保存。有些 ECU 的写流程分两步先写 RAM再执行“存储到 Flash”的指令可能是另一条 0x2E 或 0x31 服务。用例设计时一定要包含“重启后回读”的步骤。7. 0x2E 服务用例设计的最佳实践7.1 用例组织规范0x2E 服务用例建议按 DID 分组每组内部按正例、反例、边界、时序、安全、并发分层组织。用例编号采用模块化规则比如TC_0x2E_F190_001表示 DID 0xF190 的第 1 条用例。这样的编号在自动化测试和缺陷管理系统中都很容易追溯。7.2 条件前置处理0x2E 用例的“前置条件”要写清楚不能只写“车辆正常状态”。建议明确写出当前诊断会话。安全解锁状态。车辆条件车速、挡位、电源。相关 DID 的初始值。ECU 是否处于生产模式。前置条件写不清楚用例执行时就会出现“你以为解锁了其实没解锁”的情况浪费排查时间。7.3 数据管理与持久化验证测试过程中要维护一张“DID 写入记录表”记录每个测试用例写入的数据、回读的数据、重启后的数据。不要只在用例步骤里写“写入 0x1234”要把回读结果也记录下来。这样一旦出现数据丢失问题可以快速定位是哪个环节出了问题。7.4 关注写失败后的恢复策略写失败后的数据恢复也是用例设计的重要维度。比如写入 VIN 到第 10 字节时总线掉线ECU 内部是保持原 VIN还是变成全 FF如果需求文档没有定义这个用例应该作为“待确认项”提给系统工程师。8. 总结与后续学习方向0x2E 服务用例设计的核心是把需求文档里的“能写”扩展成“什么条件下能写、什么数据能写、写入后怎么验证、写失败了怎么恢复”的完整测试矩阵。本文梳理了 0x2E 的协议帧格式、NRC 定义、需求拆分方法、完整用例设计示例、自动化脚本和常见问题排查思路。其中优先级最高的是三件事一是正例必须包含回读和重启验证二是反例必须关注 NRC 优先级三是时序用例必须覆盖安全解锁超时场景。下一步可以继续深入学习 0x27 安全访问的用例设计、0x31 例程控制服务的测试方法以及如何在 CANoe 中使用诊断控制台和 CAPL 搭建完整的 UDS 自动化测试工程。如果本文对你有帮助可以收藏备用后续会有更多网络诊断系列文章更新。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进