April 18, 2024

디바이스마트 미디어:

[66호] 원하는 색상으로 제어가 가능한 아두이노 IoT 스마트 무드등 키트 -

2021-06-25

★2021 ICT 융합 프로젝트 공모전 결과 발표! -

2021-05-12

디바이스마트 국내 온라인 유통사 유일 벨로다인 라이다 공급! -

2021-02-16

★총 상금 500만원 /2021 ICT 융합 프로젝트 공모전★ -

2021-01-18

디바이스마트 온라인 매거진 전자책(PDF)이 무료! -

2020-09-29

[61호]음성으로 제어하는 간접등 만들기 -

2020-08-26

디바이스마트 자체제작 코딩키트 ‘코딩 도담도담’ 출시 -

2020-08-10

GGM AC모터 대량등록! -

2020-07-10

[60호]초소형 레이더 MDR, 어떻게 제어하고 활용하나 -

2020-06-30

[60호]NANO 33 IoT보드를 활용한 블루투스 수평계 만들기 -

2020-06-30

라즈베리파이3가 드디어 출시!!! (Now Raspberry Pi 3 is Coming!!) -

2016-02-29

MoonWalker Actuator 판매개시!! -

2015-08-27

디바이스마트 레이저가공, 밀링, 선반, 라우터 등 커스텀서비스 견적요청 방법 설명동영상 입니다. -

2015-06-09

디바이스마트와 인텔®이 함께하는 IoT 경진대회! -

2015-05-19

드디어 adafruit도 디바이스마트에서 쉽고 저렴하게 !! -

2015-03-25

[29호] Intel Edison Review -

2015-03-10

Pololu 공식 Distributor 디바이스마트, Pololu 상품 판매 개시!! -

2015-03-09

[칩센]블루투스 전 제품 10%가격할인!! -

2015-02-02

[Arduino]Uno(R3) 구입시 37종 센서키트 할인이벤트!! -

2015-02-02

[M.A.I]Ahram_ISP_V1.5 60개 한정수량 할인이벤트!! -

2015-02-02

[64호]레트로디세이(Retrodyssey)

64 ptb 레트로디세이 (555

Odyssey 미니PC로 만들어본 DIY 콘솔 게임장치

레트로디세이(Retrodyssey)

글 | 성균관대학교(IWMYS) 임혁수, 김현목, 허재호, 이형욱

 

1. 작품 개요
Odyssey x86으로 여러 가지를 개발하고 싶어 하는 개발자뿐만 아니라, 콘솔 게임에 대한 추억을 가지고 있는 일반인들도 함께 즐길 수 있도록 레트로디세이를 구성하였습니다. Window PC에서 개인이 좋아하는 온라인 게임(STEAM)을 콘솔로 플레이할 수 있으며, Window PC와 Arduino GPIO의 결합이라는 Odyssey x86의 장점을 확인할 수 있는 프로젝트입니다.
또한, 외관을 레트로 디자인으로 설계하여 과거에 대한 향수를 느끼며 추억하는 어른과, 뉴트로에 빠진 젊은이들의 흥미를 끌어 낼 수 있도록 디자인했습니다.

1.1. 관련 시장의 성장

아지는 현대인들을 위한, Window용 콘솔 장치의 시장성도 성장하리라 예측합니다. 따라서 Odyssey x86 미니 PC로 DIY 콘솔 게임 장치에 대한 사람들의 욕구를 자극하고자 합니다.
“한국콘텐츠진흥원이 발간한 2020 대한민국 게임백서에 따르면 2019년 국내 게임 시장규모는 15조 5,750억 원이다. 이 중 2019년 콘솔 게임 시장 매출은 6,946억 원을 기록했고 점유율은 4.5%다. 2018년 대비 매출액은 1,660억 원 늘었고, 성장률은 31.4%에 달했다. 어마어마한 성장세다.“(동아일보, 2020.12.23, 조광민)

64 ptb 레트로디세이 (2)

1.2. ODYSSEY x86 만의 장점 극대화
이 작품은 Window 운영체제와 아두이노를 내장한 오디세이(Odyssey) 보드로 제작되었습니다. 인터넷을 통해 스팀(Steam)을 비롯한 플랫폼에서 게임을 다운로드받아 플레이할 수 있으며, 조이스틱과 8개의 버튼을 개인별 취향에 맞게 설정할 수 있습니다. 이는 Window와 Arduino가 결합된, 오디세이 만의 장점을 극대화한 활용법입니다.

2. 작품 설명
이 작품은 1개의 조이스틱과 8개의 버튼으로 구성되어 있습니다. 조이스틱은 마우스와 키보드로 전환되어 사용될 수 있습니다. 8개의 버튼은 각각 키보드 버튼과 매핑(Mapping) 되어 있습니다. 제품과 함께 제공되는 GUI 프로그램을 이용해 매핑된 키보드 버튼을 바꿀 수 있습니다. 자신의 STEAM 최애 게임을 콘솔로 즐겨보세요!
(※ 최애 : 최고로 애정한다. 가장 사랑함)

2.1. 주요 동작 및 특징

64 ptb 레트로디세이 (1)

2.1.1. 레트로 감성의 디자인
어릴 적 오락실 게임기와 닌X도의 게임기를 떠올리게 하는 바로 그 디자인입니다. 게임을 즐기지 않더라도 인테리어 용품으로도 인기만점! 이제 하나씩 만드는 과정을 살펴보도록 하겠습니다!

64 ptb 레트로디세이 (2)

2.1.2. 조이스틱과 버튼을 키보드와 마우스로 이용
조이스틱을 A0와 A1 아날로그 입력으로 설정합니다. 조이스틱의 x축 y축의 가변저항이 기울어짐을 측정해, 아날로그값을 전달합니다. 이 데이터를 기반으로 마우스 움직임 (Mouse.move()) 또는, 키보드 방향키(←↑→↓)를 움직입니다.
또한, 키보드의 경우 (Keyboard.press())를 이용하여, 버튼이 입력되면 키의 입력을 설정합니다. 또한, count 변수를 설정해 키보드를 꾹 누를 때 발생하는 반복 입력의 경우도 대처합니다. 모든 경우가 키보드와 동일하게 작동합니다.

64 ptb 레트로디세이 (3)

2.1.3. 일반인을 위한 GUI를 이용한 키 매핑
게임의 경우 모든 키가 동일하지 않습니다. 그래서 저희 컨트롤러는 여러 게임에 호환하여 적용할 수 있도록 키를 설정합니다. 저희는 코드에 익숙하지 않은 사람들도 쉽게 사용할 수 있도록 Qt creator를 사용해 GUI 인터페이스를 만들었습니다.

64 ptb 레트로디세이 (5)

사용자는 GUI 환경에서 A부터 Z까지 25개의 알파벳과 ‘ESC’, ‘TAB’ 2개 특수키, 총 27개의 키를 게임기 버튼에 할당할 수 있습니다. 사용자는 슬라이드를 사용하거나 직접 해당하는 글자를 타이핑한 후(단, 이때 모든 글자는 대문자로 쓰여야 합니다.) ‘DEVICEMART’ 버튼을 클릭하면 해당 정보를 오디세이 x86으로 시리얼 통신을 통해 보낼 수 있는데 이를 통해 게임기의 키 셋업을 마칠 수 있습니다.

64 ptb 레트로디세이 (6)

2.2. 전체 시스템 구성

오디세이에서 Steam 게임을 구동합니다. 그리고 조이스틱과 버튼으로 키의 입력을 받습니다. 키를 변경하고 싶으면 qt 배포판 GUI를 실행하여 키를 커스텀합니다.

64 ptb 레트로디세이 (7)

2.3. 개발 환경(개발 언어, Tool, 사용 시스템 등)

아래는 사용한 부품목록입니다.

64 ptb 레트로디세이 (8)

2.3.1. Hardware(외관 설계)

Autodesk Inventor 2020, Autodesk Fusion 360을 사용했습니다. 하드웨어 설계를 위해 사용합니다.

64 ptb 레트로디세이 (9)

2.3.2. Hardware(Odyssey x86)

Arduino IDE 1.8.1 버전을 사용했습니다. 오디세이 x86으로 windows 운영체제에서 아두이노 IDE를 사용하기 위해서는 초기 세팅 과정을 거쳐야 합니다.
1단계) 파일 – 환경설정 – 추가적인 보드매니저 추가하기 아래 URL을 보드매니저에 추가해줍니다. https://files.seeedstudio.com/arduino/package_seeeduino_boards_index.json
2단계) 보드 매니저 추가하기. 툴 – 보드 – 보드매니저에서 “Seeeduino Zero” 설치
3단계) 보드와 포트를 Seeeduino Zero로 선택해주세요.

64 ptb 레트로디세이 (10)

2.3.3. GUI용 qt 프로그래밍 작성 – QT creator , C++

Qt creator는 GUI 프로그램을 개발할 때 많이 사용되는 프레임워크입니다. 저희는 Qt creator 5.15.2 버전을 사용했습니다. 사용자는 Qt creator를 설치하지 않더라도 저희가 만든 파일중 ‘Retrodyssey_성균관대(IWMYS)/소스코드/qt배포판’ 안에 있는 ‘retrodyssey’ 폴더(또는 압축파일)을 다운로드 받아 손쉽게 GUI 인터페이스를 사용할 수 있습니다.

  qt 배포판은 디바이스마트 블로그 해당 게시글 첨부자료에서 확인하실 수 있습니다.  

 

2.3.3.1 GUI 환경의 코드를 개인적으로 수정하고 싶은 경우
Qt creator는 https://www.qt.io/ 에서 다운로드 할 수 있습니다. Qt creator을 설치한다면 GUI를 위해 작성한 코드의 세부구조를 개인이 원하는 대로 수정하거나 기능을 추가할 수 있습니다. 아래는 Qt 공식 홈페이지에서 설치파일을 다운받는 과정입니다. 설치파일을 실행하는 경우, Qt creator의 버전만 5.15.2 버전으로 설정해주고 나머지는 디폴트 상태로 설정한 후 다운로드 받으면 됩니다.

64 ptb 레트로디세이 (12)

64 ptb 레트로디세이 (13)
작성한 코드는 ‘Retrodyssey_성균관대(IWMYS)/소스코드/qt’ 에 들어있는 ‘seedcontroller’ 폴더 안에 들어 있으며 해당 폴더(‘seedcontroller’)를 ‘로컬디스크/Users/사용자명/문서’로 옮기거나 해당 사용자의 qt 파일이 저장되는 경로로 옮기면 코드를 수정할 수 있습니다. 아래는 경로의 예시입니다.

64 ptb 레트로디세이 (14)

  qt 배포판은 디바이스마트 블로그 해당 게시글 첨부자료에서 확인하실 수 있습니다.  

2.3.3.2 배포한 GUI 환경을 그대로 사용하고 싶은 경우
만약 가볍게 구현해보고자 하는 사람이라면 저희가 배포한 배포 파일 ‘Retrodyssey_성균관대(IWMYS)/소스코드/qt배포판’ 안에 있는 ‘retrodyssey’ 폴더를 다운로드 받는다면 GUI 인터페이스를 사용할 수 있습니다. ‘retrodyssey’ 폴더 안에는 qt배포판 실행을 위한 여러 라이브러리가 들어 있는데 이들 중 ‘seedcontroller’을 실행하면, 게임 실행을 위한 키 설정을 할 수 있습니다.
게임기 키 설정을 위해서는 우선 오디세이 x86보드에 우선 아두이노 코드가 올라가 있어야 합니다. 아두이노 코드가 보드에 올라가 있다면, 다음으로 오디세이 x86보드와 통신하기 위해, USB 포트 번호를 설정해주어야 합니다. 배포판의 경우, ‘COM8’의 포트와 연결되도록 만들어져 있으므로 다음과 같은 과정을 거쳐 포트명을 ‘COM8’로 바꿔줘야 합니다.

1단계) 장치 관리자에서 아두이노와 연결된 포트를 찾아 선택해줍니다.
2단계) ‘포트 설정’에서 고급 탭을 선택합니다.
3단계) 포트명을 ‘COM8’로 바꿔줍니다.

64 ptb 레트로디세이 (15)

64 ptb 레트로디세이 (16)

그렇게 포트설정을 끝냈다면 이후 다운로드 받은 ‘retrodyssey’ 폴더 안의 ‘seedcontroller’ 파일을 실행해 원하는 키보드 정보를 입력하고 ‘DEVICEMART’버튼을 눌러주면 게임기 키 설정을 할 수 있습니다.

64 ptb 레트로디세이 (26)

3. 단계별 제작 과정
3.1. 3D 본체 설계
모델링을 함에 있어서 게임기의 모습을 부각시키기 위해 노력했습니다. 디자인적 측면에서 버튼과 조이스틱이 둥글다는 공통점을 가지고 있어, 버튼과 조이스틱이 차지하는 부분은 둥근 원으로 디자인하였습니다. 또한 게임기를 떠올릴 때, 강력하게 대비대는 빨간색과 파란색으로 색깔을 입혔으며 메인 프레임의 모든 색상은 이를 부각할 수 있는 하얀색으로 디자인했습니다. 수치와 설계 부분에 있어서는 Inventor를 이용하여 진행했습니다.
부품 구성은 메인 프레임 각각 좌, 우 부분과 조이스틱이 들어가는 부분과 고정시켜주는 부분 마지막으로 버튼 6개를 담을 버튼 프레임으로 구성 되어있습니다. 전체적인 모습은 다음과 같습니다.

64 ptb 레트로디세이 (18)

조이스틱과 버튼을 설치할 프레임들은 공통적으로 메인프레임에 안정적으로 거치될 수 있는 것을 목표로 했습니다. 그 결과 위아래의 지름을 다르게 하여, 걸칠 수 있게 설계하였습니다.
조이스틱을 담을 파트와 받침 부분은 조이스틱을 움직이는 과정에서 흔들림이 없이 고정되어야 하므로 나사 연결방식을 채택하였습니다. 이는 M1.2 나사를 사용하도록 설계하였고 고정하는 구멍은 총 4개로 설계되었습니다.
오디세이를 담을 메인프레임의 설계는 두 손을 올려 장시간 게임을 함에 있어서 무리가 가지 않도록 높이를 10cm로 설계하였습니다. 이는 버튼과 조이스틱 등을 오디세이와 연결할 때 많은 회로가 메인프레임 내부에서 엉키지 않게 하려는 부분도 고려한 수치입니다.
그보다도 가장 많이 신경을 쓴 부분은 단연 오디세이가 들어가는 부분입니다.
이 제품의 가장 중요한 것은 오디세이입니다. 오디세이가 어떤 식으로도 손상이 가면 안 되기 때문에, 오디세이를 단단하게 고정해줄 나사의 위치를 선정하는 것부터, 발열 해소를 위해 팬이 들어가는 공간을 특히나 신경 써 설계하였습니다. 발열 해소를 하지 못한다면, 기능의 저하와 손상으로 직결되는 부분이니 반드시 고려 해야 했습니다.
그 결과 팬이 바닥과 충분한 거리로 떨어져 있고, 바닥엔 직사각형으로 여러 구멍을 뚫어 설계를 진행하였습니다. 위의 설명의 설계 이미지는 다음과 같습니다.

64 ptb 레트로디세이 (19)

또한, 메인 윗 파트와 연결하는 부분을 원 지름을 가로질러 설계를 하였는데 이는 M3나사를 사용하도록 설계했습니다. 추가로 메인 프레임에서 HDMI 케이블이나 다른 여러 선들을 연결하기 위한 공간을 설계한 것 또한 볼 수 있습니다. 자세한 사진은 다음과 같습니다.

64 ptb 레트로디세이 (20)

최종 설계 부품들은 다음과 같습니다.

64 ptb 레트로디세이 (21)

 

64 ptb 레트로디세이 (22)

그리고 추가로 Odyssey x86 전용 게임기라는 것을 나타내기 위해, 디바이스마트와 seeed의 로고를 삽입하였습니다. 아래 별표 분에 꽂아주면 됩니다.

64 ptb 레트로디세이 (23)

그리고 조이스틱 부분도 설계하였습니다. 2가지 모델로 설계하였으나, 일반 조이스틱 모듈을 사용하는 경우, 조이스틱은 추가하지 않는 게 좋습니다. 지렛대의 원리로 인해 힘점이 작용점보다 멀어지게 되어, 조이스틱 특유의 탄성을 느끼기 힘들어집니다. 저희 팀이 시연할 때도 제거하고 플레이하였습니다. 좌측 조이스틱은 M3 규격의 나사로 조립하면 됩니다.

64 ptb 레트로디세이 (3)

3.2. 조이스틱 및 버튼 회로 연결
조이스틱 모듈과 버튼을 오디세이의 GPIO에 연결하여 줍니다. 이때, 버튼은 풀다운 저항을 사용하여, 값이 튀지 않도록 해줍니다. 내부 연결 회로를 제작한 후, 가조립하여 작동여부를 확인합니다. 먼저 아래 회로도에 따라 연결합니다.

64 ptb 레트로디세이 (24)

이후 가조립하여 테스트합니다.

64 ptb 레트로디세이 (4)

아래는 각종 부품 연결상태를 확대한 모습입니다.

64 ptb 레트로디세이 (5)

3.3. 오디세이 x86 코드 작성
우리는 Odyssey의 동작에서 크게 3가지 부분을 고려해야 합니다.
1. qt와의 시리얼통신
2. 조이스틱 조작
3. 버튼 동작

해당 코드와 함께 살펴보겠습니다. 코드는 soo_odyssey.ino와 soo_odyssey_h의 헤더파일로 구성됩니다. 헤더파일에는 주로 하기 기능 구현을 위해 제작한 함수들이 담겨있습니다. 큰 흐름은 soo_odyssey.ino를 보며 확인하시면 됩니다.

3.3.1. [soo_retrodyssey.ino]
키보드 버튼에 연결된 GPIO를 저장합니다. 저는 D2~D9가 버튼으로 동작하고 있습니다. 그리고 key라는 배열에 우리가 버튼을 눌렀을 때, 입력될 키를 보관합니다. Received 배열은 qt와의 시리얼 통신으로 받아올 데이터를 저장합니다. qt는 바꿀 버튼의 ID 1개와, 해당 버튼에 매핑될 ASCII 데이터 2개를 송신합니다.

//PART0. 변수 선언
#include “Keyboard.h”
#include “Mouse.h”
#include “soo_retrodyssey.h”

// 키보드 GPIO 세팅
const int button1 = 2; //노1
const int button2 = 3; //노2
const int button3 = 4; //노3
const int button4 = 5; //핑1
const int button5 = 6; //핑2
const int button6 = 7; //핑3
const int button7 = 8; //검1
const int button8 = 9; //검2

// 버튼의 중복입력을 방지하는 버퍼로 쓰일 배열입니다.
int button1_state[2] = {0,0};
int button2_state[2] = {0,0};
int button3_state[2] = {0,0};
int button4_state[2] = {0,0};
int button5_state[2] = {0,0};
int button6_state[2] = {0,0};
int button7_state[2] = {0,0};
int button8_state[2] = {0,0};

// ASCII 번호로 누를 키의 정보를 입력합니다. 8개의 버튼에 해당합니다.
int key[8] = {97, 98, 99, 100, 101, 102, 1, 2};//기본세팅 a,b,c,d,e,f,TAB,ESC
int received[24] = {0,};

아래는 마우스 컨트롤을 위한 변수 선언입니다. 여기서 joystick_judger는 조이스틱을 방향키로 사용할지 마우스로 사용할지 선택할 수 있게 해줍니다. joystick_judger에는 D13에 연결된 스위치의 값이 저장될 겁니다.

// 마우스세팅
const int xAxis = A0; // x축 기울기를 읽습니다.
const int yAxis = A1; // y축 기울기를 읽습니다.
// 조이스틱을 마우스로 사용할지, 키보드로 사용할지 판단하는 GPIO입니다.
const int joystick_judger = 13;
bool mouse_or_arrow = false; //true: 마우스, false:화살표
//const int mouseButton = 12; //조이스틱의 버튼을 사용하실 분은 주석을 해제하면 됩니다. 저는 사용하지 않았습니다.

간단하게 셋업을 진행합니다.

void setup() {
//디지털핀을 세팅합니다.
pinMode(button1, INPUT);
pinMode(button2, INPUT);
pinMode(button3, INPUT);
pinMode(button4, INPUT);
pinMode(button5, INPUT);
pinMode(button6, INPUT);
pinMode(button7, INPUT);
pinMode(button8, INPUT);
pinMode(joystick_judger, INPUT); //마우스 혹은 화살표

//시리얼과 마우스 키보드를 사용합니다.
Serial.begin(9600);
Mouse.begin();
Keyboard.begin();

//시리얼 통신을 시작합니다.
Serial.begin(9600);
Serial.println(“setup finished”);
}

메인 코드의 시작입니다. qt로부터 수신한 시리얼 데이터를 received 배열에 저장합니다. qt는 키매핑에 대한 데이터(바꿀 키 ID 1개와 매핑할 데이터 2개)를 시리얼로 전송합니다.

void loop() {
//PART1. 시리얼 통신
//Qt로부터 시리얼 데이터를 수신합니다.
int i =0;
while(Serial.available()> 0){
received[i] = Serial.read();
i++;
}

이제 키보드를 다룹니다. 첫번째, select_key 함수는 qt에서 받은 데이터를 저장한 received 배열의 데이터를 해석해, key 배열에 저장해 매핑합니다. 두번째, Key_press_once 함수는 중복 키 입력을 방지하는 코드입니다. 이 코드로 인해, 버튼을 꾹 누르면 키보드를 꾹 눌렀을 때와 동일하게 버튼이 작동합니다.

//PART2. 키보드
//시리얼 데이터를 바탕으로, 키를 매핑하는 함수입니다.
select_key(received, key);
//
//키를 누릅니다.
key_press_once(button1, button1_state, key[0]);//노1
key_press_once(button2, button2_state, key[1]);//노2
key_press_once(button3, button3_state, key[2]);//노3
key_press_once(button4, button4_state, key[3]);//핑1
key_press_once(button5, button5_state, key[4]);//핑2
key_press_once(button6, button6_state, key[5]);//핑3
key_press_once(button7, button7_state, key[6]);//검1
key_press_once(button8, button8_state, key[7]);//검2

조이스틱의 동작에 관한 코드입니다. joystick_judger로부터 조이스틱을 마우스로 사용할지, 방향키로 사용할지, 판단합니다. arrow_key는 마우스를 키보드 방향키로 사용하기 위해, 제작한 함수입니다. 추가로 딜레이는 조금만 주는 편이 좋습니다. 딜레이를 길게 주면, 키 입력이 무시될 가능성이 큽니다.

//PART2. 조이스틱
//조이스틱의 동작여부를 판단합니다.
//D13번 키가 high면 조이스틱이 마우스로 동작, low면 화살표로 동작
if (digitalRead(joystick_judger) == LOW) mouse_or_arrow = false;
if (digitalRead(joystick_judger) == HIGH) mouse_or_arrow = true;

//조이스틱으로 마우스 혹은 키보드 화살표를 제어합니다.
if (mouse_or_arrow) Mouse.move((int)readAxis(A0), (int)-readAxis(A1), 0);
if (!mouse_or_arrow) arrow_key((int)readAxis(A0), (int)-readAxis(A1));

//조이스틱의 버튼을 클릭하는 함수입니다. 사용하지는 않지만 만들어봤습니다.
//mouse_press(mouseButton);

//혹시 눌려져 있는 버튼이 있다면 종료합니다.
Keyboard.releaseAll();
delay(5);
}

3.3.2. [soo_retrodyssey.h]
조이스틱과 버튼 제어를 위해 직접 제작한 함수입니다. 코드는 살펴보셔도 좋고, 아니면 그 기능만 살펴보셔도 좋습니다. 헤더파일의 함수 기능을 요약해 보았습니다.

64 ptb 레트로디세이 (25)

 

3.4. GUI용 qt 프로그래밍 작성

qt creator를 사용하기 위해 총 10개의 함수를 사용하였습니다. 게임기 셋업을 할 때, 슬라이드를 사용하기 위해 8개의 버튼에 해당하는 8개의 ‘on_slidernumber_valueChanged()’를 만들었으며, 슬라이드를 사용하거나 타이핑을 통해 입력한 키보드정보(A ~ Z, 25개의 알파벳과 ESC, TAB 2개의 특수키)를 읽어 아스키코드로 바꿔주는 ‘on_pushButton_clicker()’ 함수와 오디세이x86 보드와 연결된 상태에서 값을 전송해주는 ‘updateButton()’ 함수를 만들었습니다.

64 ptb 레트로디세이 (26)

이후 원하는 키보드를 설정하기 위해 UI환경에서 게임기의 자판 배열에 맞춰 왼쪽의 목록에서 ‘Line Edit’ 위젯과 ‘Vertical Scroll Bar’ 위젯을 사용해 입력 인터페이스를 만듭니다. 이후 키보드 값을 한번에 시리얼 통신으로 전송하기 위해 ‘Push Button’ 버튼을 사용합니다.

3.5. 최종 조립
1단계) 밑판 위에 오디세이 보드를 고정시켜줍니다.

64 ptb 레트로디세이 (6)

2단계) 회로도에 맞게, 오디세이와 조이스틱 모듈 버튼을 연결합니다.

64 ptb 레트로디세이 (27)

3단계) 밑판의 나사선에 맞춰 윗판을 올린 후 M3 나사로 고정시킵니다.

64 ptb 레트로디세이 (7)

4단계) 원판(붉은색, 파란색)을 삽입하고, 로고(디바이스마트와 seeed 로고)도 끼워줍니다.

64 ptb 레트로디세이 (28)

64 ptb 레트로디세이 (8)

5단계) 완성된 게임기의 모습입니다.

64 ptb 레트로디세이 (29) 64 ptb 레트로디세이 (30)

그래서 이게 진짜, 되는 거야? 라고 하시는 분들을 위해 작동영상도 준비해봤습니다. 작품 시연은 아래 QR코드로 확인해주세요.

시연 영상 보러가기

64 ptb 레트로디세이 (31)

64 ptb 레트로디세이 (32)

 

4. 기타
4.1. 소스코드
4.1.1. odyssey x86 코드
soo_retrodyssey.ino

//PART0. 변수 선언
#include “Keyboard.h”
#include “Mouse.h”
#include “soo_retrodyssey.h”

// 키보드 GPIO 세팅
const int button1 = 2; //노1
const int button2 = 3; //노2
const int button3 = 4; //노3
const int button4 = 5; //핑1
const int button5 = 6; //핑2
const int button6 = 7; //핑3
const int button7 = 8; //검1
const int button8 = 9; //검2

// 버튼의 중복입력을 방지하는 버퍼로 쓰일 배열입니다.
int button1_state[2] = {0,0};
int button2_state[2] = {0,0};
int button3_state[2] = {0,0};
int button4_state[2] = {0,0};
int button5_state[2] = {0,0};
int button6_state[2] = {0,0};
int button7_state[2] = {0,0};
int button8_state[2] = {0,0};

// ASCII 번호로 누를 키의 정보를 입력합니다. 8개의 버튼에 해당합니다.
int key[8] = {97, 98, 99, 100, 101, 102, 1, 2};//기본세팅 a,b,c,d,e,f,TAB,ESC
int received[24] = {0,};

// 마우스세팅
const int xAxis = A0; // x축 기울기를 읽습니다.
const int yAxis = A1; // y축 기울기를 읽습니다.
// 조이스틱을 마우스로 사용할지, 키보드로 사용할지 판단하는 GPIO입니다.
const int joystick_judger = 13;
bool mouse_or_arrow = false; //true: 마우스, false:화살표
//const int mouseButton = 12; //조이스틱의 버튼을 사용하실 분은 주석을 해제하면 됩니다. 저는 사용하지 않았습니다.
void setup() {
//디지털핀을 세팅합니다.
pinMode(button1, INPUT);
pinMode(button2, INPUT);
pinMode(button3, INPUT);
pinMode(button4, INPUT);
pinMode(button5, INPUT);
pinMode(button6, INPUT);
pinMode(button7, INPUT);
pinMode(button8, INPUT);
pinMode(joystick_judger, INPUT); //마우스 혹은 화살표

//시리얼과 마우스 키보드를 사용합니다.
Serial.begin(9600);
Mouse.begin();
Keyboard.begin();
//시리얼 통신을 시작합니다.
Serial.begin(9600);
Serial.println(“setup finished”);
}

void loop() {
//PART1. 시리얼 통신
//Qt로부터 시리얼 데이터를 수신합니다.
int i =0;
while(Serial.available()> 0){
received[i] = Serial.read();
i++;
}

//PART2. 키보드
//시리얼 데이터를 바탕으로, 키를 매핑하는 함수입니다.
select_key(received, key);
//
//키를 누릅니다.
key_press_once(button1, button1_state, key[0]);//노1
key_press_once(button2, button2_state, key[1]);//노2
key_press_once(button3, button3_state, key[2]);//노3
key_press_once(button4, button4_state, key[3]);//핑1
key_press_once(button5, button5_state, key[4]);//핑2
key_press_once(button6, button6_state, key[5]);//핑3
key_press_once(button7, button7_state, key[6]);//검1
key_press_once(button8, button8_state, key[7]);//검2
//PART2. 조이스틱
//조이스틱의 동작여부를 판단합니다.
//D13번 키가 high면 조이스틱이 마우스로 동작, low면 화살표로 동작
if (digitalRead(joystick_judger) == LOW) mouse_or_arrow = false;
if (digitalRead(joystick_judger) == HIGH) mouse_or_arrow = true;

//조이스틱으로 마우스 혹은 키보드 화살표를 제어합니다.
if (mouse_or_arrow) Mouse.move((int)readAxis(A0), (int)-readAxis(A1), 0);
if (!mouse_or_arrow) arrow_key((int)readAxis(A0), (int)-readAxis(A1));

//조이스틱의 버튼을 클릭하는 함수입니다. 사용하지는 않지만 만들어봤습니다.
//mouse_press(mouseButton);

//혹시 눌려져 있는 버튼이 있다면 종료합니다.
Keyboard.releaseAll();
delay(5);
}

soo_retrodyssey.h

// 중복키 설정입니다.
#define FIRST_KEY_DELAY 5 //첫 키입력후, 둘째 키 입력까지 기다리는 정도입니다.
#define MAX_TOGGLE 8 //중복입력 속도를 조절합니다.

//Qt에서 수신한 내용을 ASCII로 저장합니다.
int special_key(int a, int b){
if((a==50) &&(b==54)){
return 1;
}
if((a==50) &&(b==55)){
return 2;
}
return a+49;
}

//Function for 시리얼통신
void select_key(int button_call[], int button_data[]){
//키매핑하는 함수입니다.
for(int i=0;i<24;i++){
switch(button_call[i]-96){
case 1: button_data[6]=special_key(button_call[i+1],button_call[i+2]); break; //검1
case 2: button_data[7]=special_key(button_call[i+1],button_call[i+2]); break; //검2
case 3: button_data[3]=special_key(button_call[i+1],button_call[i+2]); break; //핑1
case 4: button_data[4]=special_key(button_call[i+1],button_call[i+2]); break; //핑2
case 5: button_data[5]=special_key(button_call[i+1],button_call[i+2]); break; //핑3
case 6: button_data[0]=special_key(button_call[i+1],button_call[i+2]); break; //노1
case 7: button_data[1]=special_key(button_call[i+1],button_call[i+2]); break; //노2
case 8: button_data[2]=special_key(button_call[i+1],button_call[i+2]); break; //노3
}
}
}

//조이스틱의 기울기를 읽고 각 축별 -1, 0 ,1을 반환합니다..
int readAxis(int thisAxis) {
int reading = analogRead(thisAxis);
int range = 8;
reading = map(reading, 0, 1023, 0, range);
int distance = reading – (range / 2);

if (abs(distance) < (range / 2)) {
distance = 0;
}
return distance;
}

//조이스틱으로 화살표를 동작합니다.
void arrow_key( int x_line, int y_line){
if (x_line > 0) Keyboard.press(KEY_RIGHT_ARROW);
if (x_line < 0) Keyboard.press(KEY_LEFT_ARROW);
if (y_line < 0) Keyboard.press(KEY_UP_ARROW);
if (y_line > 0) Keyboard.press(KEY_DOWN_ARROW);
}

//조이스틱으로 마우스 클릭을 하는 함수입니다. 저는 사용하지 않았습니다.
void mouse_press(int mouse_button){
if (digitalRead(mouse_button) == HIGH) {
if (!Mouse.isPressed(MOUSE_LEFT)) Mouse.press(MOUSE_LEFT);
}
else {
if (Mouse.isPressed(MOUSE_LEFT)) Mouse.release(MOUSE_LEFT);
}
}

//키보드를 누르는 코드입니다.
//버튼이 꾹 눌릴경우에 대한, 중복키 설정을 하는 코드입니다.
void key_press_once(const int key_name, int pre_state[], char data){
if (*pre_state > MAX_TOGGLE){
*pre_state = 0;
}
if (digitalRead(key_name) == LOW){
*pre_state = 0;
pre_state[1] = 0;
}
if ((digitalRead(key_name) == HIGH) &&(*pre_state == 0)){
if ((pre_state[1] == 0) || (pre_state[1] > FIRST_KEY_DELAY )){
if(data==1){
Keyboard.press(KEY_TAB);
}
else if(data==2){
Keyboard.press(KEY_ESC);
}
else{
Keyboard.press(data);
}
}
*pre_state = 1;
pre_state[1] += 1;
}
if ((digitalRead(key_name) == HIGH) &&(*pre_state != 0)){
*pre_state = *pre_state +1;
}
}

4.1.2. Qt creator 코드

seedcontroller.pro

QT += core gui serialport // 아두이노 연결
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
CONFIG += c++11
SOURCES += \
main.cpp \
dialog.cpp
HEADERS += \
dialog.h
FORMS += \
dialog.ui
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target

dialog.h

#ifndef DIALOG_H
#define DIALOG_H
#include <QDialog>
#include <QSerialPort> // 시리얼 통신을 위한 라이브러리
#include <QString> // QString 자료형 사용을 위한 라이브러리

QT_BEGIN_NAMESPACE
namespace Ui { class Dialog; }
QT_END_NAMESPACE

class Dialog : public QDialog
{
Q_OBJECT
public:
Dialog(QWidget *parent = nullptr);
~Dialog();
private slots:
void updateButton(QString); //시리얼 통신을 통해 아두이노로 값 전달
void on_pushButton_clicked(); //키보드 정보를 유니코드 값으로 변환
void on_slider1_valueChanged(int value); //게임기에 키보드 정보 할당
void on_slider2_valueChanged(int value);
void on_slider3_valueChanged(int value);
void on_slider4_valueChanged(int value);
void on_slider5_valueChanged(int value);
void on_slider6_valueChanged(int value);
void on_slider7_valueChanged(int value);
void on_slider8_valueChanged(int value);

private:
Ui::Dialog *ui; // UI 사용을 위한 객체 생성
QSerialPort *arduino; // 시리얼 포트 연결을 위한 객체 생성
QString arduino_port_name; // 아두이노 포트 정보를 저장할 변수
bool arduino_is_available; // 아두이노가 연결되었는지 확인하는 변수
};
#endif // DIALOG_H

dialog.cpp

#include “dialog.h”
#include “ui_dialog.h”
#include <QSerialPort>
#include <QSerialPortInfo>
#include <QDebug>
#include <QtWidgets>

Dialog::Dialog(QWidget *parent)
: QDialog(parent)
, ui(new Ui::Dialog)
{
ui->setupUi(this);
arduino_is_available = true;
arduino_port_name = “COM5″; // 포트 지정
arduino = new QSerialPort;
if(arduino_is_available)
{
// 아두이노와 시리얼 포트 연결을 위한 설정
arduino->setPortName(arduino_port_name);
arduino->open(QSerialPort::WriteOnly);
arduino->setBaudRate(QSerialPort::Baud9600);
arduino->setDataBits(QSerialPort::Data8);
arduino->setParity(QSerialPort::NoParity);
arduino->setStopBits(QSerialPort::OneStop);
arduino->setFlowControl(QSerialPort::NoFlowControl);
}
else
{
// 에러 확인
QMessageBox::warning(this, “Port error”, “Cannot connect to Arduino!!!”);
}
}

Dialog::~Dialog()
{
if (arduino->isOpen())
{
arduino->close();
}
delete ui;
}

void Dialog::updateButton(QString command)
{
if(arduino->isWritable())
{
arduino->write(command.toStdString().c_str());
}
else
{
qDebug() << “Cannot write to serial!”;
}
}

void Dialog::on_pushButton_clicked()
{
//버튼에 적힌 값을 읽은 후, 그 값이 TAB 이면 26, ESC 이면 27,
//나머지 키보드(알파벳)이면 0 ~ 25의 값을 아두이노로 보낸다.
QString one = ui->value_label1->text();
if(one == “TAB”)
{
int a = 91;
Dialog::updateButton(QString(“a%1″).arg(a-65));
}
else if(one == “ESC”)
{
int a = 92;
Dialog::updateButton(QString(“a%1″).arg(a-65));
}
else
{
for (int i=0; i < one.length(); i++)
{
int one1 = one.at(i).unicode();
Dialog::updateButton(QString(“a%1″).arg(one1-65));
}
}

QString two = ui->value_label2->text();
if(two == “TAB”)
{
int b = 91;
Dialog::updateButton(QString(“b%1″).arg(b-65));
}
else if(two == “ESC”)
{
int b = 92;
Dialog::updateButton(QString(“b%1″).arg(b-65));
}
else
{
for (int i=0; i < two.length(); i++)
{
int two1 = two.at(i).unicode();
Dialog::updateButton(QString(“b%1″).arg(two1-65));
}
}
QString three = ui->value_label3->text();
if(three == “TAB”)
{
int c = 91;
Dialog::updateButton(QString(“c%1″).arg(c-65));
}
else if(three == “ESC”)
{
int c = 92;
Dialog::updateButton(QString(“c%1″).arg(c-65));
}
else
{
for (int i=0; i < three.length(); i++)
{
int three1 = three.at(i).unicode();
Dialog::updateButton(QString(“c%1″).arg(three1-65));
}
}

QString four = ui->value_label4->text();
if(four == “TAB”)
{
int d = 91;
Dialog::updateButton(QString(“d%1″).arg(d-65));
}
else if(four == “ESC”)
{
int d = 92;
Dialog::updateButton(QString(“d%1″).arg(d-65));
}
else
{
for (int i=0; i < four.length(); i++)
{
int four1 = four.at(i).unicode();
Dialog::updateButton(QString(“d%1″).arg(four1-65));
}
}

QString five = ui->value_label5->text();
if(five == “TAB”)
{
int e = 91;
Dialog::updateButton(QString(“e%1″).arg(e-65));
}
else if(five == “ESC”)
{
int e = 92;
Dialog::updateButton(QString(“e%1″).arg(e-65));
}
else
{
for (int i=0; i < five.length(); i++)
{
int five1 = five.at(i).unicode();
Dialog::updateButton(QString(“e%1″).arg(five1-65));
}
}

QString six = ui->value_label6->text();
if(six == “TAB”)
{
int f = 91;
Dialog::updateButton(QString(“f%1″).arg(f-65));
}
else if(six == “ESC”)
{
int f = 92;
Dialog::updateButton(QString(“f%1″).arg(f-65));
}
else
{
for (int i=0; i < six.length(); i++)
{
int six1 = six.at(i).unicode();
Dialog::updateButton(QString(“f%1″).arg(six1-65));
}
}
QString seven = ui->value_label7->text();
if(seven == “TAB”)
{
int g = 91;
Dialog::updateButton(QString(“g%1″).arg(g-65));
}
else if(seven == “ESC”)
{
int g = 92;
Dialog::updateButton(QString(“g%1″).arg(g-65));
}
else
{
for (int i=0; i < seven.length(); i++)
{
int seven1 = seven.at(i).unicode();
Dialog::updateButton(QString(“g%1″).arg(seven1-65));
}
}

QString eight = ui->value_label8->text();
if(eight == “TAB”)
{
int h = 91;
Dialog::updateButton(QString(“h%1″).arg(h-65));
}
else if(eight == “ESC”)
{
int h = 92;
Dialog::updateButton(QString(“h%1″).arg(h-65));
}
else
{
for (int i=0; i < eight.length(); i++)
{
int eight1 = eight.at(i).unicode();
Dialog::updateButton(QString(“h%1″).arg(eight1-65));
}
}
}

//슬라이드를 움직인 정도(0 ~ 27)를, A ~ Z, TAB, ESC 로 바꿔 QLabelEdit 출력
void Dialog::on_slider1_valueChanged(int value)
{
int nvalue = value+65;
if (nvalue == 91)
{
ui->value_label1->setText(“TAB”);
}
else if (nvalue == 92)
{
ui->value_label1->setText(“ESC”);
}
else
{
ui->value_label1->setText(QString(value+65));
qDebug() << value;
}
}
void Dialog::on_slider2_valueChanged(int value)
{
int nvalue = value+65;
if (nvalue == 91)
{
ui->value_label2->setText(“TAB”);
}
else if (nvalue == 92)
{
ui->value_label2->setText(“ESC”);
}
else
{
ui->value_label2->setText(QString(value+65));
qDebug() << value;
}
}
void Dialog::on_slider3_valueChanged(int value)
{
int nvalue = value+65;
if (nvalue == 91)
{
ui->value_label3->setText(“TAB”);
}
else if (nvalue == 92)
{
ui->value_label3->setText(“ESC”);
}
else
{
ui->value_label3->setText(QString(value+65));
qDebug() << value;
}
}
void Dialog::on_slider4_valueChanged(int value)
{
int nvalue = value+65;
if (nvalue == 91)
{
ui->value_label4->setText(“TAB”);
}
else if (nvalue == 92)
{
ui->value_label4->setText(“ESC”);
}
else
{
ui->value_label4->setText(QString(value+65));
qDebug() << value;
}
}
void Dialog::on_slider5_valueChanged(int value)
{
int nvalue = value+65;
if (nvalue == 91)
{
ui->value_label5->se tText(“TAB”);
}
else if (nvalue == 92)
{
ui->value_label5->setText(“ESC”);
}
else
{
ui->value_label5->setText(QString(value+65));
qDebug() << value;
}
}
void Dialog::on_slider6_valueChanged(int value)
{
int nvalue = value+65;
if (nvalue == 91)
{
ui->value_label6->setText(“TAB”);
}
else if (nvalue == 92)
{
ui->value_label6->setText(“ESC”);
}
else
{
ui->value_label6->setText(QString(value+65));
qDebug() << value;
}
}
void Dialog::on_slider7_valueChanged(int value)
{
int nvalue = value+65;
if (nvalue == 91)
{
ui->value_label7->setText(“TAB”);
}
else if (nvalue == 92)
{
ui->value_label7->setText(“ESC”);
}
else
{
ui->value_label7->setText(QString(value+65));
qDebug() << value;
}
}
void Dialog::on_slider8_valueChanged(int value)
{
int nvalue = value+65;
if (nvalue == 91)
{
ui->value_label8->setText(“TAB”);
}
else if (nvalue == 92)
{
ui->value_label8->setText(“ESC”);
}
else
{
ui->value_label8->setText(QString(value+65));
qDebug() << value;
}
}

main.cpp

#include “dialog.h”
#include <QApplication>
#include <QPixmap>

int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Dialog w;
w.setFixedSize(750, 450);
w.setWindowTitle(“DEVICEMART && SEED GAME MACHINE SETTING”);
w.show();
return a.exec();
}

4.2. 참고문헌

https://www.donga.com/news/It/article/all/20201223/104607777/1

 

64 ptb 레트로디세이 (33)

 

 

Leave A Comment

*