
Реверс-инжиниринг прошивки функциональных генераторов Philips PM5139 / PM5138A / PM5136: эмуляторы 8051, используемые в качестве измерительных приборов, 35 разделов документированного аппаратного обеспечения и исправленная прошивка V2.0
Генератор функций на 20 МГц примерно 1994 года, разобранный программно: два дампа EPROM, эмулятор 8051, используемый как измерительный прибор, и 35 разделов документации, где каждое утверждение подкреплено адресом в листинге, измерением в эмуляторе или схемой.
В итоге получилась прошивка V2.0, которая исправляет дефект, допущенный Philips, шесть собственных произвольных форм сигнала и браузерный симулятор, который выполняет оригинальный ROM инструкция за инструкцией.

Каждая таблица форм сигнала в EPROM программы, построенная прямо из бинарника. Внизу справа — та, с которой началась самая интересная часть этого проекта.
Philips PM5139 — это 20-мегагерцовая топовая модель семейства из трёх приборов (PM5136 / PM5138A / PM5139). Внутри находится PCB80C652 — ядро 8051 с аппаратным I²C — EPROM программы 27512 и шесть аналоговых сборок, подключённых к последовательной шине.
Сервисного руководства на PM5139 не существует. Люди ищут его на форумах с 2010 года. Есть руководство на PM5138A, его 10-мегагерцовую сестринскую модель, которая внутренне почти идентична.
Поэтому этот проект начался с другого конца: снять дамп EPROM и разобраться, что делает код, пока прибор не будет понят достаточно хорошо, чтобы его можно было модифицировать.
Были доступны две версии прошивки, V1.3 и V1.5, обе — дампы M27512 объёмом 64 КиБ.
| Дизассемблирование | полностью для обеих версий, ~23 000 строк, с перекрёстными ссылками |
| Аннотированный листинг | 147 именованных процедур, 145 заголовочных комментариев, 3 826 аннотированных строк |
| Документация | 35 разделов, 4 600 строк, каждое утверждение имеет источник |
| Путь сигнала | частота, амплитуда, смещение, AM, FM, пачка, симметрия, свип — всё вычислено и проверено по оригинальному коду |
| Аппаратное обеспечение | все 10 стробов, C-шина, I²C со всеми участниками, порты, клавиатура, поворотная ручка, битовая карта дисплея |
| Биты состояния | 75 из 128 с документированным эффектом |
| Сравнение версий | V1.3 и V1.5 структурно идентичны на 91,4 %; каждое изменение названо |
| Эмуляторы | один на Python, один на JavaScript (~8 млн инструкций/с), плюс однофайловый браузерный симулятор |
| Наша собственная прошивка | V2.0 — заводской дефект исправлен, контрольная сумма обработана, проверено в эмуляторе и на реальном оборудовании |
| Позиция | Тип | Функция |
|---|---|---|
| D301 | PCB80C652 | ядро 8051 с аппаратным I²C, 12 МГц |
| D306 | 27512 | EPROM программы — V1.3 занимает 0000h–AC70h |
| D310 | X28C64 | EEPROM произвольных форм сигнала на шине MOVX |
| D305 | PCF8570 | 256 байт NVRAM с батарейным питанием на I²C (A0h) |
| D304-A | PCF8576 | драйвер LCD на I²C (70h), буфер 20 байт |
| D302-A | SAA3007 | кодировщик клавиатуры, широтно-импульсное кодирование по одной линии |
| D307 | 74HCT4514 | декодер стробов — номер строба задаётся битами адреса A8…A11 |
Аналоговая часть — это последовательная C-шина: UART микроконтроллера 8051
работает в режиме сдвигового регистра, TXD — это тактовый сигнал, RXD — данные,
а строб определяет, какой из десяти сдвиговых регистров защёлкнет байты.
MOV DPH,#8nh, за которым следует MOVX @DPTR,A, запускает строб n. Эта
одна строка — ключ ко всей аналоговой части.
Это та часть, которую стоит перенять для собственного проекта.
Чтение 44-килобайтного бинарника 8051 глазами продвинет вас примерно на треть пути. Всё остальное появилось из запуска оригинального кода и наблюдения за тем, что из этого выходит:```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
Меняйте входные данные, читайте выходные, проверяйте их на соответствие гипотезе. Это сработало для частоты, амплитуды, смещения, глубины AM, девиации FM, количества импульсов, симметрии и обеих характеристик развёртки. Каждая формула в документации сопровождается точками выборки, на которых она была проверена.
Три усовершенствования сделали процесс действительно продуктивным:
**Следите за шиной, а не за дисплеем.** В разделе 15 измеряется, что бит состояния делает с буфером дисплея, и 74 из 128 бит, по-видимому, ничего не делают. Но многие из них управляют не дисплеем, а *аналоговыми узлами* — и те видны только как телеграммы на C-шине. Запись `MOV SBUF,…` и завершающего `MOVX @DPTR` подняла число документированных бит с 54 до 75.
**Нажимайте клавиши, а не изменяйте RAM вручную.** Установка байта RAM вручную создаёт состояния, которые прибор никогда не принимает. Это стоило нам двух неверных выводов и одного падения в таблицу команд. Ввод реальных кодов клавиш через эмулируемый SAA3007 даёт состояния, которых прошивка действительно достигает — и именно перебор всех 256 кодов клавиш выявил, какая клавиша запускает какой обработчик.
**Сначала подозревайте собственный эмулятор.** Три ошибки в нашем ядре породили «необъяснимое» поведение прошивки: `ACALL`, выполненный как `AJMP`, отсутствующий флаг вспомогательного переноса (из-за чего `DA A` работал неправильно и прошивка, казалось, считала в двоичном коде), и удвоенное прерывание клавиатуры. Каждый вывод того периода был перепроверен после этого.
---
## Путь сюда
**Сначала статика.** Дизассемблер с полной таблицей опкодов, затем рекурсивный спуск с эвристиками для таблиц переходов. Это дало 30 508 байт кода и оставило 13 637 байт неучтёнными.
**Затем динамика.** Трассировочный прогон — холодный старт, все 23 клавиши передней панели, оба направления ручки, все режимы работы, 86 миллионов циклов — с отметкой каждого адреса, который действительно исполнялся. В сопоставлении со статическим анализом он выявил ровно **одну** область, пропущенную спуском, а 10 686 из необъяснённых байт оказались пятью известными блоками таблиц.
**Затем схемы.** OCR сервисного руководства бесполезен для схем, но изображения страниц при 400 dpi превосходны. Разрезанные на перекрывающиеся фрагменты, они читаемы вплоть до номеров выводов. Шесть листов были прочитаны таким образом — и там, где пять параллельных дорожек идут на расстоянии 90 пикселей друг от друга, визуальный осмотр был заменён скриптом (`lines.py`), который извлекает отрезки линий из растрового изображения.
**Затем два выпаянных чипа.** Были считаны 27C64 с маркировкой «SINUS 1.1» и X28C64. Оба были размещены на схеме, и их содержимое декодировано.