Используем джойстик от Sega Genesis в проектах на Arduino

11 января 2018

В детстве я много времени проводил за игровой приставкой Sega Mega Drive, также известной, как Sega Genesis. Одна из интересных особенностей этой приставки заключается в том, что джойстики подключаются к ней через обычный DE9-порт. Теоретически, сигнал от джойстика должно быть достаточно просто декодировать. Джойстик от ретро-приставки видится мне интересным примитивом для использования в будущих DIY проектах, в связи с чем я решил попробовать сынтегрировать его с Arduino.

Достаточно толковая информация о декодировании сигнала от джойстика была найдена по следующим ссылкам:

Сам же джойстик, если у вас его нет, достаточно легко найти на eBay по запросу вроде «Sega Controller». Цена джойстика составляет в районе 2-3$. Вот как он выглядит:

Джойстик от Sega Genesis (Mega Drive)

Следующая иллюстрация объясняет, какой пин DE9-порта каким образом используется джойстиком:

Распиновка джойстика от Sega Genesis (Mega Drive)

Пины 5 и 8 используются для питания. Пин 7 называется SEL и используется приставкой для выбора режима считывания. Например, если на SEL подано высокое напряжение, пин 6 отражает состояние кнопки B джойстика. Если же на SEL подано низкое напряжение, тот же пин отражает состояние кнопки A. Таким образом, переключая напряжение на SEL, можно считать состояние кнопок вверх, вниз, влево и вправо, кнопок A, B, С, а также кнопки Start.

Старые джойстики имели только эти кнопки. Однако в новых джойстиках, вроде того, что изображен выше, были добавлены еще три кнопки X, Y, Z, а также кнопка Mode, располагаемая под указательным пальцем правой руки. Считать эти кнопки можно через пины 1-4, если послать на SEL серию из трех низких и высоких сигналов, каждый из которых имеет продолжительность около 20 микросекунд.

Вооружившись этими знаниями, несложно написать прошивку для Arduino:

#include <OLED_I2C.h>

OLED oled(SDA, SCL);
extern uint8_t SmallFont[];

#define UP_OR_Z 1
#define DOWN_OR_Y 2
#define LEFT_OR_X 3
#define RIGHT_OR_MODE 4
#define B_OR_A 6
#define SEL 7
#define C_OR_START 9

bool sega_up = false;
bool sega_down = false;
bool sega_left = false;
bool sega_right = false;

bool sega_start = false;
bool sega_mode = false;

bool sega_a = false;
bool sega_b = false;
bool sega_c = false;

bool sega_x = false;
bool sega_y = false;
bool sega_z = false;

void segaRead() {
    digitalWrite(SEL, HIGH);
    delayMicroseconds(20);

    sega_up = (digitalRead(UP_OR_Z) == LOW);
    sega_left = (digitalRead(LEFT_OR_X) == LOW);
    sega_right = (digitalRead(RIGHT_OR_MODE) == LOW);
    sega_down = (digitalRead(DOWN_OR_Y) == LOW);

    sega_c = (digitalRead(C_OR_START) == LOW);
    sega_b = (digitalRead(B_OR_A) == LOW);

    digitalWrite(SEL, LOW);
    delayMicroseconds(20);

    sega_a = (digitalRead(B_OR_A) == LOW);
    sega_start = (digitalRead(C_OR_START) == LOW);

    digitalWrite(SEL, HIGH);
    delayMicroseconds(20);
    digitalWrite(SEL, LOW);
    delayMicroseconds(20);
    digitalWrite(SEL, HIGH);
    delayMicroseconds(20);
    digitalWrite(SEL, LOW);
    delayMicroseconds(20);
    digitalWrite(SEL, HIGH);
    delayMicroseconds(20);

    sega_x = (digitalRead(LEFT_OR_X) == LOW);
    sega_y = (digitalRead(DOWN_OR_Y) == LOW);
    sega_z = (digitalRead(UP_OR_Z) == LOW);
    sega_mode = (digitalRead(RIGHT_OR_MODE) == LOW);

    digitalWrite(SEL, LOW);
    delayMicroseconds(20);

    digitalWrite(SEL, HIGH);
    delayMicroseconds(20);
}

void setup() {
    oled.begin();
    oled.setFont(SmallFont);

    pinMode(SEL, OUTPUT);
    digitalWrite(SEL, HIGH);

    pinMode(UP_OR_Z, INPUT);
    pinMode(DOWN_OR_Y, INPUT);
    pinMode(LEFT_OR_X, INPUT);
    pinMode(RIGHT_OR_MODE, INPUT);
    pinMode(B_OR_A, INPUT);
    pinMode(C_OR_START, INPUT);
}

void loop() {
    char temp[16];

    segaRead();
    oled.clrScr();

    sprintf(temp, " %s   %s  %s%s%s",
        sega_up ? "U" : "u",
        sega_mode ? "M" : "m",
        sega_x ? "X" : "x",
        sega_y ? "Y" : "y",
        sega_z ? "Z" : "z");
    oled.print(temp, CENTER, 8*4);

    sprintf(temp, "%s%s%s  %s  %s%s%s",
        sega_left ? "L": "l",
        sega_down ? "D": "d",
        sega_right ? "R" : "r",
        sega_start ? "S" : "s",
        sega_a ? "A" : "a",
        sega_b ? "B" : "b",
        sega_c ? "C" : "c");
    oled.print(temp, CENTER, 8*5);

    oled.update();
    delay(1);
}

Внешний вид получившегося прототипа:

Использование джойстика от Sega в Arduino

Плату с DE9-портом вы могли узнать по заметке Травим плату перекисью водорода с лимонной кислотой. Состояние кнопок джойстика отображается на OLED-экранчике на базе чипа SSD1306 с диагональю 0.96 дюйма (24.5 мм), имеющем I2C-интерфейс. На eBay такой экранчик можно приобрести менее, чем за 3$.

Экранчик питается от 5 В. К Arduino он подключается так: пин SDA подключаем к A4, а пин SCL — к A5. Это если мы говорим про Arduino Uno, у прочих же Ардуин I2C пины могут быть другими. Работа с экранчиком осуществляется через библиотеку OLED_I2C. Написал ее тот же человек, что является автором библиотеки для экранчиков от Nokia 5110, поэтому эти библиотеки имеют похожий интерфейс. При небольших размерах и используя всего лишь два пина микроконтроллера, экранчик позволяет отобразить шрифтом SmallFont 7 строк, каждая из которых содержит до 21 символа. Также в библиотеке есть шрифт TinyFont, который позволяет вывести еще больше информации, хотя ее будет и труднее прочитать.

Полную версию исходников к этой статье вы найдете на GitHub. Как видите, работать с джойстиком от приставки 1988 года выпуска оказалось не так уж и сложно. Если вам понравился этот проект, могу предложить вам улучшить его, взяв микроконтроллер ATmega32U4 и сделав с его помощью адаптер джойстика к современным компьютерам. Кое-какие подробности на эту тему можно найти в заметке Декодируем сигнал с OOK-модуляцией и паяем кликер.

А доводилось ли вам делать что-то прикольное с ретро-консолями, и если да, то что именно?

Метки: , .

Подпишись через RSS, Google+, Facebook, ВКонтакте или Twitter!

Понравился пост? Поделись с другими: