第3章:OBD-II协议栈三剑客——ISO 15765-4、ISO 14230、ISO 9141-2对比

各位同学,今天我们来聊聊OBD-II协议栈里的三个核心协议。说实话,我刚入行那会儿,看到这三个协议也是一头雾水。ISO 15765-4、ISO 14230、ISO 9141-2,名字长得像三胞胎,但脾气秉性完全不同。

我当年在做一个车载终端项目时,就因为搞混了KWP2000和K线的时序,导致ECU死活不响应。后来查了三天资料,才发现是协议栈选错了。嗯,这种坑,我希望你们能绕过去。

3.1 为什么会有三种协议?

说白了,这是汽车工业发展的历史产物。就像手机从功能机到智能机,OBD协议也经历了几个阶段:

  • ISO 9141-2(K线协议):90年代的老将,欧洲车的最爱
  • ISO 14230(KWP2000):K线的升级版,增加了更多功能
  • ISO 15765-4(CAN上的OBD):2000年后的新贵,现在的主流

你想想看,现在的新车基本都用CAN总线了。但路上跑的老车、商用车,可能还在用K线。所以作为嵌入式工程师,这三个协议你都得会。

3.2 物理层对比:K线 vs CAN

先看最底层的物理层差异。我习惯用一张表来对比:

特性 ISO 9141-2 (K线) ISO 14230 (KWP2000) ISO 15765-4 (CAN)
物理介质 单线(K线)+ L线 单线(K线)+ L线 双绞线(CAN-H, CAN-L)
波特率 10.4 kbps 1.2 ~ 10.4 kbps 250 kbps / 500 kbps
电压电平 0V / 12V 0V / 12V 2.5V / 3.5V(差分)
最大节点数 约16个 约16个 约110个
抗干扰能力 一般 一般 优秀

看到没?CAN的波特率是K线的几十倍。我在项目中遇到过,用K线读一个故障码要等几百毫秒,换成CAN几乎是秒回。这就是为什么新车都转向CAN了。

重要提示:ISO 9141-2和ISO 14230虽然都用K线,但初始化时序不同。千万别混用!

3.3 数据链路层:帧格式的差异

这一层决定了数据怎么打包发送。我们来看看各自的帧结构:

3.3.1 ISO 9141-2 的帧格式

K线的帧结构比较简单,说白了就是串口通信的变种:

起始位(1bit) + 数据位(8bit) + 校验位(1bit) + 停止位(1bit)

每个字节之间有个间隔时间(P4时间),这个时间很关键。我记得有一次调试,就是因为P4时间设短了,ECU一直报错。

3.3.2 ISO 14230 的帧格式

KWP2000在K线基础上增加了更多控制字节:

FMT(1byte) + TGT(1byte) + SRC(1byte) + DATA(N bytes) + CS(1byte)
  • FMT:格式字节,包含地址模式和长度信息
  • TGT:目标地址
  • SRC:源地址
  • DATA:实际数据
  • CS:校验和

你想想看,KWP2000比9141-2多了地址字段,这就意味着它可以支持多节点通信了。

3.3.3 ISO 15765-4 的帧格式

CAN的帧结构就复杂多了,但效率也高得多:

SOF(1bit) + ID(11/29bit) + RTR(1bit) + IDE(1bit) + DLC(4bit) + DATA(0-8bytes) + CRC(15bit) + ACK(2bit) + EOF(7bit)

这里要注意,OBD-II在CAN上用的是11位标准ID,不是29位扩展ID。我见过有人搞混这个,结果报文发出去ECU根本不认。

实战技巧:CAN的ID分配是有讲究的。功能寻址用0x7DF,物理寻址用0x7E0-0x7E7。这个在ISO 15765-4里有明确规定。

3.4 应用层:诊断服务的异同

到了应用层,三个协议反而统一了。它们都遵循ISO 15031(或SAE J1979)定义的诊断服务:

服务ID 服务名称 说明
0x01 请求当前动力系统数据 读车速、转速、水温等
0x02 请求冻结帧数据 故障发生时的快照
0x03 请求排放相关故障码 读DTC
0x04 清除/复位故障码 清DTC和冻结帧
0x05 请求氧传感器监测结果 仅用于非CAN协议
0x06 请求车载监测系统结果 就绪状态等
0x07 请求当前/待定故障码 连续监测的DTC
0x08 请求控制车载系统 执行器测试
0x09 请求车辆信息 VIN、CALID等
0x0A 请求永久故障码 不可清除的DTC

看到没?服务ID是通用的。但底层怎么打包这些请求,三个协议就不同了。

3.5 初始化流程对比

这是最容易出坑的地方。我一个个说:

3.5.1 ISO 9141-2 初始化

  1. 工具发送5波特率的地址字节(0x33)
  2. 等待ECU回复同步字节(0x55)
  3. 工具发送关键字字节(0x08, 0x08)
  4. ECU回复关键字字节(0x08, 0x08)
  5. 建立通信

我曾经在调试时发现,有些老款标致车的K线初始化时序特别慢,需要等2秒以上。如果你用固定的超时时间,就会失败。

3.5.2 ISO 14230 初始化

KWP2000有两种初始化方式:

  • 5波特率初始化:和9141-2类似,但地址字节不同
  • 快速初始化:直接发送唤醒模式(0xC1, 0x33, 0xF1, 0x81, 0x66)

我建议用快速初始化,省时间。但要注意,有些老ECU不支持快速初始化,会直接忽略。

3.5.3 ISO 15765-4 初始化

CAN的初始化就简单多了:

  1. 发送功能寻址请求(0x7DF -> 0x7E8)
  2. 等待ECU回复
  3. 建立通信

说白了,CAN不需要那些花里胡哨的握手流程。这也是为什么CAN效率高的原因之一。

避坑指南:我曾经在开发一个OBD终端时,用了CAN的初始化流程去初始化K线设备,结果ECU直接死机了。后来不得不断电重启。所以,一定要先检测车辆支持哪种协议,再选择对应的初始化流程。

3.6 实际项目中的选择策略

那么问题来了:实际开发中,我们该怎么选?

我的经验是这样的:

  • 新车(2010年后):优先用ISO 15765-4(CAN),速度快,稳定
  • 老车(2000-2010年):尝试ISO 14230(KWP2000),兼容性好
  • 古董车(2000年前):只能用ISO 9141-2(K线)

但现实往往更复杂。有些2008年的车,明明有CAN总线,但OBD接口还是K线。为什么?因为厂家偷懒,没把OBD接口接到CAN总线上。

所以,我建议你的终端要支持自动协议检测。流程大概是:

  1. 先尝试CAN(ISO 15765-4)
  2. 如果没响应,尝试KWP2000快速初始化
  3. 如果还不行,尝试K线5波特率初始化
  4. 最后尝试ISO 9141-2

这个顺序是我踩了无数坑总结出来的。你想想看,CAN的初始化最快,所以先试它。KWP2000快速初始化次之。最慢的9141-2放最后。

3.7 代码示例:协议检测实现

最后,我给你们一个简单的协议检测伪代码:

// 协议检测函数
OBDProtocol detectProtocol() {
    // 尝试CAN
    if (canInit() == SUCCESS) {
        return ISO_15765_4;
    }
    
    // 尝试KWP2000快速初始化
    if (kwpFastInit() == SUCCESS) {
        return ISO_14230;
    }
    
    // 尝试K线5波特率初始化
    if (kLine5BaudInit() == SUCCESS) {
        // 进一步判断是KWP2000还是9141-2
        if (isKWP2000()) {
            return ISO_14230;
        } else {
            return ISO_9141_2;
        }
    }
    
    return UNKNOWN;
}

注意,这里的isKWP2000()函数需要根据ECU回复的关键字来判断。KWP2000的关键字是0x08 0x08,而9141-2的关键字是0x94 0x94。这个细节很容易被忽略。

好了,这一章的内容就到这里。三种协议各有千秋,但核心思想是一样的:读取车辆数据,诊断故障。下一章我们会深入CAN上的OBD,讲讲报文过滤和ID分配的那些事。