В начале эпохи домашних компьютеров существовала компания под названием Apple. Она только что заработала огромный успех благодаря линейке компьютеров Apple II, но чтобы оставаться на вершине быстро развивающегося компьютерного рынка, ей необходимы были инновации. Компания уже работала над линейкой Lisa, которая вдохновлялась мини-компьютерами и была предназначена для бизнес-пользователей, а значит, и имела соответствующую цену, но для среднестатистического потребителя она казалась слишком дорогой. В качестве дополнительного проекта был разработан Macintosh, который должен был стать реализацией идеи нового поколения компьютеров для «людей с улицы» и стоить около 500 долларов. Проектом занялся Стив Джобс, и под его руководством «железо» стало более продвинутым, ПО получило вместо текстового интерфейса GUI, а цена взлетела почти до 2500 долларов. Хотя оборудование, получаемое за эту цену, немного разочаровывало, например, ему не хватало графических ускорителей и звуковых возможностей, имевшихся у других машин, зато цену оправдывало ПО. Первым Macintosh был Mac 128K, и его успех подтолкнул к созданию более продвинутых моделей этого компактного Mac, в частности, Macintosh 512K, Macintosh Plus и серии Macintosh SE.
Хотя разработка Macintosh происходила примерно в 1984 году, задолго до того, как я начал разбираться в компьютерах, я питаю к компактным Macintosh какую-то слабость: первым компьютером, купленным моими родителями, был Macintosh Plus. Позже он был дополнен жёстким диском SCSI на 20 МБ, и на этой машине я писал свои первые программы на Basic. Когда ещё жил в Нидерландах, я купил сломанную машину SE/30 и превратил её в Linux-сервер, который тем не менее способен был запускать ПО для Mac. Однако я оставил эту машину в Нидерландах, а здесь, в Шанхае, у меня больше нет классического «железа» Apple.
Хоть и очевидно, что в повседневной жизни Mac Plus мне больше не понадобится, мне понравилась идея иметь его под рукой на случай приступов ностальгии. Возможно, мне удастся получить небольшую долю ощущений от работы Macintosh, если я сам создам уменьшенную копию такой машины. Если уж у меня есть некоторый опыт в создании уменьшенных версий старого оборудования, то почему бы не попробовать применить этот процесс для сборки почтенного Mac Plus?
Дисплей
Что же мне использовать для сборки подобной машины? Сначала мне в голову пришла идея взять Raspberry Pi или нечто подобное, добавить 2,5-дюймовый ЖК-экран, эмулятор наподобие PCE или MiniVMac, напечатать на 3D-принтере корпус и на этом считать работу завершённой. Но я не думаю, что эта идея себя оправдает: машина на мой вкус не только будет слишком большой, но и сам проект слишком простым. В оригинальном Mac 128K, даже когда конечный результат оказался слишком маломощным, разработчикам удалось для экономии денег провернуть пару трюков. Простая сборка реплики из стандартного «железа» противоречит духу оригинальной конструкции. Поэтому я зашёл на Taobao за более экзотическими компонентами!
Я решил начать с дисплея. Для своего времени у Mac был экран достаточно высокого разрешения, поэтому было очень важно подобрать подходящий дисплей. Обычно, когда дело доходит до выбора на китайском рынке электроники дисплеев, ассортимент оказывается большим. К сожалению «большой ассортимент» состоит или из экранов с большим разрешением, но и большими размерами, или мелких экранов малого разрешения. В идеале мне нужно было разрешение 512x342 пикселя; это нативное разрешение Mac и на подобном дисплее я смогу отображать всё без изменения масштаба. К сожалению, готовых экранов такого разрешения на рынке нет; ближайшим аналогом будет что-то вроде 640x480. По каким-то причинам экраны такого разрешения имеют довольно большие размеры: самый маленький имеет диагональ 3,5 дюйма. Поэтому увы, если я хочу сделать Mac как можно меньше, то придётся пойти на уменьшение разрешения.
Приняв решение о том, что немного уменьшить разрешение вполне можно, я получил ассортимент из довольно большого набора дисплеев. Одним из первых встреченных дисплеев оказался x163qln01 — 1,63-дюймовый OLED-экранчик, произведённый AUO. Он немного дорог (примерно 25 долларов США за экран), но его можно часто найти на Taobao, а в datasheet по крайней мере задокументированы контакты, размеры и требования к источнику питания. Похоже, что этот дисплей разрабатывался для какой-то марки умных часов на Android, и немного погуглив, я даже нашёл некоторые последовательности инициации, которые можно использовать.
Единственная проблема (если не считать коннектор, контакты которого находятся на расстоянии 0,5 мм друг от друга) заключалась в том, что дисплей использует не параллельный интерфейс и не SPI, а интерфейс MIPI. С этим мне придётся разбираться позже.
Выбрав дисплей, можно переходить к процессору. Я выбрал модуль ESP32-Wrover. Этот модуль содержит ESP32 (чип WiFi с двумя 32-битными ЦП, работающими на частоте 240 МГц и с примерно полумегабайтом ОЗУ), 4 МиБ флеш-памяти и 4 МиБ памяти PSRAM. Я предположил, что два ядра ЦП будут достаточно быстрыми для эмуляции Mac и что я могу использовать 4 МиБ памяти PSRAM в качестве ОЗУ Mac. Хоть 4 МиБ флеш-памяти — это не очень много, их должно быть достаточно для эмулятора плюс небольшого жёсткого диска с системным ПО и программами. На руку мне и то, что я работаю в Espressif, поэтому это оборудование мне достаточно хорошо знакомо; к тому же я могу просто взять несколько модулей с работы, вместо того, чтобы покупать их и ждать доставки.
Итак, всё почти готово к работе — OLED-экрану ещё нужны были компоненты для подачи питания, поэтому число компонентов увеличилось на стабилизатор малого падения напряжения (LDO) и другие чипы подачи питания. Для Mac также требовался звук, поэтому взял дешёвый чип ускорителя и динамик, а для питания и отладки раздобыл стандартный модуль FT232. Все эти компоненты довольно малы и позволяют мне уменьшить корпус устройства; в результате должна получиться модель немного больше 1/6 от реального Mac.
Управление дисплеем
Хоть мне и нельзя пожаловаться на разрешение, размер и яркость дисплея, но вывести на него пиксели оказалось сложнее. Интерфейс MIPI не поддерживался кремнием ESP32, поэтому мне нужно было найти другой способ общаться с ним. Интерфейс MIPI DSI — это стандарт, разработанный MIPI Alliance и он не является открытым; так как это для меня хобби, мне пришлось собирать крохи информации из утёкших документов и тестирования существующих устройств. К счастью, год-два назад Майк Харрисон выполнил реверс-инжиниринг интерфейса MIPI DSI, использованного для управления дисплеями iPod (1, 2, 3, 4, 5, веб-сайт), а также нашёл несколько копий спецификаций. Это намного упростило мою жизнь: по-крайней мере, это поможет мне понять, что отправлять на дисплей.
Хотя в интерфейсе есть ещё много всего (и чтобы узнать об этом, вам стоит посмотреть все видео, ссылки на которые я дал выше), физический слой MIPI объяснить довольно просто. Протокол MIPI использует четыре провода: две шины передачи данных и две шины передачи тактовых сигналов. Также он имеет два режима передачи сигналов: режим Low Power (LP) и режим High Speed (HS).
В режиме Low Power провода используются отдельно для передачи управляющих структур данных, а также для обозначения того, что определённые команды имеют непосредственное влияние на физический получатель с другой стороны. Перепад напряжений в этом режиме довольно велик по сравнению с высокоскоростным режимом: для высокого сигнала напряжения равны примерно 1,2 В, а для низкого сигнала — примерно 0 В. Так как режим низкой мощности имеет больше состояний сигналов, он выполняет такие функции, как отправка получателю приказа о переходе в высокоскоростной режим или выходе из него. На показанном выше графике синие линии показывают передачу данных в режиме Low Power.
В режиме High Speed две шины передачи тактовых сигналов (CLKP/CLKN), а также две шины передачи данных (DP/DN) работают как дифференциальные шины: одна шина всегда противоположна другой. Получатель обнаруживает различия между двумя шинами и исходя из них устанавливает передаваемое значение: 1, если выше DP, и 0 если выше DN. Как можно понять из названия, режим High Speed обеспечивает очень быструю передачу данных с тактовой частотой до 1,5 ГГц. Чтобы добиться этого без слишком больших электромагнитных помех и потребляемой мощности, стандарт применяет следующий трюк: использует в этом режиме очень низкие напряжения: напряжения на парах в среднем равны 200 мВ, с отклонениями в ± 100 мВ на шину для обозначения нулей и единиц. На показанном выше графике красные биты переданы в высокоскоростном режиме.
С точки зрения передачи самих данных в высокоскоростном режиме интерфейс по сути можно рассматривать как довольно странный и дифференциальный интерфейс SPI: существует тактовый сигнал и канал передачи данных, и на каждом такте значение данных передаётся в интерфейс. Отличие от SPI (если не считать то, что сигналы дифференциальны) в том, что бит данных передаётся только при изменении состояния шин CLK, а не только, например, на переднем фронте. Ещё одно отличие в том, что начало передачи распознаётся не когда сигнал в шине /CS становится низким, а внутриполосным сигналом: каждая передача данных начинается с одного уникального «волшебного слова», и получатель определяет это значение, чтобы понять, когда начинается передача.
Чтобы обеспечить взаимодействие этого интерфейса с ESP32, мне придётся выполнить сдвиг уровней. Я хотел запитать ESP32 от источника 3,0 В, чтобы все GPIO тоже имели 3,0 или 0 В. Чтобы адаптировать это к уровням сигналов интерфейса MIPI, я выбрал самое малозатратное решение: просто использовал сети резисторных делителей.
Чтобы вычислить значения резисторов, я создал уравнения для трёх интересующих меня выходных состояний напряжения (1,1 В для высокого сигнала Low Power, 0,07 В для низкого сигнала High Speed, 0,33 В для высокого сигнала High Speed; напряжения выбирались так, чтобы в большинстве случаев оставались в пределах спецификации) и трёх входных состояний, которые должны их генерировать. Я получил уравнения. Теоретически можно было решить их вручную, но в конце концов я забросил их в WolframAlpha и получил требуемые значения резисторов.
3V G -R1--+ R3 G -R2--+ --+----> R4 GND R4*(1.9/R1+1.9/R3)=1.1, (1/(1/R4+1/R1+1/R2))*(2.93/R3)=0.07, (1/(1/R4+1/R1))*2.67*(1/R3+1/R2)=0.33, R2=1000 R1=280, R2=1K, R3=3K6, R4=150
В этот момент я осознал, что можно ещё и немного сжульничать: так как в высокоскоростном режиме шины являются дифференциальными, для определения передаваемых данных дисплей будет смотреть только на разность между двумя шинами. Это значит, что я смогу сэкономить GPIO, сохраняя на одной из шин фиксированное напряжение, и подавая на другую высокий или низкий сигнал. Для этого мне требовался второй тип сети резисторов:
3V R3 G -R1--+ --+----> R4 GND R4*(1.9/R1+1.9/R3)=1.1, (1/(1/R4+1/R1))*(2.8/R3)=0.2, R4=150 R1=320, R3=1500, R4=150
Ещё одной задачей было создание схемы тактовых сигналов. Обычный SPI передаёт бит на переднем фронте шины тактовых сигналов. (Или на заднем, в зависимости от конфигурации.) MIPI передаёт бит и на переднем, и на заднем фронте тактового сигнала. Хотя модуль SPI оборудования ESP32 не может сам генерировать подобные сигналы, мы можем преобразовать один в другой с помощью простого D-триггера, инвертированный вывод которого соединим с входом. Каждый тактовый импульс на входе будет менять уровень выхода, как нам и требуется.
Принципиальная схема
Разобравшись с оборудованием дисплея, мы покончили с самой сложной частью. Теперь нам всего лишь достаточно добавить всё остальное. Начнём с источника питания. Это довольно просто: я питаю всю схему 5 В от преобразователя USB-to-serial, который также можно использовать в качестве интерфейса отладки/программирования. Это напряжение берётся для генерирования +4,6 В, -3,4 В и 1,8 В, необходимых OLED-экрану, а также 3,0 В для питания ESP32. Напряжения +4,6 В и -3,4 В генерируются чипом TPS65631, а справочная схема для этого приведена в datasheet OLED-дисплея. Другие напряжения генерируются парой простых LDO.
У Macintosh был ещё и звук. По современным стандартам качество его не очень высоко (22 кГц, 8 бит), но звуки его программ стали теперь легендарными, поэтому в своём проекте я не мог от них отказаться. В ESP32 есть встроенный 8-битный ЦАП, который используется для создания аналоговых звуковых волн, генерируемых эмулятором. Затем они подаются на NS8002, который является 2-ваттным усилителем звука класса AB, смонтированным в небольшом формате SOIC8. Он дёшев, требует совсем мало поддерживающих компонентов и создаёт более чем достаточный звук для привлечения внимания к крошечному Mac.
Одним из аспектов, которые сделали Macintosh таким революционным, было то, что он стал одним из первых коммерческих компьютеров с мышью. Команда Macintosh так тщательно продумала мышь, что практически вся ОС основана на управляемых мышью элементах UI, и в отличие, например, от IBM PC, всем Macintosh можно было управлять мышью. Очевидно, что моему крошечному Mac тоже требовалось это важное периферийное устройство. Я до сих пор помню шариковые мыши, продававшиеся вместе с первыми Macintosh, но меня не очень радовала необходимость слишком часто чистить ролики от грязи; именно по этой причине эти механические устройства были целиком заменены оптическими мышами. Преимущество этого ещё и в том, что детали для этих новомодных оптических мышей найти довольно просто: например, мне не потребовалось много времени, чтобы обнаружить продавца датчиков игровых мышей ADNS9500 и соответствующей оптики.
Ещё один удобный аспект заключается в том, что датчик оптической мыши — это довольно глубоко интегрированное устройство: для работы ему требуется всего несколько внешних компонентов, и в схеме это отражено. Добавлено несколько конденсаторов для стабилизации напряжения, МОП-транзистор (скопированный прямо из datasheet) для включения лазерного диода и другие стандартные детали. Датчик мыши передаёт данные через четырёхпроводной сигнал SPI, и я использовал один из этих проводов для отправки сигнала кнопки мыши: при нажатии на кнопку контакт MISO довольно сильно утягивается вниз. Значение этого утягивающего резистора недостаточно, чтобы мышь перестала передавать данные, но достаточен, чтобы преодолеть подтягивающий резистор, который в обычном состоянии подтягивает вверх шину, поэтому когда датчик создаёт в шине MISO три состояния, ESP32 может распознать нажатие кнопки.
Наконец, нужно подключить OLED-экран. Мы уже выполнили всю сложную работу по вычислению значений всех резисторов, поэтому схема более-менее должна говорить сама за себя. Добавленный чип — это D-триггер, и он используется для уменьшения в два раза тактовой частоты: как сказано выше, стандарту MIPI новый бит требуется каждый раз, когда инвертируется полярность тактового сигнала, в то время как ESP32 передаёт новый бит только на переднем или заднем фронте.
Нарисовав принципиальную схему, я перешёл к созданию конструкции печатной платы. Выбранный мной дисплей должен был монтироваться на управляющую им плату, а коннектор должен находиться на задней стороне этой печатной платы. Хотя при этом бы не осталось много места для других компонентов, я всё равно хотел разместить все остальные компоненты на другой стороне.
Здорово иметь хорошее зрение и термофен: это позволило мне использовать компоненты 0603 и расположить всё на ограниченном пространстве платы. Особенно трудно было бы соединить обычным паяльником коннектор дисплея и QFN-чип питания OLED.
Я понял, что датчик мыши и его компоненты займут на плате слишком много места, поэтому решил припаять все компоненты к самому датчику. Благодаря этому всё можно будет поместить в мышь.
Программное обеспечение
Очевидно, что достаточно важным элементом этой сборки является ПО: необходимо эмулировать Macintosh целиком. Однако Macintosh не такая уж сложная машина. По сути, он состоит из микроконтроллера 68000, контроллера последовательной передачи Zilog Z8530, управляющего последовательным портом, 6522 VIA для внутренних вводов-выводов и для обеспечения интерфейса с клавиатурой, а также нескольких программируемых матриц логики (PAL), содержащих логику для дисплея и звука. Также в нём есть чип Integrated Woz Machine, обеспечивающий интерфейс с приводом гибких дисков. Это довольно сложный чип; однако я не планирую эмулировать гибкий диск, поэтому будет достаточно эмулировать IWM, постоянно возвращающий, что в приводе нет диска. Вместо него я планирую полностью эмулировать SCSI-чип NCR 5380, подключенный к эмулируемому жёсткому диску SCSI, который будет выполнять считывание со встроенной в модуль ESP32-Wrover флеш-памяти.
Более того, в системе будет очень мало программ с прямым доступом к оборудованию: программистам, создававшим ПО для Mac, с самого начала говорили использовать слои абстракции оборудования уровня ОС, чтобы сохранять совместимость с последующими версиями оборудования Mac. В целом это означает, что если мне удастся эмулировать «железо» в такой степени, что ОС загрузится и будет всем довольна, то большинство программ заработает без всяких проблем.
Поэтому я решил, что можно попробовать написать эмулятор с нуля. Точнее, не совсем с нуля; 68000 — довольно сложный зверь и мне не хотелось изобретать заново этот велосипед. Вместо этого я поискал в Интернете и обнаружил, что в MAME есть удобный и быстрый эмулятор 68K на основе C под названием Musashi, который вполне подходит под мои требования. Нужно будет немного поколдовать, чтобы перенести опкоды вместо ОЗУ во флеш-память, но в остальном для портирования на ESP32 почти ничего нужно.
Однако я не планировал разрабатывать весь проект на ESP32: так как у чипа есть поддержка OpenOCD, обеспечивающая достаточно широкие возможности отладки, цикл «загрузил-протестировал-исправил-загрузил» будет слишком монотонным. Поэтому я решил сначала разрабатывать всё на своей машине с Linux, не забывая ограничения ESP32. Так я приступил к работе, используя datasheet для разных чипов, информацию Linux-68K для машины, а также сведения из серии Inside Macintosh, которые можно найти в Интернете. Когда я не мог разобраться, что делать дальше, можно было заглянуть под капот других эмуляторов с открытым исходным кодом.
Вооружённый всем этим, выбрав в качестве компилятора C gcc и libsdl в качестве библиотеки для работы с графикой, я приступил к работе. Если вкратце, то спустя какое-то время у меня получится простой, зато в целом функционирующий эмулятор MacPlus: мышь, видео, жёсткий диск SCSI и звук заработали:
Так как моё «железо» ещё не было готово, я решил портировать свой эмулятор на devboard ESP-Wrover-Kit. У меня всё равно было под рукой несколько таких плат, и кроме модуля Wrover, который я и так буду использовать, на них есть удобный дисплей 320x240, который можно использовать для проверки работы видео.
После настройки эмулятор Mac достаточно хорошо заработал на этой плате; на самом деле обычно он достаточно близок к 7,8 МГц, на которых работает Mac Plus. (7,8 МГц будет немного быстрее, чем Mac Plus; так как в настоящей машине часть циклов памяти отъедают буфер кадра и звуковая система, частоту можно понизить на 35%.)
Очевидно, что работа эмулятора на devboard — это хороший шаг вперёд, но в конце концов всё должно работать на экране, который я купил, а не на экране devboard. И ещё один момент: экран devkit имеет экран 320x240 и обрезает солидную часть экрана Mac. Дисплей, который буду использовать я, имеет размер 320x320, а потому больше только по вертикали: как мне удастся отобразить на нём экран Mac размером 512x342?
Есть только один способ поместить 512x342 пикселя на экран 320x320, и это масштабирование. По сути, мы берём изображение, сжимаем его, делая меньше, а затем отображаем. Однако масштабирование можно выполнить кучей разных способов, а учитывая то, что генерируемое ОС чёрное-белое изображение предполагает, что каждый пиксель создаёт на экране чётко заданную точку света, то есть много способов всё испортить. Мне нужно потерять как можно меньше пикселей разрешения. То есть необходимо увеличить разрешение OLED-экрана.
Но как это сделать? Вряд ли можно вскрыть OLED-экран и засунуть внутрь ещё немного пикселей. Однако, делать этого и не нужно; разрешение OLED-дисплея и так в три раза больше заявленного. Причина заключается в том, что этот экран цветной: у каждого виртуального «пикселя» есть красный, зелёный и синий субпиксели. Кроме того, в этом конкретном экране субпиксели выстроены треугольником. Вот близкий снимок экрана со включенными тремя пикселями:
Как видите, пиксели — это треугольные наборы из трёх субпикселей; в зависимости от столбца треугольники указывают вниз или вверх. По сути это означает, что субпиксельное разрешение экрана равно 480 x 640 пикселям. Хоть этого всё равно не полностью хватает для отображения 512x342 пикселей, разница настолько мала, что при правильном выборе небольшого масштабирования дисплей будет настолько читаемым, насколько это возможно для 1,63-дюймового экрана, отображающего GUI, рассчитанный на 9-дюймовый экран:
Корпус
Итак, теперь у меня есть дисплей и ПО, достаточно хорошо эмулирующее Macintosh Plus, плюс микроконтроллер, на котором его можно запускать. Чего же не хватает? Очевидно, что корпуса для всего этого!
Я решил напечатать его на 3D-принтере Formlabs 1+ SLA со своей работы. Для этого мне сначала понадобится модель. Я хотел создать её с нуля. Очевидно, что лучше для этого иметь под рукой настоящий Macintosh Plus. На самом деле он у меня есть, но нас разделяют полконтинента… К счастью, мне удалось найти почти столь же хорошее решение: какой-то добрый человек указал размеры оригинального Mac 128K (корпус которого почти такой же, как у Plus) в wiki iFixit.
Я по-прежнему создаю все свои 3D-модели в OpenScad, и после мучений и ругательств мне удалось заставить все кривые выглядеть так, как нужно. Я получил красивую модель Mac в масштабе 1:6.
Мышь я тоже создал по изображениям с iFixit, но так как в неё должен помещаться достаточно крупный датчик оптической мыши, её нельзя масштабировать до 1/6 от настоящей мыши. Масштаб ближе к 2/5, поэтому мышь выглядит большой по сравнению с крошечным Mac, зато она намного удобнее для неотмасштабированных человеческих пальцев.
Итак, всё, что осталось — напечатать модели. Я экспортировал конструкцию в различные файлы STL и напечатал их на Formlabs 1+. Конечный результат оказался достаточно хорошим; я сожалею только о том, что не добавил на обе части конструкции защёлки. Решилась эта проблема каплей суперклея.
Результат
Итак, у меня были все компоненты и оставалось их только собрать. Разъёмы печатной платы в передней части корпуса крепятся несколькими зажимами. Преобразователь usb-to-serial, используемый как механизм загрузки и источник питания, подключён к задней части и тоже держится на нескольких зажимах. Я забыл сделать что-нибудь для крепления внутри динамика, но его удалось фиксировать в корпусе суперклеем. Мышь соединена набором тонких проводов. (Да, они несколько не соответствуют цветовой схеме… как только найду более красивый многожильный кабель, я это исправлю.)
Когда разрабатываешь всё на компьютере, то осознаёшь истинный масштаб изделия, только когда проект полностью материализуется, например, при получении печатных плат с фабрики или после завершения печати на 3D-принтере конструкции корпуса, над которой ты работал неделями. Разумеется, я знал, что всё будет в шесть раз меньше оригинального Mac, но только собрав всё вместе и увидев машину вживую, я осознал, что это значит. Mac действительно оказался крошечным!
И даже несмотря на малые размеры и отсутствие клавиатуры, он способен запускать большинство программ, которыми знаменит Mac. Вот демонстрация. Первые 20 секунд выполняется проверка памяти, и я знаю по опыту, что такая долгая проверка — это не баг: оригинальному Mac Plus требовалось такое же время на загрузку, даже после апгрейда.
Итак, что же хорошего в этом Mac Plus? Хоть я и получил удовольствие от его создания, нужно признать, что без клавиатуры его нельзя использовать в полную силу. Кроме того, у него нет средств связи: я планировал реализовать AppleTalk через WiFi, но мне это не удалось из-за странностей, которые я не смог эмулировать в самом чипе контроллера последовательной шины оригинального Mac. Тем не менее, имея завершённый проект в таком состоянии, я наконец-то могу исполнить свою давнюю мечту и поставить на стол Mac с летающими по экрану тостерами из скринсейвера After Dark:
Как обычно, этот проект является open-source, дизайн печатной платы и корпуса, а также прошивка выложены на Github. Всё лицензировано по Beer-Ware license, поэтому вы можете делать с этим практически всё, что захотите. Если вы когда-нибудь используете что-то в своих проектах, то можете написать об этом мне.