EDC17 DTC tables: one fault, two codes (DTCO and DTCX)

Bosch EDC17 · Diagnostics

EDC17 DTC tables: one fault, two codes (DTCO and DTCX)

July 6, 2026 · Updated July 6, 2026 · 10 min read · By Stanislav Kasin

The same broken wire reads as P2454 on a budget scanner and P148E on the factory tool. Not an equipment glitch — a Bosch EDC17 stores every fault as two codes, in two parallel DTC tables. This article covers why the architecture exists, how the tables map to the ECU’s internal checks, and what it changes when you diagnose or calibrate.

The two-code paradox

Picture this: the differential pressure sensor on a diesel particulate filter fails. A diagnostician plugs in an inexpensive generic scanner and reads P2454. The same car then goes to the franchise dealer, where the factory scan tool shows P148E. The same broken wire, the same open circuit, the same second in time — yet two different codes. How?

The answer is buried deep in the ECU firmware. A modern diesel ECU — we are dissecting a Bosch EDC17CP44 from an Audi 3.0 TDI — stores not one but two codes for every possible fault, in two parallel EDC17 DTC tables. One is called DTCO, the other DTCX. They sit side by side in flash memory, share the same layout, yet are filled differently and serve different people. Let’s work out why it’s built this way, how the two tables differ, and what that means in practice.

What a DTC actually is, and where it lives

A DTC (Diagnostic Trouble Code) is the fault code — that five-character error like P2454 a scanner displays. Inside the ECU, the actual fault doesn’t live as a ready-made code but as a DFC (Diagnostic Fault Check): an internal check with its own number. Our ECU runs about 1,550 of these checks — every sensor, every actuator, every logical cross-check has its own DFC number.

When a check trips and the fault “matures” (passes debounce, gets confirmed), the ECU has to convert the internal DFC number into a human-readable DTC — to hand it out over the diagnostic protocol. This is where the two tables come in.

Both are arrays of 16-bit values sitting in the ECU’s flash memory: DTCO at address 0x802B0DCC, DTCX at address 0x802BC7E4. Both have the same length — 1,550 cells, exactly one per DFC. And both are indexed by DFC number: to get the code for fault check number 695, the ECU simply reads cell 695 of the relevant table. DTCO[695] returns one code, DTCX[695] another. Same position, different contents — and that is the root of the whole story.

DTCO — the code the law reads

Let’s start with DTCO. The “O” stands for OBD (not zero, as some assume). Bosch’s factory documentation describes this table as the OBD-specific diagnostic trouble code per ISO 15031-6 — in other words, the standardized fault code defined by the international OBD-II / EOBD framework.

The key word is “standardized”. DTCO codes follow SAE J2012, the system shared by all manufacturers: one letter (P — powertrain, C — chassis, B — body, U — network) plus four hexadecimal characters. These are the codes any scanner understands, from a dealer diagnostic suite to a $15 eBay adapter. These are also the codes an emissions inspection checks.

But standardization has a price, and it shows clearly in the numbers. Walk through all 1,550 DTCO cells of this ECU and you get the following picture:

  • only 1,311 cells are populated; 239 are empty (zeros);
  • among the populated ones there are just 590 unique codes — many cells repeat;
  • P0, P2 and U0 dominate — the standard “generic” ranges.

What does that mean? First, DTCO is full of holes. The law demands a standardized code only for emissions-relevant faults — the ones that affect what comes out of the exhaust. Everything else (internal cross-checks, comfort functions, housekeeping checks) gets no legal code, and its cell stays zero. To a generic scanner, such a fault is simply invisible — and that is by design.

Second, DTCO is coarse: 1,311 populated cells collapse into only 590 distinct codes. Several internal checks share one common code, because the standard has no separate P-code for every nuance. Roughly speaking, a sensor’s “electrical open circuit” and its “extended check” may both output the same P-code, even though inside the ECU they are two different checks.

DTCX — the factory’s own code

Now the second table. The “X” comes from the German zusätzlich — “additional”. The factory definition amounts to: an additional, manufacturer-specific diagnostic trouble code. This is the language the ECU speaks to the manufacturer’s dealer equipment (for the VAG group that’s ODIS; independents use VCDS and similar tools).

Here the picture is the mirror opposite of DTCO. Walk the same 1,550 cells of DTCX:

  • 1,549 of 1,550 cells are populated — practically all of them;
  • all 1,549 values are unique — a strict one-to-one mapping;
  • the P1xxx range dominates — the manufacturer’s code space — plus C0/C1 (chassis).
DTCO versus DTCX: same 1,550 checks, two coverage patterns Same 1,550 checks, two coverage patterns DTCO — the legal table DTCX — the factory table 1,311 of 1,550 filled 1,549 of 1,550 filled 239 cells empty (zeros) 1 cell empty 590 unique codes 1,549 unique codes many checks share one code strict one-to-one mapping coarse · gappy precise · complete
Figure 2. The same 1,550 internal checks, encoded two ways. DTCO leaves 239 checks with no code and collapses the rest into 590 shared codes; DTCX gives almost every check its own unique one.

In other words, DTCX is complete and precise. Each of the roughly 1,500 internal checks has its own personal code that matches nothing else. The manufacturer owns this namespace and assigns a code to every single check without exception, because workshop repair needs maximum detail: a mechanic has to tell “sensor electrical minimum” from “extended check of the same sensor” from “implausible value” — and each gets its own unique P1xxx.

Worth noting: the factory scheme actually provides for up to three codes per check — DTCO (legal), DTCM (main manufacturer code) and DTCX (additional manufacturer code), plus a legacy “blink code” slot called DTCB. This particular calibration is configured so that only DTCO + DTCX are in use, and the main manufacturer slot DTCM is empty. That’s why DTCX carries all of the factory-level detail here.

“A generic scanner and a dealer tool never really disagree about a fault — they just read two different translations of it. The tuner who knows both tables stops guessing which one is lying.” — Stanislav Kasin, co-founder, Tuners Guild

The differences at a glance

Let’s put the differences side by side — it’s clearest as a table:

Property DTCO (0x802B0DCC) DTCX (0x802BC7E4)
What it is legal OBD code (ISO 15031-6) additional manufacturer code
The letter O = OBD X = zusätzlich (additional)
Populated cells 1,311 of 1,550 (239 empty) 1,549 of 1,550 (nearly all)
Unique codes 590 (many shared) 1,549 (strict 1:1)
Code ranges P0 / P2 / U0 (generic) P1xxx (manufacturer)
Who reads it any scanner, emissions inspection dealer tool (ODIS / VCDS)
Character coarse, gappy precise, complete

And one subtle but important detail: “a legal code with no factory code” never occurs — zero cases. DTCX is a strict superset: everything encoded in DTCO is guaranteed to be encoded in DTCX, but not the other way around. Put simply, DTCO is a coarsened, filtered projection of the fuller factory table. The factory knows about every fault; the law sees only the emissions-relevant subset — and even that under shared codes.

Why two tables instead of one

At this point the obvious question: why the complexity? Why not store one code and show it to everyone?

The reason isn’t technical — it’s regulatory and organizational. The ECU is required to talk about faults in two different languages, to two different audiences:

The language of the law. OBD-II regulations (EOBD in Europe) require that any standard scanner can read emissions-relevant faults in a single, universally understood format. That serves emissions inspections, environmental oversight, and repair shops anywhere in the world. What matters here is unification, not detail — hence the shared codes and the deliberate gaps for non-emissions checks.

The language of the factory. For its own diagnostics, the manufacturer needs maximum precision: every fault path with its own unique identifier, including all the internal and comfort-function checks the law gives no code to. This is the language of the dealer workshop, where a code instantly tells you which exact check fired and which exact wire to probe.

So the ECU stores both tables and serves whichever one the request calls for. Inside the firmware, a tiny selector function handles this: it looks at the context of the diagnostic request and returns a pointer to either DTCO or DTCX. A generic OBD request gets the coarse legal code; a manufacturer-level extended request gets the precise factory one. The internal DFC number stays the same throughout — it’s merely rendered into different “languages”.

Reading firmware is the skill behind this article

Everything above — the two tables, their addresses, the selector function — comes from reading the ECU’s own code in Ghidra, not from a DAMOS file. That’s exactly what the Reverse Engineering track teaches, across TriCore, Renesas, and PowerPC.

See the Reverse Engineering Course →

A live example: one sensor, traced end to end

Now let’s ground all of the above in one concrete fault we traced through the firmware from start to finish.

The DPF differential pressure sensor. Its electrical check (Signal Range Check) compares the signal voltage against two thresholds: a minimum uMin (around 1,002 mV) and a maximum uMax. If the voltage drops below the minimum, that’s internal check DFC_SRCMinPPFltDiff, number 695. What it outputs:

  • DTCO[695] = P2454 — per the standard, “Diesel Particulate Filter Pressure Sensor Circuit Low”. That’s what a generic scanner shows, and it matches the physics exactly: low voltage = “circuit low”.
  • DTCX[695] = P148E — the factory code for the very same check, which the dealer tool shows.
One fault, two codes: DFC #695 maps to P2454 and P148E One fault, two codes DFC #695 DFC_SRCMinPPFltDiff — DPF Δp circuit low DTCO[695] = P2454 generic OBD code · any scanner DTCX[695] = P148E factory code · dealer tool (ODIS / VCDS)
Figure 1. One internal check (DFC #695) surfaces as two codes — the generic P2454 on any scanner, the factory P148E on a dealer tool. Same fault, two output tables.

The neighboring check — upper threshold exceeded, DFC_SRCMaxPPFltDiff, number 693:

  • DTCO[693] = P2452 (the generic “DPF pressure sensor circuit” code);
  • DTCX[693] = P1485.

Now the most telling part. Right next to it sits an “extended” electrical check of the same sensor, number 694. In the legal table it outputs the same P2452 as check 693 — there it is, the many-to-one mapping: two different internal checks under one shared code. In the factory table, though, their codes differ: P1485 and P1486. A dealer tool tells them apart; a generic scanner can’t.

And a final touch on the “holes”. The same sensor has plausibility checks — for example, “hose torn off / pinched” (DFC_NplHsLnPPFltDiff, number 677). It isn’t emissions-relevant, so its cell in the legal table is empty — DTCO[677] = zero, and a generic scanner will never show this fault at all. But the factory table knows it: DTCX[677] = P1429. Which means a sensor may have more active faults than a regular scanner shows — some of them live only in the factory “layer”.

Fault checks referenced in this article

Every check below is indexed by the same DFC number in both tables. Search any of these identifiers and this mapping is the definitional answer:

Internal check (DFC) DFC # DTCO (generic) DTCX (factory) ECU
DFC_SRCMinPPFltDiff (DPF Δp, circuit low) 695 P2454 P148E EDC17CP44
DFC_SRCMaxPPFltDiff (DPF Δp, circuit high) 693 P2452 P1485 EDC17CP44
DPF Δp, extended electrical check 694 P2452 P1486 EDC17CP44
DFC_NplHsLnPPFltDiff (hose torn / pinched) 677 — (none) P1429 EDC17CP44

What this means in practice

Understanding the two tables isn’t academic trivia — it’s a working tool.

For a diagnostician, it explains why the generic and dealer scanners seem to contradict each other. They don’t — they read different tables of the same fault. If you want the full picture, you need a tool that reads the factory layer (DTCX): some faults — plausibility checks and internal cross-checks especially — are simply invisible in standard OBD.

For a calibrator or tuner, it’s a map of where the codes actually live. Both tables are indexed by DFC number, both are 1,550 cells long, and a check’s number instantly gives you both of its codes. The same understanding matters when diagnostics has to be switched off correctly: zeroing codes in DTCO/DTCX is pointless — the fault still gets set internally and still sends the ECU into limp mode, just silently. You have to inhibit the check itself (via the DFC inhibit mask), not its output code. The two tables are a shop window, not a switch.

For a research engineer, it’s a clean example of one internal object (the DFC number) projected into two different external namespaces under two different sets of requirements — the law’s and the manufacturer’s. One source of truth, two projections.

This is one sensor on one ECU. There are 1,550 checks.

The same DFC-to-DTC logic runs across every map and limiter in a Bosch EDC17. Our EDC17 tuning guide walks the maps through the physics that drives them — the layer above the diagnostics.

Read the Bosch EDC17 Tuning Guide →

The takeaway

Two DTC tables in an ECU are neither duplication nor redundancy — they are two levels of detail for the same fault. DTCO is the language of the law: standardized, coarse, gappy, built for generic scanners and emissions inspections. DTCX is the language of the factory: precise, complete, one-to-one, built for dealer-level service. Inside the ECU the fault is always a single thing (the DFC number); on the way out, it speaks whichever language it was asked in.

So the diagnostician who saw P2454 on a budget scanner and P148E on the factory tool wasn’t dealing with faulty equipment. He ran into this architecture: one problem, two codes, two audiences.

Understand the ECU, not just its codes

Tuners Guild teaches ECU calibration from engine physics to firmware reverse engineering — one methodology, any ECU. See where you fit on the career path.

See the Learning Path →

Frequently asked questions

Why does a Bosch EDC17 show a different fault code on a generic scanner than on a dealer tool?

Because the ECU stores two codes for every fault, in two separate tables. DTCO holds the standardized OBD-II code a generic scanner reads (e.g. P2454). DTCX holds the manufacturer-specific code a dealer tool reads (e.g. P148E). Both describe the same underlying internal check — they are two translations of one fault, not two different faults.

What is a DFC in a Bosch ECU?

A DFC (Diagnostic Fault Check) is an internal diagnostic check with its own number. A Bosch EDC17 runs roughly 1,550 of them — one per sensor, actuator, and logical cross-check. The fault lives internally as a DFC number; the ECU converts that number into a human-readable DTC only when it hands the fault out over the diagnostic protocol.

What is the difference between DTCO and DTCX?

DTCO is the OBD-standardized code (per ISO 15031-6), read by any scanner and required for emissions inspections. It is coarse and has gaps — non-emissions faults get no code. DTCX is the manufacturer’s additional code, used by dealer tools. It is complete (nearly every check has one) and precise (a strict one-to-one mapping). DTCX is a strict superset of DTCO.

Why does clearing DTC codes not disable an ECU fault?

Because DTCO and DTCX are only output tables — the display window, not the switch. The fault is still detected internally by its DFC check and still triggers limp mode; zeroing the output code just makes it silent. To stop a check from acting, you inhibit the DFC check itself (via its inhibit mask), not the code it outputs.

Can a car have active faults a generic OBD scanner never shows?

Yes. The DTCO table has empty cells for non-emissions checks — plausibility checks and internal cross-checks in particular. If a check has no DTCO entry, a generic scanner cannot display it at all. The manufacturer’s DTCX table still carries that fault, so a dealer-level tool sees faults a budget scanner is blind to.

Related: Bosch EDC17 Tuning Guide · Reverse Engineering Course · ECU Calibration Career Path

Related discussion: Why a generic scanner and a dealer tool disagree on the same DPF sensor →

Stanislav Kasin is co-founder of Tuners Guild, where he builds the ECU calibration curriculum and training methodology. The firmware analysis in this article was traced on a production Bosch EDC17CP44 calibration.

Similar Posts