(также опубликовано в моем блоге)
- Работает ли на нем DOOM?
- Аналогичная работа & самоналоженные ограничения
- Итак, как выглядит среда выполнения Micropython?
- …и формат Hatchery?
- Хитрый план №1, и провал.
- Шлифовка…
- Инструментарий не работает.
- Сбой во время выполнения.
- Хитрый план №2, и успех (в конце концов!)
- Возвращение к основам
- Формируется новый план
- Аккуратно делает это…
- DOOM вернулся, детка!
- Последние исправления
- Покажите мне код!
- Необходимое демонстрационное видео…
Работает ли на нем DOOM?
Этот вопрос становится дефакто для любой технологии с дисплеем… Холодильники, принтеры, всевозможные вещи могут запускать DOOM.
Так почему же не этот блестящий новый электронный значок на базе ESP32S3, который я только что получил от ElectroMagnetic Field 2022? Он имеет приличный двухъядерный 32-разрядный процессор Xtensa MCU, 8 МБ оперативной памяти, 8 МБ флэш-памяти (разбитой на разделы — см. ниже) и цветной пиксельный дисплей ST7789.
О, и джойстик 🙂
Аналогичная работа & самоналоженные ограничения
Существует порт DOOM для ESP32, доказывающий, что это можно сделать, однако я наложил на себя следующие дополнительные ограничения:
- Сосуществовать со средой Micropython, которая предустановлена на значке.
- Создать приложение, которое можно распространять из инкубатора TiDAL.
Итак, как выглядит среда выполнения Micropython?
- Она основана на Espressif IoT Development Framework (IDF), который определяет схему разделения хранилища, которую я не могу заменить (но могу использовать в своих интересах…).
- ESP-IDF, и, соответственно, Micropython не разрабатывались как ОС с учетом загрузки/выгрузки приложений во время выполнения…
- Micropython является расширяемым во время выполнения, через предварительно созданные модули формата
.mpy
, которые могут быть скомпилированы изC
.
…и формат Hatchery?
- Папка с именем приложения (например:
/apps/Doom
), которая должна быть модулем python (т.е. содержать файл__init__.py
), плюс любые другие файлы, необходимые для запуска. __init__.py
должен определять экземпляр классаApp
(или подкласса), который оболочка бейджа может найти/загрузить.
Хитрый план №1, и провал.
Насколько сложно просто скомпилировать DOOM как расширение .mpy
, переделать ввод-вывод (дисплей, файлы, пользовательский ввод) обратно на Micropython и загрузить его?
Оказывается, формат расширений Micropython является очень домашним (по вполне уважительным причинам), и имеет значительные ограничения по сравнению с готовыми форматами приложений, такими как ELF
или COFF
, в частности:
- Нет объявленных данных, нет поддержки для загрузки сегмента
.data
как части модуля. - Нет статических глобальных переменных (вы знаете, основа большинства программ на Си!), так как он не может перераспределять ссылки при загрузке в память, можно использовать только видимые глобальные переменные.
Мне также нужно было выбрать начальную версию DOOM, с акцентом на переносимость. Это оказалось проще простого — я выбрал DOOM generic, который с радостью скомпилировался и запустился с первого раза на моем рабочем столе Linux 😉
О, и, конечно же, мне пришлось написать новый скалер для видео, чтобы уменьшить 320×200 => 240×135.
Шлифовка…
Итак, сначала удаляем все объявленные данные из исходников DOOM, перенося инициализацию в подходящие функции xx_Init()
, если они есть, или добавляем их, если нет. Изменено ~2000 строк кода (LoC), ряд уродливых угловых случаев просто удален (например, возможность изменять привязки клавиш) и он снова работает на рабочем столе — фух!
Теперь удалите ключевое слово static
из всех глобальных переменных и снабдите их аббревиатурой локального модуля, чтобы избежать коллизий. Еще ~2000 LoC изменено, и он все еще работает на рабочем столе — двойное счастье, поскольку это заняло несколько дней!
Сложите все эти изменения в большой файл git patch
и сохраните его. Готово.
Инструментарий не работает.
Компилируем все в соответствии с прилагаемым инструментарием Micropython. Обнаружите, что он не может связать определенные объектные файлы — бррр 🙁
Исправьте множество ошибок в Micropython.
Соберите все исправления ошибок Micropython в файл git patch
и сохраните его. Готово?
Сбой во время выполнения.
Неа. Оказывается, Micropython делает больше предположений о том, как компилируется код,
в частности, что все вещи можно перемещать в памяти через единую таблицу глобальных смещений (GOT). DOOM делает другие предположения и неоднократно падает в непредсказуемых местах из-за повреждения памяти.
После нескольких дней попыток отладить это фиаско (с помощью printf
, конечно, поскольку я не могу использовать отладчик из-за проприетарного форматирования модулей и специального загрузчика), я сдаюсь.
Хитрый план №2, и успех (в конце концов!)
Возвращение к основам
Так и не сумев ничего запустить, я решаю начать с другого конца, создав программу Hello Mum!, которая полагается только на встроенный ESP-ROM (и скомпилирована без «специального инструментария» Micropython, который создает мне проблемы), предназначенную для запуска в качестве первого пользовательского кода на устройстве, на языке ESP-IDF, в качестве «вторичного загрузчика». Так родился phlashboot
, самая короткая программа, которую я могу написать, которая говорит «Привет» без сбоев.
После некоторых проб и ошибок со сторожевыми таймерами, это работает — облегчение!
Формируется новый план
Помните, я упоминал о таблице разделов Partition table
, которую использует ESP-IDF? Она помещает код приложения в определенное место во флэш-памяти^ (в один из двух разделов «over-the-air» или OTA update), вместе с заголовком, определяющим, в каком месте памяти он запускается, в том числе, если он отображается в памяти. Обычный процесс загрузки приложения ESP-IDF заключается в том, что вторичный загрузчик применяет определенное отображение памяти или копирует код/данные в оперативную память (привет .data
!), а затем переходит к определенной точке входа. Замечательно.
Вот как работает сам Micropython, могу ли я повторно использовать тот же поток для загрузки/запуска моего приложения в известных местах памяти вне Micropython, без необходимости использовать сломанный формат модуля .mpy
? Baby steps ensue…
^ для тех, кто задается вопросом, почему бы не сделать карту памяти из файловой системы части flash? MMU недостаточно гранулирован для этого, поэтому приходится создавать карту из определенного смещения раздела ‘OTA’.
Аккуратно делает это…
Я адаптирую phlashboot
так, чтобы он не ожидал, что будет загрузчиком, вместо этого он выглядит как OTA-приложение, которое вторичный загрузчик ESP-IDF с удовольствием отображает в памяти (mmap) и запускает, следующий шаг…
После долгого чтения технических руководств и копания в коде, создания тестового приложения romread
и в целом поколачивания (и падения) моего устройства, я понял, как работает менеджер памяти, что я могу трогать, а что нет, и теоретически, как я могу создать карту памяти и выполнить другое приложение в micropython. Также было проведено достаточное количество исследований по компоновке памяти Micropython, см. папку memstuff
! Так родился doomloader.mpy
— короткий и отдельный модуль Micropython, который обращается к MMU для загрузки OTA-приложения в безопасное для Micropython адресное пространство. Это работает :D, следующий шаг…
DOOM вернулся, детка!
Теперь я могу использовать стандартные инструменты, чтобы создать стандартное приложение и запустить его на устройстве «под» Micropython, так что возвращаемся к DOOM. Приходится немного повозиться с опциями компилятора и адресами загрузки, чтобы получить разумное OTA-приложение, которое легко помещается во флэш-память (~460k, разделы OTA — 2MiB), существующий план подключения ввода-вывода все еще нетронут(!), так что теоретически все должно работать… но не работает.
Оказывается, DOOM ожидает немного больше соответствия POSIX от своей библиотеки C, чем предоставляет Micropython, в частности, реализации printf
не хватает многих функций. Хорошо то, что поскольку я создаю совершенно отдельное бинарное приложение, я могу использовать чужой printf, и наконец, после долгих мучений, он работает!
Цвета ужасны. Я пока не могу ничем управлять. Я безумно счастлив 😀
Последние исправления
Искажение цвета оказалось разницей в эндиане между MPU и устройством отображения, что легко определить, посмотрев код драйвера дисплея, который меняет все байты при рисовании на устройстве, если только он не блит буфер, когда он предполагает, что они уже поменяны местами. Исправлено.
Предоставление пользовательского ввода — это поиск правильных клавиш DOOM, которые нужно подделать при нажатии на кнопки джойстика или другие на значке.
Покажите мне код!
Чтобы ужасно неправильно процитировать еще одну мою любимую игру, приди и получи детку!
…ИЛИ…
Возьмите построенные объекты из инкубатора.
Необходимое демонстрационное видео…