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图,帮你快速建立整体认知:

UDS 6大类服务功能概览 UDS 6大类服务功能概览 UDS服务(ISO 14229) 诊断管理类 0x10, 0x11, 0x27, 0x28, 0x3E 数据传输类 0x22, 0x23, 0x2E, 0x2F 存储类 0x14, 0x19, 0x2A 输入输出控制类 0x2F, 0x30 例行程序类 0x31 上传下载类 0x34, 0x35, 0x36, 0x37 说明 • 诊断管理类:会话控制、复位、安全访问、通信控制、测试仪在线 • 数据传输类:读取/写入数据、读取内存、输入输出控制 • 存储类:清除诊断信息、读取DTC信息、读取DTC状态 • 输入输出控制类:输入输出控制、远程激活 • 例行程序类:启动/停止例行程序 • 上传下载类:请求下载/上传、传输数据、请求传输退出

这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管理这个清单,每次评审时拿出来过一遍,确保没有遗漏。