第二章 UDS协议深度解析 —— 基于ISO 14229-1:2013
2.1 UDS服务全景——26个SID的地图
ISO 14229-1:2013 被你翻开第一页
你面前是这本ISO标准。如果你按传统读法——从第1页读起,第6页开始读到应用层服务定义、第9章开始逐个SID按字母序排列——不到第9章的1/3你就会昏睡过去。
这不是因为内容不精深——而是因为标准按照抽象层级组织,不按使用场景组织。你读完0x10的详细语义,接下去是0x11 ECU复位——两者的使用频率、关联场景、在诊断流程中的位置完全不同,但标准把它们按SID数值顺序排列。这就像一本字典把“挂号“和“截肢“紧接着编排——它们字序相邻,在现实世界里隔着门诊、检查、确诊、麻醉整整一条流程。
这一节的目标不是教你每个SID的报文细节(后面17节会帮你做这件事),而是给你画一张全景地图——让你在进入每一个SID的细节之前,知道自己在这个巨大的26格棋盘上站在哪个位置。
SID的地图:四大功能单元
ISO 14229-1:2013定义的功能单元是六类,但从诊断仪-ECU交互的认知流程出发,可以被更自然地合并为四层:
注:本图的“层“表示诊断流程中的调用顺序,并非OSI七层模型中的层。
SID范围
────────
┌────────────────────────────────────────────────────┐
│ 第四层:诊断和通信管理(会话、权限、连接) │
│ 0x10 会话控制 0x11 ECU复位 0x27 安全访问 │
│ 0x28 通信控制 0x3E 诊断仪保活 0x85 控制DTC设置 │
│ 0x83 访问时序 0x84 安全传输 0x86 事件响应 0x87 链路 │
├────────────────────────────────────────────────────┤
│ 第三层:数据传输(读取和写入运行参数) │
│ 0x22 按DID读 0x2E 按DID写 0x23 按地址读内存 │
│ 0x3D 按地址写 0x2A 周期读取 0x2C 动态定义DID │
├─────────────────────────────────────────────────────┤
│ 第二层:存储的数据(故障码的读写) │
│ 0x19 读DTC 0x14 清除DTC │
├─────────────────────────────────────────────────────┤
│ 第一层:主动控制与固件操作(对ECU做手术) │
│ 0x2F IO控制 0x31 例行控制 │
│ 0x34 请求下载 0x35 请求上传 0x36 传输数据 0x37 退出 │
└──────────────────────────────────────────────────────┘
所有SID的正响应码 = 请求SID + 0x40
负响应的SID固定为 0x7F
第四层在最上面——因为它是最先被调用的(你连会话都没建立,就不能读DTC、不能写DID、不能下载)。第一层在最下面——因为它是最“重“的操作(刷写固件是整个诊断流程的终点,不是起点)。
核心洞察:这四层不是标准的文本顺序——是诊断认知的依赖顺序。你不能在没挂号(建立会话)之前就开药(写DID、下载固件)。你也不能在没看症状(读DTC)之前就决定做什么手术(刷写固件)。UDS的服务编排不是随机的——它是一棵树,根是会话控制,叶是固件下载。
主谓宾结构:C/S模型的固定语法
在UDS通信里,这两句话永远成立:
- 诊断仪发起请求,ECU返回响应。 没有例外。ECU永远不会主动发起一个UDS请求——它只答复。
- 每一个请求对应至多一个响应。 要么正响应(成功+数据),要么负响应(失败+原因码),要么(在功能寻址+suppressPosRsp条件下)无响应。
这在形式上非常类似HTTP的Request-Response模型——但UDS更加严格地遵守“一请求、一响应“。HTTP有Server-Sent Events、WebSocket升级、100 Continue中间响应的概念——UDS没有任何“流式响应“或“推送“机制。ResponseOnEvent(0x86)是ECU“主动发送数据“的唯一方式,但这也严格绑定到诊断仪先在0x86请求里注册事件——是准主动。
全部26个SID一览
| SID (十六进制) | 正响应SID | 服务名称 | 子功能 | 一句话概括 |
|---|---|---|---|---|
| 0x10 | 0x50 | 诊断会话控制 | 有 | 挂号——切换诊断会话(默认/编程/扩展/安全系统) |
| 0x11 | 0x51 | ECU复位 | 有 | 重启——hardReset/keyOffOnReset/softReset |
| 0x14 | 0x54 | 清除诊断信息 | 无 | 删除病历——按groupOfDTC清DTC/快照/扩展数据 |
| 0x19 | 0x59 | 读DTC信息 | 有 | 查看症状——18种子功能覆盖DTC的所有查询维度 |
| 0x22 | 0x62 | 按标识符读数据 | 无 | 开化验单——读一个或多个DID的当前值 |
| 0x23 | 0x63 | 按地址读内存 | 无 | 按物理地址读内存,走地址+长度格式 |
| 0x24 | 0x64 | 按标识符读缩放数据 | 无 | 读DID的缩放定义(物理单位和范围) |
| 0x27 | 0x67 | 安全访问 | 有 | 病历授权——Seed/Key解锁安全等级 |
| 0x28 | 0x68 | 通信控制 | 有 | 屏蔽噪音——关闭/开启ECU特定类型的通信 |
| 0x2A | 0x6A | 按标识符周期读取 | 无 | 持续监控——让ECU定期自动上报DID值 |
| 0x2C | 0x6C | 动态定义DID | 有 | 自定义化验单——诊断仪临时定义DID的组成 |
| 0x2E | 0x6E | 按标识符写数据 | 无 | 调整参数——写一个DID的值(覆盖当前值) |
| 0x2F | 0x6F | 按标识符IO控制 | 无 | 触诊——短期替代某个DID值以观察系统行为 |
| 0x31 | 0x71 | 例行控制 | 有 | 治疗——启动/停止/获取例行程序的结果 |
| 0x34 | 0x74 | 请求下载 | 无 | 手术准备——声明数据格式/地址/长度,获取下载授权 |
| 0x35 | 0x75 | 请求上传 | 无 | 上传准备——声明数据格式/地址/长度,获取上传授权 |
| 0x36 | 0x76 | 传输数据 | 无 | 手术传送——传输实际的数据块(下载或上传) |
| 0x37 | 0x77 | 请求传输终止 | 无 | 手术结束——完成数据传输,验证并退出 |
| 0x38 | 0x78 | 请求文件传输 | 无 | 文件系统操作——传输文件。本书不展开,在实际量产项目中极少使用 |
| 0x3D | 0x7D | 按地址写内存 | 无 | 按物理地址写内存,直接操作非易失存储 |
| 0x3E | 0x7E | 诊断仪保活 | 有(子功能控制ECU是否回复) | 医生还在——诊断仪发心跳防会话超时 |
| 0x83 | 0xC3 | 访问时序参数 | 有 | 协商时序——动态修改P2/P2*超时 |
| 0x84 | 0xC4 | 安全数据传输 | 无 | 加密隧道——在逐服务基础上传输加密数据 |
| 0x85 | 0xC5 | 控制DTC设置 | 有 | 暂停记录——关闭/开启DTC状态位更新 |
| 0x86 | 0xC6 | 事件响应 | 有 | 事件驱动推送——让ECU在特定事件发生时主动发数据 |
| 0x87 | 0xC7 | 链路控制 | 有 | 波特率切换——将诊断仪的K-Line波特率(遗留功能) |
“子功能“列说明:“有“表示报文包含子功能字节(SubFunction),用于进一步指定操作模式;“无“表示报文中没有子功能字节,而非子功能值为0x00。
注:0x23、0x24、0x83、0x84、0x87这5个SID未设独立章节——它们在量产诊断场景中极少使用。0x23(按地址读)被0x22(按DID读,语义化寻址)替代;0x24(读缩放定义)依赖ODX文件而非在线查询;0x83(访问时序参数)P2/P2*值通常在0x10正响应中一次协商;0x84(安全数据传输)协议栈过于复杂且多数ECU未实现;0x87(链路控制)为K-Line波特率切换,在CAN/DoIP时代已基本废弃。
每一个SID都有“为什么存在“的故事
从历史发展的角度看,这26个SID不是一次性设计的——它们是逐层堆叠的:
第一波(OBD-II遗留,1996):0x22读数据、0x19读DTC、0x14清除DTC——这是OBD-II的Mode 0x01、0x03和0x04的UDS化。排放诊断必须得有这些。
第二波(KWP2000进化,2000):0x10会话控制、0x11 ECU复位、0x27安全访问——KWP2000把“修车不仅限于看故障码“的需求带了进来。你要执行主动治疗和安全操作——不能直接做,得先挂号、先授权、先确认你可以做。
第三波(CAN适配,2003-2006):0x34/0x35/0x36/0x37下载上传、0x28通信控制、0x3E保活——CAN总线带来了两个新需求:(1) 大块数据的分段传输;(2) 共享总线上通信的管理(刷写时要把应用报文关掉)。0x3E是应对“S3会话超时“的独立需求——诊断仪必须持续证明自己在线。
第四波(现代汽车电子,2013+):0x2F IO控制、0x31例行控制、0x2A周期读取、0x86事件响应——这些是控制功能的深化。现代车有几百个执行器、几十个例行程序(ABS自检、ADAS标定、电池平衡……),诊断已经不是“读故障码修车“,而是通过诊断接口重新标定、重新编程、重新校准整辆车的子系统。
为什么是26个而不是30个或15个
你可能会想:为什么不是把0x23(按地址读)和0x22(按DID读)合并?为什么还需要两个“读数据“的SID?为什么0x84(安全数据传输)独立而不是作为0x27的子功能?为什么0x86(事件响应)不和0x2A(周期读取)合并?
这些“为什么不合并“的问题,在标准文本里是找不到答案的——它们每一个都有一段真实的历史:
-
0x22 vs 0x23(DID读取 vs 地址读取):0x22是面向应用层的——DID是对传感器、配置参数、标定数据集的语义化命名。0x23是面向存储层的——直接按物理地址读内存。前者是“给我发动机转速“,任何ECU的硬件都能支持(因为ECU自己维护DID到地址的映射)。后者是“给我0x40024000的内容“,只有调试和工厂编程才用到。两者面对的是两种不同角色——诊断技师和ECU标定/安全工程师。
-
0x27 vs 0x84(安全访问 vs 安全数据传输):0x27是会话级的鉴权——你解锁之后,所有后续请求都拥有安全等级。0x84是消息级的加密——即使你的会话已被解锁,某些极其敏感的数据(比如密钥材料的交换)还需要端到端加密+MAC签名,防止中间人窃听。前者解决“你是谁“的身份问题,后者解决“你的消息在传输的路上会不会被偷看/篡改“。这是正交的两个维度。
-
0x2A(周期读取)和0x86(事件响应):它们共享“让ECU主动发送数据“的性子——但触发机制完全不同。0x2A是定时器驱动——“每500ms把三个DID的值发给我”。0x86是事件驱动——“当DTC状态发生任何变化时发给我”。这是时间轴上的两种不同采样模式。
核心洞察:每一个SID的“为什么不合并“背后都有一个两难权衡——合并可以简化接口面,但会混淆语义、导致一个SID承担太多不相关的职责。SID的粒度是ISO技术委员会经过长达数年的讨论确定的——最终这26个被认定是最小充分集合。少一个则不够(某些生产必需的功能没有标准接口),多一个则冗余(两个SID之间有重叠职责,增加实现的复杂度而几乎不增加新能力)。
哪些仅能在非默认会话中使用
UDS有一个严格的访问控制矩阵——不是所有SID都能在默认会话(0x01)中使用。这是为了确保普通的OBD-II扫描仪不能意外地触发敏感操作:
| 会话限制 | SID |
|---|---|
| 任何会话都可用(包括默认) | 0x10 会话控制、0x11 复位、0x3E 保活、0x14 清除DTC、0x19 读DTC、0x22 读DID、0x23 读地址、0x24 读缩放、0x2E 写DID、0x3D 写地址、0x86 事件响应 |
| 仅非默认会话(扩展/编程/安全) | 0x27 安全访问、0x28 通信控制、0x2A 周期读、0x2C 动态DID、0x2F IO控制、0x31 例行控制(受安全限制)、0x34/35/36/37/38 上下传、0x83/0x84/0x85/0x87 |
核心洞察:这张表解释了UDS的“挂号“逻辑——默认会话是一个公共可读空间(可清除DTC,不可修改配置或执行控制)——你进入这个空间不需要任何权限,但要写、控制、做手术——必须挂到其他号。这是服务粒度和安全策略之间的平衡:读数据门槛极低(不用解锁),写和控制必须显式进入专门会话。
SID地图的医院化映射
给你一张对照表,帮你把这些冰冷的SID数字翻译成你已经在第1.5节亲历过的诊断流程:
| 诊断阶段 | UDS服务 | 医院映射 | 什么时候调用 |
|---|---|---|---|
| 挂号 | 0x10 | 选择科室 | 第一步——每次诊断会话的第一条请求 |
| 心跳 | 0x3E | 医生巡视 | 每一次“诊察“的每个周期——防止你的session过期 |
| 授权 | 0x27 | 获取病历权限 | 敏感的读/写/控制操作之前 |
| 查病史 | 0x19 | 读病历 | 收集DTC、锁定症状发生的背景数据 |
| 开化验 | 0x22 | 抽血/拍片 | 实时读取发动机转速、传感器、配置参数 |
| 自定义化验 | 0x2C | 自拟化验项目 | 临时定义一个DID代表你关心的参数组合 |
| 持续监测 | 0x2A | 24小时心电图 | 让ECU自动周期性地发新数据 |
| 触诊 | 0x2F | 按压、叩诊 | 临时假写一个参数值观察系统反馈 |
| 治疗 | 0x31 | 执行治疗方案 | 触发ABS自检、ADAS重标定 |
| 调参 | 0x2E | 调整药量 | 修改标定值、配置参数 |
| 手术准备 | 0x34 | 手术授权 | 声明刷写地址和长度 |
| 手术进行 | 0x36 | 输血/移植 | 传送每一块固件数据 |
| 手术结束 | 0x37 | 拆线/缝合 | 校验下载完整性并退出传输模式 |
| 清除病历 | 0x14 | 病历更新 | 修好后清除已完成的症状记录 |
| 重置病人 | 0x11 | 让病人重启 | 刷写后复位到正常状态 |
| 暂停监控 | 0x85 | 暂停信号采集 | 不让DTC在刷写过程中误报 |
| 屏蔽噪音 | 0x28 | 关掉多余的监护器 | 刷写期间停掉应用报文防冲突 |
| 事件推送 | 0x86 | 护士按铃 | 值监控到异常自动通知医生 |
你此刻可能会有点迷:这么多服务,它们的交互逻辑和依赖关系到底怎么理清楚?别急。先从最底层的报文格式开始——你先把“语言“的语法学会,后面每一句“话“的语义就容易了。
本篇小结
- UDS的26个SID不是按数值顺序排列的结果——它们是一棵依赖树,根是会话控制0x10,叶是0x34/36/37固件下载。理解SID的依赖层级比记住每个SID的数字更重要。
- 正响应SID = 请求SID + 0x40。这是UDS协议的核心编码约束——不是可选的模板,是固定规则。负响应统一走0x7F + 原SID + NRC。
- 从历史发展看,SID集合的每一波增长都对应一个实在的工程需求——OBD-II(1996)→KWP2000(2000)→CAN适配(2003-2006)→现代控制(2013+)。没有凭空设计的SID。
- 默认会话和非默认会话的访问控制矩阵是UDS安全体系的第一道墙——所有写/控制/下载操作都必须离开默认会话才能进行。
【下集预告】:地图归地图,你还要学会说第一句’话’——UDS报文到底由哪些字节组成?SID、SubFunction、DataParameter是怎么组的?正响应报文里为什么没有SID只有一个SID+0x40?负响应的三个字节里每个字节分别代表什么?我们拆报文——从人类语音到CAN帧里从左到右的每一个bit。