3、UDS诊断服务基础:UDS服务分类(强制/可选)、常用服务ID(SID)介绍、诊断会话控制(0x10服务)

好,咱们正式开始聊UDS诊断服务。这一节是基础中的基础,但也是整个Bootloader诊断功能的骨架。你想想看,ECU刷写、故障排查、产线测试,哪一样离得开UDS?我个人习惯是把UDS服务先分个类,心里有个谱,后面学起来就顺了。

3.1 UDS服务分类:强制与可选

UDS(ISO 14229)定义了一大堆诊断服务,但不是每个服务都必须实现。标准里把它们分成了三类:强制服务条件服务可选服务。说白了,强制服务是底线,你不实现它,诊断仪连门都进不来。

强制服务(Mandatory)

  • 诊断会话控制(0x10)—— 必须要有,这是入口
  • 安全访问(0x27)—— 必须要有,这是锁
  • 通信控制(0x28)—— 必须要有,用于控制报文收发
  • 测试仪在线(0x3E)—— 必须要有,保持会话不超时
  • ECU复位(0x11)—— 必须要有,刷完要重启

条件服务(Conditional)

  • 比如读取数据(0x22)、写入数据(0x2E),如果你的ECU需要标定或读取内部参数,那就得实现
  • 例程控制(0x31),如果你的ECU需要执行特定算法(比如擦除Flash),那就得实现

可选服务(Optional)

  • 比如读取故障码(0x19)、清除故障码(0x14),如果你的ECU不需要诊断故障码,可以不做
  • 但说实话,现在几乎没有ECU不做故障码的,所以实际上大部分可选服务也成了事实上的标配

重要提醒:在Bootloader中,我们通常只实现刷写相关的服务。比如0x10、0x27、0x28、0x3E、0x11、0x22、0x2E、0x31、0x34、0x36、0x37。其他服务(如0x19)可以放在应用层实现。

3.2 常用服务ID(SID)介绍

UDS服务ID是一个字节,范围从0x00到0xFF。但常用的就那么十几个。我整理了一张表,你直接拿去用:

SID 服务名称 强制/可选 Bootloader中是否常用
0x10 诊断会话控制 强制
0x11 ECU复位 强制
0x22 读取数据 条件 是(读版本号等)
0x27 安全访问 强制
0x28 通信控制 强制
0x2E 写入数据 条件 是(写配置等)
0x31 例程控制 条件 是(擦除/检查Flash)
0x34 请求下载 条件
0x36 数据传输 条件
0x37 请求退出传输 条件
0x3E 测试仪在线 强制

嗯,这里要注意:SID的bit6(0x40)是响应位。比如你发0x10请求,ECU正常响应时返回0x50。这个设计挺巧妙的,一眼就能看出是请求还是响应。

3.3 诊断会话控制(0x10服务)

0x10服务是所有诊断服务的入口。没有它,其他服务都用不了。为什么?因为ECU上电后默认处于默认会话(Default Session),在这个会话里,大部分诊断服务是被禁用的。你想想看,如果随便一个报文就能让ECU进入刷写模式,那车在路上开着开着突然被刷了,多危险。

0x10服务的请求格式:

请求:0x10 + 子功能(会话类型)
响应:0x50 + 子功能 + P2_Server_Max + P2_Star_Server_Max

常见的会话类型:

  • 0x01:默认会话(Default Session)—— 上电后的初始状态,功能受限
  • 0x02:编程会话(Programming Session)—— 刷写专用,Bootloader里最常用
  • 0x03:扩展会话(Extended Session)—— 用于标定、调试,功能比默认多但比编程少
  • 0x04:安全系统诊断会话(Safety System Diagnostic Session)—— 用于安全相关系统

个人经验:我在项目中遇到过一个问题:ECU从编程会话切回默认会话时,没有正确清理资源,导致下次进入编程会话时Flash操作失败。后来我加了一个会话退出时的资源清理函数,问题就解决了。所以,会话切换时一定要做好状态机管理

0x10服务的响应中,P2_Server_Max和P2_Star_Server_Max是什么意思?

这两个参数告诉诊断仪:ECU处理一个请求需要多长时间。P2_Server_Max是正常响应时间,默认50ms。P2_Star_Server_Max是扩展响应时间,默认2000ms。如果ECU需要更长时间处理(比如擦除Flash),就会先发一个0x78的挂起响应,告诉诊断仪「别急,我在忙」。我曾经调试过一个Bug,就是P2_Star时间设得太短,导致诊断仪超时重发请求,结果ECU同时处理两个擦除命令,直接挂了。

0x10服务的状态机流转:

默认会话 (0x01)
    |
    ├── 请求0x10 0x02 → 进入编程会话
    ├── 请求0x10 0x03 → 进入扩展会话
    └── 请求0x10 0x01 → 保持在默认会话(或从其他会话切回)

编程会话 (0x02)
    |
    ├── 请求0x10 0x01 → 切回默认会话
    ├── 请求0x10 0x03 → 切换到扩展会话
    └── 请求0x10 0x02 → 保持在编程会话

扩展会话 (0x03)
    |
    ├── 请求0x10 0x01 → 切回默认会话
    ├── 请求0x10 0x02 → 切换到编程会话
    └── 请求0x10 0x03 → 保持在扩展会话

避坑指南:我曾经遇到过一个案例:ECU在编程会话中收到了一个非预期的0x10请求(比如切到扩展会话),结果刷写流程被打断了,导致Flash数据损坏。所以,在Bootloader中,一定要对会话切换做严格的权限检查。比如,正在刷写时,只允许切回默认会话,不允许切到其他会话。

代码示例:0x10服务处理函数(简化版)

// 诊断会话控制服务处理
uint8_t DiagSessCtrl_Handle(uint8_t subFunc)
{
    uint8_t response[4];
    
    switch(subFunc)
    {
        case 0x01:  // 默认会话
            g_activeSession = SESSION_DEFAULT;
            // 关闭编程相关功能
            Flash_Disable();
            break;
            
        case 0x02:  // 编程会话
            // 检查是否允许进入编程会话
            if(g_appStatus == APP_STATUS_RUNNING)
            {
                // 应用运行时,需要先请求进入编程模式
                return NRC_CONDITIONS_NOT_CORRECT;
            }
            g_activeSession = SESSION_PROGRAMMING;
            // 初始化Flash驱动
            Flash_Init();
            break;
            
        case 0x03:  // 扩展会话
            g_activeSession = SESSION_EXTENDED;
            break;
            
        default:
            return NRC_SUB_FUNCTION_NOT_SUPPORTED;
    }
    
    // 构建响应:0x50 + 子功能 + P2时间 + P2*时间
    response[0] = 0x50;
    response[1] = subFunc;
    response[2] = 0x32;  // P2_Server_Max = 50ms
    response[3] = 0xE8;  // P2_Star_Server_Max = 2000ms
    
    // 发送响应
    CanTp_SendResponse(response, 4);
    
    return 0;
}

好了,这一节的内容就这些。0x10服务看似简单,但它是整个诊断体系的基石。你想想看,没有会话管理,ECU就像一扇没有锁的门,谁都能进。下一节我们聊安全访问(0x27服务),那是真正的「锁」。