车载诊断系统定制:从协议到实战

车载诊断,说白了就是车子的“体检系统”。你想想看,一辆车跑在路上,发动机、变速箱、电池、传感器……这么多部件协同工作,万一哪个出了问题,怎么快速定位?这就是诊断系统要干的事。

我个人习惯把车载诊断分成两个层面:一个是法规强制的,比如OBD-II,所有量产车必须支持;另一个是厂家自定义的,比如UDS,用来做更深入的诊断和刷写。今天我们就从这两个协议入手,聊聊怎么在Android系统里把它们集成好。

OBD-II:法规的底线

OBD-II是1996年之后美国法规强制要求的,欧洲和国内也都有类似标准。它主要关注排放相关的部件,比如氧传感器、催化器、燃油系统。协议本身很简单,基于CAN总线,使用11位ID,标准诊断请求格式是02 01 XX这种。

我在项目中遇到过一个问题:某款车机在读取OBD-II数据时,总是超时。后来发现是CAN总线负载太高,OBD请求被优先级更高的报文挤掉了。解决方案是给诊断报文分配更高的CAN优先级,同时增加重试机制。

核心要点:OBD-II的PID(参数ID)是标准化的,比如01 00表示支持的PID列表,01 05表示发动机冷却液温度。你不需要自己定义,但需要实现完整的PID查询逻辑。

UDS:厂家的利器

UDS(统一诊断服务)比OBD-II灵活得多。它定义了一套服务,比如10(诊断会话控制)、22(读取数据)、2E(写入数据)、31(例程控制)等等。你可以用UDS做任何事情:读取故障码、刷写固件、校准传感器、甚至解锁隐藏功能。

UDS的请求格式是:SID + SubFunction + Data。比如读取VIN码,请求是22 F1 90,响应是62 F1 90 + VIN数据

// 一个简单的UDS请求示例
byte[] request = new byte[] {
    0x22,       // SID: ReadDataByIdentifier
    0xF1, 0x90  // DID: VIN码
};
// 发送到CAN总线
canBus.send(request);
// 等待响应
byte[] response = canBus.receive();
// 解析响应
if (response[0] == 0x62) {
    String vin = new String(response, 3, 17);
    Log.d("UDS", "VIN: " + vin);
}
我的经验:UDS的DID(数据标识符)是厂家自定义的。我建议你建立一个DID映射表,把物理含义和DID编号对应起来。这样上层应用只需要知道“我要读VIN”,底层自动映射到0xF190,维护起来方便很多。

诊断服务集成:Android端的架构

在Android系统里集成诊断服务,我推荐分层架构:

  • 应用层:诊断APP,展示故障码、传感器数据、执行诊断操作。
  • 服务层:一个系统级Service,封装UDS/OBD-II的请求响应逻辑,提供AIDL接口给APP调用。
  • 协议层:处理CAN通信,解析UDS/OBD-II协议帧,处理多帧传输。
  • 硬件层:CAN控制器驱动,通过SocketCAN或JNI访问。

我习惯在服务层做一个诊断会话管理器。为什么?因为UDS有会话状态:默认会话、扩展会话、编程会话。不同会话下支持的服务不同。比如刷写固件必须在编程会话下进行。管理器负责维护当前会话,自动处理会话切换。

// 诊断会话管理器伪代码
public class DiagnosticSessionManager {
    private int currentSession = 0x01; // 默认会话
    
    public boolean switchSession(int targetSession) {
        // 发送10 03切换到扩展会话
        byte[] request = {0x10, (byte)targetSession};
        byte[] response = sendAndWait(request);
        if (response[0] == 0x50 && response[1] == targetSession) {
            currentSession = targetSession;
            return true;
        }
        return false;
    }
    
    public boolean isSessionSupported(int serviceId) {
        // 检查当前会话是否支持该服务
        // 比如编程会话才支持刷写
    }
}

故障码管理:DTC的存储与读取

故障码(DTC)是诊断的核心输出。每个DTC对应一个5位编码,比如P0101表示“空气流量计电路范围/性能问题”。

我建议在系统里维护一个DTC数据库,包含:

字段 说明 示例
DTC码 5位编码 P0101
描述 中文描述 空气流量计电路范围/性能
严重等级 1-5级 3
触发条件 什么情况下会报这个码 流量计电压超出范围
修复建议 给维修人员的提示 检查空气流量计及线路

读取DTC时,UDS使用19服务。比如请求所有故障码:19 02。响应会返回DTC列表和状态(当前故障/历史故障)。

注意:DTC有状态位,比如“当前故障”、“历史故障”、“待定故障”。不要一读到DTC就报给用户。我见过一个案例,某个传感器偶尔抖动一下,报了个历史故障,结果用户天天看到报警,其实车完全正常。建议只显示“当前故障”给用户,历史故障留给维修人员查看。

远程诊断与日志上传

远程诊断是现在车联网的标配。用户不用去4S店,厂家就能远程读取故障码、甚至刷写固件。

实现思路是这样的:

  1. 车机端运行一个远程诊断代理,监听云端下发的诊断任务。
  2. 云端下发一个诊断请求(比如“读取所有故障码”)。
  3. 代理调用本地UDS服务,执行诊断。
  4. 结果打包成JSON或Protobuf,上传到云端。

日志上传也是类似。我建议把日志分成两类:

  • 系统日志:Android logcat、kernel log、CAN总线日志。用于排查系统问题。
  • 诊断日志:UDS请求/响应记录、DTC变化记录、传感器数据快照。用于分析车辆问题。

上传策略上,我踩过一个坑:日志文件太大,上传时占满带宽,影响用户正常使用。后来改成增量上传,只上传变化的部分,并且限制上传速度,不超过总带宽的20%。

// 远程诊断任务示例
{
    "taskId": "12345",
    "type": "READ_DTC",
    "params": {
        "session": "EXTENDED",
        "dtcMask": 0xFF
    }
}

// 响应示例
{
    "taskId": "12345",
    "status": "SUCCESS",
    "data": [
        {"dtc": "P0101", "status": "CURRENT"},
        {"dtc": "P0300", "status": "HISTORY"}
    ]
}

知识体系总览

下面这张图总结了车载诊断系统的核心模块和它们之间的关系。你可以把它当作一个架构参考图来用。

车载诊断系统架构总览 应用层 诊断APP | 远程诊断客户端 | 日志上传服务 服务层 诊断会话管理器 | DTC数据库 | 远程诊断代理 协议层 UDS协议解析 | OBD-II协议解析 | 多帧传输处理 硬件层 CAN控制器驱动 | SocketCAN | JNI接口 分层解耦,每层只依赖下层接口

嗯,诊断系统这块内容确实不少。从OBD-II的法规要求,到UDS的灵活定制,再到远程诊断的云端协同,每一步都有坑要踩。我个人觉得,最关键的还是把协议层和服务层做扎实了,上层应用再怎么变,底层都能稳得住。

如果你正在做车载诊断的定制,建议先从OBD-II入手,把基础CAN通信调通,再逐步加入UDS支持。别一上来就想搞远程刷写,先把故障码读准了再说。

一个小技巧:调试UDS时,我习惯用CANalyzer或PCAN抓总线数据,对比车机发出的请求和ECU返回的响应。很多时候问题出在请求格式不对,或者会话没切换成功。抓包一看就明白了。

公众号:蓝海资料掘金营,微信deep3321