OBD-2 in Bullet Points and Examples

Scope

OBD-2 is a standard that defines the following:

  • A connector (DLC == Data Link Connector) to connect a “test equipment” to your car, which for recent enough cars connects to a CAN bus in your car.
  • A (mostly) car-independent protocol stack on top of CAN that has access to both real-time and snapshot data in your car’s computer system.

Other stuff defined does not matter if your car is recent enough.

CAN bus

  • A CAN bus (1 twisted pair) is shared by many nodes branching off it.
  • Messages are broadcast on the bus. Everyone sees everything (but may not deliver every message; there can be software and/or hardware filters).
  • Message consists of an ID (either 11 or 29 bits), and 0 to 8 bytes of data.
    • A message with 11-bit ID is also called a “Standard Frame”
    • A message with 29-bit ID is also called an “Extended Frame”
  • Arbitration (what happens when 2 nodes are both attempting to send a message) is deterministic, based on the ID of the message: Lower numerical value ID wins. This means the ID of a message is also its priority.

The Protocol Stack

CAN messages are denoted 012 # b0 b1 b2 b3 b4 b5 b6 b7, where:

  • 012: ID
  • b0 to b7: data

Numbers in code blocks are assumed hexadecimal unless declared otherwise.

In the following sections we will dissect the OBD-2 protocol stack from CAN ID to each byte of the payload.

Request and Response IDs

A CAN bus supporting OBD-2 (one that is connected to the DLC) has 1 master node — the test equipment, and at least one slave node — the ECU(s). Master requests information by sending messages with IDs in a specific range. Slaves respond with corresponding IDs. The IDs are defined as follows:

Standard Extended
Broadcast request 7DF 18DB33F1
Single-node request 7E? where ? = 0 ~ 7 18DA??F1 where ?? = 00 ~ FF
Response 7E? where ? = 8 ~ F 18DAF1?? where ?? = 00 ~ FF

So just looking at the address space, there can be at most 8 slave nodes if Standard ID is used; 256 if Extended. However, OBD-2 allows at most 8 regardless of which ID format is used. In fact, everything else is equivalent regardless of which ID format is used (some cars support only one and others both though), so WLOG, we can assume that Standard ID is used.

Example (omitting data for now):

  • Master transmits 7DF # ... (broadcast request)
    • Slave 0 responds 7E8 # ...
    • Slave 3 responds 7EA # ...
    • Slave 7 responds 7EF # ...
  • Master transmits 7E3 # ... (request for slave 3)
    • Slave 3 responds 7EA # ...

Transport Layer, the Short Story

The length of an OBD-2 message (regardless of request or response) is 0 ~ 4095 Bytes. The length of a CAN message, on the other hand, is 0 ~ 8 Bytes. This means one OBD-2 message may not fit in one CAN message. Therefore fragmentation is needed, and the OBD-2 transport layer provides this functionality.

If OBD-2 message length ≤ 7, then the CAN message consists of 1 byte of OBD-2 message length, followed by the OBD-2 message payload. Length of the CAN message can be longer than (1 + length of the OBD-2 message) — trailing bytes after (1 + length) are simply ignored.

Examples:

  • 7DF # 02 01 00: length = 2, payload = 01 00
  • 7E0 # 06 41 00 B6 3D A8 12 55: length = 6, payload = 41 00 B6 3D A8 12 (notice the last byte 55 is NOT included in the payload)

We will cover the (length > 7) case later.

Service ID + Parameter ID

Valid OBD-2 messages always contain at least 2 bytes.

First Byte

  • bit 6: 0 => request, 1 => response
  • bits 0 to 5 (inclusive): “Service ID” (like a category, or a database table name; blame naming on historical reasons). This is matched between request and its response.

Examples:

  • 09: request, service 09
  • 49: response, service 09
  • 1E: request, service 1E
  • 5E: response, service 1E

Second Byte

The entire second byte is the “Parameter ID” (like a key in a database table). This is matched between request and its response.

Semantics

The Service ID and Parameter ID together form an address space. A part of this address space has (car-independent) standard defintions. Wikipedia has an incomplete list for it. For car-specific Parameter IDs, the third byte could also be considered a part of the Parameter ID, but WLOG it can also be treated as the first byte of the “real payload”.

Simple example:

  • 7E0 # 02 01 0C: request, service 01, parameter 0C
  • 7E8 # 04 41 0C 2E E0: response, service 01, parameter 0C, data 2E E0

The request is for vehicle engine RPM. Payload is decoded as 0x2EE0 / 4 = 3000 RPM.

Longer example:

  • 7DF # 02 01 00: request, service 01, parameter 00
  • 7E8 # 06 41 00 B6 3D A8 12 55: response, service 01, parameter 00, data B6 3D A8 12 55

The request is for a bit vector of supported PID in service 01, parameter 01 to 20 (inclusive). Meaning of the payload (B6 3D A8 12 55) is left as an exercise.

Transport Layer, the Longer Story

One example is to read the VIN (Vehicle Idenfication Number), an ASCII string that is obviously longer than 7 bytes. This is defined in standard as service 09, parameter 02, so:

  • 7DF # 02 09 02: request, service 09, parameter 02
  • 7E8 # 10 14 49 02 01 53 48 48: ???

If you squint hard, 49 02 looks like response for service 09, parameter 02, and 53 48 48 is ascii "SHH" which is a prefix of my VIN. Where is the rest? Recall that the first byte of short OBD-2 messages is its length: 00 to 07. Anything else means we’re dealing with a long (fragmented) message.

To decode part of a long message, first look at the first nibble (higher 4 bits of the first byte):

  • 1: first CAN message of a fragmented OBD-2 message
    • the next 3 nibbles (lower 4 bits of the first byte, concatenated with the entire second byte) is the total length of the OBD-2 message (in this example, 014 = 20)
    • all the rest are the first bytes of the OBD-2 payload
  • 2: following CAN messages of a fragmented OBD-2 message
    • the next nibble (lower 4 bits of the first byte) is a rolling counter (1, 2, 3, …, F, 0, 1, 2, …)
    • all the rest are part of the OBD-2 payload

Also, to prevent overflowing the receiver, by default the sender only sends the first CAN message, then starts waiting for a flow control message back from the receiver. Details can be found in the Wikipedia article on transport layer, but most commonly, sending 30 00 00 instructs the sender to proceed sending rest of the packets at once. Continuing with the example:

  • 7E8 # 10 14 49 02 01 53 48 48: long message, length = 014 (20 bytes), first bytes of payload 49 02 01 53 48 48
  • 7E0 # 30 00 00: continue to send, all remaining frames, no separation
  • 7E8 # 21 46 4B 38 47 37 35 4A: more bytes of payload 46 4B 38 47 37 35 4A
  • 7E8 # 22 55 xx xx xx xx xx xx: more bytes of payload 55 xx xx xx xx xx xx (data redacted for my own privacy 😉

Put all fragments together: 49 02 01 53 48 48 46 4B 38 47 37 35 4A 55 xx xx xx xx xx xx which matches the declared length of 20 bytes. This is service 09, parameter 02, and the data decoded as ASCII reads “SHHFK8G75JU……” which is my VIN.

P.S.: Actually my vehicle only supports extended ID (request 18DA10F1, response 18DAF110); again it does not make a difference in data.

Leave a comment