3. 诊断协议栈入门
在车载网络HIL与诊断测试工程中,诊断协议栈是连接测试上位机与ECU内部诊断服务的“语言层”。同一根以太网线或CAN总线上,可能同时跑着OBD-II排放诊断、UDS统一诊断服务和DoIP诊断传输。理解它们各自负责什么、如何寻址、如何切换会话、如何解锁安全等级,是后续编写诊断测试用例、搭建HIL台架和排查诊断失败的基础。
一、三种协议的分工与承载关系
OBD-II是法规驱动的排放相关诊断,服务号固定、数据以PID(Parameter ID)组织,典型请求如02 01 0C读取发动机转速,响应返回41 01 0C 1A F8,其中1A F8按公式换算为转速。它面向通用扫描工具,寻址方式简单,通常使用功能寻址广播请求,ECU各自应答。
UDS(ISO 14229)是面向整车研发与售后的统一诊断服务,服务号从0x10到0x3E等,覆盖会话控制、安全访问、读写数据、例程控制、DTC读取等。UDS本身只定义应用层服务,不绑定具体总线,可以跑在CAN、CAN FD、FlexRay或以太网上。
DoIP(ISO 13400)解决的是“UDS如何走以太网”的问题。它在TCP/IP之上定义诊断报文封装、车辆发现、路由激活和诊断电源模式。诊断仪先通过UDP向车辆广播车辆识别请求,获得车辆识别响应后建立TCP连接,再发送路由激活请求,之后才能传输UDS诊断报文。
二、核心服务与典型请求响应
UDS的核心服务可以按用途分组。会话控制0x10用于切换默认会话、编程会话和扩展会话;安全访问0x27通过种子密钥机制解锁受保护服务;读写数据0x22/0x2E按DID访问数据;例程控制0x31启动或停止ECU内部例程;DTC读取0x19获取故障码及快照;通信控制0x28控制报文收发;刷写相关服务包括0x34/0x36/0x37。
以读取VIN为例,请求为22 F1 90,正响应为62 F1 90后跟17字节VIN。若ECU未处于支持该DID的会话,会返回否定响应7F 22 31,其中31表示requestOutOfRange;若安全未解锁,则返回7F 22 33,33表示securityAccessDenied。这些否定响应码是诊断测试中判断失败原因的关键依据。
三、会话管理与安全访问的状态迁移
ECU上电后默认处于默认会话(0x01),此时只能执行基础诊断。要执行写入或刷写,必须先切换到扩展会话(0x03)或编程会话(0x02)。会话切换有超时机制,通常由S3定时器控制,默认约5秒无诊断请求则自动回落到默认会话。因此测试脚本在长流程中需要周期性发送3E 00(TesterPresent)维持会话。
安全访问采用种子密钥挑战。以0x27服务为例,子功能0x01请求种子,ECU返回67 01加种子;诊断仪用约定算法计算密钥,再发送27 02加密钥。若密钥正确,返回67 02;若错误,返回7F 27 35(invalidKey)。连续错误通常触发延时锁定,需要等待或重新上电。安全等级分为多个级别,不同级别解锁不同的DID和例程。
四、诊断寻址方式
诊断寻址决定了报文发给谁、谁来回。物理寻址是点对点,请求发往特定ECU的地址,只有该ECU响应,适合读取特定DID或刷写。功能寻址是广播,多个ECU可同时响应,适合OBD排放查询或UDS的3E 00维持会话。在CAN上,物理寻址使用不同的CAN ID区分请求和响应;在DoIP上,使用逻辑地址(如诊断仪0x0E00、ECU 0x0001)加路由激活来建立点对点通道。
DoIP的车辆发现流程值得单独关注:诊断仪向UDP 13400端口广播车辆识别请求0x0001,车辆返回车辆识别响应0x0004,其中包含VIN、逻辑地址和进一步动作信息。随后诊断仪与目标ECU建立TCP连接,发送路由激活请求0x0005,激活成功后返回0x0006,此时才能发送UDS诊断报文。若路由激活失败,常见原因是逻辑地址不匹配或车辆未处于诊断电源模式。
五、HIL测试中的实操要点
在HIL台架上,诊断测试通常通过CANoe、vTESTstudio或Python的udsoncan库实现。以下是一个使用Python udsoncan读取VIN的示例片段,展示会话切换、安全访问和DID读取的典型顺序:
from udsoncan.client import Client
from udsoncan.connections import PythonIsoTpConnection
import isotp
# 建立ISO-TP连接,CAN ID按实际DBC配置
conn = PythonIsoTpConnection(isotp.TransportLayer(
rxid=0x7E8, txid=0x7E0, interface="socketcan", channel="can0"))
with Client(conn, request_timeout=2) as client:
# 1. 切换到扩展会话
client.change_session(0x03)
# 2. 安全访问:请求种子并发送密钥
seed = client.request_seed(0x01)
key = bytes([b ^ 0xFF for b in seed]) # 示例算法,实际按ECU规范
client.send_key(0x02, key)
# 3. 读取VIN
vin = client.read_data_by_identifier(0xF190)
print("VIN:", vin.service_data.values[0xF190].decode())
这段代码的关键观察点在于:change_session返回正响应50 03后才继续;request_seed返回的种子长度和内容需与ECU规范一致;send_key后若返回67 02表示解锁成功。若任一步返回否定响应,应打印完整的7F响应码,结合NRC表定位问题,而不是直接重试。
总结来看,OBD-II、UDS和DoIP分别对应法规诊断、统一诊断服务和以太网诊断传输。理解它们的服务号、会话状态、安全等级和寻址方式,才能在HIL台架上正确构造请求、解读响应,并在诊断失败时快速定位到会话、安全或路由层面的原因。