Skip to content
KitploitKITPLOIT
HerramientasBlog
Enviar
HerramientasBlog
Enviar

¡Herramientas de Hacking, PenTest y Ciberseguridad para tu Arsenal de Seguridad!

Kitploit es un directorio de herramientas de hacking, ciberseguridad y pentesting. Descubre las últimas actualizaciones de proyectos para encontrar vulnerabilidades, analizar sistemas, automatizar pruebas y fortalecer tu seguridad.

··Feeds·Contacto·Privacidad·© 2026 Kitploit

Directorio de Herramientas

Categorías

Ver todas las categorías
Loading categories
Philips-PM-5139-5138A-5136-Firmware-Project — Ingeniería inversa del firmware de los generadores de funciones Philips PM5139 / PM5138A / PM5136: emuladores 8051 utilizados como instrumentos de medición, 35 secciones de hardware documentado y un firmware V2.0 corregido | Kitploit
Herramientas/GitHubGitHub/doctormord/philips-pm-5139-5138a-5136-firmware-project
Seguridad de Sistemas EmbebidosAnálisis EstáticoAnálisis Dinámico (Sandboxing)Ingeniería InversaSeguridad de HardwareAnálisis de BinariosPapers e InvestigaciónAprendizaje y EducaciónAnálisis de Firmware

Más Populares

Ver todos →

Descubre las herramientas más usadas por nuestra comunidad.

Explora todas las herramientas

Explora nuestra colección de herramientas

Ver todas las herramientas →
Compartir
GitHubdoctormord/philips-pm-5139-5138a-5136-firmware-project

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

Ingeniería inversa del firmware de los generadores de funciones Philips PM5139 / PM5138A / PM5136: emuladores 8051 utilizados como instrumentos de medición, 35 secciones de hardware documentado y un firmware V2.0 corregido

Ver Repositorio
214hace 1 díaAún no revisado

Philips PM5139 — Ingeniería inversa del firmware

Un generador de funciones de 20 MHz de alrededor de 1994, desmontado en software: dos volcados de EPROM, un emulador de 8051 usado como instrumento de medición, y 35 secciones de documentación donde cada afirmación está respaldada por una dirección de listado, una medición del emulador o el esquemático.

Al final de todo hay un firmware V2.0 que corrige un defecto que Philips envió, seis formas de onda arbitrarias propias, y un simulador de navegador que ejecuta la ROM original instrucción por instrucción.

Todas las tablas de formas de onda en la ROM V1.3

Todas las tablas de formas de onda en la EPROM del programa, graficadas directamente desde el binario. Abajo a la derecha está la que inició la parte más interesante de este proyecto.


Contenido

  • Qué es esto
  • Resultados de un vistazo
  • El instrumento
  • El método: el emulador es el instrumento de medición
  • El camino hasta aquí
  • Las partes buenas
  • Firmware V2.0 — qué hay de nuevo
  • El easter egg
  • Y luego resultó ser polifónico
  • Seis formas de onda arbitrarias propias
  • El simulador de navegador
  • Estructura del repositorio
  • Uso de las herramientas
  • Reproducir todo
  • Regrabarlo
  • ¿Qué tan confiable es esto?
  • Aún abierto
  • Fuentes

Qué es esto

El Philips PM5139 es el modelo tope de 20 MHz de una familia de tres instrumentos (PM5136 / PM5138A / PM5139). En su interior hay una PCB80C652 — un núcleo 8051 con I²C por hardware — una EPROM de programa 27512, y seis ensamblajes analógicos colgando de un bus serie.

No existe manual de servicio del PM5139. La gente ha estado buscando uno en foros desde 2010. Lo que existe es el manual del PM5138A, su modelo hermano de 10 MHz, que es internamente casi idéntico.

Así que este proyecto comenzó desde el otro extremo: volcar la EPROM, y deducir qué hace el código hasta que el instrumento se entienda lo suficientemente bien como para modificarlo.

Había dos versiones de firmware disponibles, V1.3 y V1.5, ambas volcados M27512 de 64 KiB.


Resultados de un vistazo


El instrumento

El lado analógico es un bus C serie: el UART del 8051 funciona en modo registro de desplazamiento, TXD es el reloj, RXD los datos, y un strobe decide cuál de los diez registros de desplazamiento captura los bytes. MOV DPH,#8nh seguido de MOVX @DPTR,A dispara el strobe n. Esa única línea es la clave de toda la sección analógica.


El método: el emulador es el instrumento de medición

Esta es la parte que vale la pena robar para tu propio proyecto.

Leer un binario de 8051 de 44 KB a ojo te lleva quizás un tercio del camino. Todo lo que vino después surgió de ejecutar el código original y observar qué sale:

---```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:~
Varía la entrada, lee la salida, compruébala contra la hipótesis. Eso
funcionó para frecuencia, amplitud, offset, profundidad de AM, desviación de FM, número de ráfagas, simetría y ambas características de barrido. Cada fórmula en la documentación viene con los puntos de muestra sobre los que se verificó.

Tres refinamientos lo hicieron realmente productivo:

**Vigila el bus, no la pantalla.** La sección 15 mide qué le hace un bit de estado al búfer de pantalla, y 74 de 128 bits parecen no hacer nada. Pero muchos de ellos no controlan la pantalla, controlan los *conjuntos analógicos* — y esos solo son visibles como telegramas en el bus C. Registrar `MOV SBUF,…` y el `MOVX @DPTR` de terminación elevó el recuento de bits documentados de 54 a 75.

**Pulsa teclas, no manipules la RAM.** Establecer un byte de RAM a mano produce estados que el instrumento nunca adopta. Eso nos costó dos hallazgos erróneos y un fallo en la tabla de comandos. Inyectar códigos de tecla reales a través del SAA3007 emulado da estados a los que el firmware realmente llega — y fue un barrido por fuerza bruta sobre los 256 códigos de tecla lo que reveló qué tecla activa qué manejador.

**Sospecha primero de tu propio emulador.** Tres errores en nuestro núcleo produjeron un comportamiento "inexplicable" del firmware: `ACALL` ejecutado como `AJMP`, un flag de acarreo auxiliar ausente (por lo que `DA A` se comportaba mal y el firmware parecía contar en binario), y una interrupción de teclado duplicada. Cada hallazgo de ese periodo se volvió a medir después.

---

## El camino hasta aquí

**Primero lo estático.** Un desensamblador con una tabla de opcodes completa, luego descenso recursivo con heurísticas de tablas de salto. Eso produjo 30 508 bytes de código y dejó 13 637 bytes sin contabilizar.

**Luego lo dinámico.** Una ejecución de traza — arranque en frío, las 23 teclas del panel frontal, ambas direcciones del mando giratorio, todos los modos de operación, 86 millones de ciclos — marcando cada dirección que realmente se ejecutó. Contrastada con el análisis estático, encontró exactamente **una** zona que el descenso había pasado por alto, y 10 686 de los bytes sin explicar resultaron ser cinco bloques de tablas conocidos.

**Luego los esquemas.** El OCR del manual de servicio es inútil para los esquemas, pero las imágenes de página a 400 dpi son excelentes. Cortadas en teselas solapadas, son legibles hasta los números de pin. Se leyeron seis hojas de esta forma — y donde cinco pistas paralelas discurren a 90 píxeles de distancia, la inspección visual se sustituyó por un script (`lines.py`) que extrae los segmentos de línea del mapa de bits.

**Luego los dos chips que se extrajeron.** Se leyeron un 27C64 etiquetado "SINUS 1.1" y un X28C64. Ambos se colocaron en el esquema y se decodificó su contenido.

**Luego el diff de versiones.** Tokenizar ambos ROMs (distancias de salto relativas en lugar de destinos absolutos) y ejecutar `SequenceMatcher` sobre ellos da un mapeo de direcciones que sobrevive al movimiento de código — así es como los símbolos de la V1.3 se trasladan a la V1.5.

---

## Las partes buenas

### Philips envió una forma de onda con ruido

Las tres curvas arbitrarias integradas residen en `A047h`, `A447h` y `A847h`. La tercera tiene la misma forma que una tabla que ya está en la ROM en forma computada — pero con **563 cambios de dirección frente a 13**, y una desviación estándar de 4,1 LSB.

Se muestreó de una fuente analógica en lugar de calcularse. La media de la desviación es cero, solo dos de 1024 puntos se desvían más de 10 — esta no es una forma de onda diferente, es la *misma* forma de onda con ruido encima.

### Esa tabla es una escalera de niveles de 30 dB

La versión limpia se describió en un borrador anterior como "una sinusoide con diez profundidades de AM", que era una lectura visual del gráfico, no algo que diga el código. Calculados hasta el final, los 1024 puntos se dividen en diez arcos de sinusoide cuyas extensiones son```
255  171  120  80  56  38  26  17  12  8

una serie geométrica con razón 0.681 = 10^(−1/6), es decir, 3.33 dB por paso y 30.1 dB en total. Un modelo de reducción a la mitad se desvía hasta 56, un modelo de 3 dB se desvía en 10. Es una escala logarítmica de niveles — un patrón de prueba de amplitud o atenuación.

STR9 envía 16 bits como dos telegramas de 1 byte

El controlador de amplitud tiene dos registros de desplazamiento en un solo strobe, pero el firmware solo envía un byte por telegrama. El esquema lo explica: los dos 4094 están en cascada a través de QS' (pin 10), con el pin 9 sin usar — y los telegramas vienen en pares, con ~42 000 ciclos de separación y millones de ciclos de silencio entre pares. El byte enviado primero se empuja hacia el segundo registro.

El mismo patrón de cascada apareció en cada ensamblaje con más de un registro de desplazamiento — incluyendo un caso donde la cadena cruza un límite de ensamblaje a través de una línea llamada E.

El atenuador no es un cálculo

Cinco bits en el telegrama STR9 controlan relés directamente: S1 conmuta el rango del generador de CC, S2…S5 los relés del atenuador. 20 dB (for 40dB), 20 dB, 50/600 ohms — está impreso en el esquema. No hay umbrales que calcular.

Un manejador escondido a plena vista

La tabla de saltos en 0301h se lee con JMP @A+DPTR. La entrada 15 aterriza en 0301h + 30 = 031Fh — y allí, en lugar del habitual AJMP, se encuentra el manejador mismo, en línea, ahorrando un salto. Ninguna instrucción de salto en la ROM apunta a él, por lo que el análisis estático lo perdió. Es el manejador de DIAL LOCK, y solo la traza dinámica lo encontró.

Seis ranuras arbitrarias, no veinticuatro

La hoja de datos promete 24 memorias de forma de onda. El directorio en la EEPROM dice seis. La aritmética lo resuelve:``` 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:~
El rango de lectura medido del firmware es `0100h–1EFFh` — seis curvas al byte. El instrumento fue construido con el chip pequeño.

### Código muerto hablando con un dispositivo que no está ahí

186 bytes en `9AFFh` hacen tráfico I²C con la dirección `5Ah` — una dirección que no aparece en ningún otro lugar. En **ambas** versiones del firmware, ningún salto apunta a ella. Se encuentra en el mismo bloque de tipo de dispositivo que la tarjeta de interfaz en `5Eh`, solo con bits de banco diferentes, y envía el búfer de recepción y los registros aritméticos en dos telegramas de diez bytes. Parece un diagnóstico de fábrica para un dispositivo que nunca se comercializó.

### No se puede ejecutar código desde la EEPROM arbitraria

Una idea obvia — poner código en una ranura de forma de onda arbitraria y saltar a él — está muerta al llegar. El 8051 es Harvard: las instrucciones llegan a través de `/PSEN` desde la EPROM de programa, los datos a través de `/RD` desde la EEPROM arbitraria. No está bloqueado; el cable simplemente no está ahí.

### Y la codificación de frecuencia, finalmente

La fila de dígitos de la pantalla vive en `3Eh–43h` de la imagen enviada al PCF8576, todas las posiciones comparten una codificación de segmento, y el byte `43h` cambia de kHz a MHz entre la década 7 y 8. De eso:```
f = M · 10^(D−8) kHz

Tres secuencias de escalonamiento de frecuencia medidas en el instrumento real son reproducidas exactamente por esto — incluida la que se detiene antes de tiempo porque la mantisa 2500 significaría 25 MHz, por encima del límite.


Firmware V2.0 — lo nuevo

Curva arbitraria 3: V1.5 frente a V2.0

A la izquierda la curva de fábrica, a la derecha la corregida. Abajo a la izquierda está la desviación respecto a la tabla calculada — esa banda de ±5 LSB es lo que dejó atrás una fuente analógica muestreada.

mkv20.py construye V2.0 a partir de V1.5 (o V1.3). Encuentra cada dirección por firma en lugar de codificarlas de forma fija, por lo que el mismo script funciona en ambas versiones de origen:

  1. Curva arbitraria 3 reemplazada por la tabla calculada limpia. Ambas son 1024 bytes de 8 bits, por lo que el intercambio es neutro en tamaño y no toca ningún código. 863 de 1024 bytes cambian.
  2. Curva arbitraria 2 reemplazada por un chirp logarítmico (1 → 40 periodos). Difería de la curva 1 en exactamente dos bytes — un pulso de aguja extra — por lo que era redundante.
  3. Identificación de versión en *IDN?: PHILIPS,PM5139,0,V2.0/0000.
  4. Versión en la pantalla: la secuencia de reinicio escribe dos celdas de pantalla, y esos bytes ahora deletrean 2.0 en la codificación de segmento medida.
  5. Checksum recalculado y almacenado donde el firmware lo espera.

Todo lo demás queda intacto. Se encontraron tres rarezas más y se dejaron deliberadamente sin tocar — una escritura a un SFR inexistente (inofensiva, en ambas versiones), el bloque muerto 5Ah, y tres bits de estado que se comprueban pero nunca se establecen. Parchearlos no cambia ningún comportamiento y solo añade riesgo.

M27512_PM5139_V20.bin es exactamente esto y nada más. La melodía de abajo es un paso de compilación separado y opcional.

Verificado: el arranque en frío en el emulador produce el mismo búfer de pantalla y los mismos flags que V1.5, el checksum se valida, y la compilación es reproducible byte a byte. Se ha flasheado y funciona en un PM5139 real — la pantalla muestra 2.0 y las seis ranuras arbitrarias funcionan.


El easter egg

Dado que hay 19 509 bytes sin usar detrás del checksum en V1.5, y la ruta de frecuencia toma una frecuencia de nota como tres bytes BCD, el instrumento puede reproducir música a través de su propia salida.

La codificación es agradablemente directa — década 3, luego la frecuencia en 0.01 Hz como BCD, así que 82.41 Hz es 30 82 41. Cuatro bytes por nota: tres para el tono, uno para la duración.

La parte interesante es el disparador. El menú de diagnóstico (mantener LOCAL mientras se enciende) tiene una tabla de saltos con ocho entradas, pero el bucle del menú cuenta 0Bh solo de 1 a 7 — por lo que la octava entrada es inalcanzable. También es redundante: salta al inicio del menú, al que se llega desde otros dos lugares de todos modos.

Así que todo el enganche son dos bytes:``` 5B94h table entry 8: LJMP 5B45h -> LJMP 5B62h count limit: 08h -> 09h

root@kitploit:~
No se pierde ninguna autoprueba, no se reubica ninguna tabla y no
aparece ningún elemento de menú muerto. Mantén pulsado LOCAL, enciende,
deja que el menú cuente hasta 8, pulsa una tecla.

La temporización proviene de la hoja de datos del MCS-51. Ambos emuladores ahora cuentan
ciclos de máquina junto con las instrucciones (`mcyc`, desde `mcs51.CYCLES`), y
ejecutar paso a paso el bucle de espera mide **1009 µs** por unidad — 106,95 ms por
semicorchea a 140 BPM, 0,2 % fuera del objetivo. La cifra solía ser un cálculo
manual de 1006 µs al que se le habían escapado dos instrucciones.

`mkdoom.py` también puede convertir un archivo MIDI. Hay que elegir una voz
(nota más aguda, nota más grave, o un canal) y fusionar las secciones de menos de
~25 ms — por debajo de eso una nota grave no alcanza una oscilación completa
y solo se oye un clic.

---

## Y entonces resultó ser polifónico

La melodía anterior es una sola voz. No tiene por qué serlo, y la razón es
una frase del manual de servicio que habíamos pasado por alto:

> Durante la generación de señal, las distintas muestras de amplitud de señal se leen
> desde la RAM. Si la forma de onda básica se altera [...] las muestras de amplitud
> correspondientes se **cargan en la RAM por la CPU**.

El PM5139 es un **DDS de tabla de ondas de 1024 puntos**. El TWS no es un generador
de triángulo en ningún sentido ingenuo — es un acumulador de fase que produce
direcciones de lectura 0…1023 para una RAM rápida en la unidad 4, y esa RAM la llena
la CPU a través del bus C. Seno, cuadrada, diente de sierra y arbitraria son todas el
mismo mecanismo: una tabla.

Y la tabla contiene exactamente **un periodo de la salida**. Así que una tabla construida
a partir de una *suma de armónicos* sigue siendo periódica en sus 1024 puntos, y
suena como un acorde. No un arpegio, no un truco de modulación — varias notas
sonando a la vez a los 20 Vpp completos, con la CPU sin hacer nada en absoluto
mientras suenan. Como los parciales deben ser múltiplos enteros de la
frecuencia de la tabla, los intervalos salen en entonación justa, que para un
acorde sostenido es la mejor afinación de todos modos.

`M27512_PM5139_V20_chords.bin` está en el repositorio listo para grabar — el
riff, en acordes, con la envolvente. Para construirlo tú mismo, o para usar un
archivo MIDI propio en lugar del riff incorporado:```
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 construye las tablas — power (2:3:4), major (4:5:6), minor (10:12:15), dom7 (4:5:6:7) y cinco más. mkpoly.py coloca una en la ROM libre junto con la melodía y engancha la misma entrada de menú muerta. Carga el acorde una vez, luego reproduce la melodía solo reajustando, lo que transpone todo el acorde en paralelo. Cada nota del riff de E1M1 se convierte en un power chord — que es de lo que está hecho ese riff en el original.

Arquitectónicamente esto es un PPG Wave: un contador recorriendo una forma de onda de un solo ciclo, directo a un DAC. El truco del acorde es el que usaban los trackers de Amiga — poner el acorde en la forma de onda para que una voz toque tres notas en lugar de gastar tres canales en ello. Un C64 tiene que hacer arpegio en su lugar, porque el SID no tiene wavetable escribible.

Ni siquiera necesitas una EPROM para los acordes. Las mismas tablas caben en la EEPROM arbitraria, así que python3 mkarb.py --chords te da seis acordes seleccionables desde el panel frontal con el firmware intacto.

Hay dos reproductores y una imagen lleva uno u otro, ya que ambos enganchan la misma entrada de menú:

Dos mediciones dieron forma a ese diseño:

  • El formato de descarga lleva diez bits por punto, no doce: solo aparecen cuatro bytes bajos distintos (00h 44h 88h CCh) y todo valor reconstruido es múltiplo de cuatro. La RAM de forma de onda es de doce bits de ancho, pero el bus maneja diez — exactamente lo que almacena el formato ARB, así que Philips no desperdició nada ahí.
  • Una recarga completa de tabla es de 32 a 40 ms con la salida en silencio, y no hay segunda página de búfer — RAM_PAGE en 1D62h, que suena a una, construye su palabra a partir de la frecuencia. Así que la armonía vive en la tabla y la melodía en la palabra de frecuencia; nada se recarga mientras la música suena.

El emulador no modela la RAM de forma de onda, así que el cargador se verifica por construcción: polytest.js registra lo que realmente llega al bus y compara los 1024 puntos contra lo que generó mkchord.py.

Hicieron falta cinco EPROMs para llegar ahí, y el emulador solo pudo llevarnos parte del camino: modela la CPU y el bus pero no la RAM de forma de onda, así que todo lo que puede confirmar es que salen los mismos bytes que envía el firmware. Eso es necesario y no suficiente. Tres cosas tuvieron que resolverse en el propio instrumento:

  • El orden de bytes. Dos bytes por punto, byte alto primero. Inferirlo a partir de la propia descarga del firmware daba la respuesta opuesta y la tabla salía como ruido. Lo que lo resolvió fue una EPROM con seis patrones de prueba — una línea plana, una rampa, la misma rampa con los bytes de cada punto intercambiados, y tres más — y una mirada al osciloscopio. La rampa intercambiada era la limpia.

  • Un cambio de forma de onda son diecinueve telegramas, no los tres que enviaba el primer reproductor. El que importa es una escritura de dos bytes que pone la RAM en modo escritura; sin ella 2048 bytes salen por el bus y no llegan a ningún lado.

  • El nivel de salida. El atenuador son dos etapas de relé de 20 dB separadas en un byte, la tabla ROM para ellas se lee invertida respecto a como había sido documentada (son bits de bypass), y el DAC de nivel es de siete bits, no ocho — se desborda en 80h, así que un ajuste "más fuerte" producía silencio. Ese tomó una matriz de unas treinta combinaciones en una sola imagen, usando la frecuencia de salida como número de prueba para que la propia lectura del osciloscopio diga qué combinación está activa.``` 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:~
---

## Seis formas de onda arbitrarias propias

![Las seis ranuras arbitrarias](https://assets.kitploit.com/production/public/readmes/54732/9fa22b75c34e4aaa83df1b2afd284353e019cb65f181a83e5cdeceb57ea32937/4b15ae02ae1bec49f3d1b6ab79c480cd3a73b10818c41a8b04be57ab6f691ef7-display-v1.webp)

`D310_image_V20.bin` llena cada ranura de la EEPROM — grabar el chip
merece la pena una vez:

| Ranura | Forma de onda | Vpp | Para |
|---|---|---|---|
| 1 | sinc, 8 lóbulos | 12.17 | limitación de banda, sobreimpulso |
| 2 | ringing, Q≈6 | 17.81 | comportamiento de establecimiento |
| 3 | ECG | 12.80 | demo |
| 4 | escalera, 16 escalones bipolar | 20.00 | linealidad, resolución |
| 5 | seno rectificado | 10.00 | como en el original, pero calculado |
| 6 | multitono, 5 tonos | 20.00 | intermodulación |

Dos detalles que importan y son fáciles de equivocar:

**Centrar en cero supera a estirar.** El movimiento obvio es estirar cada
curva a lo largo de todo el rango de valores. No lo hagas: el offset DC
del instrumento proviene de una ruta analógica separada y suma un voltaje
*fijo*, mientras que el contenido DC de una curva asimétrica estirada escala
*con la amplitud*. Tendrías que reajustar el offset cada vez que cambias el
nivel. Poner el cero natural de la forma de onda en el cero del convertidor
cuesta de 0.2 a 1 bit — frente a los 16 LSB de ruido que la ruta analógica
original ya aporta. No es un coste real.

**Escala en punto flotante, redondea una vez.** Redondear primero y estirar
después da 1.0–1.5 pasos de cuantización de error; escalar en float
y redondear una vez da el óptimo de 0.5.

El directorio necesita un byte de identidad por curva (un checksum de los 1280
bytes de la curva, valor inicial `55h`) y el mín/máx como valores de 10 bits
alineados a la izquierda por seis bits. Si el byte de identidad es incorrecto, el instrumento
muestra **Err 8** y rechaza la fuente arbitraria — que es exactamente lo que
ocurrió en el primer flash real.

---

## El simulador de navegador

`PM5139_Simulator.html` es un único archivo autocontenido — sin paso de compilación,
sin dependencias, sin red. Ábrelo y el firmware original V1.3 arranca
frente a ti.

El núcleo 8051 ejecuta el código real. Los temporizadores, las interrupciones, el C-bus y I²C
están emulados; la pantalla se decodifica del flujo de datos real del PCF8576,
y las teclas generan la forma de onda SAA3007 codificada por ancho de pulso en P3.3. La
RAM respaldada por batería está precargada y la EEPROM arbitraria se genera al
arranque y es verificada por el propio firmware.

Un arranque en frío tarda unos 9 millones de instrucciones, así que dale un segundo.

---

## Estructura del repositorio```
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 …

Uso de las herramientas

Python 3 y Node son todo lo que necesitas. matplotlib para los gráficos, pillow y numpy solo para lines.py.

Observar el 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:~
### Compilación 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

Construir la 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:~
### Añadir una melodía```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 parchea una imagen una vez y se niega a hacerlo dos veces — construye una V2.0 nueva con mkv20.py si quieres empezar de nuevo.

Reproducir un acorde```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:~
### Gráfico```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

Medir cosas en el emulador

Cada uno de estos imprime una tabla que puedes verificar contra la documentación:```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:~
### Leer un esquema```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

Reproducir todo

Toda la cadena de compilación es determinista — estos comandos reconstruyen el firmware y la imagen de la EEPROM byte a 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:~
---

## Volver a grabarlo

> **Conserva tu EPROM original.** Léela dos veces, compara los volcados, guarda
> el chip en un cajón. Todo lo de aquí es reversible solo si todavía la
> tienes.

El firmware verifica una suma de bytes sobre el rango ocupado al encenderse y
la compara con el byte inmediatamente posterior. Si te equivocas obtienes
`Err 1` y un bucle infinito — el instrumento no arranca. `romfix.py`
calcula e inserta el valor correcto; todos los scripts de compilación de aquí ya lo
invocan.

| Versión | Rango | Byte de checksum | Valor |
|---|---|---|---|
| V1.3 | `0000h–AC6Fh` | `AC70h` | `F2h` |
| V1.5 | `0000h–B3C9h` | `B3CAh` | `99h` |

Dos cosas aprendidas a la mala en hardware real:

- La EEPROM arbitraria necesita que se recalculen sus **bytes de identidad**, o
  obtienes `Err 8` en cada arranque y la fuente ARB no se puede seleccionar.
- Si ARB se comporta de forma extraña después de un flasheo, verifica que el pin 28 del zócalo
  esté bien asentado antes de sospechar de la imagen.

---

## ¿Qué tan confiable es esto?

Todo lo marcado como verificado se confirmó llamando a las rutinas
originales en el emulador sobre varios puntos de muestra, generalmente contrastado
también con el listado o el esquemático.

Donde las cosas salieron mal, queda escrito en lugar de corregirse silenciosamente:

- **Tres errores del emulador** (`ACALL` como `AJMP`, flag AC faltante, interrupción
  de teclado duplicada) estuvieron activos durante la fase media del proyecto.
  Todos los hallazgos afectados se volvieron a medir después — el mapa de bits de la pantalla
  volvió idéntico, la asignación de strobes coincidió con el manual de servicio,
  y la sección 16 resultó tener dos strobes faltantes.
- **Una imagen NVRAM sintética** que nunca se leyó de un instrumento
  real falsificó dos hallazgos, incluido "la perilla rotativa solo funciona
  en una dirección". La solución fue entregarle al firmware una NVRAM inválida y
  dejar que escribiera su propio estado de fábrica.
- **Estados de RAM establecidos a mano** producen configuraciones que el instrumento nunca
  adopta. Dos veces esto produjo conclusiones erróneas, una vez un crash en la
  tabla de comandos.
- **`core.js` cuenta una instrucción por ciclo**, no ciclos de máquina. Está bien
  para el orden, mal para el timing absoluto — las afirmaciones de timing aquí provienen de
  la hoja de datos del MCS-51.

Todo lo que es una suposición en lugar de una medición lo dice en el
texto.

---

## Aún abierto

- **36 de 128 bits de estado** necesitan un estímulo fuera de los seis perfiles
  operativos — autotest, rutas de error, tráfico de interfaz.
- **Campos de NVRAM desde el offset 0Dh en adelante.** El diseño hasta ahí está medido
  (`NVRAM offset + 4Bh = dirección RAM`), la marca de verificación se entiende
  (suma de bytes, valor inicial `AAh`, 25 bytes).
- **Qué comando arbitrario llega a cuál de los 13 sub-bloques** en la
  región `8871h`. Solo existen cuatro comparaciones directas de tokens; el resto
  ramifica según pruebas de bits.
- **Si un comando remoto puede saltarse la verificación de rango de parámetros.**
- **Las rutinas de carga de formas de onda** son la dependencia restante más difícil para
  una reimplementación completa — sin ellas no hay señal de salida.
- **Cómo el PM5139 genera 20 MHz a partir del mismo reloj** que su hermano
  de 10 MHz. La cadena implica que su paso bajo está en 10 MHz en lugar de 5 MHz,
  pero eso necesita un manual del PM5139 para confirmarse.

Si posees uno de estos instrumentos, dos cosas ayudarían mucho: un
**manual de servicio del PM5139**, y volcados de **otras versiones de firmware**
(puede que exista o no una V1.4).

---

## Fuentes

- **`pm5138A_service_manual.pdf`** — la fuente principal de hardware. 176
  páginas, con OCR; el texto corrido se lee limpiamente con `pdftotext -layout`, los
  esquemáticos tienen que renderizarse como imágenes. Las páginas 4-3 a 4-28 faltan
  en el escaneo.
- **Manual de usuario del PM5139** (Fluke) — escaneo trilingüe sin capa de texto;
  el capítulo 3.7.4.6 documenta los comandos arbitrarios. Vale la pena hacerle OCR tú mismo
  — la parte en inglés son las páginas 13–145 del PDF.
- **Manual de usuario del PM5136** — útil como contraste: sus números de error
  y su lista de comandos muestran qué parámetros le faltan al modelo más pequeño, lo que
  confirmó de forma independiente el orden de parámetros en la ROM.
- **Hoja de datos de los tres modelos** — límites operativos por forma de onda.

Los manuales son documentos de terceros y **no se redistribuyen en este
repositorio**. Se pueden encontrar en línea.

---

## Licencia y uso

Dos tipos de material, bajo términos diferentes — consulta [LICENSE](https://github.com/doctormord/philips-pm-5139-5138a-5136-firmware-project/blob/main/LICENSE) para
el alcance exacto:

- **El trabajo de ingeniería inversa es MIT.** Documentación, herramientas, ambos
  emuladores, tablas de símbolos, anotaciones, las formas de onda generadas y las
  gráficas. Úsalo como quieras.
- **El firmware de Philips no es nuestro para licenciar.** Las imágenes ROM, los
  volcados de chips de fábrica, los listados de desensamblado y el simulador de navegador
  (que incorpora la imagen V1.3) reproducen o derivan del trabajo de Philips.
  Están aquí como objeto de estudio, para interoperabilidad, reparación y
  documentación de instrumentos que llevan décadas sin soporte.
  Donde se mezcla nuestro propio trabajo — las anotaciones, la forma de onda
  corregida en V2.0 — solo esa contribución es MIT.

Si posees derechos sobre el firmware original y te opones, abre un issue y
se eliminará.

Si usas algo de esto, se agradece un enlace de vuelta. Si encuentras un error,
abre un issue — cada afirmación aquí nombra la dirección o medición en la que se
basa, así que debería ser falsable.
Descargar herramienta
Desensambladocompleto para ambas versiones, ~23 000 líneas, con referencias cruzadas
Listado anotado147 rutinas nombradas, 145 comentarios de encabezado, 3 826 líneas anotadas
Documentación35 secciones, 4 600 líneas, cada afirmación con fuente
Ruta de señalfrecuencia, amplitud, offset, AM, FM, burst, simetría, barrido — todo calculado y verificado contra el código original
Hardwarelos 10 strobes, el bus C, I²C con cada participante, puertos, teclado, perilla rotativa, bitmap de pantalla
Bits de estado75 de 128 con un efecto documentado
Diff de versionesV1.3 vs V1.5 es 91.4 % estructuralmente idéntico; cada cambio nombrado
Emuladoresuno en Python, uno en JavaScript (~8 M instrucciones/s), más un simulador de navegador de archivo único
Nuestro propio firmwareV2.0 — un defecto de fábrica corregido, checksum gestionado, verificado en el emulador y en hardware real
PosiciónTipoFunción
D301PCB80C652núcleo 8051 con I²C por hardware, 12 MHz
D30627512EPROM de programa — V1.3 ocupa 0000h–AC70h
D310X28C64EEPROM arbitraria en el bus MOVX
D305PCF8570256 bytes de NVRAM respaldada por batería en I²C (A0h)
D304-APCF8576driver LCD en I²C (70h), búfer de 20 bytes
D302-ASAA3007codificador de teclado, codificado por ancho de pulso en una sola línea
D30774HCT4514decodificador de strobe — el número de strobe son los bits de dirección A8…A11
mkdoom.pymkpoly.py
Vocesunavarias a la vez
Forma de ondala que esté cargadasu propia tabla de acordes
Nivelcomo lo dejó el panel frontalestablecido explícitamente, 11.6 Vpp medidos
ROM usada182 bytes2617 con el riff incorporado, 6185 desde una pista MIDI