
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
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 EPROM del programa, graficadas directamente desde el binario. Abajo a la derecha está la que inició la parte más interesante de este proyecto.
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.
| Desensamblado | completo para ambas versiones, ~23 000 líneas, con referencias cruzadas |
| Listado anotado | 147 rutinas nombradas, 145 comentarios de encabezado, 3 826 líneas anotadas |
| Documentación | 35 secciones, 4 600 líneas, cada afirmación con fuente |
| Ruta de señal | frecuencia, amplitud, offset, AM, FM, burst, simetría, barrido — todo calculado y verificado contra el código original |
| Hardware | los 10 strobes, el bus C, I²C con cada participante, puertos, teclado, perilla rotativa, bitmap de pantalla |
| Bits de estado | 75 de 128 con un efecto documentado |
| Diff de versiones | V1.3 vs V1.5 es 91.4 % estructuralmente idéntico; cada cambio nombrado |
| Emuladores | uno en Python, uno en JavaScript (~8 M instrucciones/s), más un simulador de navegador de archivo único |
| Nuestro propio firmware | V2.0 — un defecto de fábrica corregido, checksum gestionado, verificado en el emulador y en hardware real |
| Posición | Tipo | Función |
|---|---|---|
| D301 | PCB80C652 | núcleo 8051 con I²C por hardware, 12 MHz |
| D306 | 27512 | EPROM de programa — V1.3 ocupa 0000h–AC70h |
| D310 | X28C64 | EEPROM arbitraria en el bus MOVX |
| D305 | PCF8570 | 256 bytes de NVRAM respaldada por batería en I²C (A0h) |
| D304-A | PCF8576 | driver LCD en I²C (70h), búfer de 20 bytes |
| D302-A | SAA3007 | codificador de teclado, codificado por ancho de pulso en una sola línea |
| D307 | 74HCT4514 | decodificador de strobe — el número de strobe son los bits de dirección A8…A11 |
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.
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
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
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.