Как подключить процессор к ардуино
Ретрокомпьютерщики бывают различной степени привередливости. Одни довольствуются эмуляцией. Другие предпочитают ПЛИС, потому что тогда получается не эмуляция, а воссоздание. Наконец, третьим подавай настоящий процессор.
Но процессору для работы нужно столько всего! Снова дилемма: взять настоящие микросхемы тех же лет, или поместить всё в ПЛИС, оставив снаружи процессор? Впрочем, почему обязательно ПЛИС? Да здравствует союз Arduino и классического процессора!
Подарите своему Arduino «второй мозг» и сделайте его «умнее».
Настоящий восьмибитный микропроцессор выполняет программы, а Arduino эмулирует ПЗУ, ОЗУ и простейшую периферию.
Проектируйте виртуальную периферию в Arduino IDE, а на микропроцессоре запускайте код на ассемблере. Не нужно собирать сложные схемы и прошивать параллельные ПЗУ.
Поддерживаемые микропроцессоры: 6502, 6809 и Z80 (КР1858ВМ1), на подходе — другие.
Шилд с микропроцессором не мешает подключать другие шилды: с ЖКИ, картами памяти, и др.
Помимо самостоятельного программирования на ассемблере, можно попробовать запустить на микропроцессоре какой-нибудь классический код.
Правда, микропроцессор будет работать на очень небольшой частоте — порядка 95 кГц, точное её значение зависит от оптимизации кода эмуляции периферии.
Распределение адресного пространства задаётся программно в скетче. Микропроцессору можно выделить от 4 до 6 кБ ОЗУ из 8 кБ, имеющихся на Arduino Mega. ПЗУ можно выделить более 200 кБ из имеющихся 256.
При помощи последовательного порта Arduino Mega можно эмулировать UART.
Схемы, чертежи плат, Gerber-файлы доступны под CC-BY-SA 4.0 здесь. При этом имеется требование обязательно прикладывать файл README.md, потому что в нём содержится следующее предупреждение:
Не подключайте шилд, пока не залит скетч эмуляции периферии! Иначе возможно закорачивание выходных линий микропроцессора.
Да и в самом скетче что-нибудь переделывать нужно осторожно по той же причине.
Схема устройства на 6502:
Схема устройства на 6809:
Схема устройства на Z80:
Уже можно запустить:
На устройстве с Z80 — пока только эхо-тест последовательного порта, позволяющий проверить работоспособность виртуального 8251 (КР580ВВ51А).
Прошивки для эмуляции периферии — под лицензией MIT.
Краткие описания принципа действия:
К устройству на Z80 — в процессе подготовки.
Разработчик пытается продавать устройства, но с доставкой только по США. Особого смысла покупать нет, поскольку схема очень простая, повторить её на куске макетки можно за час.
Запланирована разработка аналогичных плат на RCA1802, 68008, 8085 (КР1821ВМ85А), 8088 (КР1810ВМ88). Про К1801ВМ1 не сказано, но можно подкинуть автору такую идею.
Рассмотрим взаимодействие Arduino и устройства на 6502. Arduino периодически меняет уровень на входе микропроцессора, предназначенном для подачи тактовых импульсов, с нуля на единицу и обратно. На каждом такте оно проверяет, что происходит на линиях управления и шине адреса, и, в зависимости от ситуации, считывает информацию с шины данных или отправляет её туда. Arduino может также управлять линиями IRQ и NMI, вызывая прерывания. На рисунке показаны виды данных и направления их передачи:
Соответствие портов Arduino и выводов микропроцессора сконфигурировано в скетче:
Разобьём каждый такт на следующие события:
CLK меняет состояние с единицы на нуль (спад)
CLK находится в состоянии нуля
CLK меняет состояние с единицы на нуль (нарастание)
CLK находится в состоянии единицы
CLK снова меняет состояние с единицы на нуль…
Что происходит в моменты смены состояний?
6502 получает тактовые импульсы по входу CLK0, буферизует их и отправляет на два выхода: CLK1 и CLK2. Хотя в микропроцессоре все события привязаны к CLK1, будем считать, что задержка невелика, и они привязаны к CLK0 — той линии, по которой здесь микропроцессор получает тактовые импульсы из Arduino. И называть сигнал просто CLK.
1. CLK меняет состояние с единицы на нуль.
2. Микропроцессор выводит на шину адреса новый адрес, а на выход R/W — сигнал переключения между чтением и записью. Но он ещё не готов к обмену данными.
3. CLK переходит в состояние единицы, и это означает, что обмен данными начался. Если это операция чтения, микропроцессор переводит выводы шины данных в состояние входов и принимает по рим данные, а если операция записи — переводит их в состояние выходов и отправляет данные. А сигнал R/W переключает внешнее устройство в режим записи или чтения, противоположный соответствующему состоянию микропроцессора.
4. CLK переходит в состояние нуля. Теперь на шину даннных ничего не выводят ни микропроцессор, ни устройства ввода-вывода. Микропроцессор может установить в новое состояние линии шины данных и вывод R/W.
Простое объяснение, понятное и ребёнку. Который никогда и не задумается об этих «закулисных интригах», если будет программировать только микроконтроллеры. Даже на ассемблере.
Если нужно подключить своё периферийное устройство, оно должно успевать подготавливать данные до того, как на линии CLK появится единица (время подготовки), а пока там единица — не менять их. Если периферийное устройство не успеет подготовить данные, пока на CLK нуль, или поменяет их, когда там единица, вы будете долго недоумевать, почему ваш код не работает. Поскольку тактовая частота микропроцессора здесь в десять-пятнадцать раз ниже номинальной, соблюсти это требование просто. Но нужно обязательно.
Итак, нужно «научить» Arduino генерировать тактовые импульсы, непрерывно проверяя, что при этом происходит на шине адреса и линии R/W, и соответствующим образом взаимодействуя с шиной данных. Для этого в скетче задействовано прерывание по таймеру timer1, вырабатывающему импульсы с частотой в 95 кГц. Arduino работает значительно быстрее микропроцессора, и потому между его тактами успевает всё и считывать, и подготавливать. Важно проследить, чтобы после модификации скетча это условие продолжало соблюдаться.
Вот выдержка из скетча, по которой понятно, как CLK переходит из нуля в единицу, и что происходит далее:
Распределение адресного пространства можно сделать каким угодно, в немодифицированном скетче оно такое же, как в Apple 1 с 256 байтами ПЗУ, 8 килобайтами ПЗУ для Бейсика, 4 килобайтами ОЗУ и устройством ввода-вывода 6821.
ОЗУ эмулируется массивом byte RAM[RAM_END-RAM_START+1]. Два ключевых слова PROGMEM нужны, чтобы содержимое эмулируемых ПЗУ хранилось во флеш-памяти микроконтроллера.
6821 эмулирован в достаточной мере, чтобы через «терминалку» работали виртуальные клавиатура и дисплей. Woz Monitor и Бейсик работают, чего и добивался автор.
Чтобы эмулировать любое периферийное устройство, нужно внимательно ознакомиться с его даташитом и выяснить, какие у него есть регистры, и для чего они предназначены. Удобство эмуляции — в гибкости, с которой можно делать программные аналоги периферии.
Устройства ввода-вывода находятся в адресном пространестве микропроцессора, обращение к ним происходит так же, как к ячейкам памяти. Чтобы использовать «железную» периферию, такую, как ЖК-дисплей, карту памяти, выход звука, нужно выделить им в адресном пространстве место.
Переходим к 6809, в нём имеются:
Два восьмибитных аккумулятора A и B, которые могут быть объединены в один шестрадцатиразрядный аккумулятор
Два 16-битных индексных указателя стека
Адресация относительно счётчика команд
Автоматическое прибавление или вычитание числа 1 или 2
Перемножение двух восьмиразрядных чисел без знака
16-битная арифметика
Перенос и обмен данными между всеми регистрами
Запись и чтение всех регистров и любого их сочетания
Микропроцессору 6809E (external) нужен внешний тактовый генератор, у 6809 он внутренний. У Hitachi они называются, соответственно, 6309E и 6309, от обычных они отличаются тем, что внутри операции у них выполняются в 32-разрядном виде, но возможно переключение в режим совместимости с классическим вариантом.
Собственно, весь проект RetroShield потому и начался, что автор хотел модернизировать свой же самодельный компьютер Simon6809 и назвать результат Simon6809 Turbo. Но оказалось, что микросхем стандартной логики для всего, что он хотел там реализовать, потребовалось бы очень много. Поэтому идею RetroShield автор впервые сформулировал именно применительно к 6809, и лишь затем подумал: «а что если и с другими процессорами то же проделать?».
В устройстве, разумеется, применён 6809E, требующий внешнего тактового генератора, чтобы можно было синхронизировать его работу извне. Линии E и Q у обоих процессоров называются одинаково, только у 6809 это выходы, а у 6809E — входы.
С 6809 Arduino взаимодействует так же, как с 6502, но входов тактовой частоты у него два: E и Q, а входов для прерываний — три: IRQ, FIRQ и NMI.
В этот раз соответствие портов Arduino и выводов микропроцессора сконфигурировано так:
Как видно из графиков, сигнал Q сдвинут относительно E на четверть периода:
Обращать внимание на Q мы почти не будем, так как все события привязаны к E. А происходит всё так:
- E переключается в нуль. Процессор выставляет на шину адреса новый адрес и меняет состояние линии R/W.
- E переключается в единицу, процессор становится готов к обмену данными.
- Неважно, что происходит с шиной данных, пока на E единица, главное, чтобы требуемые данные там присутствовали в момент перехода E обратно в нуль.
- При чтении данных устройство ввода-вывод должно подать на шину данных требуемые данные до перехода линии E из единицы в нуль (минимальная задержка показана числом 17 в кружке).
- При записи устройство ввода-вывода должно зафиксировать данные у себя в каком-нибудь регистре в том виде, в каком они были в момент перехода E из единицы в нуль. Процессор же подаст эти данные на шине даже раньше — в момент перехода Q в единицу (число 20 в кружке).
- После перехода E в нуль всё повторяется.
Генерация сигналов E и Q, как и в случае с 6502, с той лишь разницей, что сигналов два, и переключать их надо в соответствии с графиками. И точно так же подпрограмма, вызываемая по прерыванию, выполняет в требуемые моменты ввод или вывод данных.
Адресное пространство в немодифицированном скетче распределено так же, как в самодельном компьютере Simon6809:
ОЗУ и ПЗУ хранятся в массивах так же как в варианте на 6502, с той лишь разницей, что здесь массив с данными ПЗУ — один.
Устройствам ввода-вывода здесь также выделены участки адресного пространства, и они могут быть как виртуальными, так и реальными. Так как Simon6809 — современная машина на винтажной элементной базе, с ПК, на котором запущена «терминалка», она обменивается данными через FTDI. Здесь эмулировано и это.
Всем привет! В этой публикации я расскажу про свой опыт создания небольшой вычислительной системы, проблемах связанных с этим и способах их решения, но обо всем по порядку.
Меня зовут Евгений, я студент 4 курса МатМеха УрГУ(урфу). Примерно на первом курсе я понял, что меня завораживают старые компьютеры и старые технологии. Примерно тогда же я купил себе советский клон ZX Spectrum`а - Урал-8/64K.
ZX Spectrum, для тех, кто в танке, это совершенно легендарный компьютер, его знают и любят все, кто даже далек от этой всей компьютерной темы. Об этом, так же, говорит и наличие современной разработки компьютерных игр для него. В общем, много чего можно рассказать о нем, но не будем сильно отвлекаться. Так вот, купил я себе такой, поигрался, попрограммировал, мне понравилась его простота, какое понимание он дает о своем устройстве тем, кто впервые с ним работает.
Немного раньше я делал небольшие проекты на Arduino и думал над каким то достаточно большим проектом, но сильно не хотел делать еще одну "умную поилку для собаки", которых в интернете наплодили достаточно, чтобы потерять всякий интерес к этому, не то, чтобы он до этого был большой. Я подумал, мне нравится Spectrum, так почему бы не сделать его эмулятор, софтверных эмуляторов полно, да и физических тоже хватает, но я решил посмотреть что есть и сделать свое. Я погуглил и нашел статью, которая и толкнула меня к первым шагам.
Что мне не понравилось в уже готовых аналогичных проектах, чтобы просто их реализовать:
Абсолютно нечитаемый код на 2000+ строк кода в одном файле с кучей комментариев - иногда не только пояснительных но и кода.
Мощные платы - в моей задумке было использовать простенькую Arduino Nano, а точно не Mega!
Много плат - опять же хотелось использовать всего одну простую плату и вместить на нее все необходимое.
Необходимость обновления прошивки при появлении новых программ для устройства - убивает внутреннюю flash память устройства, нужно что то другое.
Электронный модуль
Модуль – это специальная удобная плата на базе какой-то микросхемы или электронного компонента. Модуль может быть датчиком, драйвером, интерфейсом, памятью, дисплеем и так далее. Зачем использовать модуль, почему не взять конкретный компонент? Если вы хотите делать электронное устройство на печатной плате – конечно же лучше собирать его из голых компонентов, а не из модулей, потому что в большинстве случаев это выйдет дешевле, а также изготовление такой платы можно заказать вместе со сборкой на производстве (например на JLCPCB). Но мы с вами собираемся сначала научиться программировать, поэтому модули имеют неоспоримые преимущества:
- Основная концепция Ардуино – электронный конструктор, быстрое и простое создание прототипов электронных устройств без помощи паяльника.
- Микросхема очень маленькая, подключать её к чему-то – не очень приятная затея. У модуля выведена рейка для подключения проводов и работы на макетной плате.
- Чтобы подключить голую микросхему – понадобится изучить документацию. У модуля все нужные для подключения пины выведены и подписаны.
- Для корректной работы большинства микросхем требуются дополнительные компоненты (драйверы, контроллеры, резисторы, стабилизаторы, конденсаторы, индуктивности, кварцевые генераторы), посчитанные, выбранные и установленные согласно документации. На плате модуля всё это уже есть.
- У некоторых модулей на плате предусмотрена настройка: крутилки, джамперы, переключатели, перемычки для спайки паяльником.
3. Загружаем библиотеку VGAx и код во второй Arduino, и соединяем его с основным
Для начала, загрузите код VGAx-PC.ino и скопируйте его к себе на компьютер в папку с таким же именем.
Потом — загрузите с GitHub бибилиотеку VGAx. Её нужно поместить в подпапку «libraries» Arduino IDE.
ВАЖНО! Эта бибилиотека работает с Arduno IDE 1.6.4 и может быть не совместима с другими, более новыми версиями.
Теперь загрузите VGAx-PC.ino во второй Arduino (я тестировал на Arduino Nano, но с Arduino Uno тоже не должно быть проблем).
Для этого нужно:
- два Arduino Uno Rev.3, или два Arduino Nano 3.x (на основе ATmega328)
- разъем DSUB15, т.е. гнездо VGA или кабель с таким гнездом, который не жалко отрезать
- резисторы: 2 на 68 Ом и 2 на 470 Ом
- разъем PS2
- провода
- необязательно: макетная плата
Микроконтроллер – очень универсальная штука, его можно научить взаимодействовать практически с любым другим электронным устройством: аналоговые датчики, цифровые датчики, всякие разные микросхемы, дисплеи, драйверы, контроллеры… Чтобы схема работала, входящие в неё компоненты нужно правильно соединить между собой. В этом уроке мы рассмотрим подключение электронных модулей.
Instruction Set Architecture
Ок, поговорили как он выглядит, что внутри, теперь углубимся в систему команд. Она строилась таким образом, чтобы пользователь не писал кучу лишнего кода, чтобы проинициализировать какое то устройство, как делал я, чтобы завести мышку в x86, используя только запись в порты.
Т.е. на манипуляции с устройствами выделены некоторые инструкции, такие как:
gkey - получает сканкод нажатой клавиши, если ее не было, то 0.
line x1, y1, x2, y2 - рисует линию по указанным координатам на экране.
play label - играет музыку по смещению label в памяти.
Как же будут устроены обработчики этих инструкций и их опкоды? Я решил этот вопрос очень просто - создал массив функций, теперь в основном цикле работы процессора байт, на который указывает указатель инструкции (IP) - будет восприниматься как номер функции в этом массиве. Если же у инструкции есть какие то аргументы, например операнды инструкции сложения add r0, r0, то соответствующая функция сама об этом знает и дочитывает их. Кстати говоря, поскольку регистров 16, то эту пару из инструкции выше можно упаковать в 1 байт, чем я и воспользовался, чтобы сократить код.
Распиновка
Как вы наверное поняли, микроконтроллер – это микросхема с кучей ножек. У каждой ножки есть своя функция, в частности у блоков GPIO и интерфейсов связи есть свои личные ноги. Для экономии размера и уменьшения количества ног микросхемы производители практически всегда объединяют несколько функций на одной ножке. Чтобы понять, куда подключать внешнюю “железку”, нужно посмотреть на распиновку (pinout) микросхемы или платы: это картинка, на которой подписаны функции всех ножек МК или пинов платы. Вот для примера упрощённые распиновки плат Arduino Nano и Wemos Mini, на них вы найдёте уже знакомые из предыдущей главы аббревиатуры:
Почти на всех Ардуино-совместимых платах есть “отладочный” светодиод, подключенный к одному из пинов. На распиновках я отметил его как LED.
“Простые” модули
Цифровые
Простые цифровые модули имеют два пина питания и пин с логическим выходом, он может быть подписан как OUT, S, D или DO. Плата таких модулей имеет синий цвет и содержит типовую схему – крутилка (синий корпус) и операционный усилитель (чёрная микросхема рядом с крутилкой). Такой модуль выдаёт только два состояния: датчик “сработал” и “не сработал”, на цифровом выходе появляется соответственно VCC (напряжение питания) или 0 Вольт, т.е. высокий и низкий цифровой сигнал. Крутилка на плате позволяет настроить порог срабатывания. Такие датчики подключаются к питанию и любому цифровому пину (GPIO). Опрашиваются стандартными средствами Arduino.
Примеры на картинке ниже: датчик звука, температуры, освещённости, приближения, магнитного поля.
Заключение
Вот, вроде бы все основное рассказал. Вообще говоря, там еще много всего можно рассказать, с чем сталкивался по пути, но это, скорее, просто занимательные мелочи, чем что то важное. Столько удачных костылей было вставлено - о некоторых рассказал, о других молчу.
Сейчас делаю сетевой интерфейс для передачи по встроенному UART - нашел способ без дополнительных проводов, только UART и диоды, собрать общую шину из таких компьютеров и передавать между ними данные. Так же думаю как сделать транслятор из, скажем, wav в описанный формат музыкальных файлов. Еще поглядываю в сторону нормального компилятора на основе LLVM, но пока только мысленно, потому что итак есть чем заняться с этим проектом.
Сама по себе, идея использовать Arduino для создания компьютера с BASIC не нова, но насколько я знаю, все они не поддерживают вывод цветного изображения. В некоторых проектах использовались LCD-мониторы, а в других — библиотека TVout, которая выводит чёрно-белое изображение. Кроме того, многие из этих проектов требуют дополнительные модули и специальные платы расширения. Тут же нужно всего-то иметь два Arduino, несколько резисторов, плюс разъёмы для PS/2 клавиатуры и VGA-монитора.
Для проекта нужно две платы Arduino: один будет основным (или «мастером»), в нём работает интерпретатор Tiny Basic Plus (это порт Tiny Basic на языке Си, адаптированный для поддержки Arduino). Также этот Arduino управляет PS/2 клавиатурой. Вывод из первого Arduino через последовательный порт отправляется на второй Arduino, который с помощью библиотеки VGAx генерирует VGA-сигнал.
Программу на языке BASIC в собранный нами компьютер можно будет ввести с помощью PS/2 клавиатуры, а результат можно будет посмотреть на VGA-мониторе: разрешение получившегося изображения 24 столбца на 10 строк, размер символов — 5х6 пикселей, доступно 4 цвета.
После введения программы, её можно будет сохранить в энергонезависимой памяти самого Arduino, а также код программы предусматривает управление I/O пинами Arduino.
Аналоговые
У аналоговых модулей помимо питания есть аналоговый выход, может быть маркирован как OUT, S, A или AO. Такие модули выдают аналоговый сигнал, пропорциональный показанию датчика. Подключаются к питанию и любому аналоговому пину (ADC) и опрашиваются стандартными средствами Arduino.
Примеры на картинке ниже: датчик звука, уровня жидкости, индуктивный датчик влажности почвы, обычный датчик влажности почвы, потенциометр (просто крутилка).
Интерфейсные модули
Некоторые модули имеют один или несколько логических выходов и передают данные по цифровому интерфейсу связи. Сигнальные пины таких датчиков могут быть подписаны как SCK, SDA, SCL, MISO, MOSI, SS и прочими аббревиатурами, отличными от OUT, как в “простых” модулях. Подключаются такие модули к пинам интерфейсов (подробнее в этом уроке) на плате Arduino и опрашиваются при помощи сторонних библиотек. Для работы с такими модулями нужно найти в интернете статью с описанием и примерами. Подробное описание к самым популярным модулям можно найти в базе примеров к набору GyverKIT, а также у меня в каталоге ссылок на Ардуино-компоненты. Примеры таких модулей:
Давайте вкратце рассмотрим самые распространённые интерфейсы и особенности подключения модулей с ними. Напомню распиновку плат Arduino Nano и Wemos Mini:
Микроконтроллер – чрезвычайно универсальное устройство, на его основе можно сделать бесконечно много разных электронных устройств, как полезных, так и бесполезных. Под управлением микроконтроллеров работает любая техника, в которой есть какие-то настройки, режимы или автоматизация (стиральная машина, микроволновка, мультиварка…), некоторые узлы автомобилей, станки с ЧПУ, простенькие гаджеты и так далее.
Что же делает МК настолько мощным и универсальным инструментом? Ведь фактически он умеет делать всего три вещи*:
- Измерять напряжение на пине
- Выдавать напряжение с пина
- Программироваться
(*) – ещё у МК может быть собственный беспроводной интерфейс, но это уже частный случай.
В том то и дело, что этого достаточно для решения всех мыслимых и немыслимых задач! Микроконтроллер может управлять любой внешней нагрузкой, опрашивать кнопки/крутилки/энкодеры/клавиатуры/джойстики, может работать практически с любыми датчиками, общаться с любыми сторонними микросхемами, выводить информацию на дисплей, в том числе сенсорный, управляться через Интернет из любой из точки планеты и многое другое. Самое важное, что всё это может работать абсолютно в любых сочетаниях и быть запрограммировано огромным количеством способов, то есть одна маленькая микросхема может стать сердцем бесконечного количества электронных устройств и проектов!
Прочий софт
Вот такие были пироги при работе с периферией нашего компьютера. Помимо его самого, для него я сделал транслятор ассемблера в машинные коды, чтобы писать начальные программы было удобнее - работает на Python. На нем начал реализовывать оболочку, которая умеет разбирать FAT32, ходить по ее директориям и исполнять файлы. Так же для удобства отладки был написан эмулятор девайса - он использует общий код с самим компом. Рассмотрим подробнее оболочку и эмулятор.
Оболочка
Компьютер начинает исполнение инструкций с 0 байта. На карте памяти было решено создать файловую систему FAT32 и использовать ее резервные секторы для нашего загрузчика. Как правило, начало самого первого сектора содержит служебную информацию, но самые первые байты не критичны к изменению, а именно нам надо изменить первые 5 байт на инструкцию перехода к первому байту загрузчика. Ок, мы попали в загрузочную секцию, разобрали все байты, рассчитали константы в удобном виде. Сохранили это все, теперь, чтобы не пересчитывать это при каждом старте и не перезаписывать подменим еще раз инструкцию перехода на блок непосредственной загрузки. Можно видеть это в этом коде:
Про FAT ничего не буду рассказывать, можно посмотреть какие там есть поля, как строятся файлы и про терминологию в этом видео:
У нас в программе есть поле "текущая директория", она обозначает номер первого кластера директории. Таким образом если мы в какой то директории начали работу при выключении компьютера, то при включении мы в ней и начнем работу. Выбирать файл в директории можно клавишами стрелками вверх-вниз, исполнять по Enter, при этом если это директория, то она становится в качестве текущей. Кстати говоря, под исполнение нужно выделить место. Поскольку доступно максимум 2 Гб, то 1 нижний гигабайт мы отдадим файловой системе, а старшую область под исполнение. Поскольку экран у нас может вмещать 6 строк текста по 14 символов, то мы можем отображать имя и тип файла (файл или директория) в каждой строке, а между строками перемещаться, используя скользящее окно.
Эмулятор
Наконец, немного про эмулятор. Чтобы все работало так же как на реальном компе, понятно, что нужно реализовать соответствующим образом библиотеки работы с устройствами. Карта памяти эмулируется, как не сложно догадаться - файлом.
Экран изначально был приложением на SFML, но это влияло на работу с клавиатурой, создавая две проблемы. Во-первых, нужно понимать, что SFML это библиотека для игр, поэтому там не получить никаких нормальных сканкодов, значит надо их получать откуда-то еще, но если фокус на окне SFML, то оно получает все события клавиатуры, поэтому приходится переключать фокус на консоль, но так чтобы и окно было видно. Во-вторых, SFML это графическая библиотека, т.е. в текстовой консоли (одной из тех, что доступны по ALT-CTRL-Fx) ее нельзя использовать для рисования чего-то на экране.
Хотя хотелось бы перейти в текстовую консоль, поскольку есть способ получения совсем хороших сканкодов - прямо таких, какие приходят от клавиатуры. Это показывает утилита showkey, но работает она только в текстовой консоли, так как графика в Linux для обработки хоткеев всегда читает /dev/console. Суть этого метода - перейти в неканонический вид консоли, где мы будем получать сырые сканкоды. Я просто нашел исходники утилиты showkey и подправил их для своих нужд, заодно научился компоновать программу на С++ с функциями на С - это нужно поскольку showkey написана на С, а весь проект мы пишем на плюсах. Ну а для отрисовки экрана мы воспользуемся популярной библиотекой для консольной графики - ncurses. Чтобы все отображалось хорошо, нужно настроить консоль так, чтобы размер шрифта вместо 8x16 был 8x8 - красиво, как пиксели на экране.
Начинаем разбор
Посмотрев на различные проекты и доступное оборудование, я решил сделать симулятор не ZX Spectrum, а полностью придуманного вычислителя - со своей системой инструкций, способом коммуникации с оборудованием, и языком ассемблера, соответственно! Приступим к описанию того, как строилась система. Но прежде рекомендую ознакомиться с инструкциями процессора - это основной репозиторий проекта, дальше информация оттуда будет упоминаться.
Одну из проблем, которые я пытался решить - это износ внутренней flash памяти, но оперативной памяти мало для хранения программ написанных для нашего компьютера - её всего 2 кб! Так что я решил использовать SD карту. Изначально я думал хранить на карточке файловую систему и в ней файлы и открывать нужную программу, выгружая её в оперативку. Однако для такой схемы нужна библиотека SD, использование которой не оставит от этой самой оперативки ничего. Я подумал - если понадобится ФС, напишем ее поддержку уже на нашем коде, а не будем занимать память Arduino для этого. Вполне хорошей альтернативой для работы с картой послужила библиотека sd_raw, которая предоставляет доступ к любому байту на карте на чтение и запись туда сырых данных, т.е. просто набора байт. В качестве основного устройства вывода текста я взял черно-белый дисплей Nokia 5110 - точное попадание в ретро стиль, как мне кажется. В качестве клавиатуры, понятно, возьмем PS/2. Остались свободные пины - заткнем пьезо-пищалкой. Мой хороший знакомый нарисовал и напечатал мне корпус, как многие заметили по картинке выше, похожий на корпус Macintosh.
Смешанные
Некоторые модули имеют цифровой и аналоговый выходы одновременно, пины у них обычно подписаны как DO – цифровой выход и AO – аналоговый. Крутилка на плате настраивает порог срабатывания у цифрового выхода, а аналоговый просто выдаёт “сырой” сигнал с датчика. Опрашиваются как цифровые и аналоговые датчики соответственно.
Общая схема для всех перечисленных выше типов модулей:
Примеры на картинке ниже: датчик вибрации, звука, магнитного поля, влажности почвы и освещённости.
Макетная плата
Макетная плата, она же макетка или брэдборд (breadboard) – самый удобный способ создания электронных макетов. Отверстия расположены со стандартным шагом 2.54мм, внутри каждого – пружинная клемма. Это позволяет вставлять в плату любые Arduino-модули, а также микросхемы в DIP корпусах.
Для соединения отверстий в пределах макетки используются провода штырёк-штырёк, рекомендую вариант с цилиндрическими штекерами (я пользуюсь одним комплектом на протяжении уже 5-ти лет). Также существуют провода с квадратными штекерами, они есть в вариантах гнездо-гнездо, гнездо-штырёк и штырёк-штырёк. Эти провода менее качественные, но вариант гнездо-штырёк позволяет подключить модуль к макетке, не втыкая модуль в макетку:
Что происходит на этом фото и как работает брэдборд? Очень просто! Контакты в нём соединены следующим образом:
Пройдемся по устройствам.
Дисплей - с ним особых проблем не возникало, открыл datasheet, написал библиотеку и все работает, возникла только одна проблема с тем, что необходимо сохранять содержимое буфера экрана (508 байт) в какой-то памяти для изменения отдельных пикселей. Хорошо бы можно было в оперативке, но места мало - примерно столько же ест массив инструкций и столько же библиотека sd_raw, в совокупности с прочими расходами на регистры и другие переменные и массивы. Это оставляет около 150-200 байт на локальные переменные, чего может оказаться мало. Но быстро стало ясно, что инструкции никто не будет менять (хотя идея интересная - налету подменять инструкции процессора), поэтому было решено перенести их во внутреннюю flash память, используя ключевое слово PROGMEM в Arduino IDE, которое позволяет сохранять константы любых типов в неизменяемую память, освобождая оперативную. Таким образом решилась проблема нехватки памяти для экрана.
Клавиатура - тут интереснее то, что происходило в программном эмуляторе нашего девайса, но об этом позже. В остальном, я пока что с реальной клавиатурой разбираюсь - там нужно выбрать хорошую (для наших целей) таблицу сканкодов (их 3 в PS/2) и понять как легко транслировать их в символы ASCII, или хотя бы как это делает DOS.
Карта памяти - в какой то момент мне пришло осознание, что даже, если у нас регистры 32-битные и карта не меньше 4 Гб, но мы все равно не можем адресовать больше 2 Гб - это связано с тем, какую плату картридера я установил в наш вычислитель - она не поддерживает карты большего объема. Это налагает ряд проблем - теперь мы можем использовать только карты на которых указан объем 2Гб, они всегда на самом деле меньше и даже так - они различаются по объему. Но так как раньше и IP и SP - специальные регистры были по умолчанию установлены в 0, то теперь для адекватного использования стека (который растет вниз) нужно знать верхнюю границу памяти. И очень кстати в библиотеке имелась функция для чтения заводской информации, а помимо производителя там было поле capacity (емкость). Вот именно в это значение мы и устанавливаем теперь SP перед началом основного цикла процессора.
Пьезо-пищалка - здесь было совершено большое открытие для меня - что delay в Arduino не такой уж блокирующий, как нам все говорят. Остановимся поподробнее.
Начну с того, какая задача стояла. У нас есть набор пар частот и задержек, мы хотим перебирать их и проигрывать на пищалке функцией tone, которая как раз принимает пин, частоту и задержку, пищит с нужными параметрами и отключается по прохождении задержки. Перебирать нужно не абы как, а когда исполнится инструкция play, которая раньше упоминалась. Она укажет, где лежат эти ноты и запустит проигрывание, но стандартная функция tone не умеет по циклу ходить и изымать частоты с задержками. Значит нужно как-то детектировать то, что текущая нота должна уже закончиться и пора бы включить следующую. Самым простым решением будет в основном цикле процессора следить за этим с помощью millis, которая возвращает время прошедшее с запуска контроллера в миллисекундах. Но самое простое - не самое эффективное, у нас инструкции не имеют фиксированного времени исполнения, та же delay может занимать достаточно длительное время, не давая переключить ноту. Дальнейшим решением для меня было - покопаться в исходниках функции tone и создать аналог, который принимает обработчик завершения тона, обычно это была функция, которая отключает таймер, но теперь мы ее подменяем и вместо отключения мы включаем новый тон. После того, как проигрывание завершается нужно позвать noTone, чтобы вызвать правильный обработчик и подменить его обратно. Вроде все хорошо, все работает! Однако не совсем. Я загрузил следующий код:
Оказалось, что проиграв одну ноту, он выключал проигрывание. Почему же. Обратите внимание на delay, если зайти в её исходники, то мы замечаем функцию, которая вызывается перед циклом задержки - yield, я мало чего про нее нашел, но, как я понял, это макрос, в который мы оборачиваем код и как-то можем параллельно его исполнять с основным кодом, если кто знает точно - поделитесь. Я сам попробовал решить эту проблему и у меня получилось! Покажу на примере. У нас есть такие строчки в функции noTone -
KY-модули
Существует также целое семейство модулей с названием KY-цифра, это самые дешёвые модули на чёрных платах. Среди них есть и цифровые, и аналоговые, и интерфейсные датчики, а также индикация (светодиоды, пищалки) и реле. Проблема в том, что пины почти у всех плат подписаны одинаково:
- – (минус) – минус, GND
- Средний пин без подписи – плюс, VCC
- S – сигнал
Сигналом здесь может быть как исходящий из датчика цифровой или аналоговый сигнал, так и цифровой сигнал управления, который нужно подать на модуль с микроконтроллера. Обязательно читайте описание к модулю такого типа, если не знаете, что делает компонент на его плате!
Что такое МК?
Микроконтроллер – это сильно навороченная программируемая микросхема, самый простой аналог – компьютер, точнее системный блок (без блока питания). Да, вы не ослышались! Микроконтроллер работает сам по себе, на нём может быть запущена простенькая операционная система, может даже быть выход в Интернет, а мы можем подключать к нему устройства ввода, датчики, дисплеи и прочие железки. Чем не компьютер?
Внутри любого микроконтроллера находится несколько аппаратных блоков, все они соединены между собой:
- Ядро (процессор) – отвечает за работу всех остальных блоков, связывает их между собой. Сам состоит из десятка элементов (кэш, набор вычислительных блоков, и т.д.). Аналог – процессор компьютера.
- Flash память – постоянное запоминающее устройство (ПЗУ). Хранит исполняемый код программы, также может хранить статические данные (изображения, веб-страницы, текст, таблицы с числами, и т.д.). Не очищается после сброса питания. Аналог – жёсткий диск компьютера.
- SRAM память – оперативное запоминающее устройство (ОЗУ). Хранит данные, изменяющиеся в процессе работы программы (промежуточные результаты вычислений, значения переменных, принятые от внешних устройств данные и т.д.). Очищается после сброса питания. Аналог – оперативная память компьютера.
Также в МК могут быть и другие блоки:
- GPIO (General Purpose Input-Output) – вход-выход общего назначения. Измеряет поданный на пин цифровой сигнал, либо выдаёт его с пина. Подробнее в этом уроке. Пины GPIO мы будем называть цифровые пины.
- АЦП (ADC, аналогово-цифровой преобразователь) – измеряет поданное на пин напряжение, аналоговый сигнал, и передаёт в программу. Подробнее в этом уроке. Такие пины мы будем называть аналоговые пины.
- ЦАП (DAC, цифро-аналоговый преобразователь) – выдаёт указанное напряжение с пина (аналоговый сигнал).
- Таймер (счётчик) – считает такты работы процессора
- Позволяет с очень высокой точностью (до наносекунд) измерять время.
- Часто таймер используется для генерирования ШИМ сигнала на своих выводах, они помечаются как PWM. Подробнее в этом уроке. Такие пины мы будем называть ШИМ-пины.
- UART – связь с одним внешним устройством. По одному проводу передаёт, по второму – принимает. Может работать только на передачу или только на приём, используя один провод. Названия пинов:
- RX (Receive) – приём.
- TX (Transmit) – передача.
- SDA (Serial DAta) – линия данных.
- SCL (Serial CLock) – линия синхронизации.
- MOSI (Master Output Slave Input) – линия данных от МК к внешней микросхеме.
- MISO (Master Input Slave Output) – линия данных от внешней микросхемы к МК.
- SCLK (Serial CLocK) – линия синхронизации.
Начинаем разбор
Посмотрев на различные проекты и доступное оборудование, я решил сделать симулятор не ZX Spectrum, а полностью придуманного вычислителя - со своей системой инструкций, способом коммуникации с оборудованием, и языком ассемблера, соответственно! Приступим к описанию того, как строилась система. Но прежде рекомендую ознакомиться с инструкциями процессора - это основной репозиторий проекта, дальше информация оттуда будет упоминаться.
Одну из проблем, которые я пытался решить - это износ внутренней flash памяти, но оперативной памяти мало для хранения программ написанных для нашего компьютера - её всего 2 кб! Так что я решил использовать SD карту. Изначально я думал хранить на карточке файловую систему и в ней файлы и открывать нужную программу, выгружая её в оперативку. Однако для такой схемы нужна библиотека SD, использование которой не оставит от этой самой оперативки ничего. Я подумал - если понадобится ФС, напишем ее поддержку уже на нашем коде, а не будем занимать память Arduino для этого. Вполне хорошей альтернативой для работы с картой послужила библиотека sd_raw, которая предоставляет доступ к любому байту на карте на чтение и запись туда сырых данных, т.е. просто набора байт. В качестве основного устройства вывода текста я взял черно-белый дисплей Nokia 5110 - точное попадание в ретро стиль, как мне кажется. В качестве клавиатуры, понятно, возьмем PS/2. Остались свободные пины - заткнем пьезо-пищалкой. Мой хороший знакомый нарисовал и напечатал мне корпус, как многие заметили по картинке выше, похожий на корпус Macintosh.
2. Подключаем PS/2 клавиатуру к основному Arduino
Информация и библиотека были взяты из этого материала.
По существу, вам нужно соединить следующие четыре контакта:
Data клавиатуры к Arduino pin 8,
IRQ (clock) клавиатуры к Arduino pin 3;
а также подключить землю (GND) и питание +5V, соответственно.Я использовал PS/2 разъём cо старой нерабочей материнской платы, его можно легко сдуть с помощью фена.
Распиновка PS/2 разъема есть на рисунке:
1. Подключаем основной Arduino с интерпритатором TinyBasic и с PS/2 клавиатурой
TinyBasic Plus и библиотека VGAx работают с Arduino IDE 1.6.4.
Если у вас уже установлена более новая версия, то лучшее решение — загрузить эту версию с официального сайта в формате .zip, и распаковать в отдельную папку. Здесь можно загрузить эту версию для Windows.Потом нам понадобится библиотека PS2keyboard, её можно загрузить отсюда. После скачивания просто распакуйте архив, и скопируйте его содержимое в «arduino-1.6.4\libraries».
Дальше, загрузите файл TinyBasicPlus_PS2_VGAx.ino, и прошейте его в основной Arduino.
Это версия обычного TinyBasic Plus, где была добавлена поддержка библиотеки PS/2.Больше информации о TiniBasic Plus и руководства (на английском языке) можно найти тут.
Если на этом этапе нет проблем (в том числе, проблем с совместимостью), то Tiny Basic будет автоматически запущен при включении Arduino. И его можно будет протестировать с помощью последовательного порта на вашем ПК. Для этих целей я использую PuTTY, но вы можете использовать и другую подобную программу, по вашему вкусу.
В настройках нужно правильно установить COM-порт (тот же, что используется в Arduino), и скорость передачи = 4800.
И тут уже можно протестировать нашу систему, набрав какую-нибудь программу на Basic с клавиатуры вашего ПК (позже мы подключим PS/2 клавиатуру напрямую к Arduino).
Для примера, можно набрать:
Прервать бесконечный цикл этой программы можно с помощью комбинации клавиш Ctrl+C (это не сработает, если мы будем делать это с клавиатуры PS/2).Теперь подключим PS/2 клавиатуру.
Регистры!
С ISA разобрались, немного о регистрах, их 2 набора по 16 штук - для целых чисел и чисел с плавающей запятой, все 32-битные. Есть инструкции кастинга из одного типа в другой, при этом сами регистры при программировании ничем не отличаются, инструкция сама понимает из какого именно регистра читать/писать (хотя можно сделать псевдонимы, чтобы не путаться). Кстати о плавающих числах, тут нам можно работать с плавающими числами как с целыми в плане использования их в инструкциях, не нужно использовать регистровый стек и обращаться по ссылке в память для получения константы, как это есть в x87. Однако вводя эти числа в свою систему, я не знал как безболезненно преобразовывать их, т.е. без битового разбора числа в формате IEEE754. Оказывается такой способ есть и он очень прост, нам не потребуются никакие логические операции в огромном количестве, а только 2 строчки:
Тут мы просто записали в некоторую область памяти целое число (4 байта), изначально смотрим на них именно так, потом после взятия указателя на эту область (целое число), производим каст его к указателю на вещественное число и разыменовываем. Вся основная работа происходит во второй строчке и читать ее нужно справа налево. В итоге в переменной y окажется число, байтовое представление которого в формате IEEE754 равно числу в переменной x.
Читайте также: