3、OBD-II硬件接口与协议:J1850 PWM/VPW、ISO 9141-2、ISO 14230(KWP2000)、ISO 15765(CAN)的电气特性、帧格式与选择策略

OBD-II(On-Board Diagnostics II)自1996年起成为美国市场乘用车的法定标准,其核心价值在于统一了诊断座物理形态、引脚定义和通信协议。然而,OBD-II并非单一协议,而是由SAE J1962定义物理连接器,向下兼容多种数据链路层协议。在Android车载诊断开发中,无论是通过蓝牙ELM327适配器、USB转CAN模块,还是直接访问车载以太网网关,开发者都必须理解这些协议的电气特性、帧格式和适用场景。本章将系统拆解J1850 PWM/VPW、ISO 9141-2、ISO 14230(KWP2000)和ISO 15765(CAN)四大协议族,并给出Android端协议选择与适配的工程策略。

OBD-II 协议族电气特性与选择决策图 SAE J1962 诊断座 16-Pin DLC J1850 PWM 引脚: 2(Bus+) / 10(Bus-) 速率: 41.6 kbps 占空比: 50% 归零 适用: 福特/马自达 J1850 VPW 引脚: 2 (Bus+) 速率: 10.4 kbps 可变脉宽调制 适用: 通用/GM ISO 9141-2 引脚: 7(K线) / 15(L线) 速率: 10.4 kbps UART异步串行 适用: 欧亚/克莱斯勒 ISO 15765 CAN 引脚: 6(CAN-H) / 14(CAN-L) 速率: 250/500 kbps 差分信号 适用: 2008+ 全球主流 Android 端协议选择策略 1. 读取VIN码 → 查询车型年份/品牌 → 推断协议族 2. 尝试自动探测: 依次发送 0x00 请求 → 观察ACK 3. 2008年后车型优先走CAN; 老车回退到KWP2000/J1850 关键电气差异速查 J1850 PWM: 双线差分, 占空比编码 | VPW: 单线, 脉宽编码 | ISO 9141-2: 单线UART | CAN: 双线差分, 非归零 所有协议共享OBD-II引脚定义, 但物理层互不兼容, 需适配器或可切换收发器

上图展示了OBD-II协议族的整体决策框架。在实际Android开发中,我们通常通过蓝牙或USB连接外部诊断硬件,硬件内部集成了协议收发器。因此,软件层需要关注的是:如何正确初始化适配器、如何构造符合目标协议的数据帧、以及如何解析返回的响应。下面逐一深入各协议的电气特性和帧格式。

3.1 J1850 PWM与VPW:北美老牌协议

核心原理

J1850是SAE制定的车载网络标准,主要用于1994-2004年间的北美车型。它有两种变体:PWM(Pulse Width Modulation)和VPW(Variable Pulse Width)。PWM使用双线差分信号(Bus+和Bus-),以41.6kbps速率传输,占空比固定为50%归零编码;VPW则使用单线(Bus+),速率10.4kbps,通过脉冲宽度的变化表示逻辑0和1。两者均采用载波侦听多路访问/冲突检测(CSMA/CD)机制,但物理层完全不同,不能混用。

帧格式与电气参数

参数J1850 PWMJ1850 VPW
引脚定义2 (Bus+), 10 (Bus-)2 (Bus+)
波特率41.6 kbps10.4 kbps
逻辑1表示高电平占空比 50%高电平脉宽 64μs
逻辑0表示低电平占空比 50%高电平脉宽 128μs
帧头SOF (200μs 低电平)SOF (200μs 低电平)
数据长度0-8 字节0-8 字节
校验CRC-8CRC-8

J1850的报文格式包含优先级字段(3位)、帧类型(1位)、目标地址(8位)、源地址(8位)、数据长度(4位)和数据域。在Android端,如果通过ELM327芯片访问J1850,通常只需发送AT指令(如ATSP 1或ATSP 2)选择协议,芯片会自动处理底层编码。

实战场景:兼容老款福特车型

假设我们开发一款Android诊断App,需要支持2003款福特F-150。该车使用J1850 PWM协议。通过蓝牙ELM327连接后,初始化序列如下:

// 初始化ELM327为J1850 PWM模式
发送: ATZ          // 复位
发送: ATE0         // 关闭回显
发送: ATL0         // 关闭换行
发送: ATSP 1       // 选择J1850 PWM协议
发送: 0100         // 请求PID 00(支持的PID列表)
响应: 41 00 BE 1F B8 1F  // 成功响应

这里的关键点是,ELM327内部已经集成了J1850收发器,Android端只需要通过串口发送ASCII命令。但要注意,J1850 PWM的响应时间较慢(约50ms),且总线冲突概率较高,因此App需要设置合理的超时(建议500ms)和重试机制(最多3次)。

风险提示:J1850 VPW和PWM虽然引脚定义有重叠,但绝不能混用。如果错误地使用ATSP 1(PWM)连接VPW车辆,ELM327会一直无法同步,表现为无响应或乱码。此时应尝试ATSP 2(VPW)或使用自动协议检测(ATSP 0)。

常见误区与复盘

  • 误区1:认为所有OBD-II都支持CAN。实际上2008年前的许多车型仍使用J1850或ISO 9141-2。
  • 误区2:忽略总线仲裁机制。J1850的CSMA/CD意味着高优先级报文(如0x00)优先发送,低优先级可能被延迟。
  • 复盘:在开发中,建议先读取VIN(PID 0x0902),根据VIN的第10位(年份)和第11位(工厂)判断协议,再初始化适配器,可减少无效探测。

3.2 ISO 9141-2与ISO 14230(KWP2000):单线串行协议

核心原理

ISO 9141-2是早期欧洲和亚洲车型常用的诊断协议,基于UART异步串行通信,使用K线(引脚7)和可选的L线(引脚15)。K线用于双向数据传输,L线仅用于唤醒。ISO 14230(KWP2000)是ISO 9141-2的增强版,增加了更灵活的寻址方式和更快的初始化流程,但物理层仍基于K线。两者速率均为10.4kbps,但KWP2000支持更长的数据帧(最多255字节)和更复杂的服务原语。

帧格式与初始化流程

ISO 9141-2的初始化分为两种:5波特初始化(5 baud init)和快速初始化(Fast init)。5波特初始化通过发送0x33的5波特率字符唤醒ECU,然后进行地址分配;快速初始化则直接发送0x33的10.4kbps字节。KWP2000通常使用快速初始化,其地址分配和帧格式如下:

// KWP2000 帧格式
[格式字节] [目标地址] [源地址] [长度] [数据...] [校验和]
格式字节: 0x80 (单帧) / 0x81 (首帧) / 0x82 (连续帧)
示例: 80 10 F1 02 01 00 94
     格式=0x80, 目标=0x10(ECU), 源=0xF1(诊断仪), 长度=2, 数据=01 00, 校验=0x94

在Android端,如果使用ELM327,ATSP 3对应ISO 9141-2,ATSP 4对应KWP2000(ISO 14230)。但ELM327对KWP2000的支持存在一些限制,例如不支持长帧(超过8字节)的自动重组,需要手动处理多帧响应。

案例拆解:诊断2005款大众帕萨特

该车型使用KWP2000协议。我们通过USB转K线适配器(如STN1170)直接连接,而非ELM327,以获得更好的帧控制能力。初始化流程:

  1. 发送快速初始化字节 0x33,等待ECU响应(约50ms)。
  2. 发送地址分配请求:81 10 F1 03 01 3D 00 00,其中0x3D表示地址分配服务。
  3. 收到ECU确认后,发送读取VIN请求:80 10 F1 02 09 02。
  4. ECU返回多帧响应,需要根据格式字节重组:首帧0x81包含总长度,连续帧0x82按顺序拼接。

这里的关键是,KWP2000的校验和计算为所有字节(不含校验和本身)的累加和取低8位。Android端需要实现该算法,否则ECU会拒绝请求。

经验提醒:KWP2000的地址分配是动态的,诊断仪地址(0xF1)和ECU地址(0x10)是默认值,但某些车型可能不同。建议在初始化后发送0x3D服务读取ECU实际地址,避免硬编码。

常见误区与总结

  • 误区1:将ISO 9141-2和KWP2000混为一谈。虽然物理层相同,但数据链路层和初始化流程不同,不能互换。
  • 误区2:忽略L线的作用。在ISO 9141-2中,L线用于唤醒多ECU,如果只接K线,某些车型可能无法唤醒。
  • 总结:KWP2000在2004-2008年的欧系车中非常普遍,但在Android开发中,建议优先使用支持KWP2000的专用芯片(如STN1170),而非ELM327,因为ELM327对多帧响应的处理不够透明。

3.3 ISO 15765(CAN):现代车载诊断的主流

核心原理

ISO 15765是CAN总线在OBD-II诊断中的应用标准,自2008年起成为全球主流。它基于CAN 2.0B(29位扩展标识符),物理层使用双线差分信号(CAN-H和CAN-L),速率通常为250kbps(低速)或500kbps(高速)。CAN的差分信号具有极强的抗干扰能力,且支持多主通信,因此成为车载网络的事实标准。ISO 15765定义了四种帧类型:单帧(SF)、首帧(FF)、连续帧(CF)和流控帧(FC),用于传输超过8字节的诊断数据。

帧格式与传输机制

帧类型PCI字节数据长度用途
单帧 (SF)0x0X (X为长度)0-7 字节短数据直接发送
首帧 (FF)0x1X XX (12位长度)0-4095 字节长数据起始
连续帧 (CF)0x2X (X为序号)0-7 字节后续数据
流控帧 (FC)0x3X (X为状态)0-7 字节接收方控制发送节奏

CAN诊断的寻址方式分为物理寻址(点对点)和功能寻址(广播)。物理寻址使用特定的CAN ID(如0x7E0发送,0x7E8接收),功能寻址使用0x7DF。在Android端,如果使用ELM327,ATSP 6对应CAN 11位ID(500kbps),ATSP 7对应CAN 29位ID(250kbps)。但ELM327对CAN多帧传输的自动处理能力有限,对于超过8字节的响应(如读取VIN),需要手动发送流控帧。

实战案例:通过Android直接访问CAN总线

假设我们使用USB-CAN适配器(如PCAN或SocketCAN)直接连接车辆,Android端通过JNI调用SocketCAN接口。以下是一个读取VIN的完整流程:

// 1. 打开CAN套接字
int s = socket(PF_CAN, SOCK_RAW, CAN_RAW);
struct sockaddr_can addr;
addr.can_family = AF_CAN;
addr.can_ifindex = if_nametoindex("can0");
bind(s, (struct sockaddr*)&addr, sizeof(addr));

// 2. 构造诊断请求(物理寻址)
struct can_frame frame;
frame.can_id = 0x7E0;  // 发送ID
frame.can_dlc = 8;
frame.data[0] = 0x02;  // 单帧,长度2
frame.data[1] = 0x09;  // 服务ID
frame.data[2] = 0x02;  // PID 02 (VIN)
// 发送
write(s, &frame, sizeof(frame));

// 3. 接收响应(可能多帧)
struct can_frame rsp;
read(s, &rsp, sizeof(rsp));
if (rsp.can_id == 0x7E8) {
    if (rsp.data[0] & 0xF0 == 0x10) {  // 首帧
        int total_len = ((rsp.data[0] & 0x0F) << 8) | rsp.data[1];
        // 发送流控帧
        struct can_frame fc;
        fc.can_id = 0x7E0;
        fc.can_dlc = 8;
        fc.data[0] = 0x30;  // 流控帧,允许发送
        fc.data[1] = 0x00;  // 块大小
        fc.data[2] = 0x00;  // 间隔时间
        write(s, &fc, sizeof(fc));
        // 接收连续帧并重组...
    }
}

这个案例展示了CAN诊断的核心:多帧传输需要接收方主动发送流控帧,否则发送方会等待超时。在Android中,建议使用后台线程处理CAN读写,避免阻塞UI。

关键点:CAN的物理层虽然统一,但不同OEM的CAN ID分配可能不同。例如,宝马使用0x7E0/0x7E8,而奔驰使用0x7E0/0x7E8但功能寻址ID不同。开发时应先读取ECU的VIN,再根据VIN确定具体的CAN ID映射。

常见误区与复盘

  • 误区1:认为CAN速率固定为500kbps。实际上,部分车型(如某些混合动力车)使用250kbps,且OBD-II引脚6/14可能连接多个CAN网络。
  • 误区2:忽略CAN终端电阻。OBD-II诊断座内部通常没有终端电阻,如果直接连接两个CAN节点,需要确保总线两端有120Ω电阻,否则信号反射会导致通信失败。
  • 复盘:在Android端实现CAN诊断时,建议先发送功能寻址请求(0x7DF)探测所有ECU,再根据响应列表选择目标ECU进行物理寻址。这样可以避免因CAN ID不匹配导致的通信失败。

3.4 协议选择策略与Android适配建议

核心原理

协议选择的核心原则是:先探测,后通信。由于OBD-II协议族互不兼容,且同一辆车的不同ECU可能使用不同协议(例如发动机ECU用CAN,安全气囊ECU用KWP2000),因此Android端必须实现自动协议探测机制。探测的基本方法是:依次尝试每种协议,发送标准请求(如PID 0x00),观察是否有有效响应。

判断方法:自动探测流程

  1. 读取车辆VIN(通过ISO 15765或KWP2000的0x09服务),解析年份和品牌。
  2. 根据年份推断:2008年后优先CAN,2004-2008年尝试KWP2000,2004年前尝试J1850或ISO 9141-2。
  3. 如果推断失败,则按顺序尝试:CAN 500k → CAN 250k → KWP2000 → ISO 9141-2 → J1850 PWM → J1850 VPW。
  4. 每次尝试发送0x00请求,设置500ms超时,收到响应即锁定协议。

在Android实现中,建议将协议探测逻辑封装为独立模块,使用状态机管理。以下是一个简化的状态机定义:

enum ProtocolState {
    UNKNOWN, CAN_500K, CAN_250K, KWP2000, ISO9141, J1850_PWM, J1850_VPW
}
// 状态转换条件:收到有效响应或超时

案例拆解:多协议车辆诊断

我们测试了一辆2007款别克君越(美版),该车同时支持J1850 VPW(发动机)和CAN(变速箱)。通过ELM327自动探测,最终锁定CAN 500k。但当我们尝试读取安全气囊模块时,发现该模块仅响应KWP2000。因此,Android端需要维护一个协议映射表:

ECU模块协议CAN ID / 地址
发动机ISO 15765 CAN0x7E0/0x7E8
变速箱ISO 15765 CAN0x7E1/0x7E9
安全气囊KWP20000x10/0xF1

这个案例说明,现代车辆往往是多协议共存的。Android端设计时,应将协议适配层与业务逻辑解耦,通过一个统一的诊断接口(如sendRequest(ECU, service, pid))屏蔽底层协议差异。

风险提示:在多协议切换时,必须确保物理层正确切换。例如,从CAN切换到KWP2000时,需要断开CAN收发器,启用K线收发器。如果使用ELM327,ATSP命令会自动处理,但切换时间可能长达100ms,期间不能发送任何数据。

常见误区与总结

  • 误区1:只支持CAN协议,忽略老车型。虽然CAN是主流,但全球仍有大量2008年前的车辆在运行。
  • 误区2:协议探测顺序不合理。应先尝试高速CAN,再尝试低速CAN,因为高速CAN的响应更快,且大多数现代车辆支持。
  • 总结:协议选择不是一次性的,而是动态的。Android端应支持运行时协议切换,并缓存探测结果,避免每次连接都重新探测。同时,要关注ELM327等适配器的固件版本,不同固件对协议的支持程度不同。