Skip to content

Instantly share code, notes, and snippets.

@gnif
Last active June 25, 2026 03:22
Show Gist options
  • Select an option

  • Save gnif/85b8abb5a418ad36f0cc3ed0c8aa1207 to your computer and use it in GitHub Desktop.

Select an option

Save gnif/85b8abb5a418ad36f0cc3ed0c8aa1207 to your computer and use it in GitHub Desktop.
2013 Subaru Outback Cluster EEPROM Reverse Engineering
For whatever reason, the Subaru clusters I have tested appear to read about 8–10 km/h low from the factory on KPH models, and Subaru do not provide any user calibration method. Because of that, I purchased several clusters from wreckers and compared their EEPROM dumps to identify the calibration fields so the cluster can be corrected.
I have dumped and compared EEPROMs from three different clusters:
- 2013 Diesel Outback, KPH, RHD, CVT
- 2013 Petrol Outback, MPH, LHD, MT
- 2013 Diesel Outback, KPH, LHD, MT
One interesting note: the MPH cluster I imported from the US was made in Mexico, and removing the needles tends to pull the entire shaft out of the movement. By comparison, the Japan-made clusters are much easier to disassemble without damaging the gauge movements.
What follows is what I have discovered so far.
Note: I will not be posting details on odometer tampering. That is not the goal here.
Many parts of the EEPROM are organised as 10-byte records:
- bytes +0 to +7 = data
- byte +8 = XOR checksum
- byte +9 = unknown trailing byte
The XOR checksum is:
uint8_t calc_config_checksum(const uint8_t b[10])
{
uint8_t x = 0xFF;
for (int i = 0; i < 8; i++)
x ^= b[i];
return x;
}
This record format is valid continuously from 0x00 to 0xF9 in all three dumps, and also appears again in several later regions.
Configuration / Region / Features
0x00
Bit 0: 1 = [S] Feature flashing (SI drive?)
Bit 3: 1 = Disable EyeSight
Bit 6: 1 = Left-hand drive
0x01
Bit 2: 1 = AT, 0 = MT
Bits 5–7 control language / units behaviour.
Tested values:
0 = KPH & Japanese
1 = MPH & English
2 = KPH & English
3 = KPH & English
4 = KPH & English
5 = KPH & Invalid / No Text
6 = KPH & English
7 = KPH & Japanese
0x02
Bit 5: 1 = Enable washer fluid level
0x03–0x07 = Unknown
0x08 = XOR checksum
0x09 = Unknown trailing byte
Unknown
0x0A–0x11 = Unknown
0x12 = XOR checksum
0x13 = Unknown trailing byte (seen as 0x60)
Unknown
0x14–0x1B = Unknown
0x1C = XOR checksum
0x1D = Unknown trailing byte
Speedometer calibration block
0x1E–0x1F = Unknown
0x20-0x21 = Linear speed scale multiplier
Diesel = 8863
Petrol = 5506
Formula = new_scale = round(old_scale * desired_indicated_speed / current_indicated_speed)
0x26 = XOR checksum
0x27 = Unknown trailing byte
Speedometer calibration block
0x1E–0x1F = Unknown
0x20-0x21 = Linear speed scale multiplier
Diesel = ???
Petrol = ???
Formula = ???
0x26 = XOR checksum
0x27 = Unknown trailing byte
Speedometer calibration block
0x28–0x2F = Speed input breakpoints, 4x u16 little-endian, units = KPH * 100
0x30 = XOR checksum
0x31 = Unknown trailing byte
Diesel KPH, RHD, CVT:
[2000, 12000, 22000, 24649]
Petrol MPH, LHD, MT:
[1609, 12874, 22530, 25026]
Diesel KPH, LHD, MT:
[2000, 12000, 22000, 23895]
Note: the MPH cluster still stores these in KPH * 100. The values correspond to:
10 mph = 16.09 km/h -> 1609
80 mph = 128.74 km/h -> 12874
140 mph = 225.30 km/h -> 22530
Speedometer output / angle breakpoints
0x32–0x39 = Speedometer output / angle breakpoints, 4x u16 little-endian
0x3A = XOR checksum
0x3B = Unknown trailing byte
Diesel KPH, RHD, CVT:
[527, 2868, 5206, 5825]
Petrol MPH, LHD, MT:
[396, 3008, 5247, 5825]
Diesel KPH, LHD, MT:
[527, 2946, 5367, 5825]
These values are not simple standalone step counts. The gauge movements are driven by separate SIN and COS coils directly from the MCU, so these are likely angle / drive calibration values for the air-core movement.
Confirmed behaviour:
- 0x38 is 5825 in all three known-good dumps
- 0x36 is not a free value; it matches the interpolated value derived from 0x34 and 0x38
Using:
x2 = input at 0x2A
x3 = input at 0x2C
x4 = input at 0x2E
y2 = output at 0x34
y4 = output at 0x38
the expected value at 0x36 is:
y3 = y2 + (x3 - x2) * (y4 - y2) / (x4 - x2)
rounded to nearest integer.
This reproduces the factory values in all three dumps:
Diesel KPH, RHD, CVT: 5206
Petrol MPH, LHD, MT: 5247
Diesel KPH, LHD, MT: 5367
So 0x36 appears to be a derived checkpoint rather than an independently chosen calibration point.
The two diesel clusters have identical tachometer tables but different speedometer tables, so the speedometer calibration appears to vary by cluster / drivetrain variant.
Tachometer calibration block
0x46–0x4D = Tach input breakpoints, 4x u16 little-endian, units = RPM
0x4E = XOR checksum
0x4F = Unknown trailing byte
Diesel KPH, RHD, CVT:
[1000, 3000, 5000, 5721]
Petrol MPH, LHD, MT:
[1000, 4000, 7000, 8208]
Diesel KPH, LHD, MT:
[1000, 3000, 5000, 5721]
0x50–0x57 = Tachometer output / angle breakpoints, 4x u16 little-endian
0x58 = XOR checksum
0x59 = Unknown trailing byte
Diesel KPH, RHD, CVT:
[1031, 3092, 5154, 5897]
Petrol MPH, LHD, MT:
[700, 2837, 4967, 5825]
Diesel KPH, LHD, MT:
[1031, 3092, 5154, 5897]
The tachometer follows the same general structure as the speedometer.
Using:
x2 = input at 0x48
x3 = input at 0x4A
x4 = input at 0x4C
y2 = output at 0x52
y4 = output at 0x56
the expected value at 0x54 is:
y3 = y2 + (x3 - x2) * (y4 - y2) / (x4 - x2)
rounded to nearest integer.
This reproduces the factory values in both diesel and petrol dumps.
Variant / feature candidates
0x42 and 0xA6 are strong candidate variant fields.
They differ between the diesel CVT dump and both manual dumps, but are not yet proven to be MT/CVT flags specifically.
Odometer block
0x1A0–0x1BF
Immob coding
0x2D8–0x2E3
Unknown data.
This region changes when re-pairing keys, so it is likely immobiliser-related coding.
@ktruesd

ktruesd commented Jun 25, 2026

Copy link
Copy Markdown

I have a question, looking at your notes, to enable SI drive indicator you set the first bit of the first byte to one? Would the checksum also have to be adjusted for this change?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment