Skip to content
KitploitKITPLOIT
StrumentiBlog
Invia
StrumentiBlog
Invia

Strumenti di Hacking, PenTest e Cybersecurity per il tuo Arsenale di Sicurezza!

Kitploit è una directory di strumenti di hacking, cybersecurity e pentesting. Scopri gli ultimi aggiornamenti dei progetti per trovare vulnerabilità, analizzare sistemi, automatizzare i test e rafforzare la tua sicurezza.

··Feed·Contatto·Privacy·© 2026 Kitploit

Directory degli strumenti

Categorie

Vedi tutte le categorie
Loading categories
Philips-PM-5139-5138A-5136-Firmware-Project — Reverse engineering del firmware dei generatori di funzioni Philips PM5139 / PM5138A / PM5136: emulatori 8051 utilizzati come strumenti di misura, 35 sezioni di hardware documentato e un firmware V2.0 corretto | Kitploit
Strumenti/GitHubGitHub/doctormord/philips-pm-5139-5138a-5136-firmware-project
Sicurezza Sistemi EmbeddedAnalisi StaticaAnalisi Dinamica (Sandboxing)Reverse EngineeringSicurezza HardwareAnalisi di BinariPaper e RicercaApprendimento e FormazioneAnalisi del Firmware

Più Popolari

Vedi tutti →

Scopri gli strumenti più utilizzati dalla nostra community.

Esplora tutti gli strumenti

Sfoglia la nostra collezione di strumenti

Vedi tutti gli strumenti →
Condividi
GitHubdoctormord/philips-pm-5139-5138a-5136-firmware-project

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

Reverse engineering del firmware dei generatori di funzioni Philips PM5139 / PM5138A / PM5136: emulatori 8051 utilizzati come strumenti di misura, 35 sezioni di hardware documentato e un firmware V2.0 corretto

Vedi Repository
2131 giorno faNon ancora revisionato

Philips PM5139 — Reverse Engineering del Firmware

Un generatore di funzioni da 20 MHz del 1994 circa, smontato via software: due dump di EPROM, un emulatore 8051 usato come strumento di misura, e 35 sezioni di documentazione in cui ogni singola affermazione è supportata da un indirizzo nel listing, da una misura dell'emulatore o dallo schema elettrico.

Alla fine c'è un firmware V2.0 che corregge un difetto che Philips aveva distribuito, sei forme d'onda arbitrarie nostre, e un simulatore da browser che esegue la ROM originale istruzione per istruzione.

Tutte le tabelle delle forme d'onda nella ROM V1.3

Ogni tabella di forme d'onda nella EPROM di programma, tracciata direttamente dal binario. In basso a destra c'è quella che ha dato inizio alla parte più interessante di questo progetto.


Indice

  • Di cosa si tratta
  • Risultati in breve
  • Lo strumento
  • Il metodo: l'emulatore è lo strumento di misura
  • Il percorso fino a qui
  • Le parti migliori
  • Firmware V2.0 — cosa c'è di nuovo
  • L'easter egg
  • E poi si è scoperto che è polifonico
  • Sei forme d'onda arbitrarie nostre
  • Il simulatore da browser
  • Struttura del repository
  • Uso degli strumenti
  • Riprodurre tutto
  • Riscriverlo nella memoria
  • Quanto è affidabile?
  • Ancora aperto
  • Fonti

Di cosa si tratta

Il Philips PM5139 è il modello top da 20 MHz di una famiglia di tre strumenti (PM5136 / PM5138A / PM5139). All'interno c'è un PCB80C652 — un core 8051 con I²C hardware — una EPROM di programma 27512, e sei assemblaggi analogici appesi a un bus seriale.

Non esiste un manuale di servizio del PM5139. La gente lo cerca nei forum dal 2010. Quello che esiste è il manuale del PM5138A, il suo modello gemello da 10 MHz, che è internamente quasi identico.

Quindi questo progetto è partito dall'altro capo: fare il dump della EPROM, e capire cosa fa il codice finché lo strumento non è compreso abbastanza bene da modificarlo.

Erano disponibili due versioni del firmware, V1.3 e V1.5, entrambe dump M27512 da 64 KiB.


Risultati in breve


Lo strumento

Il lato analogico è un bus C seriale: l'UART dell'8051 funziona in modalità shift register, TXD è il clock, RXD il dato, e uno strobe decide quale dei dieci shift register memorizza i byte. MOV DPH,#8nh seguito da MOVX @DPTR,A attiva lo strobe n. Quella singola riga è la chiave dell'intera sezione analogica.


Il metodo: l'emulatore è lo strumento di misura

Questa è la parte che vale la pena rubare per il vostro progetto.

Leggere un binario 8051 da 44 KB a occhio ti porta forse a un terzo del percorso. Tutto il resto è venuto eseguendo il codice originale e osservando cosa ne esce:```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:~
Varia l'input, leggi l'output, verificalo rispetto all'ipotesi. Ha
funzionato per frequenza, ampiezza, offset, profondità AM, deviazione FM,
conteggio dei burst, simmetria ed entrambe le caratteristiche di sweep.
Ogni formula nella documentazione è accompagnata dai punti campione su
cui è stata verificata.

Tre affinamenti l'hanno reso davvero produttivo:

**Guarda il bus, non il display.** La sezione 15 misura cosa fa un bit di
stato al buffer del display, e 74 dei 128 bit sembrano non fare nulla. Ma
molti di essi non pilotano il display, pilotano gli *assemblaggi
analogici* — e quelli sono visibili solo come telegrammi sul C-bus.
Registrare `MOV SBUF,…` e il terminatore `MOVX @DPTR` ha portato il
conteggio dei bit documentati da 54 a 75.

**Premi i tasti, non modificare la RAM.** Impostare a mano un byte di RAM
produce stati che lo strumento non assume mai. Ci è costato due
conclusioni errate e un crash nella tabella dei comandi. Iniettare codici
di tasto reali attraverso l'SAA3007 emulato fornisce stati che il
firmware raggiunge effettivamente — ed è stata una scansione a forza
bruta su tutti i 256 codici di tasto a rivelare quale tasto attiva quale
handler.

**Sospetta prima del tuo emulatore.** Tre bug nel nostro core hanno
prodotto comportamenti "inspiegabili" del firmware: `ACALL` eseguito come
`AJMP`, un flag di carry ausiliario mancante (così `DA A` si comportava
male e il firmware sembrava contare in binario), e un interrupt della
tastiera raddoppiato. Ogni conclusione di quel periodo è stata
rimisurata in seguito.

---

## La strada percorsa

**Prima statica.** Un disassembler con una tabella completa degli
opcode, poi discesa ricorsiva con euristiche per le jump-table. Ha
prodotto 30 508 byte di codice e ha lasciato 13 637 byte non
giustificati.

**Poi dinamica.** Una trace run — avvio a freddo, tutti i 23 tasti del
pannello frontale, entrambe le direzioni della manopola, ogni modalità
operativa, 86 milioni di cicli — marcando ogni indirizzo effettivamente
eseguito. Confrontata con l'analisi statica, ha trovato esattamente
**una** area che la discesa aveva mancato, e 10 686 dei byte inspiegati
si sono rivelati essere cinque blocchi di tabelle noti.

**Poi gli schemi.** L'OCR del manuale di servizio è inutile per gli
schemi, ma le immagini delle pagine a 400 dpi sono eccellenti. Tagliate
in tessere sovrapposte, sono leggibili fino ai numeri dei pin. Sei fogli
sono stati letti in questo modo — e dove cinque tracce parallele
corrono a 90 pixel di distanza, l'ispezione a occhio è stata sostituita
da uno script (`lines.py`) che estrae i segmenti di linea dalla bitmap.

**Poi i due chip che sono stati estratti.** Un 27C64 etichettato "SINUS
1.1" e un X28C64 sono stati letti. Entrambi sono stati inseriti nello
schema e i loro contenuti decodificati.

**Poi il diff tra versioni.** Tokenizzando entrambe le ROM (distanze
relative dei salti invece dei target assoluti) ed eseguendo
`SequenceMatcher` su di esse si ottiene una mappatura degli indirizzi che
sopravvive al movimento del codice — è così che i simboli della V1.3
vengono trasferiti sulla V1.5.

---

## Le parti buone

### Philips ha fornito una forma d'onda rumorosa

Le tre curve arbitrarie integrate si trovano a `A047h`, `A447h` e
`A847h`. La terza ha la stessa forma di una tabella che già risiede nella
ROM in forma calcolata — ma con **563 cambi di direzione contro 13**, e
una deviazione standard di 4,1 LSB.

È stata campionata da una sorgente analogica invece di essere calcolata.
La media della deviazione è zero, solo due punti su 1024 si discostano di
più di 10 — questa non è una forma d'onda diversa, è la *stessa* forma
d'onda con del rumore sopra.

### Quella tabella è una scala di livelli da 30 dB

La versione pulita era stata descritta in una bozza precedente come "una
sinusoide con dieci profondità AM", che era una lettura a occhio del
grafico, non qualcosa che il codice afferma. Calcolati fino in fondo, i
1024 punti si dividono in dieci archi di sinusoide le cui estensioni sono```
255  171  120  80  56  38  26  17  12  8

una serie geometrica con rapporto 0.681 = 10^(−1/6), cioè 3.33 dB per passo e 30.1 dB complessivi. Un modello a dimezzamento è sbagliato fino a 56, un modello a 3 dB di 10. È una scala logaritmica a livelli — un pattern di test di ampiezza o attenuazione.

STR9 invia 16 bit come due telegrammi da 1 byte

Il controller di ampiezza ha due registri a scorrimento su un unico strobe, ma il firmware invia sempre e solo un byte per telegramma. Lo schema lo spiega: i due 4094 sono in cascata attraverso QS' (pin 10), con il pin 9 inutilizzato — e i telegrammi arrivano in coppie, a ~42 000 cicli di distanza con milioni di cicli di silenzio tra le coppie. Il byte inviato per primo viene spinto nel secondo registro.

Lo stesso schema di cascata è emerso su ogni assemblaggio con più di un registro a scorrimento — incluso un caso in cui la catena attraversa un confine di assemblaggio attraverso una linea chiamata E.

L'attenuatore non è un calcolo

Cinque bit nel telegramma STR9 pilotano direttamente i relè: S1 commuta il range del generatore DC, S2…S5 i relè dell'attenuatore. 20 dB (per 40dB), 20 dB, 50/600 ohms — è stampato sullo schema. Non ci sono soglie da calcolare.

Un handler nascosto in bella vista

La jump table a 0301h viene letta con JMP @A+DPTR. La voce 15 finisce a 0301h + 30 = 031Fh — e lì, invece del solito AJMP, si trova l' handler stesso, inline, risparmiando un salto. Nessuna istruzione di salto nel ROM punta ad esso, quindi l'analisi statica l'ha perso. È l'handler del DIAL LOCK, e solo la traccia dinamica l'ha trovato.

Sei slot arbitrari, non ventiquattro

Il data sheet promette 24 memorie di forma d'onda. La directory nella EEPROM ne dice sei. L'aritmetica lo conferma:``` 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:~
Il range di lettura misurato del firmware è `0100h–1EFFh` — sei curve
al byte. Lo strumento è stato costruito con il chip piccolo.

### Codice morto che parla con un dispositivo che non c'è

186 byte a `9AFFh` fanno traffico I²C con l'indirizzo `5Ah` — un indirizzo che
non appare da nessun'altra parte. In **entrambe** le versioni del firmware, nessun salto
punta ad esso. Si trova nello stesso blocco di tipo dispositivo della scheda interfaccia a `5Eh`,
solo con bit di banco diversi, e invia il buffer di ricezione e i
registri aritmetici in due telegrammi da dieci byte. Sembra una
diagnostica di fabbrica per un dispositivo che non è mai stato commercializzato.

### Non è possibile eseguire codice dalla EEPROM arbitraria

Un'idea ovvia — mettere il codice in uno slot di forma d'onda arbitraria e saltarci dentro —
è morta sul nascere. L'8051 è Harvard: le istruzioni arrivano attraverso
`/PSEN` dalla EPROM di programma, i dati attraverso `/RD` dalla
EEPROM arbitraria. Non è bloccato; il filo semplicemente non c'è.

### E la codifica della frequenza, finalmente

La riga delle cifre del display si trova in `3Eh–43h` dell'immagine inviata al
PCF8576, tutte le posizioni condividono una codifica di segmento, e il byte `43h`
passa da kHz a MHz tra la decade 7 e 8. Da ciò:```
f = M · 10^(D−8) kHz

Tre sequenze di frequency stepping misurate sullo strumento reale sono riprodotte esattamente da questo — inclusa quella che si interrompe anticipatamente perché la mantissa 2500 significherebbe 25 MHz, oltre il limite.


Firmware V2.0 — cosa c'è di nuovo

Curva arbitraria 3: V1.5 contro V2.0

A sinistra la curva spedita, a destra quella corretta. In basso a sinistra la deviazione dalla tabella calcolata — quella banda di ±5 LSB è ciò che una sorgente analogica campionata ha lasciato dietro di sé.

mkv20.py costruisce la V2.0 dalla V1.5 (o V1.3). Trova ogni indirizzo per firma anziché codificarlo a mano, così lo stesso script funziona su entrambe le versioni sorgente:

  1. Curva arbitraria 3 sostituita con la tabella calcolata pulita. Entrambe sono 1024 byte da 8 bit, quindi lo scambio è neutro in dimensione e non tocca alcun codice. 863 dei 1024 byte cambiano.
  2. Curva arbitraria 2 sostituita con un chirp logaritmico (1 → 40 periodi). Differiva dalla curva 1 esattamente in due byte — un impulso a spillo in più — quindi era ridondante.
  3. Identificazione della versione in *IDN?: PHILIPS,PM5139,0,V2.0/0000.
  4. Versione sul display: la sequenza di reset scrive due celle del display, e quei byte ora compitano 2.0 nella codifica del segmento misurata.
  5. Checksum ricalcolato e memorizzato dove il firmware se lo aspetta.

Tutto il resto è intatto. Sono state trovate altre tre stranezze e deliberatamente lasciate stare — una scrittura a un SFR inesistente (innocua, in entrambe le versioni), il blocco 5Ah morto, e tre bit di stato che vengono testati ma mai impostati. Modificarli non cambia alcun comportamento e aggiunge solo rischio.

M27512_PM5139_V20.bin è esattamente questo e nient'altro. La melodia sotto è un passo di build separato, opzionale.

Verificato: l'avvio a freddo nell'emulatore produce lo stesso buffer del display e gli stessi flag della V1.5, il checksum si convalida, e la build è riproducibile byte per byte. È stato flashato e gira su un PM5139 reale — il display mostra 2.0 e tutti e sei gli slot arbitrari funzionano.


L'easter egg

Dato che ci sono 19 509 byte inutilizzati dietro il checksum nella V1.5, e il percorso di frequenza prende una frequenza di nota come tre byte BCD, lo strumento può suonare musica attraverso la propria uscita.

La codifica è piacevolmente diretta — decade 3, poi la frequenza in 0.01 Hz come BCD, quindi 82.41 Hz è 30 82 41. Quattro byte per nota: tre per l'intonazione, uno per la durata.

La parte interessante è il trigger. Il menu diagnostico (tenere premuto LOCAL all'accensione) ha una jump table con otto voci, ma il ciclo del menu conta 0Bh solo da 1 a 7 — quindi l'ottava voce è irraggiungibile. È anche ridondante: salta all'inizio del menu, che viene raggiunto da altri due punti comunque.

Quindi l'intero hook è di due byte:``` 5B94h table entry 8: LJMP 5B45h -> LJMP 5B62h count limit: 08h -> 09h

root@kitploit:~
Nessun autotest viene perso, nessuna tabella viene spostata e nessuna voce di menu morta
compare. Tieni premuto LOCAL, accendi, lascia che il menu conti fino a 8, premi un tasto.

La temporizzazione proviene dal data sheet dell'MCS-51. Entrambi gli emulatori ora contano
i cicli macchina insieme alle istruzioni (`mcyc`, da `mcs51.CYCLES`), e
eseguendo passo passo il ciclo di attesa si misurano **1009 µs** per unità — 106,95 ms per
una semicroma a 140 BPM, 0,2 % fuori target. Il valore era un calcolo
manuale di 1006 µs a cui erano sfuggite due istruzioni.

`mkdoom.py` può anche convertire un file MIDI. Bisogna scegliere una voce
(nota più alta, nota più bassa, o un canale) e unire le sezioni più corte di
~25 ms — al di sotto di quella soglia una nota bassa non riesce a compiere un'oscillazione
completa e si sente solo un click.

---

## E poi si è scoperto che è polifonico

La melodia qui sopra è una sola voce. Non deve esserlo per forza, e il motivo è
una frase nel manuale di servizio che avevamo letto distrattamente:

> Durante la generazione del segnale, i distinti campioni di ampiezza del segnale vengono letti
> dalla RAM. Se la forma d'onda del segnale di base viene alterata [...] i
> corrispondenti campioni di ampiezza vengono **caricati nella RAM dalla CPU**.

Il PM5139 è un **DDS a wavetable da 1024 punti**. Il TWS non è un generatore
di triangolo in alcun senso ingenuo — è un accumulatore di fase che produce
indirizzi di lettura 0…1023 per una RAM veloce sull'unità 4, e quella RAM viene riempita dalla
CPU attraverso il bus C. Sinusoide, quadra, dente di sega e arbitraria sono tutte lo
stesso meccanismo: una tabella.

E la tabella contiene esattamente **un periodo dell'uscita**. Quindi una tabella costruita
da una *somma di armoniche* è comunque periodica nei suoi 1024 punti, e
suona come un accordo. Non un arpeggio, non un trucco di modulazione — diverse note
che suonano contemporaneamente a pieni 20 Vpp, con la CPU che non fa assolutamente nulla
mentre suonano. Poiché le parziali devono essere multipli interi della
frequenza della tabella, gli intervalli risultano in intonazione giusta, che per un
accordo sostenuto è comunque l'accordatura migliore.

`M27512_PM5139_V20_chords.bin` è nel repository pronto da masterizzare — il
riff, in accordi, con l'inviluppo. Per costruirlo da te, o per usare un
file MIDI tuo invece del riff integrato:```
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 costruisce le tabelle — power (2:3:4), major (4:5:6), minor (10:12:15), dom7 (4:5:6:7) e altre cinque. mkpoly.py ne mette una nella ROM libera insieme alla melodia e aggancia la stessa voce di menu morta. Carica l'accordo una volta, poi riproduce la melodia solo ritarando, il che traspone l'intero accordo in parallelo. Ogni nota del riff di E1M1 diventa un power chord — che è ciò di cui è fatto quel riff nell'originale.

Architetturalmente questo è un PPG Wave: un contatore che scorre attraverso una forma d'onda a ciclo singolo, dritto in un DAC. Il trucco dell'accordo è quello che usavano i tracker Amiga — metti l'accordo nella forma d'onda così una voce suona tre note invece di spendere tre canali per farlo. Un C64 deve invece fare arpeggio, perché il SID non ha una wavetable scrivibile.

Non serve nemmeno una EPROM per gli accordi. Le stesse tabelle stanno nella EEPROM arbitraria, quindi python3 mkarb.py --chords ti dà sei accordi selezionabili dal pannello frontale con il firmware intatto.

Ci sono due player e un'immagine ne porta uno o l'altro, dato che entrambi agganciano la stessa voce di menu:

Due misurazioni hanno plasmato quel design:

  • Il formato di download porta dieci bit per punto, non dodici: solo quattro distinti byte bassi compaiono mai (00h 44h 88h CCh) e ogni valore ricostruito è un multiplo di quattro. La RAM della forma d'onda è larga dodici bit, ma il bus ne pilota dieci — esattamente ciò che memorizza il formato ARB, quindi Philips non ha sprecato nulla lì.
  • Un ricaricamento completo della tabella è di 32-40 ms con l'uscita silenziosa, e non c'è nessuna seconda pagina di buffer — RAM_PAGE a 1D62h, che suona come una, costruisce la sua parola dalla frequenza. Quindi l'armonia vive nella tabella e la melodia nella parola di frequenza; nulla viene ricaricato mentre la musica suona.

L'emulatore non modella alcuna RAM della forma d'onda, quindi il loader è verificato per costruzione: polytest.js registra ciò che effettivamente raggiunge il bus e confronta tutti i 1024 punti con ciò che ha generato mkchord.py.

Ci sono volute cinque EPROM per arrivarci, e l'emulatore poteva portarci solo in parte del cammino: modella la CPU e il bus ma non la RAM della forma d'onda, quindi tutto ciò che può confermare è che gli stessi byte escono come li invia il firmware. Questo è necessario e non sufficiente. Tre cose dovevano essere risolte sullo strumento stesso:

  • L'ordine dei byte. Due byte per punto, byte alto per primo. Inferirlo dal download del firmware stesso ha dato la risposta opposta e la tabella è uscita come rumore. Ciò che l'ha risolta è stata una EPROM che portava sei pattern di test — una linea piatta, una rampa, la stessa rampa con i byte di ogni punto scambiati, e altri tre — e un'occhiata a un oscilloscopio. La rampa scambiata era quella pulita.

  • Un cambio di forma d'onda è diciannove telegrammi, non i tre che il primo player inviava. Quello che conta è una scrittura di due byte che mette la RAM in modalità scrittura; senza di essa 2048 byte escono sul bus e finiscono nel nulla.

  • Il livello di uscita. L'attenuatore è due stadi a relè separati da 20 dB in un byte, la tabella ROM per essi si legge invertita rispetto a come era stata documentata (sono bit di bypass), e il DAC di livello è a sette bit, non otto — va in wrap a 80h, quindi un'impostazione "più forte" produceva silenzio. Quella ha richiesto una matrice di circa trenta combinazioni in una singola immagine, usando la frequenza di uscita come numero di test così che la lettura dell'oscilloscopio stesso dica quale combinazione è attiva.``` 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:~
---

## Sei forme d'onda arbitrarie nostre

![I sei slot arbitrari](https://assets.kitploit.com/production/public/readmes/54732/9fa22b75c34e4aaa83df1b2afd284353e019cb65f181a83e5cdeceb57ea32937/4b15ae02ae1bec49f3d1b6ab79c480cd3a73b10818c41a8b04be57ab6f691ef7-display-v1.webp)

`D310_image_V20.bin` riempie ogni slot nella EEPROM — masterizzare il chip
vale la pena farlo una volta:

| Slot | Forma d'onda | Vpp | Per |
|---|---|---|---|
| 1 | sinc, 8 lobi | 12.17 | limitazione di banda, sovraelongazione |
| 2 | ringing, Q≈6 | 17.81 | comportamento di assestamento |
| 3 | ECG | 12.80 | demo |
| 4 | a gradini, 16 passi bipolare | 20.00 | linearità, risoluzione |
| 5 | sinusoide raddrizzata | 10.00 | come nell'originale, ma calcolata |
| 6 | multi-tono, 5 toni | 20.00 | intermodulazione |

Due dettagli che contano e sono facili da sbagliare:

**La centratura sullo zero batte l'allungamento.** La mossa ovvia è allungare ogni
curva su tutto l'intervallo di valori. Non fatelo: l'offset DC dello
strumento proviene da un percorso analogico separato e aggiunge una tensione
*fissa*, mentre il contenuto DC di una curva asimmetrica allungata scala *con l'ampiezza*.
Dovreste ritrimmare l'offset ogni volta che cambiate il livello.
Mettere lo zero naturale della forma d'onda sullo zero del convertitore costa da 0.2 a
1 bit — contro i 16 LSB di rumore che il percorso analogico originale già
contribuisce. Non un costo reale.

**Scalare in virgola mobile, arrotondare una volta.** Arrotondare prima e allungare
dopo dà 1.0–1.5 passi di quantizzazione di errore; scalare in float
e arrotondare una volta dà l'ottimale 0.5.

La directory necessita di un byte di identità per curva (un checksum dei 1280
byte della curva, valore iniziale `55h`) e il min/max come valori a 10 bit
allineati a sinistra di sei bit. Sbagliate il byte di identità e lo strumento
mostra **Err 8** e rifiuta la sorgente arbitraria — che è esattamente quello che
è successo al primo flash reale.

---

## Il simulatore nel browser

`PM5139_Simulator.html` è un singolo file autonomo — nessuno step di build,
nessuna dipendenza, nessuna rete. Aprite e il firmware originale V1.3 si avvia
davanti a voi.

Il core 8051 esegue il codice reale. Timer, interrupt, il C-bus e I²C
sono emulati; il display è decodificato dal flusso dati PCF8576 reale,
e i tasti generano la forma d'onda SAA3007 codificata in larghezza di impulso su P3.3. La
RAM alimentata a batteria è precaricata e la EEPROM arbitraria è generata all'avvio
e verificata dal firmware stesso.

Un avvio a freddo richiede circa 9 milioni di istruzioni, quindi dategli un secondo.

---

## Struttura del repository```
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 …

Utilizzo degli strumenti

Python 3 e Node sono tutto ciò che serve. matplotlib per i grafici, pillow e numpy solo per lines.py.

Esaminare il firmware```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

Compilare l'EEPROM arbitraria```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:~
### Aggiungere una melodia```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 applica una patch a un'immagine una sola volta e rifiuta di farlo due volte — crea una V2.0 nuova con mkv20.py se vuoi ricominciare da capo.

Riprodurre un accordo```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:~
### Grafico```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

Misurare le cose nell'emulatore

Ognuno di questi stampa una tabella che puoi verificare rispetto alla documentazione:```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:~
### Leggere uno schema```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

Riprodurre tutto

L'intera catena di build è deterministica — questi comandi ricostruiscono il firmware e l'immagine EEPROM byte per byte:```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:~
---

## Riscrittura della EPROM

> **Conserva la tua EPROM originale.** Leggila due volte, confronta i dump,
> metti il chip in un cassetto. Tutto ciò che segue è reversibile solo se
> la possiedi ancora.

Il firmware verifica una somma di byte sull'intervallo occupato all'accensione e
la confronta con il byte immediatamente successivo. Se sbagli ottieni
`Err 1` e un ciclo infinito — lo strumento non si avvia. `romfix.py`
calcola e inserisce il valore corretto; ogni script di build qui presente lo
richiama già.

| Versione | Intervallo | Byte di checksum | Valore |
|---|---|---|---|
| V1.3 | `0000h–AC6Fh` | `AC70h` | `F2h` |
| V1.5 | `0000h–B3C9h` | `B3CAh` | `99h` |

Due cose imparate a proprie spese su hardware reale:

- La EEPROM arbitraria necessita che i suoi **byte di identità** vengano ricalcolati, altrimenti
  ottieni `Err 8` a ogni avvio e la sorgente ARB non può essere selezionata.
- Se ARB si comporta in modo strano dopo un flash, verifica che il pin 28 dello zoccolo
  sia inserito correttamente prima di sospettare dell'immagine.

---

## Quanto è affidabile tutto questo?

Tutto ciò che è contrassegnato come verificato è stato confermato richiamando le routine
originali nell'emulatore su diversi punti campione, di solito con verifica incrociata
anche rispetto al listing o allo schema elettrico.

Dove le cose sono andate storte, è scritto piuttosto che corretto silenziosamente:

- **Tre bug dell'emulatore** (`ACALL` come `AJMP`, flag AC mancante, interrupt
  della tastiera raddoppiato) erano attivi durante la fase centrale del progetto.
  Tutti i risultati interessati sono stati rimisurati in seguito — la bitmap del display
  è risultata identica, l'assegnazione degli strobe corrispondeva al manuale di servizio,
  e la sezione 16 si è rivelata avere due strobe mancanti.
- **Un'immagine NVRAM sintetica** che non è mai stata letta da uno strumento
  reale ha falsificato due risultati, incluso "la manopola rotativa funziona solo
  in una direzione". La correzione è stata fornire al firmware una NVRAM non valida e
  lasciargli scrivere il proprio stato di fabbrica.
- **Stati RAM impostati manualmente** producono configurazioni che lo strumento non
  assume mai. Due volte questo ha prodotto conclusioni errate, una volta un crash nella
  tabella dei comandi.
- **`core.js` conta un'istruzione per ciclo**, non cicli macchina. Va bene
  per l'ordinamento, sbagliato per la temporizzazione assoluta — le affermazioni sulla temporizzazione qui provengono
  dal data sheet MCS-51.

Tutto ciò che è un'ipotesi piuttosto che una misurazione lo dichiara nel
testo.

---

## Ancora aperto

- **36 dei 128 bit di stato** necessitano di uno stimolo al di fuori dei sei profili
  operativi — self-test, percorsi di errore, traffico di interfaccia.
- **Campi NVRAM dall'offset 0Dh in poi.** Il layout fino a lì è misurato
  (`NVRAM offset + 4Bh = indirizzo RAM`), il segno di spunta è compreso
  (somma di byte, valore iniziale `AAh`, 25 byte).
- **Quale comando arbitrario raggiunge quale dei 13 sotto-blocchi** nella
  regione `8871h`. Esistono solo quattro confronti diretti di token; il resto
  si dirama su test di bit.
- **Se un comando remoto può aggirare il controllo dell'intervallo dei parametri.**
- **Le routine di caricamento della forma d'onda** sono la dipendenza rimanente più difficile per
  una reimplementazione completa — senza di esse non c'è segnale di uscita.
- **Come il PM5139 produca 20 MHz dallo stesso clock** del suo fratello da 10 MHz.
  La catena implica che il suo passa-basso si trovi a 10 MHz invece che a 5 MHz,
  ma questo richiede un manuale del PM5139 per essere confermato.

Se possiedi uno di questi strumenti, due cose aiuterebbero molto: un
**manuale di servizio del PM5139**, e dump da **altre versioni del firmware**
(una V1.4 potrebbe o meno esistere).

---

## Fonti

- **`pm5138A_service_manual.pdf`** — la fonte hardware principale. 176
  pagine, OCRizzate; il testo corrente si legge bene con `pdftotext -layout`, gli
  schemi elettrici devono essere renderizzati come immagini. Le pagine da 4-3 a 4-28 mancano
  dalla scansione.
- **Manuale utente del PM5139** (Fluke) — scansione trilingue senza livello di testo;
  il capitolo 3.7.4.6 documenta i comandi arbitrari. Vale la pena OCRizzarlo da soli
  — la parte in inglese è alle pagine PDF 13–145.
- **Manuale utente del PM5136** — utile come contro-verifica: i suoi numeri di errore
  e la lista dei comandi mostrano quali parametri mancano al modello più piccolo, il che
  ha confermato indipendentemente l'ordinamento dei parametri nella ROM.
- **Data sheet di tutti e tre i modelli** — limiti operativi per forma d'onda.

I manuali sono documenti di terze parti e **non sono ridistribuiti in questo
repository**. Sono reperibili online.

---

## Licenza e utilizzo

Due tipi di materiale, con termini diversi — vedi [LICENSE](https://github.com/doctormord/philips-pm-5139-5138a-5136-firmware-project/blob/main/LICENSE) per
l'ambito esatto:

- **Il lavoro di reverse engineering è MIT.** Documentazione, strumenti, entrambi
  gli emulatori, tabelle dei simboli, annotazioni, le forme d'onda generate e i
  grafici. Usali come preferisci.
- **Il firmware Philips non è nostro da licenziare.** Le immagini ROM, i
  dump dei chip di fabbrica, i listing del disassemblaggio e il simulatore browser
  (che incorpora l'immagine V1.3) riproducono o derivano dal lavoro di Philips.
  Sono qui come oggetto di studio, per l'interoperabilità, la riparazione e la
  documentazione di strumenti fuori supporto da decenni.
  Dove è mescolato il nostro lavoro — le annotazioni, la forma d'onda corretta
  nella V2.0 — solo quel contributo è MIT.

Se detieni i diritti sul firmware originale e hai obiezioni, apri una issue e
verrà rimosso.

Se usi qualcosa di questo, un link di ritorno è apprezzato. Se trovi un errore,
apri una issue — ogni affermazione qui nomina l'indirizzo o la misurazione su cui si basa,
quindi dovrebbe essere falsificabile.
Scarica lo strumento
Disassemblaggiocompleto per entrambe le versioni, ~23 000 righe, con riferimenti incrociati
Listing annotato147 routine denominate, 145 commenti di intestazione, 3 826 righe annotate
Documentazione35 sezioni, 4 600 righe, ogni affermazione documentata
Percorso del segnalefrequenza, ampiezza, offset, AM, FM, burst, simmetria, sweep — tutto calcolato e verificato rispetto al codice originale
Hardwaretutti i 10 strobe, il bus C, l'I²C con ogni partecipante, le porte, la tastiera, la manopola rotativa, la bitmap del display
Bit di stato75 su 128 con un effetto documentato
Diff tra versioniV1.3 vs V1.5 è identica strutturalmente al 91,4 %; ogni modifica nominata
Emulatoriuno in Python, uno in JavaScript (~8 M istruzioni/s), più un simulatore da browser in singolo file
Firmware nostroV2.0 — un difetto di fabbrica corretto, checksum gestito, verificato nell'emulatore e su hardware reale
PosizioneTipoFunzione
D301PCB80C652core 8051 con I²C hardware, 12 MHz
D30627512EPROM di programma — la V1.3 occupa 0000h–AC70h
D310X28C64EEPROM arbitraria sul bus MOVX
D305PCF8570256 byte di NVRAM con batteria tampone su I²C (A0h)
D304-APCF8576driver LCD su I²C (70h), buffer da 20 byte
D302-ASAA3007encoder di tastiera, codificato in larghezza di impulso su una singola linea
D30774HCT4514decodificatore di strobe — il numero di strobe sono i bit di indirizzo A8…A11
mkdoom.pymkpoly.py
Vociunadiverse contemporaneamente
Forma d'ondaquella caricatala propria tabella di accordi
Livellocome l'ha lasciato il pannello frontaleimpostato esplicitamente, 11,6 Vpp misurati
ROM usata182 byte2617 con il riff integrato, 6185 da una traccia MIDI