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 withF1
, which is0F
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 |