목차
(1) HID 장치가 뭐길래
(2) HoodLoader2
(3) 조이스틱 제작
환승입니다
드디어 준비 단계가 다 끝나고 진짜로 조이스틱을 만들 차례다. 앞선 과정을 따라오면서 HoodLoader2를 업로드했다면, 그 보드에 있는 MCU 두 개, 즉 USB MCU와 I/O MCU는 모두 프로그래밍이 가능하다. 아두이노와 컴퓨터를 연결하면 기본적으로 인식되는 MCU는 USB MCU인 16u2이다. 이걸 I/O MCU인 328P로 인식되게 하려면,
사진의 두 핀을 빠르게 두 번 합선시켜 주면 된다.
한 번만 합선시키면 16u2로 인식되고, 두 번 빠르게 합선시키면 328P로 인식한다. 이렇게 두 MCU 사이를 갈아타면서 모든 MCU에 원하는 코드를 업로드하면 된다. 택트 스위치를 이용하면 편리하다.
왼쪽이 스위치를 한 번 눌러 USB MCU로 인식시켰을 때, 오른쪽이 두 번 눌러서 I/O MCU로 인식시켰을 때의 보드 설정이다.
라이브러리 설치
그럼 다시 본래의 목적을 찾아 HID 라이브러리를 설치해야 한다. HID에서 저장소를 클론받아 라이브러리를 설치한다. 이제 모든 준비가 끝났다.
조이스틱 만들기
I/O MCU는 조이스틱 모듈로부터 입력을 받아 USB MCU에 시리얼 통신으로 전송하는 내용이다. 여기에는 두 가지 선택지가 있다. 조이스틱 입력을 컴퓨터에 키보드 키 입력으로 전달할 수도, 마우스 조작으로 전달할 수도 있다. 마우스로 입력할 때는 조이스틱 핀이 잘 연결되어 있는지 주의해야 한다. 제대로 연결되지 않아 노이즈가 낄 경우 마우스가 혼자 폭주하기 시작한다. 제멋대로 커서를 옮기고 클릭해서 아두이노 보드에 수정할 코드를 업로드할 수 없게 만들어 버린다.
조이스틱 모듈에는 조이스틱을 아래로 누르면 눌리는 택트 스위치 입력이 하나 더 있다. 이 버튼을 키보드/마우스 조작을 전환하는 스위치로 이용한다. 코드는 다음과 같다.
const int xAxis = A0; // 조이스틱 모듈 X축 입력 핀
const int yAxis = A1; // 조이스틱 모듈 Y축 입력 핀
boolean Mode = true; // 키보드/마우스 전환 모드
int range = 12; // X축, Y축 동작 범위
int responseDelay = 5; // 응답 딜레이 5ms 지정
int threshold = range / 4; // 리셋 위치 지정
int center = range / 2; // 중심 위치 지정
void setup() {
Serial.begin(115200);
pinMode(2, INPUT_PULLUP);
pinMode(13, OUTPUT);
}
void loop() {
// 조이스틱 모듈에서 X, Y축 아날로그 값 읽기
int xReading = readAxis(A0);
int yReading = readAxis(A1);
/*
시리얼 통신을 이용해 읽어온 데이터를 USB MCU로 전송한다.
X, Y축 데이터를 순서 없이 보내면 이 데이터가 어느 축의 데이터인지 구분할 수가 없다.
따라서 조금의 문자열 조작이 필요하다.
*/
String xySeparator = ","; // X축과 Y축 데이터를 구분할 분리 문자
String modeSeparator = "@"; // 축 데이터와 모드 데이터를 구분할 분리 문자
String packetSeparator = "|"; // 한 패킷을 구분할 분리 문자
/*
X축 값 + 축 구분 문자 + Y축 값 + 데이터 구분 문자 + 모드 데이터 + 세트 구분 문자를 한 패킷으로 만든다.
예를 들어 X, Y축 값이 각각 3, 5이고 2번 핀이 HIGH라면 패킷은 3,5@HIGH| 가 된다.
연속적으로 보내면 2,4@HIGH|1,3@HIGH|4,5@HIGH|2,6@LOW| 와 같이 전송될 것이다.
*/
String Data = xReading + xySeparator + yReading + modeSeparator + digitalRead(2) + packetSeparator;
char Packet[20]; // 패킷을 담을 문자열 배열을 선언한다.
Data.toCharArray(Packet, 20); // 패킷을 문자열 배열에 담아서
Serial.write(Packet); // 시리얼 통신으로 쏘아 준다.
// 빌트인 LED로 현재 모드를 표시한다.
if(Mode) digitalWrite(13, HIGH);
else digitalWrite(13, LOW);
delay(responseDelay); // 약간의 딜레이를 준다.
}
// 조이스틱 모듈에서 아날로그 값을 읽는 함수
int readAxis(int thisAxis) {
int reading = analogRead(thisAxis);
reading = map(reading, 0, 1023, 0, range);
int distance = reading - center;
if (abs(distance) < threshold) distance = 0;
return distance;
}
USB MCU는 시리얼 통신으로 받은 값을 파싱한 후, 컴퓨터에 입력으로 전달한다. 코드는 다음과 같다.
#include <Keyboard.h>
#include <Mouse.h>
#include <HID.h>
boolean Mode = true;
int xValue;
int yValue;
int lastButtonReceive = 1;
unsigned long int time1 = 0;
void setup() {
Serial1.begin(115200);
Keyboard.begin();
Mouse.begin();
}
void loop() {
String receive = Serial1.readStringUntil('|');
Serial.println(receive);
int xySeparator = receive.indexOf(',');
int modeSeparator = receive.indexOf('@');
int buttonReceive = receive.substring(modeSeparator + 1).toInt();
receive.remove(modeSeparator);
yValue = receive.substring(xySeparator + 1).toInt()
receive.remove(xySeparator);
xValue = receive.toInt();
if(!lastButtonReceive && lastButtonReceive != buttonReceive) time1 = millis(); // push
if(lastButtonReceive && lastButtonReceive != buttonReceive) { // release
if(millis() - time1 > 1000) Mode = !Mode;
else Keyboard.write(KEY_RETURN);
}
lastButtonReceive = buttonReceive;
if(Mode) Mouse.move(xValue, yValue, 0);
else {
if(xValue > 0) Keyboard.write(KEY_RIGHT_ARROW);
if(xValue < 0) Keyboard.write(KEY_LEFT_ARROW);
if(yValue < 0) Keyboard.write(KEY_UP_ARROW);
if(yValue > 0) Keyboard.write(KEY_DOWN_ARROW);
}
}
야크 털이 너무 많이 쌓여 앞이 보이지 않는다. 하드웨어도 만들어 주어야 한다. 조이스틱이 너무 짧아 조작하는 맛이 안 나서 고장난 분광기 경통을 위에 끼웠다. 그랬더니 이번에는 경통이 너무 무거워 지지가 되지 않아서 고장난 기포발생기를 받침으로 썼다. 삽질은 성공적이다. 만든 조이스틱으로 비행하는 영상으로 글을 마무리한다.
나중에 외주 작업을 받아 이 때 작업한 내용을 이용해 컨트롤러도 만들었다. 케이스를 모델링하고 3D 프린터로 뽑아 작업한 조이스틱이다.