← На главную

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

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

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

Сам джойстик, если у вас его нет, можно найти на Avito. Вот как он выглядит:

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

На фотографии показан оригинальный джойстик Sega SJ-6000, произведенный в Японии. Учтите, что бывают и клоны. Они тоже работают, но выполненны менее качественно.

Следующая иллюстрация объясняет, какой пин 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-модуляцией и паяем кликер.

Дополнение: См также заметку Микроконтроллеры STM32: работа с OLED-экранчиками на базе SSD1306 по I2C и SPI.