Arduino Leonardo как адаптер SegaMegaDrive Gamepad->USB

в 9:18, , рубрики: arduino, arduino leonardo, sega megadrive, USB-HID

Предисловие

Недавно обнаружил у себя два давно забытых старых геймпада от SMD, сама приставка была давно утеряна и геймпады все это время пылились без дела, но они рабочие и выкинуть «живые» устройства как-то рука не поднимается. Решил подключить их к компьютеру, хоть какое-то им применение, а в качестве переходника используем Arduino Leonardo.

Протокол геймпада SMD

image
В самом простом случае получение данных выглядит так:

PIN Направление Select=LOW Select=HIGH
1 IN UP UP
2 IN DOWN DOWN
3 IN HIGH LEFT
4 IN HIGH RIGHT
5 OUT 5v 5v
6 IN A B
7 OUT Select Select
8 OUT GND GND
9 IN Start C

Показатели меняются в зависимости от изменения Select пина.
Как видите здесь не хватает 4 клавиш.
Их получение немного усложнено: необходимо сделать 2 изменения Select LOW->HIGH с интервалом 1.1-1.2 миллисекунд, после чего пины 1,2,3,4 становятся LOW, а при еще одном изменении Select LOW->HIGH на них появляются значения Z,Y,X,Mode соответственно.

Select PIN 1 PIN 2 PIN 3 PIN 4
LOW UP DOWN LOW LOW
HIGH UP DOWN LEFT RIGHT
LOW UP DOWN LOW LOW
HIGH UP DOWN LEFT RIGHT
LOW LOW LOW LOW LOW
HIGH Z Y X MODE
LOW HIGH HIGH HIGH HIGH
HIGH UP DOWN LEFT RIGHT
LOW UP DOWN LOW LOW

Библиотека

Для удобства я написал простенькую библиотеку для работы с геймпадом.

SMDjoystick.h

#ifndef _SMDJOYSTICK_H_
#define _SMDJOYSTICK_H_
#include <Arduino.h>
/**
SegaMegaDrive gamepad arduino library by AsGreyWolf
**/
enum{
	SMD_A=0,
	SMD_B,
	SMD_C,
	SMD_EMPTY_1,
	SMD_X,
	SMD_Y,
	SMD_Z,
	SMD_MODE,
	SMD_EMPTY_2,
	SMD_START,
	SMD_EMPTY_3,
	SMD_EMPTY_4,
	SMD_UP,
	SMD_DOWN,
	SMD_LEFT,
	SMD_RIGHT,
	SMD_MAX_KEYS
};
class SMDjoystick{
	public:
		SMDjoystick(int upPin,int downPin,int leftPin,int rightPin,int aPin,int selectPin,int startPin);
		bool read(int key);
		uint16_t read();
	private:
		int up;
		int down;
		int left;
		int right;
		int a;
		int select;
		int start;
};
#endif

SMDjoystick.cpp

#include "SMDjoystick.h"
SMDjoystick::SMDjoystick(int upPin,int downPin,int leftPin,int rightPin,int aPin,int selectPin,int startPin):up(upPin),down(downPin),left(leftPin),right(rightPin),a(aPin),select(selectPin),start(startPin){
	pinMode(up,INPUT);
	pinMode(down,INPUT);
	pinMode(left,INPUT);
	pinMode(right,INPUT);
	pinMode(a,INPUT);
	pinMode(start,INPUT);
	pinMode(select,OUTPUT);
}
uint16_t SMDjoystick::read(){
	uint16_t data=0;
	digitalWrite(select,LOW);
	data+=!digitalRead(a)<<SMD_A;
	data+=!digitalRead(start)<<SMD_START;
	data+=!digitalRead(up)<<SMD_UP;
	data+=!digitalRead(down)<<SMD_DOWN;
	delayMicroseconds(1200);
	digitalWrite(select,HIGH);
	data+=!digitalRead(left)<<SMD_LEFT;
	data+=!digitalRead(right)<<SMD_RIGHT;
	data+=!digitalRead(a)<<SMD_B;
	data+=!digitalRead(start)<<SMD_C;
	digitalWrite(select,LOW);
	delayMicroseconds(1200);
	digitalWrite(select,HIGH);
	digitalWrite(select,LOW);
	digitalWrite(select,HIGH);
	data+=!digitalRead(up)<<SMD_Z;
	data+=!digitalRead(down)<<SMD_Y;
	data+=!digitalRead(left)<<SMD_X;
	data+=!digitalRead(right)<<SMD_MODE;
	digitalWrite(select,LOW);
	digitalWrite(select,HIGH);
	return data;
}
bool SMDjoystick::read(int key){
	bool val=false;
	switch(key){
	case SMD_UP:
		val=!digitalRead(up);
		break;
	case SMD_DOWN:
		val=!digitalRead(down);
		break;
	case SMD_LEFT:
		digitalWrite(select,HIGH);
		val=!digitalRead(left);
		break;
	case SMD_RIGHT:
		digitalWrite(select,HIGH);
		val=!digitalRead(right);
		break;
	case SMD_START:
		digitalWrite(select,LOW);
		val=!digitalRead(start);
		break;
	case SMD_A:
		digitalWrite(select,LOW);
		val=!digitalRead(a);
		break;
	case SMD_B:
		digitalWrite(select,HIGH);
		val=!digitalRead(a);
		break;
	case SMD_C:
		digitalWrite(select,HIGH);
		val=!digitalRead(start);
		break;
	case SMD_X:
		digitalWrite(select,LOW);
		delayMicroseconds(1200);
		digitalWrite(select,HIGH);
		digitalWrite(select,LOW);
		delayMicroseconds(1200);
		digitalWrite(select,HIGH);
		digitalWrite(select,LOW);
		digitalWrite(select,HIGH);
		val=!digitalRead(left);
		digitalWrite(select,LOW);
		digitalWrite(select,HIGH);
		break;
	case SMD_Y:
		digitalWrite(select,LOW);
		delayMicroseconds(1200);
		digitalWrite(select,HIGH);
		digitalWrite(select,LOW);
		delayMicroseconds(1200);
		digitalWrite(select,HIGH);
		digitalWrite(select,LOW);
		digitalWrite(select,HIGH);
		val=!digitalRead(down);
		digitalWrite(select,LOW);
		digitalWrite(select,HIGH);
		break;
	case SMD_Z:
		digitalWrite(select,LOW);
		delayMicroseconds(1200);
		digitalWrite(select,HIGH);
		digitalWrite(select,LOW);
		delayMicroseconds(1200);
		digitalWrite(select,HIGH);
		digitalWrite(select,LOW);
		digitalWrite(select,HIGH);
		val=!digitalRead(up);
		digitalWrite(select,LOW);
		digitalWrite(select,HIGH);
		break;
	case SMD_MODE:
		digitalWrite(select,LOW);
		delayMicroseconds(1200);
		digitalWrite(select,HIGH);
		digitalWrite(select,LOW);
		delayMicroseconds(1200);
		digitalWrite(select,HIGH);
		digitalWrite(select,LOW);
		digitalWrite(select,HIGH);
		val=!digitalRead(right);
		digitalWrite(select,LOW);
		digitalWrite(select,HIGH);
		break;
	}
	return val;
}

SMDjoystick.read(SMD_id) позволяет считать значение 1 кнопки.
SMDjoystick.read() возвращает uint16_t со значениями всех кнопок, пригодную для JoyState.

Пример

Подключаем к леонардо:

Gamepad pin Arduino pin
1 2
2 3
3 4
4 5
5 5v
6 6
7 7
8 GND
9 8
#include <TimerOne.h>
#include <SMDjoystick.h>
SMDjoystick j(2,3,4,5,6,7,8);
JoyState_t s;
uint16_t data;
void setup() 
{
Timer1.initialize(10000);
Timer1.attachInterrupt( timerIsr );
s.xAxis=128;
s.yAxis=128;
s.zAxis=128;
s.xRotAxis=128;
s.yRotAxis=128;
s.zRotAxis=128;
s.throttle=128;
s.rudder=128;
s.hatSw1=0;
s.hatSw2=0;
}
void loop()
{
s.buttons=data;
Joystick.setState(&s);
}
void timerIsr()
{
data=j.read();
}

Все, теперь можно использовать геймпад сеги как любой USB геймпад.

Источники:
www.hardwarebook.info/Mega_Drive_Joystick
applause.elfmimi.jp/md6bpad-e.html

Автор: AsGreyWolf

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js