3、UDS服务分类:强制服务与可选服务、6大类服务功能概览、服务ID(SID)的编码规则
各位同学,今天我们聊聊UDS服务的分类和编码规则。这部分内容,说白了就是UDS协议的骨架。你搞清楚了SID怎么编、服务分几类,后面写代码、调诊断栈的时候,心里就有底了。
3.1 强制服务与可选服务:哪些是必须实现的?
UDS协议里,服务不是你想实现哪个就实现哪个。ISO 14229-1明确规定了哪些是强制服务,哪些是可选服务。我刚开始做诊断开发时,就吃过这个亏——以为所有服务都得实现,结果评审时被客户怼了回来。
核心原则:强制服务是所有ECU都必须支持的,可选服务则根据ECU的功能需求来决定是否实现。
强制服务其实不多,就几个:
- 诊断会话控制(0x10):没有它,你连诊断模式都进不去。
- ECU复位(0x11):诊断结束后,总得让ECU复位吧?
- 安全访问(0x27):保护敏感数据,防止非法操作。
- 通信控制(0x28):控制网络通信的开启和关闭。
- 测试仪在线(0x3E):保持诊断会话不超时。
- 读取DTC信息(0x19):诊断的核心功能,读取故障码。
- 读取/写入数据(0x22/0x2E):读写ECU内部数据。
你想想看,如果连会话控制都不支持,那诊断工具根本没法跟ECU建立连接。所以这些服务是底线,必须实现。
我的经验:在项目初期,我会先列一个强制服务清单,逐项确认。曾经有个项目,客户要求支持0x28通信控制,但我们的MCU资源有限,最后通过裁剪功能子参数来满足需求。记住,强制服务不能砍,但可选服务可以灵活处理。
3.2 6大类服务功能概览:一张图看懂UDS服务家族
UDS协议把服务分成了6大类。为什么要分类?因为不同服务干不同的事,分类后更容易管理和理解。我个人习惯把这6类记成:诊断管理、数据传输、存储、输入输出、例行程序、上传下载。
下面这张SVG图,帮你快速建立整体认知:
这6大类覆盖了诊断的所有场景。举个例子,你在产线上刷写固件,用的就是上传下载类;你在售后读取故障码,用的是存储类。每个类都有自己的职责,互不重叠。
3.3 服务ID(SID)的编码规则:SID是怎么编出来的?
SID是每个服务的身份证。它占1个字节,范围从0x00到0xFF。但UDS协议只用了其中一部分,剩下的保留给OEM自定义或未来扩展。
编码规则其实很简单:
- 高位(bit 7-4):表示服务所属的功能组。比如0x1x表示诊断管理类,0x2x表示数据传输类。
- 低位(bit 3-0):表示组内的具体服务编号。
举个例子:
- 0x10:高位0x1表示诊断管理类,低位0x0表示第一个服务——诊断会话控制。
- 0x22:高位0x2表示数据传输类,低位0x2表示读取数据。
- 0x19:高位0x1表示诊断管理类,低位0x9表示读取DTC信息。
关键点:SID的编码不是随意的。ISO 14229-1规定了每个SID的固定含义。你不能把0x10定义为别的功能,否则诊断工具和ECU之间就没法通信了。
下面这个表格,列出了常见的SID及其分类:
| SID | 服务名称 | 所属大类 | 强制/可选 |
|---|---|---|---|
| 0x10 | 诊断会话控制 | 诊断管理类 | 强制 |
| 0x11 | ECU复位 | 诊断管理类 | 强制 |
| 0x27 | 安全访问 | 诊断管理类 | 强制 |
| 0x28 | 通信控制 | 诊断管理类 | 强制 |
| 0x3E | 测试仪在线 | 诊断管理类 | 强制 |
| 0x22 | 读取数据 | 数据传输类 | 强制 |
| 0x2E | 写入数据 | 数据传输类 | 强制 |
| 0x19 | 读取DTC信息 | 存储类 | 强制 |
| 0x14 | 清除诊断信息 | 存储类 | 可选 |
| 0x31 | 例行程序控制 | 例行程序类 | 可选 |
| 0x34 | 请求下载 | 上传下载类 | 可选 |
| 0x36 | 传输数据 | 上传下载类 | 可选 |
避坑指南:我曾经在一个项目里,把0x19的SID写成了0x1A,结果诊断工具死活读不到DTC。查了半天才发现是SID编码错了。记住,SID是协议规定的,一个字都不能差。另外,OEM自定义的SID通常从0xC0开始,但一定要在文档里明确标注,避免和标准SID冲突。
3.4 实战中的SID处理:代码怎么写?
在MCU上实现UDS诊断时,SID的处理是诊断请求解析的第一步。我一般会用一个switch-case结构来分发不同的服务。下面是一个简化的示例:
/* 诊断请求处理函数 */
void UDS_ProcessRequest(uint8_t *request, uint16_t length)
{
uint8_t sid = request[0]; /* 第一个字节就是SID */
switch(sid)
{
case 0x10: /* 诊断会话控制 */
UDS_HandleSessionControl(request, length);
break;
case 0x22: /* 读取数据 */
UDS_HandleReadData(request, length);
break;
case 0x2E: /* 写入数据 */
UDS_HandleWriteData(request, length);
break;
case 0x19: /* 读取DTC信息 */
UDS_HandleReadDTC(request, length);
break;
default:
/* 不支持的SID,返回否定响应 */
UDS_SendNegativeResponse(sid, NRC_SERVICE_NOT_SUPPORTED);
break;
}
}
这段代码看起来简单,但实际项目中要考虑的东西很多。比如,有些服务需要在特定会话模式下才能执行,有些需要安全访问通过后才能操作。这些逻辑都要在switch-case里做判断。
我的建议:在写诊断栈之前,先把所有要支持的SID列一个清单,标注好强制/可选、所属大类、依赖条件。这样写代码时就不会遗漏。我习惯用Excel管理这个清单,每次评审时拿出来过一遍,确保没有遗漏。