4. UDS数据读取服务
UDS(Unified Diagnostic Services,统一诊断服务)协议中,数据读取类服务是诊断测试与验证工作中使用频率最高、覆盖场景最广的功能组。无论是开发阶段的标定参数校验、生产下线时的配置信息确认,还是售后维修时的故障数据采集,都离不开对ECU内部数据的读取。本章将深入剖析三个核心读取服务:0x22 读取数据标识符(ReadDataByIdentifier)、0x23 读取内存地址(ReadMemoryByAddress)以及0x2C 动态定义标识符(DynamicallyDefineDataIdentifier)。理解这三者的原理、适用场景、报文结构及测试验证方法,是构建完整UDS测试体系的关键基石。
上图清晰地展示了三个读取服务在诊断仪与ECU之间的桥梁作用。0x22服务通过逻辑的DID(Data Identifier,数据标识符)访问ECU内部数据,0x23服务则直接面向物理内存地址,而0x2C服务提供了一种动态组合数据源的机制。在后续的章节中,我们将逐一拆解每个服务的报文格式、时序要求、测试用例设计方法以及常见的“坑点”。
4.1 读取数据标识符服务(0x22)
4.1.1 核心原理与报文结构
0x22服务是UDS协议中最基础、最常用的读取服务。其核心思想是:ECU内部维护一张“数据标识符(DID)映射表”,每个DID(通常为16位,即2字节)唯一对应一个数据实体,例如车辆识别码(VIN)、软件版本号、系统供电电压、故障码数量等。诊断仪只需发送DID,ECU即返回对应的数据值。这种设计将物理存储地址与逻辑标识解耦,使得上层测试人员无需关心数据在ECU内存中的具体位置。
请求报文格式如下:
请求: [0x22] [DID_High] [DID_Low]
响应(肯定): [0x62] [DID_High] [DID_Low] [Data_1] ... [Data_N]
响应(否定): [0x7F] [0x22] [NRC]
其中,0x62为肯定响应SID(Service ID,服务ID),0x7F为否定响应SID。一个请求可以携带多个DID(最多支持0x22服务一次读取多个DID,但需注意ECU实现差异),响应中会按顺序返回每个DID及其数据。
4.1.2 判断方法与测试用例设计
在自动化测试中,针对0x22服务的测试用例设计应覆盖以下维度:
- 功能正确性:对每个已知DID,发送请求后,验证响应中的SID是否为0x62,且数据长度与DID定义的数据长度一致。例如,VIN码通常为17字节ASCII码,若返回长度不符,则判定为失败。
- 边界与异常:发送不存在的DID(如0xFFFF),预期返回NRC 0x31(请求超出范围)。发送长度异常的请求(如缺少DID字节),预期返回NRC 0x13(消息长度错误)。
- 会话与安全依赖:某些DID(如标定数据、密钥信息)可能要求ECU处于扩展会话(0x03)且通过安全访问(0x27服务)解锁。测试需验证在默认会话(0x01)下访问受保护DID,应返回NRC 0x22(条件不满足)或0x33(安全访问拒绝)。
- 时序性能:记录从发送请求到收到响应的时间差(P2时间),确保在ECU规范定义的P2CAN(如50ms)内完成响应。对于大数据块(如Bootloader软件版本),需关注P2*时间(如5000ms)内的分段响应。
4.1.3 案例拆解:VIN码读取与校验
背景与目标:某OEM在整车下线检测环节,需要读取ECU中存储的VIN码,并与生产系统下发的VIN码进行比对,防止错装。
关键输入:DID = 0xF190(VIN码),数据长度 = 17字节,ASCII编码。
操作步骤:
- 测试环境搭建:使用CANoe或PCAN发送0x22 请求,DID=0xF190。
- 发送请求后,捕获响应帧。预期响应为
62 F1 90 31 34 32 35 36 37 38 39 30 31 32 33 34 35 36 37(假设VIN为“1425678901234567”)。 - 自动化脚本解析响应数据,去除SID和DID字节,将剩余17字节转换为ASCII字符串。
- 与生产系统下发的VIN字符串进行精确匹配。
结果与复盘:在一次测试中,发现某ECU返回的VIN码末尾字节为0x00(空字符),导致字符串比较失败。经排查,是ECU的NVM(非易失性存储器)中VIN码写入时未做长度截断,多写入了结束符。最终通过调整ECU标定数据修复。此案例说明,测试不仅要验证数据内容,还需关注数据格式的严格性(如去除填充字节、处理大小端)。
4.1.4 常见误区与总结
- 误区一:认为所有DID在任何会话下都可读。实际上,UDS规范允许ECU根据当前会话状态和安全等级动态调整DID的可读性。测试前必须查阅ECU的DID规范文档。
- 误区二:忽略响应数据的字节序。对于多字节数据(如uint16、uint32),ECU可能采用大端(Motorola)或小端(Intel)格式。测试脚本必须按DID定义正确解析,否则会得到错误数值。
- 误区三:未考虑0x22服务对子功能(Sub-function)的支持。虽然0x22通常不使用子功能,但某些ECU实现可能要求子功能位为0。测试时需发送完整的请求字节,避免遗漏。
4.2 读取内存地址服务(0x23)
4.2.1 核心原理与适用场景
与0x22的逻辑寻址不同,0x23服务允许诊断仪直接指定ECU内存的物理起始地址和读取长度,从而读取任意内存区域的数据。该服务通常用于开发阶段的底层调试、Bootloader刷写前的内存校验、或者读取特定RAM变量(如传感器原始值)。由于直接操作物理地址,该服务具有极高的权限,通常要求ECU处于扩展会话且通过安全访问解锁。
请求报文格式如下:
请求: [0x23] [Address_And_Length_Format] [StartAddress_High] ... [StartAddress_Low] [MemorySize_High] ... [MemorySize_Low]
响应(肯定): [0x63] [Data_1] ... [Data_N]
响应(否定): [0x7F] [0x23] [NRC]
其中,Address_And_Length_Format(1字节)的高半字节表示起始地址字节数,低半字节表示内存长度字节数。例如,0x24表示起始地址占2字节,内存长度占4字节。
4.2.2 判断方法与测试要点
测试0x23服务时,需特别关注内存访问的边界与合法性:
- 地址对齐:某些ECU要求起始地址必须按2字节或4字节对齐。若未对齐,可能返回NRC 0x31或0x13。测试用例应覆盖对齐与未对齐地址。
- 长度边界:读取长度不能跨越非法内存区域(如保留地址、外设寄存器映射区)。例如,从有效RAM区域读取,若长度超出RAM末尾,应返回NRC 0x31。
- 安全与状态依赖:在默认会话下发送0x23请求,预期返回NRC 0x22(条件不满足)或0x33(安全拒绝)。在扩展会话且解锁后,方可成功读取。
- 数据一致性:对于正在实时更新的RAM变量(如发动机转速),连续读取两次,数据可能不同。测试需区分是数据正常变化还是读取错误。
4.2.3 案例拆解:读取RAM中的故障码计数器
背景与约束:在某ECU的台架测试中,需要验证故障码(DTC)计数器的递增逻辑。该计数器位于RAM地址0x3C00,类型为uint16。
初始现象:通过诊断仪读取DTC状态(0x19服务)正常,但无法确认内部计数器是否精确递增。
操作步骤:
- 进入扩展会话(发送0x10 03),并执行安全访问解锁(0x27服务)。
- 构造0x23请求:地址格式=0x24(地址2字节,长度2字节),起始地址=0x3C00,内存长度=0x0002。
- 发送请求,收到响应
63 00 05,解析为uint16值5。 - 通过诊断仪触发一次故障(如拔掉传感器),再次读取,值变为6。
- 连续触发10次,每次读取值均递增1,验证计数器逻辑正确。
风险与代价:若地址错误,可能读到其他变量或导致ECU复位。因此,测试前必须与ECU开发工程师确认内存映射表。
4.2.4 常见误区与总结
- 误区一:混淆地址格式字节。地址格式字节的高半字节和低半字节分别代表地址和长度字节数,不能写反。写反会导致ECU解析错误,返回NRC 0x13。
- 误区二:忽略字节序。起始地址和内存长度在请求中均为大端格式(高字节在前)。若使用小端发送,ECU会解析出错误的地址。
- 误区三:未考虑内存保护机制。现代ECU通常具有MMU或MPU(内存保护单元),即使解锁了安全访问,某些关键区域(如Bootloader区)依然不可读。测试前需确认访问权限。
4.3 动态定义数据标识符服务(0x2C)
4.3.1 核心原理与优势
0x2C服务提供了一种“按需组合”数据的能力。诊断仪可以动态地将多个数据源(可以是DID、内存地址、或固定值)组合成一个新的DID,后续通过0x22服务读取该新DID,即可一次性获取所有组合数据。这在需要周期性读取多个参数(如转速、车速、水温)的测试场景中,能显著减少总线负载和响应时间。
该服务包含三个子功能:
- 定义(Define,子功能=0x01):创建或覆盖一个动态DID。
- 清除(Clear,子功能=0x02):删除一个动态DID。
- 清除所有(ClearAll,子功能=0x03):删除所有动态DID。
请求报文格式(以定义为例):
请求: [0x2C] [0x01] [DynamicDID_High] [DynamicDID_Low] [Source_Count] [Source_1_Type] [Source_1_Length] [Source_1_Data...] ...
响应(肯定): [0x6C] [0x01] [DynamicDID_High] [DynamicDID_Low]
响应(否定): [0x7F] [0x2C] [NRC]
其中,Source_Type可以是DID(0x01)、内存地址(0x02)或固定值(0x03)。
4.3.2 判断方法与测试策略
测试0x2C服务时,重点在于验证动态DID的生命周期管理及数据组合的正确性:
- 定义与读取:定义一个包含两个DID和一个固定值的动态DID,然后通过0x22读取,验证返回数据顺序与定义顺序一致。
- 覆盖规则:对同一个动态DID重复定义,应覆盖旧定义。测试需验证新定义生效。
- 清除功能:清除后,再通过0x22读取该DID,应返回NRC 0x31(DID不存在)。
- 会话依赖性:动态DID通常仅在当前会话有效。切换会话(如从扩展会话切回默认会话)后,动态DID应被自动清除。测试需验证此行为。
- 数量限制:ECU对动态DID的数量有限制(如最多支持8个)。测试需验证超出限制时返回NRC 0x31或0x72(一般编程失败)。
4.3.3 案例拆解:动态DID组合读取
背景与目标:某BMS(电池管理系统)测试中,需要同时监控总电压(DID 0x0101,4字节)、总电流(DID 0x0102,4字节)和SOC(DID 0x0103,1字节)。
操作步骤:
- 进入扩展会话并解锁安全访问。
- 发送0x2C定义请求:子功能=0x01,动态DID=0xFD01,源数量=3。
- 源1:类型=0x01(DID),长度=0x02,数据=0x0101。
- 源2:类型=0x01,长度=0x02,数据=0x0102。
- 源3:类型=0x01,长度=0x02,数据=0x0103。
- 发送0x22请求,DID=0xFD01。响应数据应为9字节(4+4+1),顺序为电压、电流、SOC。
结果对比:使用动态DID后,单次请求获取全部数据,总线负载降低约67%(从3帧减少到1帧)。但需注意,动态DID的定义过程本身也占用总线时间,因此适合长时间周期性读取场景。
4.3.4 常见误区与总结
- 误区一:忽略动态DID的会话生命周期。很多测试人员定义完动态DID后,切换会话导致DID失效,误以为是ECU故障。测试脚本应在每次会话切换后重新定义。
- 误区二:未处理源数据长度变化。如果某个源DID的数据长度在ECU运行中发生变化(如故障码数量变化),动态DID的总长度可能不固定。测试需处理变长响应。
- 误区三:混淆子功能。0x2C的子功能是必填的,且定义(0x01)和清除(0x02)的响应SID不同(0x6C vs 0x6C,但数据字段不同)。测试需严格区分。
4.4 三个服务的对比与选型建议
| 维度 | 0x22 读取数据标识符 | 0x23 读取内存地址 | 0x2C 动态定义标识符 |
|---|---|---|---|
| 寻址方式 | 逻辑DID(16位) | 物理内存地址+长度 | 动态DID(16位) |
| 数据源 | ECU内部DID映射表 | RAM/Flash/寄存器 | 组合DID、内存地址、固定值 |
| 典型用途 | 读取VIN、版本、故障码 | 底层调试、内存校验 | 多参数同步采集、降低负载 |
| 安全要求 | 部分DID需解锁 | 通常必须解锁 | 定义和读取均需解锁 |
| 会话要求 | 默认/扩展均可 | 通常需扩展会话 | 通常需扩展会话 |
| 测试重点 | DID存在性、数据格式 | 地址边界、对齐、非法访问 | 生命周期、组合顺序、覆盖规则 |
在实际项目中,选择哪个服务取决于具体需求。若仅需读取少量标准参数,优先使用0x22;若需访问未映射为DID的底层数据,使用0x23;若需周期性读取多个参数且对总线负载敏感,使用0x2C。测试人员应深入理解ECU的DID规范文档和内存映射表,避免盲目使用。
1. 0x22是基础,测试需覆盖DID的合法性、数据长度和访问权限。
2. 0x23是高危操作,测试需严格校验地址边界和会话安全状态。
3. 0x2C是优化工具,测试需关注动态DID的生命周期和组合逻辑。
4. 所有读取服务的自动化测试都应基于数据驱动的框架,将测试数据与脚本逻辑分离,提高可维护性。