예전에 학생시절에는 PSP를 참 갖고 싶어했습니다. 지하철에서 플레이 하는 사람들을 보면 참 부러웠었지요.

그리고 세월이 지나 지금. PSP는 과거의 유물이 되었고 지금은 다들 스마트폰을 들고 다닙니다. 그리고 PPSSPP란 에뮬레이터가 나와서 PSP게임을 굴릴 수 있게 되었습니다.


하지만 터치인터페이스는 무진장 게임하기 불편했습니다. 그래서 스마트폰용 조이패드를 차던중 이런 녀석이 있더군요.



로지텍 파워쉘입니다. 문제는 이놈은 아이폰 전용이었고 범용성은 내다 버린 것이 문제였습니다. 사실 전 이런놈이 있는 줄도 몰랐는데 누가 컨셉 겹친다고 해서 찾아봤더니 있더군요. 결국 덤핑했다는 후문이 있지만...



어쨌거나 저는 대충 연구실에서 시간 나는대로 게임용 컨트롤러를 만들기로 했습니다.


그래서 만든 것이 바로 위의 물건입니다. 아직 하우징은 만들어지지 않았고 동작만 되는 형편입니다.


일단 제작을 위한 준비물은...



일단 준비물은 아두이노를 썼으니 아두이노가 필요하겠지요. 크기가 크면 문제가 많으므로 작은 모델이 필요합니다. 그리고 조이패드로 인식 시켜야 했으므로 Leonardo와 호환되어야 했지요. 그래서 찾은 모델이 이 모델입니다. 위의 것은 SparkFun 정품이고 아래의 물건은 그것의 호환 제품입니다. 어떤 것을 써도 상관없습니다. 개인적으로 호환품도 크게 문제는 없었습니다.


(프로 마이크로 입니다. 프로 미니, 나노 아니에요!)




버튼도 SparkFun제품을 사용했습니다. 이런 식을 나온 버튼이 상당히 누르는 것이 좋더군요. 하우징 설계할 때도 편리합니다. 뭣하면 다른 스위치를 써도 그만입니다.


조이패드도 SparkFun 호환품으로 구입. 사실 아두이노 키트사면 들어있어요.



그리고 대망의 스마트폰 고정 가이드는 없는 것 빼고 다 판다는 Coms의 제품입니다. 진짜 이 회사의 내부가 궁금합니다. 매번 있을까? 라고 생각하면 있습니다. 우리나라에서 이런 회사가 있다는 것이 자랑스럽습니다.



그외에 준비물로는 부품 고정용 만능 기판 PCB(대충 사서 톱으로 썰어야 합니다.)와 USB OTG케이블, 그리고 USB micro B 케이블(스마트폰 데이터 케이블도 상관없습니다.) 다 Coms에서 팔아요....


음...배선을 깜빡했네요. 배선은.... 그냥 아두이노 책을 보시고 해주시길 바랍니다.(...)


버튼들은 GND와 각 디지털 핀.(Tact스위치는 4개의 다리 중 대각선으로 해주시는 것이 정신건강에 좋습니다. 2개씩은 사실 하나의 다리라 헷갈리면 X되요.


조이패드는 핀배열에 맞게 해주시면 됩니다.


5V-5V

GND-GND

VRx-A0

VRy-A1

SW - 디지털핀 아무거나


그리고 프로그램은 우선 라이브러리를 설치해야 하는데

https://github.com/MHeironimus/ArduinoJoystickLibrary


이것을 사용합니다.

파일 다운로드는 https://github.com/MHeironimus/ArduinoJoystickLibrary/archive/master.zip


위 파일을 다운로드 받은다음


아두이노 IDE에서

스케치- 라이브러리 가져오기-Add Library를 선택하고

해당 파일을 선택합니다.


그리고 아두이노에 넣을 스케치는 다음과 같습니다.


 // Simple example application that shows how to read four Arduino
// digital pins and map them to the USB Joystick library.
//
// The digital pins 9, 10, 11, and 12 are grounded when they are pressed.
//
// NOTE: This sketch file is for use with Arduino Leonardo and
//       Arduino Micro only.
//
// by Matthew Heironimus
// 2015-11-20
//--------------------------------------------------------------------

#include <Joystick.h>

void setup() {
  // Initialize Button Pins
  pinMode(0, INPUT_PULLUP);
  pinMode(1, INPUT_PULLUP);
  pinMode(2, INPUT_PULLUP);
  pinMode(3, INPUT_PULLUP);
  pinMode(4, INPUT_PULLUP);
  pinMode(5, INPUT_PULLUP);
  pinMode(6, INPUT_PULLUP);
  pinMode(7, INPUT_PULLUP);
  pinMode(8, INPUT_PULLUP);
  pinMode(9, INPUT_PULLUP);
  pinMode(10, INPUT_PULLUP);
  pinMode(11, INPUT_PULLUP);
  pinMode(12, INPUT_PULLUP);
  pinMode(13, INPUT_PULLUP);
  pinMode(14, INPUT_PULLUP);
  pinMode(15, INPUT_PULLUP);
  pinMode(16, INPUT_PULLUP);
  // Initialize Joystick Library
  Joystick.begin();
}

// Constant that maps the phyical pin to the joystick button.
const int pinToButtonMap = 0;
const int deadZone = 30;

// Last state of the button
int lastButtonState[17] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
//int lastButtonState[4] = {0,0,0,0};
int X=0;
int Y=0;

void loop() {

  // Read pin values
  for (int index = 0; index < 17; index++)
  {
    int currentButtonState = !digitalRead(index + pinToButtonMap);
    if (currentButtonState != lastButtonState[index])
    {
      Joystick.setButton(index, currentButtonState);
      lastButtonState[index] = currentButtonState;
    }
  }
  X=analogRead(A0);
  Y=analogRead(A1);
  X=map(X,0,1023,127,-127);
  Y=map(Y,0,1023,-127,127);
  Joystick.setXAxis(X);
  Joystick.setYAxis(Y);

  delay(50);
}


간단하죠? 좀 반응이 느리다 싶으시면 delay를 20정도로 줄여주시면 됩니다. 저는 가끔 느린 것을 느끼기는 하는데 그냥 냅두고 있습니다.


그렇게해서 만들어진 대망의 물건



저는 실력이 미천하여 이정도밖에 못했지만 실력 좋으신 분들은 훨씬 더 좋은 물건을 만들 수 있을 거라고 봅니다.




==================2017. 5. 25======================


아두이노용 조이스틱 라이브러리의 버전이 업그레이드 되었습니다.

https://github.com/MHeironimus/ArduinoJoystickLibrary


이전과 동일한 곳이지만 그전에는

X,Y 축과 버튼까지만 지원했지만 이제는 거의 표준이라 할 수 있는

아날로그 스틱

HAT 방향키

트리거

버튼


이렇게 지원합니다. 즉, 엑스박스 패드의 형식을 그대로 지원하고 엑스박스 패드를 지원하는 게임에서 정상적으로 작동되게 할 수 있습니다.


제가 만든 구조는


왼쪽 4개의 버튼은 방향키로

아래의 아날로그는 HAT으로

오른쪽은 버튼으로 인식되게 했습니다.


하지만 마음을 먹으면 아날로그 스틱을 2개 달고 엑스박스 패드처럼 만들 수도 있는 것이지요.


그래서 이번에 수정한 소스를 공개합니다.


#include <Joystick.h>

Joystick_ Joystick(JOYSTICK_DEFAULT_REPORT_ID,JOYSTICK_TYPE_GAMEPAD,
  5, 1,                  // Button Count, Hat Switch Count
  true, true, false,     // X and Y, but no Z Axis
  false, false, false,   // No Rx, Ry, or Rz
  false, false,          // No rudder or throttle
  false, false, false);  // No accelerator, brake, or steering


int lastDpadState[]={0,0,0,0};
int lastButtonState[]={0,0,0,0,0,0,0};
int lastSWState=0;

void setup() {
  // Initialize Button Pins
  pinMode(10, INPUT_PULLUP);//A
  pinMode(14, INPUT_PULLUP);//B
  pinMode(15, INPUT_PULLUP);//X
  pinMode(16, INPUT_PULLUP);//Y
 
  pinMode(6, INPUT_PULLUP);//Up
  pinMode(8, INPUT_PULLUP);//down
  pinMode(7, INPUT_PULLUP);//left
  pinMode(9, INPUT_PULLUP);//right
 
  pinMode(2, INPUT_PULLUP);//HAT_SW

  pinMode(11, INPUT_PULLUP);
  pinMode(12, INPUT_PULLUP);
  pinMode(13, INPUT_PULLUP);
 /*not using pin Num
  pinMode(11, OUTPUT);
  pinMode(12, OUTPUT);
  pinMode(13, OUTPUT);

  digitalWrite(11,LOW);
  digitalWrite(12,LOW);
  digitalWrite(13,LOW);  
  */
  //A0=X A1=Y

  // Initialize Joystick Library
  Joystick.begin();
  Joystick.setXAxisRange(-1023, 0);
  Joystick.setYAxisRange(-1023, 0);

  Joystick.setRxAxisRange(-1, 1);
  Joystick.setRyAxisRange(-1, 1);
}

void Analog()
{
  int x = analogRead(A0);
  int y = analogRead(A1);
 
  Joystick.setXAxis(-x);
  Joystick.setYAxis(-y);
}

void Buttons()
{
  for(int index=0; index < 7 ;index++)
  {
    int currentButtonState = !digitalRead(index+10);
    if (currentButtonState !=lastButtonState[index])
    {
      //Joystick.setButton(index, currentButtonState);
      switch (index)
      {
        case 0://A
          Joystick.setButton(0, currentButtonState);
          break;

        case 4://B  
        case 5://X
        case 6://Y
          Joystick.setButton(index-3,currentButtonState);
          break;
          
        default:
          break;
      }
      
      lastButtonState[index]=currentButtonState;
    }
  }
 
}


void Dpad()
{
  for (int index = 0; index < 4; index++)
  {
      int currentDpadState = !digitalRead(index+6);
      if (currentDpadState != lastDpadState[index])
      {
        switch(index){
          case 0://UP
            if (currentDpadState == 1) {
              Joystick.setRyAxis(1);
            } else {
              Joystick.setRyAxis(0);
            }
            break;
          
          case 1://left
          if (currentDpadState == 1) {
            Joystick.setRxAxis(-1);
          } else {
            Joystick.setRxAxis(0);
          }
          break;
            
          case 2://down
          if (currentDpadState == 1) {
            Joystick.setRyAxis(-1);
          } else {
            Joystick.setRyAxis(0);
          }
          break;

          case 3://right
          if (currentDpadState == 1) {
            Joystick.setRxAxis(1);
          } else {
            Joystick.setRxAxis(0);
          }
          break;
            
        }
        lastDpadState[index] = currentDpadState;
      }
  }
}


void HAT()
{
  bool valueChanged =false;
  for (int index = 0; index < 4; index++)
  {
      int currentDpadState = !digitalRead(index+6);
      if (currentDpadState != lastDpadState[index])
      {
        valueChanged =true;
        lastDpadState[index] = currentDpadState;
      }    
  }

 
  if (valueChanged) {
      
    if ((lastDpadState[0] == 0)
      && (lastDpadState[1] == 0)
      && (lastDpadState[2] == 0)
      && (lastDpadState[3] == 0)) {
        Joystick.setHatSwitch(0, -1);//HatSwitch0 is not changed
    }
    if (lastDpadState[0] == 1) {
      Joystick.setHatSwitch(0, 0);
    }
    if (lastDpadState[1] == 1) {
      Joystick.setHatSwitch(0, 90);
    }
    if (lastDpadState[2] == 1) {
      Joystick.setHatSwitch(0, 180);
    }
    if (lastDpadState[3] == 1) {
      Joystick.setHatSwitch(0, 270);
    }
  }//value Changed()
}
void SW()
{
  int currentSWState = !digitalRead(2);
  if(lastSWState != currentSWState)
  {
    Joystick.setButton(4, currentSWState);
    lastSWState=currentSWState;
  }
}

void loop()
{
  Analog();
  //Dpad();
  HAT();
  Buttons();
  SW();
  delay(2);
}


회로 연결은


0번 버튼은 10번핀에

1번 버튼은 14번핀에

2번 버튼은 15번핀에

3번 버튼은 16번핀에


방향버튼(D-PAD)는

위버튼은 6번핀

왼쪽버튼은 7번핀

아래버튼은 8번픈

오른쪽 버튼은 9번핀 으로 연결합니다.


D-PAD는 HAT0로 인식되게 됩니다.


그리고 아날로그는 X축은 A0 Y축은 A1로 연결합니다.

만약 방향이 반대로 인식될 경우에는 위 소스에서 Analog()함수를 찾으신 다음


  Joystick.setXAxis(-x);
  Joystick.setYAxis(-y);


이 둘에서 -를 지워주시고 Seup()안의


  Joystick.setXAxisRange(-1023, 0);
  Joystick.setYAxisRange(-1023, 0);


이 두줄을 삭제해 주시면 됩니다.


아날로그 스틱을 눌렀을 때 스위치는 2번 핀에 연결했습니다.

제가 사용한 Arduino Pro Micro의 구조상 이 구조가 가장 간단해서 이렇게 만들었습니다.


코드나 회로에 자신있으신 분들은 제대로 만드실 수 있을 겁니다.

,