Близятся Новый год и Рождество, а значит, ёлки и всё связанное с ними: гирлянды, украшения и, разумеется, игрушки. Праздник слишком уж весёлый, и я решил, что ему не хватает мрачной игры, поэтому напечатал маленькую ёлочную игрушку в виде модели IBM PC, засунул в неё самый маленький ЖК-дисплей, который смог найти, добавил туда процессор и логику, а также аккумулятор. Это устройство может висеть на ёлке и показывать демонстрационный режим Doom со звуком и музыкой. Но и это ещё не всё: если подключить к ней любую BLE-клавиатуру или джойстик, то можно будет самому убивать миллиметровых зомби, какодемонов и импов.

Зеркало видео на Vimeo

Оборудование

Я уже однажды портировал Doom на чип ESP32. Это было довольно заморочено, в основном из-за требований к памяти: нужно было выбрать чип ESP32 с внешней PSRAM, но даже в этом случае портирование оказалось достаточно мучительным, потому что PSRAM должна была соответствовать требованиям Doom к памяти. Исходники этого проекта я уже давно выложил.

Позже пользователь Github под ником doomhack решил, что на Game Boy Advance тоже должен быть порт Doom. Точнее, он посчитал, что Game Boy Advance необходим настоящий порт Doom. Для этой консоли выпускали коммерческую версию Doom, но она была довольно скучной, потому что создавалась на основе порта Doom на Jaguar с упрощёнными уровнями (упрощённой геометрией, меньшим количеством текстур и частично урезанными уровнями). Кроме того, были убраны некоторые другие фичи, например, враги больше не могли слышать выстрелы игрока. А ещё её зацензурили: кровь стала зелёной или пропала вообще, трупы монстров быстро исчезали, а кровавые смерти полностью убрали.

doomhack решил исправить положение, взяв PrBoom (усовершенствованную и подчищенную версию оригинального исходного кода Id Doom) и преобразовав его так, чтобы он уместился в оборудование Game Boy Advance. Это довольно впечатляющее достижение: пространство программ не было проблемой (картриджи GBA обеспечивали доступ к 32 МиБ ROM), проблема заключалась в ОЗУ: GBA имеет всего 288 килобайт ОЗУ (не относящейся к видео); хоть в некоторых картриджах могла содержаться ОЗУ, в основном она предназначалась для сохранений, была медленной и имела размер не больше 64 КиБ. Версия doomhack не использовала эту ОЗУ. Его основным трюком стало переписывание файла WAD, в котором находилась основная часть ресурсов. В обычном движке Doom загружает эти ресурсы в память и выполняет небольшие преобразования из формата на диске в формат, который можно применять в движке. doomhack решил, что большой объём ОЗУ можно сэкономить, заранее выполнив преобразование на PC, чтобы движок мог использовать этот модифицированный файл WAD. Это немного увеличило размер ресурсов, но зато они могли оставаться на картридже (отображаемом в память), а не загружаться сначала в ОЗУ.

Почему я говорю об этом? Это определяет требования к чипу, на котором я буду запускать Doom, если захочу использовать данный порт: у него должна быть ROM (или флэш-память), способная отображать в память весь переписанный файл WAD. Кроме того, у него должно быть примерно 300 килобайт ОЗУ и скорость, сравнимая со скоростью GBA, но учитывая, что консоль работала на частоте 16,8 МГц, особых проблем это не вызовет.

В конечном итоге, я остановился на ESP32C3. Это SOC на основе RiscV с частотой 160 МГц, так что Doom совершенно точно будет работать быстро. Также у него есть 400 килобайт SRAM, так что GBA-версия Doom должна отлично в ней уместиться и даже останется место для чего-нибудь ещё. В отличие от старого ESP32 (ограниченного 4 МиБ), ESP32C3 может отображать 8 МиБ флэш-памяти непосредственно в своё адресное пространство, чего вполне достаточно для файла WAD Doom. Кроме того, у него есть WiFi и, что важнее, BLE; значит, у меня есть шанс на беспроводное подключение контроллера, чтобы можно было играть в Doom. Есть и ещё один немаловажный в наше время дефицита чипов аспект: они не только широко доступны, но и я могу достать их бесплатно, потому что работаю в Espressif.

Схема довольно тривиальна. Я решил использовать модуль ESP32-C3-Wroom-02, потому что не был абсолютно уверен, что смогу без мучений настроить всё связанное с радиопередачей. Также в схеме есть ЖК-дисплей и динамик. Динамик напрямую подключён к GPIO чипа ESP32C3. Это плохая практика, потому что у динамика есть индуктивность, но учитывая то, что он крошечный, а меня не особо беспокоит долговременная статистическая надёжность устройства, риск поломки вполне приемлем. В качестве ЖК-дисплея я выбрал прямоугольный TFT-экран диагональю 0,96 дюйма, потому что это самый маленький цветной дисплей, который мне удалось найти за разумную цену. (Можно найти дисплеи и поменьше, они предназначены для видоискателей, но чертовски дороги, а их вспомогательная электроника всё равно не поместится в наш корпус.)

Особенность модулей ESP32-C3-Wrover-02 заключается в том, что у них всего 4 МиБ флэш-памяти. Мне нужно было как минимум 8 МиБ, потому что иначе бы в память не уместились данные уровней Doom. Я решил проблему, отпаяв металлическую крышку модуля и заменив флэш-память. Если вам интересны подробности, можете посмотреть записанное мной короткое видео.

Я хотел, чтобы игрушка работала от литий-ионной ячейки, поэтому мне нужна была логика зарядки. В ней есть дополнительная цепь с диодом/МОП-транзистором: при зарядке литий-ионной ячейки нельзя одновременно получать питание от неё, потому что из-за этого зарядная ИС будет считать, что ячейка всё ещё не заряжена, что приведёт к перегрузке ячейки. Поэтому дополнительные компоненты позволяют ESP32C3 работать напрямую от USB-питания, когда оно подключено. И 4,2 В от полностью заряженной ячейки, и 5 В от USB — это слишком много для работы ESP32C3, поэтому в схеме также есть LDO-регулятор HT7833 на 3,3 В, снижающий напряжение до комфортных для ESP32C3 3,3 В.

Я спроектировал для компонентов небольшую печатную плату, которая должна крепиться на обратной стороне модуля Wroom-02. KiCad выдал множество ошибок DRC и попросил проверить их. Тем не менее, плата работает; единственная найденная мной проблема заключалась в том, что при её производстве JLC немного сдвинула площадки для монтажа Wroom. Припаять его всё равно было возможно, но из-за этого оказалось очень сложно визуально проверить наличие соединения. Я решил проблему, использовав при пайке кучу флюса, а затем проверяя соединения мультиметром. Если контакта не было, то я перепаивал соединение повторно.

Программное обеспечение

Как говорилось выше, в качестве фундамента для своего проекта я использовал великий труд владельца репозитория Doomhack GBADoom; он проделал основную часть работы, необходимой для запуска Doom на одной десятой от изначально требовавшейся ему памяти. Мои изменения движка в основном свелись к удалению привязок к GBA (дисплей, ввод) и к замене на мои собственные.

Полностью изменить мне нужно было музыку. Вместо использования оригинальных композиций порт Doom для GBA проигрывал мелодии чиптюн-трекера при помощи подпрограмм, написанных на ассемблере ARM. Во-первых, их было бы сложно портировать на RiscV, во-вторых, я хотел слышать классическую OPL-версию, на которой вырос. Оригиналы композиций Doom хранились в формате .mus, проприетарном без библиотеки проигрывателя. Кроме того, они были записаны в «универсальном» формате, то есть конкретный звуковой драйвер должен был преобразовывать инструменты в то, что требуется для его звукового оборудования. Для реализации всего этого в ESP32-C3 потребовалось бы много труда.

К счастью, я нашёл способ обойти все эти проблемы. Можно было при помощи wadext взять из WAD файлы MUS (и файлы soundfont, необходимые для воспроизведения файлов MUS на OPL, например, те, которые использовались в Sound Blaster или Adlib), а затем преобразовать их в файлы .imf при помощи imf-creator. Файлы .imf имеют гораздо более простой музыкальный формат: они предполагают наличие чипа OPL и просто содержат операции записи в регистры OPL и задержки; реализовать проигрыватель для такого формата очень просто. Очевидно, что в ESP32S3 нет чипа OPL, но это легко исправить: существует довольно много хороших эмуляторов OPL; я выбрал тот, который используется в проекте Dosbox. Вывод аудио отправляется на периферию I2S в режиме PDM, похожем на PCM тем, что можно напрямую подключить динамик, и это будет звучать хорошо. Чтобы повысить амплитуду, я сконфигурировал один GPIO на вывод сигнала PDM, а второй — на вывод обратного ему, чтобы можно было подключить динамик прямо между ними и получить удвоенную громкость. Этого тоже не стоит делать в производственных изделиях, потому что GPIO обычно плохо рассчитаны на подачу индуктивных нагрузок, но учитывая то, что у чипов ESP32, по моему опыту, довольно надёжные GPIO и мы не собираемся пускать устройство в производство, всё должно быть нормально.

Также мне хотелось создать подсистему ввода. Ёлочная игрушка с Doom была бы интересной, даже если бы просто проигрывала демо, но мне очень хотелось и самому поиграть в Doom. Я не хотел подключать устройство физически, поэтому использовал для этого BLE: хоть довольно сложно найти клавиатуру, мышь или джойстик, рассчитанные только на BLE и не ожидающие, что у хоста есть Bluetooth Classic, это вполне реализуемо.

Для этого я хотел использовать легковесный стек Bluetooth LE под названием NimBLE, потому что альтернативы занимали бы слишком много памяти. Насколько я знаю, пока нет никаких примеров его применения для работы с HID-периферией (клавиатурами, мышами, джойстиками и так далее), поэтому пришлось создавать всё самому. Позаимствовав части из libusbhid FreeBSD и примера NimBLE ble-central, я быстро получил результат, теоретически позволяющий управлять Doom с любого BLE-устройства, имеющего возможности HID: достаточно переключить его в режим сопряжения, чтобы ESP32C3 подключился к нему.

Корпус

Очевидно, основной платформой для игры Doom был IBM PC, поэтому, как уже говорилось в начале статьи, я решил использовать в качестве исходника 3D-модель чего-нибудь, напоминающего по форме PC. Больше всего походило бы на машину, которая использовалась для игры в Doom, что-то наподобие PC или компьютера, совместимого с процессором 386/486; однако из-за ограниченного пространства более подходящей моделью оказалась IBM XT без подставки под монитор. (В этом есть и определённый смысл: лично я собирал свой первый PC из любых деталей, которые мог достать; кто-нибудь вполне мог засунуть купленную с рук материнскую плату 486 в корпус IBM PC-XT.)

На Thingiverse уже есть готовая модель, рассчитанная на печать из нескольких частей с разными цветами на FDM-принтере. Я импортировал детали корпуса и монитора в OpenSCAD, соединил их и вырезал внутреннюю нишу.

Вот, как выглядит модель в разрезе. Самый большой отсек предназначен для электроники и аккумулятора. В отсек поменьше устанавливается ЖК-дисплей. Повторюсь: насколько я знаю, дешёвых экранов на 0,6 дюйма не существует, поэтому я взял дисплей на 0,96 дюйма, который как раз помещается в корпус. Через экран монитора видна только верхняя часть дисплея, поэтому в неё отрисовывается повёрнутый и уменьшенный буфер кадров Doom размером 80x60 пикселей.

Модель можно напечатать на FDM-принтере, но мне не нравится полосатая текстура печати, поэтому я намеревался печатать на SLA-принтере. Но он решил умереть как раз тогда, когда я готовился к печати. Я отправил файлы в JLC (ту же компанию, которая изготовила PCB). (Уточню, что она меня не спонсирует.) Так как SLA не могут печатать разными цветами, я добавил серые и чёрные акценты при помощи баллона для покраски моделей. Хоть белая смола на непокрашенных областях всё равно слишком белая, в целом результат меня вполне устроил.

Я припаял к печатной плате все внешние компоненты: крошечный динамик, кнопку питания, разъём micro-USB для зарядки и программирования, ЖК-дисплей. Как говорилось выше, экран намного больше, чем пластмассовая модель, и занимает почти всю высоту корпуса.

А вот как всё умещается в нижней части модели. Аккумулятор и печатная плата сэндвичем установлены в основной отсек, динамик приклеен к решётке динамика модели, а кнопка питания и USB-разъём вклеены в соответствующие отсеки. У распечатанной мной 3D-модели есть и тонкая нижняя крышка, которая защёлкивается в четыре отверстия. К сожалению, эта крышка была такой тонкой, что получилась слишком гибкой, поэтому в конечном итоге я приклеил и её.

Заключение

Вот так я собрал крошечную ёлочную игрушку с Doom из дешёвого чипа ESP32 и маленького ЖК-дисплея, выдающего себя за ещё более мелкий дисплей. Как обычно, весь проект (схемы, печатная плата, дизайн корпуса, прошивка) выложен в открытый доступ, поэтому при желании вы можете сами украсить свою новогоднюю ёлку маленьким компьютером с Doom.

Счастливого Нового года и Рождества!