Zytek/Jaguar XJ220 diagnostics

Published: 2024-12-06

In the early 1990s, three limited-production models of Jaguar road cars -- the XJR-S, XJR-15, and XJ220 -- were fitted with Zytek engine management systems. The Zytek ECUs were chosen as an alternative to the systems used on normal production Jaguars at the time, and as a result, very little information was publicly available about the electronic diagnostic capabilities of the Zytek modules.

I managed to get a copy of the Zytek software for the XJ220 from some helpful folks at the Jag-Lovers forums, so I disassembled the code with the hope that I might be able to make diagnostics for these ECUs accessible to more people. I found that the main executable was built for DOS using Borland Turbo Pascal and the Borland Graphics Interface (BGI). The selection of Pascal as the development language actually made reverse-engineering a bit more difficult, since the implementation relied heavily on the language's 48-bit "Real" data type, which was the only built-in means to represent real numbers in Pascal at that time. This is a relatively obscure format that is not compatible with x86 floating-point or IEEE 754, and having never been a Pascal developer myself, it took me a moment to figure out what I was looking at.

Running in DOSBox

The executable uses fixed-count retry loops to detect link timeouts, and these will run much too fast with default DOSBox settings on modern hardware. I had success with a cycle-count setting of 1000. The virtual serial port can be set up to connect to a TCP socket using the following configuration in the serial section of the DOSBox config file:

serial1=nullmodem server:127.0.0.1 port:5555 transparent:1

Note that it is important to include the transparent:1 parameter, as this disables the DOSBox framing communication that would put extra bytes on the wire.

Protocol

From the perspective of the software, the serial link to the ECU is used like a normal RS-232 COM port. The control register for the baud rate generator is loaded with a divisor of 0x0B, and when this is divided into a reference rate of 115,200, it yields a baud rate of 10473. Bytes are framed with 8 data bits, no parity, and 1 stop bit. I don't have access to any of the hardware used by this system, so I can't comment on the voltage levels used on the Tx/Rx lines. They are almost certainly not the strange levels used by the RS-232 standard; instead, they are probably either TTL (0V / 5V) or K-Line (0V / 12V), or some combination of the two. Any level shifting is likely the responsibility of the box that sits between the PC's serial port and the diagnostic connector in the car.

It's important to note that the bytes sent by the ECU and received by the PC are actually the negation of the intended value, so the PC software needs to take the two's-complement of each byte as it is received. After each byte in a command sequence is sent by the PC software, the ECU will send a negated echo of the byte, and the PC software must wait to receive this before it sends the next byte.

There are three basic features of the ECU's diagnostic protocol:

  • Request data frame: PC sends 02. After echoing the negation of this command byte (FE), the ECU sends a fixed-length frame of 68 bytes (decimal), and then a final checksum-forcing byte. The value of this forcing byte will complement the 8-bit checksum of the data frame to bring the total to 0xFF. For example, if the 8-bit checksum of a frame is 0x36, the checksum-forcing byte would be (0xFF - 0x36), or 0xC9. Like all other bytes sent by the ECU, this 0xC9 checksum is sent over the wire as its negation, 0x37. Note that the checksum is computed using the original/intended byte values, not the negated values that are transmitted by the ECU.
  • Memory read: PC sends 03 <address-hi> <address-lo> <numbytes-hi> <numbytes-lo>. ECU responds with the sequence of bytes from the specified address. For some revisions of the ECU, this block of bytes is followed by a checksum forcing byte in the same fashion as the data frame checksum described above. The ECU revisions that use the checksum for the read command are identified by a value greater than or equal to 0x36 at offset 0xFFDE.
  • Memory write: PC sends 08 <address-hi> <address-lo> <value>. ECU responds with F1, which is 0F on the wire.

Data

XJ220 ECU memory locations

Offset Content Format
0003 Speed sensor LOW when bit 0 is clear, HIGH when bit 0 is set
0015 Synch sensor LOW when bit 0 is clear, HIGH when bit 0 is set
0066-0067 Throttle pot voltage 0000 == 0 mV, FFFF == 5005 mV
00F0 Fault codes Bit 3: Road speed input
Bit 2: Idle speed valve
Bit 1: Air conditioning relay drive
Bit 0: Fan relay drive
00F1 Fault codes Bit 7: B-Bank boost control valve
Bit 6: A-Bank pressure fault
Bit 5: B-Bank pressure fault
Bit 4: Pressure imbalance between banks
1086 Fault codes Bit 3: Road speed input
Bit 2: Idle speed valve
Bit 1: Air conditioning relay drive
Bit 0: Fan relay drive
1087 Fault codes Bit 7: B-Bank boost control valve
Bit 6: A-Bank pressure fault
Bit 5: B-Bank pressure fault
Bit 4: Pressure imbalance between banks
A002 Data identification 13 bytes of ID info. The first 10 bytes are interpreted as ASCII, and the last three as three numeric values (which are displayed hyphen-separated in the SW). Immediately followed by 78 bytes of "additional information" in ASCII.
FFD7 Program identification 13 bytes of ID info. Same format as the "data identification" at A002.

XJ220 data frame locations

Offset Content Format
00 Throttle pot voltage 0.078125 mV per LSB; total range: 0 to 5119.9 mV
02 A-Bank manifold depression millibar = ((val * 5.7645) + 100) / 0.750062
03 Coolant temperature < 0C == short circuit, > FC == open circuit. See transfer function below.
04 A-Bank air temperature < 0C == short circuit, > FC == open circuit. See transfer function below.
05 B-Bank air temperature < 0C == short circuit, > FC == open circuit. See transfer function below.
0A Battery voltage 94.1 mV per LSB; total range: 0.0 to 24.0 V
13-14 A-bank injector opening time 1 uS per LSB
1B-1C Purge flow Percentage = (val / 25600) * 100
20 Trigger disc status All bits clear: Speed/synch OK
Bit 0 set: Synch pulse every speed pulse
Bit 1 set: No synch pulses
21 Discrete outputs Bit 4: Purge flow (fitted if set)
Bit 5: Fan enabled
Bit 6: Air conditioning enabled
23 A-Bank lambda sensor voltage 19.6 mV per LSB; total range: 0.0 to 5.0V
24 B-Bank lambda sensor voltage 19.6 mV per LSB; total range: 0.0 to 5.0V
27 Road speed 1 MPH per LSB
29 B-Bank manifold depression millibar = ((val * 5.7645) + 100) / 0.750062
30-31 B-bank injector opening time 1 uS per LSB
32 A-Bank boost valve mark/space
33 B-Bank boost valve mark/space
3C Idle speed valve status 00 == enabled, nonzero == disabled

Temperature sensor logarithmic transfer function

C=\frac{3543}{\ln\left(\frac{1.2x}{640-2.5x}\right)+12.09215}-273