Купил я одну книжку почитать - про реставрацию старинной мебели. Вообще я вышел на нее случайно в поисках другой книги того же автора - про реставрацию деревянных предметов, но ее не было в наличии.
Сергей Ненашев. Ремонт и реставрация старинной мебели. М. Юстицинформ 2024. 488 стр
На авито ее продает сам автор, поэтому я попросил ее подписать для меня, для таких книг у меня отдельное место на полке. А купил я ее прокачать свои знания по реставрации - я хоть и активныйучастник Том Сойер Феста, делаю виртуальный музей, пилю уже несколько лет ретропонедельники на пикабу, все равно стремлюсь повышать свою квалификацию. И мне было интересно сравнить как мои знания и опыт любительской реставрации бьются с техниками профессиональной музейной реставрации.
В целом эта книга как раз раписана для тех, кто делает первые шаги в любительской реставрации - восстановить старый семейный комодик, дать вторую жизнь отцовской радиоле, покрытой шпонированными панелями и т.д. Меня это очень радует, что вещи чаще стали восстанавливать, пусть и небезупречно с музейной точки зрения, но зато вещи живут, сохраняя историю.
В книжке есть исторический блок - для упрощения аттрибуции мебели. Основные ценностные установки - что есть реставрация, как она проводится в музеях и т.д. Ну а дальше после описания инструмента идет описание техник в разных типовых проблемных вещах.
За что хочу книгу похвалить:
Это отличный живой язык автора. Это не сухой академический текст, это текст написанный человеком увлеченным и погруженным в тематику. При этом стилистически нет воды и повторений, свойственных западным авторам.
Автор делал рисунки. Это вообще боль многих современных малотиражных изданий - денег на художника нет, поэтому картинка воруется (с риском), либо автор пытается нарисовать на компьютере сам (видел учебник по хирургии, где иллюстрации автор коряво рисовал в paint мышкой, это был полный позор). Здесь же самый грамотный подход по соотношению трудозатрат-результата - иллюстрации нарисованы на бумаге от руки и просто отсканированы. Средний человек ручкой на бумаге рисует значительно лучше, чем мышкой на экране.
3. В книге я не нашел фактических ошибок. Да, много мест где я могу дать уточнение или чуть иначе сформулировать мысль, но так, чтобы я был не согласен с сказанным, это противоречило моему опыту - таких мест нет.
4. Техника реставрации не содержит станков, химии и прочего, недоступного в домашних условиях. С одной стороны это минус, я с ЧПУ могу больше, чем без ЧПУ. С другой - порог входа минимальный.
Без недостатков тоже не обошлось.
Необъятное не объять. Многие техники проще показать, чем пытаться описать словами. Плюс процесс реставрации как воронка затягивает смежные вопросы - от элементарных столярных навыков, заканчивая вопросами заточки инстурмента. Увы объем книги мне кажется будет мал для тех у кого совершенно нулевой опыт, точнее по многим аспектам я бы добавил информации.
Есть проблемы с полиграфией. Понятно что книга малотиражная, всего 1000 экземпляров, но некоторые строки пропечатались не четко, и это слегка раздражает.
Мало иллюстраций. Тут понятно, что автор намеренно минимизировал число картинок - цветной вклеенный блок небольшой. Если бы иллюстраций было больше - цена книги улетала в небеса (моя книга в цветном варианте была бы примерно в 5 раз дороже при тираже 100 экз)
В целом книга на отлично, могу лично рекомендовать. А Пост накатал больше с целью узнать - а что коллеги посоветуют еще интересного и полезного почитать по тематике любительской реставрации старых вещей? Ютуб каналы, не с залипаловом на процесс, а с обучением.?
Помните статью про самую дешёвую консоль с Ozon — Sup GameBox? Тогда я рассказал вам о том, что у этого чуда инженерной мысли находится «под капотом» и почему эта консоль не так проста, как кажется на первый взгляд.
После статьи я решил написать письмо производителю процессора этой консоли. И что самое интересно — мне ответили! Даже Спустя 40 лет после релиза оригинальной NES, тайваньская компания V.R.T продолжает развивать архитектуру оригинальной NES. Мне больше ничего не оставалось, кроме как включить фен, достать программатор и хакнуть этот девайс...
❯ Предисловие
В прошлой статье мы с вами разобрали Sup GameBox и выяснили его главную тайну: на самом деле это не эмулятор, как думают многие, а полноценный аппаратный клон оригинального Famicom. Инженеры из тайваньской компании V.R.T не только реализовали полную совместимость с японской консолью, но и заметно доработали её архитектуру, превратив NES во что-то типа микроконтроллера с продвинутыми мультимедийными возможностями.
Та самая капелька!
Мы также с вами выяснили то, что чипсет устройства способен работать не только с пиратскими картриджами, но и недорогими SPI/NOR-флэшками, а при наличии программатора мы теоретически сможем заменить существующие и даже добавить новые игры к и без того обширному ромсету из 500 самых известных релизов!
Однако в комментариях некоторые читатели задавали вполне логичные вопросы в духе мол «кто вообще будет производить новые чипы с аппаратными клонами NES, если какой-нибудь AllWinner F1C100s стоит буквально 2$ и при этом в разы мощнее оригинальной консоли? Наверняка это какие-то складские остатки, а компании уже давно не существует...». Всерьёз заинтересовавшись вопросом, я решил написать V.R.T письмо с восхищением, а также просьбой поделится информацией о чипсетах в Sup'ах:
London is capital of great Great Britain. Не судите мой рунглиш.
И спустя неделю мне ответили! Я был в небольшом шоке и экстазе одновременно... Джек пояснил, что в GameBox'ах действительно используется чипсет VT38 или 39'ой серии, а также предложил использовать утилиту NesMaker с сайта компании для сборки собственного ромсета для консоли. Кроме того, Джек пояснил что реализация GameBox'а в виде девкита невозможна из-за пиратских игр, но вероятно компания прощупывает почву среди демосценеров и DIY'щиков — ведь сейчас, пожалуй, самые лучшие времена для любителей разрабатывать и собирать что-то своё!
Но пока девкита VT38 ещё нет, нам остаётся лишь ковырять и моддить уже существующие консоли... и это не так уж и сложно как кажется на первый взгляд! В первую очередь, важно понимать что Sup'ы собираются буквально «из того что было», при этом чипы памяти зачастую используются Б/У. Насколько мне известно, существует как минимум три ревизии консоли:
Первая использует NOR Flash в корпусе TSOP56 и имеет на борту 500 игр. Производитель флэш-памяти может быть любой, однако я чаще всего встречал чипы от Spansion. Объём флэши составляет 16МБ (но возможны отклонения в большую и меньшую сторону) и с этими чипами памяти связан определенный нюанс, о котором я расскажу чуточку позже.
Вторая тоже использует NOR Flash, однако уже в корпусе TSOP48, который можно прошить обычной копеечной колодкой для NAND-флэшек. Такой тип памяти встречается как минимум в версии 400 in 1 и его объём составляет 8МБ, однако судя по большому количеству стертых страниц и 0xFF в дампе — используется далеко не вся память!
Третья самая сложная — она использует специальные припаиваемые к плате адаптеры с BGA NOR-памятью. Эти адаптеры нужны неспроста: во первых многие дешевые фамиклоны используют однослойные платы, где развести пины под BGA может быть проблематичным, а во вторых BGA NOR-память ушла с рынка электроники лет так 15 назад, её в огромных количествах сдувают с устройств по типу спутниковых ресиверов и DVD-плееров и поэтому она стоит копейки на вторичке. Главная сложность заключается в сборке адаптера: @promolifeсмог такой собрать, но во время подготовки статьи мы с ним не общались.
Как вы уже могли понять, моя консоль относится к первой ревизии, что вносит определенные сложности. Дело в том, что для прошивки TSOP-56 флэшек необходимо покупать специальную колодку типа «A», которая стоит 1.500 рублей и более того — она подходит только для программатора XGecu T48! Один из моих зрителей компенсировал стоимость покупки программатора и колодки в полном объёме — за что ему огромнейшее спасибо!
Также спасибо читателю Alex за iPhone X — на него фото плат получаются просто изумительные!
Микросхема EPROM с BIOS'ом для 286-ноутбука старше меня почти на 10 лет...
И вот наконец-то весь комплект для прошивки приехал... пришло время снова разобрать консоль и включить фен!
❯ Делаем дамп
При детальном рассмотрении платы можно ещё раз убедится в том, что консоли собираются буквально из того что есть. Присмотревшись к чипу памяти я заметил трещину на корпусе, многочисленные царапины и потертости, а также пайку сомнительного качества: из-за них некоторые экземпляры не живут и недели. Интересно, где этот чип трудился в «прошлой жизни»?
Обычно выводные микросхемы снимают паяльником с использованием сплава Розе или делают импровизированные SMD-щипцы для того, чтобы лишний раз не перегревать чип памяти. Однако я решил снять его феном в щадящем режиме, поскольку не хотел чистить пятачки от Розе после каждой переустановки микросхемы, да и при снятии TSOP феном риск сорвать пятачки кратно ниже!
Далее мы отмываем плату и чип памяти от флюса, а затем вставляем его в колодку:
И выставляем параметры нашего чипа в программе Xgpro. Затем нажимаем «Verify» для проверки ID контроллера, дабы убедиться что все линии флэшки надежно прижаты к колодке и затем как минимум 3-4 раза вычитываем дамп с переустановкой чипа и последующей верификацией образов дабы точно убедится что всё прочиталось корректно.
И вот теперь начинается самое интересное: мы открываем дамп в HEX-редакторе и начинаем искать зацепки для того, чтобы примерно понять разметку памяти. Изначально я ожидал что в начале будет инициализация вектора прерываний, init-sequence для дисплея и код меню выбора игр, однако как оказалось — флэшка каким-то очень хитрым способом поделена на CHR ROM и PRG ROM, и при этом CHR ROM идёт первой!
«Наверняка названия игр хранятся в виде обычного ASCII-текста» — подумал я, так что мы сможем найти меню по ключевым словам — Mario, Angry Birds, Contra... Но никакого результата поиск текста не дал. Тогда я начал искать паттерны из CHR ROM того же самого Марио — и всё равно я не смог ничего найти. Единственное что напоминало об играх - нечитабельные ошметки названия игр.
Обратите внимание на Contra 1... Ну ведь почти то что нужно!
Тогда я решил поизучать дампы Sup'ов других людей и наткнулся на пост darknesmonk с 4pda. Образ его консоли хоть и был практически идентичен моему, у него названия игр были корректными, а данные из PRG ROM и CHR ROM можно было найти по паттернам. И вот тут то я понял в чём заключается секрет!
Дело в том, что издавна существует очень простой аппаратный способ защиты кода от изучения и реверс-инжиниринга: перестановка битов на дата-линиях. Сама шина в процессоре остаётся точно такой-же, меняется лишь разводка на плате: там, где по логике должен быть бит (сигнальная линия) DQ8 — может оказаться бит DQ10, а там где DQ9 — DQ11 и наоборот. На последнем этапе подготовки прошивки запускается специальный скрипт, который переставляет биты так, чтобы они оказались на своём месте и по итогу для процессора всё остаётся прозрачным. Также поступили и разработчики Sup'а, перемешав биты DQ1 с DQ9 и DQ2 с DQ10.
Обратите внимание на странный порядок распиновки дата-линий.
На первый взгляд может показаться что инженеры пытаются что-то скрыть, однако на практике это скорее всего особенности трассировки платы: мы ведь не забыли, что она у нас однослойная? Иными словами, эти линии может быть проблематично подвести напрямую к процессору, а резисторы-нулевики ставить банально некуда, поэтому разработчикам пришлось выкручиваться вот таким костылем!
С виду трассировка у наших плат практически идентичная, особенно шина к флэшке, но почему у меня биты поменяны местами — мне так и непонятно.
❯ Реверсим
Теперь когда мы разгадали секрет «защиты» ROM консоли, я решил написать небольшую утилиту, которая преобразовывает дамп в формат, удобный для реверса и обратно. Конечно можно просто порезать дороги до соответствующих линий, раскидать перемычки и покрыть маской, но это не совсем изящное решение :)
byte[] data = newbyte[input.Length]; input.Read(data, 0, data.Length);
/* VT chipsets use whole 16 data lines, which means we have D1 swapped with D9 and D2 swapped with D10 */ for(int i = 0; i < data.Length / 2; i++) { byte b1 = data[i * 2]; byte b2 = data[i * 2 + 1]; byte tmp = b1;
После подготовки дампа, я начал искать текстовые строки с названиями игр в надежде выйти на таблицу их дескрипторов в многоигровке. Увы, текстовый пул здесь находится отдельно от описания самих игр. Тогда я решил сверится с официальный утилитой NesMaker от производителя чипсета, однако у него на выходе игры как раз имеют формат структуры без отдельного пула строк.
Выйти на XRef'ы не получалось поскольку у меня нет опыта реверса программ для 6502 и я уже начал терять надежду... Однако я вспомнил что в основном, игры для NES имеют фиксированный формат с двумя секциями: PRG ROM (область памяти с кодом игры), а также CHR ROM (тут находится графика). Учитывая что в ромах .nes есть информация об используемом типе маппера и размерах секций PRG ROM/CHR ROM, мы можем подменять игры на нужные путем поиска секций существующих игр по паттернам и замены их на нужные! Однако такой способ подойдет только для игр с идентичными мапперами и размерами областей. Иными словами: мы можем заменить Dr. Mario с маппером типа 0 на хак Super Mario Bros или Battle City, но не сможем записать, например, Batman.
Сделать это просто: открываем нужную игру в HEX-редакторе, переходим на 5-й байт и смотрим размер PRG ROM, который указывается в блоках размерностью 16.384 байт. В следующем байте находится размер CHR ROM, который указывается в виде блоков по 8.192 байта. В Dr. Mario PRG имеет размерность 2 (32.768 байт), а CHR — 4 (тоже 32.768 байт).
Заголовок имеет размер в 16 байт.
Далее переходим на 17-й байт (при отсчете от единицы) и начиная от него выделяем столько же байт, сколько у вас получилось при умножении числа блоков PRG ROM на 16.384 - в моём случае 32768 байта и копируем их в буфер обмена.
Затем нам необходимо найти начало PRG ROM заменяемой игры: для этого выделяем 8 байт с отступом хотя-бы в 64-128 байт от начала секции (поскольку иниты в играх Nintendo очень похожи и можно случайно найти не ту игру) и записываем их значения в виде паттерна «78 D8 A9 10 8D 00 20 A2».
Если говорить простыми словами, то лучше выбирать паттерн начиная с адреса A0
Далее ищем паттерн в нашем дампе и устанавливаем позицию курсора на начало секции PRG ROM, который в моём случае оказался по адресу 0x60000:
И просто вставляем скопированные ранее данные секции с заменой существующих данных. Получается вот так:
Тоже самое проделываем с CHR ROM, однако учтите что в начале этой секции у некоторых игр идёт стандартный шрифт. Поэтому паттерн лучше брать с смещения 64-128 байт от начала. Для проверки того, что вы нашли секцию от нужной игры, можно использовать замечательную программу YY-CHR, которая покажет её спрайты.
Осталось заменить название игры в пуле строк, не выходя за пределы нуль-терминатора, иначе мы сломаем названия других игр!
❯ Оно реально работает?
Пришло время записать наш дамп обратно на флэшку и посмотреть что у нас получилось в итоге!
Далее ставим флэшку на место, не забывая хорошо пропаять все её пины и отмываем флюс:
Момент истины: если консоль просто висит с черным экраном без подсветки — значит контакт одной из сигнальных линий был нарушен и необходимо пропаять чип ещё раз. У моей флэшки почему-то был немного гнутый корпус, поэтому иногда приходилось пропаивать по 2-3 раза перед тем как консоль включалась и стабильно работала. Далее выбираем наш хак Mario и...
Вот это графика, почти что SEGA Mega Drive! Не то что в оригинале!
Всё работает идеально!
❯ Заключение
Ну что друзья, как я и обещал — контенту с моддингом Sup'а быть! Конечно всё прошло не так гладко, как хотелось бы, но... кто знает, вдруг V.R.T действительно сделает какую-нибудь NES-совместимую DIY консоль, на которую игры можно будет легко заливать с помощью CH341A. А там глядишь и демосцена подтянется!
На данный момент, Sup GameBox — самый доступный гаджет для написания хоумбрю под NES и тестирования его на реальном железе... И кто знает, вдруг на нём когда-нибудь окажется игра моей собственной разработки...
А если вам интересна тематика ремонта, моддинга и программирования для гаджетов прошлых лет — подписывайтесь на мой Telegram-канал «Клуб фанатов балдежа», куда я выкладываю бэкстейджи статей, ссылки на новые статьи и видео, а также иногда выкладываю полезные посты и щитпостю. А ролики (не всегда дублирующие статьи) можно найти на моём YouTube канале.
Если вам понравилась статья...
И у вас появилось желание что-то мне задонатить (например прикольный гаджет) - пишите мне в телегу или в комментариях :) Без вашей помощи статьи бы не выходили! А ещё у меня есть Boosty.
Если V.R.T выпустит консоль по типу Sup GameBox, но при этом с возможностью лёгкой разработки и прошивки своих игр — купили бы её?
Что думаете о таком моддинге?
Как относитесь к формату статей с моддингом чего-то необычного… или наоборот чего-то очень обычного?
А можно рассмотреть старые компы типа XT? Эмулятор для них есть в проекте "мурмулятор". Там же схемы эмуляции различных приставок, компьютеров типа "Радио-86РК" и "ZX-Spectrum".
Надеюсь скоро можно будет эмулировать i286 и i386 - и, возможно, даже получится запустить OS/2 Warp 4.0 Merlin или что-то подобное для офисной работы...
Нашлось у меня несколько очень старых инструментов, выпущенных в 1970-х, 1960-х и раньше. Гаечные ключи с клеймом "ЛЗИ". Льговский завод инструментов, в Курской области, в исправительной колонии, учреждение ОХ-30/3 (ФКУ ИК-3 по Курской области). Примерно 1960-1970х гг.
ЛЗИ
Хромованадий
ЛЗИ
Доставшиеся в наследство пассатижи, которыми я пользовался ещё в детстве, лет 15 назад, когда мне было 12 и я учился мастерить по хозяйству, ещё на старой даче. Были покрыты слоем отполированной руками ржавчины и никак не интересовали меня, пока я не обнаружил, что уж очень они старого образца и не решил их почистить. И меня ждал сюрприз - Adolf Klauke! Небось трофейные! Ну или 1950-х, по репарации.
Пассатижи Adolf Klauke
Adolf Klauke
Ключ с дюймовыми размерами 3/8 на 1/2 с клеймом "Ижев.З.Ф". Расшифровки не нашёл. Ижевск? Или фамилия Ижев? Явно очень старый.
1/2 на 3/8
Ижев.З.Ф
Тоже доставшийся в наследство и найденный на старой даче ключ 19 на 22 с клеймом ЗАТИ. Это Павловский завод автотракторного инструмента, который существовал с 1932 по 1952 год, а потом стал автобусным заводом. Насколько я знаю, такое клеймо использовали с 1932 года и до середины-конца 30-х, значит, ключ довоенный!
Начинаем новую неделю с нового экспоната в виртуальном музее sovtech.su. Сегодня это монтажные приспособления для 8 и 16 мм кинопленок. Это доступные любительские форматы, в которых советский человек мог сделать домашнее кино. Монтаж видео в доцифровую эпоху - процесс весьма времязатратный. Итак - начнем с Купавы-16
Буду запихивать фото в карусельки, иначе все не влезет. Монтажный стол позволяет просматривать пленку в любом направлении с любой скоростью, при этом световой поток через нее минимальный - если в кинопроекторе остановить пленку, её просто сожгет светом. В монтажном столике есть источник света, проекционный объектив и вращающаяся призма, которая прерывает световой поток, когда кажр не в окне, обеспечивая плавность картинки.
1/7
В работе выглядит примерно так, нашел только такую пленку
Лампы - боль. Абы-какой не заменишь, напряжение экзотическое, плюс спираль накала специально сделана близкой к точке.
Следующий монтажный столик Селена - аналогичный, только под 8 мм пленку.
1/10
В случае разрыва пленки нужно ее срастить, соблюдая точные размеры, иначе после склейки кадры убегут. Для этого в комплекте с проекторами шли следующие приспособления:
1/2
1/2
Ну и наконец вот такое приспособление - 10х лупа с держателем 8 мм кинопленки.
телеграм канал в профиле, если удобнее следить там
Хочу скромно поделиться моим опытом пользования мобильниками почти за четверь века. И сделать общий анализ не каждой модели в отдельности, а попытаться более широко описать общую тенденцию индустрии в динамике времени.
Телефоны / смартфоны Samsung
Имеем марку: Мобильные телефоны "Samaung". (Кроме самого первого - Nokia 3310, все остальные телефоны были Samsung).
2. Временной интервал ровно ТРИ года. Первый мобильный у меня появился в 2001 году. Я их менял ровно каждые три года:
#2..2004 3.2007 4.2010 5.2013 6.2016 7.2019 8.2023 г.
3. Одинаковая примерно стоимость всех моделей #2-8 если считать не в рублях, а в USD (Модели ВСЕ, кроме первого, примерно одного ценового ряда. Не флагманы, но выше стоимостной средней ( S серия, но модели прошлого года ).
Попробую добавить свой кирпичик в стену, описанного в статье Технологического пика (плато) последнего десятилетия (и возможных признаков начавшегося когнитивного регресса технологий) на примере конретного повсеместно распространенного гаджета: мобильного телефона / смартфона.
Начнем. Восторги от первого моего использования первого моего мобильника стандарта GSM описывать не буду. Смотрю только на общую траекторию прогресса технологий. Качественный рост технологичности первых ЧЕТЫРЕХ моделей 2001-2010 годов меня впечатлял. От Nokia 3310 до первого смартфона Samsung с тачскрином. Вторая модель раскладушка с цветным экраном. Третья с картами памяти и интеграции с пк. Четвертая модель - уже перавый смартфон. За 10 лет. Следующие ДВЕ модели - увеличивлись размеры экрана, улучшилось разрешение и возможности камеры телефона (на 6 модели уже две камеры), процессоры, оперативка RAM и постоянная память... Операционка везде Android. Рост технологичности, рост, но не такой впечатляющий как модели 1-4. И наконец последние ДВЕ модели 7-8 (условно назовём)... 2019 и 2023. Ну... небольшие тенденции улучшения, но уже ничего принципиально нового. Производительность выросла несущественно. Только карты памяти на последнем нет (для меня неудобно). Аккумуляторы тяжелее, но смартфоны и тратят больше энергии. Камеры... на модели 8 стали хуже чем на модели 7. Пропал круглый разъём Jack 3.5. Аккумулятор вообще был лучшим по совокупности на модели 6 (+ он был ещё и сменный) . А на модели 8 хуже чем на модели 7.
SAMSUNG Galaxy S ....
Итог:
Конечно, это нерепрезентативное мнение только одного человека. Только об одной марке. Но, тем не менее, по опыту общения с другими людьми, общее мое наблюдение находит подтверждение и у других наблюдателей (пользователей).
Рост технологичности не наблюдаю с модели #7. Небольшие правки и улучшения и даже некоторые ухудшения, но ничего сопоставимого с траекторией роста 2010-2020, тем более 2000-2010...
Сейчас смотрю новые смартфоны Samsung. В следующем 2026 году плановая замена :) Гипотеза подтверждается наблюдением! Может, что посоветуете из смартфонов, воможно надо перейти на другие марки....??
Тенденция, однако.... или нет? ошибаюсь... Но в данном случае, гипотеза о технологическом плато (и возможном начале спуска, упрощения) на конкретном примере опыта может служить небольшим подтвержденим общей теории... как думаете?
Я маргинал, который знает исключительно вершки-корешки и не умеет ни код нормальный писать, ни что то доводить до конца. Я не закончил три пту в своем городе из за того, что просто отбитый маргинал. Поэтому мне не светит ни госка, ни частная контора, ни даже работа на галере - у меня нет скиллов, а умело продавать себя я не умею и у меня не хватает хватки.
Так что фигня это все, мой максимум - второсортные развлекательные статейки, чисто как бульварное чтиво, только про IT. Почти как нейромусор.
Осторожно: Статья написана максимально простым языком. Так что если вы гик, но не умеете программировать - вам всё равно будет интересно!
Недавно я наткнулся на DIY-игровую консоль за 1.500 рублей - Waveshare GamePi13. Когда гаджет приехал ко мне, я запустил примеры игр от производителя... и оторопел от 5 FPS в Pong - это ж как плохо нужно код писать!
Не желая мириться с этим, я открыл схему устройства, даташит на RP2040 и принялся писать свой собственный BIOS. Если вам интересно узнать, как работают DIY-консоли «изнутри», можно ли запускать внешние программы на микроконтроллерах из RAM, как реализованы различные подсистемы BIOS, а в конце даже написать «Змейку» - добро пожаловать под кат!
❯ Предисловие
Иногда китайские производители выпускают на рынок дешевые гаджеты с ориентиром исключительно на гиков. Чего-уж говорить, с какой-нибудь R36s чего только не сделали: и кастомные прошивки, и порты игр с ПК, и даже достаточно сложные аппаратные модификации. Однако в тусовке DIY'щиков обычно всё куда хардкорнее...
«Андерграундные» консоли выходят чуть ли не каждый день, но лишь единицы из них становятся хоть сколь либо популярными и попадают на массовый конвейер. От «больших» консолей их отличает простая схемотехника, использование распространенных и дешевых микроконтроллеров общего назначения и полная свобода творчества — что хочешь, то и твори! По характеристикам они чаще всего близки к оригинальному GameBoy или GameBoy Advance, а покупают их инженеры, демосценеры и ретро-энтузиасты, которые не только играют во что-то готовое, но и пишут небольшие игрушки сами!
Самые известные консоли такого формата — это нашумевший Playdate и чуть менее известный Arduboy. Обе консоли сильно ограничены в характеристиках и это подстегивает интерес гиков к постоянной оптимизации кода и попыткам впихнуть «невпихуемое». Выделился даже российский «Микрон», представив свою DIY-консоль «для хардкорных ардуинщиков» — некий MikBoy на базе своего же МИК32 «Амур»!
Я уверен что Микроновцы будут читать эту статью... Если вдруг всё получится и MikBoy пойдёт в серию — то напишите мне пожалуйста сообщение :)
Подобным «ардуинщиком» являюсь и я. Ещё со школьных лет меня нереально тянет к микроконтроллерам и Embedded-электронике в целом. О консоли собственной разработки я мечтаю с 14 лет, при этом мне не просто хочется собрать прототип и «забить», но и запустить мелкосерийное ручное производство и продавать устройства подписчикам! К своим 24-годам я сделал два прототипа и развел три платы, но все эти проекты так или иначе откладывались в долгий ящик...
Один из ранних-ранних прототипов, предназначенный для обкатки драйвера дисплея.
И вот, 25 сентября мне стукнуло 24 годика. Уже взрослый мальчик получил в качестве подарка донат от постоянного читателя и пошёл изучать маркетплейсы в поисках интересного железа. По ключевым словам «tft lcd diy» был найден «ESP32 Bitcoin Miner V2» (выгодный девкит с 2.8" и ESP32-S2), девкит ESP32 с 4.3" дисплеем и емкостным тачскрином, а также некий Waveshare GamePi13, о котором мы сегодня с вами и поговорим!
Отдельное спасибо хотелось бы сказать тем самым подписчикам. Без вашей поддержки этой статьи бы не было!
Waveshare — знаменитый в кругах энтузиастов SBC производитель. В основном компания занимается дисплеями, модулями расширения и одноплатными компьютерами.
В тот же день я заказал устройство, и уже через 3 недели трепетного ожидания, GamePi13 оказался у меня на столе. На первый взгляд консоль показалась очень маленькой: её 1.3" дисплей был даже меньше, чем у Nokia 6230i, а кнопки оказались расположены непривычно близко друг к другу. Ко всему прочему, у консоли не было предусмотрено вообще никакого корпуса: ни «болванки» от производителя, ни STL-файлов для печати. Что-ж, это только придаёт брутальности нашему устройству!
Оба устройства помещаются в одну ладошку... А ведь когда-то 6230i казался реально большим!
Как вы уже могли заметить, консоль состоит из двух независимых модулей: платы разработки Waveshare RP2040-PiZero и «бутербродного» геймпада с дисплеем, который подключается к гребёнке основной платы. В этом и кроется главный секрет устройства: геймпад изначально рассчитан именно для «одноплатников» Raspberry Pi, но поскольку Waveshare также выпускает плату RP2040 с Pi-совместимой гребёнкой, они решили заодно адаптировать его и для PiZero.
❯ Что внутри?
Хоть PiZero и похожа на референсную плату в лице Raspberry Pi Pico, у неё есть несколько серьёзных отличий:
Во первых, на плате установлена SPI-флэшка объёмом аж в 16МБ. Это максимальный объём, который поддерживает XIP-контроллер в RP2040. В RPi Pico же используется флэш-память объёмом всего в 2МБ.
Далее внимание привлекает использование менее эффективного ULDO RT9193 вместо полноценного DC-DC преобразователя в оригинальном Pico. Сам микроконтроллер сможет работать при разрядке аккумулятора ниже 3.6В, а вот периферия — под вопросом. Иными словами, мы не сможем использовать «все соки» из аккумулятора и нам придётся реализовывать отсечку по напряжению.
На плате распаяна микросхема-чарджер литий-ионных аккумуляторов ETA6096 с током зарядки аж в 1А. Если захотите использовать аккумулятор меньшей емкости — стоит подобрать резистор ISET большего номинала, иначе есть риск перегрева.
Из разъёмов распаян HDMI (да, я тоже в шоке), слот для MicroSD (под него отдали весь SPI0) и два Type-C: один для аппаратного USB-контроллера в RP2040, второй для USB через PIO. В общем, пытались угодить всем.
Плата с геймпадом не менее интересная. С фронтальной стороны у нас расположилось 10 кнопок и 1.3" IPS-дисплей с разрешением 240x240, использующий контроллер ST7789. Вообще, для такой диагонали разрешение дисплея крайне избыточно: оно не только съедает драгоценные килобайты оперативной памяти для фреймбуфера, но и значительно грузит DMA-контроллер и всю шину SPI. Я бы на месте инженеров установил бы сюда «золотой стандарт» — недорогой 1.8" 128x160. Все кнопки подключены к отдельным пинам без сдвигового регистра и занимают значительную часть доступных GPIO.
Я бы сделал лучше!
С обратной стороны расположился небольшой динамик, усилитель, построенный на базе NS8002, 3.5мм джек для подключения наушников, а также токоограничивающий резистор подсветки и обвязка для дисплея. Подсветка подключена напрямую к VSYS и рассчитана на питание от 3.3В, так что никакой регулировки яркости и продвинутых режимов сна!
Производитель платы — компания SpotPear.
Ну что-ж, собираем наш бутерброд обратно, подключаем Type-C и смотрим на одну из представленных демо-игр — Тетрис!
Нет, это не пережатая гифка, игра действительно идёт буквально в 1 FPS и с мерцанием — и это на микроконтроллере с ядром Cortex-M0+ на частоте аж в 150МГц! Я напомню, что N-Gage с процессором TI OMAP на более старом ядре ARM926EJ-S с частотой 104МГц умудрялся тянуть первый Tomb Raider с полностью программным рендерингом в 25 FPS!!!
Далее я решил открыть официальный вики Waveshare и изучить информацию о консоли, где нашел несколько примеров игр для неё, одной из которых был Pong. Какое же было моё разочарование, когда я узнал, что обе игры написаны полностью на Python: игровая логика, маршалинг данных, работа с «железом» — всё это было на интерпретируемом языке и более того, написано плохо и крайне неэффективно!
Ни о каком подобии SDK или библиотеки для абстрагирования работы с железом даже речи не шло, практически всё, кроме номеров пинов, было захардкожено прямо в коде игры. О хорошей архитектуре тоже речи не идёт: один класс на всю логику с глобальными переменными... В общем, сэмплы писал либо новичок, либо прожженный эмбеддер :)
Драйвер дисплея даже не пытается использовать DMA, из-за чего даже Понг, состоящий из трёх прямоугольников умудряется тормозить.
def blit_buffer(self, buffer, x, y, width, height): """ Copy buffer to display at the given location.
Args: buffer (bytes): Data to copy to display x (int): Top left corner x coordinate Y (int): Top left corner y coordinate width (int): Width height (int): Height """ self.set_window(x, y, x + width - 1, y + height - 1) self.write(None, buffer)
Звуковая подсистема, состоящая из одноканальной тональной пищалки на аппаратном ШИМ-контроллере, тоже была со своими «приколами». Например «тишина» — это 0, то есть магнит всегда прижат к нижней части, хотя должно быть PWM_MAX / 2.
Под впечатлением от такого кода, я решил попробовать написать SDK для этой консоли сам. Однако моё видение идеальной DIY-консоли сильно отличалось от того-же Arduboy или Playdate!
❯ Архитектура
При проработке архитектуры будущего «BIOS», я сразу же поставил для себя несколько чётких задач:
Во первых, BIOS должен быть достаточно абстрактным для того, чтобы скрывать от игры детали реализации конкретного «железа». Иными словами, игра оперирует не DMA-контроллерами, FPU-сопроцессором и SPI, а набором простых и понятных подсистем: графика, ввод, звук, хранилище. Кроме того, это позволяет легко портировать игры для такого BIOS'а на другие платформы: можно без проблем реализовать симулятор (не эмулятор!) консоли на ПК или портировать её на ESP32 с минимальными изменениями.
Во вторых, мы ставим производительность в основной приоритет при разработке устройства. В конце-концов это же позорище, что простейшая игра тормозит и мерцает на мощном микроконтроллере, но при этом тетрисы с трёхмерной графикой вполне шустро работали на телефонах Sony Ericsson 2005 года. Именно поэтому для написания игр используются не скриптовые языки по типу Lua или JS, а самый обычный «C с классами».
В третьих, сам BIOS должен быть легко портируем между разными платами (у SpotPear есть вторая похожая плата — уже с 1.5" и стиком) и даже аппаратными платформами. Этот проект может стать основной прошивкой для консоли уже моей разработки и иметь вот такую «кроссплатформу» было бы отнюдь не лишним!
Руководствуясь критериями выше, я решил писать BIOS на C++ (на деле C с классами) с активным использованием интерфейсов и VMT. Это позволяет не только удобно структурировать модули и повышает читаемость кода игры, но и избавляет от необходимости вручную составлять таблицу системных вызовов к API. Тем не менее, в таком подходе есть один серьёзный нюанс: когда у подсистем появляются новые методы или добавляются перегрузки к прошлым, их необходимо по порядку добавлять в конец интерфейса, иначе VMT ломается.
vtable for CTest: .word 0 .word typeinfo for CTest .word CTest::Test() .word CTest::Abc() vtable for ITest: .word 0 .word typeinfo for ITest .word __cxa_pure_virtual .word __cxa_pure_virtual
В своё время Microsoft решила эту проблему в COM с помощью QueryInterface и миллиона вариаций этих самых интерфейсов: IDirectSound8, IDirectDraw7 и т.д, но мы можем не изобретать велосипед, а просто предоставлять «старым» играм такие же «старые» версии VMT.
Основным объектом в BIOS'е является CSystem, который содержит в себе ссылки на другие подсистемы консоли, а также на информацию о текущей аппаратной платформе:
/// @brief Primary system service, supplied to both games and system modules. class ISystem { public: virtual CSystemInfo* GetSystemInfo() = 0;
Несмотря на кажущуюся «динамическую» натуру системы, никаких IID я переизобретать не стал. BIOS должен реализовывать ровно тот минимальный функционал системы, который нужен. Экземпляр CSystem создаётся так называемым «портом» на конкретную плату, который должен заполнить структуру с указателями на реализации подсистем — прямо как machine-файлы в Linux! И RAII не нарушили, и полный контроль без костылей сохранили — ляпота!
void InitializePlatform() { CommManager = new CCommunicationManager(); CDebugService* dbgSvc = new CDebugService();
/* Print some userful debug information */ CJEDECFlashID* flashId = FlashManager.GetFlashID();
while (true) { /* Tick all platform-depend services here */ CommManager->Tick(); PowerStateManager.Tick(); InputService->Tick();
System->Tick(); } }
В целом, базовая архитектура примитивная и понятная. Перейдем же к деталям реализации конкретных модулей.
❯ Графика
Первая подсистема, которую я реализовал — была графической. Концептуально она разделена на два отдельных модуля: драйвер дисплея, который позволяет получить его параметры и в будущем управлять его состоянием, а также модуль для рисования на поверхностях. Прямо как в DirectDraw:
class IDrawingSurface : public ISystemService { public: virtualvoid Clear(CColor color) = 0; virtualvoid DrawBitmap(CBitmap* bitmap, int x, int y) = 0; virtualvoid DrawBitmapEx(CBitmap* bitmap, int x, int y, CSpriteInfo* spriteInfo) = 0; virtualvoid DrawRect(CColor color, int x, int y, int width, int height) = 0; virtualvoid FillRect(CColor color, int x, int y, int width, int height) = 0; virtualvoid DrawLine(CColor color, int x1, int y1, int x2, int y2) = 0; virtualvoid DrawString(CColor color, int x, int y, CAnsiChar* str) = 0; };
class IGraphicsService : public ISystemService { public: virtualvoid SetPowerState(bool isPowerEnabled) = 0; virtualvoid SetBacklightState(bool isBacklightEnabled) = 0; /* Maybe some controller-related functions in future? Like BIAS and HW rotation? */
Сам драйвер дисплея классический: в его задачи входит инициализация контроллера, выделение памяти под фреймбуфер и регулярное обновление изображения на матрице. Поскольку в таких устройствах используются стандартные MIPI DBI экраны с набором команд DCS, часть кода инициализации и работы с дисплеем стало возможным унифицировать:
/* Perform hardware reset */ gpio_put(PIN_LCD_RST, 0); sleep_ms(DISPLAY_INIT_SLEEP_TIME); gpio_put(PIN_LCD_RST, 1); sleep_ms(DISPLAY_INIT_SLEEP_TIME); /* Wait for display controller to complete initialization */
Reset(); /* Perform software reset to maintain default register state */ SendCommand(cmdSLPOUT, 0, 0); /* Disable sleep mode */ SendCommand(cmdCOLMOD, 0x05); /* Set color format and decoding*/ SendCommand(cmdINVON, 0, 0); /* Disable inversion */ SendCommand(cmdNORON, 0, 0); /* Enable normal mode */ SendCommand(cmdMADCTL, cmdMADCTL_RGB); /* Set pixel size */
uint8_t windowSize[] = { 0 >> 8, 0, DISPLAY_WIDTH >> 8, DISPLAY_WIDTH }; /* Set display window (note this is not safe for displays with sides not equal in size) */ SendCommand(cmdCASET, windowSize, 4); SendCommand(cmdRASET, windowSize, 4);
SetPowerState(true); /* Enable display */
Вероятно читатель может спросить: «зачем выделять целых 115КБ под фреймбуфер, если можно использовать команды CASET/RASET и рисовать отдельные спрайты прямо в память дисплея?». Дело в том, что в таком случае скорость отрисовки будет падать обратно пропорционально размеру и числу рисуемых изображений. Если мы попытаемся нарисовать параллакс-фон, состоящий из трёх картинок с размерами 240x240, то нашим узким местом станет не только цена обращения к XIP-кэшу, но и производительность SPI-контроллера (который напрямую тактируется от системного PLL) и мы получим те самые 1-2 FPS. Кроме того мы потеряем возможность использования DMA и нам придётся ждать каждой транзакции на экран: это проблема многих «самодельных» консолей, которую, впрочем, можно решить обратившись к опыту предков — а именно PPU.
В своём проекте я решил активно задействовать DMA-контроллер для отправки фреймбуфера на дисплей. Концепция простая: мы указываем ему переслать фреймбуфер, начинаем подготавливать следующий кадр и если транзакция ещё не завершена - то дожидаемся её окончания, дабы картинка оставалась целостной. Однако если обновление логики следующего кадра завершается быстрее, чем DMA-контроллер успевает отправить сканлайны - мы можем получить эффект тиринга.
/* Setup DMA for SPI */ dmaChannel = dma_claim_unused_channel(true);
Далее переходим к фактической отрисовке изображений. На данный момент поддерживается только один формат пикселей — RGB565, поскольку нет особого смысла использовать 8-битную палитру для изображений 32x32 (но есть смысл использовать 4х-битную, как на NES). Процесс рисования называется блиттингом и поскольку реализация полноценного альфа-блендинга слишком дорогая для реалтайм графики на микроконтроллерах, для описания прозрачности используется техника колоркеев.
ColorKey — это как ChromaKey, но для описания прозрачного цвета используется только базовый цвет, а не цвет + порог допустимых цветов. Помните как в играх 90-х были картинки с розовым фоном цвета Magenta? Вот это оно самое :)
Рисование текста реализовано знакомым для Embedded-инженеров способом: шрифты описываются в формате 8x8, где 8 битов каждого байта обозначают наличие или отсутствие пикселя в текущей позиции. Такие шрифты не только занимают очень мало места, но их также очень легко и быстро рисовать, а также масштабировать под различные разрешения экранов. На данный момент я задумываюсь — стоит ли добавлять в консоль поддержку полноценного UTF-16, если учесть что основной таргет на русскоязычную аудиторию, где и CP866 хватает с головой?
Какой же дисплей чёткий...
❯ Ввод
Далее мы плавно переходим к реализации драйвера ввода. Как я уже говорил выше, все кнопки подключены к своим отдельным GPIO без использования сдвигового регистра или I/O Expander'а, что с одной стороны и хорошо (некоторые китайские производители реализовывают консоли с кнопками, основанными на матричном (!!!) принципе), а с другой — отъедает большинство GPIO у RP2040. Свободными пинами мы могли бы выполнять множество полезной работы: получать уровень заряда аккумулятора у Fuel Gauge, управлять уровнем подсветки с помощью ШИМ-контроллера и ключа, или, в конце-концов, сделать порт для подключения периферии... но нет так нет.
Сам по себе драйвер ввода до жути примитивный: он позволяет получить состояние отдельных кнопок, осей (как Input.GetAxis в Unity) и проверить, нажата ли хоть какая-то кнопка:
Для удобства и портабельности BIOS'а между платами, кнопки геймпада маппятся к соответствующим GPIO в отдельной таблице трансляции, которая также содержит состояния этих самых кнопок:
// Should be layouted in order of EKeyCode enum CButtonState ButtonMapping[] = { { PIN_KEY_LEFT }, { PIN_KEY_RIGHT }, { PIN_KEY_UP }, { PIN_KEY_DOWN }, { PIN_KEY_A }, { PIN_KEY_B }, { PIN_KEY_X }, { PIN_KEY_Y }, { PIN_KEY_LEFT_TRIGGER }, { PIN_KEY_RIGHT_TRIGGER } };
Дело в том, что в нашем проекте недостаточно иметь лишь одно булево: нажата-ли кнопка или нет, для компенсации дребезга кнопок у нас также реализуется задержка перед следующей проверкой и дополнительное состояние для удобства реализации меню — «только что отпущена».
for(int i = 0; i < ButtonMappingCount; i++) { CButtonState* buttonState = &ButtonMapping[i]; bool gpioState = !gpio_get(buttonState->GPIO); // Buttons are pull-up to high when not pressed
// Check if there was elapsed enough time if(timeStamp > buttonState->LastStateChange) { if(buttonState->State == EKeyState::ksReleased) buttonState->State = EKeyState::ksIdle;
Таким образом, мы получаем куда более удобную подсистему ввода, чем условная битовая маска с обозначением каждой кнопки и ручной обработкой её состояний в игре...
Вот мы и подошли к, возможно, самой интересной подсистеме в нашем BIOS'е. Думаю многие читатели так или иначе интересовались тем, как же компилятор и линкер превращают исходный код и объектный файлы в пригодные для выполнения программы и библиотеки. Вопрос запуска нативных программ на микроконтроллерах интересовал и меня — я даже написал целых три статьи об этом: в первой мы поговорили о ESP32 и Xtensa, а во второй реализовали BinLoader путём реверс-инжиниринга и хакинга кнопочного телефона, а в третьей сделали полу-универсальный ElfLoader для нескольких моделей телефонов на разных платформах.
Но начнём мы с простого. Каждая программа делится на три основных секции:
.text — содержит в себе машинный код функций и так называемые Literal pools. Может быть как в ROM, так и в RAM. На системах, где есть возможность выполнять код и в ROM, и в RAM, есть отдельная секция - .iram.
.data — содержит инициализированные переменные, которые обычно попадают в оперативную память. Для статических констант есть отдельная секция, называемая .rodata.
.bss — содержит в себе не-инициализированные переменные, обычно это нули. В исполняемый файл секция .bss напрямую не записывается, остаётся лишь информация о том, каков её размер, а саму секцию затем выделит динамический линкер.
Куда попадут части программы определяет специальная утилита — линкер, которая на основе специального скрипта «раскладывает» данные по нужным секциям. Благодаря этому скрипту, мы можем, например, перенести часть функций в оперативную память для более быстрого исполнения или добавить в начало программы заголовок с описанием приложения.
В моём случае, я решил загружать игры в SRAM и дабы не реализовывать нормальный динамический линкер и релокации, решил выделить под игру фиксированный кусочек оперативной памяти объёмом в 128КБ. Для этого я отредактировал скрипт линкера Pico C SDK так, чтобы сразу после вектора прерываний шла наша программа:
Для компиляции программы также используется кастомный скрипт для линкера и особый Makefile, где после сборки программы мы копируем все её секции в выходной файл в «сыром» виде. Поскольку программа собирается под выполнение из конкретного адреса — пока речь идёт о переносимости только между одной аппаратной платформой. На RP2040, RP2350 и возможно STM32 такое «прокатит», но вот на других ARM-процессорах — большой вопрос!
Каждое приложение, как и базовая система, предполагает использование ООП и поэтому представляет из себя реализацию класса IApplication. Для этого нам нужна некоторая runtime-поддержка: аллокатор, функция для создания экземпляра приложения, а также указатель на ISystem. Именно поэтому каждая программа должна экспортировать специальный заголовок, где содержится указатель на функцию-инициализатор:
Таким образом, для выполнения нашей программы и вызова её обработчиков событий нам достаточно лишь загрузить файл по адресу 0x200000c0 и создать экземпляр IApplication. Всё очень просто и понятно!
Но "моргалка" ведь слишком просто, согласитесь? Поэтому мы с вами напишем ремейк классической игры Змейка, которая работает в настоящие 60 FPS!
❯ Заключение
Вот таким нехитрым образом я понемногу реализовываю свою мечту детства: «андерграунд" консоль собственной разработки. Конечно здесь ещё много чего нужно доделывать перед тем, как начинать разводить свою плату, но начало ведь положено! В контексте GamePi13, я считаю что моя реализация SDK для консоли всё таки немного лучше, чем то, что предлагает производитель «из коробки».
Я понимаю что мой не совсем трушный эмбеддерский подход может вызвать разные ощущения у читателей: так что приглашаю всех заинтересованных в комментарии, обсудим с вами «сломанный Branch-prediction из-за виртуалов», «UB из-за того, что порядок указателей на реализации в VMT может отличаться» и «какого фига игры у тебя оказались в SRAM, а высокопроизводительный код на Flash, если у XIP кэш всего в 16КБ!».
А если вам интересна тематика ремонта, моддинга и программирования для гаджетов прошлых лет — подписывайтесь на мой Telegram-канал «Клуб фанатов балдежа», куда я выкладываю бэкстейджи статей, ссылки на новые статьи и видео, а также иногда выкладываю полезные посты и щитпостю. А ролики (не всегда дублирующие статьи) можно найти на моём YouTube канале.
Если вам понравилась статья...
И у вас появилось желание что-то мне задонатить (например прикольный гаджет) - пишите мне в телегу или в комментариях :) Без вашей помощи статьи бы не выходили! А ещё у меня есть Boosty.
Что думаете о таком формате статей?
Если бы я собрался с духом и произвел 20-50 штучек консолей-самоделок с полностью готовым SDK, примерами и туториалами, купили бы себе такую