Взаимодействие с контроллером NES на Arduino Uno

Добавлено 29 сентября 2018 в 22:07

Nintendo Entertainment System, также известная как консоль NES (а в России известен ее клон, Dendy), была когда-то королем всех видеоигровых систем.

Пришло время захватить мир
Пришло время захватить мир

В настоящее время они довольно устарели, и только самые ностальгирующие геймеры будут играть на ней. Для тех из нас, кто перешел на современные системы или просто пытается идти в ногу с повседневной работой, NES стала пылесборником. Но, если вы возитесь с электроникой, NES может стать полезной в DIY мире с помощью своего простого в управлении контроллера. Контроллер NES может управлять множеством предметов от освещения до робота и даже до вашего устройства судного дня – его возможности зависят только от вашего воображения! Для этого проекта мы будем использовать не более чем Arduino Uno и контроллер NES, чтобы продемонстрировать, насколько просто они могут взаимодействовать.

Перечень компонентов

Аппаратное обеспечение:

  • Arduino Uno;
  • перемычки для макетной платы;
  • NES контроллер (оригинальный или аналог);
  • макетная плата (необязательно);
  • удлинитель NES (необязательно).

Программное обеспечение:

  • Arduino IDE.

Теория

Если вы откроете контроллер NES, то обнаружите, насколько он прост и элегантен. Он состоит не более чем из одного сдвигового регистра и нескольких подтягивающих резисторов. В частности, это называется «параллельный-ввод-последовательный-вывод» (Parallel-In-Serial-Out, сокращенно PISO). Сдвиговый регистр, который используется внутри этих контроллеров, это 4021. Хотя для понимания кода Arduino в этом проекте нет необходимости, но любознательный читатель может найти здесь техническое описание типовой версии.

Способ использования 4021 можно увидеть ниже. Это схема того, как контроллер NES выглядит изнутри.

Схема контроллера NES
Схема контроллера NES

Как вы можете видеть, 4021 – это 8-разрядный регистр, которого достаточно, чтобы все восемь кнопок контроллера были подключены к нему. Одна сторона каждой кнопки подключена к земле, а другой конец каждой кнопки идет к отдельному входу данных сдвигового регистра. Чтобы сохранить определенное состояние входов регистра, используются подтягивающие резисторы. Это означает, что когда кнопка не нажата, сдвиговый регистр будет интерпретировать состояние на входе как логическую «1». Если кнопка нажата, регистр сдвига будет интерпретировать состояние на входе как логический «0». Это важно, так как здесь впервые появляется точка пользовательского ввода.

Чтобы сдвиговый регистр «захватил» состояние входов линий данных, требуется сигнал, указывающий о необходимости сделать это. Для этого используется линия LATCH. При переходе линии LATCH низкий-высокий-низкий уровень состояние кнопок, подключенных к выводам регистра, будут зафиксированы в регистре. Продолжительность высокого уровня может быть довольно короткой, но для справки протокол NES будет делать это примерно 12 микросекунд. После того, как данные зафиксированы, состояние первой кнопки становится доступным на линии DATA. Линия DATA будет прокручивать состояния кнопок каждый раз, когда на выводе CLOCK будет проходить импульс так же, как было на линии LATCH. Это означает, что после фиксирования данных семь тактовых импульсов вытянут все данные о состоянии кнопок. Точный порядок кнопок, которые будут отправляться последовательно, четко описывается в коде для Arduino.

Чтобы подвести итог, код Arduino должен выполнять следующее:

  1. Выдать импульс на линию LATCH, чтобы сдвиговый регистр мог «собрать» данные.
  2. Прочитать состояние на линии DATA и сохранить состояние кнопки.
  3. Выдать импульс на линию CLOCK и сохранить состояние кнопки.
  4. Повторить шаг 3 еще шесть раз.
  5. Сделать что-нибудь крутое со всеми этими данными!

Чтобы лучше понять конструкцию контроллера NES, вы всегда можете разобрать его. Хотя это совершенно необязательно, это даст вам более полное представление о том, как вы будете создавать свой собственный проект, и дополнительный опыт с точки зрения проектирования и сборки электроники. Чтобы начать разборку, вам понадобится небольшая отвертка с крестообразным шлицем. На задней панели контроллера находится шесть винтов.

Задняя панель контроллера NES

Будьте осторожны, отвинчивая их. Эти винты находятся на немного более дешевой стороне, поэтому у них легко срывается шлиц. Обязательно найдите отвертку, которая позволяет прикладывать достаточное давление при повороте винта. Как только винты будут удалены, положите контроллер кнопками вниз. Затем осторожно снимите заднюю крышку.

Печатная плата контроллера NES
Печатная плата контроллера NES

Теперь вы можете увидеть обратную сторону печатной платы. Конкретно эта печатная плата изготовлена из гетинакса, что является обычным явлением в бытовой электронике, поскольку он намного дешевле в производстве. Однако, в качестве примечания, большинство качественных печатных плат изготавливаются из стеклотекстолита. Помимо печатной платы, вы можете увидеть микросхему 4021 вместе с проводами кабеля. Можно поразиться, насколько всё просто. Инженеры Nintendo сделали это, сохранив высокую функциональность, всего на одной микросхеме. Чтобы увидеть другую сторону печатной платы, осторожно снимите ее. Если вы внимательно посмотрите на дорожки, то сможете заметить, что их рисовали вручную. Должно быть, это был довольно сложный процесс. Вы можете заметить, что на другой стороне нет резисторов. Но для опытного взгляда они находятся на виду. На самом деле это черные прямоугольники с блестящей зеленой поверхностью. Это углеродные резисторы, которые были напечатаны на печатной плате, чтобы сэкономить время и деньги. И так как они имеют одинаковый номинал, это, вероятно, было идеальным решением в то время. Последнее, что нужно убрать с контроллера, – это контакты кнопок и резиновые кнопки. Они работают за счет того, что черная круглая часть резиновой прокладки устанавливает соединение между контактами кнопки с помощью углеродного слоя. Хотя он не кажется проводящим, его достаточно, чтобы установить логический высокий или низкий уровень состояния кнопки.

Передняя часть платы контроллера NES
Передняя часть платы контроллера NES

Соединим всё вместе

Схема соединений в этом проекте довольно проста, так как разъем контроллера NES легко позволяет использовать перемычки для макетных плат. Конечно, если вы хотите что-то более надежное, то можно купить удлинитель кабеля контроллера NES, разрезать его и воспользоваться проводами из него. Таким образом, вы можете вставить их напрямую в Arduino, получив надежное соединение и ответный разъем для контроллера.

Схема соединений показана ниже.

Схема подключения контроллера NES (Dendy) к Arduino Uno
Схема подключения контроллера NES (Dendy) к Arduino Uno

Как вы можете видеть, контроллеру для взаимодействия требуются только 5 из 7 контактов. Два для питания (+5V и GND), один для фиксирования состояния кнопок (D2), один для тактового сигнала состояний кнопок (D3) и один для считывания последовательных данных о состояниях кнопок (D4)

Код Arduino

Этот код хорошо прокомментирован для описания состояний и порядка действий. В конце всего этого вы увидите, что состояния кнопок появляются на мониторе последовательного порта. Код в мониторе последовательного порта даст вам, четкое представление о том, как вы можете связать это с вашим будущим безвредным проектом устройства судного дня. Код Arduino здесь должен хорошо совпадать с разделом «Теория», и он должен тщательно повторять процесс шаг за шагом.

/*
================================================================================
   Примечания
================================================================================
- Контроллер NES содержит один 8-разрядный сдвиговый регистр 4021. 

- Этот регистр берет параллельные вводы и преобразует их в последовательный вывод.

- Данный код сначала фиксирует данные, а затем сдвигает первый бит на линию данных.
  Затем он выдает тактовый сигнал и сдвигает на линию данных, пока все биты не будут 
  приняты.
  
- Состояния кнопок контроллера NES отлажено.

- Логическая "1" означает, что кнопка не нажата. Логический "0" означает, что кнопка
  нажата.
  
- Данный код сдвигает первый бит данных в младший значащий бит.

- Порядок сдвига для кнопок показан в таблице ниже:

    Бит# | Кнопка   
    --------------
      0  |   A  
    --------------
      1  |   B  
    --------------
      2  | Select   
    --------------
      3  | Start  
    --------------
      4  | Вверх  
    --------------
      5  | Вниз  
    --------------
      6  | Влево   
    --------------
      7  | Вправо   
    --------------
    
- Распиновка разъема контроллера NES показана ниже (взгляд на конец разъема):
      __________
     /          |
    /       O 1 |	1 - Земля
    |           |	2 - Тактовый сигнал Clock
    | 7 O   O 2 |   3 - Сигнал фиксации Latch
    |           |	4 - Выход данных
    | 6 O   O 3 |	5 - Не подключен
    |           |   6 - Не подключен
    | 5 O   O 4 |   7 - +5В
    |___________|

================================================================================

*/

//===============================================================================
//  Заголовочные файлы
//===============================================================================

//===============================================================================
//  Константы
//===============================================================================
// Здесь у нас пачка констант, которые становятся понятнее, когда мы взглянем на
// функцию readNesController(). В основном мы будем использовать это, чтобы понимать
// биты. Они выбраны согласно таблице, приведенной выше.
const int A_BUTTON         = 0;
const int B_BUTTON         = 1;
const int SELECT_BUTTON    = 2;
const int START_BUTTON     = 3;
const int UP_BUTTON        = 4;
const int DOWN_BUTTON      = 5;
const int LEFT_BUTTON      = 6;
const int RIGHT_BUTTON     = 7;

//===============================================================================
//  Переменные
//===============================================================================
byte nesRegister  = 0;    // Мы будем использовать это, чтобы хранить текущее
                          // состояние кнопок.

//===============================================================================
//  Объявление выводов
//===============================================================================
//Входы:
int nesData       = 4;    // Вывод данных для контроллера NES

//Выходы:
int nesClock      = 2;    // Вывод тактового сигнала для контроллера NES
int nesLatch      = 3;    // Вывод сигнала фиксации для контроллера NES

//===============================================================================
//  Инициализация
//===============================================================================
void setup() 
{
  // Инициализация скорости последовательного порта для терминала
  Serial.begin(9600);
  
  // Настройка необходимых выводов на вход
  pinMode(nesData, INPUT);
  
  // Настройка необходимых выводов на выход
  pinMode(nesClock, OUTPUT);
  pinMode(nesLatch, OUTPUT);
  
  // Установка начальных состояний
  digitalWrite(nesClock, LOW);
  digitalWrite(nesLatch, LOW);
}

//===============================================================================
//  Основной цикл
//===============================================================================
void loop() 
{
  // Вызов данной функции возвратит состояние всего регистра контроллера NES
  // в нормальном формате 8-битной переменной. Для определения, где в ней находится
  // какая-то кнопка, смотрите таблицу и константы выше!
  nesRegister = readNesController();
  
  // Небольшая задержка перед отладкой нажатия кнопок, так как нам не нужен мусор
  // в мониторе последовательного порта.
  delay(180);
  
  // Чтобы подкинуть вам идею, как использовать эти данные для управления чем-то
  // в следующем вашем проекте, посмотрите на вывод в терминале последовательного
  // порта ниже. В основном здесь выбор бита и проверка его состояния, HIGH (не нажата)
  // или LOW (нажата). Что хорошо в этом тесте, так это то, что мы назначили все
  // биты именам реальных кнопок, поэтому нет необходимости в бесполезном запоминании.
  if (bitRead(nesRegister, A_BUTTON) == 0)
    Serial.println("JUMP!");
    
  if (bitRead(nesRegister, B_BUTTON) == 0)
    Serial.println("PUNCH!");
    
  if (bitRead(nesRegister, START_BUTTON) == 0)
    Serial.println("DOOMSDAY ACTIVATED");
  
  if (bitRead(nesRegister, SELECT_BUTTON) == 0)
    Serial.println("WHY DON'T YOU MAP SOMETHING HERE?");
    
  if (bitRead(nesRegister, UP_BUTTON) == 0)
    Serial.println("...OR HERE?");
    
  if (bitRead(nesRegister, DOWN_BUTTON) == 0)
    Serial.println("PLAY WITH THE CODE ALREADY!");
    
  if (bitRead(nesRegister, LEFT_BUTTON) == 0)
    Serial.println("MAKE SOMETHING WITH THIS!");  
  
  if (bitRead(nesRegister, RIGHT_BUTTON) == 0)
    Serial.println("GOOD LUCK WITH YOUR PROJECT ");
}

//===============================================================================
//  Функции
//===============================================================================
///////////////////////
// readNesController //
///////////////////////
byte readNesController() 
{  
  // Предустановить переменную с всеми единицами, которые предполагают, что все 
  // кнопки не нажаты. Но, когда мы будем прокручивать биты, если мы обнаружим LOW,
  // т.е. 0, мы очистим этот бит. В конце мы найдем состояния всех кнопок за раз.
  int tempData = 255;
    
  // Выдать короткий импульс на выводе nesLatch, чтобы регистр захватил то,
  // что он видит на своих параллельных выводах данных.
  digitalWrite(nesLatch, HIGH);
  digitalWrite(nesLatch, LOW);
 
  // После фиксирования доступен для просмотра первый бит, который соответствует 
  // состоянию кнопки A. Смотрим, равен ли он 0, и если да, то очищаем первый бит
  // переменной, чтобы указать на это. 
  if (digitalRead(nesData) == LOW)
    bitClear(tempData, A_BUTTON);
    
  // Тактовый сигнал выдает следующий бит, соответствующий кнопке B, и определяем
  // состояние, как мы делали выше.
  digitalWrite(nesClock, HIGH);
  digitalWrite(nesClock, LOW);
  if (digitalRead(nesData) == LOW)
    bitClear(tempData, B_BUTTON);
  
  // Теперь делаем то же самое для оставшихся битов!
  
  // Кнопка Select 
  digitalWrite(nesClock, HIGH);
  digitalWrite(nesClock, LOW);
  if (digitalRead(nesData) == LOW)
    bitClear(tempData, SELECT_BUTTON);

  // Кнопка Start 
  digitalWrite(nesClock, HIGH);
  digitalWrite(nesClock, LOW);
  if (digitalRead(nesData) == LOW)
    bitClear(tempData, START_BUTTON);

  // Кнопка Вверх
  digitalWrite(nesClock, HIGH);
  digitalWrite(nesClock, LOW);
  if (digitalRead(nesData) == LOW)
    bitClear(tempData, UP_BUTTON);
    
  // Кнопка Вниз
  digitalWrite(nesClock, HIGH);
  digitalWrite(nesClock, LOW);
  if (digitalRead(nesData) == LOW)
    bitClear(tempData, DOWN_BUTTON);

  // Кнопка Влево
  digitalWrite(nesClock, HIGH);
  digitalWrite(nesClock, LOW);
  if (digitalRead(nesData) == LOW)
    bitClear(tempData, LEFT_BUTTON);  
    
  // Кнопка Вправо
  digitalWrite(nesClock, HIGH);
  digitalWrite(nesClock, LOW);
  if (digitalRead(nesData) == LOW)
    bitClear(tempData, RIGHT_BUTTON);
    
  // После всего этого у нас есть переменная в комплекте со всеми состояниями кнопок.
  return tempData;
}

Мысли на будущее

Если вам понравился данный проект, и вы хорошо научились взаимодействовать с контроллером NES, то контроллер SNES – это почти то же самое, но с большим регистром сдвига (16 бит вместо 8 бит).

Удачи в ваших проектах!

Теги

ArduinoArduino UnoDendyNES (Nintendo Entertainment System)

На сайте работает сервис комментирования DISQUS, который позволяет вам оставлять комментарии на множестве сайтов, имея лишь один аккаунт на Disqus.com.

В случае комментирования в качестве гостя (без регистрации на disqus.com) для публикации комментария требуется время на премодерацию.