Kodierung und Ablage des Kilometerstandes im EEPROM

Der Kilometerstand in den Mondeo-Tachos wird in einem separaten EEPROM gespeichert. Dieses beinhaltet neben dem KM-Stand noch zahlreiche andere Daten, z.B. die Fahrgestellnummer, der HW/SW Typ des KI, die Konfiguration des KI (Deutsch/Englisch, KM/Meilen, usw.), usw.

Zum speichern des KM-Standes verwendet das KI die Radimpulse des ABS-Systems. Diese entstehen durch ein am Radlager befestigtes Zahnrad, das über einen Hall-Sensor abgefragt wird. Pro Radumdrehung entstehen damit eine bestimmte Anzahl Impulse. Diese verwendet das ABS um blockierende Räder zu erkennen, das ESP um Schlupf zu erkennen. Die Daten entstehen im HS-CAN (Motorraum) und werden über das BCM gesammelt und als Radimpulswerte im MS-CAN (Komfortsysteme) wiedergegeben. Das KI schnappt diese auf und berechnet daraus die Fahrgeschwindigkeit und zurückgelegte Wegstrecke.

Intern werden die Daten im KI in KM (Kilometer) bzw. KM/H (Kilometer pro Stunde) gespeichert. Für die Anzeige in Meilen wird zusätzlich umgerechnet.

Es gibt grundsätzlich zwei unabhängige KM-Zähler im KI, einen Gesamt- und einen Tageskilometerzähler. Der Gesamtkilometerzähler zeigt nur ganze KM an, wohingegen der Tageskilometerzähler auch eine Zehnerstelle (100m Schritte) beinhaltet.

In einem Versuch konnte ich ermitteln das die Nachkommastelle des Tageskilometerzählers scheinbar nur flüchtig im RAM gehalten wird. Hat der Tageszähler z.B. „4.2 km“ drauf und man macht den Tacho kurz stromlos, stehen danach nur noch „4.0 km“ drauf. Das bedeutet das auch der Tages-KM im EEPROM gespeichert werden, aber eben auch nur als ganzzahliger Wert. Im RAM des Tachos wird der exakte Wert gepuffert und genutzt, nur eben nach dem einschalten (und damit ist nicht die Zündung gemeint, sondern wirklich den Tacho stromlos machen) muss dieser RAM-Wert initialisiert werden und dazu wird der Wert aus dem EEPROM geladen, genauso wie für den Gesamt-KM Zähler.

Für die Speicherung der nicht-flüchtigen Daten wie dem KM-Stand wird ein externes, serielles EEPROM vom Typ 24C16 mit 2.048 Bytes (0x800) Speicherkapazität verwendet. Das EEPROM befindet sich hier auf der Platine eines Typ-3 Tachos:

Die Serielle Schnittstelle des EEPROM entspricht dem I²C Standard. Der Anschluß am µC (MAC7116) erfolgt an den Pins SCL (16) (via 470 Ohm, R212 auf der Display-Seite) und SDA (15) (via 470 Ohm, R213, auf der Bauteilseite):

Der MODE /WC Pin vom EEPROM ist über R211 (4,7 kOhm auf der Display-Seite) mit SCL (16) vom µC verbunden. Somit ist er beim HIGH des Clock-Signals SCL ebenfalls HIGH, was dem Multibyte-Write-Mode entspricht. Der Host (µC) kann also bis zu 8 Bytes pro Schreib/Leseoperation übertragen. Ob aus dem EEPROM gelesen oder dahinein geschrieben wird, entscheidet das Protokoll.

Laut Memory-Map des MAC7116 ist die virtuelle Speicheradresse (Memory-Mapped-IO) für den I2C-Bus 0xFC0A C000 aufwärts:

Im Disassembler-Code der Firmware kann man danach als Immediate Value suchen und findet die Routinen welche den I2C-Bus bedienen. Da hier in der Tat nur das EEPROM dran hängt und keine weiteren Busteilnehmer, handelt es sich somit ausschließlich um Routinen welche Low-Level auf den EEPROM-Inhalt zugreifen und damit im Zusammenhang mit den Funktionen stehen die diesen Inhalt verarbeiten. Das ist neben dem KM-Stand natürlich auch noch eine ganze Menge mehr (Konfigurationen). Aber eben auch ein guter Ausgangspunkt für das Reverse-Engineering.

Der I2C-Controller im MAC7116 verfügt nur über 4 Register:

Entropy-Tabelle für XOR-Verschlüsselung:

MAIN:00050064 crc_table_4b    DCB  0x00
MAIN:00050065                 DCB  0x0D
MAIN:00050066                 DCB  0x07
MAIN:00050067                 DCB  0x0A
MAIN:00050068                 DCB  0x0E
MAIN:00050069                 DCB  0x03
MAIN:0005006A                 DCB  0x09
MAIN:0005006B                 DCB  0x04
MAIN:0005006C                 DCB  0x01
MAIN:0005006D                 DCB  0x0C
MAIN:0005006E                 DCB  0x06
MAIN:0005006F                 DCB  0x0B
MAIN:00050070                 DCB  0x0F
MAIN:00050071                 DCB  0x02
MAIN:00050072                 DCB  0x08
MAIN:00050073                 DCB  0x05

In der von IDA-Pro disassemblierten Firmware finden wir folgende Funktion:

MAIN:00007A9A ; =============== S U B R O U T I N E =======================================
MAIN:00007A9A loc_7A9A
MAIN:00007A9A                 LDR             R1, =total_odometer
MAIN:00007A9C                 LSLS            R0, R0, #2
MAIN:00007A9E                 ADDS            R0, R0, R1
MAIN:00007AA0                 BX              LR
MAIN:00007AA0 ; ---------------------------------------------------------------------------

In Adresse 7A9A wird das Register R1 mit =total_odometer geladen.

Kurzer Ausflug in die indirekte Adressierung

Das = im Ladebefehl bedeutet das hier im Flash nach dem Maschinencode für LDR R1 nicht direkt der Wert der Speicheradresse folgt, also ein 4-Byte Wert, sondern das dort ein Offset von der aktuellen Programmposition (PC-Register) zur Speicheradresse steht wo der 4-Byte Wert abgelegt ist.

Der Maschinencode für diesen Befehl, also das was wirklich im Flash-Speicher steht, sieht dort so aus und besteht nur aus 2 Bytes:

49 89

Dies ist eine typische Optimierung der Compiler von Hochsprachen wie C, da werden dann am Ende eines Blocks reihenweise solche Offsets abgelegt um den Code klein zu halten. Das schaut in unserem Beispiel dann so aus:

MAIN:00007CAA ; ---------------------------------------------------------------------------
MAIN:00007CAC off_7CAC        DCD crc_table_4b        ; DATA XREF: mileage_7904+6↑r
MAIN:00007CAC                                         ; mileage_7926+8↑r ...
MAIN:00007CAC                                         ; Mileage algo CRC entropy table
MAIN:00007CB0 off_7CB0        DCD byte_40002F90       ; DATA XREF: sub_79B4+8↑r
MAIN:00007CB0                                         ; sub_79B4+60↑r ...
MAIN:00007CB4 dword_7CB4      DCD 0xFFFFFFF           ; DATA XREF: sub_79B4+18↑r
MAIN:00007CB8 dword_7CB8      DCD 0x1388              ; DATA XREF: sub_79B4+30↑r
MAIN:00007CB8                                         ; sub_7A40+4↑r
MAIN:00007CBC off_7CBC        DCD byte_40000680       ; DATA XREF: sub_79B4+36↑r
MAIN:00007CC0 off_7CC0        DCD total_odometer      ; DATA XREF: mileage_7A9A↑r
MAIN:00007CC0                                         ; KM-Counter value
MAIN:00007CC4 dword_7CC4      DCD 0xFFFF              ; DATA XREF: mileage_7AA2:loc_7AD2↑r
MAIN:00007CC4                                         ; mileage_7B0C+34↑r ...
MAIN:00007CC8

IDA benennt diese Offsetadresse mit „off_7CC0“. Hier steht im Flash-Speicher dann die Bytefolge 40 00 07 24 (die CPU ist Big Endian), also die tatsächliche RAM-Adresse. IDA-Pro vereinfacht die Lesart im Disassembling durch die Angabe von = gefolgt dem Label der eigentlichen Speicheradresse. Um den Code zu lesen muss man das also nicht weiter berücksichtigen, es ist aber gut zu wissen wie es tatsächlich gemacht wird.

Zurück zum Code

Der nächste Befehl LSLS R0, R0, #2 schiebt den Inhalt des Registers R0 bitweise um 2 Stellen nach links und lädt das Ergebnis wieder in R0. Das kommt einer Multiplikation mit 4 gleich (Pseudo-Code R0 = R0 * 4). In R0 steht also beim Aufruf der Routine eine Datensatznummer als Index-Wert. 0=Erste 4-Byte Zahl im Array, 1=Zweite 4-Byte Zahl, usw. Dieser Wert ist dann der Offset zum gewünschten Datensatz im Array in Form von Bytes.

Mit dem danach folgenden ADDS R0, R0, R1 wird zu diesem Offset die zuvor nach R1 geladene Startadresse des KM-Stand Arrays hinzu addiert und das Ergebnis nach R0 geladen. Dort befindet sich nun direkt die absolute Adresse zum gewünschten Datensatz.

Der letzte Befehl BX LR springt wieder zurück an die Programmstelle nach der von der aus die Subroutine aufgerufen wurde.

Die grundsätzliche Funktion ist also folgende:

  1. Vor dem Aufruf der Subroutine wird das Register R0 mit dem gewünschten Datensatz-Index (0-4) geladen
  2. Die Routine wird aufgerufen und kehrt mit der absoluten Speicheradresse des Datensatzes in R0 zurück

Von IDA-Pro wird das so in Pseudo-C Code decompiliert:

void **__fastcall loc_7A9A(int idx)
{
  return (void **)&byte_40000724[4 * idx];
}
RAM:400006C1 byte_400006C1   DCB 0x2B                ; DATA XREF: sub_2D938+12↑r
RAM:400006C2                 DCB 0xAE
RAM:400006C3                 DCB 0xFF
RAM:400006C4                 DCB 0xFF
RAM:400006C5                 DCB 0xFF
RAM:400006C6                 DCB 0xFF
RAM:400006C7                 DCB    0
RAM:400006C8                 DCB 0xFF
RAM:400006C9                 DCB 0xFF
RAM:400006CA                 DCB    0
RAM:400006CB                 DCB 0xFF
RAM:400006CC                 DCB    0
RAM:400006CD                 DCB 0x2B ; +
RAM:400006CE                 DCB    0
RAM:400006CF                 DCB 0xFF
RAM:400006D0                 DCB    0
RAM:400006D1                 DCB 0xAF
RAM:400006D2                 DCB 0xFF
RAM:400006D3                 DCB 0xFF
RAM:400006D4                 DCB 0xFF
RAM:400006D5                 DCB 0xFF
RAM:400006D6                 DCB 0xFF
RAM:400006D7                 DCB    0
RAM:400006D8                 DCB    0
RAM:400006D9                 DCB 0x2A ; *
RAM:400006DA                 DCB 0xAE
RAM:400006DB                 DCB 0xFF
RAM:400006DC                 DCB 0xFF
RAM:400006DD                 DCB    0
RAM:400006DE                 DCB 0xFF
RAM:400006DF                 DCB    0
RAM:400006E0                 DCB 0xAE
RAM:400006E1                 DCB 0xFF
RAM:400006E2                 DCB    0
RAM:400006E3                 DCB 0xFF
RAM:400006E4                 DCB    0
RAM:400006E5                 DCB 0x2B ; +
RAM:400006E6                 DCB    0
RAM:400006E7                 DCB    0
RAM:400006E8                 DCB 0x2B ; +
RAM:400006E9                 DCB    0
RAM:400006EA                 DCB    0
RAM:400006EB                 DCB 0x2B ; +
RAM:400006EC                 DCB    0
RAM:400006ED                 DCB    0
RAM:400006EE                 DCB 0x2B ; +
RAM:400006EF                 DCB    0
RAM:400006F0                 DCB    0
RAM:400006F1                 DCB 0x2A ; *
RAM:400006F2                 DCB    0
RAM:400006F3                 DCB    0
RAM:400006F4 unk_400006F4    DCB 0x2B ; +            ; DATA XREF: RAM:40003000↓o
RAM:400006F5                 DCB    0
RAM:400006F6                 DCB 0xFF
RAM:400006F7                 DCB    0
RAM:400006F8                 DCB 0xAE
RAM:400006F9                 DCB 0xFF
RAM:400006FA                 DCB    0
RAM:400006FB                 DCB 0xFF
RAM:400006FC                 DCB 0xFF
RAM:400006FD                 DCB    0
RAM:400006FE                 DCB 0x2B ; +
RAM:400006FF                 DCB 0xFF
RAM:40000700                 DCB    0
RAM:40000701                 DCB 0xFF
RAM:40000702                 DCB 0xFF
RAM:40000703                 DCB    0
RAM:40000704                 DCB 0xAF
RAM:40000705                 DCB    0
RAM:40000706                 DCB 0x2B ; +
RAM:40000707                 DCB 0xAE
RAM:40000708                 DCB 0xFF
RAM:40000709                 DCB    0
RAM:4000070A                 DCB 0xAE
RAM:4000070B                 DCB    0
RAM:4000070C                 DCB 0x2B ; +
RAM:4000070D                 DCB 0xAE
RAM:4000070E                 DCB    0
RAM:4000070F                 DCB 0x2A ; *
RAM:40000710                 DCB    0
RAM:40000711                 DCB 0xFF
RAM:40000712                 DCB    0
RAM:40000713                 DCB 0xAE
RAM:40000714                 DCB    0
RAM:40000715                 DCB 0x2A ; *
RAM:40000716                 DCB    0
RAM:40000717                 DCB    0
RAM:40000718                 DCB 0x2B ; +
RAM:40000719                 DCB    0
RAM:4000071A                 DCB    0
RAM:4000071B                 DCB 0x2A ; *
RAM:4000071C                 DCB    0
RAM:4000071D                 DCB 0xFF
RAM:4000071E                 DCB    0
RAM:4000071F                 DCB 0xAE
RAM:40000720                 DCB 0xFF
RAM:40000721                 DCB    0
RAM:40000722                 DCB 0xFF
RAM:40000723                 DCB 0xEF
RAM:40000724 ; char byte_40000724[]
RAM:40000724 byte_40000724   DCB 0                   ; DATA XREF: mileage_7A9A↑o
RAM:40000724                                         ; MAIN:off_7CC0↑o ...
RAM:40000725                 DCB    6
RAM:40000726                 DCB 0xFF
RAM:40000727                 DCB 0x80
RAM:40000728                 DCB 0
RAM:40000729                 DCB    6
RAM:4000072A                 DCB 0xFF
RAM:4000072B                 DCB 0x80
RAM:4000072C                 DCB 0
RAM:4000072D                 DCB    6
RAM:4000072E                 DCB 0xFF
RAM:4000072F                 DCB 0x80
RAM:40000730                 DCB 0
RAM:40000731                 DCB    6
RAM:40000732                 DCB 0xFF
RAM:40000733                 DCB 0x80
RAM:40000734                 DCB 0
RAM:40000735                 DCB    6
RAM:40000736                 DCB 0xFF
RAM:40000737                 DCB 0x80
RAM:40000738 ; _DWORD dword_40000738[2]
RAM:40000738 dword_40000738  DCD 0xD0000032, 0xF4F4F4F4
RAM:40000738                                         ; DATA XREF: sub_7FC6+24↑o
RAM:40000738                                         ; sub_7FC6+32↑o ...
RAM:40000740 a7m2t10849Df    DCB "7M2T-10849-DF",0   ; DATA XREF: MAIN:0002BA46↑o
RAM:40000740                                         ; MAIN:off_2BD3C↑o ...
RAM:4000074E                 DCB    0
RAM:4000074F                 DCB    0
RAM:40000750                 DCB    0
RAM:40000751                 DCB    0
RAM:40000752                 DCB    0
RAM:40000753                 DCB    0
RAM:40000754                 DCB    0
RAM:40000755                 DCB    0
RAM:40000756                 DCB    0
RAM:40000757                 DCB    0
RAM:40000758 aUkw918lpddf    DCB "UKW918LPDDF",0     ; DATA XREF: MAIN:0002BC3A↑o
RAM:40000758                                         ; MAIN:off_2BD90↑o ...
RAM:40000764                 ALIGN 8
RAM:40000768 a7m2t14c226Ab   DCB "7M2T-14C226-AB",0  ; DATA XREF: MAIN:0002BC84↑o
RAM:40000768                                         ; MAIN:off_2BD98↑o ...
RAM:40000777                 ALIGN 0x10
RAM:40000780 unk_40000780    DCB 0xFF 
void __fastcall mileage_7904(unsigned int a1)
{
  unsigned int v1; // r2
  unsigned int v2; // r1

  v1 = 0xF;
  v2 = 0;
  do
  {
    v1 = (unsigned __int8)crc_table_4b[v1] ^ a1 & 0xF;
    a1 >>= 4;
    v2 = (v2 + 1) & 0xFF;
  }
  while ( v2 < 4 );
  JUMPOUT(loc_7922);
}

unsigned int __fastcall mileage_7926(unsigned int a1)
{
  unsigned int v1; // r1
  unsigned int result; // r0
  unsigned int v3; // r2

  v1 = a1;
  result = 0xF;
  v3 = 0;
  do
  {
    result = (unsigned __int8)crc_table_4b[result] ^ v1 & 0xF;
    v1 >>= 4;
    v3 = (v3 + 1) & 0xFF;
  }
  while ( v3 < 7 );
  if ( result != v1 )
    result |= 0x80u;
  return result;
}

void **__fastcall mileage_7A9A(int idx)
{
  return (void **)&byte_40000724[4 * idx];      // return byte from EEPROM pos 0x0724 + offset given in register idx
}

signed int __fastcall mileage_7AA2(int a1)
{
  void **v1; // r0
  unsigned int v2; // r1
  int v3; // r4
  signed int v4; // r5
  int v5; // r0
  signed int result; // r0

  v1 = mileage_7A9A(a1);
  v2 = *(unsigned __int16 *)v1;
  v3 = v2 & 0xF;
  v4 = (((unsigned int)(~*((unsigned __int8 *)v1 + 2) << 24) >> 28 << 12) | (v2 >> 4)) & 0xFFFF;
  mileage_7904(v4);
  if ( v5 == v3 )
    result = v4;
  else
    result = 0xFFFF;
  return result;
}

// return 0 or 1 depend on number of 1 bits in given byte is even or odd
int __fastcall mileage_paritycheck(int a1)
{
  int v1; // r1

  v1 = 0;
  while ( a1 )
  {
    v1 = (v1 + 1) & 0xFF;
    a1 &= a1 - 1;
  }
  return v1 & 1;
}

int __fastcall mileage_7AEE(int a1)
{
  void **v1; // r0
  char v2; // r3
  int result; // r0

  v1 = mileage_7A9A(a1);
  result = mileage_paritycheck(*((unsigned __int8 *)v1 + 3));
  if ( result )
    result = (v2 & 0x7F) + 1;
  return result;
}

void __fastcall mileage_7B0C(unsigned __int16 *a1, _BYTE *a2)
{
  unsigned __int16 *v2; // r5
  _BYTE *v3; // r4
  int v4; // r0
  unsigned int v5; // r0

  v2 = a1;
  v3 = a2;
  do
  {
    v4 = ((unsigned __int8)(*v3)-- + 255) & 0xFF;
    if ( v4 != (unsigned __int8)byte_40002FB4 || byte_40002FBB & 2 )
    {
      *((_BYTE *)v2 + 4) = v4;
      *v2 = mileage_7AA2((unsigned __int8)*v3);
      v5 = mileage_7AEE((unsigned __int8)*v3);
      *((_BYTE *)v2 + 2) = v5;
      if ( *v2 != 0xFFFF && v5 && v5 <= 0x80 )
        break;
    }
  }
  while ( *v3 );
  JUMPOUT(&loc_7A3A);
}

int __fastcall mileage_7B56(unsigned __int16 *a1, unsigned __int16 *a2)
{
  unsigned __int16 *v2; // r5
  unsigned __int16 *v3; // r4
  int v4; // r0
  unsigned int v5; // r6
  unsigned int v6; // r0
  unsigned int v7; // r1
  unsigned int v8; // r1
  int v9; // r1
  int result; // r0

  v2 = a1;
  v3 = a2;
  do
  {
    v4 = ((unsigned __int8)(*((_BYTE *)v3 + 3))-- + 255) & 0xFF;
    if ( v4 != (unsigned __int8)byte_40002FB4 || byte_40002FBB & 2 )
    {
      v5 = mileage_7AEE(v4);
      if ( v5 - 1 <= 0x7F )
      {
        v6 = mileage_7AA2(*((unsigned __int8 *)v3 + 3));
        if ( v6 != 0xFFFF )
        {
          v7 = *v2;
          if ( (v7 != v6 || *((unsigned __int8 *)v2 + 2) >= v5) && v7 >= v6 )
          {
            v8 = *v3;
            if ( v8 == v6 && *((unsigned __int8 *)v3 + 2) <= v5 || v8 < v6 )
            {
              v9 = *((unsigned __int8 *)v3 + 3);
              if ( v9 != *((unsigned __int8 *)v2 + 4) )
              {
                *v3 = v6;
                *((_BYTE *)v3 + 2) = v5;
                *((_BYTE *)v2 + 3) = v9;
              }
            }
          }
          else if ( !*((_BYTE *)v3 + 4) )
          {
            *v3 = v7;
            *((_BYTE *)v3 + 2) = *((_BYTE *)v2 + 2);
            *((_BYTE *)v2 + 3) = *((_BYTE *)v2 + 4);
            *((_BYTE *)v2 + 4) = *((_BYTE *)v3 + 3);
            *v2 = v6;
            *((_BYTE *)v2 + 2) = v5;
          }
        }
      }
    }
    result = *((unsigned __int8 *)v3 + 3);
  }
  while ( *((_BYTE *)v3 + 3) );
  return result;
}

signed int __fastcall mileage_7BE2(unsigned __int16 *a1)
{
  unsigned __int16 *v1; // r4
  int v2; // r1
  int v4; // [sp+0h] [bp-18h]
  int v5; // [sp+4h] [bp-14h]

  v1 = a1;
  v4 = 5;
  v5 = 0xEFEF;
  *((_BYTE *)a1 + 3) = -1;
  mileage_7B0C(a1, (_BYTE *)&v4 + 3);
  if ( (_BYTE)v4 )
  {
    do
    {
      mileage_7B56(v1, (unsigned __int16 *)&v4);
      if ( *((unsigned __int8 *)v1 + 3) == 255 )
        break;
      v2 = *v1;
      if ( v2 == HIWORD(v4) && *((unsigned __int8 *)v1 + 2) - BYTE2(v4) - 3 < 0
        || v2 - HIWORD(v4) == 1 && BYTE2(v4) - *((unsigned __int8 *)v1 + 2) - 125 > 0 )
      {
        *((_DWORD *)v1 + 2) = (*v1 << 7) + *((unsigned __int8 *)v1 + 2);
        byte_40002FBB &= 0xF7u;
        return 1;
      }
      ++HIBYTE(v5);
      *v1 = HIWORD(v4);
      *((_BYTE *)v1 + 2) = BYTE2(v4);
      *((_BYTE *)v1 + 4) = *((_BYTE *)v1 + 3);
      *((_BYTE *)v1 + 3) = -1;
      v4 = 5;
    }
    while ( HIBYTE(v5) < 2u );
  }
  byte_40002FBB |= 8u;
  return 0;
}

void __fastcall mileage_7C8A(int a1)
{
  char v1; // r3

  if ( mileage_paritycheck((a1 + 255) & 0xFF) )
    byte_40002FBA = v1;
  else
    byte_40002FBA = v1 | 0x80;
  JUMPOUT(&loc_7B00);
}

signed int __fastcall mileage_8040(int a1, unsigned int a2, int a3)
{
  _DWORD *v3; // r4
  unsigned int v4; // r5
  signed int v5; // r7
  unsigned int v6; // r0
  unsigned int v7; // r6
  int v8; // r3
  int v9; // r2
  int v10; // r1
  unsigned int v12; // [sp+4h] [bp-24h]
  int v13; // [sp+8h] [bp-20h]
  unsigned int v14; // [sp+Ch] [bp-1Ch]
  int v15; // [sp+10h] [bp-18h]

  v13 = a1;
  v14 = a2;
  v15 = a3;
  v3 = (_DWORD *)a1;
  v4 = a2;
  v5 = 0;
  if ( a2 < 1 )
  {
    byte_40002FC1[a2] = 1;
    if ( sub_7F84(&v12, 1) )
    {
      v5 = 1;
      v6 = dword_40000738[v4];
      v7 = v6 & 0xFFFFFFF;
      if ( mileage_7926(v6) & 0x80 || v7 > v12 )
      {
        sub_7FC6(v4, v10, v9, v8);
        *v3 = 0;
      }
      else
      {
        *v3 = v12 - v7;
      }
    }
    else
    {
      *v3 = 0;
    }
    if ( !v15 )
      sub_7994(v3);
  }
  return v5;
}

void __fastcall mileage_83A2(int a1)
{
  int v1; // r6
  unsigned int v2; // r5
  unsigned int v3; // r4
  unsigned int v4; // r6
  unsigned int v5; // r0
  unsigned int v6; // r6
  void **v7; // r0
  char v8; // r0
  __int16 v9; // [sp+0h] [bp-28h]
  char v10; // [sp+2h] [bp-26h]
  unsigned __int8 v11; // [sp+3h] [bp-25h]
  unsigned __int8 v12; // [sp+4h] [bp-24h]
  unsigned int v13; // [sp+8h] [bp-20h]

  v1 = a1;
  v2 = 0;
  v3 = 0;
  if ( mileage_7BE2((unsigned __int16 *)&v9) )
  {
    v4 = sub_4D6BC(v1 + 5);
    if ( v13 - (unsigned __int8)byte_40000680 - 1 < v4 && v4 <= 0x188EB3 )
    {
      v5 = (sub_4D5AC(1000) + (unsigned __int8)byte_40000682 + 1) & 0xFFFF;
      if ( v5 <= 0xFF )
        byte_40002FBD = v5;
      else
        byte_40002FBD = -1;
      v13 = (unsigned __int8)byte_40000680 + v4;
      v9 = v13 >> 7;
      v10 = v13 & 0x7F;
      sub_7CC8((unsigned __int16 *)&v9);
      v6 = v12;
      byte_40002FBC = 0;
      while ( v3 < 3 )
      {
        v6 = (v6 + 1) & 0xFF;
        if ( v6 >= 5 )
          v6 = 0;
        if ( v12 == v6 )
          break;
        if ( v11 != v6 )
        {
          v7 = mileage_7A9A(v6);
          if ( sub_7A88(v7, &word_40002FB7, sub_8378, 4) )
            v2 = (v2 + 1) & 0xFF;
          v3 = (v3 + 1) & 0xFF;
        }
      }
      if ( v2 > 1 )
      {
        word_40002F92 = 0;
        mileage_7904(0);
        byte_40002F94 = v8;
        dword_40002FA0 = v13 + 1;
        sub_794E(&dword_40002FA0);
        sub_79B4();
      }
    }
  }
  JUMPOUT(&loc_80A2);
}

Das EEPROM hat einen Adressraum von 0x0000-0x0800 (=2048 Bytes). Der KM-Stand ist in den Bytes 0x770-0x78F in verschlüsselter Form hinterlegt:

Offset(h) 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
00000770  FF 00 FF EF B4 05 FF 6E B4 05 FF EF B4 05 FF 70
00000780  B4 05 FF EC B4 05 FF 6D E2 32 52 4D F4 F4 F4 F4

Der KM-Stand selbst wird als 2 Byte Wert (=1 Word) im Big-Endian Format abgespeichert. Ein Teil der Verschlüsselung besteht darin den Wert 5 mal hintereinander abzulegen. Hier mit den Beispieldaten von oben, jedoch die nicht relevanten Bytes mit „xx“ ausgeblendet:

Offset(h) 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
00000770  xx xx xx xx B4 05 xx xx B4 05 xx xx B4 05 xx xx
00000780  B4 05 xx xx B4 05 xx xx xx xx xx xx xx xx xx xx

Gut zu erkennen der kodierte Wert 0xB405. Diesen gilt es bei der KM-Anpassung zu ändern. Am einfachsten schreibt man hier den Wert 0x0006 rein, das ändert den KM-Stand auf „000006“. Von diesem Wert kann man dann mit ForScan problemlos den Tachostand nach oben hin auf einen beliebigen Wert anpassen.

Die Inhalte in einem EEPROM können gelesen aber auch wieder gelöscht und neu programmiert werden, die Anzahl der möglichen Löschzyklen ist jedoch physikalisch begrenzt. Der Hersteller garantiert nur eine bestimmte Anzahl, danach kann es sein das sich bestimmte Bits nicht mehr löschen lassen, das EEPROM wäre unbrauchbar. Im Falle des zum Einsatz kommenden 24C16 sind das rund 1 Million Lösch/Schreibzyklen. Für ein Autoleben reicht das völlig aus und entspricht witzigerweise genau der Anzahl der möglichen Stellen „999.999“ bevor ein Überlauf auf „000.000“ stattfinden würde.

  • artikel/ipc/km_algorithmus.txt
  • Zuletzt geändert: Sat. 11.02.2023 09:05
  • von wikiadmin