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

С тех пор я декапсулировал и отснял под микроскопом больше десятка кристаллов этого семейства из различных игр. Среди них были как привычные Brick Game и разные электронные брелки, так и игры от именитых производителей, например из серий Nintendo Mini Classics и Bandai Mame. Для девяти из них удалось прочитать ПЗУ и добавить в эмулятор из предыдущей статьи.

Ушастый виртуальный питомец, который тоже работает на 4-битном Holtek
Ушастый виртуальный питомец, который тоже работает на 4-битном Holtek

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

Apollo 126 in 1 English Talking (B0202)

Стандартный для линейки Apollo корпус и коробка
Стандартный для линейки Apollo корпус и коробка

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

Под каплей компаунда обнаружился микроконтроллер Sunplus с маркировкой PA2071-160:

Микросхема Sunplus с маркировкой на кристалле PA2071-160
Микросхема 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 и 6502 (оригинальное фото)
Сравнение ядра SPL02 и 6502 (оригинальное фото)

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

ПЗУ

ПЗУ на кристалле
ПЗУ на кристалле

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

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

Часть ПЗУ содержащая каждый 0-ой бит из 2048-ти байт основной памяти
Часть ПЗУ содержащая каждый 0-ой бит из 2048-ти байт основной памяти
Фрагмент ПЗУ с подписанными состояниями ячеек
Фрагмент ПЗУ с подписанными состояниями ячеек

Слева показан увеличенный фрагмент, обведённый красным, где подписано значение ячеек памяти. Видно, что разрешение снимка оставляет желать лучшего, но у меня просто нет достаточно качественных объективов для съёмки больших областей такого плотного техпроцесса. Хотя даже так, биты вполне читаемы, а поигравшись с фокусом и апертурой, у меня получилось сделать фотографии подходящие для автоматического распознавания по разности контраста, с приемлемым количеством ошибок, в тысячные доли процента. Но даже тысячные процента от 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 байт.

Здесь можно увидеть как 7 цифровых линий приходящих слева сверху управляют напряжением на аудио-выводе в центре
Здесь можно увидеть как 7 цифровых линий приходящих слева сверху управляют напряжением на аудио-выводе в центре

Эмуляция

Прошлую статью я закончил упоминанием эмулятора 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, поэтому я предположил, что в нем используется тот же или похожий микроконтроллер и стал экспериментировать на нем.

Основная плата B0302. Красным подписаны выводы, используемые в отладочном режиме.
Основная плата B0302. Красным подписаны выводы, используемые в отладочном режиме.

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

 Канал 1: линия 0 порта B; канал 2: линия 1 порта B.
Канал 1: линия 0 порта B; канал 2: линия 1 порта 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

Комментарии (10)


  1. dlinyj
    06.11.2025 08:48

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

    Но в какой-то момент вдохновение покинуло меня в этом вопросе. Какие-то наработки остались, но всё либо выбросил, либо продал.


    1. Azya Автор
      06.11.2025 08:48

      Спасибо за теплые слова! В смысле вы хотели поменять железо и запустить на нем эмулятор в корпусе новодела?


      1. dlinyj
        06.11.2025 08:48

        Идея была ваш эмулятор перенести в 2040, и дальше уже грузить образы в реальном железе.
        Я купил обычный дешёвый тетрис. Сделал ему реверс клавиатуры (ооочень мудрёно сделана). Скрины с видео:

        Схема распайки клавиатуры
        Схема распайки клавиатуры

        Сделал плату для коннекта дисплея (которая вполне себе работала).

        Даже написал свой тетрис, и игрался с этой клавиатуры в консоли. Но потом, что-то вдохновение кончилось


        1. Azya Автор
          06.11.2025 08:48

          Классно! Понимаю, тоже копятся старые недоделанные проекты, на которые иссяк запал(


          1. dlinyj
            06.11.2025 08:48

            У меня много сил отняло бодание к RP 2040, я хотел сделать многопоточку. Чтобы один поток отвечал за эмуляцию и логику, а второй за отображение и работу с кнопками. Играл и проиграл


            1. Azya Автор
              06.11.2025 08:48

              Так обычно и бывает – хочется сделать красиво и основательно, а в итоге устаешь от задачи или появляется новая более безумная идея)


  1. iliasam
    06.11.2025 08:48

    Хабр - торт!


  1. Swamp_Dok
    06.11.2025 08:48

    Не ожидал, что и в тетрисе 6502. Особенно забавно, что я тоже пишу игры под 6502 для денди) 6502 повсюду.

    Это ведь ещё и эпл 2 и коммодор 64, жаль только у всех слишком разная обязка и весь софт пропитан хаками, портировать проблематично.


    1. Azya Автор
      06.11.2025 08:48

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


  1. zartarn
    06.11.2025 08:48

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