
Engenharia reversa de firmware dos geradores de função Philips PM5139 / PM5138A / PM5136: emuladores 8051 usados como instrumentos de medição, 35 seções de hardware documentado e um firmware V2.0 corrigido
Um gerador de funções de 20 MHz de cerca de 1994, desmontado em software: dois dumps de EPROM, um emulador de 8051 usado como instrumento de medição, e 35 secções de documentação onde cada afirmação é sustentada por um endereço de listagem, uma medição do emulador, ou o esquema.
No final há um firmware V2.0 que corrige um defeito que a Philips lançou, seis formas de onda arbitrárias nossas, e um simulador de navegador que executa a ROM original instrução por instrução.

Todas as tabelas de formas de onda na EPROM de programa, traçadas diretamente a partir do binário. Em baixo à direita está a que deu início à parte mais interessante deste projeto.
O Philips PM5139 é o modelo de topo de 20 MHz de uma família de três instrumentos (PM5136 / PM5138A / PM5139). No interior encontra-se um PCB80C652 — um núcleo 8051 com I²C por hardware — uma EPROM de programa 27512, e seis conjuntos analógicos pendurados num barramento série.
Não existe manual de serviço do PM5139. As pessoas procuram um em fóruns desde 2010. O que existe é o manual do PM5138A, o seu modelo irmão de 10 MHz, que é internamente quase idêntico.
Por isso este projeto começou pelo outro lado: fazer o dump da EPROM, e perceber o que o código faz até o instrumento estar suficientemente compreendido para o modificar.
Estavam disponíveis duas versões de firmware, V1.3 e V1.5, ambas dumps M27512 de 64 KiB.
| Desmontagem | completa para ambas as versões, ~23 000 linhas, com referências cruzadas |
| Listagem anotada | 147 rotinas nomeadas, 145 comentários de cabeçalho, 3 826 linhas anotadas |
| Documentação | 35 secções, 4 600 linhas, cada afirmação com fonte |
| Caminho do sinal | frequência, amplitude, offset, AM, FM, burst, simetria, varrimento — tudo calculado e verificado contra o código original |
| Hardware | todos os 10 strobes, o barramento C, I²C com todos os participantes, portas, teclado, botão rotativo, bitmap do visor |
| Bits de estado | 75 de 128 com um efeito documentado |
| Diferenças de versão | V1.3 vs V1.5 é 91,4 % estruturalmente idêntica; cada alteração nomeada |
| Emuladores | um em Python, um em JavaScript (~8 M instruções/s), mais um simulador de navegador num único ficheiro |
| O nosso próprio firmware | V2.0 — um defeito de fábrica corrigido, checksum tratado, verificado no emulador e em hardware real |
| Posição | Tipo | Função |
|---|---|---|
| D301 | PCB80C652 | núcleo 8051 com I²C por hardware, 12 MHz |
| D306 | 27512 | EPROM de programa — V1.3 ocupa 0000h–AC70h |
| D310 | X28C64 | EEPROM arbitrária no barramento MOVX |
| D305 | PCF8570 | 256 bytes de NVRAM com bateria em I²C (A0h) |
| D304-A | PCF8576 | controlador de LCD em I²C (70h), buffer de 20 bytes |
| D302-A | SAA3007 | codificador de teclado, codificado por largura de impulso numa única linha |
| D307 | 74HCT4514 | descodificador de strobe — o número do strobe são os bits de endereço A8…A11 |
O lado analógico é um barramento C série: a UART do 8051 funciona em modo
shift register, TXD é o clock, RXD os dados, e um strobe decide qual
dos dez shift registers faz latch dos bytes. MOV DPH,#8nh seguido de
MOVX @DPTR,A dispara o strobe n. Essa única linha é a chave para toda a
secção analógica.
Esta é a parte que vale a pena roubar para o seu próprio projeto.
Ler um binário 8051 de 44 KB a olho leva-o talvez a um terço do caminho. Tudo o resto veio de executar o código original e observar o que sai:
---```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
Varie a entrada, leia a saída, confira com a hipótese. Isso
funcionou para frequência, amplitude, offset, profundidade de AM, desvio
de FM, contagem de burst, simetria e ambas as características de sweep.
Cada fórmula na documentação vem com os pontos de amostra sobre os quais
foi verificada.
Três refinamentos tornaram isso realmente produtivo:
**Observe o barramento, não o display.** A seção 15 mede o que um bit de
estado faz com o buffer do display, e 74 de 128 bits parecem não fazer
nada. Mas muitos deles não acionam o display, eles acionam os *conjuntos
analógicos* — e esses só são visíveis como telegramas no barramento C.
Registrar `MOV SBUF,…` e o `MOVX @DPTR` de terminação elevou a contagem
de bits documentados de 54 para 75.
**Pressione teclas, não cutuque a RAM.** Definir um byte da RAM à mão
produz estados que o instrumento nunca assume. Isso nos custou duas
conclusões erradas e um crash na tabela de comandos. Injetar códigos de
tecla reais através do SAA3007 emulado fornece estados que o firmware
realmente alcança — e foi uma varredura por força bruta sobre todos os
256 códigos de tecla que revelou qual tecla dispara qual handler.
**Suspeite primeiro do seu próprio emulador.** Três bugs no nosso núcleo
produziram comportamento "inexplicável" do firmware: `ACALL` executado
como `AJMP`, um flag de carry auxiliar ausente (de modo que `DA A` se
comportava mal e o firmware parecia contar em binário), e uma interrupção
de teclado duplicada. Toda conclusão daquele período foi remedida depois.
---
## O caminho até aqui
**Primeiro o estático.** Um disassembler com uma tabela de opcodes
completa, depois descida recursiva com heurísticas de jump-table. Isso
produziu 30 508 bytes de código e deixou 13 637 bytes sem explicação.
**Depois o dinâmico.** Uma execução de trace — cold start, todas as 23
teclas do painel frontal, ambos os sentidos do knob, todos os modos de
operação, 86 milhões de ciclos — marcando cada endereço que realmente
executou. Comparado com a análise estática, encontrou exatamente **uma**
área que a descida havia perdido, e 10 686 dos bytes não explicados
revelaram-se cinco blocos de tabela conhecidos.
**Depois os esquemas.** O OCR do manual de serviço é inútil para
esquemas, mas as imagens das páginas a 400 dpi são excelentes. Cortadas
em tiles sobrepostos, são legíveis até os números dos pinos. Seis folhas
foram lidas dessa forma — e onde cinco trilhas paralelas correm a 90
pixels de distância, a inspeção visual foi substituída por um script
(`lines.py`) que extrai os segmentos de linha do bitmap.
**Depois os dois chips que foram extraídos.** Um 27C64 rotulado "SINUS
1.1" e um X28C64 foram lidos. Ambos foram colocados no esquema e seus
conteúdos decodificados.