A modern car is a parliament of fifty-odd computers, each with its own grievances. This primer is the protocol they answer to — what UDS asks, how a DTC is shaped, and what changes when the powertrain runs on electrons instead of gasoline.
Imagine a doctor with a stethoscope. Now imagine fifty patients in one room, each with its own heartbeat, each capable of failing silently. That is, more or less, the situation inside a modern car. The diagnostic system is the stethoscope — and more importantly, the language each patient agrees to answer in.
A modern vehicle contains anywhere from 30 to 150 ECUs (Electronic Control Units) — each a small computer with its own firmware, memory, sensors and faults. When something goes wrong, the technician needs to ask, in a uniform way:
To make this possible, the industry standardized one common diagnostic protocol that runs on top of whatever bus the ECU happens to be on. That protocol is UDS — Unified Diagnostic Services, defined in ISO 14229. core idea One language, fifty ECUs, every workshop on Earth.
UDS doesn't run alone. It sits on top of a transport, which sits on a network, which sits on a wire. Think of it as a letter: UDS is the language inside the envelope, the transport is how the envelope is sealed and segmented, the network decides how envelopes get routed.
For most of the last twenty years, the wire was a classical CAN bus at 500 kbps, the transport was ISO-TP, and CAN identifiers were assigned in pairs (one for request, one for response) per ECU. EVs and modern domain controllers increasingly use CAN-FD (up to 8 Mbps payload) and, for high-bandwidth or zonal architectures, DoIP (Diagnostics over IP) — the same UDS payload, just inside Ethernet/TCP frames.
22 F1 90 ("read the VIN") looks the same whether it travels on CAN, CAN-FD or DoIP. Only the envelope changes. This is what makes UDS such a durable standard.
Every UDS conversation has the same skeleton: tester sends a request, ECU sends back either a positive response or a negative response. That's it. The shape is brutally simple:
NRC 0x31 = requestOutOfRange. The ECU is saying "I understood you, but that DID is not one I serve."
Three patterns to memorize and you can read most UDS traces:
0x22 = read, 0x2E = write, 0x19 = read DTCs, 0x31 = run a routine.0x22 becomes 0x62, 0x10 becomes 0x50, etc.0x7F, then the original SID, then a one-byte NRC (Negative Response Code) explaining why.Common NRCs you'll see in the wild: 0x11 service not supported, 0x12 sub-function not supported, 0x13 incorrect message length, 0x22 conditions not correct (often: ECU is not in the right session or the high-voltage system is interlocked), 0x31 request out of range, 0x33 security access denied, 0x7E sub-function not supported in active session, 0x7F service not supported in active session, and the famous 0x78 — "requestCorrectlyReceived, responsePending" — the ECU's way of saying hold on, I'm working on it.
If UDS is a private conversation between the OEM and its workshop, OBD is a public broadcast in a language every emissions inspector on Earth has to speak. It predates UDS by a decade — but today it's best understood as a fixed, legally-mandated subset of UDS, exposing only what the regulator needs to see.
OBD stands for On-Board Diagnostics. It is the part of a vehicle's diagnostic system that is required by law to be present, accessible without special tools, and answer in a standardized way regardless of manufacturer. The 16-pin connector under your dashboard is its physical face. Every $20 scanner from a hardware store talks to it. So does your state's emissions inspection station.
| OBD-II | UDS | |
|---|---|---|
| Standard | SAE J1979, ISO 15031 | ISO 14229 |
| Purpose | Emissions compliance, mandated by regulators | OEM service, calibration, flashing — anything the OEM wants |
| Service IDs used | 0x01 – 0x0A (called "Modes") | 0x10 – 0x3E, 0x80+ (called "Services") |
| Parameter space | 1-byte PIDs (≈256 values, SAE-managed) | 2-byte DIDs (≈65,536 values, OEM-managed) |
| Connector access | Mandated, tool-free, near steering wheel | Same connector physically, but session-locked |
| Security | None — public | Seed/key (0x27), session-gated |
| DTC scope | Emissions-related only | Everything (powertrain, chassis, body, network) |
| Read by a $20 dongle? | Yes | Generally no (needs OEM tester or known DIDs) |
0x01 the ECU recognizes it as "OBD-II Mode 01". When you send 0x22 the ECU recognizes it as "UDS Read Data By Identifier". They share the same physical wires and the same response patterns — the OBD modes are simply a reserved low-numbered slice of the service ID space.
| Mode | Name | What it does |
|---|---|---|
0x01 | Show current data | Real-time sensor values via PIDs (RPM, speed, coolant temp, MAF, O2 voltage, …). The mode most consumer scanners spend 95% of their time in. |
0x02 | Show freeze-frame data | Snapshot of the PIDs at the moment the most recent emissions DTC was set. |
0x03 | Show stored DTCs | List of confirmed emissions-related fault codes. |
0x04 | Clear DTCs & stored data | Wipes the emissions DTC list, freeze frames, and resets readiness monitors. |
0x05 | O2 sensor test results | Legacy — non-CAN only. Replaced by Mode 06 on CAN-based OBD-II. |
0x06 | On-board test results | Non-continuous monitor test results, indexed by TID (Test ID) and CID (Component ID). |
0x07 | Show pending DTCs | Faults seen once but not yet confirmed across cycles. |
0x08 | Control on-board systems | Bidirectional control — rare on light-duty, used in heavy-duty/EOBD for component activation. |
0x09 | Vehicle info | VIN, calibration IDs, calibration verification numbers, ECU name. |
0x0A | Permanent DTCs | "Sticky" DTCs that cannot be cleared by Mode 04. Only erased when the ECU itself confirms the fault is gone over several drive cycles. Defeats the "clear codes before inspection" trick. |
So a complete OBD-II read of engine RPM looks like this on the wire:
Engine RPM is encoded as (256·A + B) / 4. So 1A F8 = 6904/4 = 1726 RPM. Every PID has its own encoding formula in the J1979 spec — most are simple linear scales.
The single most-read piece of OBD-II data is PID 0x01: a 4-byte status word that tells you whether the Check Engine Light is on, how many emissions DTCs are stored, and how many readiness monitors have run. The very first byte ("Byte A") is the part everyone cares about.
Bytes B, C, D of the PID 01 response encode readiness monitor status — 11 emissions monitors that must each run to completion before an emissions inspection station will pass the vehicle. The monitors split into:
0x00.
Until very recently, "OBD for BEVs" was almost a contradiction. With no engine, no catalyst, no evaporative emissions, the entire J1979 framework barely applied. The regulatory situation is changing fast:
EV-relevant PIDs added to SAE J1979 (and J1979-DA / J1979-2 amendments) include:
0x5B — Hybrid/EV battery pack remaining life (a single-byte percentage)0x8E — engine-friction-percentage (irrelevant for BEV but reported as "not supported")0x22 <DID> requests to OEM-specific DIDs reverse-engineered by the community.UDS defines ~25 services, but in practice you'll touch maybe a dozen. Click any tile to see its request shape, an example, and where it shows up in EV work.
The catalog covers the services you'll actually wire up in CDD/ODX files.
An ECU is not a single piece of cooperative software — it has modes. By default, in defaultSession, it will answer basic identification questions and report DTCs but refuse to change anything important. To unlock dangerous operations, the tester escalates the ECU into a different session, and for the truly dangerous ones, also passes a security check.
Sessions matter because UDS services are session-gated. You can't write a DID in default session. You can't erase flash unless you're in programming session. The ECU will return NRC 0x7F if you try.
For services that touch real state (writing calibration DIDs, erasing flash, unlocking developer routines), UDS layers in service 0x27: a challenge-response handshake.
27 01 → ECU replies 67 01 <seed>.27 02 <key>. If the key matches what the ECU computed independently, it replies 67 02 and you're unlocked.A DTC — Diagnostic Trouble Code — is the receipt the ECU prints when a monitored condition fails. The familiar form P0420 on a scanner screen is a human gloss; on the wire it is three bytes.
P0AA6 · P0D2A · U1A14 · C0561
Every five-character DTC encodes into two bytes (plus a third byte for the failure type byte, see below). The mapping:
P powertrain, C chassis, B body, U network/communication. (Bits 15..14 of the 2-byte code.)0 = generic / SAE-defined, 1 = manufacturer-specific, 2/3 = reserved or generic depending on system. (Bits 13..12.)On the bus, a DTC is always reported as 3 bytes: the 2-byte code above, then a failure type byte (FTB) — an SAE J2012 sub-classification telling you what kind of fault it was (open circuit, short to ground, signal out of range high, signal stuck, performance, etc.). So a complete DTC reference is e.g. C0 5C 12 = "C05C-12" = "HV Battery Cell 12 Voltage — circuit short to battery".
P0A, P0B, P0C, P0D — it almost certainly belongs to your high-voltage or e-machine domain.
A DTC alone is not very informative — has it just happened? Is it confirmed? Is it intermittent? UDS attaches an 8-bit status byte to every DTC report, where each bit is a flag from ISO 14229-1 Annex D. The status byte is how an ECU compresses an entire diagnostic history into 8 bits.
Click bits to toggle. Bit 0 is rightmost.
Service 0x19 has many sub-functions — they all ask "tell me about your DTCs" but slice the set differently. The five you'll use most:
19 01 <mask> — reportNumberOfDTCByStatusMask. "How many DTCs match this status pattern?" Mask 0x09 = "test failed + confirmed".19 02 <mask> — reportDTCByStatusMask. The same, but actually list them.19 04 <dtc> <snapshot#> — reportDTCSnapshotRecordByDTCNumber. Returns the freeze frame: the snapshot of operating conditions when the fault first occurred.19 06 <dtc> <ext#> — reportDTCExtDataRecordByDTCNumber. Aging counter, occurrence counter, fault detection counter.19 0A — reportSupportedDTC. "What DTCs do you support?" — useful to validate a CDD file against the actual ECU.And of course 14 FF FF FF — clearDiagnosticInformation with the wildcard group "everything". After a fault is repaired, this is how you reset the DTC memory.
This is where EVs diverge from ICE diagnostics. Click each subsystem to see what kinds of faults it reports — and what's actually happening physically when those DTCs fire.
Software updates over UDS are not a single command — they are a choreographed sequence. The basic shape, used by virtually every OEM (with variations), is:
10 03 — needed before you can elevate further.28 03 01 — stop non-diagnostic CAN traffic so the ECU isn't getting hammered with periodic messages during the flash.85 02 — flashing causes legitimate DTCs to fire all over the network; suppress them.10 02 — this typically reboots the ECU into the bootloader. You'll lose the link briefly.27 01 → seed; 27 02 <key> → key. Bootloader has its own seed/key, often stronger than the application's.2E F1 5A <tester ID, date> — leaves a forensic record of who flashed this part.31 01 FF 00 <addr> <len> — routine 0xFF00, eraseMemory. The ECU returns 0x78 (pending) for several seconds while it actually erases the flash sectors.34 <format> <addr> <len> — ECU answers with the max block size it can accept.36 <blockSeq> <data…> until the whole image is sent. The block sequence counter wraps 01→FF→00→01…37 — "I'm done sending."31 01 FF 01 — routine 0xFF01, checkProgrammingDependencies. The ECU verifies the new image's CRC/signature.11 01 — hardReset. The ECU reboots into the new application.85 01 to allow DTCs again, 28 00 01 to re-enable communication.0x36 with 0x73 (wrongBlockSequenceCounter) or 0x71 (transferDataSuspended). Always parse the response to 0x34 before sending the first 0x36.
python-udsoncan, can-isotp, scapy-automotive, CaringCaribou — perfectly capable for prototyping and fuzzing.0x22 (conditions not correct) unless the HV system is in a permitted state — typically HV+ contactor open, vehicle stationary, ignition in a specific mode. The CDD will list these as preconditions per service.UDS over DoIP over Activation Line over BroadR-Reach. The application payload is identical; what changes is that the gateway routes the message to the right zonal controller using the logical address in the DoIP header. Each ECU still has an address; it's just no longer a CAN ID.ISO 14229-1 — Unified Diagnostic Services (UDS) — specification and requirements. The canonical UDS document. Annex D defines the status byte bits. Annex A enumerates NRCs.
ISO 14229-3 — UDS on CAN (UDSonCAN). Application-layer specifics for CAN-bound implementations.
ISO 15765-2 — Network & transport layer for CAN (ISO-TP). Defines how UDS messages longer than 7 bytes are segmented across CAN frames with flow control.
ISO 15765-4 — Requirements for emissions-related systems (the CAN-based OBD-II profile). What an ELM327 dongle actually speaks. Constrains baud rate, message format, and ECU response timing.
ISO 15031 (parts 1–7) — Communication between vehicle and external test equipment for emissions-related diagnostics. The umbrella ISO framework that wraps J1979/J2012/J1962 into a single normative reference.
ISO 13400-2 — Diagnostics over Internet Protocol (DoIP). The Ethernet/TCP transport for UDS.
ISO 27145 (parts 1–6) — World-wide Harmonized On-Board Diagnostics (WWH-OBD). The modern UDS-based replacement for the J1979 "modes" framework, mandated for heavy-duty and increasingly extending to light-duty & BEVs.
SAE J1979 / J1979-DA / J1979-2 — E/E Diagnostic Test Modes. The OBD-II PIDs and modes (01–0A). The "-2" amendment adds extensive hybrid and EV PIDs.
SAE J1962 — Diagnostic connector. Defines the 16-pin DLC, pinout, mounting location and accessibility requirements.
SAE J1850 / ISO 9141-2 / ISO 14230-4 (KWP2000) — Legacy pre-CAN OBD-II physical/transport protocols. Phased out for new US vehicles after model year 2008.
SAE J2012 — Diagnostic Trouble Code Definitions. The official DTC dictionary including the P0Axx–P0Dxx hybrid/EV expansion.
SAE J2534 — Pass-thru programming. The US EPA-mandated interface that lets independent shops reflash emissions-related ECUs.
CARB Title 13 CCR §1968.2 & §1962.5 — California OBD-II regulation and the new BEV battery durability monitoring rule (effective 2026 MY).
ISO 15118 (parts 1–20) — Vehicle-to-grid (V2G) communication interface. Not diagnostics — it covers charging-station handshake, plug-and-charge, smart charging. Listed here to disambiguate; OBD and 15118 are often conflated.
ISO 26262 — Road vehicles — Functional safety. Not a diagnostic standard per se, but it's why your fault detection has confirmation counters and why some DTCs are tagged ASIL-relevant.
AUTOSAR Dem & Dcm SWS — Diagnostic Event Manager & Diagnostic Communication Manager software specifications. If your ECU runs AUTOSAR Classic, these two modules are your UDS stack.
ASAM MCD-2 D / ODX — open data format for diagnostic descriptions.
ASAM MCD-2 NET / FIBEX — network description format, often paired with ODX for the dealer tooling chain.