Skip to content
KitploitKITPLOIT
FerramentasBlog
Enviar
FerramentasBlog
Enviar

Ferramentas de Hacking, PenTest e Cibersegurança para o seu Arsenal de Segurança!

Kitploit é um diretório de ferramentas de hacking, cibersegurança e pentesting. Descubra as últimas atualizações de projetos para encontrar vulnerabilidades, analisar sistemas, automatizar testes e fortalecer sua segurança.

··Feeds·Contato·Privacidade·© 2026 Kitploit

Diretório de Ferramentas

Categorias

Ver todas as categorias
Loading categories
Philips-PM-5139-5138A-5136-Firmware-Project — 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 | Kitploit
Ferramentas/GitHubGitHub/doctormord/philips-pm-5139-5138a-5136-firmware-project
Segurança de Sistemas EmbarcadosAnálise EstáticaAnálise Dinâmica (Sandboxing)Engenharia ReversaSegurança de HardwareAnálise de BináriosPapers e PesquisaAprendizado e EducaçãoAnálise de Firmware

Mais Populares

Ver todos →

Descubra as ferramentas mais usadas pela nossa comunidade.

Explore todas as ferramentas

Navegue pela nossa coleção de ferramentas

Ver todas as ferramentas →
Compartilhar
GitHubdoctormord/philips-pm-5139-5138a-5136-firmware-project

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

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

Ver Repositório
213há 1 diaAinda não revisado

Philips PM5139 — Engenharia Reversa de Firmware

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 ROM V1.3

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.


Conteúdo

  • O que é isto
  • Resultados em resumo
  • O instrumento
  • O método: o emulador é o instrumento de medição
  • O caminho até aqui
  • As partes boas
  • Firmware V2.0 — o que há de novo
  • O easter egg
  • E depois revelou-se polifónico
  • Seis formas de onda arbitrárias nossas
  • O simulador de navegador
  • Estrutura do repositório
  • Utilizar as ferramentas
  • Reproduzir tudo
  • Gravar de volta
  • Quão fiável é isto?
  • Ainda em aberto
  • Fontes

O que é isto

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.


Resultados em resumo


O instrumento

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.


O método: o emulador é o instrumento de medição

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

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:~
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.

**Depois o diff de versões.** Tokenizar ambas as ROMs (distâncias
relativas de salto em vez de alvos absolutos) e rodar `SequenceMatcher`
sobre elas fornece um mapeamento de endereços que sobrevive à movimentação
de código — é assim que os símbolos da V1.3 são transportados para a V1.5.

---

## As partes boas

### A Philips entregou uma forma de onda ruidosa

As três curvas arbitrárias embutidas ficam em `A047h`, `A447h` e `A847h`.
A terceira tem a mesma forma de uma tabela que já está na ROM em forma
computada — mas com **563 mudanças de direção contra 13**, e um desvio
padrão de 4,1 LSB.

Ela foi amostrada de uma fonte analógica em vez de ser computada. A média
do desvio é zero, apenas dois de 1024 pontos estão errados por mais de
10 — esta não é uma forma de onda diferente, é a *mesma* forma de onda
com ruído sobre ela.

### Essa tabela é uma escada de nível de 30 dB

A versão limpa foi descrita em um rascunho anterior como "uma senoide com
dez profundidades de AM", o que era uma leitura visual do gráfico, não
algo que o código diz. Computados, os 1024 pontos se dividem em dez arcos
de senoide cujos intervalos são```
255  171  120  80  56  38  26  17  12  8

uma série geométrica com razão 0,681 = 10^(−1/6), ou seja, 3,33 dB por passo e 30,1 dB no total. Um modelo de metade está errado em até 56, um modelo de 3 dB em 10. É uma escada de níveis logarítmica — um padrão de teste de amplitude ou atenuação.

O STR9 envia 16 bits como duas telegramas de 1 byte

O controlador de amplitude tem dois registradores de deslocamento em um único strobe, mas o firmware só envia um byte por telegrama. O esquema explica: os dois 4094 estão em cascata através de QS' (pino 10), com o pino 9 sem uso — e os telegramas vêm em pares, ~42 000 ciclos de distância com milhões de ciclos de silêncio entre pares. O byte enviado primeiro é empurrado para o segundo registrador.

O mesmo padrão de cascata apareceu em todas as montagens com mais de um registrador de deslocamento — incluindo um caso em que a cadeia atravessa uma fronteira de montagem através de uma linha chamada E.

O atenuador não é um cálculo

Cinco bits no telegrama do STR9 acionam relés diretamente: S1 comuta a faixa do gerador DC, S2…S5 os relés do atenuador. 20 dB (para 40dB), 20 dB, 50/600 ohms — está impresso no esquema. Não há limiares a calcular.

Um handler escondido à vista de todos

A tabela de saltos em 0301h é lida com JMP @A+DPTR. A entrada 15 cai em 0301h + 30 = 031Fh — e ali, em vez do habitual AJMP, está o próprio handler, inline, economizando um salto. Nenhuma instrução de salto na ROM aponta para ele, então a análise estática o perdeu. É o handler do DIAL LOCK, e apenas o rastreamento dinâmico o encontrou.

Seis slots arbitrários, não vinte e quatro

A folha de dados promete 24 memórias de forma de onda. O diretório na EEPROM diz seis. A aritmética resolve:``` 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:~
O intervalo de leitura medido do firmware é `0100h–1EFFh` — seis curvas
ao byte. O instrumento foi construído com o chip pequeno.

### Código morto a comunicar com um dispositivo que não está lá

186 bytes em `9AFFh` fazem tráfego I²C com o endereço `5Ah` — um endereço que
não aparece em mais nenhum lugar. Em **ambas** as versões de firmware, nenhum salto aponta
para ele. Fica no mesmo bloco de tipo de dispositivo que o cartão de interface em `5Eh`,
apenas com bits de banco diferentes, e envia o buffer de receção e os
registos aritméticos em dois telegramas de dez bytes. Parece um
diagnóstico de fábrica para um dispositivo que nunca foi lançado.

### Não se pode executar código a partir da EEPROM arbitrária

Uma ideia óbvia — colocar código num slot de forma de onda arbitrária e saltar para ele —
está morta à chegada. O 8051 é Harvard: as instruções chegam através
de `/PSEN` da EPROM de programa, os dados através de `/RD` da EEPROM
arbitrária. Não está bloqueado; o fio simplesmente não está lá.

### E a codificação de frequência, finalmente

A linha de dígitos do visor vive em `3Eh–43h` da imagem enviada para o
PCF8576, todas as posições partilham uma codificação de segmento, e o byte `43h`
muda de kHz para MHz entre a década 7 e 8. A partir disso:```
f = M · 10^(D−8) kHz

Três sequências de stepping de frequência medidas no instrumento real são reproduzidas exatamente por isto — incluindo a que para cedo porque a mantissa 2500 significaria 25 MHz, acima do limite.


Firmware V2.0 — o que há de novo

Curva arbitrária 3: V1.5 contra V2.0

À esquerda a curva original, à direita a corrigida. Em baixo à esquerda está o desvio em relação à tabela calculada — essa faixa de ±5 LSB é o que uma fonte analógica amostrada deixou para trás.

mkv20.py constrói a V2.0 a partir da V1.5 (ou V1.3). Encontra cada endereço por assinatura em vez de os codificar fixamente, pelo que o mesmo script funciona em ambas as versões de origem:

  1. Curva arbitrária 3 substituída pela tabela calculada limpa. Ambas são 1024 bytes de 8 bits, pelo que a troca é neutra em tamanho e não toca em código. 863 de 1024 bytes mudam.
  2. Curva arbitrária 2 substituída por um chirp logarítmico (1 → 40 períodos). Diferia da curva 1 em exatamente dois bytes — um pulso de agulha extra — pelo que era redundante.
  3. Identificação de versão em *IDN?: PHILIPS,PM5139,0,V2.0/0000.
  4. Versão no visor: a sequência de reset escreve duas células do visor, e esses bytes agora soletram 2.0 na codificação de segmento medida.
  5. Checksum recalculado e armazenado onde o firmware o espera.

Tudo o resto fica intacto. Foram encontradas três outras peculiaridades e deliberadamente deixadas em paz — uma escrita para um SFR inexistente (inofensiva, em ambas as versões), o bloco 5Ah morto, e três bits de estado que são testados mas nunca definidos. Corrigi-los não altera comportamento e só acrescenta risco.

M27512_PM5139_V20.bin é exatamente isto e nada mais. A melodia abaixo é um passo de compilação separado e opcional.

Verificado: o arranque a frio no emulador produz o mesmo buffer de visor e os mesmos flags que a V1.5, o checksum valida, e a compilação é reproduzível byte a byte. Foi gravado e corre num PM5139 real — o visor mostra 2.0 e todos os seis slots arbitrários funcionam.


O easter egg

Como há 19 509 bytes não usados atrás do checksum na V1.5, e o caminho de frequência recebe uma frequência de nota como três bytes BCD, o instrumento pode tocar música através da sua própria saída.

A codificação é agradavelmente direta — década 3, depois a frequência em 0.01 Hz como BCD, pelo que 82.41 Hz é 30 82 41. Quatro bytes por nota: três para o tom, um para a duração.

A parte interessante é o trigger. O menu de diagnóstico (manter LOCAL enquanto se liga) tem uma jump table com oito entradas, mas o ciclo do menu conta 0Bh apenas de 1 a 7 — pelo que a oitava entrada é inalcançável. Também é redundante: salta para o início do menu, que é alcançado a partir de dois outros locais de qualquer forma.

Portanto todo o hook são dois bytes:``` 5B94h table entry 8: LJMP 5B45h -> LJMP 5B62h count limit: 08h -> 09h

root@kitploit:~
Nenhum autoteste é perdido, nenhuma tabela é realocada e nenhum item de menu morto aparece. Mantenha LOCAL, ligue, deixe o menu contar até 8, pressione uma tecla.

A temporização vem da folha de dados do MCS-51. Ambos os emuladores agora contam ciclos de máquina junto com as instruções (`mcyc`, de `mcs51.CYCLES`), e avançar o loop de espera mede **1009 µs** por unidade — 106,95 ms por semicolcheia a 140 BPM, 0,2 % fora do alvo. O valor costumava ser um cálculo manual de 1006 µs que havia perdido duas instruções.

`mkdoom.py` também pode converter um arquivo MIDI. Uma voz precisa ser escolhida (nota mais alta, nota mais baixa, ou um canal) e seções menores que ~25 ms mescladas — abaixo disso uma nota baixa não consegue uma oscilação completa e você ouve apenas um clique.

---

## E então descobriu-se que era polifônico

A melodia acima é uma voz. Não precisa ser, e a razão é uma frase no manual de serviço que havíamos lido sem notar:

> Durante a geração de sinal, as distintas amostras de amplitude do sinal são lidas
> da RAM. Se a forma de onda básica do sinal for alterada [...] as
> amostras de amplitude correspondentes são **carregadas na RAM pela CPU**.

O PM5139 é um **DDS de wavetable de 1024 pontos**. O TWS não é um gerador de triangular em nenhum sentido ingênuo — é um acumulador de fase que produz endereços de leitura 0…1023 para uma RAM rápida na unidade 4, e essa RAM é preenchida pela CPU através do barramento C. Senoide, quadrada, dente de serra e arbitrária são todas o mesmo mecanismo: uma tabela.

E a tabela contém exatamente **um período da saída**. Então uma tabela construída a partir de uma *soma de harmônicos* ainda é periódica em seus 1024 pontos, e toca como um acorde. Não um arpejo, não um truque de modulação — várias notas soando ao mesmo tempo com os 20 Vpp completos, com a CPU não fazendo absolutamente nada enquanto elas soam. Como os parciais devem ser múltiplos inteiros da frequência da tabela, os intervalos saem em entonação justa, que para um acorde sustentado é a afinação melhor de qualquer forma.

`M27512_PM5139_V20_chords.bin` está no repositório pronto para gravar — o riff, em acordes, com o envelope. Para construí-lo você mesmo, ou para usar um arquivo MIDI seu em vez do riff embutido:```
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 constrói as tabelas — power (2:3:4), major (4:5:6), minor (10:12:15), dom7 (4:5:6:7) e mais cinco. mkpoly.py coloca uma na ROM livre junto com a melodia e liga a mesma entrada de menu morta. Ele carrega o acorde uma vez, depois toca a melodia apenas por reafinação, o que transpõe o acorde inteiro em paralelo. Cada nota do riff de E1M1 torna-se um power chord — que é do que esse riff é feito no original.

Arquiteturalmente isto é um PPG Wave: um contador a percorrer uma forma de onda de ciclo único, direto para um DAC. O truque do acorde é o que os trackers de Amiga usavam — colocar o acorde na forma de onda para que uma voz toque três notas em vez de gastar três canais nisso. Um C64 tem de fazer arpejo, porque o SID não tem wavetable gravável.

Nem sequer precisas de uma EPROM para os acordes. As mesmas tabelas cabem na EEPROM arbitrária, por isso python3 mkarb.py --chords dá-te seis acordes selecionáveis a partir do painel frontal com o firmware intacto.

Há dois players e uma imagem transporta um ou o outro, já que ambos ligam a mesma entrada de menu:

Duas medições moldaram esse design:

  • O formato de download transporta dez bits por ponto, não doze: só aparecem quatro bytes baixos distintos (00h 44h 88h CCh) e cada valor reconstruído é um múltiplo de quatro. A RAM de forma de onda tem doze bits de largura, mas o bus transporta dez — exatamente o que o formato ARB armazena, por isso a Philips não desperdiçou nada aí.
  • Uma recarga completa da tabela leva 32 a 40 ms com a saída em silêncio, e não há segunda página de buffer — RAM_PAGE em 1D62h, que soa como uma, constrói a sua palavra a partir da frequência. Assim a harmonia vive na tabela e a melodia na palavra de frequência; nada é recarregado enquanto a música corre.

O emulador não modela RAM de forma de onda, por isso o loader é verificado por construção: polytest.js regista o que realmente chega ao bus e compara todos os 1024 pontos com o que mkchord.py gerou.

Foram precisas cinco EPROMs para lá chegar, e o emulador só nos pôde levar parte do caminho: modela a CPU e o bus mas não a RAM de forma de onda, por isso tudo o que pode confirmar é que os mesmos bytes saem tal como o firmware os envia. Isso é necessário e não suficiente. Três coisas tiveram de ser resolvidas no próprio instrumento:

  • A ordem dos bytes. Dois bytes por ponto, byte alto primeiro. Inferi-la a partir do próprio download do firmware deu a resposta oposta e a tabela saiu como ruído. O que a resolveu foi uma EPROM com seis padrões de teste — uma linha plana, uma rampa, a mesma rampa com os bytes de cada ponto trocados, e mais três — e uma olhadela a um osciloscópio. A rampa trocada era a limpa.

  • Uma mudança de forma de onda são dezanove telegramas, não os três que o primeiro player enviava. O que importa é uma escrita de dois bytes que coloca a RAM em modo de escrita; sem ela, 2048 bytes saem no bus e não aterram em lado nenhum.

  • O nível de saída. O atenuador são dois estágios de relé de 20 dB separados num byte, a tabela ROM para eles lê-se invertida em relação a como tinha sido documentada (são bits de bypass), e o DAC de nível tem sete bits, não oito — dá a volta em 80h, por isso uma definição "mais alta" produzia silêncio. Essa levou uma matriz de cerca de trinta combinações numa única imagem, usando a frequência de saída como número de teste para que a própria leitura do osciloscópio diga qual combinação está ativa.``` 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 arbitrárias próprias

![Os seis slots arbitrários](https://assets.kitploit.com/production/public/readmes/54732/9fa22b75c34e4aaa83df1b2afd284353e019cb65f181a83e5cdeceb57ea32937/4b15ae02ae1bec49f3d1b6ab79c480cd3a73b10818c41a8b04be57ab6f691ef7-display-v1.webp)

`D310_image_V20.bin` preenche todos os slots da EEPROM — gravar o chip
vale a pena uma vez:

| Slot | Forma de onda | Vpp | Para |
|---|---|---|---|
| 1 | sinc, 8 lóbulos | 12.17 | limitação de banda, overshoot |
| 2 | ringing, Q≈6 | 17.81 | comportamento de assentamento |
| 3 | ECG | 12.80 | demonstração |
| 4 | escada, 16 degraus bipolar | 20.00 | linearidade, resolução |
| 5 | seno retificado | 10.00 | como no original, mas calculado |
| 6 | multi-tom, 5 tons | 20.00 | intermodulação |

Dois detalhes que importam e são fáceis de errar:

**Centrar no zero é melhor que esticar.** A jogada óbvia é esticar cada
curva por toda a faixa de valores. Não faça isso: o offset DC do
instrumento vem de um caminho analógico separado e adiciona uma tensão
*fixa*, enquanto o conteúdo DC de uma curva assimétrica esticada escala
*com a amplitude*. Você teria que reajustar o offset toda vez que mudasse
o nível. Colocar o zero natural da forma de onda no zero do conversor
custa de 0,2 a 1 bit — contra os 16 LSB de ruído que o caminho analógico
original já contribui. Não é um custo real.

**Escale em ponto flutuante, arredonde uma vez.** Arredondar primeiro e
esticar depois dá 1,0–1,5 passos de quantização de erro; escalar em float
e arredondar uma vez dá o ótimo de 0,5.

O diretório precisa de um byte de identidade por curva (um checksum dos
1280 bytes da curva, valor inicial `55h`) e o mín/máx como valores de
10 bits alinhados à esquerda por seis bits. Erre o byte de identidade e o
instrumento mostra **Err 8** e recusa a fonte arbitrária — que é
exatamente o que aconteceu no primeiro flash real.

---

## O simulador de navegador

`PM5139_Simulator.html` é um único arquivo autocontido — sem etapa de
build, sem dependências, sem rede. Abra-o e o firmware original V1.3
inicializa na sua frente.

O núcleo 8051 executa o código real. Timers, interrupções, o C-bus e I²C
são emulados; o display é decodificado do fluxo de dados real do PCF8576,
e as teclas geram a forma de onda SAA3007 codificada por largura de pulso
em P3.3. A RAM com bateria de backup é pré-carregada e a EEPROM
arbitrária é gerada na inicialização e verificada pelo próprio firmware.

Uma partida a frio leva cerca de 9 milhões de instruções, então dê um
segundo.

---

## Estrutura do repositório```
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 …

Usando as ferramentas

Python 3 e Node são tudo o que você precisa. matplotlib para os gráficos, pillow e numpy apenas para lines.py.

Examine o 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:~
### Compilar 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 a EEPROM arbitrária```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:~
### Adicionar uma 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 aplica um patch a uma imagem uma vez e recusa-se a fazê-lo duas vezes — crie uma V2.0 nova com mkv20.py se quiser começar de novo.

Reproduzir um 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 coisas no emulador

Cada um destes imprime uma tabela que você pode verificar em relação à documentação:```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:~
### Ler um 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

Reproduzindo tudo

Toda a cadeia de compilação é determinística — estes comandos reconstroem o firmware e a imagem da 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:~
---

## Regravando de volta

> **Guarde sua EPROM original.** Leia duas vezes, compare os dumps, coloque o
> chip em uma gaveta. Tudo aqui é reversível apenas se você ainda a tiver.

O firmware verifica uma soma de bytes sobre o intervalo ocupado na energização e
compara com o byte imediatamente após. Se errar, você recebe
`Err 1` e um loop infinito — o instrumento não inicializa. O `romfix.py`
calcula e insere o valor correto; todo script de build aqui já o
chama.

| Versão | Intervalo | Byte de checksum | Valor |
|---|---|---|---|
| V1.3 | `0000h–AC6Fh` | `AC70h` | `F2h` |
| V1.5 | `0000h–B3C9h` | `B3CAh` | `99h` |

Duas coisas aprendidas na prática em hardware real:

- A EEPROM arbitrária precisa que seus **bytes de identidade** sejam recalculados, ou você
  recebe `Err 8` a cada inicialização e a fonte ARB não pode ser selecionada.
- Se a ARB se comportar de forma estranha após uma gravação, verifique se o pino 28 do soquete
  está bem assentado antes de suspeitar da imagem.

---

## Quão confiável é isto?

Tudo marcado como verificado foi confirmado chamando as rotinas originais
no emulador em vários pontos de amostragem, geralmente com verificação cruzada
contra a listagem ou o esquema elétrico também.

Onde as coisas deram errado, está registrado em vez de silenciosamente corrigido:

- **Três bugs do emulador** (`ACALL` como `AJMP`, flag AC ausente, interrupção
  de teclado duplicada) estavam ativos durante a fase intermediária do projeto.
  Todas as descobertas afetadas foram remedidas depois — o bitmap do display
  voltou idêntico, a atribuição de strobe correspondeu ao manual de serviço,
  e a seção 16 acabou tendo dois strobes ausentes.
- **Uma imagem de NVRAM sintética** que nunca foi lida de um instrumento
  real falsificou duas descobertas, incluindo "o botão rotativo só funciona
  em uma direção". A correção foi entregar ao firmware uma NVRAM inválida e
  deixá-lo escrever seu próprio estado de fábrica.
- **Estados de RAM definidos manualmente** produzem configurações que o instrumento nunca
  assume. Duas vezes isso produziu conclusões erradas, uma vez um crash na
  tabela de comandos.
- **`core.js` conta uma instrução por ciclo**, não ciclos de máquina. Bom
  para ordenação, errado para temporização absoluta — as afirmações de temporização aqui vêm do
  data sheet do MCS-51.

Qualquer coisa que seja uma suposição em vez de uma medição diz isso no
texto.

---

## Ainda em aberto

- **36 dos 128 bits de estado** precisam de um estímulo fora dos seis perfis
  operacionais — autoteste, caminhos de erro, tráfego de interface.
- **Campos da NVRAM a partir do offset 0Dh.** O layout até lá é medido
  (`NVRAM offset + 4Bh = endereço de RAM`), a marca de verificação é compreendida
  (soma de bytes, valor inicial `AAh`, 25 bytes).
- **Qual comando arbitrário alcança qual dos 13 sub-blocos** na
  região `8871h`. Existem apenas quatro comparações diretas de token; o resto
  ramifica em testes de bits.
- **Se um comando remoto pode contornar a verificação de faixa de parâmetros.**
- **As rotinas de carregamento de forma de onda** são a dependência restante mais difícil para
  uma reimplementação completa — sem elas não há sinal de saída.
- **Como o PM5139 produz 20 MHz a partir do mesmo clock** que seu irmão de 10 MHz.
  A cadeia implica que seu passa-baixas fica em 10 MHz em vez de 5 MHz,
  mas isso precisa de um manual do PM5139 para confirmar.

Se você possui um desses instrumentos, duas coisas ajudariam muito: um
**manual de serviço do PM5139**, e dumps de **outras versões de firmware**
(uma V1.4 pode ou não existir).

---

## Fontes

- **`pm5138A_service_manual.pdf`** — a principal fonte de hardware. 176
  páginas, com OCR; o texto corrido é lido limpo com `pdftotext -layout`, os
  esquemas elétricos precisam ser renderizados como imagens. As páginas 4-3 a 4-28 estão faltando
  no scan.
- **Manual do usuário do PM5139** (Fluke) — scan trilíngue sem camada de texto;
  o capítulo 3.7.4.6 documenta os comandos arbitrários. Vale a pena fazer OCR você mesmo
  — a parte em inglês são as páginas 13–145 do PDF.
- **Manual do usuário do PM5136** — útil como contraverificação: seus números de erro
  e lista de comandos mostram quais parâmetros o menor modelo não possui, o que
  confirmou independentemente a ordenação de parâmetros na ROM.
- **Data sheet dos três modelos** — limites operacionais por forma de onda.

Os manuais são documentos de terceiros e **não são redistribuídos neste
repositório**. Eles são encontráveis online.

---

## Licença e uso

Dois tipos de material, sob termos diferentes — veja [LICENSE](https://github.com/doctormord/philips-pm-5139-5138a-5136-firmware-project/blob/main/LICENSE) para
o escopo exato:

- **O trabalho de engenharia reversa é MIT.** Documentação, ferramentas, ambos os
  emuladores, tabelas de símbolos, anotações, as formas de onda geradas e os
  gráficos. Use como quiser.
- **O firmware Philips não é nosso para licenciar.** As imagens de ROM, os
  dumps de chip de fábrica, as listagens de disassembly e o simulador de navegador
  (que incorpora a imagem V1.3) reproduzem ou derivam do trabalho da Philips.
  Eles estão aqui como objeto de estudo, para interoperabilidade, reparo e
  documentação de instrumentos que estão sem suporte há décadas.
  Onde nosso próprio trabalho está misturado — as anotações, a forma de onda
  corrigida na V2.0 — apenas essa contribuição é MIT.

Se você detém direitos sobre o firmware original e se opõe, abra uma issue e
ele será removido.

Se você usar qualquer parte disto, um link de volta é apreciado. Se encontrar um erro,
abra uma issue — toda afirmação aqui nomeia o endereço ou medição em que se baseia,
então deve ser falseável.
Baixar ferramenta
Desmontagemcompleta para ambas as versões, ~23 000 linhas, com referências cruzadas
Listagem anotada147 rotinas nomeadas, 145 comentários de cabeçalho, 3 826 linhas anotadas
Documentação35 secções, 4 600 linhas, cada afirmação com fonte
Caminho do sinalfrequência, amplitude, offset, AM, FM, burst, simetria, varrimento — tudo calculado e verificado contra o código original
Hardwaretodos os 10 strobes, o barramento C, I²C com todos os participantes, portas, teclado, botão rotativo, bitmap do visor
Bits de estado75 de 128 com um efeito documentado
Diferenças de versãoV1.3 vs V1.5 é 91,4 % estruturalmente idêntica; cada alteração nomeada
Emuladoresum em Python, um em JavaScript (~8 M instruções/s), mais um simulador de navegador num único ficheiro
O nosso próprio firmwareV2.0 — um defeito de fábrica corrigido, checksum tratado, verificado no emulador e em hardware real
PosiçãoTipoFunção
D301PCB80C652núcleo 8051 com I²C por hardware, 12 MHz
D30627512EPROM de programa — V1.3 ocupa 0000h–AC70h
D310X28C64EEPROM arbitrária no barramento MOVX
D305PCF8570256 bytes de NVRAM com bateria em I²C (A0h)
D304-APCF8576controlador de LCD em I²C (70h), buffer de 20 bytes
D302-ASAA3007codificador de teclado, codificado por largura de impulso numa única linha
D30774HCT4514descodificador de strobe — o número do strobe são os bits de endereço A8…A11
mkdoom.pymkpoly.py
Vozesumavárias ao mesmo tempo
Forma de ondaa que estiver carregadaa sua própria tabela de acordes
Nívelcomo o painel frontal o deixoudefinido explicitamente, 11,6 Vpp medidos
ROM usada182 bytes2617 com o riff incorporado, 6185 a partir de uma faixa MIDI