Automotive

UDS Diagnostics

UDS — Unified Diagnostic Services, ISO 14229 — is the protocol a diagnostic tool speaks to an ECU: "what's your fault code?", "give me your VIN", "let me reprogram you." It rides on top of CAN as a request/response protocol built on one very literal rule: a response's first byte is either your request's Service ID plus 0x40 (success), or 0x7F (failure, with a specific reason attached).

Quick reference
What it is
A request/response diagnostic protocol riding on CAN, letting a tool query or command an ECU — read fault codes, read live data, reprogram firmware, unlock protected functions.
Mental model
A literal question-and-answer protocol: every request is a Service ID plus parameters; every response is either "yes, and here's your answer" or "no, and here's exactly why."
Key numbers
Positive response = request SID + 0x40 (request 0x22 → positive response 0x62); negative response is always 0x7F.
Reach for it when
Reading/clearing fault codes, reading live ECU data, unlocking protected diagnostic or programming functions, or building any diagnostic tool integration.

Why a dedicated diagnostic protocol?

A raw CAN frame's meaning is fixed at design time — an identifier always means the same signal, agreed on by every node ahead of time. Diagnostics needs the opposite: an open-ended way for an external tool, from any vendor, to ask an ECU an arbitrary question it wasn't necessarily hardwired to answer. UDS solves this with a standard service catalog and message format every compliant ECU and every compliant tool both understand, without needing a bespoke message design per query.

The request/response pattern and the +0x40 rule

A diagnostic tool sends a request with a service ID; the ECU replies with the same service ID plus 0x40 to signal success Tool ECU Request: SID 0x22 + DID 0xF190 "Read Data By Identifier" — asking for the VIN Response: 0x62 0xF1 0x90 <VIN bytes> 0x62 = 0x22 + 0x40 → positive response response[0] == requestSID + 0x40 → success. response[0] == 0x7F → failure.
This single rule is the whole "is it true or false" logic in UDS — check one byte against the request's own SID plus 0x40.

Checking a response is almost embarrassingly simple once you know the rule:

bool uds_is_positive_response(uint8_t request_sid, const uint8_t *response) {
    return response[0] == (uint8_t)(request_sid + 0x40);
}
// response[0] == 0x7F means a negative response instead —
// see "Negative responses and NRCs" below for what comes next.

Common UDS services

SIDServicePurpose
0x10Diagnostic Session ControlSwitch sessions (default, extended, programming) — some services only work outside the default session.
0x11ECU ResetRequest the ECU reset itself (soft or hard reset).
0x14Clear Diagnostic InformationClear stored fault codes.
0x19Read DTC InformationRead stored/pending fault codes and their status.
0x22Read Data By IdentifierRead a specific data value by its Data Identifier (DID) — VIN, live sensor values, calibration data.
0x27Security AccessSeed-key handshake to unlock protected services — see below.
0x2EWrite Data By IdentifierWrite a value by DID — calibration, configuration.
0x31Routine ControlStart/stop/check a defined ECU routine (a self-test, an actuator test, an erase routine).
0x34 / 0x36 / 0x37Request Download / Transfer Data / Request Transfer ExitThe three-step sequence that carries a new firmware image into the ECU during reflashing.
0x3ETester PresentA keep-alive so the ECU doesn't time out of a non-default session while the tool is momentarily idle.

Negative responses and NRCs

A negative response is always three bytes: 0x7F, the original request's SID (so the tool knows which request failed), and a Negative Response Code (NRC) saying exactly why.

NRCMeaning
0x11Service not supported
0x12Sub-function not supported
0x13Incorrect message length or invalid format
0x22Conditions not correct (e.g. wrong session, engine running when it shouldn't be)
0x31Request out of range
0x33Security access denied
0x78Request correctly received, response pending — "still working, don't give up on me yet"

0x78 is worth calling out specifically: it's not a failure at all, it's the ECU explicitly buying itself more time on a slow operation. A tool that doesn't recognize this and extend its timeout will misread a perfectly successful long-running operation as a failure.

Security access: the seed-key handshake

Many services (reflashing, calibration writes, certain routines) are locked behind a challenge-response handshake so an arbitrary tool can't invoke them without proving it knows a manufacturer secret:

  1. Tool requests a seed: 0x27 with an odd sub-function (e.g. 0x01).
  2. ECU returns a pseudo-random seed value.
  3. Tool computes a key from that seed using a manufacturer-specific algorithm (often involving a shared secret).
  4. Tool sends the key back: 0x27 with the next even sub-function (e.g. 0x02).
  5. ECU validates the key and unlocks the protected services on success, or returns securityAccessDenied — and counts the failure toward a lockout, on failure.

Carrying UDS on CAN: ISO-TP segmentation

A classical CAN frame carries at most 8 data bytes, but plenty of UDS messages (a VIN, a chunk of firmware) are longer than that. ISO-TP (ISO 15765, the Transport Protocol layer) segments a longer message across multiple CAN frames, using the first byte (or nibble) of each frame's data as a PCI (Protocol Control Information) field that tells the receiver which of four frame types it's looking at:

The four ISO-TP frame types, distinguished by the top nibble of the first PCI byte: Single Frame, First Frame, Consecutive Frame, and Flow Control 0 L Single Frame whole msg ≤ 7 bytes 1 len(12b) First Frame starts a multi-frame msg 2 SN Consecutive Frame sequence number, wraps 3 FS Flow Control continue / wait / abort Every frame's first nibble is the type (0-3 above); the rest of that byte (and sometimes the next byte too) carries type-specific detail — a length, a sequence number, or a flow-control status. First Frame's length field is 12 bits wide: 4 bits in this nibble, 8 more bits in the very next byte.
This is exactly what "standard frame vs. enhanced frame" means in the diagnostic context — a Single Frame fits everything in one go; anything longer needs the First Frame / Consecutive Frame / Flow Control dance below.

For a message longer than one frame, the tool and ECU actually coordinate the transfer:

A multi-frame ISO-TP transfer: the sender's First Frame is answered with a Flow Control frame, after which the sender streams Consecutive Frames until the message is complete Tool ECU First Frame — total length 20, first 6 bytes Flow Control — continue, block size, min. gap Consecutive Frame #1 — bytes 7–13 Consecutive Frame #2 — bytes 14–20 All 20 bytes reassembled — the UDS message is now complete
The Flow Control frame is the receiver setting the pace — its block size and minimum separation time keep a fast sender from overrunning a slower receiver's buffer.

Scan tools and CAN analyzers

OBD-II scan tool
A consumer/technician-level tool reading standardized emissions-related fault codes and live data — a narrower, older, legally-mandated sibling of full UDS.
Professional/OEM diagnostic tool
Full UDS access — manufacturer-specific DIDs and routines, security access, and firmware reflashing via the download/transfer service sequence.
CAN analyzer / bus analyzer
Tools like Vector CANoe/CANalyzer or PCAN-View capture and display every frame on the bus, not just a curated diagnostic session — used for development and debugging, not just repair. See SocketCAN for the Linux-side equivalent.
J2534 pass-through
A standardized PC-to-vehicle interface API letting general-purpose PC software (not just a dedicated handheld tool) send and receive diagnostic messages — heavily used by OEM reprogramming software.

Real-world applications

Gotchas

Field notes