예전에 아주 오래전에 싸울아비 조이스틱을 샀었다. 10년은 족히 넘은 것으로 기억한다. 이후 결혼을 하고, 아이를 낳고 살다가 어느날 오후 아이들에게 추억의 오락실 게임을 시켜주려고 했다. 그러다 문득 그 녀석. '싸울아비 조이스틱' 이 생각났다. 거실 TV 에 작은 PC - 정확히는 라떼판다 - 를 꺼내어 연결해 둔게 있어서 여기 싸울아비 조이스틱만 연결하면 될 거라고 생각했다.

 

 

하.지.만.

아무리 연결을 해 보아도, Plug And Prey 를 해 보아도 Prey 가 듣질 않았다.

인터넷을 찾아보니 Windows 10 미만으로 하위호환 설정을 하라는 둥, 드라이버를 다시 설치하라는 둥.. 온갖 민간요법에 가까운 솔루션들이 넘쳐 났지만 그 어느하나 되는게 없었다.

 

이후 제대로 된 자료를 찾아보니, '싸울아비 온라인' 의 경우 USB 1.x 버전이며 요즘 나오는 메인보드들은 대부분 USB 2.0 이상부터 지원한다고 한다. USB 1.x 는 너무 오래된 스펙이라 지원 자체를 안 한다고...ㅠㅠ

그러니 Windows 10 의 문제가 아니라, 하드웨어 자체의 호환성 문제라는 것. 즉,, 소프트웨어 적으로는 해결이 불가능하다는 이야기지.

 

 

하드웨어 호환성 문제로 요즘 PC 에서는 사용이 불가

 

그렇다. 하드웨어 호환성 문제이니까, 이건 하드웨어로 쇼부를 봐야 한다는 그런 당연한 이야기다.

어떡하지? 어떡하긴 뭘 어떡해. 고치면 되지. 하드웨어적으로.

 

 

 

그까이꺼,, 대충.. 만들면 되는거 아녀?

 

생각을 해 보자. 조이스틱이란 무엇인가?

'버튼을 누른다.', '그 정보를 PC 로 보낸다.' 매우 단순하지 않은가?

 

필요한 재료들을 생각해 보자.

버튼과 스틱이 필요하다. 준비 되었는가? ㅇㅋ. 싸울아비!

버튼의 상태를 PC 로 보내려면? 음.. 아두이노 쓰면 되지!

아두이노를 PC 에서 HID (Human Interface Device) 로 인식하게 하려면? 아두...이노..?!

 

자료를 찾아보니 아두이노 중에서 Atmega 32U4 칩셋을 쓰는 녀석이 가능하다고 한다. HID 회로가 내장 되어 있다고 한다.

보니까 크기도 작아서 '싸울아비 온라인' 어딘가에 구겨 넣기도 적당하군.

 

 

 

아두이노는 Micro 를 사용할 것이고, Pinout 을 보면 위와 같다. 사울아비 온라인의 경우 버튼 10개와 4방향 스틱으로 구성 되어 있으므로, 총 14개의 Pin 이 필요하다. Digital 2 ~ 10 번핀과 16번 핀으로 10개의 버튼을 커버할 것이다. 그리고 Analog 0 ~ 3 까지 4 개의 Pin 을 스틱을 위해 사용할 예정이다.

 

 

 

내장(?)을 뜯어내자

 

외과적인 수술을 먼저 수행하기로 했다. 왜? 쉬우니까 ㅋ

 

안에 있던 기판을 다 뜯어내었다. 잘...하고 있는거겠지?

기판이 들어갈만한 위치를 찾다보니 후보지가 두 곳 정도 보였다.

 

 

A 후보지의 경우, B 에 비하여 공간이 협소하다. 즉, 매우 작게 만들어서 우겨 넣어야 한다. 반면, B 는 A 에 비하여 다소 넓은 공간 확보가 가능하다. 다만 지형에 이상한 불필요한 구조물이 있어서 철거를 추가적으로 해야 한다. 철거의 문제라기보다 본래의 기판에 물리적으로 손상을 가해야 한다는 점이 내키지 않았다.

 

포스팅 마지막에 자세히 후술하겠지만, A 에 넣기 위해서 시도를 했었다. 그러기 위해 기판을 매우매우 작게 만들어야 했고, 모든 소자를 우겨 넣었다. 결국은 실패하여 B 에 위치 시키기로 하고 다시 만들었다.

 

자, B 에 넣기로 하고 회로구성을 해 보자.

 

 

 

어떻게 회로를 구성할까?

 

나는 순수 소프트웨어쟁이다. 하드웨어는 어디까지나 취미일 뿐,, 본업은 소프트웨어 개발자. 앱/서버/웹 등등.. 하드웨어/전자랑은 거리가 멀다. 중등 교육을 받았다면 모두가 아는 F=MA, V=IR 과 키르히호프의 법칙 정도가 내가 아는 전부다 ㅎㅎㅎ.

 

풀업회로로 구성하면, 아두이노 내부적으로 풀업저항이 있어서, 회로 구성이 매우매우 간단해 질 것으로 생각했다. 하지만,, 디바운싱 문제가 남아 있었다. 기계식 버튼을 생각해보자. 버튼이 눌리려면 회로가 접지 되어야 한다. 두 개의 구리판이 붙어있으면 눌린 것이고, 떨어지면 눌리지 않은 거겠지. 그런데 아날로그 세상에서는 생각처럼 이게 간단하지 않다. 어디까지 가까워 졌을때 붙은 것이고, 어디까지 멀어져야 떨어진 것이라고 할 수 있을까? 그리고 진동에 의해 순간적으로 버튼이 붙었다 떨어졌다를 반복하는 시점이 존재한다. 이를 채터링이라고 한다.

 

 

저 바운싱을 제거하기 위해서 어떻게 해야 하는가?

 

 

 

채터링, 바운싱, 디바운싱

 

가장 손쉽게 해결 할 수 있는것은 버튼의 상태를 10~30 ms 정도 단위로 delay 를 주면서 읽는 것이다. 그러면 위의 현상을 어느정도 제거할 수 있다. 100%가 아닌 어느정도라고 표현한 이유가 있다. 만약 버튼을 누르는 속도가 30 ms 이하라면, 입력이 무시된다는 뜻이다. 즉,, 사람의 반응속도를 추정하고, 이보다 빠른 입력을 강제로 막는 방법으로 볼 수 있겠다. 재수가 없어서 바운싱이 30 ms 보다 길어진다면, 의도한대로 동작하지 않게 된다. 겁나,,찝찝하지 않은가?

 

소프트웨어 개발자라면,, 상당히 내키지 않는 방법이라고 할 수 있겠다. ㅠㅠ

 

그러면 효과적인 방법은 무엇이 있을까? 바로 RC 필터 회로를 구성해서 넣는 것이다. RC 필터에는 RC 로우패스 필터와 RC 하이패스 필터가 있다. 하드웨어적으로 설명하자면 어느 주파수 대역의 노이즈를 없앨것인지, 통과시킬건지에 대한 차이라고 할 수 있겠다.

 

 

그러면 커패스터와 저항은 어떤 값을 써야 하는가? 내가 제거해야 하는 주파수는 몇 Hz 인가? 계산 방법이 쉽지 않다. 나는 구글링을 통해서 적절한 값을 찾았고, 계산은 아래 사이트를 통해서 손쉽게 하였다. 

 

 

계산기)

https://www.digikey.kr/ko/resources/conversion-calculators/conversion-calculator-low-pass-and-high-pass-filter

 

 

여기에 더하여, 하나를 추가해 주면 더욱 완벽해 진다. 그것은 바로 NOT 게이트(Invertor)를 붙이는 것이다. 1을 넣으면 0 이 나오고, 0을 넣으면 1 이 나오는 기본적인 게이트 소자이다. 이게 왜 도움이 되는가?

해당 소자인 74HC14 의 스펙을 보면 알 수 있다.

 

 

 

0.3 ~ 1.2 V 입력이면 LOW (0) 으로 인식하고, 1.5 v 이상이면 HIGH (1) 로 인식이 된다. 1.2 와 1.5 사잇값이면 변동이 없다라는 거겠지. 즉,, 디바운싱 효과를 얻을 수 있다.

 

 

아두이노 프로그래밍을 해 보자

 

소스 코드는 생각보다 단순하다. 소프트웨어 개발자라면,, 눈 감고 발가락으로 짜도 10분이 채 걸리지 않는다. 라고 허세를 부릴 만큼 단순하다. 풋ㅋ;

 

#include <Joystick.h>

#define BTN_1 2
#define BTN_2 3
#define BTN_3 4
#define BTN_4 5
#define BTN_5 6
#define BTN_6 7
#define BTN_7 8
#define BTN_8 9
#define BTN_9 10
#define BTN_10 16

#define BTN_UP A3
#define BTN_RIGHT A2
#define BTN_DOWN A1
#define BTN_LEFT A0


// Global variables
Joystick_ Joystick(JOYSTICK_DEFAULT_REPORT_ID, JOYSTICK_TYPE_GAMEPAD,
  10, 0,                  // 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;

// the setup function runs once when you press reset or power the board
void setup() {
  pinMode(BTN_1, INPUT);
  pinMode(BTN_2, INPUT);
  pinMode(BTN_3, INPUT);
  pinMode(BTN_4, INPUT);
  pinMode(BTN_5, INPUT);
  pinMode(BTN_6, INPUT);
  pinMode(BTN_7, INPUT);
  pinMode(BTN_8, INPUT);
  pinMode(BTN_9, INPUT);
  pinMode(BTN_10, INPUT);

  pinMode(BTN_UP, INPUT);
  pinMode(BTN_RIGHT, INPUT);
  pinMode(BTN_DOWN, INPUT);
  pinMode(BTN_LEFT, INPUT);

  Joystick.begin();
  Joystick.setXAxisRange(-1, 1);
  Joystick.setYAxisRange(-1, 1);
}


int prevUp = LOW;
int prevRight = LOW;
int prevDown = LOW;
int prevLeft = LOW;

int prevBtn1 = LOW;
int prevBtn2 = LOW;
int prevBtn3 = LOW;
int prevBtn4 = LOW;
int prevBtn5 = LOW;
int prevBtn6 = LOW;
int prevBtn7 = LOW;
int prevBtn8 = LOW;
int prevBtn9 = LOW;
int prevBtn10 = LOW;

// the loop function runs over and over again forever
void loop() {
  int up = digitalRead(BTN_UP);
  int right = digitalRead(BTN_RIGHT);
  int down = digitalRead(BTN_DOWN);
  int left = digitalRead(BTN_LEFT);

  int button1 = digitalRead(BTN_1);
  int button2 = digitalRead(BTN_2);
  int button3 = digitalRead(BTN_3);
  int button4 = digitalRead(BTN_4);
  int button5 = digitalRead(BTN_5);
  int button6 = digitalRead(BTN_6);
  int button7 = digitalRead(BTN_7);
  int button8 = digitalRead(BTN_8);
  int button9 = digitalRead(BTN_9);
  int button10 = digitalRead(BTN_10);

  if(up != prevUp || down != prevDown) {
    prevUp = up;
    prevDown = down;
    Joystick.setYAxis(up == HIGH ? -1 : (down == HIGH ? 1 : 0));
  }
  if(right != prevRight || left != prevLeft) {
    prevRight = right;
    prevLeft = left;
    Joystick.setXAxis(right == HIGH ? 1 : (left == HIGH ? -1 : 0));
  }
  

  if(button1 != prevBtn1) {
    prevBtn1 = button1;
    Joystick.setButton(0, button1);
  }
  if(button2 != prevBtn2) {
    prevBtn2 = button2;
    Joystick.setButton(1, button2);
  }
  if(button3 != prevBtn3) {
    prevBtn3 = button3;
    Joystick.setButton(2, button3);
  }
  if(button4 != prevBtn4) {
    prevBtn4 = button4;
    Joystick.setButton(3, button4);
  }
  if(button5 != prevBtn5) {
    prevBtn5 = button5;
    Joystick.setButton(4, button5);
  }
  if(button6 != prevBtn6) {
    prevBtn6 = button6;
    Joystick.setButton(5, button6);
  }
  if(button7 != prevBtn7) {
    prevBtn7 = button7;
    Joystick.setButton(6, button7);
  }
  if(button8 != prevBtn8) {
    prevBtn8 = button8;
    Joystick.setButton(7, button8);
  }
  if(button9 != prevBtn9) {
    prevBtn9 = button9;
    Joystick.setButton(8, button9);
  }
  if(button10 != prevBtn10) {
    prevBtn10 = button10;
    Joystick.setButton(9, button10);
  }
}

 

 

 

하드웨어? 난 초보자

 

소프트웨어 개발자가 겁대가리 없이 하드웨어를 바로 제작한다? X되는 지름길이라는것을 경험을 통해서 이미 익히 알고 이기 때문에, 예우를 갖춰 빵판에 먼저 회로 작업을 했다.

 

 

 

아름답지 아니한가?! 팩토리오에서 공장을 짓듯이 한땀한땀 만들어 보았다. 굳이 14개의 모든 버튼을 만들 필요가 있을까? 있다. 왜냐고? 나는 전자회로/하드웨어에 있어서 브론즈는 커녕 아이언 등급이니까.

 

아두이노에 올리고 PC 와 연결해서 테스트 해 보니 잘 동작한다 ^O^

이제 가장 큰 것이 남았다. 위 빵판을 고스란히 싸울아비 온라인 내부에 넣을 수 없다는 것. 만능 기판에 작업을 해야 한다. 여기서 실수하면 진짜 X 된다...

 

 

 

한땀한땀 땜질

 

먼저 주요 소자를 위한 자리를 잡아 주었다. 바로 소자를 기판에 붙일 수도 있겠지만, 혹시라도 제거하고 다시 해야 하는 최악의 상황을 위해서 소켓을 붙여 주었다. 언제든지 제거할 수 있도록 :)

 

 

 

 

74HC14 는 총 다리를 14 개 가지고 있다. 근데,, 내가 가지고 있는 소켓은 15 핀 짜리네? 뭐..어쩔수 없지. 이거라도 써야지 ㅎ. 좌하단에 2 핀 짜리 JST XH 소켓은 GND 로 쓸 것이고, 그 다음 4 핀 짜리는 스틱의 4방향 핀. 나머지 5 핀 짜리 2개는 각각 1 ~ 5 번 버튼, 6 ~ 10 번 버튼을 위한 것이다.

 

뒷편 작업을 마쳤다.

 

나름 최선의 회로 구성을 위해서 배치한 노력이 느껴지는가? 나는 이것도 꽤나 어려웠다. 최단 경로. 그리고 최적의 선 꼬임을 모두 고려해야 했다.

 

 

후...여기까지가 74HC14 의 Output 핀과 아두이노 기판의 연결을 제외한 모든 연결을 마친 상태이다. 이제 가장 큰 놈이 남았다. 74HC14 에서 총 14개의 선을 아두이노 기판에 연결해야 한다. 문제는 자리가 너무 협소하다. 앞판에 이 모든것을 우겨 넣는것은 거의 불가능하다고 판단되어, 해당 연결은 뒷판에서 하기로 했다.

 

 

마음에 들지 않는다. 너무 지저분하다. 아마 IDC 에서 서버 UTP 케이블을 저렇게 연결해놨다면, 권고사직을 받았을 것 같다... ㅠㅠ

 

아무튼 저렇게 연결하고, 각 소자와 아디우노를 끼워보았다.

 

 

 

이제 납땜 작업을 위해서 끼워뒀던 PCV 서포터를 제거하고, 싸울아비 온라인에 우겨넣기 적당한 길이의 서포터로 교체. 이후 순간접착제를 이용하여 붙여보았다. 여담이지만, 다행히도 순간접착제로 잘 붙었다. 간혹 플라스틱 제질에 따라 순간접착제가 붙지 않는 거지같은 경우가 있기 때문이다.

 

 

 

빨.주.노.초.파.남.보 등등.. 나는 모든 성소수자를 지지합니다..

 

 

 

두근두근, 잘 될까?

 

이제 모든 준비가 끝났다. PC 와 연결하고 잘 동작하는지 테스트 해 보았다.

 

 

오호.. 내가 만든 스틱에 윈도우가 반응하기 시작했다.

 

 

두근두근. 장치 목록을 살펴보자. CrystalCube Stick 이라고 잘 나온다.

 

 

 

상세 화면에 들어가서 살펴보자.

 

 

이것저것 버튼을 눌러보니, 잘 인식이 된다.

 

 

메마에 내가 좋아하던, 마하브레이커스를 설치하고 연결해서 테스트 해보면~!

 

 

잘 된다. 매우 잘 된다.

 

이렇게해서 죽어가던 싸울아비 온라인을 Windows 10 에서도 잘 동작하도록 문제 해결!

 

 

Conclusion

 

그냥 스틱하나 새로 사는게 정신건강에 이롭다.

본인처럼 전자회로에 관심이 있고, 관련 이론을 공부하고 그것을 증명하기 위해서 만드는게 아니라면, 금전적으로나 물리적인 시간으로나 그냥 사는게 낫다.

 

그리고 앞서 서술했듯이 처음에 더 작은 기판을 만들었었으나 실패하였다. 각 소자의 배치 문제와 납땜의 문제로 인하여 어디선가 오류가 발생하였다. 그런데 디버깅 또한 쉽지 않았다. 결국 다 버리고 조금더 큰 기판에 새로 작업했다라는 슬픈 이야기.

 

망한 기판

 

지옥이 있다면 이런게 아닐까.

 

 

재미있는 점

 

아두이노 보드를 사용했기 때문에, 소프트웨어 업데이트가 편리하다. 무슨 이야긴가면 USB 로 PC 와 연결하고 나면, 그 상태에서 언제든 소프트웨어(펌웨어) 업데이트가 가능하다. 그리고 Serial 로 디버깅도 가능하다.

예를들어 10번 키를 누르면, 특정 버튼을 엄청난 속도로 클릭하는 메크로와 같은 이상한 기능들은 언제든 간편하게 추가할 수 있다라는 것이다 :)

 

다음에 또 재미있는 작업이 생기면, 공유하도록 하겠다.

 

 

 

 

'Hardware' 카테고리의 다른 글

아두이노의 소소한 함정들  (1) 2024.10.19
타이머 LED 만들기  (0) 2024.10.17
하드웨어 프로그래밍의 기초  (0) 2024.10.17
[ESP8266-01] 아두이노 프로그래밍  (1) 2022.03.22
[ESP8266-01] 최신 Firmware 업그레이드  (11) 2022.03.20