[64호]EYESHOE (시각 장애인을 위한 신발과 이와 연동된 상황별 안내 어플리케이션)
2020 ICT 융합 프로젝트 공모전 장려상
EYESHOE (시각 장애인을 위한 신발과 이와 연동된 상황별 안내 어플리케이션)
글 | 이화여자대학교 황시은, 오지영, 박지은, 김가연
1. 심사평
칩센 시각 장애인을 위한 보조 장치는 여러 가지 방법으로 늘 제안되는 작품 주제 중 하나입니다. 지원자(팀)께서 개발하신 작품과 같이 초음파 센서를 이용하여 지형 또는 장애물을 파악하는 방안 또한 자주 제시되는 기술 방안이긴 하지만, 이 장치를 신발 안에 넣겠다는 발상이 신선합니다. 테스트 진행한 시제품의 경우 일반 상용 부품을 사용하여 조금 둔탁한 구조를 가지긴 하지만, 동등 이상의 소형 부품으로 구성한다면 신발의 심미적인 면 또한 어느 정도 개선이 가능할 것으로 보입니다. 다만 구조적으로 신발과 일체형이 되어야 할 소지가 보여, 일체형을 의도한 것이 아니라면 이 부분에 대하여 쉽게 적용 가능한 방안을 찾는 것은 필요할 것으로 보이고, 장애물을 좀 더 정확하고 정밀하게 확인할 수 있는 알고리즘 보완을 기대합니다. 재미있는 작품과 시연 잘 보았습니다.
펌테크 세심한 관찰력이 반영된 실생활과 밀접한 아이디어와 실용성이 우수한 작품이라고 생각합니다. 기획의도에 맞게 전체 시스템을 안정적이고 완성도 높게 구현하였다고 판단이 되고 전체적으로 기획의도, 기술 구현도, 완성도 등에서 우수한 작품으로 생각됩니다.
위드로봇 초음파 센서 여러 파형의 융합을 어떻게 처리할 것인지 고민이 좀 더 필요해 보입니다.
2. 작품 개요
장애인은 사회 공동체와 단절되지 않고 비장애인과 더불어 다 같이 행복한 삶을 누릴 자격이 있다. 그러므로 장애인과 같은 사회적 약자들도 비장애인 같이 평등한 권리를 보장받아야 한다. 그러기 위해 인간의 가장 기본적인 권리인 이동의 자유를 보장해 줄 수 있는 보조공학기기가 그들의 생계 유지와 더불어 중요한 문제임을 인식해야 한다. 그리고, 기술 개발을 통한 이러한 기기들의 업그레이드가 필수적이다.
시각장애인은 흔히 점자블록의 도움을 받거나 보행 보조 수단으로 시각장애인용 지팡이와 장애인 보조견을 사용한다. 그러나 시각장애인이 이러한 기존의 방법에만 의지하며 복잡한 도심 속에서 보행하기에는 여러 가지 어려움들이 있다. 먼저, 도로에 점자블록이 있다고 하더라도 시각장애인이 장애물의 거리와 위치를 완벽하게 인지하기에는 무리가 있다. 또한, 일반 보도블록도 울퉁불퉁한 경우가 많아 점자블록과 구분하기 어려운 경우가 많다. 뿐만 아니라, 2020-03-18에 작성된 ‘연합뉴스’의 기사에 따르면, 서울시가 장애인 보행환경 개선을 위해 강북권 보도만을 전수 조사했음에도 보도의 점자블록이 제대로 설치되어 있지 않은 경우 등의 이상이 총 1만6천268건이나 발견되었다고 밝혔다. 두 번째로, 지팡이는 직접 장애물과 닿아야 시각장애인이 장애물이 있음을 인지할 수 있기 때문에 ‘지팡이의 길이’라는 한계점을 지닌다. 이에 우리는 ‘지팡이의 길이’라는 한계점에서 벗어나, 시각장애인이 장애물과 일정거리 떨어져 있어도 장애물의 존재와 지형의 변화를 미리 알고 대비할 수 있도록 하는 시각장애인용 보행 보조 수단을 개발하고자 하였다. 물론, 요즘에는 시각장애인을 위해 전방에 장애물이 있으면 진동으로 알리는 스마트 지팡이도 개발되었다. 그러나 실제 시각장애인의 경우 일반인이 느끼는 진동모터의 진동을 수십 배 이상으로 느끼기 때문에 지팡이를 조금만 사용해도 손의 감각이 쉽게 피로해지는 현상을 호소할 수 있다. 다음으로 안내견은 장기간의 훈련을 필요로 하여 많은 시간과 비용이 소모된다. Eyeshoe를 사용하면 안내견을 훈련하는 사회적 복지 비용과 시각장애인 개인이 안내견을 키우는 데 드는 비용과 어려움을 절감할 수 있을 것으로 예상된다.
국내에는 시각장애인을 위한 신발이 전무하고 해외에는 적은 수가 존재하나, 단순하게 전방의 장애물의 유무를 알려주는 정도에 그쳤다. 그러나 사실 사용을 위해서는 다양한 상황에 대한 알림이 필수적이라 생각한다. 따라서 일상생활에서 가장 자주 접하고, 시각장애인에게 불편을 줄 수 있는 상황들에 대한 알림을 주고자 한다. 또한, 신발에서 직접적으로 소리가 나면, 주변 소음이 더 커서 소리를 듣지 못하거나 정숙을 유지해야 하는 상황에서 소리가 나서 당황하는 경우가 생길 수 있다. 이에 우리는 아두이노를 스마트폰 애플리케이션과 연동하여 사용자가 직접 이어폰을 착용하거나 볼륨을 조절하며 사용할 수 있도록 한다. 이는 EyeShoe를 사용하기에 훨씬 직관적이고 편리한 환경을 제공할 것이다.
3. 작품설명
EyeShoe에는 초음파 센서가 부착되어 있어 장애물까지의 거리를 측정한다. 단순 장애물 뿐만 아니라 오르막길, 내리막길, 계단, 벽 등을 구분하여 시각장애인이 자신이 나아갈 경로가 시각적으로 보이지 않더라도 미리 대비하여 안전하게 통행할 수 있도록 하였다. 더 나아가 우리는 이 신발을 안드로이드 스튜디오를 통해 직접 제작한 스마트폰 애플리케이션과 블루투스 모듈을 이용하여 연동하였다. 앱은 시작 화면에서 사용자의 발 사이즈를 입력받고 이를 토대로 보폭을 계산하여 장애물과의 거리를 안내한다. 이 과정은 화면을 읽지 못하는 사용자를 고려하여 음성으로 들을 수 있게 하였다. 첫 이용 시 발 사이즈 값을 입력하면 자동으로 저장되며 이에 따라 개인 맞춤형 알림을 받을 수 있다.
3.1 사용한 부품
· 아두이노 UNO R3 호환보드: 초음파 센서와 블루투스 모듈을 코딩을 통해 구동시키기 위해 사용
· 초음파 거리 센서(HC-SR04P): 신발과 장애물 및 계단 사이의 거리를 측정
· 블루투스 모듈 HC-06: 아두이노에서 안드로이드 스튜디오로 초음파 센서 값을 전송
· 9V 망간 배터리 (6F22): 아두이노 보드에 전원을 공급
· 배터리 배럴잭 어댑터 클립: 아두이노에 배터리를 이용하여 전원공급할 때 사용
· 전기인두: 점퍼 케이블들을 서로 접합시킴
· 땜납: 땜질에 사용
· 브레드 보드: 점퍼 케이블을 이용하여 초음파 센서, 아두이노 보드, 그리고 블루투스 모듈 사이를 연결
· 아두이노우노 통신 케이블: 아두이노 보드와 PC 사이를 연결
· 점퍼 케이블 (MM): 양쪽이 M 타입 커넥터로 브레드 보드, 아두이노, F 타입 헤더핀에 연결
· 점퍼 케이블 (FM): 한쪽은 F 타입 커넥터로, 초음파센서, M 타입 헤더핀과 연결하였고, 반대편은 M 타입 커넥터로 브레드보드, 아두이노, 그리고 F 타입 헤더핀에 연결
우리는 EyeShoe를 작동시키는 것뿐만 아니라 실제로 상용화되었을 때의 가격과 무게를 예상해 보았다. 먼저 신발 한 짝을 기준으로 예상하는 단가는 약 18,230원으로, 충분한 상업적 가치가 있다. EyeShoe에 부착된 기기들은 비교적 가벼워서, 기기들을 부착하지 않은 상태와 비교했을 때 무게가 거의 달라지지 않았다. Eyeshoe를 직접 신고 테스트도 시행해보았는데, 현재 시중에 판매되고 있는 신발과 무게가 비슷하여 걷기에 불편함이 없었다. 다양한 실전 테스트(첨부 영상 참고)로 신뢰성을 확보한 상태에서 이와 같이 저렴하고 가볍게 보급된다면 유익한 제품이라 생각된다.
3.2 주요 동작 및 특징
1) 스마트폰 애플리케이션의 첫 화면에서 사용 안내 버튼을 누르면 시각장애인용 신발과 이와 연동된 애플리케이션에 대한 설명이 팝업창에 뜬다. 스마트폰에 기본으로 탑재되어 있는 기능인 Voice Assistant 모드를 켠 후 이 팝업창을 누르면 음성으로 안내 받을 수 있다.
다음은 음성 안내의 내용이다.
· “이 앱은 시각장애인 분들을 위해 제작한 신발과 함께 사용하는 앱입니다. 앱 시작 시 발 사이즈를 입력해주세요. 전방의 장애물을 보폭 기준 세 걸음 전에 안내합니다. 오르막, 내리막, 벽에 접근 시 각각 안내합니다. 15cm 이내로 접근 시 경보음이 울립니다. START 버튼을 누른 후 연결 버튼을 눌러 블루투스(THIS)를 연결해주세요.”
2) 전방 장애물 알림
스마트폰 애플리케이션의 첫 화면에서 자신의 발 사이즈를 입력한 후 연결 버튼을 누르면, 자동으로 보폭이 계산되고 신발과 안드로이드가 블루투스로 연동되어 장애물이 사용자의 보폭 기준 약 세 걸음 전에 있을 때 안내 음성이 나온다. 발 사이즈를 입력하고 save 체크박스에 체크하면 발 사이즈가 자동으로 저장되어 다음에 어플을 실행할 경우 다시 입력하지 않아도 된다.
다음은 음성 알림의 내용이다: “장애물 조심“
장애물과의 거리가 15cm 이하이면 근접 경보를 울린다.
3) 벽 알림
벽이 사용자의 보폭 기준 약 세 걸음 전에 있을 때 안내한다.
다음은 음성 안내의 내용이다: “벽 조심”
벽과의 거리가 15cm 이하이면 근접 경보를 울린다.
4) 오르막길(계단) 알림
오르막길이 가까워지면 음성 알림을 시작한다.
다음은 음성 안내의 내용이다: “오르막 조심”
5) 내리막길(계단) 알림
내리막길 및 계단이 가까워지면 음성 알림을 시작한다.
다음은 음성 안내의 내용이다: “내리막 조심”
3.3 전체 시스템 구성
신발에는 총 4개의 초음파 센서가 부착되어 있다. 먼저 발바닥으로부터 지면의 거리를 측정하는 센서이다. 발이 공중에 떠 있을 때는 측정을 멈추기 위해 발바닥 가운데에 있는 센서를 사용한다. 초음파 센서값이 3.0cm일 때, 즉 발바닥 전체가 지면과 맞닿을 때 전체적인 값 측정을 시작하게 한다. 나머지 3개의 초음파 센서는 각각 전방 장애물, 오르막길, 내리막길을 감지한다. 측정한 초음파 센서 값은 블루투스 모듈을 통해 안드로이드 스튜디오로 전송된다.
안드로이드 스튜디오에 업로드 된 코드에는 측정한 3개의 센서값을 이용해 알림 설정을 하는 알고리즘이 구성되어 있다. 먼저, 사용자가 앱 실행 시 발 사이즈(mm)를 입력하게 한 후, 이를 cm 단위로 만들어주기 위해 이에 10을 나누고, 2.5를 곱해 일반적인 보폭 값을 계산한다. 이를 이용하여 전방 초음파 센서값이 세 걸음 이내일 때 앱을 통해 “장애물 조심” 알림을 한다. 다음은 여러 지형 변화에 따른 센서 감지 모습을 나타낸 그림이다.
벽 : 전방 센서값과 오르막 센서값이 직각 삼각형을 이룰 경우 벽에 근접했다고 인식한다.
오르막: 벽이 아닐 경우(전방 센서값과 오르막 센서값이 직각 삼각형을 이루지 않을 경우) 오르막이라고 인식한다.
내리막: “Eyeshoe” 앞코에 부착된 내리막 센서는 약 65°의 기울기로 아래를 향하고 있다. 사용자가 평지를 걷는 중에 내리막 센서에는 일정한 값이 나오지만, 내리막 접근 시 기존에 나오던 값(약 20cm)보다 큰 값이 나오게 된다. 이때 사용자가 내리막(계단)에 근접했다고 인식하고 알림을 준다.
안드로이드 스튜디오에서 위의 알고리즘을 적용했다.
3.4 기존 지팡이와의 차이점
3.5 개발 환경(개발 언어, Tool, 사용 시스템 등)
개발 언어: C언어(Arduino), Java(Android Studio)
사용 tool: Arduino Uno, Fritzing(아두이노 회로도 작성 프로그램), Android Studio, 3D CAD Solidworks(설계도 제작)
4. 단계별 제작 과정
4.1 아이디어 회의
시각장애인이 기존 지팡이만으로는 거리에 있는 장애물들을 피하며 지형을 예측하는 것에 어려움을 느낀다는 것을 깨닫고 보다 안전하고 사용하기 편리한 보행 보조 장치를 만들고자 했다. 이에 기능을 신발에 직접 넣어 시각장애인이 더욱 안전하게 이동할 수 있도록 했다. 최대 4m까지 물체감지가 가능한 초음파 센서가 이에 적합하다고 생각하여 신발의 내/외부에 센서를 부착하고 상황별 센서값 처리 방법에 대해 의논했다. 또한, 효과적인 알림을 주기 위해 사용이 편리한 애플리케이션을 제작하기로 결정했다.
4.2 시각장애인용 신발 및 애플리케이션 제작
먼저 브레드보드와 초음파센서, 블루투스 모듈, 그리고 아두이노 보드를 연결한 후 아두이노에 코드를 업로드했다.
본격적으로 신발을 제작하기 전에 3D CAD Soild Works를 활용하여 설계도를 그렸다.
테스트용 신발을 제작하여 우리가 구상한 알고리즘이 정상적으로 작동하는지 확인해 보았다.
본격적으로 신발을 제작하였다. 왼쪽 사진은 납땜을 하는 모습이다.
Android Studio를 통해 알고리즘을 구상하고 어플 개발을 하였다.
우리가 제작한 애플리케이션의 아이콘이다.
4.3 제품 테스트
EyeShoe를 신고 다양한 지형(장애물, 벽, 오르막길, 내리막길)에서 테스트를 했다. 테스트 영상은 따로 첨부하였다.(영상은 어플로 거리 값이 잘 전달되는 것을 확인시켜 드리기 위해 어플 화면의 Textview 부분을 늘린 상태입니다.)
5. 기타
5.1. 주요 소스코드
5.1.1 Arduino
#include <SoftwareSerial.h>
int Tx=2; //블루투스 전송
int Rx=3; //블루투스 수신
SoftwareSerial btSerial(Tx, Rx);
void setup() {
Serial.begin(9600);
btSerial.begin(9600);
//오르막길 센서(distance4)
pinMode(9, OUTPUT);
pinMode(8, INPUT);
//발바닥 센서(distance1)
pinMode(7,OUTPUT);
pinMode(6,INPUT);
//전방 센서(distance2)
pinMode(5, OUTPUT);
pinMode(4, INPUT);
//내리막길 센서(distance3)
pinMode(13,OUTPUT);
pinMode(12, INPUT);
}
void loop() {
//발바닥 센서 측정 시작한 후, 거리값 계산
digitalWrite(7, LOW);
digitalWrite(6, LOW);
delayMicroseconds(2);
digitalWrite(7,HIGH);
delayMicroseconds(10);
digitalWrite(7,LOW);
unsigned long duration1 = pulseIn(6, HIGH);
float distance1 = ((float)(340L*duration1)/10000)/2.0;
//발바닥 센서에서 측정한 값이 3.8cm 이하일 때, 나머지 센서(전방 센서, 오르막길 센서, 내리막길 센서) 측정 시작한 후 거리값 계산
if(distance1<3.8f){
Serial.println(“Under 10cm”);
digitalWrite(9, LOW);
digitalWrite(8, LOW);
delayMicroseconds(2);
digitalWrite(9, HIGH);
delayMicroseconds(10);
digitalWrite(8,LOW);
unsigned long duration4 = pulseIn(8, HIGH);
float distance4 = ((float)(340L*duration4)/10000)/2.0;
digitalWrite(5, LOW);
digitalWrite(4, LOW);
delayMicroseconds(2);
digitalWrite(5, HIGH);
delayMicroseconds(10);
digitalWrite(5,LOW);
unsigned long duration2 = pulseIn(4, HIGH);
float distance2 = ((float)(340L*duration2)/10000)/2.0;
digitalWrite(13, LOW);
digitalWrite(12, LOW);
delayMicroseconds(2);
digitalWrite(13, HIGH);
delayMicroseconds(10);
digitalWrite(13,LOW);
unsigned long duration3 = pulseIn(12, HIGH);
float distance3 = ((float)(340L*duration3)/10000)/2.0;
Serial.println(distance2);
Serial.println(distance3);
Serial.println(distance4);
//센서가 측정 가능한 거리값을 과도하게 벗어나는 오류값을 받지 않기 위해 3500cm 이하의 값만 안드로이드 스튜디오로 전송
if (distance2<3500&&distance3<3500&&distance4<3500){
btSerial.print(distance2);
btSerial.print(‘,’);
btSerial.print(distance3);
btSerial.print(‘,’);
btSerial.print(distance4);
btSerial.println();
}
}
else{
}
delay(1000);
}
5.1.2 Android Studio
Mainactivity.java
package com.example.sinbal;
import android.app.Activity;
import android.content.Intent;
import android.widget.TextView;
import android.widget.Toast;
import app.akexorcist.bluetotohspp.library.BluetoothSPP;
import app.akexorcist.bluetotohspp.library.BluetoothState;
import app.akexorcist.bluetotohspp.library.DeviceList;
import android.media.MediaPlayer;
import android.bluetooth.BluetoothAdapter;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
public class MainActivity extends AppCompatActivity {
private BluetoothSPP bt;
int size;
boolean blockDetected = false;
boolean wallDetected = false;
boolean downhillDetected = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent = getIntent(); //변수 받아오기 추가 부분
size = intent.getIntExtra(“size”,0);
bt = new BluetoothSPP(this); //Initializing
if (!bt.isBluetoothAvailable()) { //블루투스 사용 불가
Toast.makeText(getApplicationContext()
, “Bluetooth is not available”
, Toast.LENGTH_SHORT).show();
finish();
}
bt.setOnDataReceivedListener(new BluetoothSPP.OnDataReceivedListener() {
TextView distance22 = findViewById(R.id.distance2); //텍스트뷰를 통해 초음파 센서 값 받아오기
TextView distance33 = findViewById(R.id.distance3);
TextView distance44 = findViewById(R.id.distance4);
double finalSize = MainActivity.this.size/10*2.5*3; // final size를 사용자의 보폭 기준 3걸음으로 설정
public void onDataReceived(byte[] data, String message) { //데이터 수신용 코드 추가
String[] array = message.split(“,”);
distance22.setText(array[0].concat(“cm”));
distance33.setText(array[1].concat(“cm”));
distance44.setText(array[2].concat(“cm”));
double distance2 = Double.parseDouble(array[0]); //초음파센서값 3개 array 형식으로 안드로이드 스튜디오에 받아오기
double distance3 = Double.parseDouble(array[1]);
double distance4 = Double.parseDouble(array[2]);
double hypotenuse = distance2/0.93969; //오르막센서 각도를 20도로 설정. 밑변 길이를 cos20으로 나눈 값, 대각선 길이
//벽 & 오르막
if(distance4<400){
if(distance4>hypotenuse){ //오르막 센서값과 전방센서값이 직각삼각형 형성하지 않을 경우 오르막으로 인식
//오르막
final MediaPlayer mp = MediaPlayer.create(MainActivity.this, R.raw.uphill);
mp.start();
}
else{ // 오르막 센서값과 전방센서값이 직각삼각형 형성할 경우 벽으로 인식
//벽
if(distance2 < finalSize){
if(!wallDetected) { //벽 중복 알람 방지
wallDetected = true;
final MediaPlayer mp = MediaPlayer.create(MainActivity.this, R.raw.wall);
mp.start();
}
if(distance2 < 15) { //근접 경보
final MediaPlayer mp = MediaPlayer.create(MainActivity.this, R.raw.close);
mp.start();
}
}
else {
wallDetected = false;
}
}
}
//장애물
else{ // 오르막 센서가 감지되지 않을 경우, 전방센서값은 측정되는 경우.
if(distance2< finalSize){
if(!blockDetected) { //장애물 중복 알람 방지
blockDetected = true;
final MediaPlayer mp = MediaPlayer.create(MainActivity.this, R.raw.block);
mp.start();
}
if(distance2<15) { //근접 경보
final MediaPlayer mp = MediaPlayer.create(MainActivity.this, R.raw.close);
mp.start();
}
}
else {
blockDetected = false;
}
}
//내리막센서가 일정 값 이상이 나올 경우
//내리막
if(distance3>100) {
if (!downhillDetected) {
downhillDetected = true; //내리막길 중복 알람 방지
final MediaPlayer mp = MediaPlayer.create(MainActivity.this, R.raw.downhill);
// mp.start();
}
else {
downhillDetected = false;
}
}
});
bt.setBluetoothConnectionListener(new BluetoothSPP.BluetoothConnectionListener() { //연결됐을 때
public void onDeviceConnected(String name, String address) {
Toast.makeText(getApplicationContext()
, “Connected to ” + name + “\n” + address
, Toast.LENGTH_SHORT).show();
}
public void onDeviceDisconnected() { //연결해제
Toast.makeText(getApplicationContext()
, “Connection lost”, Toast.LENGTH_SHORT).show();
}
public void onDeviceConnectionFailed() { //연결실패
Toast.makeText(getApplicationContext()
, “Unable to connect”, Toast.LENGTH_SHORT).show();
}
});
Button btnConnect = findViewById(R.id.btnConnect); //연결시도
btnConnect.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
if (bt.getServiceState() == BluetoothState.STATE_CONNECTED) {
bt.disconnect();
} else {
Intent intent = new Intent(getApplicationContext(), DeviceList.class);
startActivityForResult(intent, BluetoothState.REQUEST_CONNECT_DEVICE);
}
}
});
}
public void onDestroy() {
super.onDestroy();
bt.stopService(); //블루투스 중지
}
public void onStart() {
super.onStart();
if (!bt.isBluetoothEnabled()) { //
Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(intent, BluetoothState.REQUEST_ENABLE_BT);
} else {
if (!bt.isServiceAvailable()) {
bt.setupService();
bt.startService(BluetoothState.DEVICE_OTHER); //DEVICE_ANDROID는 안드로이드 기기 끼리
setup();
}
}
}
public void setup() {
Button btnSend = findViewById(R.id.btnSend); //데이터 전송
btnSend.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
bt.send(“Text”, true);
}
});
}
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == BluetoothState.REQUEST_CONNECT_DEVICE) {
if (resultCode == Activity.RESULT_OK)
bt.connect(data);
} else if (requestCode == BluetoothState.REQUEST_ENABLE_BT) {
if (resultCode == Activity.RESULT_OK) {
bt.setupService();
bt.startService(BluetoothState.DEVICE_OTHER);
setup();
} else {
Toast.makeText(getApplicationContext()
, “Bluetooth was not enabled.”
, Toast.LENGTH_SHORT).show();
finish();
}
}
super.onActivityResult(requestCode, resultCode, data);
} }
Initial.java
package com.example.sinbal;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
import androidx.appcompat.app.AppCompatActivity;
public class initial extends AppCompatActivity {
Button button;
EditText userSize;
CheckBox checkSize;
SharedPreferences UserInfo;
SharedPreferences.Editor editor;
public static final int sub = 1001; /*다른 액티비티를 띄우기 위한 요청코드(상수)*/
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_initial);
button = (Button) findViewById(R.id.alert);
userSize = (EditText) findViewById(R.id.inputSize);
checkSize = (CheckBox) findViewById(R.id.checkSize);
checkSize.setChecked(true);
UserInfo = getSharedPreferences(“UserInfo”, 0);
editor = UserInfo.edit();
//사용자에게 발사이즈 입력받는 변수 설정
final int inputSizeValue = UserInfo.getInt(“inputSize”, 0);
if (inputSizeValue > 0) {
userSize.setText(Integer.toString(inputSizeValue));
}
Button button = findViewById(R.id.start); /*페이지 전환버튼*/
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(getApplicationContext(), MainActivity.class); //변수 받아오기 추가 부분
// Intent intent = new Intent(this, MainActivity.class);
String SizeValue = userSize.getText().toString();
if (!SizeValue.isEmpty()) {//Empty 아닐 때만 실행
int inputSizeValue = Integer.parseInt(SizeValue);
intent.putExtra(“size”, inputSizeValue);
startActivity(intent);//액티비티 띄우기
}
}
});
}
public void onPause() {
super.onPause();
if (checkSize.isChecked()) {
String sSize = userSize.getText().toString();
if (!sSize.isEmpty()) {
editor.putInt(“inputSize”, Integer.parseInt(sSize));
}
} else {
editor.putInt(“inputSize”, 0);
}
editor.commit();
}
public void onStart(){
super.onStart();
button.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v) {
Intent intent = new Intent(initial.this, PopupActivity.class);
startActivity(intent);
}
});
}
}
5.2 참고문헌
· https://www.yna.co.kr/view/AKR20200317176900004?input=1195m
· http://www.kbuwel.or.kr/Blind/What
· http://mechasolution.com/shop/goods/goods_view.php?goodsno=119&category=
· https://blog.codejun.space/13
5.3 회로도
5.4 Flow Chart
[64호]실험용 온도계 (Temperature Data logger)
ICT 융합 프로젝트 공모전 장려상
실험용 온도계 (Temperature Data logger)
글 | 포항공과대학교 김재유
1. 심사평
칩센 작품 개발의 의도가 조금 이해되지 않습니다. 하드웨어 개발을 위한 프레임 구성 및 각 메인 시스템과 외부 Peripheral 에 대한 Modulation 이라고 한다면 오픈 하드웨어 모듈레이션의 개념으로 접근하는건 어땠을까 합니다. 외관상 모듈레이션이 된것처럼 보이긴 하지만 내부 연결 방식이 모듈 블록간에 헤더핀과 Wire로 연결되어 다른 모듈 블록으로 교체하려면 케이스를 오픈해야하는 구조로 보입니다. 메인 시스템 기구 구성시에 각 Port를 특정 기능으로 매핑하고 B2B Connector 또는 Peripheral 전용 Connector Type으로 구성을 하였더라면 어땠을까요?
펌테크 실용적인 제품이라고 생각되며 전체적으로 꼼꼼하게 잘 기획되었고, 간결하게 잘 구성한 작품이라고 생각합니다.
위드로봇 아두이노의 모듈화를 꾀한 작품입니다. 인터페이스 커넥터에 대한 고민이 더 있으면 좋겠습니다.
2. 작품개요
2.1. 개발 동기
최근 과학자들도 과학실험을 할 때 아두이노를 요긴하게 이용하곤 한다. 가격이 저렴하고 개발이 빠르고 쉽기 때문이다. 그런데 어떤 경우에는 배보다 배꼽이 더 커질 때도 있다. 다시 말해 간단히 작동할 수 있는 디바이스를 빨리 만들려다가 생각보다 너무 많은 시간이 소모되는 것이다. 지난 몇 년간 아두이노를 가지고 여러 장치를 개발해보며 그 원인에 대해서 생각하게 되었다.
보통 어떤 디바이스를 개발하려면 기계분야, 전자, 컴공분야가 힘을 합치곤 한다. 아두이노는 전자, 컴공분야에 대해서 비전공자도 쉽게 개발을 할 수 있도록 길을 열어주었다. 그런데 기계분야에 대해서는 별다른 길이 열리지 않은 것 같다. 그래서 사람들은 각자 자신이 자주 이용하던 방식으로 소위 “프레임”을 만들어서 아두이노를 설치하곤 한다. 알루미늄 프로파일을 이용하거나 철판을 절곡해서 박스를 만들기도 하고, 목공을 잘 하시는 분은 목공으로도 박스를 만들기도 하고 패브릭, 도자기 등 기상천외한 방법들이 사용된다. 전통적인 방식의 빵판(Bread Board)을 남는 목재로 만들어서 나름대로 아두이노로 무엇을 개발할 때 프레임을 만들어 기계적 완성도를 높이려곤 했었다. 그러나 위와 같은 방법은 생각보다 안정성이 높지 않았다. 그래서 아두이노 프로젝트의 기계적 완성도를 높이고 개발을 효율적으로 빠르게 할 수 있도록 모듈화를 해보면 어떨까 생각해보게 되었다.
2.2. 개발의 목적
개발의 목적은 아래와 같이 두 가지로 요약할 수 있다.
① 아두이노 및 센서별 프레임 모듈화
② 아두이노 모듈화 시스템을 활용할 수 있는 방안 연구
3. 작품 설명
3.1. 주요 동작 및 특징
이 작품은 아두이노 우노를 중심으로 3가지 센서, 액추에이터를 쉽고 빠르게 연결하여 개발의 수월성을 높이는 모듈이다. 이 작품은 전자보드, 프레임, 샘플 코드 세 가지로 구성되어 아두이노로 구현하고자 하는 아이디어를 안정적 결과물로 개발할 수 있게 도와준다.
3.2. 개발 환경
① 개발언어: 아두이노
② Tool: 아두이노, 3D 프린터
3.3. 전체 시스템 구성
4. 단계별 제작과정
4.1. 개발방향수립
아두이노 모듈화를 통해서 아두이노 프로젝트 결과물의 기계적 안정성, 완성도를 제고한다. 누구나 3D 프린터를 통해서 출력해서 사용할 수 있어야 하며 회로연결작업과 코딩작업이 용의해야 한다. 가격이 너무 비싸지 않아야 한다.
4.2. 모듈개발준비
그림과 같이 모듈화 개발에 대한 컨셉을 정하였다. MCU로서 아두이노 우노를 선택하였다. 왜냐하면 아두이노 보드들 중에서 가장 대중적으로 이용되고 있기 때문이다. 또한 가격이 저렴하며 사용 가능한 핀의 개수도 수십개로 수 개의 센서 또는 액츄에이터 등을 연결하기에 적당했다.
4.3. 3D 프린터를 이용한 프레임의 개발
4.3.1. 아두이노 우노 프레임 개발
아두이노 우노를 장착할 수 있는 프레임을 사진과 같이 3D 프린터를 활용하여 개발하였다. 아두이노 우노는 M2 볼트를 이용해서 프레임에 고정되도록 하였다. 아두이노 우노 보드에는 네 개의 체결구멍이 뚫려있는데 이탈리아산 아두이노 우노 보드와 중국/대만산 아두이노 우노 보드의 구멍 위치가 조금씩 달랐으며 저렴한 호환보드에 맞추어서 프레임을 제작하였다.
하지만 출력 과정에서 전선구멍을 원형으로 하면 흠결이 생길 여지가 높았다. 그 부분을 수정하여 아래와 같은 2차 개발물을 완성하였다.
아두이노 우노를 장착할 수 있는 프레임을 사진과 같이 3D 프린터를 활용하여 개발하였다. 바닥의 환기 구멍은 슬롯형태로 설계하였다. USB 및 DC 전원잭이 연결될 구멍은 옆면에 내었다. 또한 센서와 액츄에이터 등 아두이노 우노에 연결될 부품들에서 나오게 될 전선은 정사각형 구멍을 통하도록 하였다. 마지막으로 바닥에는 고무재질 범폰을 붙여서 안정성을 높힐 수 있었다. 이렇게 아두이노 우노 프레임을 완성하였다.
4.3.2. 소형 센서/액츄에이터 하부 프레임 개발
각종 소형 센서 및 액츄에이터를 장착할 수 있는 프레임을 사진과 같이 3D 프린터를 활용하여 개발하였다. 그 결과 M3 볼트가 체결되는 구멍의 크기는 적당하여 볼트가 잘 체결되었다. 하지만 같은 금속재질의 너트가 없기 때문에 강한 힘으로 볼트를 체결할 시 구멍이 제 역할을 하지 못하고 망가져버릴 가능성이 있었다. 향후 금속너트를 넣는 방식을 쓰면 이 문제를 해결할 수 있겠지만 큰 힘을 받는 부분은 아니기 때문에 그대로 남겨두어 편의성을 제고하고자 하였다.
가장 큰 문제점은 윗면의 나사구멍이 아니라 밑면의 나사구멍이 너무 얕아서 체결의 강도가 감소한다는 것이었다. 그래서 기둥을 세워서 이 문제를 해결하는 방향으로 다음 버전의 형상을 설계하고 출력하였다.
각종 소형 센서 및 액츄에이터를 장착할 수 있는 프레임을 사진과 같이 3D 프린터를 활용하여 2차 개발하였다. 그 결과 밑면의 환기구멍은 적당히 공기를 통할 수 있게 하였지만 환기구멍의 크기는 너무 작아 출력의 질을 떨어트리는 요소로 작용하였다. 옆면 전선구멍의 크기는 AWG30의 래핑와이어나 AWG26의 점퍼와이어가 십 수개 정도는 널널히 드나들 수 있을만큼 컸으나 원형으로 FDM 방식 출력 시 출력물에 결함이 생길 가능성이 높았다. 옆면의 M3 나사구멍은 조금 좁아 나사가 부드럽게 통과하지 못했다. 이와 같은 사항을 고려하여 3차 설계와 출력을 진행하였다.
위와 같이 3D 프린터를 활용하여 프레임을 3차 개발하였다. 그 결과 밑면의 환기구멍은 적절한 크기와 숫자였고 잘 출력되었다. 옆면의 전선통과 구멍을 아두이노 우노 프레임과 마찬가지로 마름모꼴로 변경하여 마지막 형상을 확정하였다.
사진과 같이 3D 프린터를 활용하여 프레임을 최종개발하였다. 옆면의 마름모꼴 구멍은 3D프린터로 출력될 때의 결함을 최소화할 수 있었다.
4.3.3. 대형 센서/액츄에이터 하부 프레임 개발
사진과 같이 3D 프린터를 활용하여 소형센서보다 커서 프레임의 크기가 더 커야하는 대형센서 혹은 엑츄에이터를 담을 수 있는 프레임을 최종 개발하였다.
4.3.4. 센서/액츄에이터별 상부 프레임 개발
OLED 디스플레이를 부착할 수 있도록 설계된 상부 프레임을 위과 같이 제작하였다.
푸시 버튼모듈을 부착할 수 있도록 설계된 상부 프레임을 위와 같이 제작하였다.
위와 같이 네 개의 부품을 모듈화하였다.
4.4. 결과분석을 통한 모듈화 검증
개발된 아두이노 모듈을 이용해서 실제로 아두이노 우노에 4종의 부품을 부착해보면서 기능을 검증해보았다.
위와 같이 아두이노 우노를 하부 프레임에 M2 5mm 볼트를 이용해서 체결하였다. 예상과는 다르게 정품보드의 구멍과 호환보드의 구멍이 서로 위치가 동일하지 않았다. 그래서 호환보드의 구멍에 맞추어 하부 프레임을 제작하기로 하였다. 총 네 개의 볼트를 이용해서 아두이노 우노는 체결되었다.
위와 같이 센서/액츄에이터를 아두이노 우노와 연결하였다. 그 결과 완성도 높은 센서-아두이노 체결이 가능하였으며 따라서 기계적 안정성이 높아졌다. 래핑와이어를 통한 전선체결 또한 기존의 듀폰 점퍼와이어보다 안정적이라고 판단되었다.
그 다음 회로, 코딩작업의 용의성 검증하고자 하였다. 아두이노와 센서 등 부품을 연결하는데 통상적으로 듀폰 점퍼케이블을 이용하곤 하지만 그 경우의 단점은 아래와 같았다.
1. 쉽게 연결이 빠져버리며 다시 연결할 때 힘들다.
2. 한정된 아두이노 우노의 Power pin으로 여러 개의 센서에 전원공급이 힘들다.
3. 선이 두꺼워 뻣뻣한 힘으로 인해 좁은 공간에 구겨넣기 힘들다.
4. 다른 아두이노 보드로 그대로 옮겨 꽂기 힘들다.
그래서 듀폰 점퍼케이블로 회로를 구성하지 않고 래핑와이어를 이용한 회로구성을 선택하게 되었다.
래핑와이어를 이용해서 아두이노 우노의 회로연결 작업을 실제로 해 본 결과 듀폰 점퍼와이어를 사용했을 때 발생하는 문제들이 모두 해결됨을 확인할 수 있었다.
단점으로는 래핑와이어가 AWG30으로 가늘기 때문에 많은 양의 전류를 사용하지는 못한다는 점이었다. 하지만 아두이노 우노 자체가 큰 전류를 직접 사용하지는 않기 때문에 극복할 수 있는 단점이라고 판단되었다.
결과물이다. M3 볼트를 이용해서 아두이노 우노 프레임과 센서 프레임이 성공적으로 체결되었다. 개발을 진행하다보니 아두이노 우노보드에서 5V 핀을 여러개로 만드는 과정에서 허공에 떠있는 핀헤더가 다른 곳에 닿아서 합선될 가능성이 있었다. 이를 해결하기 위해서 아두이노 우노 보드에 핀헤더를 고정할 수 있는 구멍을 만들면 개선될 것으로 보인다.
5. 프로그램 활용가능성 모색
예시1. 3D 프린트용 온습도센서 개발
1. 회로
2. 소스코드
int selection = 0; //변수지정
#include “U8glib.h” //OLED 출력을 위한 라이브러리 호출
U8GLIB_SSD1306_128X64 u8g(U8G_I2C_OPT_DEV_0|U8G_I2C_OPT_NO_ACK|U8G_I2C_OPT_FAST); //OLED 객체 지정
const uint8_t temp[] PROGMEM = {0×00, 0×00, 0×00, 0×00, 0×00, 0×00, 0×00, 0×00, 0×00, 0×00, 0×00, 0×00, 0×00, 0×00, 0×00, 0×00, (생략)}; //OLED 디스플레이에 내보낼 이미지
#include <AM2320_asukiaaa.h //온습도센서 라이브러리 호출>
AM2320_asukiaaa mySensor; //온습도센서 객체 지정
int temperature, humidity; //온도, 습도값 변수 지정
void setup() { //셋업함수 시작
{Serial.begin(115200); //시리얼통신 시작
while(!Serial); //시리얼통신 개발 실패시 대기할 것
Wire.begin(); //Wire 통신 시작
mySensor.setWire(&Wire); //온습도센서 통신 시작
u8g.setFont(u8g_font_fub14); //OLED 디스플레이 폰트 지정
u8g.setColorIndex(1); //OLED 디스플레이 글자색 지정
pinMode(2, OUTPUT); //LED 출력핀 지정
pinMode(3, OUTPUT);
pinMode(4, OUTPUT);
pinMode(5, INPUT);} //버튼 입력핀 지정
void loop() { //루프함수 시작
Serial.println(selection); //시리얼 통신을 통해 selection 값을 출력
if (digitalRead(5)==1) {selection++; delay(150);} //만약 버튼이 눌러진다면 selection 값을 1만큼 증가
switch (selection) { //스위치문 시작
case 0: //selection이 0일 경우에
u8g.firstPage(); do {draw0();} while( u8g.nextPage() ); //OLED를 통해 첫번째 이미지를 출력
break; //스위치문 나가기
case 1: //selection이 1일 경우에
digitalWrite(2, 1); //첫번째 LED의 불을 켜고
u8g.firstPage(); do {draw1();} while( u8g.nextPage() ); //OLED를 통해 두번째 이미지를 출력
break; //스위치문 나가기
‘case 2: //selection이 2일 경우에
digitalWrite(2, 0); digitalWrite(3, 1); //첫번째 LED의 불을 끄고 두 번째 LED의 불을 켜고
u8g.firstPage(); do {draw2();} while( u8g.nextPage() ); //OLED를 통해 세번째 이미지를 출력
break; //스위치문 나가기
case 3: //selection이 3일 경우에
digitalWrite(3, 0); digitalWrite(4, 1); //두번째 LED의 불을 끄고 세번째 LED의 불을 켜고
u8g.firstPage(); do {draw3();} while( u8g.nextPage() ); //OLED를 통해 네번째 이미지를 출력
break; //스위치문 나가기
case 4: //selection이 4일 경우에
digitalWrite(4, 0); //세번째 LED의 불을 끄고
mySensor.update(); //온습도센서의 값을 불러오며
u8g.firstPage(); do {draw4();} while( u8g.nextPage() ); //OLED를 통해 다섯번째 이미지를 출력
delay(2000); //2초간 대기
selection=0; //selection을 다시 0으로
break;}} //스위치문 나가기
void draw0() {u8g.drawBitmapP(0, 0, 16, 64, temp);} //OLED 구동함수
void draw1() {u8g.drawStr(33, 32, “PC”);}
void draw2() {u8g.drawStr(33, 32, “ABS”);}
void draw3() {u8g.drawStr(33, 32, “PLA”);}
void draw4() {u8g.drawStr(20, 22, “T:”);
u8g.drawStr(20, 60, “H:”);
u8g.setPrintPos(50, 22);
u8g.print(mySensor.temperatureC);
u8g.setPrintPos(50, 60);
u8g.print(mySensor.humidity);}
3. 작동모습
개발된 디바이스의 작동 모습, 온습도가 잘 표시되고 버튼을 누르면 LED가 순차적으로 켜진다.
예시2. MP3 모듈 장착 예시
개발된 디바이스의 작동 모습. 음성파일 이름이 잘 출력되고 재생되었다.
[64호]두근두근 원격 모니터링, Electro Cardio Gram
ICT 융합 프로젝트 공모전 장려상
두근두근 원격 모니터링, Electro Cardio Gram
글 | 김창운
1. 심사평
칩센 심장질환으로 인한 질병이 발현하였을 때, 그에 대한 사전 또는 빠른 처치를 진행하기 위한 목적을 가진 작품임을 보고서 서두만으로도 충분히 알 수 있었습니다. 심사원인 저 또한 의료전문가가 아니라서 도출한 결론이 얼마나 의학적으로 명확한지는 알 수가 없습니다만, 분명한 목표와 방향을 가지고 최종 결과물까지 진행한 과정이 매우 훌륭합니다. 동영상과 보고서를 통하여 보여주는 심전도 Pulse 또한 병원 등에서 보았던 듯한 형태라 신기한 마음도 들었습니다.
펌테크 아이디어와 실용성, 상업성을 두루 갖춘 작품이라고 생각합니다. 전체적으로 세심하고 짜임새 있게 잘 구성이 되었다고 생각되며 기술적 구현도, 작품 완성도 등이 우수한 작품이라고 생각합니다.
위드로봇 심전도 신호는 측정하기가 어려운 분야인데, 노이즈를 잘 제거했습니다. 심박 신호로 응용예제까지 만들어 내면 더욱 좋겠습니다.
2. 작품 개요
이 프로젝트를 진행하기 전에 앞서서 제가 할 수 있는 범위 내에서 ‘코로나19 바이러스’와 같은 질병을 예방 할 수 있는 방법이 무엇일까? 고민해보았습니다. 바이러스처럼 사람의 목숨을 빼앗아 가는 ‘심장질환 문제’에 대해서 잠시 소개하자면, 심장은 우리 몸의 중심이 되며 가장 중요한 장기입니다. 또한, 가장 예민하다고 볼 수 있는 이 심장에서 심장 질환 문제 및 심혈관 질환 문제가 세계 10대 사망원인 중 1위를 차지하고 있습니다. 특히, 심장 질환은 기존에 고혈압이나 저혈압을 앓고 있었던 환자분들과 잦은 흡연, 스트레스 등과 함께하고 있는 바쁜 현대인들에게 많이 나타나고 있습니다.
또한, 건강한 사람들도 누워서 쉬고 있는 중에 갑자기 심장질환 문제가 종종 발생하고 있습니다. 심장이 멈춘 후로부터 4분 ~ 10분 안에 심장 충격이 들어가지 않으면 생존 확률은 희박합니다. 하지만, 119 응급대원 분들이 현장에 도착하기까지 평균시간이 8분이 넘는다고 합니다. 이처럼 이제는 가정에서도 원격 진료가 필요하다고 생각합니다.
물론, 의학 전문인이 아닌 일반인이 함부로 몸의 상태를 파악할 수도 파악해서도 안 되겠지만, 의심되는 환자분들이나 진료를 희망하시는 사람을 대상으로 가정에서도 실시간으로 심박 수를 미리 모니터링 하여 가까운 병원의 의료진분들과 119 응급대원 분들께 빠른 시간 내에 현 상황을 알려서 더 빠른 조치가 취해져야 한다고 생각합니다.
“Eletrocardiogram” 즉, 심전도는 심장을 전기로 기록하는 것을 의미합니다. 심전도 파형은 직류이며 단지 x축(가로 축)이 시간이 아닌 주파수이기 때문에 그림과 같은 파형이 나타나는 것을 볼 수 있습니다.
심장 수축 시 눈에 보이지 않는 미세한 생체 전기신호가 발생합니다. 뇌에서 전기신호가 근육으로 가면 근육은 수축하게 되는 원리를 이용하였습니다.
심장은 혈관, 근육, 전기신호, 판막 등과 함께 어우러져 기능을 하기 때문에 그 중 하나만 이상이 생겨도 심장질환이 발생하게 됩니다. 때문에, 원격으로 심장질환 문제를 모니터링 하여 질병을 예방하고자 하였습니다. 설계된 심전도 측정 회로를 통해 심전도 신호(=심근육 펄스 신호)를 측정하여 가까운 병원의 의료진분들과 119 응급대원 분들께 환자의 현 상황을 실시간으로 모니터링 할 수 있습니다. 또, 환자의 위험상태 근처 도달 시 위험 상황을 가까운 병원의 의료진분들과 119 응급대원 분들께 알릴 수 있습니다.
이 제품의 타겟 고객을 “혼자 거주하시고 생활 하시는 독거노인분”, “기존에 심장질환을 앓고 있는 의심 환자분” 등으로 선정하였습니다.
3. 작품설명
3.1. 사용매뉴얼
1. 심전도 패드를 오른쪽 다리(오금), 양 팔꿈치 안쪽에 붙여 줍니다.
2. ISP의 USB를 PC나 스마트폰 충전기에 연결해서 +5V를 공급합니다.
3. “-5V 전원 스위치를 킨 후 -5V가 공급되는지” LED를 통해서 확인 할 수 있습니다.
4. “심전도 신호 검출”을 빨간색 LED를 통해서 확인할 수 있습니다.
5. “심전도 신호 검출”을 파란색 LED를 통해서 확인할 수 있습니다.
6. LCD에 BPM값이 표시되는 것을 확인한 뒤 스마트폰 어플에서 블루투스 연결 후 BPM이 표시 되는 것을 원격으로 확인할 수 있습니다.
7. 원격으로 심장 질환 유/무를 확인할 수 있습니다.
3.2. 앱 설치
3.2.1. 블루투스 초기 등록
HC-06 pincode : 1234
3.2.2. 블루투스 연결
3.3. 전체적인 주요 시스템 구성
3.3.1. 심전도 신호 처리 회로
3.3.2. 설계 알고리즘 : 플로우차트
3.4. 주요 동작 및 특징(동작원리)
· INA128 소자의 입력으로써 -5V 전압이 필요하기 때문에 필요한 -5V 전압을 9V 사각전지와 LM7805 소자를 이용하여 만들어 낼 수 있습니다. INA128 소자는 주로 계측용 차분증폭을 할 때 사용 되는 차분 증폭기 모듈입니다. INA128 소자의 특징은 CMRR(공통모드제거비)으로써 두 개의 단자에 발생하는 잡음과 전송선로의 잡음을 없앨 수 있습니다. 따라서, 심전도 리드케이블로 얻어지는 생체 신호를 증폭하며 잡음을 없애기 위해서 INA128 소자가 꼭 필요하다고 생각하였습니다.
· 필터의 입력으로써 2개의 전압이 필요하기 때문에 외부전원 +5V 전압을 Rs232케이블을 통해 끌어옵니다.
· 최종 증폭된 신호 때 까지 주파수는 그대로 유지 되어야하며 단지 변하는 것은 전압범위입니다. 전극과 같은 역할을 하는 리드케이블로 심장 근육에 가는 전기신호를 읽어 들여서 노이즈 주파수만 걸러지고 심박 주파수는 그대로 있어야 측정할 수 있습니다. 심박 주파수에 잡음이 섞여 있기 때문에 심박 주파수를 기준으로 높거나 낮은 주파수를 필터를 통해서 걸러냅니다. HPF(고주파 성분만 통과) LPF(저주파 성분만 통과)
· LM324 소자를 통해서 증폭된 신호의 전압레벨이 5V 로 출력 됩니다.
· ATmega128(MCU)에 입력되기 위해서 저항 10[] 과 다이오드를 이용하여 0[V] 이하 전압은 0[V]로 만들어 주었습니다.
· 심전도 신호가 직접 LED로 확인 될 수 있게 해서 리드 케이블과 연결된 심전도 패드가 잘 부착 되어서 심전도 신호가 잘 입력되었는지 등을 확인 할 수 있습니다.
· 측정한 심전도 신호를 실시간으로 계속 전송하기 때문에 가까운 병원의 의료진분들과 119 구급대원분들이 가정 내에서 생활 하고 있는 환자분을 모니터링 할 수 있습니다.
· 측정한 심전도 신호 중 이상 신호가 발견 될 시 가까운 병원의 의료진분들과 119구급대원분들이 부저를 통해 응급상황을 빠르게 알 수 있습니다.
3.5. 개발환경
Code Vision AVR, Code Vision AVR, APP INVENTOR, 오실로스코프, 멀티미터
3.6. 재료
4. 단계별 제작 과정
심전도 측정 회로의 아날로그 신호를 ATmega128에서 획득한다.
4.1. 전체 회로도
5. 소스코드
5.1. ATmega128 소스코드
#define F_CPU 16000000UL
#define sbi(PORTX,bitX) PORTX|=(1<<bitX)
#define cbi(PORTX,bitX) PORTX&=~(1<<bitX)
#include <mega128.h>
#include <delay.h>
#include <stdio.h>
void LCD_init(void);
void LCD_data(char data);
void LCD_command(char command);
void LCD_string(char line, char *string);
int Adc_Channel(char Adc_input);
void USART_Transmit(char data);
void USART_Transmit_int(int data);
char buf[16] = {‘ ‘,};
int adc_data = 0;
int cnt_ms = 0;
float cnt_s = 0.000;
float cnt_m = 0.000;
float cnt_data = 0.000;
float bpm = 0.000;
int bef_flag = 0;
int flag = 0;
int tick = 0;
int tick_flag = 0;
void main()
{
// io 핀 설정
DDRA = 0xFF;
DDRB = 0xFF;
DDRC = 0xFF;
PORTA = 0×00;
PORTB = 0×00;
PORTC = 0×00;
// ADC
ADMUX = 0×00;
ADCSRA = (1<<ADEN)|(1<<ADSC)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0);
// TIMER/COUNTER
TCCR0 = (1<<CS02)|(1<<CS00); // 128분주
TCNT0 = 256-125; // 125번 => 0.001s
TIMSK = (1<<TOIE0);
// UART
UCSR1A = 0×00;
UCSR1B = (1<<TXEN1);
UCSR1C = (1<<UCSZ11)|(1<<UCSZ10); //8bit
UBRR1H = 0;
UBRR1L = 103;
// LCD
LCD_init();
LCD_string(1,” BPM “);
#asm(“sei”)
// 동작 시작 부저 알림 “삡,삡”
sbi(PORTC,0);
delay_ms(100);
cbi(PORTC,0);
delay_ms(300);
sbi(PORTC,0);
delay_ms(100);
cbi(PORTC,0);
while(1)
{
adc_data = Adc_Channel(0);
bef_flag = flag;
if(adc_data > 500) // 기준치 보다 전압 신호 크면
{
sbi(PORTB,1);
flag = 1;
}
else
{
cbi(PORTB,1);
flag = 0;
}
if((bef_flag == 0) && (flag == 1)) // 기준치 보다 전압신호 높아지는 순간
{
cnt_data = (float)cnt_ms; // 카운트된 값 저장 . 심박 신호간 길이 (밀리초)
cnt_ms = 0;
cnt_s = cnt_data/1000; // 심박 신호간 길이 (초)
cnt_m = cnt_s/60; // 심박 신호간 길이 (분)
bpm = 1/cnt_m; // 분당 심박수 (주기와 주파수는 역수관계)
}
if(tick_flag == 1) // 2초에 한번 동작 하는 곳
{
//LCD 표시
sprintf(buf, ” %4d “,(int)bpm);
LCD_string(2,buf);
//블루투스 전송
USART_Transmit_int(bpm);
USART_Transmit(0);
//부저 동작
if ((bpm < 50) || (bpm > 180)) // 이상 BPM
{
sbi(PORTC,0);
USART_Transmit(‘ ‘);
USART_Transmit(‘W’);
USART_Transmit(‘A’);
USART_Transmit(‘R’);
USART_Transmit(‘N’);
USART_Transmit(‘ ‘);
USART_Transmit(‘S’);
USART_Transmit(‘T’);
USART_Transmit(‘A’);
USART_Transmit(‘T’);
USART_Transmit(‘E’);
}
else
cbi(PORTC,0);
tick_flag = 0;
}
}
}
interrupt [TIM0_OVF] void timer0_ovf_isr(void)
{
cnt_ms ++; // 0.001s 1씩 증가
tick ++;
if(tick >= 2000)
{
tick = 0;
tick_flag = 1;
}
TCNT0 = 256-125;
}
int Adc_Channel(char Adc_input)
{
ADMUX = 0b01000000 | Adc_input;
ADCSRA |= 0×40;
while((ADCSRA & 0×10) == 0);
return ADCW;
}
void LCD_command(char command)
{
PORTA = (command&0xF0); // send High data
cbi(PORTA,0); // RS=0
//cbi(PORTA,1); // RW=0
sbi(PORTA,2); // Enable
delay_us(1);
cbi(PORTA,2); // Disable
PORTA = (command&0x0F)<<4; // send Low data
cbi(PORTA,0); // RS=0
//cbi(PORTA,1); // RW=0
sbi(PORTA,2); // Enable
delay_us(1);
cbi(PORTA,2); // Disable
}
void LCD_data(char data)
{
delay_us(100);
PORTA = (data&0xF0); // send High data
sbi(PORTA,0); // RS=1
//cbi(PORTA,1); // RW=0
sbi(PORTA,2); // Enable
delay_us(1);
cbi(PORTA,2); // Disable
PORTA = (data&0x0F)<<4; // send Low data
sbi(PORTA,0); // RS=1
//cbi(PORTA,1); // RW=0
sbi(PORTA,2); // Enable
delay_us(1);
cbi(PORTA,2); // Disable
}
void LCD_init(void)
{
delay_ms(50);
LCD_command(0×28); // DL=0(4bit) N=1(2Line) F=0(5×7)
delay_ms(2); // [function set] 0b00101000
// 4:(DL) 1이면 8bit모드, 0이면 4bit모드
// 3:(N) 0이면 1줄짜리, 1이면 2줄짜리
// 2:(F) 0이면 5x8dots, 1이면 5x11dots
LCD_command(0x0C); // LCD ON, Cursor X, Blink X
delay_ms(2); // [display on/off control] 0b00001100
// 2:(D) 1이면 display on, 0이면 off
// 1:(C) 1이면 cursor on, 0이면 off
// 0:(B) 1이면 cursor blink, 0이면 off
LCD_command(0×06); // Entry Mode
delay_ms(2); // [entry mode set] 0b00000110
// 1:(I/D) 1이면 오른쪽으로, 0이면 왼쪽
// 0:(SH) CGRAM 사용관련
LCD_command(0×01); // LCD Clear
delay_ms(2);
}
void LCD_string(char line, char *string)
{
LCD_command(0×80+((line-1)*0×40));
while(*string)
LCD_data(*string++);
}
void USART_Transmit(char data)
{
while(!(UCSR1A & 0×20));
UDR1 = data;
}
void USART_Transmit_int(int data)
{
int temp;
if(data < 10)
{
temp = data;
USART_Transmit(temp+48);
}
else if(data < 100)
{
temp = data/10;
USART_Transmit(temp+48);
temp = (data%10)/1;
USART_Transmit(temp+48);
}
else if(data < 1000)
{
temp = (data%1000)/100;
USART_Transmit(temp+48);
temp = (data%100)/10;
USART_Transmit(temp+48);
temp = (data%10)/1;
USART_Transmit(temp+48);
}
else
{
temp = data/1000;
USART_Transmit(temp+48);
temp = (data%1000)/100;
USART_Transmit(temp+48);
temp = (data%100)/10;
USART_Transmit(temp+48);
temp = (data%10)/1;
USART_Transmit(temp+48);
}
}
5.2. 앱인벤터 코드
6. 참고문헌
· 윤성우 “열혈 C++ 프로그래밍” (오렌지미디어 2010)
· 천정아 “개념을 콕콕 잡아주는 C 프로그래밍” (이한출판사 2009)
· 윤덕용 “ AVR ATMEGA128A 바이블 ” (OHM사 2007)
· 이재창 “ AVR ATmega128 제어 ” (21세기사 2017)
· 필터(Filter)관련 참고자료 : https://blog.naver.com/ojh3110/221647169821, https://blog.naver.com/racoonjonny/221619139911
· 관련 기사문 참고자료 : https://www.hankyung.com/society/article/202003264188i
· 의학용어 관련 참고자료 : https://www.youtube.com/watch?v=4Ddb1bclk1c, https://www.youtube.com/watch?v=vdNoT-159dQ
6.1. 응용분야
전극(패드)을 조금 더 추가 사용하여 차량 내부 시스템 등에 사용 할 수 있는 활용 범위가 높은 제품입니다.
[64호]레트로디세이(Retrodyssey)
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, 조광민)
1.2. ODYSSEY x86 만의 장점 극대화
이 작품은 Window 운영체제와 아두이노를 내장한 오디세이(Odyssey) 보드로 제작되었습니다. 인터넷을 통해 스팀(Steam)을 비롯한 플랫폼에서 게임을 다운로드받아 플레이할 수 있으며, 조이스틱과 8개의 버튼을 개인별 취향에 맞게 설정할 수 있습니다. 이는 Window와 Arduino가 결합된, 오디세이 만의 장점을 극대화한 활용법입니다.
2. 작품 설명
이 작품은 1개의 조이스틱과 8개의 버튼으로 구성되어 있습니다. 조이스틱은 마우스와 키보드로 전환되어 사용될 수 있습니다. 8개의 버튼은 각각 키보드 버튼과 매핑(Mapping) 되어 있습니다. 제품과 함께 제공되는 GUI 프로그램을 이용해 매핑된 키보드 버튼을 바꿀 수 있습니다. 자신의 STEAM 최애 게임을 콘솔로 즐겨보세요!
(※ 최애 : 최고로 애정한다. 가장 사랑함)
2.1. 주요 동작 및 특징
2.1.1. 레트로 감성의 디자인
어릴 적 오락실 게임기와 닌X도의 게임기를 떠올리게 하는 바로 그 디자인입니다. 게임을 즐기지 않더라도 인테리어 용품으로도 인기만점! 이제 하나씩 만드는 과정을 살펴보도록 하겠습니다!
2.1.2. 조이스틱과 버튼을 키보드와 마우스로 이용
조이스틱을 A0와 A1 아날로그 입력으로 설정합니다. 조이스틱의 x축 y축의 가변저항이 기울어짐을 측정해, 아날로그값을 전달합니다. 이 데이터를 기반으로 마우스 움직임 (Mouse.move()) 또는, 키보드 방향키(←↑→↓)를 움직입니다.
또한, 키보드의 경우 (Keyboard.press())를 이용하여, 버튼이 입력되면 키의 입력을 설정합니다. 또한, count 변수를 설정해 키보드를 꾹 누를 때 발생하는 반복 입력의 경우도 대처합니다. 모든 경우가 키보드와 동일하게 작동합니다.
2.1.3. 일반인을 위한 GUI를 이용한 키 매핑
게임의 경우 모든 키가 동일하지 않습니다. 그래서 저희 컨트롤러는 여러 게임에 호환하여 적용할 수 있도록 키를 설정합니다. 저희는 코드에 익숙하지 않은 사람들도 쉽게 사용할 수 있도록 Qt creator를 사용해 GUI 인터페이스를 만들었습니다.
사용자는 GUI 환경에서 A부터 Z까지 25개의 알파벳과 ‘ESC’, ‘TAB’ 2개 특수키, 총 27개의 키를 게임기 버튼에 할당할 수 있습니다. 사용자는 슬라이드를 사용하거나 직접 해당하는 글자를 타이핑한 후(단, 이때 모든 글자는 대문자로 쓰여야 합니다.) ‘DEVICEMART’ 버튼을 클릭하면 해당 정보를 오디세이 x86으로 시리얼 통신을 통해 보낼 수 있는데 이를 통해 게임기의 키 셋업을 마칠 수 있습니다.
2.2. 전체 시스템 구성
오디세이에서 Steam 게임을 구동합니다. 그리고 조이스틱과 버튼으로 키의 입력을 받습니다. 키를 변경하고 싶으면 qt 배포판 GUI를 실행하여 키를 커스텀합니다.
2.3. 개발 환경(개발 언어, Tool, 사용 시스템 등)
아래는 사용한 부품목록입니다.
2.3.1. Hardware(외관 설계)
Autodesk Inventor 2020, Autodesk Fusion 360을 사용했습니다. 하드웨어 설계를 위해 사용합니다.
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로 선택해주세요.
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 버전으로 설정해주고 나머지는 디폴트 상태로 설정한 후 다운로드 받으면 됩니다.
작성한 코드는 ‘Retrodyssey_성균관대(IWMYS)/소스코드/qt’ 에 들어있는 ‘seedcontroller’ 폴더 안에 들어 있으며 해당 폴더(‘seedcontroller’)를 ‘로컬디스크/Users/사용자명/문서’로 옮기거나 해당 사용자의 qt 파일이 저장되는 경로로 옮기면 코드를 수정할 수 있습니다. 아래는 경로의 예시입니다.
qt 배포판은 디바이스마트 블로그 해당 게시글 첨부자료에서 확인하실 수 있습니다.
2.3.3.2 배포한 GUI 환경을 그대로 사용하고 싶은 경우
만약 가볍게 구현해보고자 하는 사람이라면 저희가 배포한 배포 파일 ‘Retrodyssey_성균관대(IWMYS)/소스코드/qt배포판’ 안에 있는 ‘retrodyssey’ 폴더를 다운로드 받는다면 GUI 인터페이스를 사용할 수 있습니다. ‘retrodyssey’ 폴더 안에는 qt배포판 실행을 위한 여러 라이브러리가 들어 있는데 이들 중 ‘seedcontroller’을 실행하면, 게임 실행을 위한 키 설정을 할 수 있습니다.
게임기 키 설정을 위해서는 우선 오디세이 x86보드에 우선 아두이노 코드가 올라가 있어야 합니다. 아두이노 코드가 보드에 올라가 있다면, 다음으로 오디세이 x86보드와 통신하기 위해, USB 포트 번호를 설정해주어야 합니다. 배포판의 경우, ‘COM8’의 포트와 연결되도록 만들어져 있으므로 다음과 같은 과정을 거쳐 포트명을 ‘COM8’로 바꿔줘야 합니다.
1단계) 장치 관리자에서 아두이노와 연결된 포트를 찾아 선택해줍니다.
2단계) ‘포트 설정’에서 고급 탭을 선택합니다.
3단계) 포트명을 ‘COM8’로 바꿔줍니다.
그렇게 포트설정을 끝냈다면 이후 다운로드 받은 ‘retrodyssey’ 폴더 안의 ‘seedcontroller’ 파일을 실행해 원하는 키보드 정보를 입력하고 ‘DEVICEMART’버튼을 눌러주면 게임기 키 설정을 할 수 있습니다.
3. 단계별 제작 과정
3.1. 3D 본체 설계
모델링을 함에 있어서 게임기의 모습을 부각시키기 위해 노력했습니다. 디자인적 측면에서 버튼과 조이스틱이 둥글다는 공통점을 가지고 있어, 버튼과 조이스틱이 차지하는 부분은 둥근 원으로 디자인하였습니다. 또한 게임기를 떠올릴 때, 강력하게 대비대는 빨간색과 파란색으로 색깔을 입혔으며 메인 프레임의 모든 색상은 이를 부각할 수 있는 하얀색으로 디자인했습니다. 수치와 설계 부분에 있어서는 Inventor를 이용하여 진행했습니다.
부품 구성은 메인 프레임 각각 좌, 우 부분과 조이스틱이 들어가는 부분과 고정시켜주는 부분 마지막으로 버튼 6개를 담을 버튼 프레임으로 구성 되어있습니다. 전체적인 모습은 다음과 같습니다.
조이스틱과 버튼을 설치할 프레임들은 공통적으로 메인프레임에 안정적으로 거치될 수 있는 것을 목표로 했습니다. 그 결과 위아래의 지름을 다르게 하여, 걸칠 수 있게 설계하였습니다.
조이스틱을 담을 파트와 받침 부분은 조이스틱을 움직이는 과정에서 흔들림이 없이 고정되어야 하므로 나사 연결방식을 채택하였습니다. 이는 M1.2 나사를 사용하도록 설계하였고 고정하는 구멍은 총 4개로 설계되었습니다.
오디세이를 담을 메인프레임의 설계는 두 손을 올려 장시간 게임을 함에 있어서 무리가 가지 않도록 높이를 10cm로 설계하였습니다. 이는 버튼과 조이스틱 등을 오디세이와 연결할 때 많은 회로가 메인프레임 내부에서 엉키지 않게 하려는 부분도 고려한 수치입니다.
그보다도 가장 많이 신경을 쓴 부분은 단연 오디세이가 들어가는 부분입니다.
이 제품의 가장 중요한 것은 오디세이입니다. 오디세이가 어떤 식으로도 손상이 가면 안 되기 때문에, 오디세이를 단단하게 고정해줄 나사의 위치를 선정하는 것부터, 발열 해소를 위해 팬이 들어가는 공간을 특히나 신경 써 설계하였습니다. 발열 해소를 하지 못한다면, 기능의 저하와 손상으로 직결되는 부분이니 반드시 고려 해야 했습니다.
그 결과 팬이 바닥과 충분한 거리로 떨어져 있고, 바닥엔 직사각형으로 여러 구멍을 뚫어 설계를 진행하였습니다. 위의 설명의 설계 이미지는 다음과 같습니다.
또한, 메인 윗 파트와 연결하는 부분을 원 지름을 가로질러 설계를 하였는데 이는 M3나사를 사용하도록 설계했습니다. 추가로 메인 프레임에서 HDMI 케이블이나 다른 여러 선들을 연결하기 위한 공간을 설계한 것 또한 볼 수 있습니다. 자세한 사진은 다음과 같습니다.
최종 설계 부품들은 다음과 같습니다.
그리고 추가로 Odyssey x86 전용 게임기라는 것을 나타내기 위해, 디바이스마트와 seeed의 로고를 삽입하였습니다. 아래 별표 분에 꽂아주면 됩니다.
그리고 조이스틱 부분도 설계하였습니다. 2가지 모델로 설계하였으나, 일반 조이스틱 모듈을 사용하는 경우, 조이스틱은 추가하지 않는 게 좋습니다. 지렛대의 원리로 인해 힘점이 작용점보다 멀어지게 되어, 조이스틱 특유의 탄성을 느끼기 힘들어집니다. 저희 팀이 시연할 때도 제거하고 플레이하였습니다. 좌측 조이스틱은 M3 규격의 나사로 조립하면 됩니다.
3.2. 조이스틱 및 버튼 회로 연결
조이스틱 모듈과 버튼을 오디세이의 GPIO에 연결하여 줍니다. 이때, 버튼은 풀다운 저항을 사용하여, 값이 튀지 않도록 해줍니다. 내부 연결 회로를 제작한 후, 가조립하여 작동여부를 확인합니다. 먼저 아래 회로도에 따라 연결합니다.
이후 가조립하여 테스트합니다.
아래는 각종 부품 연결상태를 확대한 모습입니다.
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,};
// 마우스세팅
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]
조이스틱과 버튼 제어를 위해 직접 제작한 함수입니다. 코드는 살펴보셔도 좋고, 아니면 그 기능만 살펴보셔도 좋습니다. 헤더파일의 함수 기능을 요약해 보았습니다.
3.4. GUI용 qt 프로그래밍 작성
qt creator를 사용하기 위해 총 10개의 함수를 사용하였습니다. 게임기 셋업을 할 때, 슬라이드를 사용하기 위해 8개의 버튼에 해당하는 8개의 ‘on_slidernumber_valueChanged()’를 만들었으며, 슬라이드를 사용하거나 타이핑을 통해 입력한 키보드정보(A ~ Z, 25개의 알파벳과 ESC, TAB 2개의 특수키)를 읽어 아스키코드로 바꿔주는 ‘on_pushButton_clicker()’ 함수와 오디세이x86 보드와 연결된 상태에서 값을 전송해주는 ‘updateButton()’ 함수를 만들었습니다.
이후 원하는 키보드를 설정하기 위해 UI환경에서 게임기의 자판 배열에 맞춰 왼쪽의 목록에서 ‘Line Edit’ 위젯과 ‘Vertical Scroll Bar’ 위젯을 사용해 입력 인터페이스를 만듭니다. 이후 키보드 값을 한번에 시리얼 통신으로 전송하기 위해 ‘Push Button’ 버튼을 사용합니다.
3.5. 최종 조립
1단계) 밑판 위에 오디세이 보드를 고정시켜줍니다.
2단계) 회로도에 맞게, 오디세이와 조이스틱 모듈 버튼을 연결합니다.
3단계) 밑판의 나사선에 맞춰 윗판을 올린 후 M3 나사로 고정시킵니다.
4단계) 원판(붉은색, 파란색)을 삽입하고, 로고(디바이스마트와 seeed 로고)도 끼워줍니다.
5단계) 완성된 게임기의 모습입니다.
그래서 이게 진짜, 되는 거야? 라고 하시는 분들을 위해 작동영상도 준비해봤습니다. 작품 시연은 아래 QR코드로 확인해주세요.
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호]인사이드 3D 프린팅 컨퍼런스 & 엑스포 2020
INSIDE 3D PRINTING
인사이드 3D프린팅 컨퍼런스 & 엑스포 2020
글 | 박진아 기자 jin@ntrex.co.kr
Inside 3D Printing은 2013년 뉴욕을 시작으로 세계 8개 주요 도시에서 개최되는 세계 최대의 3D프린팅 및 적층제조(AM) 전문 이벤트이다. 제7회 인사이드 3D프린팅 컨퍼런스 & 엑스포가 ‘제조 기술의 미래(New Era of Advanced Manufacturing)’를 주제로 2020년 6월에 개최예정이었으나, 지속되는 코로나 상황으로 인해 2020년 11월 18일부터 20일까지 킨텍스 제1전시장에서 진행되었다. 연기된 만큼 기업참여나, 관람에 제약이 있었을 것으로 예상되며, 이번 전시회에는 산업용, 가정용 3D 프린터를 제조하여 판매하거나, 외국의 제품을 수입 유통하는 공급사, 3D 프린터를 통한 시제품을 제작하는 업체와 각 분야에서 활용 중인 적층제조 기술을 직접 만나 볼 수 있었다.
먼저, 삼영부스에서는 대형 바인더젯 샌드 BR-S900을 전시하였다.
BR-S900은 국내 최초로 국산화 개발된 대형 바인더 젯팅 방식의 샌드 3D 프린터로 빌드 플랫폼 사이즈 900X520X450(㎣)을 갖는 산업용 적층 제조 장비이다. 동급 외산 장비 대비 더욱 빠른 적층 속도 및 400 dpi의 고해상도를 통해 더욱 정교한 삼차원 형상 구현이 가능하다. 또한, 국산화 개발된 퓨란 바인더 시스템을 통해 합리적인 비용으로 장비 운용을 할 수 있고, 국내 최다 경험의 샌드 3D 프린터 운용 전문성을 바탕으로 지연 없는 A/S지원이 가능한 특징이 있다. 주요 용도로는 금속(알루미늄, 주철, 주강, SUS, 특수강, 구리 등)및 금형 주조용 몰드 제작, 캐스팅용(UHPC등의 콘크리트 및 실리콘, 에폭시 등) 몰드 제작, 목업 및 조형물 등 제작에 활용될 수 있다.
국내 최초 모래를 이용한 3D 프린팅 기술로 900X520X450(㎣) 기준, 최단 9시간 이내 출력이 가능한 BR-S900은 다양한 몰드 제작 활동에 시간을 단축해주는 등 다양한 이점을 줄것으로 기대해본다.
국내에 비교적 덜 알려진 미국의 대표적인 산업용 FDM 장비 제조업체 이센시움(Essentium)에서는 HSE 180 제품군을 전시하였다. Essentium의 HSE (고속 압출) 3D 프린팅 플랫폼은 제조 현장을 혁신하고 강도, 속도 및 비용 문제를 해결하기 위해 구축되었으며, 경쟁 제품 보다 5~15배 빠르게 부품을 인쇄할 수 있다. HSE 180시리즈는 가용한 온도 범위에 따라 LT, S, HT(High Temperature) 3개 모델로 구성되어 있다. 사진속 제품은 HSE 180·S HT로 고온 재료에 사용이 가능하고, 주로 항공우주 및 방위, 반도체 장치 제조, 석유 및 가스 산업에서 사용할 수 있다.
HSE 180 3D 프린터는 모든 선형 서보 모터 XY 스테이지, 3G 가속 및 1M / sec 이동 속도를 사용하여 인쇄 중에 낭비되는 움직임을 제거 할 수 있는 기능을 제공하고, 이러한 고급 모션 제어와 전원의 조합은 기존 프린터에 비해 인쇄에 대한 제어 및 정확성을 향상 시킨다.
속도와 힘이 증가함에 따라 수준 높은 노즐이 필요한데 Hozzle이라는 특허를 받은 HSE의 노즐은 고속 프린팅을 위해 특별히 설계되어, 상온에서 섭씨 500도로 가열하는데 3초면 가능한 장점이 있다.
HSE 180 높은 생산성을 갖추고 있지만, 가격은 타사의 동급 장비 기준의 대략 40% 수준이며, 소재 역시 경쟁력을 가지고 있는 제품이다.
또한, 3D 프린터 업체로 빼놓을 수 없는 세계 1위 3D 프린터 회사로 불리는 스트라타시스(Stratasys)의 제품도 더블에이엠, 프로토텍, 다양한 부스에서 확인할 수 있었다. 그중 기자는 부스에서 설명과 사은품 이벤트를 진행하여 관람자들이 몰린 더블에이엠 부스를 통해 스트라타시스 제품을 확인해 보았다.
더블에이엠은 스트라타시스의 국내 공인 리셀러 기업으로, 산업용 전문 프린터를 다수 보유하고, 국내 각 산업 분야에서 요구하는 3D프린팅 시제품 제작 및 제조용 툴, 최종 사용 파트 제작을 수행하는 기업이다.
해당 부스에는 스트라타시스(Stratasys)제품이 많이 전시되어 있었는데 그중 J55 제품이 신제품이라 한다. J55 3D프린터는 경제성을 자랑하는 제품으로, 엔터프라이즈용 폴리젯 프린터의 약 3분의 1가격으로 동시에 다섯 가지 재료 출력이 가능하다. 약 50만 가지 색상을 출력할 수 있는 동시에 팬톤 인증이 된 색상, 사실적인 질감 및 투명도까지 구현이 가능하며 사무실 또는 작업실 공간에 알맞은 사이즈로 제작되었다. 동급의 프린터와 비교했을 때 장비 설치 공간은 적고, 이에 비해 모델을 생산할 수 있는 트레이 크기는 큰 특징을 갖고있다. 또한, 가정용 냉장고의 소음 수준인 53데시벨 이하에서 작동되며, 냄새 방지 시스템이 탑재되어 스튜디오, 사무실, 강의실에서도 효율적으로 사용이 가능한 장점도 있다.
3D코리아에서는 국내 최초 Tool Change 기술의 기반으로 만든 Good Bot Series 프린터와 자사에서 생산하는 각종 필라멘트를 전시하였다. 10가지 색상의 기본 필라멘트에서 부터 향기, 야광, 메탈 느낌의 주문제작 필라멘트까지 다양한 제품이 준비돼있었다. 그중 KPLA 필라멘트는 10종의 화련한 컬러 제품으로 기존 PLA에 비해 수축이 적고 강하며 고유동성으로 디테일이 우수한 특징을 갖는다. KPLAHR 제품은 흰색, 검정, 내츄럴 3가지 색상을 보유하고 있으며, PLA의 장점에 내열성을 가지고 있다. 또한, 녹는점이 높아 후가공이 용이하고, 치수 안정성이 더욱 향상된 제품으로 다양한 산업용, 의료용에 적합하다고 한다.
많은 관람객들의 발 걸움을 멈춘 곳은 국내 3D 프린터 전문업체 STICK부스였다. 해당 부스에서는 3D 프린팅으로 제작한 스틱키링을 나눠주기도 하였고, 3D 프린터로 만든 작품을 한곳에 모아 전시하고 있었다. 사진 속 검정색 3D 프린터는 스틱플러스 3D 프린터로 고성능 챔버형 프린터로 안전성 향상, 온도유지, ABS 출력, 안정적 출력이 가능한 장점을 갖는다. 또한, 저소음 TMC 모터 드라이버가 적용되어 집에서도 사무실에서도 조용한 프린터 작업이 가능하다. 그 옆 흰색 프린터는 스틱 미니제품으로 이동성과 편의성을 극대화한 아담한 사이즈로 교육용, 취미용으로 탁월하게 사용이 가능하다. 이 제품 역시 무소음 드라이버가 탑재되어 있으며, 신속하게 온도를 낮춰주는 두개의 강력한 블로우팬, PLA 및 드론 부품을 위한 플렉시블 소재 출력에 특화된 E3D-LITE 핫엔드가 탑재되어 있다. 해당 기업에서는 레벨링부터 소재투입, 슬라이서 설정 등 3D 프린터가 생소한 초보자를 위해서는 기술지원은 물론 특수 소재사용 및 정밀치수 등 전문가를 위한 지원까지 유선상담, 카페와 카카오톡 채팅방을 운영중이라고 하니 3D 프린터를 처음 접하는 사람이 이용하기에 좋을 것으로 보인다.
독특한 소재, 용도에 사용되는 3D 프린터 제품도 볼 수 있었는데 그 중 인공치아를 전시해둔 에이온 주식회사의 부스가 유독 눈에 들어왔다.
에이온 주식회사는 세계 최초로 DLP(Digital Light Processing) 3D 프린터를 통한 인공치아(보철물) 제조에 성공한 치과 의료 기기 선도적 기업으로 세라믹 3D 프린터 INNI,INNI-ROBO를 선보였다.
INNI는 바이오 세라믹 3D 프린터로 DLP 방식으로 전용 지르코니아 슬러리(INNI CERA)를 사용하여 치아 보철물(비니어, 코핑, 크라운)등을 제작할 수 있다. 치과용으로 사용되는 해당 제품은 기존 보철물을 제작 의뢰 후 기다리는데 들었던 시간을 줄여주는 이점이 있어 보였다.
INNI-ROBO는 산업용 바이오 세라믹 3D 프린터로 전용 바이오 액상 소재를 이용하여 첨단 기술을 활용한 차세대 3D 프린터라고 한다. 항공, 자동차, 의료산업 등에서 시제품 제작이 가능하며, 연구용으로 사용이 가능하다. 두 제품 모두 480mm x 370mm x 543mm 사이즈에 46kg로 콤팩트한 사이즈로 많은 공간을 차지하지 않고 가벼운 특징이 있다. 의료기기 제조, 독특한 소재를 사용한 3D 프린터에 관심이 있는 관람객들에게는 흥미로운 부스였을 것으로 생각된다.
프린팅 기기 및 콘텐츠를 연구, 개발, 생산하는 엘에스비 부스에서도 특이한 소재를 사용한 프린팅 시연을 볼 수 있었다. 엘에스비 부스에서는 달콤하고, 먹음직스러운 초코를 사용하여 모양이나 문장을 프린팅하였고, 해당 제품은 영국 Choc Edge사의 ‘Choc Creator’로 엘에스비가 한국 공식 판매점이라고 한다. Choc Creator을 이용하면 원하는 디자인으로 나만의 특별한 시그니처 초콜릿을 만들며 초콜릿을 이용해 예술작품을 만드는 쇼콜라티에가 하는 일을 간접 체험해 볼 수 있다고 한다. 이렇게 만들어진 초콜릿 문구나, 모양 등은 빵과 쿠키 등 장식으로 이용하여 특별함을 더 해주거나, 다양한 체험과 교육용 활동으로 활용이 가능하다.
이번 2020 Inside 3D Printing 전시는 저먼 렙랩, 폼랩스, 유니온테크, 이오에스, 데스크톱메탈 등 대형·산업용 장비뿐 아니라 크레아텍, 드림티엔에스, 온스캔스 등 3D 스캐닝 전문기업, 그래피, 프랩스, 링크솔루션을 비롯한 소재 전문기업 그리고 메디컬아이피 등 3D 모델링 소프트웨어 등 다양한 산업군의 제품을 한자리에서 만나볼 수 있었다.
비록, 코로나로 인해 작은 규모로 진행되었으나 전 산업 영역에 빠르게 진입하고 있는 3D 프린팅의 최신 경향을 확인할 수 있는 좋은 기회였으며, 올해 하반기에 진행하는 2021 Inside 3D Printing에는 더 다양한 신기술과 많은 기업들이 참여하길 바라며 이번 관람기를 마친다.