第3章:UDS服务基础:安全访问、通信控制与测试仪在线

大家好,欢迎来到第三章。今天我们要聊的三个服务——0x27安全访问、0x28通信控制、0x3E测试仪在线——可以说是UDS诊断栈里最常用的“三剑客”。我个人习惯把它们放在一起讲,因为它们在很多场景下是配合使用的。你想想看,一个完整的诊断会话,往往是从安全解锁开始,然后控制通信,最后保持连接。嗯,咱们一个一个来拆解。

3.1 安全访问服务 (0x27) —— 诊断的“看门人”

安全访问服务,说白了就是给诊断加了一把锁。为什么需要它?因为不是谁都能随便改写ECU里的数据。比如刷写软件、标定参数、执行特殊功能,这些操作如果被恶意或误操作触发,后果很严重。我在项目中遇到过,有台车在产线上因为诊断仪没做安全校验,直接把标定数据写乱了,最后返工了好几天。

0x27服务的核心机制是“挑战-应答”。流程是这样的:

  1. 诊断仪发送0x27 + 请求种子(seed)的子功能,比如0x01表示请求种子。
  2. ECU返回一个随机种子(通常是4字节或8字节)。
  3. 诊断仪用约定的算法(比如AES、自定义多项式)计算密钥(key)。
  4. 诊断仪发送0x27 + 发送密钥的子功能(如0x02) + 计算出的密钥。
  5. ECU内部也计算一遍,比对一致则解锁成功。

重要: 种子和密钥的算法是OEM(整车厂)自己定义的,标准里只规定了流程,没规定算法。所以不同车企的算法千差万别,有的甚至用查表法。

这里有个细节要注意:安全等级。0x27服务支持多个安全等级,比如0x01/0x02是等级1,0x03/0x04是等级2。等级越高,能访问的权限越大。我建议在设计诊断栈时,把安全等级和具体的服务/子功能做一张映射表,这样代码逻辑清晰,也方便后期维护。

我的小技巧: 在实现种子生成时,建议加入时间戳或计数器,避免种子重复。我曾经见过一个项目,种子是固定的,结果被破解后整个ECU的安全防线就形同虚设了。

3.2 通信控制服务 (0x28) —— 诊断的“开关”

0x28服务,它的作用是控制ECU的通信行为。说白了,就是让ECU“闭嘴”或者“说话”。为什么需要这个?举个例子:你在做CAN总线测试时,如果ECU一直往外发报文,会干扰你的诊断通信。这时候,你可以用0x28服务让ECU暂时停止发送某些报文。

0x28服务有以下几个子功能:

子功能 含义 说明
0x00 启用Rx和Tx 恢复所有通信
0x01 禁用Rx和Tx 完全静默
0x02 启用Rx,禁用Tx 只收不发
0x03 禁用Rx,启用Tx 只发不收

你可能会问:“禁用Rx和Tx,那诊断通信还能继续吗?” 嗯,这里要注意:0x28服务控制的是应用报文,而不是诊断报文本身。也就是说,ECU仍然可以响应0x28请求,但其他非诊断的报文(比如周期发送的传感器数据)会被抑制。

避坑指南: 我曾经在项目里遇到一个问题:用0x28禁用Tx后,ECU的看门狗因为长时间没有发送报文而触发了复位。所以,在使用0x28服务时,一定要确认ECU的底层机制是否依赖周期性报文来维持正常运行。

3.3 测试仪在线服务 (0x3E) —— 诊断的“心跳”

0x3E服务,也叫“保持激活”服务。它的作用很简单:告诉ECU“诊断仪还在,别超时”。

UDS协议规定,ECU在非默认会话(比如扩展会话、编程会话)下,如果一段时间内没有收到任何诊断请求,就会自动跳回默认会话。这个时间通常由S3Server定时器控制,一般是5秒左右。0x3E服务就是用来重置这个定时器的。

0x3E服务只有一个子功能:0x00(请求保持激活)。它的响应也很简单,就是肯定响应码(0x7F + 0x3E + 0x00)。

关键点: 0x3E服务不需要安全访问解锁就可以使用。这是标准规定的,因为它的目的就是维持连接,而不是执行敏感操作。

在实际项目中,我建议诊断仪每隔2-3秒发送一次0x3E请求。为什么是这个频率?因为要留出余量。如果S3Server定时器是5秒,你4秒发一次,万一网络延迟一次,就可能超时。我个人习惯用2.5秒的间隔,既不会太频繁占用总线,又能保证连接稳定。

这里有个容易忽略的点:0x3E服务只对当前会话有效。如果你从扩展会话切换到编程会话,需要重新发送0x3E来维持新的会话。我记得有一次调试,测试人员发现ECU总是莫名其妙跳回默认会话,查了半天才发现是切换会话后忘了发0x3E。

3.4 三个服务的配合使用

在实际诊断流程中,这三个服务经常是串在一起的。我画一个典型的流程给你看:

  1. 诊断仪进入扩展会话(0x10 0x03)。
  2. 发送0x27服务,完成安全解锁。
  3. 发送0x28服务,禁用某些应用报文,避免干扰。
  4. 执行需要高权限的诊断操作(比如写入数据、执行例程)。
  5. 定时发送0x3E服务,保持会话不超时。
  6. 操作完成后,发送0x28服务恢复通信,或者发送0x11服务复位ECU。

这个流程看起来简单,但每个环节都有坑。比如,安全解锁失败后,ECU通常会有一个“延迟时间”,在延迟时间内不允许再次尝试解锁。这个延迟时间一般是1秒到10秒不等,具体看OEM定义。我曾经见过一个项目,测试人员连续快速发送解锁请求,结果ECU直接进入了“锁定状态”,必须断电重启才能恢复。

我的建议: 在实现诊断栈时,一定要把这三个服务的状态机设计好。比如,安全解锁状态、通信控制状态、会话状态,它们之间是相互影响的。我习惯用一个全局的状态结构体来管理,这样代码可读性高,也容易调试。

3.5 代码示例:0x3E服务的简单实现

最后,给你看一段简化的0x3E服务处理代码。这只是示意,实际项目中要考虑更多细节,比如超时处理、错误码等。

// 假设有一个全局的会话定时器
static uint32_t session_timer = 0;

// 处理0x3E请求
uint8_t handle_service_0x3E(uint8_t *request, uint16_t length) {
    // 检查子功能
    if (request[1] != 0x00) {
        // 子功能不支持,返回NRC 0x12
        return 0x12;
    }

    // 重置会话定时器
    session_timer = 0;

    // 返回肯定响应
    // 响应格式:0x7E + 0x00
    response[0] = 0x7E;
    response[1] = 0x00;
    return 0; // 表示成功
}

// 在主循环中调用,检查会话是否超时
void check_session_timeout(void) {
    session_timer++;
    if (session_timer > S3_SERVER_TIMEOUT) {
        // 超时,跳回默认会话
        switch_to_default_session();
    }
}

这段代码很简单,但核心逻辑就在那里:收到0x3E就重置定时器,定时器超时就切换会话。嗯,实际项目中你还要考虑多会话、多任务的情况,但原理是一样的。

好了,第三章的内容就到这里。这三个服务是UDS诊断栈的基石,理解透了,后面的高级服务学起来就轻松多了。下一章我们会聊0x22和0x2E服务——读写数据,到时候见。