Chapter 10 · CAN 总线 / 汽车电子

OBD-II 车载诊断

从排放法规到全球统一诊断接口:J1962 十六针座、ISO 15765 让 CAN 成为 OBD 物理层、功能/物理寻址、Mode 与 PID 的解析公式,用几行代码读出实车转速与车速。

课程进度63%

关键概念

OBD-II (On-Board Diagnostics II)
第二代车载诊断系统。起源于美国加州为控制汽车尾气排放推出的强制标准(1996 年起全美新车强制搭载,欧洲对应 EOBD 2001 年、中国 OBD 2008 年起)。核心目的是让检测设备能实时读取发动机排放相关数据、读取并清除故障码,任何品牌的车用同一把「万能诊断仪」即可。
J1962 诊断座(16 针 DLC)
SAE J1962 规定的标准 16 针梯形诊断连接器(Diagnostic Link Connector),几乎所有乘用车都装在方向盘下方。关键引脚:Pin 6 = CAN HighPin 14 = CAN LowPin 16 = 电瓶正极 12VPin 4/5 = 底盘/信号地。诊断仪插上即可通过 Pin 6/14 与车内 CAN 网络通信。
ISO 15765 (Diagnostics over CAN)
规定 OBD 诊断如何跑在 CAN 总线上的标准,通常用 500 kbps 的 11 位标准帧。它把 OBD 请求/响应封装到 CAN 数据帧里,并借助 ISO 15765-2(ISO-TP,第 11 章详解)实现超过 8 字节的多帧传输。现代车辆 OBD 物理层几乎都是 CAN(老车曾用 ISO 9141、KWP2000、J1850)。
功能寻址 vs 物理寻址
功能寻址用广播 ID 0x7DF 把请求发给所有支持 OBD 的 ECU,谁能应答谁回;诊断仪初次扫描用它。物理寻址0x7E0~0x7E7 点对点请求特定 ECU(0x7E0 常为发动机 ECU)。ECU 的响应 ID = 请求 ID + 8,即 0x7E8~0x7EF(0x7DF 的应答也落在 0x7E8 段)。
Mode / Service(诊断模式)
OBD-II 请求的第一个字节是「模式号」。Mode 01 读实时数据流(当前 PID 值);Mode 02 读冻结帧(故障发生瞬间的快照);Mode 03 读已确认的故障码 DTC;Mode 04 清除故障码与 MIL 灯;Mode 09 读车辆信息(如 VIN 码)。响应模式号 = 请求模式号 + 0x40(如 01→41、09→49)。
PID (Parameter ID)
在 Mode 01/02 下用来指定「要读哪个参数」的编号。常用:0x0C 发动机转速、0x0D 车速、0x05 冷却液温度、0x04 计算负荷、0x11 节气门位置、0x2F 油量。每个 PID 都有官方规定的字节数与换算公式,把原始字节转成物理量。
DTC (Diagnostic Trouble Code)
故障码,如 P0301(1 缸失火)。首字母表示系统:P 动力总成、C 底盘、B 车身、U 网络通信。Mode 03 返回的是 2 字节编码,需按 SAE J2012 规则解码成字母数字形式。MIL(Malfunction Indicator Lamp,仪表故障灯)亮起时就意味着存在已确认的 DTC。
MIL / Readiness Monitors
MIL 是仪表盘那盏「发动机故障灯」。Readiness Monitors(就绪监视器)是 ECU 对各排放子系统(催化器、氧传感器、EGR 等)的自检状态,年检时若监视器未完成会判定「未就绪」。Mode 01 PID 0x01 可读取 MIL 状态、存储的 DTC 数量及各监视器就绪情况。

J1962 诊断座与 CAN 物理连接

诊断仪并不直接读寄存器,而是插上 J1962 座,通过 Pin 6/14 挂到车内 CAN 总线上,与各 ECU 平等对话。

SAE J1962 十六针诊断座(俯视,方向盘下方) ═══════════════════════════════════════════════════════════ ┌───┬───┬───┬───┬───┬───┬───┬───┐ │ 1 │ 2 │ 3 │ 4 │ 5 │6 │ 7 │ 8 │ 上排 ├───┼───┼───┼───┼───┼───┼───┼───┤ │ 9 │10 │11 │12 │13 │14│15 │16│ 下排 └───┴───┴───┴───┴───┴───┴───┴───┘ Pin 6 = CAN HighPin 14 = CAN Low ┘ ISO 15765 (500 kbps) Pin 16 = +12V 电瓶 给诊断仪供电 Pin 4 = 底盘地 Pin 5 = 信号地 诊断仪 车内 CAN 网络 ┌──────────────┐ Pin6/14 ┌────────┬────────┬────────┐ │ OBD Scanner │◄════════════►│ 发动机变速箱ABS │ │ (0x7DF/0x7E0)│ CAN Bus │ 0x7E8 │ 0x7E9 │ 0x7EB │ └──────────────┘ └────────┴────────┴────────┘
为什么 OBD 用 CAN 而不是老协议?

2008 年起(美国)所有车辆强制采用 ISO 15765(CAN)作为 OBD 物理层,淘汰了 ISO 9141、KWP2000(K 线)和 J1850。原因是 CAN 速率高(500 kbps)、抗干扰强、天然多主仲裁,一条总线就能连接全部 ECU。因此今天写 OBD 工具,默认就是操作 CAN 帧。

请求 / 响应帧格式

OBD 请求帧固定 8 字节:第 1 字节是「附加数据长度」,随后是模式、PID,其余用 0x55(或 0x00)填充。响应帧第 1 字节同样是长度,模式号加 0x40。

读取发动机转速:请求 → 响应 诊断仪请求 CAN ID = 0x7DF (功能寻址广播) ┌────┬────┬────┬────┬────┬────┬────┬────┐ │02010C │ 55 │ 55 │ 55 │ 55 │ 55 │ └────┴────┴────┴────┴────┴────┴────┴────┘ │ │ └─ PID 0x0C(发动机转速) │ └────── Mode 01(实时数据) └──────────── 后面有效数据 2 字节 发动机 ECU 响应 CAN ID = 0x7E8 (0x7E0+8) ┌────┬────┬────┬────┬────┬────┬────┬────┐ │04410C1AF8 │ 00 │ 00 │ 00 │ └────┴────┴────┴────┴────┴────┴────┴────┘ │ │ │ └────┴─ 数据 A=0x1A, B=0xF8 │ │ └─ PID 回显 0x0C │ └────── 41 = 01+0x40(正响应) └──────────── 有效数据 4 字节 转速 = (256*A + B) / 4 = (256*0x1A + 0xF8)/4 = (6656 + 248)/4 = 1726 RPM

常用 PID 与解析公式

PID参数字节换算公式单位/范围
0x04计算发动机负荷AA × 100 / 2550–100 %
0x05冷却液温度AA − 40−40–215 ℃
0x0B进气歧管压力AA0–255 kPa
0x0C发动机转速A,B(256A + B) / 40–16384 RPM
0x0D车速AA0–255 km/h
0x0F进气温度AA − 40−40–215 ℃
0x11节气门位置AA × 100 / 2550–100 %
0x2F燃油液位AA × 100 / 2550–100 %
0x42控制模块电压A,B(256A + B) / 10000–65 V
如何知道车支持哪些 PID?

PID 0x00 返回一个 4 字节位掩码,表示 0x01–0x20 中哪些 PID 被支持;PID 0x20 返回 0x21–0x40 的支持情况,以此类推(0x40、0x60…)。写诊断工具时先查 0x00,再只请求被支持的 PID,避免无效请求。

用 python-can 读取实车转速与车速

下面用 SocketCAN(Linux)+ python-can 直接收发 OBD 帧。先启用 can0,再发功能寻址请求并解析响应。

# obd_read.py —— 通过 python-can 读取转速与车速
# 前置:sudo ip link set can0 up type can bitrate 500000
import can
import time

bus = can.Bus(channel="can0", interface="socketcan")

def query_pid(mode, pid):
    # 请求帧:长度=02, mode, pid, 剩余用 0x55 填充
    data = [0x02, mode, pid, 0x55, 0x55, 0x55, 0x55, 0x55]
    msg = can.Message(arbitration_id=0x7DF,      # 功能寻址
                      data=data, is_extended_id=False)
    bus.send(msg)
    # 等待 0x7E8~0x7EF 段的响应
    t0 = time.time()
    while time.time() - t0 < 1.0:
        resp = bus.recv(timeout=1.0)
        if resp and 0x7E8 <= resp.arbitration_id <= 0x7EF:
            if resp.data[1] == mode + 0x40 and resp.data[2] == pid:
                return resp.data
    return None

# 读发动机转速 PID 0x0C:RPM = (256A + B) / 4
d = query_pid(0x01, 0x0C)
if d:
    A, B = d[3], d[4]
    rpm = (256 * A + B) / 4
    print(f"发动机转速: {rpm:.0f} RPM")

# 读车速 PID 0x0D:km/h = A
d = query_pid(0x01, 0x0D)
if d:
    print(f"车速: {d[3]} km/h")

bus.shutdown()
安全提醒

Mode 01/03/09 都是只读操作,插上 OBD 读数据是安全的。但绝不要在行驶中插拔诊断仪或长时间保持连接(部分车辆会因此触发通信超时故障码)。Mode 04(清码)会清除排放监视器状态,年检前误清会导致监视器「未就绪」而复检,务必谨慎。

STM32 侧读取 OBD(bxCAN)

在车规 MCU 上(如 STM32F407 的 bxCAN 外设),发送 OBD 请求与解析响应的逻辑完全一致,只是换成寄存器/HAL API 收发。

/* obd_stm32.c —— HAL 库发送 OBD 请求并解析转速 */
#include "stm32f4xx_hal.h"

extern CAN_HandleTypeDef hcan1;

void OBD_RequestRPM(void) {
  CAN_TxHeaderTypeDef tx = {0};
  uint8_t data[8] = { 0x02, 0x01, 0x0C, 0x55, 0x55, 0x55, 0x55, 0x55 };
  uint32_t mailbox;

  tx.StdId = 0x7DF;           /* 功能寻址广播 */
  tx.IDE   = CAN_ID_STD;       /* 11 位标准帧 */
  tx.RTR   = CAN_RTR_DATA;
  tx.DLC   = 8;
  HAL_CAN_AddTxMessage(&hcan1, &tx, data, &mailbox);
}

/* FIFO0 接收中断回调里解析响应 */
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan) {
  CAN_RxHeaderTypeDef rx;
  uint8_t d[8];
  HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &rx, d);

  if (rx.StdId >= 0x7E8 && rx.StdId <= 0x7EF) {
    if (d[1] == 0x41 && d[2] == 0x0C) {  /* 转速正响应 */
      uint16_t rpm = (256 * d[3] + d[4]) / 4;
      /* 此处使用 rpm,如打印到串口 */
      (void)rpm;
    }
  }
}

Mode 03 读故障码 / Mode 09 读 VIN

Mode 03 无需 PID,直接请求即可返回所有已确认 DTC;VIN(17 位车架号)超过 8 字节,必须依赖 ISO-TP 多帧接收(下一章详解)。

# 读故障码 Mode 03(请求帧仅 1 字节 mode)
data = [0x01, 0x03, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55]
# 响应 43 xx ... 每 2 字节一个 DTC,需按 J2012 解码

def decode_dtc(b0, b1):
    # 高 2 位选系统字母,其余为数字
    letters = ["P", "C", "B", "U"]
    sys = letters[(b0 >> 6) & 0x03]
    d1 = (b0 >> 4) & 0x03
    d2 = b0 & 0x0F
    return f"{sys}{d1}{d2:X}{b1:02X}"

print(decode_dtc(0x03, 0x01))  # -> P0301(1缸失火)

# 读 VIN:Mode 09 PID 0x02(响应为多帧,需 ISO-TP)
data = [0x02, 0x09, 0x02, 0x55, 0x55, 0x55, 0x55, 0x55]
本章小结

OBD-II 因排放法规而生,J1962 十六针诊断座通过 Pin 6/14(CAN High/Low)挂到车内 CAN 总线,物理层由 ISO 15765 规定,默认 500 kbps。诊断仪用功能寻址 0x7DF 广播扫描、用物理寻址 0x7E0~0x7E7 点对点请求,ECU 响应 ID = 请求 ID + 8(0x7E8~0x7EF),响应模式号 = 请求模式号 + 0x40。请求帧首字节是有效数据长度,随后是 Mode + PID,其余填 0x55。Mode 01 读实时数据,Mode 03 读故障码,Mode 09 读 VIN。每个 PID 有固定字节数与换算公式,如转速 PID 0x0C = (256A+B)/4、车速 PID 0x0D = A。读数据操作全部只读且安全,但 Mode 04 清码会重置排放监视器需谨慎。VIN 等超 8 字节数据必须靠 ISO-TP 多帧传输——正是下一章 UDS 的核心。

⚒️ 配套开发者工具箱 · JSON / 正则 / 时间戳 / Cron 等 17 款免费在线工具