Pull to refresh

Самодельный компьютер из платы АОНа

Reading time10 min
Views19K
В последнее время на Хабре появилось несколько статей про самодельные компьютеры, созданные из различных нестандартных компонентов. Я тоже решил рассказать о своем компьютере, созданном в далеком 1993 году. На волне всеобщего увлечения синклерами, мне захотелось иметь полностью оригинальный 8-ми битный компьютер на основе z80 и, кроме того, создать для него программное обеспечение, начиная от операционной системы и заканчивая игрушками. Что из этого получилось, читайте под катом.

Первым вопросом, возникшим при создании своего компьютера был вопрос архитектуры. Я решил, что он будет достаточно компактным, на основе процессора z80, без вывода изображения на телевизор, но с LCD текстовым экраном, достаточно большой клавиатурой, выводом звука в виде однотоновой пищалки (стандартной в то время для большинства компьютеров) и портом RS232 для подключения к другим компьютерам с целью программирования и отладки.

Однако, возникла проблема, где взять печатную плату под полностью оригинальную схему, ведь китайцы в 1993 году платы для всего мира еще не производили, а заказывать разработку и производство на заводе было очень дорого. И тут я обратил свое внимание на платы, из которых делали АОНы — автоматические определители номера. Они были в продаже на Митинском рынке, имели возможность установить необходимые мне компоненты, но нуждались в доработке. В продаже имелось несколько разновидностей таких печатных плат из которых, если мне не изменяет память, я выбрал плату под названием «Русь».

Напомню для молодых читателей, что представлял из себя АОН. Это был телефон с кнопочным набором (кстати, в то время это было довольно круто, поскольку большинство телефонов были с крутящимся диском), состоящим из 12 кнопок и светящимся 9-ти разрядным, 7-ми сегментным индикатором, на котором высвечивался телефонный номер, если его удавалось определить и некоторая другая информация. Плата АОНа содержала процессор z80, параллельный порт КР580ВВ55, схему дешифратора, таймер КР580ВИ53, 32-х килобайтное ПЗУ (с ультрафиолетовым стиранием), 8-ми килобайтное статическое ОЗУ, аналоговую часть, связанную с телефонной линией и, возможно, что-то еще, о чем я, за давностью лет, забыл.

12 кнопочная клавиатура и имеющийся на АОНе индикатор меня категорически не устраивали, поскольку ни вводить информацию ни отображать ее (в частности буквы) они нормально не могли. Поэтому, было принято решение использовать 40-кнопочную самодельную клавиатуру и текстовый 24-х символьный, 2-х строчный LCD экран, которые тогда появились в продаже. Для подключения клавиатуры и LCD экрана необходимо было модифицировать схему АОНа. Первое, что я сделал — это выбросил всю аналоговую часть, связанную с телефонной линией, вернее, просто не стал ее запаивать. При этом освободились выходные ножки на дешифраторе, который выбирал разряд индикатора и ножки на параллельном порту КР580ВВ55. LCD экран требовал 4 линии для передачи информации, а вот для клавиатуры необходимо было задействовать дешифратор. Поскольку 40 кнопок на клавиатуре располагались матрицей в 5 рядов по 8 штук, было принято решение подключить эти 8 кнопок к дешифратору, чтобы можно было их сканировать, подавая разные комбинации по трем линиям на вход дешифратора и считывая 5 значений с рядов кнопок с помощью КР580ВВ55. Таким образом, получался скан-код нажатой кнопки на клавиатуре величиной в один байт, где первые три бита определяли колонку, в которой была нажата кнопка, а 5 остальных битов указывали на то, в каком ряду эта кнопка (или сразу несколько кнопок) была нажата. Кроме того, я посчитал, что 8-ми килобайт ОЗУ будет недостаточно и заменил его на 32-х килобайтное. При этом пришлось перепаять пару дорожек на печатной плате, благо, что 8-ми и 32-х килобайтные корпуса ОЗУ были почти идентичны по распиновке. Таким образом, у меня получилось 32 килобайта ПЗУ и 32 килобайта ОЗУ (напомню, что z80 может адресовать максимум 64 килобайта, так, что я использовал адресное пространство по максимуму). Кроме того, еще две линии КР580ВВ55 ушли на последовательный порт RS232, необходимый для подключения к другому компьютеру. Все это хозяйство я поместил в корпус от тестера. В результате получилась такая конструкция:

image

После того, как «железо» было сделано, пришло время создавать программное обеспечение. Тут надо заметить, что процесс его создания выглядел совсем не так, как выглядит сегодня. Во-первых, большая часть была написана на ассемблере z80 (хотя в то время практически все программы для подобных систем писались на ассемблере, ну, если не считать бейсика). Для компиляции с ассемблера на первом этапе мне помогал мой товарищ, у которого был компьютер «Профи» — клон синклера, но с TR-DOS на борту. Потом я стал использовать мой IBM PC, на котором стояла OS/2, в которой запускался MS-DOS в отдельном окне, в которой, в свою очередь, запускался эмулятор TR-DOS, в котором и происходила компиляция. Надо сказать, что когда программное обеспечение заметно разрослось, процесс компиляции и сборки стал занимать десяток минут. Второй проблемой было то, что никаких отладочных средств не существовало и, поэтому, было необходимо каждый раз загружать все на мой самодельный компьютер и проверять, как это работает (первое, что пришлось сделать — это программу для работы RS232 на 9600 бит в секунду для загрузки). Ну и наконец, следует сказать, что после отладки образа ПЗУ, его необходимо было в это самое ПЗУ записать, предварительно стерев то, что там было уже записано. Стирание происходило с помощью ультрафиолетовой лампы для загара через специальное окошко в корпусе микросхемы, которое затем закрывалось специальной черной клейкой бумажкой, служащей для заклеивания дырки сбоку на 5-ти дюймовой дискете для защиты от записи. Кроме того, поскольку программатора у меня не было, для записи новой версии программного обеспечения в ПЗУ, каждый раз приходилось ехать к моему товарищу на другой конец Москвы, у которого этот программатор был. Тут надо прояснить вопрос, как было возможно отлаживать образ ПЗУ, не загружая его в это самое ПЗУ? Для этого имелся базовый адрес, который использовало все мое программное обеспечение смещая адреса на 32 килобайта. То есть новый образ ПЗУ загружался в ОЗУ и отлаживался там, а после отладки базовый адрес устанавливался в ноль, все заново компилировалось и я ехал к товарищу для записи новой версии в ПЗУ.

Когда процесс «кодирование»-«отладка»-«прошивка» был отлажен, встал вопрос написания операционной системы. Операционная система должна была поддерживать ввод с клавиатуры, отображение информации на LCD экране, поддержку вывода разных звуков и делать все это по возможности параллельно, то есть быть многозадачной. Для всего этого была выбрана следующая архитектура — в ОС существовали три типа задач: несколько задач реального времени с высшим приоритетом, одна задача пользователя, которая взаимодействовала собственно с человеком и фоновые задачи, которые работали, когда процессор простаивал. Задачи с высшим приоритетом я повесил на прерывание, которое генерировалось таймером КР580ВИ53 10 раз в секунду. Тут следует сказать, что в АОНе прерывания генерировались 400 раз в секунду, поскольку было необходимо обновлять индикатор очень часто для того, чтобы человек не замечал мерцания. Кроме обновления индикатора в прерываниях происходил опрос клавиатуры на предмет поиска нажатой клавиши. Такое частое прерывание в АОНе приводило к тому, что большая часть времени работы процессора уходило собственно на прерывания, а я хотел, чтобы мой компьютер делал еще что-то полезное. Поскольку я в качестве устройства отображения информации установил LCD экран, который имел собственную память и не нуждался в динамическом обновлении, необходимость в такой частоте прерываний отпала. Экспериментальным путем было установлено, что 10 прерываний в секунду достаточно для опроса клавиатуры.

Таким образом, задачи реального времени запускались на каждом прерывании и помещали новую информацию в специальную очередь событий. События были нескольких типов и содержали соответствующую типу события информацию, например, событие «нажата кнопка» содержала скан-код. Собственно одна из приоритетных задач, запускавшаяся на каждом прерывании, была задача опроса клавиатуры и программное удаление дребезга контактов. Кроме этой задачи были еще приоритетные задачи — таймеры которые могла установить задача пользователя, и которые по истечении заданного времени ставили в очередь соответствующее событие, были будильники, которые срабатывали в определенное время, часы и календарь, а также была задача, которая играла музыку с помощью программирования КР580ВИ53, причем на вход ей подавались данные в виде нот. Таким образом, задача пользователя могла просто запустить проигрывание музыки и заниматься другими делами.

Полезную работу в моем компьютере делала задача пользователя, которая управлялась событиями. Типичный вид такой задачи выглядел как:

while(true) {
  Event e;

  GetEvent(e);

  /* process event */
}


Здесь функция GetEvent(e) ждала появления события в очереди и при его появлении заполняла структуру Event. Понятно, что по определенному событию, программа могла выйти из бесконечного цикла и передать управление обратно системному монитору, который запускал пользовательские задачи. Поскольку события в пользовательской задаче обычно обрабатывались быстро, программа ждала появления нового события в процедуре GetEvent(e). Чтобы как-то утилизировать это ожидание были введены фоновые задачи, которые могла запускать пользовательская программа и которые работали независимо. То есть при возникновении прерывания от таймера сначала отрабатывали задачи реального времени, помещая события в очередь, затем по окончании прерывания задача пользователя обрабатывала все события из очереди, а оставшееся время до следующего прерывания отдавалось фоновым задачам. Сегодня подобная схема выглядит естественной, но в 1993 году она была очень прогрессивной.

Была еще одна проблема, связанная с тем, что LCD экран не умел выводить русские буквы. Это было связано с тем, что такие индикаторы только что появились у нас на рынке и никто их специально не русифицировал. Естественно, библиотек для работы с ними тоже не было, а была только спецификация и описание команд. Поэтому всю работу по инициализации и работе с ним пришлось написать с нуля. Проблему с русификацией я решил следующим образом: была написана специальная процедура, на вход которой подавалась строка с русским текстом, который необходимо было отобразить. Эта процедура заменяла все русские буквы, которые были похожи на латинские соответствующими латинскими, а те, которые заменить было невозможно, создавались на лету с помощью пользовательских изображений, которые можно было загрузить в LCD экран. Следует заметить, что таких пользовательских изображений в нем было восемь и, соответственно, в русском тексте могло быть не более восьми букв, аналогов которых не было в латинском алфавите. Но обычно этого хватало. Еще одна забавная возможность, которую я использовал, состояла в том, что если менять пользовательские изображения для одного символа, то получается забавная анимация. Я использовал такую возможность в игре «сапер», о которой расскажу ниже, где флаг над найденной миной развевается на ветру, а бомба взрывается.

Теперь расскажу про те пользовательские задачи, которые я создал для своего компьютера. Когда я стал создавать их, то быстро понял, что писать их на ассемблере, как все делали в то время для систем типа синклер, довольно грустно. Поскольку на IBM PC я программировал на С, то я задумался о том, можно ли программировать на С и на моем компьютере. После некоторого поиска я обнаружил компилятор с языка С для TR-DOS, причем в его классическом K&R варианте. Этот компилятор, был довольно примитивный, например, он не проверял соответствие количества передаваемых процедуре параметров их действительному количеству, не говоря уже о проверке их типов. Но если бы удалось использовать этот компилятор для моего компьютера, это был бы грандиозный прогресс. Проблема заключалась в том, как адаптировать полученный этим компилятором код для моего компьютера. Здесь я пошел классическим путем, о котором сейчас уже забыли, а в 1993 году еще помнили. Этот путь заключался в том, чтобы компилировать сишный код не сразу в объектный файл, а в ассемблерный исходный код. Таким образом, необходимо было только написать макросы, которые позволяли бы вызывать из ассемблера процедуры, скомпилированные из сишного кода и наоборот, с правильной передачей параметров, что и было сделано. Использование этого компилятора кроме удобства работы с кодом давало еще одно большое преимущество — стало возможным работать с умножением/делением целых чисел (напомню, что в z80 отсутствуют команды умножения и деления целых чисел), а также (о чудо !), возможность работы с числами с плавающей запятой. Правда, для этого пришлось разбираться с прилагаемой к компилятору библиотекой, поскольку все математические операции после компиляции выполнялись в виде обращения к этой библиотеке. Проблема была в том, что эта библиотека использовала временные переменные, которые компилятор размещал в модели памяти, характерной для TR-DOS, что, понятно, совершенно не соответствовало модели памяти моего компьютера. Пришлось искать все такие временные переменные и переделывать под адреса, подходящие под мое ОЗУ. Зато в качестве бонуса я получил множество стандартных функций типа синуса и косинуса, которые я использовал в своих задачах, о чем расскажу ниже.

Первой программой, которая была создана с использованием сишного компилятора, была программа для проверки правильности работы ОЗУ. Эта программа проходила по всему ОЗУ, записывала, а затем считывала различные паттерны и сравнивала результат. При этом весь процесс отображался на LCD экране, что также подтверждало правильность работы процедур, связанных с ним. Понятно, что это была просто проба пера перед написанием более интересных программ. Следующее, что я сделал — это создал шестнадцатеричный редактор для изменения содержимого ОЗУ. Этот редактор обладал уже достаточно широкими возможностями, включая не только просмотр и редактирование ячеек памяти, но и поиск интересующих байт в ОЗУ. В частности, появилась возможность писать маленькие программы прямо в ОЗУ в машинных кодах. Чтобы проверить звуковые возможности своего компьютера, я создал программу типа «музыкальная шкатулка», которая проигрывала различные мелодии (напомню, что мелодии записывались в виде нот). Далее, была написана программа для игры «быки и коровы», где человеку предлагалось угадать задуманное компьютером случайное четырехзначное число (да, генератор случайных чисел я тоже сделал). В этой программе мне пришлось создать прокручивающийся список с историей уже введенных чисел, поскольку на двух строках особо длинный список отобразить было нельзя. Следующей созданной программой был классический «сапер» на поле 16x16. Поскольку у меня было в распоряжении только две строки, я сделал скроллинг, добавив музыкальное сопровождение и анимацию развевающегося на ветру флага и взрывающихся бомб. Далее, для медитации я сделал программу, которая показывала ползающих в случайном направлении червячков, задействовав возможность загружать пользовательские изображения в экран. Ну и куда же без классических крестиков-ноликов? В них я протестировал алгоритм минимакс, то есть компьютер просчитывал (довольно быстро) все варианты и никогда не проигрывал. Поскольку у меня в операционной системе поддерживались будильники, время и календарь, я сделал пользовательскую программу, которая выставляла время, дату и несколько будильников, которые при достижении заданного времени играли разные мелодии. Ну и наконец, венец творения в моем компьютере — это программа расчета траектории движения в задаче двух гравитирующих тел. То есть, по заданному начальному положению и скорости тела, а также массы притягивающего центра, решалась задача численного интегрирования системы из двух нелинейных обыкновенных дифференциальных уравнений второго порядка с заданным шагом интегрирования. Для этого я добавил процедуру, которая численно интегрировала эти уравнения. Все переменные были формата double с плавающей запятой. Здесь я еще раз подчеркну, что в процессоре z80 нет не только аппаратной поддержки работы с плавающей запятой, но и обычного умножения/деления целых чисел, все делалось программно. Это интегрирование работало довольно неспешно, примерно один шаг в секунду, но работало!

Что мне хотелось еще реализовать, но не сложилось — во-первых динамическое выделение/освобождение памяти, чтобы можно было гибко управлять ресурсами каждой задачи. Также осталась нереализованной запись/чтение на магнитофонную ленту, как это было сделано в синклере. Но в целом я остался доволен тем, что удалось сделать. Особо подчеркну, что все вышеперечисленное находилось в ПЗУ объемом 32 килобайта — сегодняшний размер одного не очень большого электронного письма.

Посмотреть на мой компьютер в действии можно здесь:




А скачать его программное обеспечение здесь
Tags:
Hubs:
+54
Comments44

Articles

Change theme settings