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
: IDb0
tob7
: dataNumbers 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 # ...
- Slave 0 responds
- Master transmits
7E3 # ...
(request for slave 3)- Slave 3 responds
7EA # ...
- Slave 3 responds
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 byte55
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, service09
49
: response, service09
1E
: request, service1E
5E
: response, service1E
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, service01
, parameter0C
7E8 # 04 41 0C 2E E0
: response, service01
, parameter0C
, data2E E0
The request is for vehicle engine RPM. Payload is decoded as 0x2EE0
/ 4 = 3000 RPM.
Longer example:
7DF # 02 01 00
: request, service01
, parameter00
7E8 # 06 41 00 B6 3D A8 12 55
: response, service01
, parameter00
, dataB6 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, service09
, parameter02
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
- 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,
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
- the next nibble (lower 4 bits of the first byte) is a rolling counter (
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 payload49 02 01 53 48 48
7E0 # 30 00 00
: continue to send, all remaining frames, no separation7E8 # 21 46 4B 38 47 37 35 4A
: more bytes of payload46 4B 38 47 37 35 4A
7E8 # 22 55 xx xx xx xx xx xx
: more bytes of payload55 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
, response18DAF110
); again it does not make a difference in data.