Продолжаю серию статей (раз, два) про "тетрисы" и используемые в них микроконтроллеры. В предыдущих частях был описан 4-битный контроллер Holtek, вариации которого использовались в Brick Game и многих других портативных электронных играх, выпускавшихся в 90-х годах.
С тех пор я декапсулировал и отснял под микроскопом больше десятка кристаллов этого семейства из различных игр. Среди них были как привычные Brick Game и разные электронные брелки, так и игры от именитых производителей, например из серий Nintendo Mini Classics и Bandai Mame. Для девяти из них удалось прочитать ПЗУ и добавить в эмулятор из предыдущей статьи.

Естественно, мне встречались микроконтроллеры не только Holtek, но и других производителей. Их я тоже изучал и по возможности дампил и эмулировал. Об одном таком я расскажу подробнее в этой статье.
Apollo 126 in 1 English Talking (B0202)

Мой экземпляр выпущен в апреле 1995 года. Корпус чёрного цвета, обычный для линейки Apollo. Расположение сегментов на дисплее точно такое же, как на многих других Brick Game, в том числе E-23 из предыдущей статьи. Но набор игр немного нестандартный: кроме обычных тетрисов и разных гонок-стрелялок, есть, например, подобие Frogger и слот-машина. Ещё одна отличительная особенность – голосовые комментарии, сопровождающие игры.
Под каплей компаунда обнаружился микроконтроллер Sunplus с маркировкой PA2071-160:

Первым делом, необходимо было определить название микроконтроллера или хотя бы его семейство, по которому можно будет попробовать найти документацию. Для поиска у нас есть сразу несколько параметров, определяемых по фотографии: производитель Sunplus, ПЗУ 20 КБ, ОЗУ 192 Б, ЖК драйвер на 40 сегментов и 8 общих выводов, 8-битное ядро 6502. Плюс к этому так как игра имела голосовое сопровождение, у искомого контроллера должны быть соответствующие возможности. С этой информацией идём на archive.org и исследуем самые ранние бэкапы sunplus.org.tw, находим там такие красивые таблицы и по ним подбираем максимально подходящий SPL02B. Можно заметить несоответствие размера ПЗУ: заявлено только 19.5 КБ, а у нас вроде как 20, причина расхождения будет понятна позже.
Документация на SPL02B, к счастью, доступна, хотя и очень поверхностна, что обычно для специализированных микроконтроллеров того времени.

Есть ещё некоторая нестыковка по контактным площадкам: на нашем кристалле присутствуют две лишние вокруг VDD2 и VDD3, которых нет на схеме из даташита (рисунок слева). Не знаю, с чем это связано, то ли не совсем верно определен микроконтроллер и это не SPL02B, а, допустим, SPL02A (документации на который найти не удалось); то ли площадки изначально не предназначены для разварки, на что намекает отсутствие на них следа от зонда тестирования.
Несмотря на эти небольшие нестыковки, в целом микроконтроллер установлен и можно оценить его параметры по официальной документации: 8-битное ядро (урезанная вариация 6502) с частотой до 1.5 МГц, 19.5 КБ ПЗУ, 192 Б ОЗУ, возможность подключения 32 кГц часового кварца, а звуковая часть включает два канала тонов, канал шума и речевой канал. Очевидно, характеристики заметно выше, чем у Holtek из предыдущей статьи.
Рассмотрим каждый параметр подробнее.
6502

На фото слева ядро SPL02, а справа оригинальный MOS 6502D. Видно, что топологии совершенно разные, только структура остаётся той же: дешифратор инструкций сверху, восьмиразрядное АЛУ снизу и управляющая логика между ними.
ПЗУ

Цифрами слева обозначены участки, соответствующие битам каждого разряда, а цветными прямоугольниками размечены области основной памяти программ и четырёх дополнительных банков. Если перевернуть карту распределения адресного пространства из даташита, то порядок блоков ПЗУ будет соответствовать тому, что мы видим на фото:

Изучая структуру ПЗУ, можно понять причину несоответствия фактического объема памяти заявленному в документации: первые 512 байт, отведённые под ОЗУ и регистры, физически присутствуют на кристалле, но не адресуются. Если увеличить участок ПЗУ с каждым нулевым битом, занимающий адреса $0000 - $07FF, то видно, что первые 512 байт просто заполнены нулями (та же картина наблюдается и для остальных разрядов):


Слева показан увеличенный фрагмент, обведённый красным, где подписано значение ячеек памяти. Видно, что разрешение снимка оставляет желать лучшего, но у меня просто нет достаточно качественных объективов для съёмки больших областей такого плотного техпроцесса. Хотя даже так, биты вполне читаемы, а поигравшись с фокусом и апертурой, у меня получилось сделать фотографии подходящие для автоматического распознавания по разности контраста, с приемлемым количеством ошибок, в тысячные доли процента. Но даже тысячные процента от 163840 бит – это сотни ошибок, раскиданных по изображению с разрешением около 6500x14000, которые потом я искал и исправлял вручную, просматривая фотографию бит за битом. Долгая и нудная работа.
Фото всего ПЗУ в двух вариантах съёмки можно найти здесь.
ОЗУ

Размер ОЗУ – 192 байта: первые 48 байт занимает видеопамять, где каждые 6 байт отводятся под один общий вывод, а каждый бит в этом блоке управляет одним из 48 сегментов. Так как микроконтроллер поддерживает только 40 сегментов, каждый шестой байт не используется. Остальные 144 байта доступны для общего назначения, и организации стека.
Регистры ввода-вывода и управления
Как и у большинства микроконтроллеров, SPL02 имеет специальные регистры, управляющие периферией и режимами работы. Под них отведено 64 байта по адресам $00C0 - $00FF, хотя фактически используется только 14 регистров. И здесь возникает одна из основных сложностей для эмуляции этого микроконтроллера, потому что в даташите регистры описаны в общих чертах, для большинства даже не указаны адреса. Частично эту проблему помогла решить среда разработки G+ IDE for 6502, в которой есть заголовочные файлы для этого микроконтроллера с более детальным описанием регистров:
;----------------------------------------------
; I/O configuration for hardware system
;----------------------------------------------
P_C0H_IO_Ctrl EQU 0C0H
;?? duty
; ? cpu clock
; ? Frosc
; ? port b 1:0
; ? port a 7:4
; ? port a 3:0
P_C1H_PortA_DataPort EQU 0C1H
P_C3H_PortB_DataPort EQU 0C3H
P_C4H_Freq_ToneA EQU 0C4H
P_C6H_Freq_Vol_ToneA EQU 0C6H
P_C8H_Freq_ToneB EQU 0C8H
P_CAH_Freq_Vol_ToneB EQU 0CAH
P_CCH_Noise_Type EQU 0CCH
P_CEH_Noise_Vol EQU 0CEH
P_D0H_Standby_Normal_Config EQU 0D0h
P_D2H_INT_Config EQU 0D2h
P_D4H_Speech_Mode EQU 0D4h
P_D5H_Speech_Data_Port EQU 0D5h
P_D7H_Bank_Select_Port EQU 0D7h
А некоторые моменты получилось выяснить уже на практике – запуская код на реальном железе. Об этой возможности я расскажу чуть дальше.
Звук

Отличительной особенностью этого микроконтроллера является наличие 7-битного ЦАП через который выводится звук с двух тональных каналов, одного канала шума и речевого канала, который непосредственно управляет ЦАПом через регистр Speech. Благодаря последнему, есть возможность программного воспроизведения 7-битного звука – именно это позволяет "тетрису" Apollo комментировать игру разными короткими, неразборчивыми фразами.
На самом деле, даже семью битами при достаточной частоте дискретизации можно воспроизводить относительно качественный звук, но из-за ограниченного размера ПЗУ, разработчики не могли позволить себе в полной мере раскрыть потенциал микроконтроллера. Я немного изучил код воспроизведения и выяснил, что фразы хранятся в 4-битном PCM, по два сэмпла на байт, которые табличной подстановкой растягиваются на весь 7-битный диапазон и воспроизводятся с частотой около 6 кГц. Даже при таких ограничениях, полусекундное воспроизведение слова "Apollo" занимает 1406 байт.

Эмуляция
Прошлую статью я закончил упоминанием эмулятора BrickEmuPy, который я тогда написал под микроконтроллеры Holtek и конкретно для E-23. Изначально я думал на этом и остановиться, но тема эмуляции меня так увлекла, что я стал покупать другие "тетрисы" и вообще все игры с сегментными ЖК экранами 80-90-х годов, декапсулировать их для получения прошивок и добавлять в свой эмулятор.
Естественно, не все купленные игры работали на Holtek'ах, но если у микроконтроллеров было ПЗУ, которое я мог оптически прочитать, я так же добавлял их в BrickEmuPy. SPL02 стал уже 11-м семейством микроконтроллеров, поддержка которых добавлена в BrickEmuPy, а общее количество игр сейчас приближается к 50.
Видео, демонстрирующее эмуляцию Apollo 126 in 1:
Запуск внешнего кода
Отлаживая эмулятор B0202 на полученном образе ПЗУ, я наткнулся на интересный участок кода, вызываемый внешним прерыванием:
2FC0: sei ;78
2FC1: lda #$CB ;A9CB
2FC3: sta $C0 ;85C0
2FC5: lda #$00 ;A900
2FC7: sta $C6 ;85C6
2FC9: sta $CA ;85CA
2FCB: sta $CE ;85CE
2FCD: lda #$01 ;A901
2FCF: sta $C3 ;85C3
2FD1: lda #$00 ;A900
2FD3: sta $C3 ;85C3
2FD5: ldx #$00 ;A200
2FD7: lda $C1 ;A5C1
2FD9: sta $38, X ;9538
2FDB: lda #$02 ;A902
2FDD: sta $C3 ;85C3
2FDF: inx ;E8
2FE0: lda #$00 ;A900
2FE2: sta $C3 ;85C3
2FE4: cpx #$78 ;E078
2FE6: bne 2FD7 ;D0EF
2FE8: lda #$01 ;A901
2FEA: sta $C3 ;85C3
2FEC: jmp 0038 ;4C3800
;когда уже Хабр начнет подерживать разные ассемблерные нотации :(
Эта подпрограмма читает 120 байт из порта A и кладет их в ОЗУ начиная с $0038, тактируя передачу вторым разрядом порта B, после чего переходит по этому адресу. Если какие-либо данные с порта поступили, микроконтроллер естественно начнет их выполнять, а если нет и там окажется 0, что соответствует инструкции BRK, вызывающей внешнее прерывание, то история повторится пока что-то все же не будет получено.
Такой вот отладочный режим, позволяющий выполнять внешний код уже после изготовления кристаллов с масочным ПЗУ на заводе.
Мягко говоря, меня заинтересовала возможность запуска своего кода на "тетрисе". Первой мыслью было купить такой же B0202 и попробовать вызвать этот отладочный режим, но у меня лежал и ждал своего часа ещё один Apollo – 18 в 1 B0302. Распиновка у него оказалась очень похожа на B0202, поэтому я предположил, что в нем используется тот же или похожий микроконтроллер и стал экспериментировать на нем.

Первая проблема, которая могла оказаться единственной и непреодолимой – внешнее прерывание не разварено на кристалле и вызвать его штатно никак не получится. Но оказалось, что наводками на частотозадающий резистор, микроконтроллер с высокой вероятностью переходит по вектору внешнего прерывания. То есть, просто ткнув пальцем в частотозадающий резистор на плате, я увидел на площадках, куда выведены 2 младших разряда порта B, заветную картину тактирования передачи, с таймингами соответствующими коду, приведенному выше. Позже я заметил, что даже простое перетыкание батареи иногда приводило к тому же результату (многие, наверное, помнят из детства всякие странные баги появлявшиеся при смене батареек).

Первым делом, я попробовал передать инструкции просто нажимая кнопки на "тетрисе". Конечно, это будет одна команда повторённая 120 раз, но для проверки теории сгодится. И да – это сработало: микроконтроллер предсказуемо прерывал передачу на то количество тактов, которые соответствовали количеству переданных инструкций. Уже на этом этапе я столкнулся со второй сложностью: от порта A на кнопки выведено только 7 младших разрядов, так что все инструкции, у которых задействован старший бит, напрямую передать не получится. Как я это обходил расскажу чуть позднее.
Следующая задача, которую надо было решить – это сделать какой-нибудь источник данных, который бы по тактовому сигналу с порта B нажимал кнопки за нас передавал данные на порт A. Для этого я использовал Raspberry Pi Pico 2, которая будет принимать данные с ПК через встроенный последовательный порт и после того, как тактовый импульс с порта B будет стабильным некоторое время, передавать их на микроконтроллер Apollo.

Всё готово для передачи осмысленного набора инструкций, но сперва надо разобраться с проблемой отсутствующего старшего бита. Сначала, конечно, я попробовал просто не использовать такие инструкции. И здесь меня поджидала очередная проблема: программы выполнялись некорректно если использовались инструкции с абсолютным адресом. Оказалось, что, в отличие от B0202, отладочный режим B0302 кладёт полученные данные не с $0038, а с $0000. Разобравшись с этим , я наконец смог запустить свой код. Не помню точно, какой именно, но, допустим, он мог выглядеть так:
0000: sec
loop:
0001: rol $00
0003: jmp loop

Результат выполнения на GIFке слева. Программа просто бесконечно сдвигает нулевой байт ОЗУ и сегменты адресованные на него начинают мерцать. Статические сегменты – это кусочки самой программы, поскольку она располагается в области ОЗУ, отведённой под видеопамять. Обратите внимание на способ запуска: я наклеил медную ленту одним концом на дорожки около микросхемы, а второй на корпус Apollo, таким образом нет необходимости лезть внутрь чтобы вызвать прерывание.
Безусловно, имея только половину инструкций, много не напишешь. Надо добавлять какой-то исправляющий код перед выполнением основного блока программы и лучшее, что мне пришло в голову: передавать каждый байт, использующий старший бит сдвинутым вправо, а в исправляющей части сдвигать его обратно через флаг переноса, предварительно установив или сбросив его соответственно младшему разряду. Моё, возможно, путаное объяснение лучше проиллюстрировать кодом:
;исправляющий код
0000: sec
0001: rol $03
;исправляемый код
0003: .byte $54, $00
;после выполнения первых двух инструкций, "54 00" превратится в "A9 00"
;что соответсвует недоступной ранее инструкции lda #0
У этого подхода есть существенный недостаток – на одно исправление тратится 2-3 байта, что довольно расточительно, когда у нас их всего 120. Да и размер исправляющего кода будет непостоянным, что не очень удобно. Поэтому, в итоге я остановился на таком варианте подготовки кода перед отправкой:
dataSize = 74
;исправляем основной код методом описанным выше
sec
rol $17
rol $0E
rol $10
rol $15
sec
rol $16
rol $1B
;основной код исправляющий data
ldx #dataSize-1
mainLoop:
inc loBitPtr+1
clc
loop8:
loBitPtr:
ror loBit-1
beq mainLoop
lda data, X
rol
pha
dex
bmi start
jmp loop8
loBit:
;здесь лежат младшие биты
* = 120-dataSize
data:
;здесь сдвинутые вправо байты
* = 128-dataSize
start:
Таким способом получается передать 74 полных байта и положить их начиная с $0036. Наверняка можно придумать и ещё что-то более оптимальное (например передать сначала отдельный загрузчик), но я решил на этом остановиться.
На Python я написал скрипт, который принимает до 74 байт машинного кода, объединяет с программой выше и передает готовые 120 байт на Pico. Весь процесс теперь автоматизирован: остаётся только коснуться частотозадающего резистора на Apollo и Pico передаст программу, которая тут же начнет выполняться.
Считывание прошивки
Первым делом была написана программа передающая прошивку B0302 по порту B:
ldx #$00
lda #$00
bankLoop: ;обходим банки ПЗУ
sta $D7 ;записывая номер текущего в регистр выбора банка D7
byteLoop:
lda (romPtr, X) ;получаем очередной байт ПЗУ
rol
ldx #$08
bitLoop: ;сдвигаем этот байт бит за битом
and #$FE
sta $C3 ;и передаем его через 1-ый разряд порта B
ora #$01
sta $C3 ;а по 0-ому тактируем передачу
ror
dex
bne bitLoop
inc romPtrL ;инкрементируем 16-битный адрес
bne byteLoop
inc romPtrH
lda romPtrH
cmp #$20
bne byteLoop ;пока не доходим до конца банка
lda #$10 ;все банки начинаются с $1000
sta romPtrH
inc bank ;переходим к следующему банку
lda bank
cmp #$04
bne bankLoop ;пока не доходим до последнего возможного
brk ;после завершения передачи начинаем сначала
romPtr:
romPtrL:
.byte $00
romPtrH:
.byte $00
bank:
.byte $00
Переданные через порт B данные я считывал логическим анализатором. И никаких сложностей с декапсуляцией, фотографированием и распознаванием битов!
Оказалось, что у B0302 только два банка ПЗУ (на остальных данные повторяются) т.е. всего 11.5 КБ ПЗУ. Если мы обратимся к той же таблице с параметрами микроконтроллеров, то такой размер есть только у SPL03. Выходит, в этих двух Apollo стоят немного разные контроллеры и можно предположить, что цифры в кодах игр B0202 и B0302 указывают на их индекс. Тут же становится понятно, почему данные кладутся начиная с $0000 а не с $0038 – размер ОЗУ здесь ещё меньше, чем у B0202, только 128 байт. Ещё одно неприятное ограничение: вместо 7-битного ЦАП, у SPL03 только 6-битный ШИМ, причем он раскидан на два вывода, а схема Apollo использует только один, то есть фактически, для вывода звука можно использовать только 5 бит.
Bad Apollo
Изначально я планировал написать какую-нибудь простенькую игру, но после того, как стало понятно, что на все про все доступно даже не 192 байта, а только 128, эту идею пришлось отбросить. Тогда решил написать проигрыватель "Bad Apple!!" – заведомо эффектное демо для запуска на "тетрисе", при этом, если передавать несжатые данные, достаточно простое для реализации.
Видео и аудио будут храниться на Pico 2, который выступает в роли внешнего ПЗУ с последовательным доступом, а на самом Apollo программа будет запрашивать и получать данные синхронно частоте кадров, которая равна 1/16384 от частоты работы микроконтроллера, то есть примерно 42 кадра в секунду. При этом, большую часть данных занимает аудиопоток в виде 5-битного PCM с частотой около 22 кГц.
Поэкспериментировав некоторое время, я остановился на таком формате данных:
Каждый передаваемый байт (без старшего бита, как вы помните) содержит 5-битный звуковой сэмпл, и 1 бит под состояние одного сегмента. На обработку одного байта я выделил 32 цикла ядра SPL03, за это время необходимо запросить байт, получить его, закинуть сэмпл в голосовой порт и включить/выключить нужный сегмент. Всего видеопамять на SPL03 использует 32 байта, но они раскиданы на 48 байтах, которые я для удобства округлил до 64. В итоге, на каждый кадр уйдет 64 × 8 × 32, то есть 16378 циклов, так что обновляться дисплей будет синхронно с частотой кадров ЖК дисплея – что и требовалось. При этом, частота дискретизации звука получится равной 1/32 от частоты микроконтроллера, что для B0302 будет примерно соответствовать 21.7 кГц – вполне достаточно, более качественный несжатый звук просто не влезет в 4 МБ флэш Pico 2.
Подготовка данных
С аудиопотоком проблем никаких не возникло: просто открыл аудиодорожку оригинального видео в Audacity и экспортировал в 8-битный сырой PCM с нужной частотой дискретизации.
С видео пришлось повозиться дольше. Для начала, надо было определиться с частотой кадров – в оригинальном видео 30 кадров в секунду, а в B0302, как я уже упоминал, 42. И здесь можно пойти двумя путями: просто дублировать некоторые кадры подгоняя оригинальную частоту к B0302. Или наоборот, отбрасывать до получения 21 к/с – половины нужной частоты, но при этом каждый лишний кадр использовать для имитации дополнительного оттенка серого. Сравнив оба варианта, я остановился на втором.
Аудио и Видео объединяет Python-скрипт, который отбрасывает у аудиопотока лишние 3 младших бита и кладет оставшиеся в 5-1 разряды, а младший разряд устанавливает по состоянию одного из 256 сегментов перебирая их последовательно для кадр за кадром.
Проигрыватель
Данные подготовлены, пора перейти к коду, который будет их воспроизводить на Apollo:
* = $36
ldx #$36
txs
jsr clearLCD
lda #$01
sta $D4 ;включаем Speech Play Mode
main:
ldx #00
mainLoop:
lda #$02 ;просим у Pico очередной байт
sta $C3 ;по переднему фронту B1
lda #$00
sta $C3
lda $C1 ;получаем его из порта A
sta $D5 ;и сразу кладем в порт Speech
ror ;сохраняем 0-й бит с состоянием сегмента во флаг переноса
lda segStates
rol ;сдвигаем segStates влево добавляя состояние нового сегмента
bcc byteNotFull ;и одновременно проверяя, получен ли полный байт
sta $00, X ;сохраняем полученный байт в видеопамять
inx
lda #$02 ;дальше дублируем тот же процесс что и выше, но
sta $C3 ;с небольшими изменениями, чтобы исключить задержку
lda #$01 ;связанную с сохранением байта в видеопамять
sta $C3 ;и уложиться ровно в 32 цикла
sta segStates
lda $C1
sta $D5
ror
rol segStates
cpx #64 ;проходим по всем 48 + 16 байтам видеопамяти
beq main ;некритично задевая область кода
jmp mainLoop
byteNotFull:
sta segStates
jmp mainLoop
segStates:
.byte 01
* = $055B
clearLCD:
За счет удобного формата входных данных, код получился компактным, и без проблем помещается в доступные 74 байта, единственная сложность здесь была уложиться ровно в 32 цикла на обработку байта, ни больше ни меньше, иначе частота обновления видеопамяти будет не совпадать с частотой обновления ЖК дисплея, из-за чего появятся неприятные мерцания.
Что в итоге получилось:
Заключение
Конечно, Bad Apollo – это просто демо для привлечения внимания. Главным и самым сложным в описанной работе было получение прошивок ещё двух Brick Game из 90-х, что позволило написать их эмуляторы и сохранить для истории (как бы это пафосно не звучало).
Надеюсь, со временем получится сдампить и другие модели Apollo, которых, похоже, было не меньше десятка. Но, конечно, совсем не обязательно, что на всех них будет так же легко вызвать внешнее прерывание – я, например, пробовал провернуть то же самое на Apollo 12 in 1 и у меня ничего не вышло.
Если кто-то после прочтения этой статьи захочет поэкспериментировать с другими моделями Apollo, делитесь результатами в комментариях или пишите в личку, даже если они будут отрицательными. Со своей стороны, постараюсь ответить на возможные вопросы.
Исходники описанного в статье опубликованы на GitHub
Фотографии и другие материалы по Apollo B0202 опубликованы на archive.org
Комментарии (52)

Swamp_Dok
06.11.2025 08:48Не ожидал, что и в тетрисе 6502. Особенно забавно, что я тоже пишу игры под 6502 для денди) 6502 повсюду.
Это ведь ещё и эпл 2 и коммодор 64, жаль только у всех слишком разная обязка и весь софт пропитан хаками, портировать проблематично.

Azya Автор
06.11.2025 08:48О да, 6502 был очень популярен и до сих пор встречается в подобных игрушках. Nintendo, кстати, кроме Famicom, еще использовала 6502 под своим брендом в шагомере Pocket Pikachu 1998 года и в некоторых Mini classics в нулевых.

tormozedison
06.11.2025 08:48Они там что, название повторно использовали, получается? Сначала выпустили Mini Classics, а потом, когда об этой серии уже почти забыли - совершенно другую железку, и назвали её Classic Mini.

TemArtem
06.11.2025 08:486502 легенда, настоящий Z80 своего времени, дешевый, простой и достаточно мощный. Неудивительно что его пихали во все подряд, от компьютеров и консолей до тетрисов и промышленных контроллеров)

tormozedison
06.11.2025 08:48Ему ещё повезло, что малопотребляющую КМОП-версию сделали. Вот это тоже на нём.
https://compumuseum.com/player.html?machine=ggvnc1020
А на обычном (не КМОП) - «Агат» и один из двух вариантов «Аюши».

tormozedison
06.11.2025 08:48И ещё компактная плата Nippel SuperGames для встраивания в монитор «Электроника 32ВТЦ-202» и получения тем самым портативного игрового автомата.

zartarn
06.11.2025 08:48Теперь осталось это в fpga повторить, и сделать пинсовместимым) хотя по воспоминания из детства, чаще умирали 'резинки', которыми подключался дисплей.

TemArtem
06.11.2025 08:48Повторить в FPGA ядро 6502 - задача решенная, есть куча готовых реализаций. А вот реверс-инжиниринг и реализация всей периферии это уже серьезный проект, но да, вполне реальный) И тогда можно будет сделать вечный тетрис

iliasam
06.11.2025 08:48Так у автора статьи уже есть программный эмулятор - https://habr.com/ru/articles/773040/

alexey_public
06.11.2025 08:48Если программа будет ещё и отдавать оттуда кнопки - то можно будет сделать дум на тетрисе :-)

TemArtem
06.11.2025 08:48Ох как же я понимаю эту боль с поиском документации на старые чипы,когда у тебя есть только маркировка, пара мутных фотографий и бэкапы сайта производителя из archive.org. И ты сидишь как детектив и по крупицам собираешь информацию, пытаясь понять, за что отвечает каждый регистр

tormozedison
06.11.2025 08:48Ага, подтвердилась гипотеза, что там применяли 6502-совместимую платформу до конца девяностых - начала нулевых, а на полностью отличающуюся 4-битную платформу от Holtek перешли лишь потом. Именно после этого сменили мелодию и сделали, чтобы она включалась не после начала игры, а прямо на заставке, и ещё некоторые вещи поменяли. Сделать новую прошивку с нуля оказалось проще, чем переносить имеющуюся на совершенно иную систему команд.

Azya Автор
06.11.2025 08:48Тетрисы на Holtek-ах начали выпускать не позднее 93-94 гг., а эти Apollo наверно 94-95, т.е. обе платформы примерно одного времени а не шли друг за другом. Вообще, использовалось много семейств микроконтроллеров, я пока натыкался на следующие: Epson E0C62, Sanyo LC86, Holtek 4-bit, Sunplus на 6502, EMC EM73, неопознанный Toshiba, неопознанные UMC, Samsung KS56. Из них, наиболее часто мне попадались Holtek и EMC, трудно даже сказать, какой из них был более распространен. А микроконтроллеры с ядром 6502 применяются думаю и по сей день на многих, а может и на большинстве новоделах

tormozedison
06.11.2025 08:48Ничего себе, сколько разновидностей применялось. Интересно, был ли у разработчиков какой-нибудь переносимый кусок кода, который можно скомпилировать для нескольких разных архитектур? Похоже - нет, проще каждый раз с нуля с вкраплениями ассемблера. Потому они и по поведению заметно различаются.

dlinyj
06.11.2025 08:48А есть техническая возможность узнать какой контроллер? Может на новодельном можно повернуть тот финт ушами с загрузкой своего кода?

Nick0las
06.11.2025 08:48Одними из первых были E-32 и E-88, появились они не позднее 1994. Apollo, который разговаривает, появился позже и производил двоякое впечатление - разговаривает он не очень хорошо. Позднее появились дикие варианты типа 999игр и 9999игр (никто не знает чем игры отличаются и до нужной фиг долистаешь), и были еще модели "цветным" экраном. Примерно в 1998 я увидел компактный вариант, который был раза в 2 меньше классического варианта с полукруглой выемкой. Реверс "капли" это конечно круто. В детстве мы смотрели на эти "капли" в часах, калькуляторах и игрушках и расстраивались что даже названия микросхемы чтобы поискать в литературе - нет.

bodyawm
06.11.2025 08:48Круто, поставил плюс!
Видос наверное уже и смысла нет делать, если раньше процент моей работы был хотя бы 30% с статьей, где я описываю систему команд холтеков, то теперь она максимум 10 и я вложу слишком мало своего импакта в него кроме пересказа своей статьи и переозвучки фактов из вашей. Так что видос вам необходимо сделать самому :)

Azya Автор
06.11.2025 08:48Так сделать хорошее видео – это 100% работы) Сам я точно не осилю, максимум на что способен, это короткие ролики-демки. То, что кроме Холтеков использовалось много других микроконтроллеров не отменяет же факта, что одни из самых "классических" E-23 или E-88 работали именно на них.

bodyawm
06.11.2025 08:48Я пока хз. Я слишком завистливый человек, только не в плане обливания желчью, а скорее обесценивания самого себя. В такие моменты синдром самозванца появляется на все сто и чую не спроста))
у меня есть некоторые наработки для очень лампового ролика включая ирл подсъемы. Если смогу справится с самим собой, может что-то и смогу родить

VT100
06.11.2025 08:48Вперёд! Иногда я подкрепляю это цитатой из "Короля Лира", хотя там и совсем про другое:
"Дуй, Ветер! Дуй, пока не лопнут щёки!"

tormozedison
06.11.2025 08:48«Здесь можно увидеть как 7 цифровых линий приходящих слева сверху управляют напряжением на аудио-выводе в центре»
Это ЦАП R-2R такой?

VT100
06.11.2025 08:48Если не вообще COVOX...

tormozedison
06.11.2025 08:48Среднестатистический самодельный COVOX как раз на R-2R чаще всего. Оригинальный - на микросхеме ЦАП, требующей внешнего питания.
Вот этот способ хорошо подошёл бы для получения резисторного ЦАПа в один приём. Но именно для этой цели почему-то не применили.
https://rw6ase.narod.ru/index1/radiopriem/rp_pp_suw/mikro_rp.html

Azya Автор
06.11.2025 08:48Посмотрел внимательно на фото) похоже я ошибся и там токовый выход, а схема ЦАП на двоично-взвешенных резисторах.

BenGunn
06.11.2025 08:48К этому Брик Гейму еще бы добавить раскладушку Apollo в которой был платформер Croco Mario.

checkpoint
06.11.2025 08:48Интересно почему в нулевых адресах располагается область Display LCD RAM ? У MOS 6502 нулевая страница адресного пространства отводится под указатели, а первая - под стек. У SPL02 всё подругому ?

Azya Автор
06.11.2025 08:48У SPL02/03 и на одну страницу ОЗУ не наберется, поэтому все помещается на нулевой.

LTS-Pettrov
06.11.2025 08:48Ну вот такие статьи читаю не отрываясь с огромным интересом спасибо автору, у меня был тетрис тоже с pcm звуком даже по русски говорил "Давай", скорее всего с этим железом внутри.

Mimar_Sinan
06.11.2025 08:48Ура друзья ! Наконец поставлена точка в философском споре что же там в конце игры ! Там все-таки есть мультик !!!
p.s. это я смеюсь если что))), эти споры ходили среди детей про "Ну погоди"
p.p.s специально зарегистрировался чтобы оставить этот комментарий )))
dlinyj
Большая моя личная благодарность за ваши статьи. Они меня очень вдохновили. Я ковырялся с тетрисами, тоже пытался их завести. Из забавного, пытался сделать эмулятор из новодельных тетрисов. Хотя бы потому, что их валом можно купить дёшево, а старые исчерпаемый ресурс и стоят дорого.
Но в какой-то момент вдохновение покинуло меня в этом вопросе. Какие-то наработки остались, но всё либо выбросил, либо продал.
Azya Автор
Спасибо за теплые слова! В смысле вы хотели поменять железо и запустить на нем эмулятор в корпусе новодела?
dlinyj
Идея была ваш эмулятор перенести в 2040, и дальше уже грузить образы в реальном железе.
Я купил обычный дешёвый тетрис. Сделал ему реверс клавиатуры (ооочень мудрёно сделана). Скрины с видео:
Сделал плату для коннекта дисплея (которая вполне себе работала).
Даже написал свой тетрис, и игрался с этой клавиатуры в консоли. Но потом, что-то вдохновение кончилось
Azya Автор
Классно! Понимаю, тоже копятся старые недоделанные проекты, на которые иссяк запал(
dlinyj
У меня много сил отняло бодание к RP 2040, я хотел сделать многопоточку. Чтобы один поток отвечал за эмуляцию и логику, а второй за отображение и работу с кнопками. Играл и проиграл
Azya Автор
Так обычно и бывает – хочется сделать красиво и основательно, а в итоге устаешь от задачи или появляется новая более безумная идея)
jetcat78
Там же два ядра - часть задач на нулевое, часть задач на первое и всё заработает
bodyawm
У Сергея гемор с синхронизацией возник думаю. С другой стороны, я думаю острой необходимости параллелить такой проект нет, рп2040 даже на гораздо меньшем клоке, чем 150мгц, должен фуллспид эмулировать Тетрис :)
dlinyj
Спасибо, я умею читать документацию. И проблема не в синхронизации, как говорит @bodyawm . Просто либо мне попался кривой sdk, либо одно из двух. Оно не работает.
То есть, берешь пример, всё ок. Чуток обмазываешь перламутровых пуговиц, оно работает криво. В линуксе на многопоточке тот же пример ок. Я несколько месяцев бился, и понял что это не моё
jetcat78
Мы тут пол года как Game&Watch вполне себе успешно "Мурмулируем" на RP2040 и RP2350 https://github.com/MadedCat/murmulator_game_n_watch ;)
bodyawm
За пару дней до того как увидел рп2040 в супе тоже захотел выкинуть кристалл из бракованной консоли и засунуть туда пико :)
Ну, учитывая что у меня проект немного другого формата, по типу ардубоя, думаю все равно есть смысл попробовать :)
jetcat78
https://oshwlab.com/tecnocat/works
тут как раз исходники плат под RP2040Zero в корпус от SUP ;)
dlinyj
Это очень крутой проект. У меня была задача сохранить lcd от тетриса