Last active
May 1, 2026 01:57
-
-
Save gnif/85b8abb5a418ad36f0cc3ed0c8aa1207 to your computer and use it in GitHub Desktop.
2013 Subaru Outback Cluster EEPROM Reverse Engineering
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment