Skip to content
KitploitKITPLOIT
ToolsBlog
Einreichen
ToolsBlog
Einreichen

Hacking-, PenTest- und Cybersicherheits-Tools für Ihr Sicherheitsarsenal!

Kitploit ist ein Verzeichnis von Hacking-, Cybersicherheits- und Pentesting-Tools. Entdecken Sie die neuesten Projekt-Updates, um Schwachstellen zu finden, Systeme zu analysieren, Tests zu automatisieren und Ihre Sicherheit zu stärken.

··Feeds·Kontakt·Datenschutz·© 2026 Kitploit

Tool-Verzeichnis

Kategorien

Alle Kategorien anzeigen
Loading categories
Philips-PM-5139-5138A-5136-Firmware-Project — Firmware-Reverse-Engineering der Philips PM5139 / PM5138A / PM5136 Funktionsgeneratoren: 8051-Emulatoren als Messinstrumente, 35 Abschnitte dokumentierter Hardware und eine korrigierte Firmware V2.0 | Kitploit
Tools/GitHubGitHub/doctormord/philips-pm-5139-5138a-5136-firmware-project
Embedded-System-SicherheitStatische AnalyseDynamische Analyse (Sandboxing)Reverse EngineeringHardware-SicherheitBinäranalysePapers & ForschungLernen & BildungFirmware-Analyse

Beliebteste

Alle anzeigen →

Entdecken Sie die meistgenutzten Tools unserer Community.

Alle Tools erkunden

Durchsuchen Sie unsere Tool-Sammlung

Alle Tools anzeigen →
Teilen
GitHubdoctormord/philips-pm-5139-5138a-5136-firmware-project

Philips-PM-5139-5138A-5136-Firmware-Project

Firmware-Reverse-Engineering der Philips PM5139 / PM5138A / PM5136 Funktionsgeneratoren: 8051-Emulatoren als Messinstrumente, 35 Abschnitte dokumentierter Hardware und eine korrigierte Firmware V2.0

Repository anzeigen
213vor 1 TagNoch nicht geprüft

Philips PM5139 — Firmware-Reverse-Engineering

Ein 20-MHz-Funktionsgenerator von etwa 1994, in Software zerlegt: zwei EPROM-Dumps, ein 8051-Emulator als Messinstrument und 35 Dokumentationsabschnitte, in denen jede einzelne Aussage durch eine Listing- Adresse, eine Emulator-Messung oder den Schaltplan belegt ist.

Am Ende steht eine Firmware V2.0, die einen von Philips ausgelieferten Defekt behebt, sechs eigene Arbiträrwellenformen und ein Browser-Simulator, der das Original-ROM Instruktion für Instruktion ausführt.

Alle Wellenformtabellen im V1.3-ROM

Jede Wellenformtabelle im Programm-EPROM, direkt aus dem Binärbild geplottet. Unten rechts diejenige, die den interessantesten Teil dieses Projekts ins Rollen gebracht hat.


Inhalt

  • Worum es geht
  • Ergebnisse auf einen Blick
  • Das Instrument
  • Die Methode: Der Emulator ist das Messinstrument
  • Der Weg hierher
  • Die Highlights
  • Firmware V2.0 — was neu ist
  • Das Easter Egg
  • Und dann stellte sich heraus, dass sie polyphon ist
  • Sechs eigene Arbiträrwellenformen
  • Der Browser-Simulator
  • Repository-Struktur
  • Verwendung der Werkzeuge
  • Alles reproduzieren
  • Zurückspielen
  • Wie zuverlässig ist das?
  • Noch offen
  • Quellen

Worum es geht

Der Philips PM5139 ist das 20-MHz-Topmodell einer Familie aus drei Instrumenten (PM5136 / PM5138A / PM5139). Im Inneren sitzt ein PCB80C652 — ein 8051-Kern mit Hardware-I²C — ein 27512-Programm-EPROM und sechs analoge Baugruppen, die an einem seriellen Bus hängen.

Es gibt kein Servicehandbuch für den PM5139. In Foren wird seit 2010 danach gesucht. Was existiert, ist das Handbuch für den PM5138A, sein 10-MHz-Schwestermodell, das intern nahezu identisch ist.

Dieses Projekt begann also von der anderen Seite: EPROM auslesen und herausfinden, was der Code tut, bis das Instrument gut genug verstanden ist, um es zu modifizieren.

Zwei Firmware-Versionen waren verfügbar, V1.3 und V1.5, beide 64-KiB-M27512-Dumps.


Ergebnisse auf einen Blick


Das Instrument

Die analoge Seite ist ein serieller C-Bus: Der UART des 8051 läuft im Schieberegistermodus, TXD ist der Takt, RXD die Daten, und ein Strobe entscheidet, welches der zehn Schieberegister die Bytes übernimmt. MOV DPH,#8nh gefolgt von MOVX @DPTR,A löst Strobe n aus. Diese eine Zeile ist der Schlüssel zum gesamten analogen Abschnitt.


Die Methode: Der Emulator ist das Messinstrument

Das ist der Teil, den man für sein eigenes Projekt übernehmen sollte.

Ein 44-KB-8051-Binärbild mit bloßem Auge zu lesen, bringt einen vielleicht ein Drittel des Weges. Alles darüber hinaus kam daraus, den Originalcode laufen zu lassen und zu beobachten, was dabei herausfällt:```python

What formula turns the entered amplitude into the byte on the bus?

Don't read the routine. Call it.

c = CPU(rom) for w in test_values: set_amplitude(c, w) c.call(0x0AAC) # the original routine, untouched print(w, c.ram[0x1C]) # the byte that goes out on STR9

root@kitploit:~
Die Eingabe variieren, die Ausgabe lesen, sie gegen die Hypothese prüfen. Das
funktionierte für Frequenz, Amplitude, Offset, AM-Tiefe, FM-Hub, Burst-Anzahl,
Symmetrie und beide Sweep-Eigenschaften. Jede Formel in der Dokumentation
kommt mit den Abtastpunkten, über die sie verifiziert wurde.

Drei Verfeinerungen machten es tatsächlich produktiv:

**Den Bus beobachten, nicht das Display.** Abschnitt 15 misst, was ein
Statusbit mit dem Display-Puffer macht, und 74 von 128 Bits scheinen nichts zu
tun. Aber viele von ihnen steuern nicht das Display, sie steuern die *analogen
Baugruppen* — und die sind nur als Telegramme auf dem C-Bus sichtbar. Das
Aufzeichnen von `MOV SBUF,…` und dem abschließenden `MOVX @DPTR` hob die Anzahl
der dokumentierten Bits von 54 auf 75.

**Tasten drücken, nicht RAM manipulieren.** Ein RAM-Byte von Hand zu setzen
erzeugt Zustände, die das Instrument nie einnimmt. Das kostete uns zwei falsche
Erkenntnisse und einen Absturz in die Befehlstabelle. Das Einspeisen echter
Tastencodes über den emulierten SAA3007 liefert Zustände, die die Firmware
tatsächlich erreicht — und es war ein Brute-Force-Durchlauf über alle 256
Tastencodes, der aufdeckte, welche Taste welchen Handler auslöst.

**Zuerst den eigenen Emulator verdächtigen.** Drei Fehler in unserem Kern
erzeugten „unerklärliches" Firmware-Verhalten: `ACALL` als `AJMP` ausgeführt,
ein fehlendes Auxiliary-Carry-Flag (sodass `DA A` sich falsch verhielt und die
Firmware scheinbar binär zählte) und ein doppelter Tastatur-Interrupt. Jede
Erkenntnis aus diesem Zeitraum wurde danach neu gemessen.

---

## Der Weg hierher

**Zuerst statisch.** Ein Disassembler mit vollständiger Opcode-Tabelle, dann
rekursiver Abstieg mit Sprungtabellen-Heuristiken. Das ergab 30 508 Bytes Code
und ließ 13 637 Bytes ungeklärt.

**Dann dynamisch.** Ein Trace-Lauf — Kaltstart, alle 23 Frontplattentasten,
beide Drehrichtungen des Knopfes, jeder Betriebsmodus, 86 Millionen Zyklen —
der jede Adresse markierte, die tatsächlich ausgeführt wurde. Gegen die
statische Analyse gehalten, fand er genau **einen** Bereich, den der Abstieg
übersehen hatte, und 10 686 der unerklärten Bytes entpuppten sich als fünf
bekannte Tabellenblöcke.

**Dann die Schaltpläne.** Die OCR des Servicehandbuchs ist für Schaltpläne
unbrauchbar, aber die Seitenbilder mit 400 dpi sind hervorragend. In
überlappende Kacheln zerschnitten, sind sie bis hinunter zu Pin-Nummern lesbar.
Sechs Blätter wurden auf diese Weise gelesen — und wo fünf parallele Leiterbahnen
90 Pixel voneinander entfernt verlaufen, wurde das Augenmaß durch ein Skript
(`lines.py`) ersetzt, das die Liniensegmente aus der Bitmap extrahiert.

**Dann die beiden Chips, die gezogen wurden.** Ein 27C64 mit der Bezeichnung
„SINUS 1.1" und ein X28C64 wurden ausgelesen. Beide wurden in den Schaltplan
eingesetzt und ihre Inhalte dekodiert.

**Dann der Versionsvergleich.** Beide ROMs tokenisieren (relative
Sprungdistanzen statt absoluter Ziele) und `SequenceMatcher` darüber laufen
lassen, ergibt eine Adresszuordnung, die Code-Verschiebungen übersteht — so
werden die V1.3-Symbole auf V1.5 übertragen.

---

## Die guten Stellen

### Philips lieferte eine verrauschte Wellenform

Die drei eingebauten beliebigen Kurven liegen bei `A047h`, `A447h` und `A847h`.
Die dritte hat dieselbe Form wie eine Tabelle, die bereits in berechneter Form
im ROM liegt — aber mit **563 Richtungswechseln gegenüber 13** und einer
Standardabweichung von 4,1 LSB.

Sie wurde von einer analogen Quelle abgetastet, anstatt berechnet zu werden. Der
Mittelwert der Abweichung ist null, nur zwei von 1024 Punkten weichen um mehr
als 10 ab — dies ist keine andere Wellenform, es ist die *gleiche* Wellenform
mit Rauschen darauf.

### Diese Tabelle ist eine 30-dB-Pegelleiter

Die saubere Version wurde in einem früheren Entwurf als „ein Sinus mit zehn
AM-Tiefen" beschrieben, was eine Augenmaß-Lesung des Diagramms war, nicht
etwas, was der Code aussagt. Durchgerechnet zerfallen die 1024 Punkte in zehn
Sinusbögen, deren Spannen```
255  171  120  80  56  38  26  17  12  8

eine geometrische Reihe mit dem Verhältnis 0,681 = 10^(−1/6), d. h. 3,33 dB pro Schritt und 30,1 dB insgesamt. Ein Halbierungsmodell weicht um bis zu 56 ab, ein 3-dB- Modell um 10. Es ist eine logarithmische Pegelleiter — ein Amplituden- oder Dämpfungstestmuster.

STR9 sendet 16 Bits als zwei 1-Byte-Telegramme

Der Amplitudencontroller hat zwei Schieberegister an einem Strobe, aber die Firmware sendet pro Telegramm immer nur ein Byte. Der Schaltplan erklärt es: Die beiden 4094 sind über QS' (Pin 10) kaskadiert, wobei Pin 9 unbenutzt ist — und die Telegramme kommen paarweise, ~42 000 Zyklen auseinander, mit Millionen von Zyklen Stille zwischen den Paaren. Das zuerst gesendete Byte wird in das zweite Register durchgeschoben.

Dasselbe Kaskadenmuster tauchte bei jeder Baugruppe mit mehr als einem Schieberegister auf — einschließlich eines Falls, bei dem die Kette eine Baugruppengrenze über eine Leitung namens E überschreitet.

Der Abschwächer ist keine Berechnung

Fünf Bits im STR9-Telegramm steuern Relais direkt: S1 schaltet den DC-Generatorbereich, S2…S5 die Abschwächerrelais. 20 dB (für 40dB), 20 dB, 50/600 Ohm — es steht auf dem Schaltplan. Es gibt keine Schwellenwerte zu berechnen.

Ein Handler, der sich offen zeigt

Die Sprungtabelle bei 0301h wird mit JMP @A+DPTR gelesen. Eintrag 15 landet bei 0301h + 30 = 031Fh — und dort steht statt des üblichen AJMP der Handler selbst, inline, was einen Sprung einspart. Keine Sprunganweisung im ROM zeigt darauf, sodass die statische Analyse ihn verlor. Es ist der DIAL-LOCK- Handler, und nur der dynamische Trace fand ihn.

Sechs beliebige Slots, nicht vierundzwanzig

Das Datenblatt verspricht 24 Wellenformspeicher. Das Verzeichnis im EEPROM sagt sechs. Die Arithmetik entscheidet es:``` 1024 points × 10 bit, packed 4 values per 5 bytes -> 1280 bytes per curve 6 × 1280 = 7 680 bytes, 0100h…1EFFh (X28C64, 8 KB) <- what was fitted 24 × 1280 = 30 720 bytes, 0100h…78FFh (X28C256, 32 KB) <- what the schematic says

root@kitploit:~
Die gemessene Lesereichweite der Firmware beträgt `0100h–1EFFh` — sechs Kurven
auf das Byte genau. Das Instrument wurde mit dem kleinen Chip gebaut.

### Toter Code, der mit einem nicht vorhandenen Gerät spricht

186 Bytes bei `9AFFh` führen I²C-Verkehr mit der Adresse `5Ah` durch — eine Adresse, die
nirgendwo sonst auftaucht. In **beiden** Firmware-Versionen springt kein Sprungbefehl
darauf. Er befindet sich im selben Gerätetyp-Block wie die Schnittstellenkarte bei `5Eh`,
nur mit anderen Bank-Bits, und er sendet den Empfangspuffer und die
Arithmetikregister in zwei Zehn-Byte-Telegrammen aus. Es sieht aus wie eine
Werksdiagnose für ein Gerät, das nie ausgeliefert wurde.

### Man kann keinen Code aus dem Arbitrary-EEPROM ausführen

Eine naheliegende Idee — Code in einen Arbitrary-Waveform-Slot legen und dorthin springen —
ist von vornherein zum Scheitern verurteilt. Der 8051 ist Harvard: Anweisungen kommen über
`/PSEN` aus dem Programm-EPROM, Daten über `/RD` aus dem Arbitrary-
EEPROM. Es ist nicht blockiert; die Leitung ist einfach nicht vorhanden.

### Und schließlich die Frequenzkodierung

Die Anzeige-Ziffernreihe befindet sich in `3Eh–43h` des an den
PCF8576 gesendeten Bildes, alle Positionen teilen sich eine Segmentkodierung, und Byte `43h`
schaltet zwischen Dekade 7 und 8 von kHz auf MHz um. Daraus folgt:```
f = M · 10^(D−8) kHz

Drei Frequenzschrittsequenzen, die am realen Instrument gemessen wurden, werden hiermit exakt reproduziert — einschließlich derjenigen, die vorzeitig abbricht, weil die Mantisse 2500 25 MHz bedeuten würde, über dem Limit.


Firmware V2.0 — was ist neu

Beliebige Kurve 3: V1.5 gegen V2.0

Links die ausgelieferte Kurve, rechts die korrigierte. Unten links die Abweichung von der berechneten Tabelle — dieses Band von ±5 LSB ist das, was eine abgetastete analoge Quelle hinterlassen hat.

mkv20.py baut V2.0 aus V1.5 (oder V1.3). Es findet jede Adresse über Signaturen statt sie fest zu verdrahten, sodass dasselbe Skript auf beiden Quellversionen funktioniert:

  1. Beliebige Kurve 3 ersetzt durch die saubere berechnete Tabelle. Beide sind 1024 Bytes von 8 Bit, sodass der Austausch größenneutral ist und keinen Code berührt. 863 von 1024 Bytes ändern sich.
  2. Beliebige Kurve 2 ersetzt durch einen logarithmischen Chirp (1 → 40 Perioden). Sie unterschied sich von Kurve 1 in genau zwei Bytes — ein zusätzlicher Nadelimpuls — war also redundant.
  3. Versionsidentifikation in *IDN?: PHILIPS,PM5139,0,V2.0/0000.
  4. Version auf dem Display: Die Reset-Sequenz schreibt zwei Display- Zellen, und diese Bytes buchstabieren nun 2.0 in der gemessenen Segment- Kodierung.
  5. Prüfsumme neu berechnet und dort gespeichert, wo die Firmware sie erwartet.

Alles andere ist unberührt. Drei weitere Kuriositäten wurden gefunden und bewusst in Ruhe gelassen — ein Schreibzugriff auf ein nicht existierendes SFR (harmlos, in beiden Versionen), der tote 5Ah-Block und drei Statusbits, die getestet, aber nie gesetzt werden. Sie zu patchen ändert kein Verhalten und fügt nur Risiko hinzu.

M27512_PM5139_V20.bin ist genau dies und nichts anderes. Die Melodie unten ist ein separater, optionaler Build-Schritt.

Verifiziert: Kaltstart im Emulator erzeugt denselben Display-Puffer und dieselben Flags wie V1.5, die Prüfsumme validiert, und der Build ist byte-reproduzierbar. Er wurde geflasht und läuft auf einem echten PM5139 — das Display zeigt 2.0 und alle sechs beliebigen Slots funktionieren.


Das Easter Egg

Da es 19 509 ungenutzte Bytes hinter der Prüfsumme in V1.5 gibt, und der Frequenzpfad eine Notenfrequenz als drei BCD-Bytes entgegennimmt, kann das Instrument Musik über seinen eigenen Ausgang abspielen.

Die Kodierung ist angenehm direkt — Dekade 3, dann die Frequenz in 0,01 Hz als BCD, also ist 82,41 Hz 30 82 41. Vier Bytes pro Note: drei für die Tonhöhe, eines für die Dauer.

Der interessante Teil ist der Trigger. Das Diagnosemenü (LOCAL halten beim Einschalten) hat eine Sprungtabelle mit acht Einträgen, aber die Menü- Schleife zählt 0Bh nur von 1 bis 7 — der achte Eintrag ist also unerreichbar. Er ist auch redundant: Er springt zum Menüanfang, der ohnehin von zwei anderen Stellen erreicht wird.

Der ganze Hook sind also zwei Bytes:``` 5B94h table entry 8: LJMP 5B45h -> LJMP 5B62h count limit: 08h -> 09h

root@kitploit:~
Kein Selbsttest geht verloren, keine Tabelle wird verschoben und kein toter Menüpunkt erscheint. LOCAL halten, einschalten, das Menü bis 8 zählen lassen, eine Taste drücken.

Die Zeitangaben stammen aus dem MCS-51-Datenblatt. Beide Emulatoren zählen jetzt Maschinenzyklen neben Instruktionen (`mcyc`, aus `mcs51.CYCLES`), und das schrittweise Durchlaufen der Warteschleife misst **1009 µs** pro Einheit — 106,95 ms pro Sechzehntelnote bei 140 BPM, 0,2 % vom Ziel entfernt. Der Wert war früher eine Handrechnung von 1006 µs, bei der zwei Instruktionen verloren gegangen waren.

`mkdoom.py` kann auch eine MIDI-Datei konvertieren. Eine Stimme muss ausgewählt werden (höchster Ton, niedrigster Ton oder ein Kanal) und Abschnitte kürzer als ~25 ms zusammengeführt werden — darunter schafft ein tiefer Ton keine vollständige Schwingung und man hört nur ein Klicken.

---

## Und dann stellte sich heraus, dass es polyphon ist

Die obige Melodie ist eine Stimme. Sie muss es nicht sein, und der Grund ist ein Satz im Servicehandbuch, den wir überlesen hatten:

> Während der Signalgenerierung werden die einzelnen Signal-Amplitudenwerte aus dem RAM ausgelesen. Wenn die Grundsignalwellenform verändert wird [...] werden die entsprechenden Amplitudenwerte **vom CPU in das RAM geladen**.

Der PM5139 ist ein **1024-Punkt-Wavetable-DDS**. Der TWS ist in keinem naiven Sinne ein Dreiecksgenerator — er ist ein Phasenakkumulator, der Leseadressen 0…1023 für ein schnelles RAM auf Einheit 4 erzeugt, und dieses RAM wird vom CPU über den C-Bus gefüllt. Sinus, Rechteck, Sägezahn und beliebige Wellenformen sind alle derselbe Mechanismus: eine Tabelle.

Und die Tabelle enthält genau **eine Periode des Ausgangssignals**. Eine Tabelle, die aus einer *Summe von Harmonischen* aufgebaut ist, ist also in ihren 1024 Punkten immer noch periodisch, und sie spielt als Akkord. Kein Arpeggio, kein Modulationstrick — mehrere Töne erklingen gleichzeitig mit den vollen 20 Vpp, während der CPU überhaupt nichts tut, während sie erklingen. Da die Teiltöne ganzzahlige Vielfache der Tabellenfrequenz sein müssen, ergeben sich die Intervalle in reiner Stimmung, was für einen gehaltenen Akkord ohnehin die bessere Stimmung ist.

`M27512_PM5139_V20_chords.bin` liegt im Repository bereit zum Brennen — das Riff, in Akkorden, mit der Hüllkurve. Um es selbst zu bauen oder eine eigene MIDI-Datei anstelle des eingebauten Riffs zu verwenden:```
python3 mkpoly.py --chord crunch M27512_PM5139_V20.bin out.bin
python3 mkpoly.py --chord crunch --midi yours.mid --channel 1 \
        M27512_PM5139_V20.bin out.bin

mkchord.py baut die Tabellen — power (2:3:4), major (4:5:6), minor (10:12:15), dom7 (4:5:6:7) und fünf weitere. mkpoly.py legt eine davon zusammen mit der Melodie in das freie ROM und hängt denselben toten Menüeintrag an. Es lädt den Akkord einmal, spielt dann die Melodie nur durch Umstimmen, was den gesamten Akkord parallel transponiert. Jede Note des E1M1-Riffs wird zu einem Powerchord — woraus dieses Riff im Original auch besteht.

Architektonisch ist das ein PPG Wave: ein Zähler, der durch eine Single-Cycle-Wellenform läuft, direkt in einen DAC. Der Akkord-Trick ist derjenige, den Amiga-Tracker verwendeten — den Akkord in die Wellenform legen, damit eine Stimme drei Noten spielt, statt drei Kanäle dafür auszugeben. Ein C64 muss stattdessen arpeggieren, weil der SID keine beschreibbare Wavetable hat.

Man braucht nicht einmal ein EPROM für die Akkorde. Dieselben Tabellen passen in das beliebige EEPROM, also liefert python3 mkarb.py --chords sechs Akkorde, die von der Frontplatte aus wählbar sind, ohne dass die Firmware angetastet wird.

Es gibt zwei Player, und ein Image enthält den einen oder den anderen, da beide denselben Menüeintrag anhängen:

Zwei Messungen prägten dieses Design:

  • Das Download-Format trägt zehn Bits pro Punkt, nicht zwölf: Es erscheinen nur vier verschiedene niedrige Bytes (00h 44h 88h CCh), und jeder rekonstruierte Wert ist ein Vielfaches von vier. Das Wellenform-RAM ist zwölf Bits breit, aber der Bus treibt zehn — genau das, was das ARB-Format speichert, sodass Philips dort nichts verschwendet hat.
  • Ein vollständiges Neuladen der Tabelle dauert 32 bis 40 ms bei stillem Ausgang, und es gibt keine zweite Pufferseite — RAM_PAGE bei 1D62h, was nach einer klingt, bildet sein Wort aus der Frequenz. Also lebt die Harmonie in der Tabelle und die Melodie im Frequenzwort; während die Musik läuft, wird nichts neu geladen.

Der Emulator modelliert kein Wellenform-RAM, daher wird der Loader stattdessen durch Konstruktion verifiziert: polytest.js zeichnet auf, was tatsächlich auf den Bus gelangt, und vergleicht alle 1024 Punkte mit dem, was mkchord.py erzeugt hat.

Es brauchte fünf EPROMs, um dorthin zu gelangen, und der Emulator konnte uns nur einen Teil des Weges bringen: Er modelliert die CPU und den Bus, aber nicht das Wellenform-RAM, sodass er nur bestätigen kann, dass dieselben Bytes hinausgehen, die die Firmware sendet. Das ist notwendig und nicht hinreichend. Drei Dinge mussten am Instrument selbst geklärt werden:

  • Die Byte-Reihenfolge. Zwei Bytes pro Punkt, hohes Byte zuerst. Sie aus dem eigenen Download der Firmware abzuleiten, ergab die entgegengesetzte Antwort, und die Tabelle kam als Rauschen heraus. Was es klärte, war ein EPROM mit sechs Testmustern — eine flache Linie, eine Rampe, dieselbe Rampe mit vertauschten Bytes jedes Punktes und drei weitere — und ein Blick auf ein Oszilloskop. Die vertauschte Rampe war die saubere.

  • Eine Wellenform-Änderung sind neunzehn Telegramme, nicht die drei, die der erste Player sendete. Das entscheidende ist ein Zwei-Byte-Schreiben, das das RAM in den Schreibmodus versetzt; ohne es gehen 2048 Bytes auf den Bus und landen nirgendwo.

  • Der Ausgangspegel. Das Dämpfungsglied sind zwei separate 20-dB- Relaisstufen in einem Byte, die ROM-Tabelle dafür liest sich invertiert gegenüber der bisherigen Dokumentation (es sind Bypass-Bits), und der Pegel-DAC hat sieben Bits, nicht acht — er läuft bei 80h über, sodass eine „lautere" Einstellung Stille erzeugte. Diese brauchte eine Matrix von etwa dreißig Kombinationen in einem einzigen Image, wobei die Ausgangsfrequenz als Testnummer diente, damit die eigene Anzeige des Oszilloskops sagt, welche Kombination aktiv ist.``` telegrams emitted by the loader: STR6 4 byte(s) 122 machine cycles 1E 00 20 01 STR2 0 byte(s) 132 machine cycles STR1 2050 byte(s) 39490 machine cycles CC 89 88 8A 44 8B 44 8C ... -> all 1024 points identical to the table mkchord.py built

    note 1 f0 = 41.20 Hz chord 2:3:4 = 82.4 / 123.6 / 164.8 Hz root E2 note 8 f0 = 36.71 Hz chord 2:3:4 = 73.4 / 110.1 / 146.8 Hz root D2

root@kitploit:~
---

## Sechs eigene beliebige Wellenformen

![Die sechs beliebigen Slots](https://assets.kitploit.com/production/public/readmes/54732/9fa22b75c34e4aaa83df1b2afd284353e019cb65f181a83e5cdeceb57ea32937/4b15ae02ae1bec49f3d1b6ab79c480cd3a73b10818c41a8b04be57ab6f691ef7-display-v1.webp)

`D310_image_V20.bin` füllt jeden Slot im EEPROM — den Chip einmal zu
brennen lohnt sich:

| Slot | Wellenform | Vpp | Für |
|---|---|---|---|
| 1 | sinc, 8 Keulen | 12.17 | Bandbegrenzung, Überschwingen |
| 2 | Ringing, Q≈6 | 17.81 | Einschwingverhalten |
| 3 | EKG | 12.80 | Demo |
| 4 | Treppe, 16 Stufen bipolar | 20.00 | Linearität, Auflösung |
| 5 | gleichgerichteter Sinus | 10.00 | wie im Original, aber berechnet |
| 6 | Multiton, 5 Töne | 20.00 | Intermodulation |

Zwei Details, die wichtig sind und leicht falsch gemacht werden:

**Nullzentrierung schlägt Streckung.** Der naheliegende Ansatz ist, jede
Kurve über den vollen Wertebereich zu strecken. Nicht tun: Der
DC-Offset des Instruments kommt aus einem separaten analogen Pfad und
addiert eine *feste* Spannung, während der DC-Anteil einer gestreckten
asymmetrischen Kurve *mit der Amplitude skaliert*. Man müsste den Offset
jedes Mal neu abgleichen, wenn man den Pegel ändert. Den natürlichen
Nullpunkt der Wellenform auf die Null des Wandlers zu legen kostet 0,2
bis 1 Bit — gegenüber den 16 LSB Rauschen, die der ursprüngliche analoge
Pfad ohnehin beiträgt. Kein wirklicher Kostenfaktor.

**In Fließkomma skalieren, einmal runden.** Erst runden und danach
strecken ergibt 1,0–1,5 Quantisierungsstufen Fehler; in Float skalieren
und einmal runden ergibt die optimalen 0,5.

Das Verzeichnis benötigt ein Identitätsbyte pro Kurve (eine Prüfsumme
der 1280 Kurvenbytes, Startwert `55h`) und die Min/Max-Werte als
10-Bit-Werte, um sechs Bits linksbündig ausgerichtet. Ist das
Identitätsbyte falsch, zeigt das Instrument **Err 8** und verweigert die
beliebige Quelle — genau das passierte beim ersten echten Flash.

---

## Der Browser-Simulator

`PM5139_Simulator.html` ist eine einzelne, in sich geschlossene Datei —
kein Build-Schritt, keine Abhängigkeiten, kein Netzwerk. Öffnen Sie sie,
und die ursprüngliche V1.3-Firmware bootet vor Ihnen.

Der 8051-Kern führt den echten Code aus. Timer, Interrupts, der C-Bus
und I²C werden emuliert; das Display wird aus dem tatsächlichen
PCF8576-Datenstrom dekodiert, und die Tasten erzeugen die
pulsweitenkodierte SAA3007-Wellenform auf P3.3. Das batteriegepufferte
RAM ist vorgeladen, und das beliebige EEPROM wird beim Start generiert
und von der Firmware selbst geprüft.

Ein Kaltstart dauert etwa 9 Millionen Instruktionen, also geben Sie ihm
eine Sekunde.

---

## Repository-Struktur```
Documentation
  PM5139_Hardware_Reference.md      the main document, 35 sections
  PM5139_Firmware_Modification.md   how to change the firmware and flash it back
  PM5139_Tables.md                  command and message tables, both versions
  PM5139_Changelog_V13_V15.md       what changed from V1.3 to V1.5, in prose
  PM5139_Bit_Crossreference.md      flags 20h–2Fh: set / cleared / tested
  HANDOVER.md                       state of play
  BACKLOG.md                        open questions, each with an entry point

Firmware and data
  M27512_PM5139_V13.bin  V15.bin    the two original dumps
  M27512_PM5139_V20.bin             our own version
  D310_image.bin                   the arbitrary EEPROM as read out
  D310_image_V20.bin               six waveforms of our own, ready to burn
  PCF8570_image.bin                NVRAM in the factory state
  PM5139_V13_annotated.asm  V15     the annotated listings

Emulation
  emu.py system.py system2.py keys.py    Python core and peripherals
  core.js                                the same core in JavaScript
  shell.html + build.py                  -> PM5139_Simulator.html

Analysis
  mcs51.py analyze2.py seqdiff.py mapv15.py symbols.py annotate.py

Building
  romfix.py mkv20.py mkarb.py waveforms.py asm51.py mkdoom.py
  midi.py mid2ton.py mkchord.py mkpoly.py

Measurement scripts          (see "Using the tools")
  bitmap.js flags.js cmd16.js iface.js trace.js arb.js xrange.js
  polytest.js cyclecheck.py
  limits.js param.js keycodes.js decade.js whoruns.js remote.js
  display.js digits.js readout.js nvram.js nv2.js nv3.js …

Verwendung der Tools

Python 3 und Node sind alles, was Sie brauchen. matplotlib für die Diagramme, pillow und numpy nur für lines.py.

Die Firmware ansehen```bash

python3 annotate.py 13 # -> PM5139_V13_annotated.asm python3 mapv15.py --write # map V1.3 symbols onto V1.5 python3 annotate.py 15 # -> PM5139_V15_annotated.asm python3 seqdiff.py # structural diff of both versions python3 romfix.py M27512_PM5139_V13.bin

root@kitploit:~
### Build V2.0```bash
python3 mkv20.py                                  # from V1.5 (default)
python3 mkv20.py M27512_PM5139_V13.bin out.bin    # or from V1.3
python3 romfix.py M27512_PM5139_V20.bin           # verify the checksum

Das beliebige EEPROM erstellen```bash

python3 waveforms.py # what the generators produce python3 mkarb.py # -> D310_image_V20.bin python3 plot_arb.py # -> PM5139_ARB_V20.png

root@kitploit:~
### Eine Melodie hinzufügen```bash
# the built-in bass line, into a separate image
python3 mkdoom.py M27512_PM5139_V20.bin M27512_PM5139_V20_melody.bin

# or bring your own tune (no MIDI file is shipped here)
python3 midi.py song.mid                                # what is in the file
python3 mid2ton.py song.mid --voice high                # inspect the conversion
python3 mkdoom.py --midi song.mid --channel 1 M27512_PM5139_V20.bin out.bin

node doomtest.js M27512_PM5139_V20_melody.bin           # play it back in the emulator

mkdoom.py patcht ein Image einmal und weigert sich, es zweimal zu tun — baue ein frisches V2.0 mit mkv20.py, wenn du von vorne beginnen möchtest.

Einen Akkord spielen```bash

python3 mkchord.py # the chords on offer python3 mkpoly.py --chord power M27512_PM5139_V20.bin out.bin python3 romfix.py out.bin node polytest.js out.bin # check it on the bus

root@kitploit:~
### Diagramm```bash
python3 plot_waveforms.py                                    # V2.0 by default
python3 plot_waveforms.py M27512_PM5139_V13.bin out.png
python3 plot_v20.py                                          # before/after

Dinge im Emulator messen

Jede dieser Ausgaben gibt eine Tabelle aus, die Sie mit der Dokumentation abgleichen können:```bash node bitmap.js # which state bits change the display (31 / 23 / 74) node flags.js # which bits change the C-bus telegrams, over six profiles node cmd16.js # which strobes each command token triggers node keycodes.js # which key code reaches which handler node decade.js # decade limits, driven by real key presses node limits.js # parameter limits by bisection node whoruns.js # does this routine ever run in normal operation? node arb.js # does the firmware accept this EEPROM image? node xrange.js # which EEPROM addresses are read at all node iface.js # emulate the interface card, log the I²C traffic node remote.js # how the instrument enters remote mode node nvram.js # which NVRAM bytes change when you adjust something node readout.js # decode a display digit row into plain text node showversion.js # read the version indication out of all three ROMs node trace.js # dynamic execution trace

root@kitploit:~
### Einen Schaltplan lesen```bash
pdftoppm -f 157 -l 157 -r 400 -png pm5138A_service_manual.pdf page
python3 lines.py page-157.png 1200 800 3000 2400 150

Alles reproduzieren

Die gesamte Build-Kette ist deterministisch — diese Befehle bauen die Firmware und das EEPROM-Image Byte für Byte neu auf:```bash python3 mapv15.py --write python3 annotate.py 13 && python3 annotate.py 15 python3 mkv20.py # -> M27512_PM5139_V20.bin python3 romfix.py M27512_PM5139_V20.bin python3 mkarb.py # -> D310_image_V20.bin python3 mkdoom.py M27512_PM5139_V20.bin M27512_PM5139_V20_melody.bin python3 build.py # rebuild the browser simulator

root@kitploit:~
---

## Zurückflashen

> **Bewahre dein Original-EPROM auf.** Lies es zweimal aus, vergleiche die
> Dumps, lege den Chip in eine Schublade. Alles hier ist nur umkehrbar,
> wenn du es noch hast.

Die Firmware prüft beim Einschalten eine Bytesumme über den belegten
Bereich und vergleicht sie mit dem Byte unmittelbar danach. Machst du es
falsch, bekommst du `Err 1` und eine Endlosschleife — das Instrument bootet
nicht. `romfix.py` berechnet und fügt den korrekten Wert ein; jedes
Build-Skript hier ruft es bereits auf.

| Version | Bereich | Prüfsummen-Byte | Wert |
|---|---|---|---|
| V1.3 | `0000h–AC6Fh` | `AC70h` | `F2h` |
| V1.5 | `0000h–B3C9h` | `B3CAh` | `99h` |

Zwei Dinge, die auf echter Hardware auf die harte Tour gelernt wurden:

- Das Arbitrary-EEPROM braucht neu berechnete **Identitätsbytes**, sonst
  bekommst du bei jedem Start `Err 8` und die ARB-Quelle lässt sich nicht
  auswählen.
- Wenn sich ARB nach einem Flash seltsam verhält, prüfe, ob Pin 28 des
  Sockels richtig sitzt, bevor du das Image verdächtigst.

---

## Wie zuverlässig ist das?

Alles, was als verifiziert markiert ist, wurde durch Aufrufen der
Originalroutinen im Emulator über mehrere Stützpunkte bestätigt, meist
zusätzlich gegen das Listing oder den Schaltplan gegengeprüft.

Wo etwas schiefging, ist es niedergeschrieben statt stillschweigend
korrigiert:

- **Drei Emulator-Bugs** (`ACALL` als `AJMP`, fehlendes AC-Flag,
  verdoppelter Tastatur-Interrupt) waren während der mittleren Phase des
  Projekts aktiv. Alle betroffenen Befunde wurden danach neu gemessen —
  die Display-Bitmap kam identisch zurück, die Strobe-Zuordnung stimmte
  mit dem Servicehandbuch überein, und Abschnitt 16 hatte tatsächlich
  zwei fehlende Strobes.
- **Ein synthetisches NVRAM-Image**, das nie aus einem echten Instrument
  ausgelesen wurde, verfälschte zwei Befunde, darunter „der Drehknopf
  funktioniert nur in eine Richtung". Die Lösung war, der Firmware ein
  ungültiges NVRAM zu übergeben und sie ihren eigenen Werkszustand
  schreiben zu lassen.
- **Handgesetzte RAM-Zustände** erzeugen Konfigurationen, die das
  Instrument nie einnimmt. Zweimal führte das zu falschen Schlüssen,
  einmal zu einem Absturz in die Befehlstabelle.
- **`core.js` zählt einen Befehl pro Zyklus**, nicht Maschinenzyklen. Gut
  für die Reihenfolge, falsch für absolute Timing-Angaben — Timing-Aussagen
  hier stammen aus dem MCS-51-Datenblatt.

Alles, was eine Annahme statt einer Messung ist, sagt das im Text.

---

## Noch offen

- **36 von 128 Zustandsbits** brauchen einen Stimulus außerhalb der sechs
  Betriebsprofile — Selbsttest, Fehlerpfade, Schnittstellenverkehr.
- **NVRAM-Felder ab Offset 0Dh.** Das Layout bis dorthin ist gemessen
  (`NVRAM offset + 4Bh = RAM address`), das Häkchen ist verstanden
  (Bytesumme, Startwert `AAh`, 25 Bytes).
- **Welcher Arbitrary-Befehl welchen der 13 Sub-Blöcke** im Bereich
  `8871h` erreicht. Es existieren nur vier direkte Token-Vergleiche; der
  Rest verzweigt über Bittests.
- **Ob ein Fernbefehl die Parameterbereichsprüfung umgehen kann.**
- **Die Wellenform-Laderoutinen** sind die härteste verbleibende
  Abhängigkeit für eine vollständige Reimplementierung — ohne sie gibt es
  kein Ausgangssignal.
- **Wie das PM5139 20 MHz aus demselben Takt** macht wie sein 10-MHz-
  Geschwister. Die Kette legt nahe, dass sein Tiefpass bei 10 MHz statt
  5 MHz liegt, aber das braucht ein PM5139-Handbuch zur Bestätigung.

Wenn du eines dieser Instrumente besitzt, würden zwei Dinge sehr helfen:
ein **PM5139-Servicehandbuch** und Dumps von **anderen Firmware-
Versionen** (eine V1.4 mag existieren oder auch nicht).

---

## Quellen

- **`pm5138A_service_manual.pdf`** — die primäre Hardwarequelle. 176
  Seiten, OCRed; Fließtext liest sich sauber mit `pdftotext -layout`, die
  Schaltpläne müssen als Bilder gerendert werden. Die Seiten 4-3 bis 4-28
  fehlen im Scan.
- **PM5139-Benutzerhandbuch** (Fluke) — dreisprachiger Scan ohne
  Textebene; Kapitel 3.7.4.6 dokumentiert die Arbitrary-Befehle. Es lohnt
  sich, selbst zu OCRen — der englische Teil sind die PDF-Seiten 13–145.
- **PM5136-Benutzerhandbuch** — nützlich als Gegenprobe: seine
  Fehlernummern und die Befehlsliste zeigen, welche Parameter dem
  kleinsten Modell fehlen, was die Parameterreihenfolge im ROM unabhängig
  bestätigte.
- **Datenblatt aller drei Modelle** — Betriebsgrenzen pro Wellenform.

Die Handbücher sind Fremddokumente und werden **in diesem Repository
nicht weiterverbreitet**. Sie sind online auffindbar.

---

## Lizenz und Nutzung

Zwei Arten von Material unter unterschiedlichen Bedingungen — siehe
[LICENSE](https://github.com/doctormord/philips-pm-5139-5138a-5136-firmware-project/blob/main/LICENSE) für den genauen Umfang:

- **Die Reverse-Engineering-Arbeit ist MIT.** Dokumentation, Werkzeuge,
  beide Emulatoren, Symboltabellen, Annotationen, die generierten
  Wellenformen und die Plots. Nutze sie, wie du willst.
- **Die Philips-Firmware ist nicht unsere zu lizenzieren.** Die ROM-Images,
  die Werks-Chip-Dumps, die Disassembly-Listings und der Browser-Simulator
  (der das V1.3-Image einbettet) reproduzieren oder leiten sich von
  Philips-Arbeit ab. Sie sind hier als Untersuchungsobjekt, für
  Interoperabilität, Reparatur und Dokumentation von Instrumenten, die seit
  Jahrzehnten nicht mehr unterstützt werden. Wo unsere eigene Arbeit
  eingemischt ist — die Annotationen, die korrigierte Wellenform in V2.0 —
  ist nur dieser Beitrag MIT.

Wenn du Rechte an der Original-Firmware hältst und Einspruch erhebst,
eröffne ein Issue, und sie wird entfernt.

Wenn du etwas davon nutzt, ist ein Rücklink willkommen. Wenn du einen
Fehler findest, eröffne ein Issue — jede Aussage hier nennt die Adresse
oder Messung, auf der sie beruht, also sollte sie falsifizierbar sein.
Tool herunterladen
Disassemblierungvollständig für beide Versionen, ~23 000 Zeilen, mit Querverweisen
Annotiertes Listing147 benannte Routinen, 145 Kopfkommentare, 3 826 annotierte Zeilen
Dokumentation35 Abschnitte, 4 600 Zeilen, jede Aussage belegt
SignalpfadFrequenz, Amplitude, Offset, AM, FM, Burst, Symmetrie, Sweep — alles berechnet und gegen den Originalcode verifiziert
Hardwarealle 10 Strobes, der C-Bus, I²C mit jedem Teilnehmer, Ports, Tastatur, Drehknopf, Display-Bitmap
Statusbits75 von 128 mit dokumentierter Wirkung
VersionsdiffV1.3 vs. V1.5 ist zu 91,4 % strukturell identisch; jede Änderung benannt
Emulatoreneiner in Python, einer in JavaScript (~8 Mio. Instruktionen/s), plus ein Single-File-Browser-Simulator
Eigene FirmwareV2.0 — ein Werksdefekt behoben, Checksumme behandelt, im Emulator und auf echter Hardware verifiziert
PositionTypFunktion
D301PCB80C6528051-Kern mit Hardware-I²C, 12 MHz
D30627512Programm-EPROM — V1.3 belegt 0000h–AC70h
D310X28C64Arbiträr-EEPROM am MOVX-Bus
D305PCF8570256 Bytes batteriegepuffertes NVRAM an I²C (A0h)
D304-APCF8576LCD-Treiber an I²C (70h), 20-Byte-Puffer
D302-ASAA3007Tastatur-Encoder, pulslängenkodiert auf einer einzigen Leitung
D30774HCT4514Strobe-Decoder — die Strobe-Nummer sind die Adressbits A8…A11
mkdoom.pymkpoly.py
Stimmeneinemehrere gleichzeitig
Wellenformwas auch immer geladen istihre eigene Akkordtabelle
Pegelwie ihn die Frontplatte hinterlassen hatexplizit gesetzt, 11,6 Vpp gemessen
Verwendetes ROM182 Bytes2617 mit dem eingebauten Riff, 6185 aus einem MIDI-Track