OBD-II 车载诊断
从排放法规到全球统一诊断接口:J1962 十六针座、ISO 15765 让 CAN 成为 OBD 物理层、功能/物理寻址、Mode 与 PID 的解析公式,用几行代码读出实车转速与车速。
从排放法规到全球统一诊断接口:J1962 十六针座、ISO 15765 让 CAN 成为 OBD 物理层、功能/物理寻址、Mode 与 PID 的解析公式,用几行代码读出实车转速与车速。
Pin 6 = CAN High、Pin 14 = CAN Low、Pin 16 = 电瓶正极 12V、Pin 4/5 = 底盘/信号地。诊断仪插上即可通过 Pin 6/14 与车内 CAN 网络通信。0x7DF 把请求发给所有支持 OBD 的 ECU,谁能应答谁回;诊断仪初次扫描用它。物理寻址用 0x7E0~0x7E7 点对点请求特定 ECU(0x7E0 常为发动机 ECU)。ECU 的响应 ID = 请求 ID + 8,即 0x7E8~0x7EF(0x7DF 的应答也落在 0x7E8 段)。Mode 01 读实时数据流(当前 PID 值);Mode 02 读冻结帧(故障发生瞬间的快照);Mode 03 读已确认的故障码 DTC;Mode 04 清除故障码与 MIL 灯;Mode 09 读车辆信息(如 VIN 码)。响应模式号 = 请求模式号 + 0x40(如 01→41、09→49)。0x0C 发动机转速、0x0D 车速、0x05 冷却液温度、0x04 计算负荷、0x11 节气门位置、0x2F 油量。每个 PID 都有官方规定的字节数与换算公式,把原始字节转成物理量。P0301(1 缸失火)。首字母表示系统:P 动力总成、C 底盘、B 车身、U 网络通信。Mode 03 返回的是 2 字节编码,需按 SAE J2012 规则解码成字母数字形式。MIL(Malfunction Indicator Lamp,仪表故障灯)亮起时就意味着存在已确认的 DTC。0x01 可读取 MIL 状态、存储的 DTC 数量及各监视器就绪情况。诊断仪并不直接读寄存器,而是插上 J1962 座,通过 Pin 6/14 挂到车内 CAN 总线上,与各 ECU 平等对话。
2008 年起(美国)所有车辆强制采用 ISO 15765(CAN)作为 OBD 物理层,淘汰了 ISO 9141、KWP2000(K 线)和 J1850。原因是 CAN 速率高(500 kbps)、抗干扰强、天然多主仲裁,一条总线就能连接全部 ECU。因此今天写 OBD 工具,默认就是操作 CAN 帧。
OBD 请求帧固定 8 字节:第 1 字节是「附加数据长度」,随后是模式、PID,其余用 0x55(或 0x00)填充。响应帧第 1 字节同样是长度,模式号加 0x40。
| PID | 参数 | 字节 | 换算公式 | 单位/范围 |
|---|---|---|---|---|
0x04 | 计算发动机负荷 | A | A × 100 / 255 | 0–100 % |
0x05 | 冷却液温度 | A | A − 40 | −40–215 ℃ |
0x0B | 进气歧管压力 | A | A | 0–255 kPa |
| 0x0C | 发动机转速 | A,B | (256A + B) / 4 | 0–16384 RPM |
| 0x0D | 车速 | A | A | 0–255 km/h |
0x0F | 进气温度 | A | A − 40 | −40–215 ℃ |
0x11 | 节气门位置 | A | A × 100 / 255 | 0–100 % |
0x2F | 燃油液位 | A | A × 100 / 255 | 0–100 % |
0x42 | 控制模块电压 | A,B | (256A + B) / 1000 | 0–65 V |
PID 0x00 返回一个 4 字节位掩码,表示 0x01–0x20 中哪些 PID 被支持;PID 0x20 返回 0x21–0x40 的支持情况,以此类推(0x40、0x60…)。写诊断工具时先查 0x00,再只请求被支持的 PID,避免无效请求。
下面用 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(清码)会清除排放监视器状态,年检前误清会导致监视器「未就绪」而复检,务必谨慎。
在车规 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 无需 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 的核心。