OBD2 Fundamentals: Protocols, Connectors, and Diagnostic Services
Document ID: OBD-01 Series: Telematics Tutorial Series Target audience: Beginner-Intermediate — you should have a basic understanding of Controller Area Network (CAN) communication (CAN-01 and CAN-02 recommended) but no prior OBD2 experience is required.
Learning Objectives
By the end of this document, you will be able to:
- Explain the history and purpose of On-Board Diagnostics (OBD2) and why it is standardized
- Identify the J1962 diagnostic connector, its pin assignments, and which protocol each pin supports
- Describe all five OBD2 communication protocols and determine which one your vehicle uses
- List all ten OBD2 Service Identifier (SID) modes and explain what each provides
- Construct and decode OBD2 request/response messages for common Parameter Identifiers (PID)
1. What Is OBD2?
On-Board Diagnostics, Second Generation (OBD2) is a standardized system that allows external diagnostic equipment to communicate with a vehicle’s Electronic Control Units (ECU). It provides access to emissions-related data, Diagnostic Trouble Codes (DTC), real-time sensor values, and system readiness information.
Hex notation: This tutorial uses hexadecimal (base-16) notation extensively, prefixed with
0x. For example,0x7E8is the hex representation of decimal 2024. If you are unfamiliar with hex, the key conversion: each hex digit represents 4 bits, so0xFF= 1111 1111 in binary = 255 in decimal.
1.1 History
| Year | Milestone |
|---|---|
| 1968 | Volkswagen introduces the first on-board diagnostic system (Type 3) |
| 1988 | California Air Resources Board (CARB) mandates OBD1 for all vehicles sold in California |
| 1991 | CARB defines OBD2 requirements — standardized connector, protocols, and data |
| 1996 | OBD2 becomes mandatory for all passenger vehicles sold in the United States (Environmental Protection Agency (EPA) 40 Code of Federal Regulations (CFR) Part 86; CARB Title 13, California Code of Regulations (CCR) §1968.2) |
| 2001 | European On-Board Diagnostics (EOBD) mandated for gasoline vehicles in the European Union |
| 2003 | EOBD mandated for diesel vehicles in the EU |
| 2006 | Japanese On-Board Diagnostics (JOBD) mandated for vehicles sold in Japan |
| 2008 | CAN becomes the only required OBD2 protocol for US vehicles (SAE J1979, ISO 15765-4) |
| 2020+ | J1979-2 (OBDonUDS) begins deployment for next-generation vehicles |
1.2 Why OBD2 Matters
OBD2 exists primarily for emissions compliance — it allows regulators, mechanics, and vehicle owners to verify that a vehicle’s emissions control systems are functioning correctly. But the standardized diagnostic interface has become invaluable far beyond emissions:
- Mechanics use OBD2 to read DTCs and diagnose faults
- Fleet managers use OBD2 telematics for real-time vehicle monitoring
- Insurance companies offer usage-based insurance through OBD2 dongles
- Enthusiasts use OBD2 to monitor performance, log data, and tune parameters
- Researchers use OBD2 for vehicle data collection and analysis
Regulatory basis: The EPA requires OBD-II on all light-duty vehicles sold in the United States since 1996 (40 CFR Part 86). CARB’s OBD-II regulation (Title 13, CCR §1968.2) sets additional requirements for California-certified vehicles. JOBD applies equivalent requirements in the Japanese market.
1.3 Standards Governing OBD2
OBD2 is defined by a family of standards from Society of Automotive Engineers (SAE) International and the International Organization for Standardization (ISO):
| Standard | Scope |
|---|---|
| SAE J1962 | Diagnostic connector (physical plug, pinout) |
| SAE J1979 | Diagnostic test modes (service modes, PIDs) |
| SAE J2012 | Diagnostic trouble code definitions |
| ISO 15031-1 to -7 | International equivalent of SAE J1962/J1979/J2012 |
| ISO 15765-4 | Diagnostics on CAN (DoCAN) — how OBD2 uses CAN |
| ISO 14230 | Keyword Protocol 2000 (KWP2000) — OBD2 over K-line |
| ISO 9141-2 | ISO 9141 diagnostic protocol (K-line, legacy) |
| SAE J1850 | Pulse-Width Modulation (PWM) and Variable Pulse Width (VPW) protocols |
ISO 15031 part mappings: OBD-II services are formally defined in the ISO 15031 series: Part 5 covers emission-related diagnostic services (maps to SAE J1979), Part 6 covers test results for monitored systems, and Part 3 covers the diagnostic connector and related protocols.
2. The J1962 Diagnostic Connector
Every OBD2-compliant vehicle has a standardized 16-pin Data Link Connector (DLC) defined by SAE J1962. This is the port where you plug in a scan tool, ELM327 adapter, or diagnostic computer. The term “J1962 connector” and “DLC” are used interchangeably — both refer to the same OBD2 diagnostic port.
2.1 Connector Location
The J1962 connector must be located within the driver’s compartment, accessible without tools. Common locations:
- Under the dashboard, left of the steering column (most common)
- Under the dashboard, center console area
- Behind a small cover panel near the driver’s knee area
💡 Tip: If you cannot find the OBD2 port, check the vehicle's owner's manual or search online for "[your vehicle year/make/model] OBD2 port location."
2.2 Pin Assignments
(see Diagram D-OBD01-01 for a detailed J1962 pinout illustration)
block-beta
columns 9
HEADER["Pin"]:1 P1["1"] P2["2"] P3["3"] P4["4"] P5["5"] P6["6"] P7["7"] P8["8"]
R1["Row 1"]:1 MFR1["Mfr"] J1850P["J1850+
PWM"] MFR3["Mfr"] CGND["Chassis
GND"] SGND["Signal
GND"] CANH["CAN_H"] KLINE["K-Line"] MFR8["Mfr"]
space:9
HEADER2["Pin"]:1 P9["9"] P10["10"] P11["11"] P12["12"] P13["13"] P14["14"] P15["15"] P16["16"]
R2["Row 2"]:1 MFR9["Mfr"] J1850M["J1850−
PWM Ret"] MFR11["Mfr"] MFR12["Mfr"] MFR13["Mfr"] CANL["CAN_L"] LLINE["L-Line"] BATT["+12V
Battery"]
style MFR1 fill:#9E9E9E,stroke:#333
style J1850P fill:#FF9800,stroke:#333,color:#fff
style MFR3 fill:#9E9E9E,stroke:#333
style CGND fill:#212121,stroke:#333,color:#fff
style SGND fill:#212121,stroke:#333,color:#fff
style CANH fill:#1565C0,stroke:#333,color:#fff
style KLINE fill:#388E3C,stroke:#333,color:#fff
style MFR8 fill:#9E9E9E,stroke:#333
style MFR9 fill:#9E9E9E,stroke:#333
style J1850M fill:#FF9800,stroke:#333,color:#fff
style MFR11 fill:#9E9E9E,stroke:#333
style MFR12 fill:#9E9E9E,stroke:#333
style MFR13 fill:#9E9E9E,stroke:#333
style CANL fill:#1565C0,stroke:#333,color:#fff
style LLINE fill:#388E3C,stroke:#333,color:#fff
style BATT fill:#D32F2F,stroke:#333,color:#fff
Figure: OBD01 01 j1962 pinout
📝 Note: The actual J1962 connector is trapezoidal (wider at the bottom row). The diagram above is a simplified rectangular representation showing pin positions. View is from the front (plug-in side) of the vehicle connector.
| Pin | Function | Protocol |
|---|---|---|
| 1 | Manufacturer discretionary | — |
| 2 | SAE J1850 Bus+ | J1850 PWM / VPW |
| 3 | Manufacturer discretionary | — |
| 4 | Chassis ground | All |
| 5 | Signal ground | All |
| 6 | CAN High (CAN_H) | ISO 15765 (CAN) |
| 7 | ISO 9141-2 / ISO 14230 K-line | ISO 9141 / KWP2000 |
| 8 | Manufacturer discretionary | — |
| 9 | Manufacturer discretionary | — |
| 10 | SAE J1850 Bus- (PWM return) | J1850 PWM |
| 11 | Manufacturer discretionary | — |
| 12 | Manufacturer discretionary | — |
| 13 | Manufacturer discretionary | — |
| 14 | CAN Low (CAN_L) | ISO 15765 (CAN) |
| 15 | ISO 9141-2 / ISO 14230 L-line | ISO 9141 / KWP2000 |
| 16 | Battery positive (+12V) | All (power) |
Key pins: - Pins 4, 5 — Ground references (always present) - Pins 6, 14 — CAN bus (present on all 2008+ US vehicles, many earlier vehicles) - Pin 7 — K-line (ISO 9141/KWP2000, common on pre-2008 European and Asian vehicles) - Pin 16 — Permanent +12V supply (always hot, even with ignition off)
⚠️ Warning: Pin 16 provides permanent battery voltage. An OBD2 device left plugged in with the ignition off will draw power from the battery. Some devices have sleep modes, but others can drain the battery over days or weeks. Always unplug devices when not in use, or use devices with built-in low-voltage cutoff.
3. OBD2 Communication Protocols
OBD2 defines five communication protocols. A vehicle uses exactly one of these protocols for OBD2 communication (though some vehicles support multiple protocols on different bus systems).
3.1 Protocol Overview
(see Diagram D-OBD01-02 for the OBD2 protocol stack)
graph TB
subgraph APP["Application Layer — SAE J1979 (shared by all protocols)"]
J1979["Service Modes 0x01–0x0A
PIDs, DTCs, Vehicle Info"]
end
subgraph TRANSPORT["Transport Layer"]
ISOTP["ISO-TP
(ISO 15765-2)
CAN only"]
NONE["Direct framing
K-line / J1850"]
end
subgraph DATALINK["Data Link / Physical Layer"]
direction LR
CAN_P["CAN
(ISO 15765)
Pins 6,14
250/500 kbit/s"]
KWP["KWP2000
(ISO 14230)
Pin 7
10.4 kbit/s"]
ISO["ISO 9141
Pin 7
10.4 kbit/s"]
PWM["J1850 PWM
Pins 2,10
41.6 kbit/s"]
VPW["J1850 VPW
Pin 2 only
10.4 kbit/s"]
end
J1979 --> ISOTP --> CAN_P
J1979 --> NONE
NONE --> KWP
NONE --> ISO
NONE --> PWM
NONE --> VPW
style APP fill:#C8E6C9,stroke:#2E7D32
style TRANSPORT fill:#E3F2FD,stroke:#1565C0
style CAN_P fill:#1565C0,stroke:#333,color:#fff
style KWP fill:#388E3C,stroke:#333,color:#fff
style ISO fill:#388E3C,stroke:#333,color:#fff
style PWM fill:#FF9800,stroke:#333,color:#fff
style VPW fill:#FF9800,stroke:#333,color:#fff
Figure: OBD01 02 protocol stack
| Protocol | Standard | Speed | Physical layer | Common on |
|---|---|---|---|---|
| CAN (ISO 15765-4) | ISO 15765 | 250 or 500 kbit/s | CAN bus (pins 6, 14) | All US vehicles 2008+, most 2004+ |
| KWP2000 (ISO 14230) | ISO 14230 | 10.4 kbit/s | K-line (pin 7) | European vehicles 2000–2008 |
| ISO 9141-2 | ISO 9141 | 10.4 kbit/s | K-line (pin 7) | European/Asian vehicles 1996–2004 |
| J1850 PWM | SAE J1850 | 41.6 kbit/s | Differential bus (pins 2, 10) | Ford vehicles 1996–2008 |
| J1850 VPW | SAE J1850 | 10.4 kbit/s | Single-wire bus (pin 2) | GM vehicles 1996–2008 |
KWP2000 baud rate detail: KWP2000 (ISO 14230) uses a slow initialization rate of 5 baud for the wake-up pattern, then switches to 10.4 kbaud for data transfer. The fast initialization variant uses a 25 ms low pulse at the data rate.
3.2 CAN-Based OBD2 (ISO 15765-4)
Since 2008, all new vehicles sold in the United States are required to use CAN as the OBD2 protocol. CAN-based OBD2 is also the most common protocol worldwide. For CAN physical layer details (bit timing, differential signaling, termination), see CAN-01. For CAN protocol details (arbitration, Cyclic Redundancy Check (CRC), error frames), see CAN-02.
CAN OBD2 addressing:
| Parameter | Value |
|---|---|
| Bit rate | 500 kbit/s (most common) or 250 kbit/s |
| ID format | 11-bit (standard) or 29-bit (extended) |
| Request ID (functional) | 0x7DF (broadcast to all ECUs) |
| Request ID (physical) | 0x7E0–0x7E7 (specific ECU) |
| Response ID | 0x7E8–0x7EF (response from specific ECU) |
| Response ID mapping | Request 0x7E0 → Response 0x7E8 (offset +8) |
| Transport protocol | ISO Transport Protocol (ISO-TP, formally ISO 15765-2) for multi-frame messages |
📝 Note: The response CAN ID is always the request ID + 0x08 (e.g., request on 0x7DF, response on 0x7E8). The functional broadcast address 0x7DF can trigger responses from multiple ECUs, each using its own response ID.
11-bit vs 29-bit addressing:
Most passenger vehicles use 11-bit CAN IDs for OBD2. Some heavy-duty vehicles and trucks use 29-bit extended IDs with different addressing:
| Parameter | 11-bit IDs | 29-bit IDs |
|---|---|---|
| Functional request | 0x7DF | 0x18DB33F1 |
| Physical request to ECU 1 | 0x7E0 | 0x18DA00F1 |
| Physical response from ECU 1 | 0x7E8 | 0x18DAF100 |
3.3 Determining Your Vehicle’s Protocol
You can determine the OBD2 protocol by inspecting the J1962 connector pins:
| Present pins | Protocol |
|---|---|
| Pins 6 and 14 (CAN_H, CAN_L) | CAN (ISO 15765-4) |
| Pin 7 only (K-line), no CAN pins | ISO 9141-2 or KWP2000 (ISO 14230) |
| Pins 2 and 10 (J1850 Bus+, Bus-), no CAN pins | J1850 PWM (Ford) — differential pair |
| Pin 2 only (J1850 Bus+), no Pin 10, no CAN pins | J1850 VPW (GM) — single-wire |
💡 Tip: If your vehicle has pins 6 and 14 populated in the OBD2 connector, it almost certainly uses CAN. Most ELM327-based adapters auto-detect the protocol, so you do not need to configure it manually.
4. OBD2 Service Modes (SIDs)
OBD2 defines ten service modes (also called Service Identifiers — SID), each providing a different category of diagnostic data. The service mode is the first byte of every OBD2 request.
4.1 All Ten Service Modes
| SID | Name | Purpose | Request example |
|---|---|---|---|
| 0x01 | Show current data | Read real-time sensor values (engine RPM, speed, temperature) | 01 0C (PID 0x0C = engine RPM) |
| 0x02 | Show freeze frame data | Read sensor values captured when a DTC was stored (see freeze frame detail below) | 02 0C 00 (PID 0x0C, frame 0) |
| 0x03 | Show stored DTCs | Read all confirmed diagnostic trouble codes | 03 |
| 0x04 | Clear DTCs and stored values | Clear all DTCs, freeze frames, and readiness monitors | 04 |
| 0x05 | Oxygen sensor monitoring | Read oxygen sensor test results (non-CAN only) | 05 01 01 |
| 0x06 | On-board monitoring test results | Read test results for specific monitored systems | 06 01 |
| 0x07 | Show pending DTCs | Read DTCs detected during current driving cycle but not yet confirmed | 07 |
| 0x08 | Control on-board systems | Request control of on-board components (e.g., activate evap test) | 08 01 00 00 ... |
| 0x09 | Vehicle information | Read vehicle identification data (VIN, calibration IDs) | 09 02 (VIN) |
| 0x0A | Permanent DTCs | Read DTCs that cannot be cleared by the user (requires repair) | 0A |
graph TB
subgraph DATA[" Data Reading Modes"]
S01["0x01 Show Current Data
Real-time sensor values"]
S02["0x02 Freeze Frame Data
Snapshot at DTC trigger"]
S05["0x05 O2 Sensor Monitoring
Non-CAN only"]
S06["0x06 On-Board Test Results
System-specific tests"]
S09["0x09 Vehicle Information
VIN, Cal IDs"]
end
subgraph DTC[" DTC Modes"]
S03["0x03 Stored DTCs
Confirmed fault codes"]
S07["0x07 Pending DTCs
Current-cycle faults"]
S0A["0x0A Permanent DTCs
Cannot be user-cleared"]
end
subgraph ACTION[" Action Modes"]
S04["0x04 Clear DTCs
Reset codes & monitors"]
S08["0x08 Control Systems
Activate components"]
end
style DATA fill:#E3F2FD,stroke:#1565C0
style DTC fill:#FFF3E0,stroke:#E65100
style ACTION fill:#FFF9C4,stroke:#F9A825
style S01 fill:#BBDEFB,stroke:#1565C0,stroke-width:3px
style S03 fill:#FFE0B2,stroke:#E65100,stroke-width:3px
style S04 fill:#FFF176,stroke:#F9A825,stroke-width:3px
style S09 fill:#BBDEFB,stroke:#1565C0,stroke-width:3px
Figure: OBD01 04 service modes
4.1.1 Freeze Frame Detail (Service 0x02)
A freeze frame is a snapshot of engine parameters captured at the moment a DTC is stored. It typically includes:
- Engine RPM (PID 0x0C)
- Vehicle speed (PID 0x0D)
- Calculated engine load (PID 0x04)
- Engine coolant temperature (PID 0x05)
- Short-term and long-term fuel trim (PIDs 0x06, 0x07)
- Intake manifold pressure (PID 0x0B)
- Time since engine start (PID 0x1F)
- The specific DTC that triggered the capture (PID 0x02 of Service 0x02)
Only one freeze frame is stored at a time (for the highest-priority DTC). If a new, higher-priority DTC occurs, it overwrites the existing freeze frame. Freeze frame data is read via Service 0x02 using the same PID numbers as Service 0x01 — for example, 02 0C 00 returns the RPM at the time of the fault (the trailing 00 is the freeze frame number, always 0 for standard OBD2). This data is invaluable for diagnosing intermittent faults because it captures the exact operating conditions when the fault was detected.
4.2 Request/Response Format
(see Diagram D-OBD01-03 for a visual request/response flow and Diagram D-OBD01-04 for the service mode overview)
sequenceDiagram
participant T as Scan Tool
participant ECU as Engine ECU
T->>ECU: CAN ID: 0x7DF
Data: [02 01 0C 00 00 00 00 00]
SID=0x01, PID=0x0C
Note over T,ECU: Request: "Read current engine RPM"
ECU->>T: CAN ID: 0x7E8
Data: [04 41 0C 1A F8 00 00 00]
SID=0x41, PID=0x0C, A=0x1A, B=0xF8
Note over T: Decode: RPM = ((0x1A × 256) + 0xF8) / 4
= (6656 + 248) / 4
= 6904 / 4
= 1726.0 RPM
Figure: OBD01 03 request response
Every OBD2 request follows this structure:
Request: [SID] [PID] [optional data...]
Response: [SID + 0x40] [PID] [data bytes...]
The response SID is always the request SID plus 0x40. This makes it easy to match responses to requests.
Example — Read Engine RPM (Service 0x01, PID 0x0C):
Request CAN frame:
ID: 0x7DF (functional broadcast)
Data: [02, 01, 0C, 00, 00, 00, 00, 00]
│ │ │
│ │ └── PID 0x0C (Engine RPM)
│ └────── SID 0x01 (Show current data)
└────────── Number of data bytes (ISO-TP single frame: 2 bytes follow)
Response CAN frame:
ID: 0x7E8 (response from ECU 1)
Data: [04, 41, 0C, 1A, F8, 00, 00, 00]
│ │ │ │ │
│ │ │ └───┘── RPM data: 0x1AF8
│ │ └────────── PID 0x0C
│ └─────────────── SID 0x41 (response to 0x01)
└─────────────────── Number of data bytes (4 bytes follow)
Decoding the RPM value:
# OBD2 PID 0x0C: Engine RPM
# Formula: RPM = ((A * 256) + B) / 4
A = 0x1A # 26
B = 0xF8 # 248
rpm = ((A * 256) + B) / 4
print(f"Engine RPM: {rpm}") # 1726.0 RPM
# Output
Engine RPM: 1726.0
Example — Read Vehicle Speed (Service 0x01, PID 0x0D):
Request CAN frame:
ID: 0x7DF
Data: [02, 01, 0D, 00, 00, 00, 00, 00]
Response CAN frame:
ID: 0x7E8
Data: [03, 41, 0D, 55, 00, 00, 00, 00]
# OBD2 PID 0x0D: Vehicle Speed
# Formula: Speed = A (single byte, no conversion)
A = 0x55 # 85
speed = A
print(f"Vehicle Speed: {speed} km/h") # 85 km/h
# Output
Vehicle Speed: 85 km/h
Example — Read Coolant Temperature (Service 0x01, PID 0x05):
# OBD2 PID 0x05: Engine Coolant Temperature
# Formula: Temperature = A - 40
A = 0x7D # 125
temp = A - 40
print(f"Coolant Temperature: {temp} °C") # 85 °C
# Output
Coolant Temperature: 85 °C
Example — Read MAF Sensor (Service 0x01, PID 0x10):
# OBD2 PID 0x10: Mass Air Flow (MAF) Rate
# Formula: MAF = ((A * 256) + B) / 100
A = 0x01 # 1
B = 0xF4 # 244
maf = ((A * 256) + B) / 100
print(f"MAF Rate: {maf} g/s") # 5.0 g/s
# Output
MAF Rate: 5.0 g/s
Example — Read O2 Sensor Voltage (Service 0x01, PID 0x14):
Request CAN frame:
ID: 0x7DF
Data: [02, 01, 14, 00, 00, 00, 00, 00]
Response CAN frame:
ID: 0x7E8
Data: [04, 41, 14, 9A, 80, 00, 00, 00]
# OBD2 PID 0x14: O2 Sensor Voltage (Bank 1, Sensor 1)
# Formula: Voltage = A / 200 (volts)
# Formula: Short-term fuel trim = (B - 128) * 100/128 (%)
A = 0x9A # 154
B = 0x80 # 128
voltage = A / 200
fuel_trim = (B - 128) * 100 / 128
print(f"O2 Voltage: {voltage:.3f} V") # 0.770 V
print(f"Short-term fuel trim: {fuel_trim:.1f}%") # 0.0%
# Output
O2 Voltage: 0.770 V
Short-term fuel trim: 0.0%
4.3 Common Service 0x01 PIDs
In the formulas below, A is the first data byte after the PID in the response (byte 3 of the CAN data field), and B is the second data byte (byte 4). For single-byte PIDs, only A is used.
| PID | Name | Formula (A = first data byte, B = second) | Unit | Data bytes |
|---|---|---|---|---|
| 0x00 | PIDs supported (01-20) | Bitmask | — | 4 |
| 0x04 | Calculated engine load | A × 100/255 | % | 1 |
| 0x05 | Engine coolant temperature | A − 40 | °C | 1 |
| 0x06 | Short-term fuel trim (Bank 1) | (A − 128) × 100/128 | % | 1 |
| 0x0B | Intake manifold pressure | A | kPa | 1 |
| 0x0C | Engine RPM | ((A × 256) + B) / 4 | RPM | 2 |
| 0x0D | Vehicle speed | A | km/h | 1 |
| 0x0F | Intake air temperature | A − 40 | °C | 1 |
| 0x10 | Mass Air Flow (MAF) rate | ((A × 256) + B) / 100 | g/s | 2 |
| 0x11 | Throttle position | A × 100/255 | % | 1 |
| 0x14 | O2 sensor voltage (Bank 1, Sensor 1) | A / 200 (voltage); (B − 128) × 100/128 (fuel trim) | V / % | 2 |
| 0x1C | OBD standard supported | Enumerated value | — | 1 |
| 0x1F | Run time since engine start | (A × 256) + B | seconds | 2 |
| 0x21 | Distance traveled with Malfunction Indicator Lamp (MIL) on | (A × 256) + B | km | 2 |
| 0x2F | Fuel tank level input | A × 100/255 | % | 1 |
| 0x31 | Distance since DTCs cleared | (A × 256) + B | km | 2 |
| 0x42 | Control module voltage | ((A × 256) + B) / 1000 | V | 2 |
| 0x46 | Ambient air temperature | A − 40 | °C | 1 |
| 0x51 | Fuel type | Enumerated value | — | 1 |
📝 Note: Not all PIDs are supported by all vehicles. Always query PID 0x00 first — the response is a 32-bit bitmask indicating which PIDs (0x01–0x20) are supported. PIDs 0x20, 0x40, 0x60, 0x80, 0xA0, 0xC0 each provide the next range of supported PIDs.
4.4 Querying Supported PIDs
# Query which PIDs are supported (PID 0x00)
# Request: 01 00
# Response: 41 00 BE 1F B8 10
# ^^^^^^^^^^
# 32-bit bitmask
def decode_supported_pids(response_bytes, base_pid=0x01):
"""Decode supported PIDs from a 4-byte bitmask response."""
bitmask = int.from_bytes(response_bytes, 'big')
supported = []
for i in range(32):
if bitmask & (1 << (31 - i)):
supported.append(base_pid + i)
return supported
# Example response bytes for PID 0x00
response = bytes([0xBE, 0x1F, 0xB8, 0x10])
supported = decode_supported_pids(response)
print(f"Supported PIDs: {[f'0x{p:02X}' for p in supported]}")
# Output
Supported PIDs: ['0x01', '0x03', '0x04', '0x05', '0x06', '0x07', '0x0C', '0x0D', '0x0E', '0x0F', '0x10', '0x11', '0x13', '0x14', '0x15', '0x1C']
4.5 Quick Start with python-OBD
The python-OBD library provides a high-level Python interface to ELM327-compatible OBD2 adapters. The ELM327 is an OBD2 interpreter chip that translates between the vehicle’s diagnostic protocol and a serial Universal Asynchronous Receiver-Transmitter (UART) interface, typically connected to a host computer via Universal Serial Bus (USB) or Bluetooth. The python-OBD library handles protocol detection, PID decoding, and unit conversion automatically, making it the fastest way to start reading live vehicle data programmatically.
# pip install obd
import obd
# Connect to the ELM327 adapter (auto-detect port)
connection = obd.OBD() # or obd.OBD("/dev/ttyUSB0") for explicit port
if not connection.is_connected():
print("Failed to connect. Check adapter and ignition.")
exit(1)
print(f"Connected via: {connection.port_name()}")
print(f"Protocol: {connection.protocol_name()}")
print(f"ELM327 version: {connection.query(obd.commands.ELM_VERSION).value}")
# Query Engine RPM (PID 0x0C)
rpm_response = connection.query(obd.commands.RPM)
if not rpm_response.is_null():
print(f"Engine RPM: {rpm_response.value}") # e.g., 750.0 RPM
else:
print("RPM: not supported or engine off")
# Query Vehicle Speed (PID 0x0D)
speed_response = connection.query(obd.commands.SPEED)
if not speed_response.is_null():
print(f"Vehicle Speed: {speed_response.value}") # e.g., 0 kph
else:
print("Speed: not supported")
# Query Coolant Temperature (PID 0x05)
temp_response = connection.query(obd.commands.COOLANT_TEMP)
if not temp_response.is_null():
print(f"Coolant Temp: {temp_response.value}") # e.g., 85 degC
else:
print("Coolant temp: not supported")
# List all supported PIDs
supported = connection.supported_commands
print(f"\nSupported PIDs: {len(supported)}")
for cmd in sorted(supported, key=lambda c: c.pid):
print(f" PID 0x{cmd.pid:02X}: {cmd.name}")
connection.close()
Expected output:
Connected via: /dev/ttyUSB0
Protocol: ISO 15765-4 (CAN 11/500)
ELM327 version: ELM327 v2.1
Engine RPM: 750.0 revolutions_per_minute
Vehicle Speed: 0 kph
Coolant Temp: 85 degC
Supported PIDs: 42
PID 0x00: PIDS_A
PID 0x01: STATUS
PID 0x03: FUEL_STATUS
...
For advanced usage including asynchronous monitoring and freeze frame queries, see OBD-03 Section 3.
5. Service 0x03 and 0x07: Reading DTCs
Diagnostic Trouble Codes (DTC) are the primary diagnostic output of OBD2. When the vehicle detects a fault in an emissions-related system, it stores a DTC that identifies the fault. For DTCs and Unified Diagnostic Services (UDS), see OBD-02. For diagnostic tools and python-OBD advanced usage, see OBD-03.
5.1 DTC Format
Each DTC is a 5-character code with the format X0000:
P 0 4 2 0
System letters:
| Letter | System | Hex prefix |
|---|---|---|
| P | Powertrain | 0x0–0x3 |
| C | Chassis | 0x4–0x7 |
| B | Body | 0x8–0xB |
| U | Network/Communication | 0xC–0xF |
Category digit:
| Digit | Meaning |
|---|---|
| 0 | SAE-defined (generic) |
| 1 | Manufacturer-specific |
| 2 | SAE-defined (generic) |
| 3 | SAE/manufacturer joint |
5.2 DTC Encoding in CAN
Each DTC is encoded as 2 bytes in the response. Here is a step-by-step walkthrough using P0420 (Catalyst system efficiency below threshold):
DTC: P 0 4 2 0
Step 1: System letter → top 2 bits of Byte 1
P = 00, C = 01, B = 10, U = 11
P → bits 7-6 = 00
Step 2: Second character (0) → bits 5-4
0 → bits 5-4 = 00
Step 3: Third character (4) → bits 3-0
4 → bits 3-0 = 0100
Step 4: Characters 4-5 (20) → Byte 2
20 hex → 0x20 = 0010 0000
Result:
Byte 1: 00 00 0100 = 0x04
Byte 2: 0010 0000 = 0x20
Binary: 0000 0100 0010 0000
📝 Note: Characters 3, 4, and 5 are hexadecimal digits (0-9, A-F), so fault codes range from 000 to FFF, not 000 to 999.
def decode_dtc(byte1, byte2):
"""Decode a 2-byte OBD2 DTC to its 5-character format."""
# Bits 7-6: system letter (P, C, B, U)
top2 = (byte1 >> 6) & 0x03
letter = ['P', 'C', 'B', 'U'][top2]
# Bits 5-4: second character (0-3)
second_digit = (byte1 >> 4) & 0x03
# Bits 3-0: third character (0-F)
third_digit = byte1 & 0x0F
# Byte 2: fourth and fifth characters (00-FF)
fourth_fifth = byte2
return f"{letter}{second_digit}{third_digit:01X}{fourth_fifth:02X}"
# Example: P0420
dtc = decode_dtc(0x04, 0x20)
print(f"DTC: {dtc}") # P0420
# Output
DTC: P0420
Second example — Decoding U0100 (Lost Communication with ECM/PCM “A”):
DTC: U 0 1 0 0
Result:
Byte 1: 11 00 0001 = 0xC1
Byte 2: 0000 0000 = 0x00
# Decode U0100
dtc = decode_dtc(0xC1, 0x00)
print(f"DTC: {dtc}") # U0100
# Verify: encode U0100 back to bytes
# U = 11 (bits 7-6), 0 = 00 (bits 5-4), 1 = 0001 (bits 3-0) → 0xC1
# 00 → 0x00
print(f"Byte 1: 0x{0xC1:02X}, Byte 2: 0x{0x00:02X}")
# Output
DTC: U0100
Byte 1: 0xC1, Byte 2: 0x00
5.3 Reading DTCs with Service 0x03
📝 Note: The examples below show the **OBD2 application-layer payload** only — the ISO-TP PCI byte and CAN frame padding are omitted for clarity. To see the full CAN frame format including the PCI byte, refer to Section 7.1.
Request: 03
Response: 43 02 04 20 01 33 00 00
Number of DTCs: 02
DTC 1: P0420 — Catalyst System Efficiency Below Threshold (Bank 1)
DTC 2: P0133 — O2 Sensor Circuit Slow Response (Bank 1, Sensor 1)
6. Service 0x09: Vehicle Information
Service 0x09 provides vehicle identification data. The most commonly requested item is the Vehicle Identification Number (VIN).
ISO-TP prerequisite: The VIN example below uses multi-frame ISO-TP transport because the 17-byte VIN exceeds a single CAN frame’s 7-byte payload limit. In brief: the response is split into a First Frame (FF) containing the total length and initial data, followed by one or more Consecutive Frames (CF) carrying the rest. The receiver sends a Flow Control (FC) frame after the FF to authorize transmission. For a complete ISO-TP primer, see Section 7.2. For ISO-TP transport details, see OBD-02 Section 1.
6.1 Reading the VIN (PID 0x02)
The VIN is a 17-character American Standard Code for Information Interchange (ASCII) string. Because it exceeds the 7-byte payload limit of a single CAN frame, it uses ISO-TP (ISO 15765-2) multi-frame transport:
Request: 09 02
Response (multi-frame ISO-TP):
Frame 1 (First Frame): 10 14 49 02 01 57 44 42
Frame 2 (Consec. Frame): 21 52 46 36 31 4A 37 36
Frame 3 (Consec. Frame): 22 41 30 31 32 33 34 35
Reassembled payload: 49 02 01 57 44 42 52 46 36 31 4A 37 36 41 30 31 32 33 34 35
VIN: WDBRF61J76A012345
7. OBD2 on CAN: Message Structure
7.1 Single-Frame Messages
Most OBD2 requests and responses fit in a single CAN frame (≤ 7 data bytes after the ISO-TP PCI byte):
CAN Frame (8 bytes):
PCI byte = number of OBD2 payload bytes (1-7)
Padding = 0x00 or 0x55 (fills to 8 bytes)
Example — request engine RPM:
Byte: [02] [01] [0C] [00] [00] [00] [00] [00]
7.2 Multi-Frame Messages
Responses larger than 7 bytes (e.g., VIN, supported PIDs list, DTC list) use ISO-TP multi-frame transport. See OBD-02 for complete ISO-TP coverage.
ISO-TP Transport Primer
CAN frames carry a maximum of 8 bytes of data (64 for CAN FD). Many diagnostic responses exceed this limit — for example, the Vehicle Identification Number (VIN) is 17 characters. ISO-TP (ISO 15765-2) solves this by splitting large messages across multiple CAN frames:
- Single Frame (SF): Messages that fit in one frame (≤7 bytes after the PCI byte). The first nibble is
0x0and the second nibble is the data length. - First Frame (FF): The first frame of a multi-frame message. The first nibble is
0x1, followed by 12 bits of total message length, then the first data bytes. - Consecutive Frame (CF): Subsequent frames carrying the remaining data. The first nibble is
0x2, followed by a 4-bit sequence number (0-F, wrapping). - Flow Control (FC): Sent by the receiver to tell the sender how to pace remaining frames. Contains: flow status (0=continue, 1=wait, 2=overflow), block size (0=send all), and STmin (minimum separation time between CFs).
The VIN query in Section 6 demonstrates this in practice — see that section for a complete multi-frame walkthrough. For the full ISO-TP specification and implementation details, see OBD-02 Section 1.
7.3 Flow Control
For multi-frame responses, the tester must send a Flow Control (FC) frame after receiving the First Frame (FF) to tell the ECU how to send the remaining Consecutive Frames (CF):
Tester → ECU: Request (single frame)
ECU → Tester: First Frame (first part of response)
Tester → ECU: Flow Control (continue sending, no delay)
ECU → Tester: Consecutive Frame 1
ECU → Tester: Consecutive Frame 2
...
7.4 Negative Responses
When an ECU cannot fulfill a diagnostic request, it responds with Service ID 0x7F followed by the requested service ID and a Negative Response Code (NRC):
| Response Byte | Content |
|---|---|
| Byte 1 | 0x7F (negative response indicator) |
| Byte 2 | The service ID that was rejected (e.g., 0x01) |
| Byte 3 | Negative Response Code (NRC) |
Common NRCs:
| NRC | Hex | Meaning |
|---|---|---|
| serviceNotSupported | 0x11 |
The ECU does not support the requested service |
| subFunctionNotSupported | 0x12 |
The specific PID or sub-function is not supported |
| incorrectMessageLengthOrInvalidFormat | 0x13 |
The request message length or format is wrong |
| conditionsNotCorrect | 0x22 |
Prerequisites not met (e.g., engine must be running) |
| requestOutOfRange | 0x31 |
The PID number is outside the supported range |
| responsePending | 0x78 |
ECU needs more time; will send actual response later |
For a complete NRC reference and UDS-level negative response handling, see OBD-02.
Example 1 — requestOutOfRange: Requesting PID 0xFF (undefined) returns a negative response:
Request CAN frame:
ID: 0x7DF
Data: [02, 01, FF, 00, 00, 00, 00, 00]
Response CAN frame:
ID: 0x7E8
Data: [03, 7F, 01, 31, 00, 00, 00, 00]
Example 2 — serviceNotSupported: Requesting Service 0x08 (control on-board systems) on an ECU that does not support it:
Request CAN frame:
ID: 0x7DF
Data: [03, 08, 01, 00, 00, 00, 00, 00]
Response CAN frame:
ID: 0x7E8
Data: [03, 7F, 08, 11, 00, 00, 00, 00]
Negative Response Handling in Python
When using python-OBD, negative responses typically surface as null responses. The following helper function adds explicit negative response detection and NRC interpretation:
import obd
def query_with_error_handling(connection, command):
"""Query a PID with negative response detection."""
response = connection.query(command)
if response.is_null():
# Check if this was a negative response
raw = str(response.value) if response.value else ""
if "7F" in raw:
# Parse negative response: 7F [service_id] [nrc]
print(f"Negative response for {command.name}: {raw}")
nrc_codes = {
0x11: "serviceNotSupported",
0x12: "subFunctionNotSupported",
0x22: "conditionsNotCorrect",
0x31: "requestOutOfRange",
0x78: "responsePending (wait and retry)"
}
# In a raw implementation:
# nrc = int(raw_bytes[-1])
# print(f" NRC: {nrc_codes.get(nrc, 'unknown')}")
else:
print(f"{command.name}: not supported by this vehicle")
return None
return response.value
NRC 0x78 — responsePending: This NRC is special — it means the ECU needs more time to process the request and will send the actual response later. Robust implementations should wait and retry rather than treating it as a failure.
8. OBD2 Readiness Monitors
OBD2 systems continuously run self-tests called readiness monitors. These monitors verify that specific emissions control systems are functioning correctly. The monitor status is available through Service 0x01, PID 0x01.
8.1 Monitor Types
Continuous monitors (always running): - Misfire detection - Fuel system monitoring - Comprehensive component monitoring
Non-continuous monitors (require specific driving conditions): - Catalyst monitor - Heated catalyst monitor - Evaporative system monitor - Secondary air system monitor - Oxygen sensor monitor - Oxygen sensor heater monitor - Exhaust Gas Recirculation (EGR) / Variable Valve Timing (VVT) monitor
Newer monitors: Vehicles meeting OBD-II 2020+ requirements may support additional monitors, including Gasoline Particulate Filter (GPF) for direct-injection engines and updated EVAP monitoring procedures. Hybrid vehicles replace some traditional monitors with battery/motor-specific checks.
8.2 Why Monitors Matter
During a state emissions inspection, the technician checks whether all applicable readiness monitors have completed. If too many monitors are “not ready” (incomplete), the vehicle will fail the inspection — even if no DTCs are present. This commonly happens when:
- The battery was recently disconnected (clears all monitors)
- DTCs were recently cleared with Service 0x04 (also resets monitors)
- The vehicle has not been driven enough under the required conditions
💡 Tip: After clearing DTCs, you typically need to drive 50–100 miles through a mix of city and highway driving to complete all readiness monitors. Some monitors require specific conditions (e.g., cold start, steady-speed cruising) that may take multiple drive cycles.
Troubleshooting
💡 Tip: If this table is truncated in your viewer, scroll horizontally or view the raw Markdown source.
| # | Symptom | Likely cause | Diagnostic step | Resolution |
|---|---|---|---|---|
| 1 | Scan tool shows “no connection” or “unable to connect” | Wrong protocol, OBD2 port has no power, blown fuse | Check pin 16 for +12V with a multimeter; verify pin 4/5 for ground | Check the OBD2 fuse (consult vehicle manual for location); try a different scan tool |
| 2 | Only some PIDs return data | Vehicle does not support all PIDs | Query PID 0x00 to see supported PIDs bitmask | Only request PIDs that appear in the supported PID response |
| 3 | DTC count is 0 but MIL (check engine light) is on | Permanent DTCs (Service 0x0A) cannot be cleared by scan tool | Query Service 0x0A for permanent DTCs | Repair the underlying fault; the MIL will clear after the repair is verified by drive cycles |
| 4 | VIN request returns no data | Vehicle ECU does not support Service 0x09 PID 0x02 | Try querying PID 0x00 of Service 0x09 to see supported info PIDs | VIN may only be available through manufacturer-specific diagnostics |
| 5 | Readiness monitors show “not ready” after DTC clear | Normal — monitors reset when DTCs are cleared | Drive the vehicle through a complete drive cycle (50-100 miles) | Complete the required drive cycle; specific monitor conditions may be needed |
| 6 | Getting responses from multiple ECUs | Functional request (0x7DF) broadcasts to all ECUs | Use physical addressing (0x7E0-0x7E7) to target a specific ECU | Filter responses by CAN ID (0x7E8-0x7EF) to identify which ECU sent each response |
| 7 | OBD2 adapter works on one vehicle but not another | Different protocol or bit rate | Auto-detect may fail; try manually setting the protocol | Configure the adapter for the correct protocol (CAN 500k, CAN 250k, ISO 9141, etc.) |
References
-
SAE J1962:2016 — Diagnostic Connector. SAE International. Defines the OBD2 16-pin connector pinout and physical requirements.
-
SAE J1979:2014 — E/E Diagnostic Test Modes. SAE International. Defines all ten OBD2 service modes and PIDs.
-
SAE J2012:2013 — Diagnostic Trouble Code Definitions. SAE International. Defines the DTC format and standard codes.
-
ISO 15031-5:2015 — Communication between vehicle and external equipment for emissions-related diagnostics — Part 5: Emissions-related diagnostic services. ISO equivalent of SAE J1979.
-
ISO 15765-4:2016 — Diagnostic communication over Controller Area Network (DoCAN) — Part 4: Requirements for emissions-related systems. Defines how OBD2 uses CAN.
-
ISO 14230-4:2000 — Keyword Protocol 2000 — Part 4: Requirements for emission-related systems. Defines OBD2 over KWP2000.
-
ISO 9141-2:1994 — Road vehicles — Diagnostic systems — Part 2: CARB requirements for interchange of digital information. Legacy K-line protocol.
-
SAE J1850 — Class B Data Communication Network Interface. PWM and VPW protocols.
-
Wikipedia — OBD-II PIDs. Community-maintained list of standard and manufacturer-specific PIDs.
-
SAE J1979-2 — OBD2 on UDS (OBDonUDS) for model year 2027+ vehicles. Defines the transition of OBD-II diagnostic services from the legacy J1979 framework to Unified Diagnostic Services (UDS, ISO 14229).
-
ISO 15765-2 — Diagnostic communication over CAN (ISO-TP transport layer). Defines the segmentation, flow control, and reassembly of diagnostic messages that exceed a single CAN frame.
Changelog
| Version | Date | Author | Summary of changes |
|---|---|---|---|
| 1.0 | 2026-03-16 | Telematics Tutorial Series | Initial publication |
```