在实际车载诊断项目里,第一次接触 UDS 的朋友,看到“19 02 FF”或“19 04 0A 05 01”这类报文时,很容易被参数和响应结构绕晕。19 服务是 UDS 中读取故障码信息的核心服务,涵盖数量统计、故障码列表、快照记录、扩展数据等多个子功能。本文以 19 服务的 01、02、04、06 四个高频子功能为主线,结合请求报文和正/负响应报文实例,完整拆解每一帧的含义、参数设计和排查思路。
如果你是刚接触 UDS 诊断协议的学生、测试工程师,或者正在做 ECU 刷写、产线检测、售后诊断工具开发的开发者,这篇文章都适用。看完之后,你不仅知道“怎么发”,还能理解“为什么这样设计”。
1. 19服务是什么:从UDS诊断体系说起
1.1 UDS诊断协议定位
UDS(Unified Diagnostic Services,统一诊断服务)是 ISO 14229 标准定义的一套汽车诊断协议,目前在整车电子电气架构中基本属于标配。它在 OSI 七层模型中位于应用层,底层可以承载在 CAN、CAN FD、LIN、以太网(DoIP)等不同的传输层协议上。
在 UDS 的协议框架里,服务通过 SID(Service Identifier,服务标识符)区分。例如 0x10 是诊断会话控制,0x22 是按标识符读取数据,0x27 是安全访问,0x14 是清除故障码,0x19 则是读取故障码信息。
每个服务还可以细分为不同的子功能,19 服务是整个 UDS 体系中结构最丰富、使用频率最高的服务之一。故障码的产生、确认、恢复、历史存储、环境数据记录,都要依赖 19 服务来读取。
1.2 19服务解决什么问题
ECU(Electronic Control Unit,电子控制单元)在运行过程中会持续监控传感器、执行器、通信链路的状态。当某个监测项超出阈值或逻辑不满足时,ECU 内部会记录一个 DTC(Diagnostic Trouble Code,诊断故障码),并在状态字节中标记故障类型。
但记录 DTC 只是第一步,诊断人员还需要知道:
- 当前 ECU 一共存了多少条 DTC?
- 这些 DTC 是当前激活的还是历史残留的?
- 每个 DTC 对应的故障状态、发生次数、行驶里程、环境温度等附加信息是什么?
- 怎么在清除 DTC 之前把现场数据保存下来,方便复现和分析?
这些诉求全部由 19 服务来承载。所以 19 服务的核心定位就是:按条件读取 DTC 相关信息。
1.3 19服务子功能总览
19 服务下有多个子功能,不同子功能获取的信息粒度不同。本文重点讲解实际项目中最常用的 4 个:
| 子功能 | 名称 | 作用 |
|---|---|---|
| 0x01 | reportNumberOfDTCByStatusMask | 按状态掩码统计 DTC 数量 |
| 0x02 | reportDTCByStatusMask | 按状态掩码返回 DTC 列表 |
| 0x04 | reportDTCSnapshotRecordByDTCNumber | 返回指定 DTC 的快照记录 |
| 0x06 | reportDTCExtDataRecordByDTCNumber | 返回指定 DTC 的扩展数据记录 |
简单理解:01 是“数一下有多少条”,02 是“把故障码列出来”,04 是“查看某个故障码发生时的现场快照”,06 是“查看某个故障码的附加统计信息”。这几类信息组合起来,基本能覆盖故障诊断和售后分析的绝大多数场景。
2. 必须理解的基础概念:DTC、状态字节与状态掩码
2.1 DTC常见格式
在传统 CAN 诊断系统中,最常用的是 ISO 15031-6 规定的 2 字节 DTC 格式。例如 0x0A05、0x0A06、0x12AA 这类数值。2 字节 DTC 中的高 2 位用于表示故障类型,例如:
- 00:P 开头(动力系统)
- 01:C 开头(底盘系统)
- 10:B 开头(车身系统)
- 11:U 开头(网络通信系统)
剩余位用于表示具体的故障编号和子类型。
随着 ISO 14229-1:2020 标准的更新,DTC 也可以使用 3 字节格式,其中第三字节用于描述故障的具体类型,例如“信号无效”“信号超时”“信号不合理”等。目前很多新平台已经在使用 3 字节 DTC,但对于老平台和现产车,2 字节 DTC 仍然大量存在。
在 19 服务的响应中,DTCFormatIdentifier(DTC 格式标识符)字段用于告知调用方当前 ECU 使用的 DTC 格式。例如 0x00 通常表示 ISO 14229-1 定义的标准格式。读者在实际开发时,必须先确认目标项目使用的是 2 字节还是 3 字节 DTC,否则解析 DTC 列表时会出现错位。
2.2 DTC状态字节每一位的含义
DTC 状态字节是 19 服务中最重要的概念之一。它不只表示“是不是有故障”,而是一个包含 8 个独立含义的位域,每一位都代表一种诊断状态。
以 2 字节 DTC + 1 字节状态为例,状态字节定义如下:
| Bit | 掩码值 | 含义 |
|---|---|---|
| bit0 | 0x01 | testFailed:最近一次测试失败 |
| bit1 | 0x02 | testFailedThisOperationCycle:当前操作循环中测试失败 |
| bit2 | 0x04 | pendingDTC:待确认故障码 |
| bit3 | 0x08 | confirmedDTC:已确认故障码 |
| bit4 | 0x10 | testNotCompletedSinceLastClear:上次清除后测试未完成 |
| bit5 | 0x20 | testFailedSinceLastClear:上次清除后测试失败过 |
| bit6 | 0x40 | testNotCompletedThisOperationCycle:当前操作循环测试未完成 |
| bit7 | 0x80 | warningIndicatorRequested:请求点亮故障指示灯 |
实际报文中一个状态字节可能同时包含多位。例如 0x45 表示 bit0、bit2、bit6 置位,即“最近测试失败 + 待确认 + 当前循环未完成”。这里的组合逻辑完全取决于故障检测策略,不同 ECU 可能给出不同的状态组合。
2.3 状态掩码如何筛选目标DTC
19 服务的 01 和 02 子功能都要求调用方传入一个状态掩码,用于筛选想要查看的 DTC。例如:
- 状态掩码 0xFF:匹配所有状态位,返回全部 DTC。
- 状态掩码 0x08:只匹配 confirmedDTC 位,即只返回已确认的 DTC。
- 状态掩码 0x04:只匹配 pendingDTC 位,即只返回待确认的 DTC。
- 状态掩码 0x29:匹配 bit0、bit3、bit5,即返回最近测试失败且已确认的 DTC。
筛选的原理是对状态字节做按位与运算。只要状态字节与掩码的与结果不为 0,该 DTC 就满足查询条件。这里容易误以为 19 服务返回的 DTC 状态必须完全等于掩码值,实际上并不是,正确理解是“状态字节与掩码按位与后非 0”。
因此,在工程中查询“所有 DTC”时,一般直接使用 0xFF;查询“有效故障”时,优先使用包含 confirmedDTC(bit3)的掩码,例如 0x09 或 0x0D;查询“历史故障”时,配合 testFailedSinceLastClear(bit5)、confirmedDTC(bit3)等位一起判断。
2.4 会话控制与安全访问预备知识
19 服务通常在默认会话下就允许调用,但存在一些限制:
- 部分 ECU 只在扩展诊断会话或编程会话下开放 19 服务的某些子功能。
- 读取某些敏感 DTC 或环境数据时,可能需要先执行 0x27 安全访问,完成解锁后再发 19 请求。
- 如果车辆处于行驶模式或总线通信繁忙状态,ECU 可能因为内部条件不满足,返回 NRC 0x22(conditionsNotCorrect)。
所以在排查 19 服务异常响应时,不要一上来就怀疑报文格式,先确认当前诊断会话状态以及是否已解锁安全访问。这也是很多初学者最容易忽略的点。
3. 子功能01:报告DTC数量
3.1 功能说明与请求格式
子功能 01 的名称是 reportNumberOfDTCByStatusMask,作用是让 ECU 统计符合条件的 DTC 数量。它只返回数量,不返回具体 DTC 内容,因此报文很短。
请求报文格式如下:
| 字节位置 | 内容 | 说明 |
|---|---|---|
| byte0 | 0x19 | SID,读 DTC 信息 |
| byte1 | 0x01 | 子功能:报告 DTC 数量 |
| byte2 | 状态掩码 | 筛选规则,例如 0xFF |
举一个最简单的例子,发一帧 CAN 请求:
19 01 FF- 0x19:服务 ID
- 0x01:子功能 01
- 0xFF:状态掩码,匹配所有状态
这里如果使用物理寻址,请求报文 ID 通常是指向目标 ECU 的诊断物理请求 ID(例如 0x7E0),实际值取决于整车网络设计。如果使用功能寻址(例如 0x7DF),总线上的多个 ECU 都可能响应,需要特别注意响应冲突。
3.2 正响应报文结构
ECU 收到 19 01 请求后,如果支持该子功能且参数合法,会返回正响应:
| 字节位置 | 内容 | 说明 |
|---|---|---|
| byte0 | 0x59 | 正响应 SID,等于请求 SID + 0x40 |
| byte1 | 0x01 | 子功能回显 |
| byte2 | DTCFormatIdentifier | DTC 格式标识 |
| byte3 | numberOfDTC_HighByte | DTC 数量高字节 |
| byte4 | numberOfDTC_LowByte | DTC 数量低字节 |
| byte5 | statusOfDTC | 回显请求中的状态掩码 |
其中 numberOfDTC 是两字节大端整数,最大可以表示 65535 个 DTC。statusOfDTC 字段回显请求掩码,用于告诉调用方这个数量是基于什么筛选条件统计出来的。
3.3 报文实例
假设某个 BMS(电池管理系统)内存有两条 DTC:0x0A05 和 0x0A06,发送 19 01 FF 后,正响应示意如下:
59 01 00 00 02 FF逐字节解析:
- 0x59:正响应 SID
- 0x01:子功能回显,确认是 01 的响应
- 0x00:DTCFormatIdentifier,表示 ISO 14229-1 标准格式
- 0x00 0x02:DTC 数量为 2
- 0xFF:回显状态掩码 0xFF
如果发送 19 01 04,只统计 pendingDTC 状态的故障码,而当前只有 0x0A05 处于 pending 状态,响应就可能是:
59 01 00 00 01 04这里 DTC 数量变成了 1,状态掩码回显为 0x04。通过这种方式,诊断仪可以在不获取完整 DTC 列表的情况下,快速判断是否存在某个状态下的故障码。
4. 子功能02:报告DTC
4.1 功能说明与请求格式
子功能 02 的名称是 reportDTCByStatusMask,作用是按状态掩码返回 DTC 列表以及每个 DTC 的状态字节。这是诊断工具显示故障码列表时最常用的请求。
请求报文格式如下:
| 字节位置 | 内容 | 说明 |
|---|---|---|
| byte0 | 0x19 | SID |
| byte1 | 0x02 | 子功能:报告 DTC |
| byte2 | 状态掩码 | 筛选规则,例如 0xFF |
示例请求:
19 02 FF这条报文的意思是:把当前 ECU 存储的所有 DTC 和状态返回给我。
4.2 正响应报文结构
19 02 的正响应相对复杂一些,因为它包含了一个变长的 DTC 列表。以 2 字节 DTC 格式为例:
| 字节位置 | 内容 | 说明 |
|---|---|---|
| byte0 | 0x59 | 正响应 SID |
| byte1 | 0x02 | 子功能回显 |
| byte2 | 状态掩码回显 | 请求中的 statusOfDTC |
| byte3 | DTCFormatIdentifier | DTC 格式标识 |
| byte4~byte5 | numberOfDTC | DTC 总数量 |
| byte6~byte8 | DTC1 + Status1 | 第 1 条 DTC 和状态 |
| byte9~byte11 | DTC2 + Status2 | 第 2 条 DTC 和状态 |
| ... | ... | 以此类推 |
每条 DTC 记录由 3 字节组成:2 字节 DTC 号 + 1 字节状态。如果响应很长,底层传输层会按照 ISO-TP 协议拆分成多帧发送。
4.3 报文实例
继续使用上述 BMS 的示例。请求:
19 02 FF假设 ECU 返回单帧正响应:
59 02 FF 00 00 02 0A 05 45 0A 06 0A逐字节解析:
- 0x59:正响应 SID
- 0x02:子功能回显
- 0xFF:状态掩码回显
- 0x00:DTC 格式标识
- 0x00 0x02:共有 2 条 DTC
- 0x0A 0x05:第 1 条 DTC 是 0x0A05
- 0x45:DTC 0x0A05 的状态字节,示例状态值为 45
- 0x0A 0x06:第 2 条 DTC 是 0x0A06
- 0x0A:DTC 0x0A06 的状态字节,示例状态值为 0A
这里 0x45 表示 0100 0101,即 bit0、bit2、bit6 置位,代表“最近测试失败”“待确认”“当前循环测试未完成”。0x0A 表示 0000 1010,即 bit1、bit3 置位,代表“当前循环测试失败”且“已确认”。实际项目中状态字节的具体含义需要结合故障监测策略判断。
如果只查询已确认故障码,请求为:
19 02 08响应可能只剩已确认的 DTC:
59 02 08 00 00 01 0A 06 0A这里返回了 1 条 DTC,说明只有 0x0A06 处于 confirmed 状态。
4.4 多帧响应与缓冲区不足的处理
当 DTC 列表数量较大,或者 DTC 状态数据结构较长时,单帧 CAN 报文可能放不下,底层会自动使用 ISO-TP 多帧传输。在 CAN 2.0 中,单帧数据场最多 8 字节,而 19 02 的正响应很可能超过 8 字节,因此大部分项目都会进入多帧流程。
多帧传输包括首帧、连续帧和流控帧。工具侧需要正确解析 ISO-TP 层,才能把多个连续帧拼接成完整数据,再交给上层解析 19 02 的有效载荷。如果在 CANoe 等工具中看到 19 02 响应只返回了前半部分,通常是因为:
- 没有正确配置 ISO-TP 接收缓冲区。
- 测试工具的网络层没有启用接收流控能力。
- 请求中未要求 ECU 拆分传输。
另外需要注意,19 02 响应中把 numberOfDTC 放在 DTC 列表前面,是为了让上位机可以预估数据长度。但由于一帧 CAN 报文很难装下全部 DTC,上位机不能只根据首帧长度判断列表结束,必须按 numberOfDTC 字段循环解析完整 DTC 记录。
5. 子功能04:报告快照记录
5.1 快照记录的概念和用途
快照记录(Snapshot Record)是故障发生瞬间 ECU 保存的一批环境数据。比如单体电压、SOC、电池温度、车速、总里程、系统电压等。
快照记录的意义在于,故障发生后,很多现场信息会随工况变化而消失。诊断人员通过读取快照,可以还原故障发生时的“现场”,从而判断故障原因。这是一种非常关键的诊断手段。
每个 DTC 可以关联一个或多个快照记录,快照记录编号从 1 开始。不同的快照记录可能代表不同工况或不同故障分类下的环境数据。
5.2 请求报文结构
子功能 04 的请求必须指定 DTC 号和快照记录号:
| 字节位置 | 内容 | 说明 |
|---|---|---|
| byte0 | 0x19 | SID |
| byte1 | 0x04 | 子功能:报告快照记录 |
| byte2~byte3 | DTC number | 目标 DTC,例如 0x0A05 |
| byte4 | 快照记录号 | 0x01 表示第 1 条快照,0xFF 表示全部 |
示例请求:
19 04 0A 05 01含义:读取 DTC 0x0A05 的第 1 条快照记录。
5.3 正响应报文结构
19 04 正响应包含 DTC 信息以及一个快照记录参数列表。由于快照的数据内容由 OEM 自定义,所以参数标识符、参数长度和参数值都无法一概而论。报文中必须通过“参数标识符 + 参数长度 + 参数值”的方式组织数据,上位机才能逐个解析。
典型的正响应结构如下:
| 字节位置 | 内容 | 说明 |
|---|---|---|
| byte0 | 0x59 | 正响应 SID |
| byte1 | 0x04 | 子功能回显 |
| byte2~byte3 | DTC number | 当前 DTC |
| byte4 | statusOfDTC | DTC 当前状态 |
| byte5 | snapshotRecordNumber | 当前快照记录号 |
| byte6 | 参数项数量 | 该快照中包含的参数个数 |
| byte7~byteN | 参数列表 | 多个“参数ID + 参数长度 + 参数值” |
快照记录的核心是参数列表。只要数据组织方式一致,诊断工具就能把其中所有参数解析出来,并以表格或曲线形式展示给用户。
5.4 报文实例
假设 DTC 0x0A05 的第 1 条快照包含两个参数:电池组最高电压(参数标识符 0x01,长度 2 字节)和电池组最高温度(参数标识符 0x02,长度 1 字节)。
请求:
19 04 0A 05 01正响应示例:
59 04 0A 05 45 01 02 01 02 03 E8 02 01 5A逐字节解析:
- 0x59:正响应 SID
- 0x04:子功能回显
- 0x0A 0x05:DTC 为 0x0A05
- 0x45:DTC 当前状态字节
- 0x01:当前快照记录号
- 0x02:该快照包含 2 个参数
- 0x01:第 1 个参数的标识符,表示“电池组最高电压”
- 0x02:参数长度为 2 字节
- 0x03 0xE8:参数值,即电压 1000(单位 0.1V,实际为 100.0V)
- 0x02:第 2 个参数的标识符,表示“电池组最高温度”
- 0x01:参数长度为 1 字节
- 0x5A:参数值,即温度 90(单位 1℃,实际为 90℃)
从这个例子可以看出,19 04 的难点并不在于协议本身,而在于参数标识符和参数长度的 OEM 自定义规则。开发诊断工具时,需要先拿到对应 ECU 的诊断规范或 DBC/CDD 文件,才能正确翻译快照数据。
6. 子功能06:报告扩展数据记录
6.1 扩展数据记录的概念和用途
扩展数据记录(Extended Data Record)也是附加在 DTC 上的信息,但和快照记录略有不同。快照记录强调的是“故障发生瞬间的环境数据”,扩展数据则更偏向“统计与累计信息”。
常见的扩展数据包括:
- 故障出现次数
- 故障老化计数器
- 故障第一次发生的时间或里程
- 故障最后一次发生的时间或里程
- 故障连续激活次数
- 内存中循环测试的累计次数
扩展数据主要用于故障频次分析和老化评估。售后场景中,通过扩展数据可以知道某个故障码是偶发还是持续,是刚出现还是已经存在很长时间。
6.2 请求报文结构
子功能 06 的请求格式与 04 非常相似,也需要 DTC 号和扩展数据记录号:
| 字节位置 | 内容 | 说明 |
|---|---|---|
| byte0 | 0x19 | SID |
| byte1 | 0x06 | 子功能:报告扩展数据记录 |
| byte2~byte3 | DTC number | 目标 DTC |
| byte4 | 扩展数据记录号 | 0x01 表示第 1 块扩展数据,0xFF 表示全部 |
示例请求:
19 06 0A 05 01含义:读取 DTC 0x0A05 的第 1 块扩展数据记录。
6.3 正响应报文结构
19 06 的正响应与 19 04 类似:
| 字节位置 | 内容 | 说明 |
|---|---|---|
| byte0 | 0x59 | 正响应 SID |
| byte1 | 0x06 | 子功能回显 |
| byte2~byte3 | DTC number | 当前 DTC |
| byte4 | statusOfDTC | DTC 当前状态 |
| byte5 | 扩展数据记录号 | 当前记录号 |
| byte6~byteN | 扩展数据参数 | 参数长度与参数值 |
与快照记录一样,扩展数据的具体内容由 ECU 诊断规范定义,不同厂商的差异很大。例如同一个“故障出现次数”字段,在 A 厂商的 ECU 里可能使用 2 字节,在 B 厂商的 ECU 里可能使用 4 字节。开发工具时必须以目标 OEM 的规范为准。
6.4 报文实例
假设 DTC 0x0A05 的第 1 块扩展数据只有一项:故障出现次数,长度 2 字节,当前值为 10(0x000A)。
请求:
19 06 0A 05 01正响应示例:
59 06 0A 05 45 01 01 02 00 0A逐字节解析:
- 0x59:正响应 SID
- 0x06:子功能回显
- 0x0A 0x05:DTC 为 0x0A05
- 0x45:DTC 当前状态字节
- 0x01:扩展数据记录号
- 0x01:当前扩展数据项数量为 1
- 0x02:数据长度 2 字节
- 0x00 0x0A:故障出现次数为 10
如果 ECU 把故障出现次数放在偏移固定位置,也可能不返回参数标识符,而直接返回定长数据。例如某些 OEM 会规定扩展数据记录按固定字节结构排列,这样上位机只需要按照字节偏移去解析即可。所以 19 06 的响应格式会有“定长结构”和“TLV 结构”两种常见形态,必须在开发前确认。
7. 负响应与常见NRC梳理
7.1 NRC报文格式
当 ECU 无法处理请求时,不会返回 0x59 开头的正响应,而是返回负响应报文:
| 字节位置 | 内容 | 说明 |
|---|---|---|
| byte0 | 0x7F | 负响应 SID |
| byte1 | 0x19 | 原始服务 ID,表示是 19 服务的负响应 |
| byte2 | NRC | 负响应码 |
例如