owned this note changed 2 years ago
Linked with GitHub

 

G-건(총 모양 마우스) 만들기 프로젝트

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

센서 및 버튼 입력을 이용해 마우스의 기능을 하는 총 모양 장치를 개발하는 프로젝트.

**현재 이 프로젝트의 내용은 최신화가 되어 있지 않습니다.

따라서 프로젝트 내용 그대로 만들시 작동이 보장되지 않습니다.

기본적인 설계의 흐름만 참조하는 용도로 사용하시기 바랍니다.**

Github

 

타임라인

1. 똘삼이 본격적으로 프로젝트를 시작함. 2021, July 3rd, Saturday
2. 똘삼이 프로젝트명으로 G-건(GTA Gun)을 제안함. 2021, July 4th, Sunday 11:32 PM
3. 처음으로 버튼 입력 기능이 동작하는 수신기 프로토타입을 제작함. 2021, July 5th, Monday 1:39 AM
4. 수신기 프로토타입이 아날로그 입력 기능이 동작하도록 개선함. 2021, July 5th, Monday 10:54 PM
5. Orientation 센서에서 지역 가속도를 전역 가속도로 변환하는 방법을 찾는 과정에서 난관에 봉착함. 2021, July 6th, Tuesday 12:28 AM
6. 방향을 전환해, 리얼 임팩트 건과 같이 지역 가속도만을 이용해 마우스를 움직이는 방법을 먼저 시도해 보기로 함. 2021, July 6th, Tuesday 6:40 PM
7. 가속도 방식을 철저히 포기하고, 각속도를 이용하기로 함. 2021, July 6th, Tuesday 10:37 PM
8. 기적적으로, 시청자가 가져온 코드가 자이로를 이용하여 기울어진 상태에서도 상당히 잘 작동하는 것을 확인함. 2021, July 6th, Tuesday 11:59 PM
9. 이동 평균 필터를 적용하는 것을 마지막으로, 마우스 커서 변위값을 도출해내는 방식을 확정함. 2021, July 7th, Thursday
10. 블루투스 통신을 통해 센서 및 장치 등의 값을 전송하여 컴퓨터를 조작하는 시도를 시작함. 2021, July 11th, Sunday
11. 두 장치를 직접 연결하여 조작하는 데에는 성공하였으나, 블루투스 모듈을 이용하였을때에는 레이턴시가 크게 나타남. 2021, July 11th, Sunday
12. 블루투스 버전 4.0을 지원하는 다른 모듈을 이용해서 낮은 레이턴시로 작동시키는 데에 성공함. 2021, July 12th, Monday 2:02 AM
13. 프로토타입을 장난감 총 내부에 집어넣기 위한 작업에 돌입함. 2021, July 12th, Monday
14. 프로토타입을 총 내부에 집어넣은 제품을 완성하였으나, 기울기를 반영한 동작이 제대로 동작하지 않음. 이 부분을 보완하려면 돌아가야 할 것들이 많아, 프로젝트를 여기서 마무리지으려고 함. 2021, July 18th, Sunday
15. 인줄 알았으나, 센서 모듈을 짭 모듈로 변경하여 생긴 차이에 의한 오류였던 것으로 판명됨. 즉, 기울기를 정상적으로 반영하는 장난감 총을 완성하는 데에 성공한 것이었고, 프로젝트는 성공적이었음. 2021, July 18th, Sunday 1:49 AM
프로젝트 완결 (휴식 기간 포함 약 15일 소요됨)

 
 

요구 사항

  • 기울기 반영

    아두이노를 사용한 기존의 에어마우스형 컨트롤러는 기울인 뒤 이동시 움직임이 부정확해지는 부분이 있음. 이를 LG의 매직 리모컨과 유사한 형태로 어떤 그립상태에서도 사용자가 원하는 움직임이 가능하도록 제작한다.

  • 저지연 무선

    블루투스 or wifi등을 이용하여 최소의 레이턴시로 움직임을 반영하도록 한다.

  • 플러그 앤 플레이

    별도 프로그램 및 드라이버 설치 없이 연결하는 순간 바로 작동하도록

  • 총 상호작용 구현

    진동 모터를 이용한 격발 체감 효과, 일반 모터 혹은 리니어 모터를 이용한 상부 블로우백 효과 등 가능한 한 실총과 유사한 경험을 제공하도록

 
 

구상 및 자료 조사

 

구상도

초기

graph TD
subgraph "장치 내부"
    %% nodes
    ARD[(아두이노 나노)]
    BTN1[[버튼 1: 좌클릭]]
    BTN2[[버튼 2: 우클릭]]
    BTN3[[버튼 3: 재장전]]
    BTN4[[버튼 4: Reset]]
    ANALOG((아날로그 스틱))
    BNO[다중 기능 센서]
    BLT[블루투스 모듈]
    
    %% 
	BTN1 --- ARD
	BTN2 --- ARD
	BTN3 --- ARD
	BTN4 --- ARD
    ARD --- ANALOG
	ARD --- BNO
	ARD --- BLT
end

subgraph PC[컴퓨터]
	BLT -.-|블루투스 시리얼 통신| PROGRAM[대충 드라이버 프로그램]
    
end

%% classDef global font-size:15px
%% class ARD,BTN1,BTN2,BTN3,BTN4,BNO,BLT, global

 

개선안 1. 수신기 추가(채택)

graph TD
subgraph gun[장치]
    %% nodes
    ARD1[(아두이노 나노)]
    BTN1[[버튼 1: 좌클릭]]
    BTN2[[버튼 2: 우클릭]]
    BTN3[[버튼 3: 재장전]]
    BTN4[[버튼 4: Reset]]
    ANALOG((아날로그 스틱))
    BNO[다중 기능 센서]
    BLT1[블루투스 모듈]
    
    %% links
	BTN1 --- ARD1
	BTN2 --- ARD1
	BTN3 --- ARD1
	BTN4 --- ARD1
    ARD1 --- ANALOG
	ARD1 --- BNO
	ARD1 --- BLT1
end

subgraph receiver[수신기]
    %% nodes
    BLT2[블루투스 모듈]
    ARD2[(아두이노 레오나르도)]
    
    %% links
    BLT1 -.-|블루투스 마스터/슬레이브 통신| BLT2
    BLT2 --- ARD2
end

PC[컴퓨터] ----|USB| ARD2

%% classDef global fill:#fffffff0,stroke:#a0a0a0,stroke-width:1.5px
%% class ARD,BTN1,BTN2,BTN3,BTN4,BNO,BLT, global

블루투스로 PC와 시리얼 통신해 PC에서 파이썬 등으로 컨트롤할 수 있으나 게임에 따라 사용이 불가능할 수 있어(핵으로 인식) 키보드 마우스로 작동하는 아두이노를 추가하는 방식으로 구상도를 그려봤습니다.
sko153
출처

 

경험상 블루투스 모듈 연결이 문제를 일으키는 경우가 많으니 좀더 간단하고 싼? RF 모듈을 쓰는 것도 좋을 것 같습니다. Gang V
구매처

 

개선안 2. ESP32 보드 사용

graph TD
subgraph "장치 내부"
    %% nodes
    ARD[(ESP32)]
    BTN1[[버튼 1: 좌클릭]]
    BTN2[[버튼 2: 우클릭]]
    BTN3[[버튼 3: 재장전]]
    BTN4[[버튼 4: Reset]]
    ANALOG((아날로그 스틱))
    BNO[다중 기능 센서]
    
    %% 
	BTN1 --- ARD
	BTN2 --- ARD
	BTN3 --- ARD
	BTN4 --- ARD
    ARD --- ANALOG
	ARD --- BNO
end

PC[컴퓨터]

ARD -..-|"블루투스(입력 장치로 인식)"| PC

ESP32(ESP32 D1)라는 보드를 사용하면 내장 블루투스를 사용해 블루투스 키보드, 마우스 장치로 인식이 가능합니다.
(일반적인 아두이노 보드가 아니어서 어려울 수 있습니다.)
sko153
의견 출처, 관련 자료

 

자료

Arduino Nano

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

  • Arduino Nano pinout
Arduino Micro

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

  • Arduino Micro pinout
Arduino Pro Micro

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

  • Arduino Pro Micro pinout
BNO055

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

HC-05 블루투스 모듈

 

코드공유소

성황리에 폐쇄

 
 

회로 설계

 

부분 동작 시연용 회로

circuit_prototype_1
아두이노 프로 마이크로에 버튼, BNO055, 아날로그 스틱을 연결한 모습.

회로 참고용 영상

 

최종 회로

circuit_fritzing

 
 

프로그램 설계 및 개발

 

수신기 부분 동작 시연 코드

#include <Keyboard.h> #include <Mouse.h> const byte Pin_Mouse_Left = 7; const byte Pin_Mouse_Right = 8; const byte Pin_Reload = 9; const byte Pin_Recenter = 10; const byte Pin_Joybutton = 4; boolean state = true; void setup() { Serial.begin(9600); Keyboard.begin(); Mouse.begin(); pinMode(Pin_Mouse_Left, INPUT_PULLUP); pinMode(Pin_Mouse_Right, INPUT_PULLUP); pinMode(Pin_Reload, INPUT_PULLUP); pinMode(Pin_Recenter, INPUT_PULLUP); pinMode(Pin_Joybutton, INPUT_PULLUP); } void loop() { int sensorValueY = analogRead(A1); int sensorValueX = analogRead(A0); if (digitalRead(Pin_Mouse_Left) == LOW){ Mouse.press(MOUSE_LEFT); Serial.println("press mouse left"); } else { Mouse.release(MOUSE_LEFT); } if (digitalRead(Pin_Mouse_Right) == LOW){ Mouse.press(MOUSE_RIGHT); Serial.println("press mouse right"); } else { Mouse.release(MOUSE_RIGHT); } if (digitalRead(Pin_Reload) == LOW){ Keyboard.press('r'); Serial.println("press Reload"); } else { Keyboard.release('r'); } if (digitalRead(Pin_Recenter) == LOW){ Keyboard.press(KEY_BACKSPACE); Serial.println("press Recenter"); } else { Keyboard.release(KEY_BACKSPACE); } if (digitalRead(Pin_Joybutton) == LOW){ Keyboard.press('p'); Serial.println("press Joybutton"); } else { Keyboard.release('p'); } // Y+ if (sensorValueY < 300) { Keyboard.press('w'); } else { Keyboard.release('w'); } //Y- if (sensorValueY > 700) { Keyboard.press('s'); } else { Keyboard.release('s'); } //X if (sensorValueX < 300) { Keyboard.press('a'); } else { Keyboard.release('a'); } //X- if (sensorValueX > 700) { Keyboard.press('d'); } else { Keyboard.release('d'); } delay (20); }

수신기가 키보드 및 마우스로서 동작하는 모습을 체크하고 디버깅 하기 위한 코드.
버튼 및 아날로그 스틱을 수신기에 연결하여 독립적으로 테스트함.

 

센서 동작 시연 코드

#include <Wire.h> #include <Adafruit_Sensor.h> #include <Adafruit_BNO055.h> #include <utility/imumaths.h> // 마우스 제어용 헤더 #include <Mouse.h> #include <Keyboard.h> /* This driver reads raw data from the BNO055 Connections =========== Connect SCL to analog 5 Connect SDA to analog 4 Connect VDD to 3.3V DC Connect GROUND to common ground History ======= 2015/MAR/03 - First release (KTOWN) */ /* Set the delay between fresh samples */ #define BNO055_SAMPLERATE_DELAY_MS (10) // ===================== 코드에서 사용할 전역 변수 값들 ===== // 가속도의 무시를 위한 데드존 const float DEADZONE = 0.01f; // ======================================================== // Check I2C device address and correct line below (by default address is 0x29 or 0x28) // id, address Adafruit_BNO055 bno = Adafruit_BNO055(-1, 0x29); void setup(void) { Serial.begin(115200); Serial.println("Orientation Sensor Raw Data Test"); Serial.println(""); /* Initialise the sensor */ if(!bno.begin()) { /* There was a problem detecting the BNO055 ... check your connections */ Serial.print("Ooops, no BNO055 detected ... Check your wiring or I2C ADDR!"); while(1); } delay(1000); /* Display the current temperature */ int8_t temp = bno.getTemp(); Serial.print("Current Temperature: "); Serial.print(temp); Serial.println(" C"); Serial.println(""); bno.setExtCrystalUse(true); Serial.println("Calibration status values: 0=uncalibrated, 3=fully calibrated"); // 마우스 초기화 Mouse.begin(); } void loop(void) { // Possible vector values can be: // - VECTOR_ACCELEROMETER - m/s^2 // - VECTOR_MAGNETOMETER - uT // - VECTOR_GYROSCOPE - rad/s // - VECTOR_EULER - degrees // - VECTOR_LINEARACCEL - m/s^2 // - VECTOR_GRAVITY - m/s^2 imu::Vector<3> euler = bno.getVector(Adafruit_BNO055::VECTOR_EULER); /* Display calibration status for each sensor. */ uint8_t system, gyro, accel, mag = 0; bno.getCalibration(&system, &gyro, &accel, &mag); // 각속도 얻어오는 부분 imu::Vector<3> Gyro = bno.getVector(Adafruit_BNO055::VECTOR_GYROSCOPE); float gyroX = -Gyro.x(); float gyroY = Gyro.y(); float c = cosf(euler.z() / 180.0f * 3.141592f); float s = sinf(euler.z() / 180.0f * 3.141592f); float newGyroX = gyroX * c + gyroY * s; float newGyroY = gyroX * s * -1 + gyroY * c; // 각속도 테스트용 출력 부분 Serial.print("newGyroX:"); Serial.print(newGyroX); Serial.print(" newGyroY:"); Serial.print(newGyroY); Mouse.move(newGyroX, newGyroY, 0); delay(BNO055_SAMPLERATE_DELAY_MS); }

자이로 측정값과 오일러 각도를 이용하여, 장치의 기울기에 대응하여 마우스 커서가 적절한 방향으로 이동하게 만듦. 완벽하지는 않지만 상당히 높은 수준의 정확도를 보임.

햙쨕

두 시연 코드를 합쳐놓은 코드

 

최종 코드

수신기와 송신기 기능을 각각 구현한 최종 코드다. 송신기에는 센서와 각종 입력 핀들이 구성되어 있고, 수신기에는 간단하게 블루투스 모듈만 구성되어 있다.

 

transmitter.ino

//************************************************************************************* // G-gun 송신기용 코드 (총) //************************************************************************************* #include <Wire.h> //IMU 센서 제어용 헤더 #include <Adafruit_Sensor.h> #include <Adafruit_BNO055.h> #include <utility/imumaths.h> #include <math.h> // boolean 타입 사용을 위한 헤더 #include <stdbool.h> //폴링 딜레이 설정. #define BNO055_SAMPLERATE_DELAY_MS (1) #define STARTUP_DELAY_MS (1000) #define RAD_TO_DEG 57.295779513082320876798154814105 #define DEG_TO_RAD 0.01745329251994329576923690768489 //필요 #define FRAME_START 0xAA #define FRAME_END 0xDD #define FRAME_MAX_SIZE 13 //필요 typedef struct _data { byte pin; uint8_t joystick_x; uint8_t joystick_y; float mouse_x; float mouse_y; } DATA; // ===================== 코드에서 사용할 전역 변수 값들 ===== // 가속도의 무시를 위한 데드존 const float DEADZONE = 0.01f; bool bHold = false; // recenter 용도 const byte Pin_Joybutton = 4; // 전송될 핀들 const byte Pin_Mouse_Left = 7; const byte Pin_Mouse_Right = 8; const byte Pin_Reload = 9; const byte Pin_Jump = 10; const byte Pin_Use = A3; const byte Pin_Skill = A2; const byte Pin_Weapon = 5; const byte Pin_JoyUp = A0; const byte Pin_JoyDown = A1; const byte Pin_JoyLeft = 15; const byte Pin_JoyRight = 14; double filteredX, filteredY; // set alpha 0.0f to turn off ema filter const float alpha = 0.8f; // 인코딩 및 전송 관련 DATA data; byte buffer[FRAME_MAX_SIZE]; void write_bytes(byte *buffer, DATA *data) { buffer[0] = FRAME_START; buffer[1] = (byte)(data->pin); buffer[2] = (byte)(data->joystick_x); buffer[3] = (byte)(data->joystick_y); // mouse_x memcpy(buffer + 4, &data->mouse_x, 4); // *((float *)(buffer + 4)) = data->mouse_x; // mouse_y memcpy(buffer + 8, &data->mouse_y, 4); // *((float *)(buffer + 8)) = data->mouse_y; buffer[12] = FRAME_END; } // 핀 데이터를 읽어서 각 bit에 저장함 byte get_pin_data() { byte pin_data = 0; pin_data |= (byte)(digitalRead(Pin_Mouse_Left) == LOW) << 0; pin_data |= (byte)(digitalRead(Pin_Mouse_Right) == LOW) << 1; pin_data |= (byte)(digitalRead(Pin_Reload) == LOW) << 2; pin_data |= (byte)(digitalRead(Pin_Jump) == LOW) << 3; pin_data |= (byte)(digitalRead(Pin_Use) == LOW) << 4; pin_data |= (byte)(digitalRead(Pin_Skill) == LOW) << 5; pin_data |= (byte)(digitalRead(Pin_Weapon) == LOW) << 6; return pin_data; } // 조이스틱 핀 데이터를 읽어서 상태를 -1, 0, 1로 저장함. uint8_t get_joystick_data_x() { bool xPlus = digitalRead(Pin_JoyRight) == LOW; bool xMinus = digitalRead(Pin_JoyLeft) == LOW; uint8_t status; // will be translated to -1, 1, 0 if (xMinus) { status = 0; } else if (xPlus) { status = 2; } else { status = 1; } return status; } // 조이스틱 핀 데이터를 읽어서 상태를 -1, 0, 1로 저장함. uint8_t get_joystick_data_y() { bool yPlus = digitalRead(Pin_JoyDown) == LOW; bool yMinus = digitalRead(Pin_JoyUp) == LOW; uint8_t status; // will be translated to -1, 1, 0 if (yMinus) { status = 0; } else if (yPlus) { status = 2; } else { status = 1; } return status; } // ======================================================== // IMU 센서 I2C 주소 설정 (-1, 0x29) Adafruit_BNO055 bno = Adafruit_BNO055(-1, 0x29); /**************************************************************************/ /* Arduino setup function (automatically called at startup) */ /**************************************************************************/ void setup(void) { Serial.begin(9600); Serial1.begin(9600); //HC 05 블루투스 시리얼 Serial.println("Orientation Sensor Raw Data Test\n"); /* 센서 연결 */ if (!bno.begin()) { /* 센서가 연결되지 않을 경우 오류 메시지 출력 */ Serial.print("Ooops, no BNO055 detected ... Check your wiring or I2C ADDR!"); while (1) ; } bno.setExtCrystalUse(true); // 핀 설정 pinMode(A0, INPUT); //아날로그 핀을 디지털 핀으로 교체 pinMode(A1, INPUT); //아날로그 핀을 디지털 핀으로 교체 pinMode(A2, INPUT); //아날로그 핀을 디지털 핀으로 교체 pinMode(A3, INPUT); //아날로그 핀을 디지털 핀으로 교체 pinMode(Pin_Mouse_Left, INPUT_PULLUP); pinMode(Pin_Mouse_Right, INPUT_PULLUP); pinMode(Pin_Reload, INPUT_PULLUP); pinMode(Pin_Jump, INPUT_PULLUP); pinMode(Pin_Joybutton, INPUT_PULLUP); pinMode(Pin_JoyUp, INPUT_PULLUP); pinMode(Pin_JoyDown, INPUT_PULLUP); pinMode(Pin_JoyLeft, INPUT_PULLUP); pinMode(Pin_JoyRight, INPUT_PULLUP); pinMode(Pin_Use, INPUT_PULLUP); pinMode(Pin_Skill, INPUT_PULLUP); pinMode(Pin_Weapon, INPUT_PULLUP); delay(STARTUP_DELAY_MS); } void loop(void) { bHold = digitalRead(Pin_Joybutton) == LOW; // 조이스틱 중앙버튼 if (!bHold) // 리센터 버튼을 누를경우 마우스 움직임 처리를 무시함. { imu::Vector<3> eulerVector = bno.getVector(Adafruit_BNO055::VECTOR_EULER); imu::Vector<3> gyroVector = bno.getVector(Adafruit_BNO055::VECTOR_GYROSCOPE); /* Display calibration status for each sensor. */ uint8_t system, gyro, accel, mag = 0; bno.getCalibration(&system, &gyro, &accel, &mag); // z축 회전값으로 코사인 사인 함수값 계산 double c = cos(eulerVector.z() * DEG_TO_RAD); double s = sin(eulerVector.z() * DEG_TO_RAD); // DEADZONE 이상의 수치를 보여주는 각속도 값 읽어오기 double gyroX = fabs(gyroVector.x()) < DEADZONE ? 0 : -gyroVector.x(); double gyroY = fabs(gyroVector.y()) < DEADZONE ? 0 : gyroVector.y(); // 해당 각속도를 회전변환을 통해 중력과 평행하는 각속도로 변환 double moveX = gyroX * c + gyroY * s; double moveY = gyroX * -s + gyroY * c; //전역으로 안되어있길래 수정함 // 이동 평균 필터링으로 각속도를 수정 filteredX = filteredX * alpha + moveX * (1 - alpha); filteredY = filteredY * alpha + moveY * (1 - alpha); data.mouse_x = filteredX; data.mouse_y = filteredY; } else { // recenter 상태인 경우 0.0f을 전송 data.mouse_x = 0.0f; data.mouse_y = 0.0f; } data.pin = get_pin_data(); data.joystick_x = get_joystick_data_x(); data.joystick_y = get_joystick_data_y(); // data를 인코드해서 buffer에 저장함 write_bytes(buffer, &data); // buffer를 블루투스 시리얼로 전송함 Serial1.write(buffer, FRAME_MAX_SIZE); Serial1.flush(); // 디버그 용 시리얼 출력 // Serial.print(",mouse_x:"); // Serial.print(data.mouse_x); // Serial.print(",mouse_y:"); // Serial.println(data.mouse_y); // Serial.print("pin:"); // Serial.print(data.pin, BIN); // Serial.print(",joy_x:"); // Serial.print(data.joystick_x); // Serial.print(",joy_y:"); // Serial.println(data.joystick_y); delay(BNO055_SAMPLERATE_DELAY_MS); }

 

transmitter_final.ino

//************************************************************************************* // G-gun 송신기용 코드 (총) //************************************************************************************* #include <Wire.h> //IMU 센서 제어용 헤더 #include <Adafruit_Sensor.h> #include <Adafruit_BNO055.h> #include <utility/imumaths.h> #include <math.h> // boolean 타입 사용을 위한 헤더 #include <stdbool.h> //폴링 딜레이 설정. #define BNO055_SAMPLERATE_DELAY_MS (1) #define STARTUP_DELAY_MS (1000) #define RAD_TO_DEG 57.295779513082320876798154814105 #define DEG_TO_RAD 0.01745329251994329576923690768489 //필요 #define FRAME_START 0xAA #define FRAME_END 0xDD #define FRAME_MAX_SIZE 13 //필요 typedef struct _data { byte pin; uint8_t joystick_x; uint8_t joystick_y; float mouse_x; float mouse_y; } DATA; // ===================== 코드에서 사용할 전역 변수 값들 ===== // 가속도의 무시를 위한 데드존 const float DEADZONE = 0.01f; bool bHold = false; // recenter 용도 const byte Pin_Joybutton = 6; // 전송될 핀들 (7월 18일 핀배열 전부 새로함) const byte Pin_Mouse_Left = 16; const byte Pin_Mouse_Right = 14; const byte Pin_Reload = 15; const byte Pin_Jump = A0; const byte Pin_Use = 4; const byte Pin_Skill = A2; const byte Pin_Weapon = 5; const byte Pin_JoyUp = 8; const byte Pin_JoyDown = 7; const byte Pin_JoyLeft = 9; const byte Pin_JoyRight = 10; double filteredX, filteredY; const float alpha = 0.0f; // 인코딩 및 전송 관련 DATA data; byte buffer[FRAME_MAX_SIZE]; void write_bytes(byte *buffer, DATA *data) { buffer[0] = FRAME_START; buffer[1] = (byte)(data->pin); buffer[2] = (byte)(data->joystick_x); buffer[3] = (byte)(data->joystick_y); // mouse_x memcpy(buffer + 4, &data->mouse_x, 4); // *((float *)(buffer + 4)) = data->mouse_x; // mouse_y memcpy(buffer + 8, &data->mouse_y, 4); // *((float *)(buffer + 8)) = data->mouse_y; buffer[12] = FRAME_END; } // 핀 데이터를 읽어서 각 bit에 저장함 byte get_pin_data() { byte pin_data = 0; pin_data |= (byte)(digitalRead(Pin_Mouse_Left) == LOW) << 0; pin_data |= (byte)(digitalRead(Pin_Mouse_Right) == LOW) << 1; pin_data |= (byte)(digitalRead(Pin_Reload) == LOW) << 2; pin_data |= (byte)(digitalRead(Pin_Jump) == LOW) << 3; pin_data |= (byte)(digitalRead(Pin_Use) == LOW) << 4; pin_data |= (byte)(digitalRead(Pin_Skill) == LOW) << 5; pin_data |= (byte)(digitalRead(Pin_Weapon) == LOW) << 6; return pin_data; } // 조이스틱 핀 데이터를 읽어서 상태를 -1, 0, 1로 저장함. uint8_t get_joystick_data_x() { bool xPlus = digitalRead(Pin_JoyRight) == LOW; bool xMinus = digitalRead(Pin_JoyLeft) == LOW; uint8_t status; // will be translated to -1, 1, 0 if (xMinus) { status = 0; } else if (xPlus) { status = 2; } else { status = 1; } return status; } // 조이스틱 핀 데이터를 읽어서 상태를 -1, 0, 1로 저장함. uint8_t get_joystick_data_y() { bool yPlus = digitalRead(Pin_JoyDown) == LOW; bool yMinus = digitalRead(Pin_JoyUp) == LOW; uint8_t status; // will be translated to -1, 1, 0 if (yMinus) { status = 0; } else if (yPlus) { status = 2; } else { status = 1; } return status; } // ======================================================== // IMU 센서 I2C 주소 설정 (-1, 0x29) Adafruit_BNO055 bno = Adafruit_BNO055(-1, 0x28); //짭센서 0x29 정품센서 0x28 /**************************************************************************/ /* Arduino setup function (automatically called at startup) */ /**************************************************************************/ void setup(void) { Serial.begin(9600); Serial1.begin(9600); //HC 05 블루투스 시리얼 Serial.println("Orientation Sensor Raw Data Test\n"); /* 센서 연결 */ if (!bno.begin()) { /* 센서가 연결되지 않을 경우 오류 메시지 출력 */ Serial.print("Ooops, no BNO055 detected ... Check your wiring or I2C ADDR!"); while (1) ; } bno.setExtCrystalUse(true); // 핀 설정 pinMode(A0, INPUT); //아날로그 핀을 디지털 핀으로 교체 pinMode(A1, INPUT); //아날로그 핀을 디지털 핀으로 교체 pinMode(A2, INPUT); //아날로그 핀을 디지털 핀으로 교체 pinMode(A3, INPUT); //아날로그 핀을 디지털 핀으로 교체 pinMode(Pin_Mouse_Left, INPUT_PULLUP); pinMode(Pin_Mouse_Right, INPUT_PULLUP); pinMode(Pin_Reload, INPUT_PULLUP); pinMode(Pin_Jump, INPUT_PULLUP); pinMode(Pin_Joybutton, INPUT_PULLUP); pinMode(Pin_JoyUp, INPUT_PULLUP); pinMode(Pin_JoyDown, INPUT_PULLUP); pinMode(Pin_JoyLeft, INPUT_PULLUP); pinMode(Pin_JoyRight, INPUT_PULLUP); pinMode(Pin_Use, INPUT_PULLUP); pinMode(Pin_Skill, INPUT_PULLUP); pinMode(Pin_Weapon, INPUT_PULLUP); delay(STARTUP_DELAY_MS); } void loop(void) { bHold = digitalRead(Pin_Joybutton) == LOW; // 조이스틱 중앙버튼 if (!bHold) // 리센터 버튼을 누를경우 마우스 움직임 처리를 무시함. { imu::Vector<3> eulerVector = bno.getVector(Adafruit_BNO055::VECTOR_EULER); imu::Vector<3> gyroVector = bno.getVector(Adafruit_BNO055::VECTOR_GYROSCOPE); /* Display calibration status for each sensor. */ uint8_t system, gyro, accel, mag = 0; bno.getCalibration(&system, &gyro, &accel, &mag); // z축 회전값으로 코사인 사인 함수값 계산 double c = cosf(eulerVector.z() * DEG_TO_RAD); double s = sinf(eulerVector.z() * DEG_TO_RAD); // DEADZONE 이상의 수치를 보여주는 각속도 값 읽어오기 double gyroX = fabs(gyroVector.z()) < DEADZONE ? 0 : -gyroVector.z(); //정품센서와 짭센서가 X와 Z 오리엔테이션이 달라서 변경함. double gyroY = fabs(gyroVector.y()) < DEADZONE ? 0 : gyroVector.y(); // 해당 각속도를 회전변환을 통해 중력과 평행하는 각속도로 변환 double moveX = gyroX * c + gyroY * s; double moveY = gyroX * -s + gyroY * c; //전역으로 안되어있길래 수정함 // 이동 평균 필터링으로 각속도를 수정 // filteredX = filteredX * alpha + moveX * (1 - alpha); // filteredY = filteredY * alpha + moveY * (1 - alpha); data.mouse_x = moveX; data.mouse_y = moveY; } else { // recenter 상태인 경우 0.0f을 전송 data.mouse_x = 0.0f; data.mouse_y = 0.0f; } data.pin = get_pin_data(); data.joystick_x = get_joystick_data_x(); data.joystick_y = get_joystick_data_y(); // data를 인코드해서 buffer에 저장함 write_bytes(buffer, &data); // buffer를 블루투스 시리얼로 전송함 Serial1.write(buffer, FRAME_MAX_SIZE); Serial1.flush(); // 디버그 용 시리얼 출력 // Serial.print(",mouse_x:"); // Serial.print(data.mouse_x); // Serial.print(",mouse_y:"); // Serial.println(data.mouse_y); // Serial.print("pin:"); // Serial.print(data.pin, BIN); // Serial.print(",joy_x:"); // Serial.print(data.joystick_x); // Serial.print(",joy_y:"); // Serial.println(data.joystick_y); delay(BNO055_SAMPLERATE_DELAY_MS); }

입력 핀 배열 및 BNO055 센서 축 방향에 관해서 변경이 있었음. 정품 BNO055 센서를 이용했다면 마우스 변위 계산 부분에 이쪽 코드를 이용해야 할 것.

 

receiver.ino

//************************************************************************************* // G-gun 수신기용 코드 (동글) //************************************************************************************* #include <Wire.h> #include <Mouse.h> #include <stdbool.h> #include <Keyboard.h> #define FRAME_START 0xAA #define FRAME_END 0xDD #define FRAME_MAX_SIZE 13 bool mouse_left, mouse_right, reload, joybutton, jump, use, skill, weapon; short joystick_x, joystick_y; float mouse_x, mouse_y; const float MouseXScale = 0.3f; //마우스 감도 설정 const float MouseYScale = 0.2f; //마우스 감도 설정 byte buffer[FRAME_MAX_SIZE]; void setup() { Serial.begin(9600); Serial1.begin(9600); //HC 05 블루투스 시리얼 시작. Serial1.setTimeout(20); while (!Serial1) { } } void loop() { memset(buffer, 0, sizeof(buffer)); //FRAME을 한번에 읽는 버전 // 이후의 Serial을 Bluetooth를 이용하는 Serial로 바꿔주세요 if (Serial1.available() >= FRAME_MAX_SIZE) { Serial1.readBytes(buffer, 1); // Serial.print(buffer[0], HEX); // 프레임이 미리 지정된 FRAME_START로 시작하지 않으면 건너뜀 if (buffer[0] != FRAME_START) { // Serial.println(""); return; } Serial1.readBytes(buffer, FRAME_MAX_SIZE - 1); // for(int i=0; i< FRAME_MAX_SIZE - 1; i++) { // Serial.print(":"); // Serial.print(buffer[i], HEX); // } // Serial.println(""); // boolean values mouse_left = buffer[0] & 0x01; mouse_right = buffer[0] >> 1 & 0x01; reload = buffer[0] >> 2 & 0x01; jump = buffer[0] >> 3 & 0x01; use = buffer[0] >> 4 & 0x01; skill = buffer[0] >> 5 & 0x01; weapon = buffer[0] >> 6 & 0x01; // joystick values // -1 means negative, 0 means neutral, and 1 means positive joystick_x = (short)buffer[1] - 1; joystick_y = (short)buffer[2] - 1; // joystick_x = (signed short)(uint8_t)buffer[1] - 1; // joystick_y = (signed short)(uint8_t)buffer[2] - 1; // read mouse_x as float memcpy(&mouse_x, buffer + 3, 4); // read mouse_y as float memcpy(&mouse_y, buffer + 7, 4); // 역시 프레임이 FRAME_END로 끝나지 않으면 건너뜀 if (buffer[11] != FRAME_END) return; } else { return; } // 처리 if (mouse_left) //마우스 왼클릭 { Mouse.press(MOUSE_LEFT); // Serial.println("press mouse left"); } else { Mouse.release(MOUSE_LEFT); } if (mouse_right) //마우스 오른클릭 { Mouse.press(MOUSE_RIGHT); // Serial.println("press mouse right"); } else { Mouse.release(MOUSE_RIGHT); } if (reload) //리로드 버튼 R { Keyboard.press('r'); // Serial.println("press Reload"); } else { Keyboard.release('r'); } if (jump) { Keyboard.press(0x20); // Serial.println("press Jump"); } else { Keyboard.release(0x20); } if (use) { Keyboard.press('f'); // Serial.println("press use"); } else { Keyboard.release('f'); } if (skill) { Keyboard.press(KEY_CAPS_LOCK); // Serial.println("press skill"); } else { Keyboard.release(KEY_CAPS_LOCK); } if (weapon) { Keyboard.press(KEY_TAB); // Serial.println("press weapon"); } else { Keyboard.release(KEY_TAB); } // ============= 5방향 스틱 (중앙 버튼은 송신기에서 리센터 버튼으로 사용) ============ // X+ : right // X- : left // Y+ : down // Y- : up // Y- if (joystick_y == -1) { Keyboard.press('w'); } else { Keyboard.release('w'); } //Y+ if (joystick_y == 1) { Keyboard.press('s'); } else { Keyboard.release('s'); } //X- if (joystick_x == -1) { Keyboard.press('a'); } else { Keyboard.release('a'); } //X+ if (joystick_x == 1) { Keyboard.press('d'); } else { Keyboard.release('d'); } //마우스 움직임 Mouse.move(mouse_x * MouseXScale, mouse_y * MouseYScale, 0); }

 
 

프로토타입 제작 및 테스트

 

제작 과정

>>> 똘튜브 영상 희망 <<<

 

결과물

 
 

완성품 설계 및 제작

 

제작 과정

>>> 똘튜브 영상 희망 <<<

 

결과물

g-gun_final

G-건을 이용해서 Grand Theft Auto를 조작하고 있는 모습

 

Select a repo