Skip to content
KitploitKITPLOIT
ИнструментыБлог
Отправить
ИнструментыБлог
Отправить

Инструменты для хакинга, пентеста и кибербезопасности — ваш арсенал защиты!

Kitploit — это каталог инструментов для хакинга, кибербезопасности и пентестинга. Находите последние обновления проектов для поиска уязвимостей, анализа систем, автоматизации тестирования и усиления вашей безопасности.

··Ленты·Контакты·Конфиденциальность·© 2026 Kitploit

Каталог инструментов

Категории

Все категории
Loading categories
Philips-PM-5139-5138A-5136-Firmware-Project — Реверс-инжиниринг прошивки функциональных генераторов Philips PM5139 / PM5138A / PM5136: эмуляторы 8051, используемые в качестве измерительных приборов, 35 разделов документированного аппаратного обеспечения и исправленная прошивка V2.0 | Kitploit
Инструменты/GitHubGitHub/doctormord/philips-pm-5139-5138a-5136-firmware-project
Безопасность встроенных системСтатический анализДинамический анализ (песочница)Обратная инженерияАппаратная БезопасностьАнализ Бинарных ФайловСтатьи и ИсследованияОбучение и ОбразованиеАнализ Прошивок

Популярное

Смотреть все →

Откройте для себя самые используемые инструменты нашего сообщества.

Изучить все инструменты

Просмотрите нашу коллекцию инструментов

Смотреть все инструменты →
Поделиться
GitHubdoctormord/philips-pm-5139-5138a-5136-firmware-project

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

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

Репозиторий
2131 день назадЕщё не проверено

Philips PM5139 — реверс-инжиниринг прошивки

Генератор функций на 20 МГц примерно 1994 года, разобранный программно: два дампа EPROM, эмулятор 8051, используемый как измерительный прибор, и 35 разделов документации, где каждое утверждение подкреплено адресом в листинге, измерением в эмуляторе или схемой.

В итоге получилась прошивка V2.0, которая исправляет дефект, допущенный Philips, шесть собственных произвольных форм сигнала и браузерный симулятор, который выполняет оригинальный ROM инструкция за инструкцией.

Все таблицы форм сигнала в ROM V1.3

Каждая таблица форм сигнала в EPROM программы, построенная прямо из бинарника. Внизу справа — та, с которой началась самая интересная часть этого проекта.


Содержание

  • Что это такое
  • Результаты кратко
  • Прибор
  • Метод: эмулятор — это измерительный прибор
  • Путь к этому
  • Самое интересное
  • Прошивка V2.0 — что нового
  • Пасхалка
  • А потом оказалось, что она полифоническая
  • Шесть собственных произвольных форм сигнала
  • Браузерный симулятор
  • Структура репозитория
  • Использование инструментов
  • Воспроизведение всего
  • Прошивка обратно
  • Насколько это надёжно?
  • Что ещё открыто
  • Источники

Что это такое

Philips PM5139 — это 20-мегагерцовая топовая модель семейства из трёх приборов (PM5136 / PM5138A / PM5139). Внутри находится PCB80C652 — ядро 8051 с аппаратным I²C — EPROM программы 27512 и шесть аналоговых сборок, подключённых к последовательной шине.

Сервисного руководства на PM5139 не существует. Люди ищут его на форумах с 2010 года. Есть руководство на PM5138A, его 10-мегагерцовую сестринскую модель, которая внутренне почти идентична.

Поэтому этот проект начался с другого конца: снять дамп EPROM и разобраться, что делает код, пока прибор не будет понят достаточно хорошо, чтобы его можно было модифицировать.

Были доступны две версии прошивки, V1.3 и V1.5, обе — дампы M27512 объёмом 64 КиБ.


Результаты кратко


Прибор

Аналоговая часть — это последовательная C-шина: UART микроконтроллера 8051 работает в режиме сдвигового регистра, TXD — это тактовый сигнал, RXD — данные, а строб определяет, какой из десяти сдвиговых регистров защёлкнет байты. MOV DPH,#8nh, за которым следует MOVX @DPTR,A, запускает строб n. Эта одна строка — ключ ко всей аналоговой части.


Метод: эмулятор — это измерительный прибор

Это та часть, которую стоит перенять для собственного проекта.

Чтение 44-килобайтного бинарника 8051 глазами продвинет вас примерно на треть пути. Всё остальное появилось из запуска оригинального кода и наблюдения за тем, что из этого выходит:```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:~
Меняйте входные данные, читайте выходные, проверяйте их на соответствие гипотезе. Это сработало для частоты, амплитуды, смещения, глубины 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. Оба были размещены на схеме, и их содержимое декодировано.

**Затем сравнение версий.** Токенизация обоих ROM (относительные расстояния переходов вместо абсолютных целей) и прогон `SequenceMatcher` по ним даёт отображение адресов, устойчивое к перемещению кода — именно так символы V1.3 переносятся на V1.5.

---

## Хорошие моменты

### Philips поставил зашумлённую форму сигнала

Три встроенные произвольные кривые находятся по адресам `A047h`, `A447h` и `A847h`. Третья имеет ту же форму, что и таблица, уже присутствующая в ROM в вычисленном виде — но с **563 изменениями направления против 13** и стандартным отклонением 4,1 LSB.

Она была снята с аналогового источника, а не вычислена. Среднее отклонения равно нулю, только две из 1024 точек отличаются более чем на 10 — это не другая форма сигнала, это *та же* форма сигнала с шумом.

### Эта таблица — 30-дБ лестница уровней

Чистая версия была описана в более раннем черновике как «синусоида с десятью глубинами AM», что было визуальным прочтением графика, а не тем, что говорит код. При вычислении 1024 точки разбиваются на десять синусоидальных дуг, диапазоны которых```
255  171  120  80  56  38  26  17  12  8

геометрическая прогрессия со знаменателем 0.681 = 10^(−1/6), то есть 3.33 дБ на шаг и 30.1 дБ в целом. Модель с делением пополам ошибается вплоть до 56, модель с шагом 3 дБ — на 10. Это логарифмическая лестница уровней — тестовый шаблон амплитуды или затухания.

STR9 отправляет 16 бит как две однобайтовые телеграммы

У контроллера амплитуды два сдвиговых регистра на одном стробе, но прошивка всегда отправляет только один байт за телеграмму. Схема объясняет это: два 4094 каскадированы через QS' (вывод 10), при этом вывод 9 не используется — и телеграммы идут парами, с интервалом ~42 000 циклов и миллионами циклов тишины между парами. Байт, отправленный первым, проталкивается во второй регистр.

Тот же паттерн каскадирования обнаружился в каждой сборке с более чем одним сдвиговым регистром — включая случай, когда цепочка пересекает границу сборки через линию под названием E.

Аттенюатор — это не расчёт

Пять бит в телеграмме STR9 управляют реле напрямую: S1 переключает диапазон генератора постоянного тока, S2…S5 — реле аттенюатора. 20 dB (for 40dB), 20 dB, 50/600 ohms — это напечатано на схеме. Здесь нет порогов, которые нужно вычислять.

Обработчик, скрывающийся на виду

Таблица переходов по адресу 0301h читается с помощью JMP @A+DPTR. Запись 15 ведёт на 0301h + 30 = 031Fh — и там, вместо обычного AJMP, находится сам обработчик, встроенный, что экономит переход. Ни одна инструкция перехода в ROM не указывает на него, поэтому статический анализ его потерял. Это обработчик DIAL LOCK, и только динамическая трассировка его нашла.

Шесть произвольных слотов, а не двадцать четыре

Техническое описание обещает 24 области памяти сигналов. Каталог в EEPROM говорит о шести. Арифметика расставляет всё по местам:``` 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:~
Измеренный диапазон чтения прошивки — `0100h–1EFFh` — шесть кривых
байт в байт. Прибор был построен на маленьком чипе.

### Мёртвый код, обращающийся к устройству, которого нет

186 байт по адресу `9AFFh` осуществляют обмен по I²C с адресом `5Ah` — адресом, который
больше нигде не встречается. В **обеих** версиях прошивки ни один переход не указывает на
него. Он находится в том же блоке типа устройства, что и интерфейсная карта по адресу `5Eh`,
только с другими битами банка, и он отправляет буфер приёма и
арифметические регистры двумя десятибайтовыми телеграммами. Это похоже на
заводскую диагностику для устройства, которое так и не было выпущено.

### Нельзя исполнять код из произвольного EEPROM

Очевидная идея — поместить код в слот произвольной формы сигнала и перейти к нему —
мертва при рождении. 8051 — гарвардская архитектура: инструкции поступают через
`/PSEN` из программного EPROM, данные через `/RD` из произвольного
EEPROM. Это не заблокировано; провода просто нет.

### И кодирование частоты, наконец

Строка цифр дисплея находится в `3Eh–43h` изображения, отправляемого в
PCF8576, все позиции используют одну и ту же кодировку сегментов, а байт `43h`
переключается с кГц на МГц между декадой 7 и 8. Отсюда:```
f = M · 10^(D−8) kHz

Три последовательности частотных шагов, измеренные на реальном приборе, воспроизводятся этим точно — включая ту, что останавливается раньше, потому что мантисса 2500 означала бы 25 МГц, что превышает предел.


Прошивка V2.0 — что нового

Произвольная кривая 3: V1.5 против V2.0

Слева — поставляемая кривая, справа — исправленная. Внизу слева — отклонение от вычисленной таблицы — эта полоса ±5 LSB — то, что оставил после себя дискретизированный аналоговый источник.

mkv20.py собирает V2.0 из V1.5 (или V1.3). Он находит каждый адрес по сигнатуре, а не жёстко прописывает их, поэтому один и тот же скрипт работает с обеими версиями исходника:

  1. Произвольная кривая 3 заменена на чистую вычисленную таблицу. Обе — 1024 байта по 8 бит, поэтому замена нейтральна по размеру и не затрагивает код. Изменяются 863 из 1024 байт.
  2. Произвольная кривая 2 заменена на логарифмический чирп (1 → 40 периодов). Она отличалась от кривой 1 ровно в двух байтах — один лишний игольчатый импульс — так что была избыточной.
  3. Идентификация версии в *IDN?: PHILIPS,PM5139,0,V2.0/0000.
  4. Версия на дисплее: последовательность сброса записывает две ячейки дисплея, и эти байты теперь образуют 2.0 в измеренной сегментной кодировке.
  5. Контрольная сумма пересчитана и сохранена там, где её ожидает прошивка.

Всё остальное не тронуто. Были найдены ещё три странности, и их намеренно оставили в покое — запись в несуществующий SFR (безвредно, в обеих версиях), мёртвый блок 5Ah и три бита состояния, которые проверяются, но никогда не устанавливаются. Их исправление не меняет поведения и лишь добавляет риск.

M27512_PM5139_V20.bin — это ровно оно и ничего больше. Мелодия ниже — отдельный, опциональный шаг сборки.

Проверено: холодный старт в эмуляторе даёт тот же буфер дисплея и те же флаги, что и V1.5, контрольная сумма проходит проверку, а сборка воспроизводима побайтово. Она была прошита и работает на реальном PM5139 — дисплей показывает 2.0, и все шесть произвольных слотов работают.


Пасхалка

Поскольку в V1.5 за контрольной суммой есть 19 509 неиспользуемых байт, а частотный тракт принимает частоту ноты как три BCD-байта, прибор может проигрывать музыку через собственный выход.

Кодировка приятно прямолинейна — декада 3, затем частота в 0,01 Гц в виде BCD, так что 82,41 Гц — это 30 82 41. Четыре байта на ноту: три для высоты тона, один для длительности.

Интересна здесь триггерная часть. Диагностическое меню (удерживайте LOCAL при включении) имеет таблицу переходов с восемью записями, но цикл меню считает 0Bh только от 1 до 7 — так что восьмая запись недостижима. Она также избыточна: она выполняет переход к началу меню, куда и так попадают из двух других мест.

Так что весь хук — два байта:``` 5B94h table entry 8: LJMP 5B45h -> LJMP 5B62h count limit: 08h -> 09h

root@kitploit:~
Ни один самотест не теряется, ни одна таблица не перемещается, и ни один
мёртвый пункт меню не появляется. Удерживайте LOCAL, включите, дайте меню
досчитать до 8, нажмите клавишу.

Тайминги взяты из даташита MCS-51. Оба эмулятора теперь считают машинные
циклы наряду с инструкциями (`mcyc`, из `mcs51.CYCLES`), и пошаговое
выполнение цикла ожидания даёт **1009 µs** на единицу — 106,95 мс на
шестнадцатую ноту при 140 BPM, отклонение 0,2 % от цели. Раньше это число
было ручным расчётом в 1006 µs, в котором потерялись две инструкции.

`mkdoom.py` также умеет конвертировать MIDI-файл. Нужно выбрать голос
(самая высокая нота, самая низкая нота или один канал) и объединить
секции короче ~25 мс — ниже этого порога низкая нота не успевает
совершить полное колебание, и вы слышите только щелчок.

---

## А потом оказалось, что он полифонический

Мелодия выше — это один голос. Это не обязательно, и причина — фраза в
сервисном руководстве, которую мы пропустили при чтении:

> Во время генерации сигнала отдельные отсчёты амплитуды сигнала
> считываются из RAM. Если базовая форма сигнала изменяется [...] 
> соответствующие отсчёты амплитуды **загружаются в RAM процессором**.

PM5139 — это **DDS с волновой таблицей на 1024 точки**. TWS не является
генератором треугольного сигнала в каком-либо наивном смысле — это
фазовый аккумулятор, который формирует адреса чтения 0…1023 для быстрой
RAM на блоке 4, и эта RAM заполняется процессором через C-шину. Синус,
меандр, пила и произвольная форма — всё это один и тот же механизм:
таблица.

И таблица содержит ровно **один период выходного сигнала**. Поэтому
таблица, построенная из *суммы гармоник*, всё ещё периодична в своих
1024 точках, и она воспроизводится как аккорд. Не арпеджио, не трюк с
модуляцией — несколько нот звучат одновременно при полных 20 Vpp, и
процессор при этом вообще ничего не делает, пока они звучат. Поскольку
партиалы должны быть целыми кратными частоты таблицы, интервалы
получаются в чистом строе, что для выдержанного аккорда в любом случае
лучший строй.

`M27512_PM5139_V20_chords.bin` лежит в репозитории, готовый к прошивке —
рифф, в аккордах, с огибающей. Чтобы собрать его самостоятельно или
использовать вместо встроенного риффа собственный MIDI-файл:```
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 строит таблицы — power (2:3:4), major (4:5:6), minor (10:12:15), dom7 (4:5:6:7) и ещё пять. mkpoly.py помещает одну из них в свободную ROM вместе с мелодией и привязывает к тому же мёртвому пункту меню. Он загружает аккорд один раз, затем играет мелодию только перестройкой, что транспонирует весь аккорд параллельно. Каждая нота риффа E1M1 становится пауэр-аккордом — а из чего ещё состоит этот рифф в оригинале.

Архитектурно это PPG Wave: счётчик, пробегающий по одноцикловой волновой форме, прямо в ЦАП. Трюк с аккордом — тот самый, что использовали трекеры Amiga: поместить аккорд в волновую форму, чтобы один голос играл три ноты вместо того, чтобы тратить на это три канала. C64 приходится вместо этого использовать арпеджио, потому что у SID нет записываемой таблицы волновых форм.

Вам даже не нужна EPROM для аккордов. Те же таблицы помещаются в произвольную EEPROM, так что python3 mkarb.py --chords даёт вам шесть аккордов, выбираемых с передней панели без изменения прошивки.

Есть два плеера, и образ несёт один или другой, поскольку оба привязываются к одному и тому же пункту меню:

Два измерения определили этот дизайн:

  • Формат загрузки несёт десять бит на точку, а не двенадцать: встречаются только четыре различных младших байта (00h 44h 88h CCh), и каждое восстановленное значение кратно четырём. ОЗУ волновой формы имеет ширину двенадцать бит, но шина передаёт десять — ровно столько, сколько хранит формат ARB, так что Philips здесь ничего не потеряла.
  • Полная перезагрузка таблицы занимает от 32 до 40 мс при молчащем выходе, и второй страницы буфера нет — RAM_PAGE по адресу 1D62h, который звучит как таковая, строит своё слово из частоты. Так что гармония живёт в таблице, а мелодия — в слове частоты; ничего не перезагружается, пока играет музыка.

Эмулятор не моделирует ОЗУ волновой формы, поэтому загрузчик проверяется конструктивно: polytest.js записывает то, что фактически доходит до шины, и сравнивает все 1024 точки с тем, что сгенерировал mkchord.py.

Понадобилось пять EPROM, чтобы до этого дойти, и эмулятор мог провести нас лишь часть пути: он моделирует CPU и шину, но не ОЗУ волновой формы, так что всё, что он может подтвердить, — это что наружу уходят те же байты, что отправляет прошивка. Это необходимо, но недостаточно. Три вещи пришлось выяснять на самом инструменте:

  • Порядок байтов. Два байта на точку, старший байт первым. Вывод его из собственной загрузки прошивки дал противоположный ответ, и таблица вышла шумом. Решила вопрос одна EPROM с шестью тестовыми паттернами — плоская линия, рампа, та же рампа с переставленными байтами каждой точки и ещё три — и взгляд на осциллограф. Переставленная рампа оказалась чистой.

  • Смена волновой формы — это девятнадцать телеграмм, а не три, которые отправлял первый плеер. Важна та, что представляет собой двухбайтовую запись, переводящую ОЗУ в режим записи; без неё 2048 байт уходят на шину и приземляются в никуда.

  • Уровень выхода. Аттенюатор — это две отдельные 20-дБ релейные ступени в одном байте, ROM-таблица для них читается инвертированно по сравнению с тем, как это было задокументировано (это биты обхода), а ЦАП уровня семибитный, а не восьмибитный — он заворачивается на 80h, так что одна настройка «громче» давала тишину. На это ушла матрица примерно из тридцати комбинаций в одном образе, с использованием частоты выхода в качестве тестового номера, чтобы собственные показания осциллографа говорили, какая комбинация активна.``` 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:~
---

## Шесть произвольных сигналов, созданных нами

![Шесть произвольных слотов](https://assets.kitploit.com/production/public/readmes/54732/9fa22b75c34e4aaa83df1b2afd284353e019cb65f181a83e5cdeceb57ea32937/4b15ae02ae1bec49f3d1b6ab79c480cd3a73b10818c41a8b04be57ab6f691ef7-display-v1.webp)

`D310_image_V20.bin` заполняет каждый слот в EEPROM — прошивка чипа
стоит того, чтобы сделать это один раз:

| Слот | Сигнал | Vpp | Для |
|---|---|---|---|
| 1 | sinc, 8 лепестков | 12.17 | ограничение полосы, выброс |
| 2 | звон, Q≈6 | 17.81 | поведение при установлении |
| 3 | ЭКГ | 12.80 | демонстрация |
| 4 | ступенчатый, 16 шагов биполярный | 20.00 | линейность, разрешение |
| 5 | выпрямленная синусоида | 10.00 | как в оригинале, но вычисленная |
| 6 | многотоновый, 5 тонов | 20.00 | интермодуляция |

Две детали, которые важны и которые легко сделать неправильно:

**Центрирование по нулю лучше растяжения.** Очевидный ход — растянуть каждую
кривую на весь диапазон значений. Не делайте так: смещение постоянной составляющей
в приборе идёт по отдельному аналоговому тракту и добавляет *фиксированное*
напряжение, тогда как постоянная составляющая растянутой асимметричной кривой
масштабируется *вместе с амплитудой*.
Вам пришлось бы заново подстраивать смещение каждый раз при изменении уровня.
Размещение естественного нуля сигнала на нуле преобразователя стоит от 0.2 до
1 бита — против 16 LSB шума, который оригинальный аналоговый тракт уже
вносит. Это не реальная потеря.

**Масштабируйте в плавающей точке, округляйте один раз.** Округление сначала и
растяжение после даёт погрешность в 1.0–1.5 шага квантования; масштабирование в
float и однократное округление даёт оптимальные 0.5.

Каталогу нужен байт идентичности для каждой кривой (контрольная сумма 1280
байт кривой, начальное значение `55h`) и минимум/максимум в виде 10-битных значений,
выровненных влево на шесть битов. Ошибётесь с байтом идентичности — и прибор
покажет **Err 8** и откажется от произвольного источника — что и произошло
при первой реальной прошивке.

---

## Симулятор в браузере

`PM5139_Simulator.html` — это один самодостаточный файл — без этапа сборки,
без зависимостей, без сети. Откройте его, и оригинальная прошивка V1.3 загрузится
прямо перед вами.

Ядро 8051 выполняет реальный код. Таймеры, прерывания, C-шина и I²C
эмулируются; дисплей декодируется из фактического потока данных PCF8576,
а клавиши генерируют широтно-импульсно кодированный сигнал SAA3007 на P3.3. RAM
с батарейным питанием предварительно загружена, а произвольный EEPROM генерируется при
запуске и проверяется самой прошивкой.

Холодный старт занимает около 9 миллионов инструкций, так что дайте ему секунду.

---

## Структура репозитория```
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 …

Использование инструментов

Всё, что вам нужно — это Python 3 и Node. matplotlib для графиков, pillow и numpy только для lines.py.

Просмотр прошивки```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:~
### Сборка 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

Создание произвольной EEPROM```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:~
### Добавить мелодию```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 пропатчивает образ один раз и отказывается делать это дважды — соберите свежий V2.0 с помощью mkv20.py, если хотите начать заново.

Воспроизведение аккорда```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:~
### График```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

Измерение параметров в эмуляторе

Каждая из этих команд выводит таблицу, которую можно сверить с документацией:```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:~
### Чтение схемы```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

Воспроизведение всего

Вся цепочка сборки детерминирована — эти команды пересобирают прошивку и образ EEPROM байт в байт:```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:~
---

## Прошивка обратно

> **Сохраните оригинальную EPROM.** Считайте её дважды, сравните дампы,
> положите микросхему в ящик. Всё здесь обратимо только при условии, что
> она у вас всё ещё есть.

Прошивка при включении проверяет сумму байтов по занятому диапазону и
сравнивает её с байтом сразу после него. Ошибётесь — получите `Err 1` и
бесконечный цикл: прибор не загрузится. `romfix.py` вычисляет и вставляет
правильное значение; каждый скрипт сборки здесь уже вызывает его.

| Версия | Диапазон | Байт контрольной суммы | Значение |
|---|---|---|---|
| V1.3 | `0000h–AC6Fh` | `AC70h` | `F2h` |
| V1.5 | `0000h–B3C9h` | `B3CAh` | `99h` |

Две вещи, выясненные на реальном железе методом проб и ошибок:

- Произвольной EEPROM нужно пересчитать её **идентификационные байты**,
  иначе при каждом запуске получаете `Err 8` и источник ARB нельзя
  выбрать.
- Если ARB ведёт себя странно после прошивки, проверьте, что контакт 28
  разъёма правильно посажен, прежде чем подозревать образ.

---

## Насколько это надёжно?

Всё, помеченное как проверенное, было подтверждено вызовом оригинальных
подпрограмм в эмуляторе в нескольких точках, обычно с перекрёстной
проверкой по листингу или схеме.

Там, где что-то пошло не так, это записано, а не тихо исправлено:

- **Три бага эмулятора** (`ACALL` как `AJMP`, отсутствующий флаг AC,
  удвоенное прерывание клавиатуры) были активны в средней фазе проекта.
  Все затронутые находки были переизмерены afterwards — битовая карта
  дисплея вернулась идентичной, назначение строба совпало с сервисным
  руководством, а в разделе 16 обнаружились два пропущенных строба.
- **Синтетический образ NVRAM**, который никогда не считывался из
  реального прибора, сфальсифицировал две находки, включая «поворотная
  ручка работает только в одном направлении». Исправление состояло в
  том, чтобы передать прошивке недействительный NVRAM и позволить ей
  записать собственное заводское состояние.
- **Вручную заданные состояния RAM** порождают конфигурации, которые
  прибор никогда не принимает. Дважды это привело к неверным выводам,
  один раз — к падению в таблицу команд.
- **`core.js` считает одну инструкцию за такт**, а не машинные циклы.
  Годится для порядка, неверно для абсолютного тайминга — утверждения о
  таймингах здесь взяты из даташита MCS-51.

Всё, что является предположением, а не измерением, указано в тексте.

---

## Что ещё открыто

- **36 из 128 бит состояния** требуют стимула вне шести рабочих
  профилей — самотестирование, пути ошибок, трафик интерфейса.
- **Поля NVRAM начиная со смещения 0Dh.** Раскладка до этого места
  измерена (`NVRAM offset + 4Bh = RAM address`), контрольная метка
  понятна (сумма байтов, начальное значение `AAh`, 25 байт).
- **Какая произвольная команда достигает какого из 13 подблоков** в
  области `8871h`. Существует только четыре прямых сравнения токенов;
  остальное ветвится по проверкам битов.
- **Может ли удалённая команда обойти проверку диапазона параметров.**
- **Подпрограммы загрузки формы сигнала** — самая сложная оставшаяся
  зависимость для полной реимплементации; без них нет выходного сигнала.
- **Как PM5139 получает 20 МГц из того же тактового сигнала**, что и
  его 10-мегагерцовый собрат. Цепочка подразумевает, что его
  низкочастотный фильтр находится на 10 МГц вместо 5 МГц, но для
  подтверждения нужен мануал PM5139.

Если у вас есть один из этих приборов, две вещи очень помогли бы:
**сервисное руководство PM5139** и дампы **других версий прошивки**
(V1.4 может существовать, а может и нет).

---

## Источники

- **`pm5138A_service_manual.pdf`** — основной источник по аппаратной
  части. 176 страниц, OCR; сплошной текст читается чисто через
  `pdftotext -layout`, схемы приходится рендерить как изображения.
  Страницы 4-3 — 4-28 отсутствуют в скане.
- **Руководство пользователя PM5139** (Fluke) — трёхъязычный скан без
  текстового слоя; раздел 3.7.4.6 документирует произвольные команды.
  Стоит прогнать OCR самостоятельно — английская часть это страницы
  PDF 13–145.
- **Руководство пользователя PM5136** — полезно как перекрёстная
  проверка: его номера ошибок и список команд показывают, каких
  параметров не хватает самой младшей модели, что независимо
  подтвердило порядок параметров в ROM.
- **Даташит всех трёх моделей** — рабочие пределы по каждой форме
  сигнала.

Руководства являются сторонними документами и **не распространяются в
этом репозитории**. Их можно найти в интернете.

---

## Лицензия и использование

Два вида материалов на разных условиях — точный охват см. в
[LICENSE](https://github.com/doctormord/philips-pm-5139-5138a-5136-firmware-project/blob/main/LICENSE):

- **Работа по реверс-инжинирингу под MIT.** Документация, инструменты,
  оба эмулятора, таблицы символов, аннотации, сгенерированные формы
  сигналов и графики. Используйте как угодно.
- **Прошивка Philips не наша, чтобы её лицензировать.** Образы ROM,
  заводские дампы микросхем, листинги дизассемблирования и браузерный
  симулятор (который встраивает образ V1.3) воспроизводят или
  производны от работы Philips. Они здесь как объект изучения, для
  совместимости, ремонта и документирования приборов, которые уже
  десятилетия не поддерживаются. Там, где примешана наша собственная
  работа — аннотации, исправленная форма сигнала в V2.0 — только этот
  вклад под MIT.

Если вы обладаете правами на оригинальную прошивку и возражаете,
откройте issue, и она будет удалена.

Если вы используете что-либо из этого, ссылка обратно приветствуется.
Если найдёте ошибку, откройте issue — каждое утверждение здесь называет
адрес или измерение, на котором оно основано, так что оно должно быть
опровержимым.
Скачать инструмент
Дизассемблированиеполностью для обеих версий, ~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 — заводской дефект исправлен, контрольная сумма обработана, проверено в эмуляторе и на реальном оборудовании
ПозицияТипФункция
D301PCB80C652ядро 8051 с аппаратным I²C, 12 МГц
D30627512EPROM программы — V1.3 занимает 0000h–AC70h
D310X28C64EEPROM произвольных форм сигнала на шине MOVX
D305PCF8570256 байт NVRAM с батарейным питанием на I²C (A0h)
D304-APCF8576драйвер LCD на I²C (70h), буфер 20 байт
D302-ASAA3007кодировщик клавиатуры, широтно-импульсное кодирование по одной линии
D30774HCT4514декодер стробов — номер строба задаётся битами адреса A8…A11
mkdoom.pymkpoly.py
Голосаодиннесколько одновременно
Волновая формата, что загруженасвоя таблица аккордов
Уровенькак его оставила передняя панельзадаётся явно, измерено 11,6 Вpp
Использовано ROM182 байта2617 со встроенным риффом, 6185 из MIDI-трека