4、DoIP诊断通信:基于DoIP的UDS诊断、诊断请求与响应、诊断会话控制、安全访问机制

好,我们进入第四章。这一章可以说是整个远程刷写方案的核心通信层。你想想看,刷写说白了就是往ECU里写数据,但怎么保证写进去的数据是对的?怎么保证不是随便谁都能写?这就得靠UDS诊断协议来管了。而DoIP,就是UDS在以太网上的“运输车”。

我个人习惯把DoIP诊断通信拆成四个部分来理解:UDS请求响应的基本格式、会话控制、安全访问,以及它们如何在DoIP的帧结构里跑起来。咱们一个一个来。

4.1 UDS诊断请求与响应:一问一答的规矩

UDS(统一诊断服务)本质上是一种“请求-响应”式的通信协议。客户端(比如你的诊断仪或刷写工具)发一个请求,服务器(ECU)回一个响应。就这么简单。

但这里有个关键点:请求和响应必须一一对应。你不能发完请求就不管了,ECU也不会主动给你发消息。我在项目中遇到过一个问题,有个同事写了个刷写脚本,连续发了三个请求,结果ECU只处理了第一个,后面两个全丢了。为什么?因为ECU的接收缓冲区就那么点大,你发太快它来不及处理。

UDS请求报文格式:

  • SID(服务标识符):1字节,告诉ECU你要干什么。比如0x10是诊断会话控制,0x27是安全访问,0x2E是写入数据。
  • 子功能(Sub-function):1字节,可选。比如0x10服务下的0x01表示“默认会话”,0x02表示“编程会话”。
  • 数据参数(Data):N字节,根据具体服务而定。

响应报文也类似,但SID要加上0x40。比如请求SID是0x10,那正常响应SID就是0x50。如果ECU处理不了,它会回一个否定响应,SID是0x7F,后面跟着原请求的SID和一个错误码(NRC)。

举个例子,你发一个诊断会话控制请求:

请求:02 10 03
解释:
02 - 数据长度(2字节)
10 - 服务ID(诊断会话控制)
03 - 子功能(扩展会话)

响应:02 50 03
解释:
02 - 数据长度
50 - 响应SID(0x10 + 0x40)
03 - 子功能(确认进入扩展会话)

嗯,这里要注意:否定响应的NRC码是调试时的关键线索。比如0x22表示“条件不满足”,0x31表示“请求超出范围”。我曾经花了一整天查一个刷写失败的问题,最后发现是NRC 0x22——ECU当时正在跑一个关键任务,不允许进入编程会话。

4.2 诊断会话控制:ECU的三种工作模式

ECU不是什么时候都让你刷写的。它有三种会话模式,你可以理解为三种“权限等级”:

会话模式 SID/子功能 说明
默认会话 0x10 01 上电后的默认状态,只允许读一些基本信息
扩展会话 0x10 03 允许读写部分数据,但还不能刷写
编程会话 0x10 02 最高权限,允许刷写、解锁、复位等操作

你想想看,如果ECU一上电就允许刷写,那多危险。万一哪个诊断仪发了个错误的刷写请求,车就废了。所以刷写前,你必须先切换到编程会话。

切换过程很简单:发一个0x10 02请求,ECU回一个0x50 02。但这里有个坑——会话切换后,ECU可能会复位某些功能。比如我之前调试一个项目,切换到编程会话后,ECU的CAN通信突然断了。查了半天才发现,编程会话会禁用某些非必要的通信通道,包括部分CAN报文。所以切换会话后,一定要确认通信链路是否还正常。

避坑指南:我曾经在切换会话后直接发刷写请求,结果ECU没响应。后来发现是会话切换后,ECU需要一点时间(通常几十毫秒)来重新初始化内部状态。所以切换会话后,建议等100ms再发下一个请求。

4.3 安全访问机制:不是谁都能刷的

就算你进入了编程会话,ECU也不会让你直接刷写。它还需要你通过“安全访问”验证。说白了,就是ECU要确认你是不是“自己人”。

安全访问的流程是这样的:

  1. 请求种子(Seed):客户端发0x27 01(请求种子),ECU回一个随机数(种子)。
  2. 发送密钥(Key):客户端用约定的算法,把种子算出一个密钥,发0x27 02(发送密钥)。
  3. 验证结果:ECU用同样的算法算一遍,如果匹配,就回0x67 02表示解锁成功。

这个算法通常是OEM自定义的,有的是简单的异或运算,有的是复杂的加密算法。我见过最离谱的是用了一个查表法,密钥表有256个字节,每次算密钥都要查三次表。嗯,这种设计虽然安全,但刷写工具也得跟着维护这个表,挺麻烦的。

重要提醒:安全访问的种子和密钥都是明文传输的。如果你用的是DoIP,数据包在以太网上跑,理论上可以被抓包。所以有些OEM会在DoIP层之上再加一层TLS加密。我个人建议,如果刷写数据涉及车辆安全,一定要上加密。

还有一个细节:安全访问有次数限制。比如连续3次密钥错误,ECU会锁定安全访问功能一段时间(比如10秒)。这是为了防止暴力破解。我在测试时遇到过,脚本写错了,连续发了5次错误密钥,结果ECU锁了30秒。那30秒里我什么都干不了,只能干等。

4.4 DoIP帧结构:UDS在以太网上的封装

好了,现在UDS请求和响应都有了,会话也切换了,安全也解锁了。但这些东西怎么在以太网上传输呢?这就得靠DoIP协议来封装了。

DoIP的帧结构是这样的:

| 协议版本 (1字节) | 反向协议版本 (1字节) | 负载类型 (2字节) | 负载长度 (4字节) | 负载 (N字节) |

其中,负载类型决定了这个DoIP帧是干什么的。常见的类型有:

  • 0x0001:路由激活请求/响应(用于建立诊断连接)
  • 0x8001:诊断消息(UDS请求/响应就封装在这里)
  • 0x0004:Alive Check(保活消息,防止连接超时)

当你要发一个UDS请求时,DoIP帧的负载部分就是UDS报文。比如前面那个0x10 03请求,封装成DoIP帧就是:

02 FD 80 01 00 00 00 02 02 10 03
解释:
02 - 协议版本(ISO 13400-2)
FD - 反向协议版本(0xFF - 0x02)
80 01 - 负载类型(诊断消息)
00 00 00 02 - 负载长度(2字节)
02 10 03 - 负载(UDS报文:长度+SID+子功能)

这里有个容易出错的地方:负载长度是UDS报文的长度,不是整个DoIP帧的长度。我见过有人把整个帧的长度写进去,结果ECU解析失败,直接丢包。嗯,这个坑我踩过,后来写代码时专门加了个断言来检查。

4.5 实战中的通信流程

最后,我把整个DoIP诊断通信的流程串起来,给你一个完整的例子:

  1. 建立TCP连接:客户端连ECU的13400端口。
  2. 路由激活:发0x0005请求,ECU回0x0006响应,确认诊断连接建立。
  3. 切换会话:发0x10 03(扩展会话),ECU回0x50 03。
  4. 安全访问:发0x27 01请求种子,ECU回种子;发0x27 02发送密钥,ECU回0x67 02解锁成功。
  5. 执行刷写:发0x34(请求下载)、0x36(传输数据)、0x37(请求退出传输)等UDS服务。
  6. 复位ECU:发0x11 01(硬复位),ECU重启。

每一步都要检查响应,如果收到否定响应,就要根据NRC码排查问题。我个人习惯在代码里加一个超时重试机制,比如每个请求等500ms,超时重试3次。如果3次都失败,就报错退出,防止刷写卡死。

总结一下:DoIP诊断通信的核心就是UDS协议在以太网上的封装。会话控制决定了你能做什么,安全访问决定了你是谁,而DoIP帧结构决定了数据怎么传。这三者缺一不可。下一章我们会讲具体的刷写流程,到时候你会看到这些机制是怎么配合起来的。