[아두이노] 게임 조종기 아두이노

IOT/아두이노|2019. 7. 3. 09:00

[아두이노] 게임 조종기 아두이노



오늘은 모바일 게임을 모모앱플레이어로 했을 때 PC에서 조정이 가능합니다. 이 때 게임 조정키를 키보드로 세팅해놓으면 이전 시간에 아두이노 키보드를 이용하면 모바일 게임을 조정할 수 있게 됩니다. 여기서, 키보드 방향 조정을 조이스틱으로 표현하면 좀 더 조정기 다운 표현이 되겠죠.


아두이노 마이크로에서 게임 조정기를 만들어 볼까요.

1. 방향키 제어 복습



사전학습에 가시면 조이스틱을 방향키를 조절하는 방법에 대해 나와 있습니다.

[기본소스]

  if(analogRead(AXIS_X)<=300){
    Serial.println('a');
  }
  else if(analogRead(AXIS_X)>=700){
    Serial.println('d');   
  }
  //Y축 방향값
  if(analogRead(AXIS_Y)<=300){
    Serial.println('w');      
  }
  else if(analogRead(AXIS_Y)>=700){
    Serial.println('s');
  }

X축과 Y축의 아날로그 신호값을 나눠서 X축 방향으로 300이하면 왼쪽(a)키이고 700이상은 오른쪽(d)키이다. Y축 방향으로 300이하면 위쪽(w)키이고 700이상이면 아래쪽(s) 키이다 라고 정의해 놓았으면 아두이노 마이크로에서 키보드 라이브러리로 이용하면 아래와 같이 코딩하면 됩니다.

  //X축 방향값
  if(AXIS_X<=300){
     Keyboard.press('a');
     delay(50);
  }
  else if(AXIS_X>=700){
     Keyboard.press('d');
     delay(50);
  }
  else{
    Keyboard.release('a');
    Keyboard.release('d');  
  }
  //Y축 방향값
  if(AXIS_Y<=300){
    Keyboard.press('w');
    delay(50);
  }
  else if(AXIS_Y>=700){
    Keyboard.press('s');
    delay(50);    
  }
  else{
    Keyboard.release('w');
    Keyboard.release('s');
  }

비슷한데 좀 차이가 있죠. 키고 눌름 press()함수를 키 누름을 표현하지만 조이스틱이 키누름 값을 갖지 않을 때에만 해제 release() 함수로 표현 했습니다. 왜! 이렇게 표현했을까요.

Keyboard.press('a');
delay(100);
Keyboard.release('a');

가 한쌍으로 키 클릭을 표현했었죠. 이렇게 하면 0.1초간 눌러지고 키가 해제해 버립니다. 즉 순간 찰라에 키가 누르고 해제되면 조이스틱의 조정 방향으로 움찍하다가 끝나버리게 됩니다. 지속성이 사라져버리는 것이죠. 그렇기 때문에 조정키가 계속 지속성을 조이스틱의 키 누름 상태를 계속 유지해야 합니다. 그러면 release()함수를 쓰지 말아야 합니다. 이때 해제 함수를 안쓰면 계속 특정 키가 눌러지니깐 이때 문제가 생기기 때문에 조이스틱 방향으로 움직일 때는 키 누름을 하고 움직이지 않을 때는 키 누름을 해제하게 코딩을 하면 되겠죠. else 이하문에서 키누름을 해제하는 함수를 표현하여 조정 안하고 있을 때 키누름의 문제가 해결 됩니다.

좀 코딩이 약간 길어 졌지만 이렇게 표현하면 조이스틱으로 좀 더 자연스럽게 조정이 됩니다. 이방법은 문득 떠오른 생각을 코딩한 거라 최근 일이 있어서 집중을 못하고 그냥 대충 표현한 코딩이네요. 이 방법이 아닌 더 좋은 방볍으로 조이스틱을 방향키로 조절을 해보세요.

2. 아두이노 조이스틱 게임 조정기 회로도


  • 준비물 : 조이스틱 1개, 스위치버턴 1개, LED 1개, 저항 220옴 1개, 스위치버턴 5개, 아두이노 마이크로
  • 내용 : 조이스틱 xAxis, yAxis축은 A0, A1 핀에 연결하고 방향스위치버턴은 3,4,5,6번에 연결하시오. 인터럽트스위치버턴은 2번에 연결하고 LED 7번에 연결하면 됩니다.


조이스틱은 X,Y축값만 사용합니다. 스위치버턴은 사용하지 않았습니다. 위 그림에서 스위치버턴중 노란전선색의 스위치버턴은 인터럽트스위치로 아두이노 조이스틱 게임 조정기의 사용 가능/불가를 나타내고 나머지 스위치는 스킬키버턴으로 사용됩니다. Green LED은 게임 조정기의 사용 가능/불가 상태를 시각적으로 표현는데 사용합니다.

3. 코딩



**설계 : **

  • 조이스틱 방향키(위쪽-w, 아래쪽-s, 왼쪽-a, 오른쪽-d)로 조정
  • 스킬키 4개의 키보드 키는 "u,i,o,p"로 조정(스위치버턴의 한계로 스킬장 전환은 못함)
  • 실험 게임(검사 모바일)

[검사모바일] - 아두이노 게임 조정기를 만들어 단순히 게임을 컨트롤 해보기 위해서 1회 실험 목적으로 사용함.


대충 가상 키들을 방향키와 스킬키로 나누어 배치해 놓았습니다.

다음으로는 코딩인데 스킬키는 스위치버턴으로 키보드 키값을 누름을 표현했던 아두이노 키보드 제어편의 내용을 그대로 적용하면 되고 조이스틱 방향키만 위에서 복습한 내용을 아두이노 키보드 제어편 소스를 합치면 됩니다.

종합해보면,

#include <Keyboard.h>

const byte interruptPin = 2;
boolean state = true;

const byte xAxis = A0;         //X axis
const byte yAxis = A1;         //Y axis
const byte Pin_U = 3;
const byte Pin_I = 4;
const byte Pin_O = 5;
const byte Pin_P = 6;
const int ledPin = 7;         // Mouse control LED 

void setup() {
  pinMode(ledPin, OUTPUT);
  pinMode(Pin_U, INPUT_PULLUP); 
  pinMode(Pin_I, INPUT_PULLUP);
  pinMode(Pin_O, INPUT_PULLUP);
  pinMode(Pin_P, INPUT_PULLUP);
  
  pinMode(interruptPin, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(interruptPin), exchange, FALLING);  //인터럽트 발생 시 호출
  
  Keyboard.begin();
}

void loop() {
  
  if(state==false){ //인터럽트스위치 버턴에 의해서 열림/닫힘
    int AXIS_X = analogRead(xAxis);
    int AXIS_Y = analogRead(yAxis);
     
  //X축 방향값
  if(AXIS_X<=300){
     Keyboard.press('a');
     delay(50);
  }
  else if(AXIS_X>=700){
     Keyboard.press('d');
     delay(50);
  }
  else{
    Keyboard.release('a');
    Keyboard.release('d');  
  }
  //Y축 방향값
  if(AXIS_Y<=300){
    Keyboard.press('w');
    delay(50);
  }
  else if(AXIS_Y>=700){
    Keyboard.press('s');
    delay(50);    
  }
  else{
    Keyboard.release('w');
    Keyboard.release('s');
  }
    
  //스킬키 제어
  if(digitalRead(Pin_U) == LOW){
    Keyboard.press('u');
    delay(100);
    Keyboard.release('u');
  }
  if(digitalRead(Pin_I) == LOW){
    Keyboard.press('i');
    delay(100);
    Keyboard.release('i');      
  }
  if(digitalRead(Pin_O) == LOW){
    Keyboard.press('o');
    delay(100);
    Keyboard.release('o');      
  }
  if(digitalRead(Pin_P) == LOW){
    Keyboard.press('p');
    delay(100);
    Keyboard.release('p');      
  }     
 }  
}
void exchange() { //인터럽트스위치버턴  이벤트
 state=!state;
 digitalWrite(ledPin, !state);
}

방향키 IF문과 스킬키 스위치버턴 IF문 두개만 자세히 보시면 그렇게 어렵지 않을꺼에요.

4. 결과



아래 실험영상은 우선 인터럽트 스위치 버턴은 게임조성기를 활성화 하는 버턴입니다. 불이 들어오면 게임조정기를 조정할 수 있고 불이 안들어오면 게임 조정기를 사용할 수 없습니다. 한손으로 촬영하고 한손으로 조정하다 보니 케릭터 방향조정 따로 스킬키 동작 따로 단순한 동작만 실험했네요. 감안하시고 대충 이런 느낌으로 아두이노 조정기로 게임을 조정할 수 있구나 정도로 이해하시면 됩니다.


실험하려고 잠깐 검사모바일 게임 케릭터를 하나 만들어서 테스트 해 봤네요.

마무리


며칠 바빠서 post를 작성 할 시간도 스팀잇 접속도 어려웠네요. 오늘도 겨우 짬내서 급하게 코딩을 만들고 실험을 했네요. 코딩해 놓은게 별로 맘에 들지 않네요. 원하는 스타일도 아니고 대충 느낌만 전달하고자 간단히 실험했습니다.


댓글()

[아두이노] 조이스틱+processing 3D 도형 회전(2)

IOT/아두이노|2019. 4. 28. 09:00

[아두이노] 조이스틱+processing 3D 도형 회전(2)



지난 시간에는 아두이노는 아두이노 제어 코딩을 하고 processing은 아두이노에서 전송된 방향키 data를 시리얼통신 받아와서 3D 도형 회전을 하였다면 오늘은 prcoessing에서 3D 도형 뿐 아니라 아두이노 코딩까지 전부 담당하는 코딩을 살펴보도록 하겠습니다.


1. 아두이노 소스



지난 시간에 아두이노 방향키를 만든 소스입니다. 시리얼모니터로 해당 방향 알파벳을 출력한 소스인데 processing 코딩에 그대로 복사해서 이식할 예정입니다.

const int AXIS_X = A0;
const int AXIS_Y = A1;
const int SW_P = 3; 
 
void setup() {
  Serial.begin(9600);
  pinMode(SW_P,INPUT_PULLUP);
}
 void loop() {
  //X축 방향값
  if(analogRead(AXIS_X)<=300){
    Serial.println('a');
  }
  else if(analogRead(AXIS_X)>=700){
    Serial.println('d');   
  }
  //Y축 방향값
  if(analogRead(AXIS_Y)<=300){
    Serial.println('w');      
  }
  else if(analogRead(AXIS_Y)>=700){
    Serial.println('s');
  }
  delay(20);
}

2. processing으로 옮기기



위 아두이노 기본 방향키 값을 구하는 로직을 processing으로 전부 가져 오겠습니다.
지난 시간의 processing 코딩한 소스에다가 옮겨야 하니깐 우선 원래 소스는 다음과 같습니다.

[processing 소스]

import processing.serial.*;

Serial myPort;

int x=0;
int y=0;

void setup() {

    println(Serial.list());
    myPort = new Serial(this, Serial.list()[0], 9600);
    myPort.bufferUntil('\n');
    noStroke();
    size(600,600,P3D);
    
} 

void draw() {  
    background(0); //배경색
    lights();  //조명

    pushMatrix();  //Start
    fill(0,255,0); //채우기
    translate(width/2,height/2,-100); //이동
    rotateX(radians(y)); //x축 회전
    rotateY(radians(x)); //y축 회전
    
    box(200,100,200); //상자
    popMatrix(); //End
}
 
void serialEvent(Serial p) { 
  String inString = myPort.readStringUntil('\n');
  char ch=inString.charAt(0);
    
  println(ch);
  
  if (ch == 'w') {
      y+=1;
  } 
  else if (ch == 's') {
     y-=1;
  } 
  else if (ch == 'a') {
     x-=1;
  } 
  else if (ch == 'd') {
     x+=1;
  }
}

아두이노 함수들을 사용하기 위해서는 다음과 같은 과정이 필요합니다.

아두이노 라이브러리를 import 해서 객체 변수로 하나 만들어야 합니다.

[ processing에서 아두이노 코딩 ]

import cc.arduino.*;
Arduino arduino;

Arduino arduino;

arduino = new Arduino(this, Arduino.list()[0], 57600); //인스턴스화
    

아두이노 소스코딩 부분에 앞에 arduino 객체변수를 접근자로 연결해서 바꿔주시면 됩니다.

[아두이노 소스랑 processing 소스를 결합]을 하면 다음과 같습니다. const 상수형 표현은 processing에서 에러가 발생하더군요. 그래서 int형으로 만들었습니다.스위치 SW_P 변수로 사용준비만 해놓기만 했고 실제로 사용하지 않습니다. 나중에 이 키값은 3D box 의 색상을 변경하는 코딩을 여러분들이 한번 IF문을 써서 해보세요.

import processing.serial.*;
import cc.arduino.*;


Arduino arduino;

int AXIS_X = 0;
int AXIS_Y = 1;
int SW_P = 3; 

int x=0;
int y=0;

void setup() {  // this is run once.   

    println(Serial.list());
    arduino = new Arduino(this, Arduino.list()[0], 57600);
    arduino.pinMode(SW_P,Arduino.INPUT_PULLUP);

    size(600,600,P3D);
    noStroke();    
} 

void draw() {  
    
    move();
    
    background(0); //배경색
    lights();  //조명

    pushMatrix();  //Start
    fill(0,255,0); //채우기
    translate(width/2,height/2,-100); //이동
    rotateX(radians(y)); //x축 회전
    rotateY(radians(x)); //y축 회전
    
    box(200,100,200); //상자
    popMatrix(); //End
}
 
void move() { 
  int axisX = arduino.analogRead(AXIS_X);
  int axisY = arduino.analogRead(AXIS_Y);  
  
  //X축 방향값  
  if(axisX <= 300){
    x-=1;
  }
  else if(axisX >= 700){
    x+=1;
  }
  //Y축 방향값
  if(axisY <= 300){
    y-=1;
  }
  else if(axisY >= 700){
    y+=1;
  }
  delay(20);
}

따로 방향키 알파벳을 만들 필요 없고 시리얼 호출 함수명을 move()함수명으로 사용자 정의 함수로 변경 했습니다. 아날로그 핀이니깐 0,1번 핀으로 아날로그로 읽으면 A0, A1핀의 값을 읽습니다. 방향키 알파벳 자리에 x, y 변수값을 해당 조이스틱의 방향으로 증감시키면 됩니다.

아마 이 코딩이 지난 시간에 비해 훨씬 편한 코딩인 것 처럼 보일 꺼에요. 아두이노에서 명령을 내린 코딩을 processing으로 전부 가져와야 하니깐 좀 번거롭고 코딩이 될 수 있스니다. 나중에 processing이나 아두이노 코딩이 복잡해지고 길어지면 코딩이 산만해 질 가능성이 있기 때문에 아두이노는 아두이노 코딩을 하고 processing은 processing 코딩을 하는게 좋습니다.

3. 실행 과정


[회로도]


회로도는 동일합니다.

[1단계] : 아두이노는 processing이 사용할 수 있는 조건으로 펌웨어 해야합니다. "Firmata->StandardFirmata" 업로드 시키면 됩니다. 그러면 processing에서 아두이노를 제어할 수 있습니다.




[2단계] : processing에 아두이노+processing 가 합쳐진 소스를 복사해서 붙여 넣으시면 됩니다. 아래 이미지는 기존 소스에서 지워야 했던 시리얼객체변수가 이미지에 남아 있네요. 지웠어야 했는데 오의티네요.


[3단계] : processing 실행하시면 윈도우창이 뜨면서 3D box가 나타납니다. console창에는 com넘버 출력되고 3D box가 정상적으로 출력되면 제대로 실행 된거라 생각히시면 됩니다.


4. 결과


폰으로 촬영하려다 화질이 구려서 포기 했네요. 아래 움직이는 이미지는 어제 사용한 아미지가 아니고 다시 pc 녹화한 영상입니다. 옆에 코딩 소스를 보시면 Arduino 객체가 선언되어 있는 걸 보실 꺼에요. pc 실행 영상만 녹화했습니다. 정상적으로 조이스틱에 따라 3D box가 회전하네요.


마무리


원래는 다른 포스트를 할 예정이였는데 processing은 두가지 방식으로 아두이노를 접근하기 때문에 어쩔 수 없이 두개의 포스트로 나눠서 오늘까지 쓰게 되었네요. 추가적으로, 한번 Bluetooth를 스마트폰에 연결해서 스마트폰으로 조정한 값을 토대로 processing 3D box를 회전를 해보셨으면 합니다. 이건 포스팅 하지 않겠습니다. 금요일 소스에서 스마트폰에서 방향키 값을 Bluetooth로 통해서 아두이노에 시리얼통신 한 값을 다시 pc에 시리얼모니터로 출력하는 문장으로 보내면 그 값을 processing이 읽어서 회전 시키면 되기 때문입니다. 금요일 소스에서 조이스틱 부분 빼고 Bluetooth통신으로 값을 읽고 Serial 통신으로 값을 보내면 끝납니다.

어렵지 않으니깐 한번 해보셨으면 하네요.

댓글()

[아두이노] 조이스틱+processing 3D 도형 회전(1)

IOT/아두이노|2019. 4. 27. 09:00

[아두이노] 조이스틱+processing 3D 도형 회전(1)



오늘은 조이스틱의 키 값을 가지고 지난 시간에 포스트한 3D 도형을 회전시키는 실험을 해보겠습니다. 참고로 오늘 접근 방식은 아두이노는 기존 조이스틱 제어 코딩을 그대로 동작하고 processing에서 조이스틱을 제어한 값을 시리얼통신을 통해서 값을 받아 3D 도형을 회전 시킵니다. 즉, 아두이노는 아두이노 역할에 충실하고 processing은 processing의 역할을 충실한 코딩으로 생각하시면 됩니다.


1. 아두이노 소스


지난 시간에 아두이노 방향키를 만든 소스입니다. 시리얼모니터로 해당 방향 알파벳을 출력한 소스인데 그대로 적용할 예정입니다.

const int AXIS_X = A0;
const int AXIS_Y = A1;
const int SW_P = 3; 
 
void setup() {
  Serial.begin(9600);
  pinMode(SW_P,INPUT_PULLUP);
}
 void loop() {
  //X축 방향값
  if(analogRead(AXIS_X)<=300){
    Serial.println('a');
  }
  else if(analogRead(AXIS_X)>=700){
    Serial.println('d');   
  }
  //Y축 방향값
  if(analogRead(AXIS_Y)<=300){
    Serial.println('w');      
  }
  else if(analogRead(AXIS_Y)>=700){
    Serial.println('s');
  }
  delay(20);
}

2. processing 소스


지난 시간에 3D box를 제어한 포스트에서 다룬 소스를 기반으로 아두이노의 시리얼 통신 값을 가지고 3D box를 제어 해보기 위해서 몇가지 지난 소스에서 수정을 해야 합니다.

복습 : 시리얼 통신



시리얼통신을 사용하기 위해서는 Serial를 프로세싱에 import해야 합니다. 그리고 사용 할 객체 변수를 하나 선언해야겠죠.

import processing.serial.*;

Serial myPort;  

다음으로 myPort를 인스턴스화 해야 합니다.

 println(Serial.list()); //연결된 포트정보
 myPort = new Serial(this, Serial.list()[0], 9600); //시리얼통신 세팅

연결된 포트정보를 list()함수로 찾을 수 있고 그 정보를 시리얼모니터로 출력합니다. 그리고 myPort는 객체변수를 선언했다고 해서 바로 사용되는게 아닙니다. 객체변수 선언은 비유로 들자면 껍때기만 만들어 놓은 거고 인스턴스를 해야 합니다. 할당한다는 의미로 생각하면 될 듯 싶네요. 비유로 알맹이를 채운다고 생각하면 좋을 듯 싶네요.

이렇게 해서 시리얼통신의 준비 작업은 끝났습니다.

다음으로 시리얼통신에서 들어온 데이터를 읽는 방법은 두가지 방법이 있습니다. 아두이노에서 시리얼통신으로 읽는 방식과 동일하게 코딩하는 방법과 processing 자체에서 시리얼통신에서 데이터가 들어오면 자동으로 호출되는 함수가 있습니다. 두번째 호출함수로 코딩을 해보겠습니다.

void serialEvent(Serial p) { 
    명령;
}

SerialEvent()함수가 바로 시리얼통신으로 데이터가 들어오면 바로 호출되는 함수입니다.

void serialEvent(Serial p) { 
  String inString = myPort.readStringUntil('\n');
  char ch=inString.charAt(0);
}

시리얼 통신에서 일부러 문자열로 받았습니다. myPort.readString()함수는 문자열로 읽는 함수입니다. 여기서 myPort.readStringUntile('\n') 함수는 찾고자 하는 인자가 없을 때 null을 반환합니다. 문자열 라인 단위로 읽는다고 생각하면 될 듯 싶네요.

참고로, 아두이노에서는 시리얼통신으로 println('a') 함수로 보내면 "a\n"이렇게 전송되게 됩니다. 받는 쪽에서 만약 문자니깐 함수를 myPort.readChar() 함수를 쓰게 되면 한 글짜씩 개별로 읽게 됩니다. 'a'라는 문자를 보냈지만 추가로 옆에 기호까지 같이 보내지기 때문에 Char으로 읽으면 안됩니다. 나중에 여러개 값을 한번에 보낼 때를 사용하시라고 String 문자열로 읽겠습니다.
'a'라는 한글자지만 문자열이기 때문에 해당 글자를 한글자 문자로 읽어서 변수에 저장하겠습니다. charAt(0)으로 0번째 위치의 문자를 읽는 함수인데 'a'라는 문자를 읽게 됩니다. 그걸 char 문자 변수에 저장하면 되겠죠.

진짜 번거로운 과정을 거쳐서 코딩을 했네요. 그 이유는 나중에 여러분들이 자이로센서같은 것을 다루게 되면 한번에 x,y,z 값을 받아서 읽게 되는데 이때 문자열로 읽고 해당 위치의 값만 빼내서 제어하는데 활용하시라고 미리 이렇게 번거로운 코딩을 하게 되었네요.

시리얼 통신에서 데이터를 읽는 함수의 종류는 다양합니다. 그 다양한 함수들은 위에 링크한 라퍼런스을 보고 한번 여러분들이 따로 코딩해 보셨으면 합니다.

추가로,

inString = trim(inString);

문자열의 시작과 끝에서 공백 문자를 제거하는 함수입니다.("nbsp" 제거) 이 명령라인을 추가해 주시면 더 안정적으로 데이터를 읽겠지만 실험에서는 그냥 사용하지 않았네요. 이런 함수가 있다는 정도만 이해하시고 나중에 활용해 보세요.

ch변수에 겨우 방향키 값을 저장할 수 있게 되었습니다. 지난 3D box 제어에서 키보드로 제어했던 것을 기억하실 지 모르겠네요.

[ 키보드 제어 원 소스]

void keyPressed() {
  if (key == CODED) {
    if (keyCode == UP) {
      y+=1;
    } 
    else if (keyCode == DOWN) {
      y-=1;
    } 
    else if (keyCode == LEFT) {
      x-=1;
    } 
    else if (keyCode == RIGHT) {
      x+=1;
    }
  }
  else if (key == 'z' || key == 'Z') {
      z+=10;
  } 
  else if (key == 'x' || key == 'X') {
      z-=10;
  } 
}

여기서, 수정을 하면은 다음과 같습니다.

void serialEvent(Serial p) { 
    String inString = myPort.readStringUntil('\n');
    char ch=inString.charAt(0);
    println(ch); //시리얼모니터로 방향키 값 출력
    
    if (ch == 'w') {
        y+=1;
    } 
    else if (ch == 's') {
        y-=1;
     } 
     else if (ch == 'a') {
        x-=1;
     } 
     else if (ch == 'd') {
        x+=1;
     }  
}

여기서는 X, Y축을 한꺼번에 if~ else if문으로 표현했습니다. 지난 시간에 방향키 값을 만들 때는 X축과 Y축을 따로 if문을 했는데 여기서 묶여서 처리한 이유가 뭘까요. 방향키 ch변수에는 하나의 값만 존재합니다. 하나의 값만 순서대로 읽어오기 때문에 X,Y축을 구별 안하고 읽어온 하나에 값에 대한 하나의 동작만이 필요하기 때문에 선택문으로 전부 연결한 것이죠.

if문은 상황에 따라 맞게 의미를 이해하시고 사용하셔야 합니다.

전체적으로, 코딩을 하면 아래와 같습니다.

import processing.serial.*;

Serial myPort;

int x=0;
int y=0;

void setup() {

    println(Serial.list());
    myPort = new Serial(this, Serial.list()[0], 9600);
    myPort.bufferUntil('\n');
    noStroke();
    size(600,600,P3D);
    
} 

void draw() {  
    background(0); //배경색
    lights();  //조명

    pushMatrix();  //Start
    fill(0,255,0); //채우기
    translate(width/2,height/2,-100); //이동
    rotateX(radians(y)); //x축 회전
    rotateY(radians(x)); //y축 회전
    
    box(200,100,200); //상자
    popMatrix(); //End
}
 
void serialEvent(Serial p) { 
  String inString = myPort.readStringUntil('\n');
  char ch=inString.charAt(0);
    
  println(ch);
  
  if (ch == 'w') {
      y+=1;
  } 
  else if (ch == 's') {
     y-=1;
  } 
  else if (ch == 'a') {
     x-=1;
  } 
  else if (ch == 'd') {
     x+=1;
  }
}

3. 실행 과정


[회로도]


[1단계] : 아두이노 IDE에 방향키 소스를 복사해서 붙여 넣으시고 화살표에 업로드 버턴을 누르면 아래 정상적으로 처리되면 업로드 완료라고 뜹니다.


[2단계] : 시리얼모니터로 결과 검사



정상적으로 방향키값이 시리얼모니터로 출력되는 것을 확인 하실 수 있습니다.

[3단계] : processing에 오늘 수정한 코드를 복사해서 붙여 넣으시고 화살표 실행 버턴을 누르시면 아래 그림처럼 console창에 방향키값이 정상적으로 출력되는 것을 확인 하실 수 있을 겁니다.



[4단계] : processing에서 만든 윈도우창이 뜨면서 3D box가 보여지는데 조이스틱으로 조정을 하면 이 3D box가 움직이는 걸 확인 하실 수 있을 꺼에요.


4. 결과


processing에서 실제로 조이스틱을 조정하면 움직이는 이미지 입니다.


실제 폰으로 촬영한 영상으로 살펴 봅시다.


만약, 여러분들이 processing를 좀 더 깊게 공부하시면 아두이노와 processing를 연동한 재밌는 작품들을 만드실 수 있을 거라 생각됩니다.

마무리


이렇게 시리얼통신을 통해서 읽은 데이터 값으로 processing 제어하는 실험을 하였습니다. 예전에는 processing에서 아두이노를 제어 했다면 반대로 아두이노에서 processing를 제어한 실험인데 그렇게 어렵지 않죠. 여기서 부터 출발하여 좀 더 다양한 시각화 표현을 processing 에서 코딩하면 좀 더 멋진 작품으로 만들어 질 수 있습니다.

여러분들도 한번 다른 모형을 만들어서 제어를 꼭 해보셨으면 합니다. 최대한 의미 전달을 목적으로 코딩을 최소화 하기 위해서 box()함수로 상자도형을 간단히 제어를 했짐나 좀 더 멋진 모형을 만들어서 멋진 작품을 만드셨으면 하네요.

댓글()

[아두이노] 조이스틱 방향키 값 출력

IOT/아두이노|2019. 4. 26. 09:00

[아두이노] 조이스틱 방향키 값 출력



지난 조이스틱 포스트에서 현재 상태값을 출력하는 실험을 했었습니다. 그리고 조이스틱 두번째 포스트에서는 조이스틱 X축 0~1023 아날로그 신호 값을 기준으로 Servo Motor를 제어도 해보았습니다. 마무리에 방향 조절에 대한 로직을 간단히 글로 소개 한 적이 있는데 이번에 제대로 이 주제에 대해 포스트를 할까 합니다.


날씨가 우중충해서 그런가 사진이 이상하게 나왔네요.

1. 조이스틱 방향키 값 만들기




위그림은 지난 조이스틱 회로도 입니다.


조이스틱의 X(A0)핀, Y(A1)핀 값을 위 그림과 같은 방향 키값으로 만들어 내려고 합니다.

[복습] : 조이스틱 현재 상태값 출력

const int AXIS_X = A0;
const int AXIS_Y = A1;
const int SW_P = 2; 
 
void setup() {
  Serial.begin(9600);
  pinMode(SW_P,INPUT_PULLUP);
}
 
void loop() {

  Serial.print("Axis_X: ");
  Serial.println(analogRead(AXIS_X));

  Serial.print("Axis_Y: ");
  Serial.println(analogRead(AXIS_Y));

  Serial.print("SW_P:  ");
  Serial.println(digitalRead(SW_P));

  delay(500);
}

위 소스는 조이스틱의 상태값을 시리얼모니터로 출력하는 예제입니다. 방향키 값을 만들기 위해서 Axis_X, Axis_Y 값에서 방향값으로 분리해 낼 능력이 필요합니다.

Axis_X, Axis_Y 값은 0~1023의 아날로그 값을 가집니다. 이론상으로 조이스틱의 중심값은 (X, Y)은 (512, 512)가 됩니다.

여기서, X축으로 아날로그 값 0~1023에서 A방향의 아날로그 신호값의 범위는 0~512 사이가 되고 D방향의 아날로그 신호값의 범위는 512~1023 사이가 됩니다. 그런데 실제로 조이스틱은 512가 중심값이 아니라고 했죠. 하드웨어 자체 조립과정의 문제와 환경적 요인에 의해 512의 근사값이라고 저번 시간에 이야기 했는데 오늘 실험에서는 정밀 제어가 아닌 기초적인 간단한 제어임으로 512이라고 가정하겠습니다.

위 그림을 보면 동그라미 모양이 있죠. 이 동그라미 영역은 방향 불인정 영역으로 잡아 놓고 나머지 영역을 방향으로 하면 방향을 명확하게 나눌 수 있습니다. 즉, 조이스틱의 방향을 움직일 때 동그라미 영역을 무시하고 그 나머지 신호값을 해당 방향의 키값으로 설정하면 명확하게 나누어지게 됩니다. 그 이유는 중심 근처는 방향 신호값의 변화가 너무 심하기 때문입니다. A와 D값의 교차 변화가 중심 근처에서 빈번하게 발생하면 키 값이 교차로 빈번하게 출력되고 그 값을 통해서 움직이는 물체는 떠는 것처럼 "덜덜덜" 하면서 움직이게 됩니다. 움직임의 값이 크게 세팅하면 왔다 갔다 반복하는 현상이 그 중심점에서 빈번하게 일어난다고 생각하시면 될 듯 싶네요. 그래서 중심지점 근방은 방향 신호값으로 인정 안함으로써 좀 도 명확하게 방향키 값을 잡는 것이죠.

제 경우는 그냥 500을 기준으로 X축 좌/우 방향을 200씩 무시구간을 잡았습니다. 정확히 200이 아니라 그냥 500기준 200으로 정교함보다는 의미 전달 방향키 값임을 참고하고 보시기 바랍니다.

[X축 키값 구하기]

  //X축 방향값
  if(analogRead(AXIS_X)<=300){
    Serial.println('a');
  }
  else if(analogRead(AXIS_X)>=700){
    Serial.println('d');   
  }

위 코딩은 X축을 기준으로 방향키 a, d를 출력하는 로직입니다. 500을 기준으로 300이하일때 왼쪽(a)키로 인정하고 700이상일때 오른쪽(d)키로 인정한다는 문장입니다.
만약에, 정확하게 나눌려면 조이스틱 X중심이 521로 설정되어 있으면 200차이로 나눈다면 왼쪽 기준은 321이고, 오른쪽 기준 721이겠죠. 간단한 실험이라 수치를 보기 편하게 단순화 시킨 수치입니다. 여러분들은 조이스틱로 실제 제어한다면 300, 700으로 하지 말고 좀 더 조정된 값으로 제어를 하셨으면 합니다.

여기서, if문을 개별적으로 해도 되지만 else if문을 쓴 이유가 뭘까요. AXIS_X 기준 신호로 보면 기준 신호 값에서 +, - 값인 둘중 하나상태만 존재하기 때문입니다. 예를 들면은 X축의 조정신호 값이 오른쪽이면서 왼쪽일 수 없잖아요. 오른쪽이면 오른쪽 신호값만 왼쪽이면 왼쪽 신호값만 존재합니다. 동시 오른쪽, 왼쪽이 나올 수 없습니다. 그래서 if~else if 문으로 왼쪽이냐 묻고 아니면 오른쪽이냐 묻는 문게 했습니다. 이 표현이 가장 괜찮은 코딩으로 이렇게 if~else if문을 사용하게 되었습니다.

반대로, Y축을 살펴볼까요 동일합니다.

[Y축 키값 구하기]

//Y축 방향값
  if(analogRead(AXIS_Y)<=300){
    Serial.println('w');      
  }
  else if(analogRead(AXIS_Y)>=700){
    Serial.println('s');
  }

그러면, 여기서 왜 X축과 Y축을 다 묶어서 if~else if문을 쓰지 않았을 까요.

  if(analogRead(AXIS_X)<=300){
    Serial.println('a');
  }
  else if(analogRead(AXIS_X)>=700){
    Serial.println('d');   
  }
  else if(analogRead(AXIS_Y)<=300){
    Serial.println('w');      
  }
  else if(analogRead(AXIS_Y)>=700){
    Serial.println('s');
  }

이렇게 안한 이유는 조이스틱에서 X, Y축을 동시에 읽어오게 됩니다. 그러면 X축을 읽고 X축의 값에 만족하는 왼쪽/오른쪽이 결정되면은 Y축은 무시하고 넘어가게 됩니다. 그래서 X축의 방향키를 구하고 Y축의 방향키를 구하는 식을 따로 if문을 분리해서 코딩한 것이죠.

if문을 어떻게 처리하냐에 따라서 그 의미는 완전 달라지기 때문에 if문도 신중하게 써야 합니다. 그리고 위 코딩도 썩 좋은 코딩이 아닙니다. 해당 축을 읽는 함수는 한번으로 하면 좋지만 두번 연속으로는 읽는다면 비효율적이겠죠.

int x = analogRead(AXIS_X);
int y = analogRead(AXIS_Y);

x, y값을 if문으로 비교하시면 구지 두번 반복해서 읽을 필요는 없게 됩니다. 전 그냥 2번 반복읽기로 뒀네요.

2. 코딩


const int AXIS_X = A0;
const int AXIS_Y = A1;
const int SW_P = 3; 
 
void setup() {
  Serial.begin(9600);
  pinMode(SW_P,INPUT_PULLUP);
}
 void loop() {
  //X축 방향값
  if(analogRead(AXIS_X)<=300){
    Serial.println('a');
  }
  else if(analogRead(AXIS_X)>=700){
    Serial.println('d');   
  }
  //Y축 방향값
  if(analogRead(AXIS_Y)<=300){
    Serial.println('w');      
  }
  else if(analogRead(AXIS_Y)>=700){
    Serial.println('s');
  }
  delay(20);
}

스위치 버턴도 선언은 했지만 그냥 해당 버턴키는 만들지 않았습니다. 다음에 processing과 연동 할 때 줌인/줌아웃키로 하려고 햇는데 스위치버턴이 한개라 따로 뻥판에 스위치 버턴을 만들어 표현 할 수 있었지만 그냥 방향제어만으로만 포스팅을 하고 위 틀은 확장 코딩을 할 수 있는 상태로 표현했다고 생각하시면 될 듯 싶네요. 나머지 코딩은 여러분들이 추가로 스위치 버턴을 가지고 제어해보시라고 남겨 둡니다.

미완성 코딩으로 위에 아날로그 신호값을 읽어 변수에 저장하는 것과 스위치 버턴을 추가로 로직을 짜셔 완성 시켜보세요.

3. 결과


시리얼 모니터로 출력되는 영상 입니다.


깔끔하게 잘 나오네요. 다음은 폰으로 조이스틱을 움직일 때 키 값이 나오는 영상입니다.


마무리


오늘은 간단히 조이스틱의 방향 키 값을 시리얼모니터로 출력 했습니다. 이 출력이 무슨 키 값이야 하고 생각하실 수 있지만 내일 포스트는 processing과 연동합니다. 시리얼통신으로 보내지는 이 알파벳 값을 processing에서 읽고 그 값을 기준으로 지난 processing 예제인 box 3D 도형을 도형을 움직이게 할 예정 입니다. 키보드로 제어 했던 부분을 오늘 배운 키 값으로 제어하겠다는 의미인 셈이죠. 왜! 키 값인지 아시겠지요.

오늘 포스트 한 내용을 잘 따라오셔야 내일 재밌는 것을 할 수 있습니다.

이상 마무리 하고 오늘도 배운 내용을 어디서 써먹을지 상상의 나래를 펼쳐 보세요.

댓글()

[아두이노] 조이스틱 + Servo Motor 제어

IOT/아두이노|2019. 4. 20. 09:00

[아두이노] 조이스틱 + Servo Motor 제어



조이스틱으로 뭔가를 조정하는 응용 예제로 소개할 만한게 없나 고민하다가 지금까지 포스트 한 것 중에서 찾다가 조정 느낌이 느낄 수 있는 LED로 실험 했다가 그냥 포스트에 올리는 걸 포기했네요. 다른 부품으로 찾다가 Servo Motor를 제어를 해보는게 재밌 소재가 될 것 같고 따로 코딩하는 부분도 별로 없을 것 같아 실험 주제로 결정 했네요. 코딩도 아주 초 간단하게 원리만 표현 했고 지난 시간에 조이스틱 조정기 값을 읽는 것을 배웠으니깐 이제는 그 값을 기준으로 Servo Motor의 Angle을 정하면 되니깐 별로 어렵지 않고 재밌는 포스팅이 될 것 같네요.

자! 그러면 Servo Motor를 조이스틱으로 제어를 해 봅시다.

1. 회로도 구성


  • 준비물 : 조이스틱 1개, Servo Motor 1개, Red LED 1개, 저항 220옴 1개, 아두이노우노
  • 내용 : A0, A1 핀은 x,y 방향 아날로그 신호를 받고 5번핀은 스위치 신호를 받도록 선은 연결하고 7번핀에 Servo Motor 출력핀으로 연결하고 12번핀은 Red LED의 출력으로 사용한다.

[ Fritzing 디자인 ]


회로도를 보시면 좀 복잡해 보일 수 있는데, 조이스틱에서 방향 신호(VRX, VRY)와 스위치 신호(SW) 선만 아날로그 핀과 디지털 핀에 연결 하고, Servo Motor핀과 LED 핀들은 사용하고자 하는 디지털 핀을 선택해서 연결하시면 됩니다. 나머지는 다 전원에 관련된 핀이라 회로도 그림만 좀 복잡해 보이고 선 연결은 어렵지 않습니다.

2. 코딩



함수

  • pinMode(사용핀, 입/출력모드) : 해당 사용핀을 어떤 모드로 사용할지 결정한다. INPUT_PULLUP모드로 설정(내부저항을 사용할 예정)
  • analogRead(아날로그핀) : 아날로그 신호를 읽음(0~1023)
  • digitalRead(디지털핀) : 디지털 신호를 읽음(0 or 1)
  • digitalWrite(출력핀, HIGH/LOW) : 출력핀에 HIGH(5V) or LOW(0)를 출력
  • delay(시간값) : 시간값만큼 대기한다.
  • map(입력값,입력최소,입력최대,출력최소,출력최대) : 입력값이 입력 최소~최대범위가 출력 최소~최대에 매칭되어 출력

Servo Motor 함수

#include <Servo.h> 
  • Servo servo : 서보모터 객체 선언
  • servo.attach(서보핀) : 서보 모터에 출력을 담당할 핀 선언
  • servo.write(회전각) : 회전각 만큼 서보모터를 회전 시킴

설계

조이스틱의 어떤 값으로 Servo Motor를 제어 해야 할까요. X 방향, Y 방향, 스위치 버턴 이 세가지 신호 중에 어떤 것을 사용할까요. 여기서, X, Y 값 중에 하나를 선택하면 됩니다. Servo Motor 한개가 하나의 축 방향이라고 생각하면서 접근하시면 됩니다.

간단히, 저는 X 방향 아날로그 신호로 Servo Motor를 제어 해볼까 합니다.

그러면 조정값을 어떻게 Servo Motor랑 매칭해야 할까요. 한번 생각을 해보세요.

조이스틱이 처음 어떤 상태로 어떤 값을 초기값으로 되어 있나요. 조이스틱의 현재 상태와 현재 신호를 곰곰히 생각 해보시면 그 안에 답이 있습니다. 바로, 조이스틱은 방향 아날로그 신호값이 중앙값으로 처음 시작 합니다. 즉, 0~1023의 아날로그 신호에서 512이라는 중심값이 초기 상태로 아날로그 신호로 시작 합니다.

이때 왼쪽은 0~512 사이가 되고 오른쪽은 512~1023 사이의 값으로 좌우 방향을 나타낼 수 있습니다. 이 원리를 이용하시면 쉽게 해결 됩니다. 512를 기점으로 좌우 신호 값으로 Angle를 표현하면 되니깐요. 여기서 0~1023의 아날로그 신호값에서 0도가 0의 값이 되고 180도는 1023이 되게 하면 중앙값 512은 90도가 되고 이 개념을 가지고 코딩을 하면 됩니다.

그렇게 하면, 처음 시작은 중앙값 512로 Servo Motor은 90도에서 시작하게 되고 아날로그 신호가 0에 가까울 수록 0도에 가까워지고 아날로그 신호가 1023에 가까울 수록 180도에 가까워지겠죠.

여기서, 문제점은 0을 0도로 1023을 180도로 어떻게 표현 할까요. map()함수를 이용하면 됩니다. 은근히 자주 사용하는 함수인데 여기에서도 사용 하네요.

m_Angle = map(analogRead(AXIS_X),0,1023,0,180);

AXIS_X핀의 아날로그 신호값이 0~1023 범위에서 출력 0~180범위에 어느 정도의 위치가 되는지 알아서 해당 값을 찾아주는 함수입니다. 만약, 512값이 입력신호로 들어오면 출력 0~180 범위에서 90이라는 값의 위치하니깐 90이 반환되어 나옵니다. 즉, 입력신호값을 0~180사이의 값으로 자동으로 변환시켜주는 함수인 셈이죠.

이 한줄로 조이스틱의 X방향의 아날로그 신호를 각도로 만들어 낼 수 있겠죠.

 servo.write(m_Angle); 
 delay(100); 

아날로그 신호 512일 때, Servo.write(90)함수로 90도를 Servo Motor가 회전하게 됩니다. delay()함수는 Servo Motor가 충분이 회전할 시간값을 줘야 하기때문에 같이 코딩 해야 합니다. 100은 0.1초인데 더 짧게 하시면 좋겠죠. 그냥 이전 포스트 소스를 가져다가 표현한거라 딜레이 시간은 그냥 뒀네요. 원래 저 값은 특정한 각도로 회전 한뒤에 다시 다른 특정한 각도로 회전할 때까지의 충분한 시간 값인데 조이스틱 조정기에서는 이렇게 큰 시간값은 필요 없습니다. 각도 값 범위가 변화율은 크지 않기 때문에 짧게 시간을 주면 됩니다. 100은 아주 큰 값이지만 조정기로 해본 결과 그냥 사용해도 제 실험에서는 상관 없어서 그냥 뒀네요.

결론은 조이스틱의 X방향 신호값을 읽어와서 map()함수로 각도를 만들어 내고 servo.write()로 각도를 출력하는 코딩으로 딱 두줄이면 조이스틱으로 Servo Motor를 움직이게 할 수 있게 됩니다.

그러면, 전체 소스를 살펴봅시다.

[ 소스 ]

#include <Servo.h> 

Servo servo;

const int AXIS_X = A0;
const int AXIS_Y = A1;
const int SW_P = 3; 

const int servoPin = 7;
const int redLed = 12;

int m_Angle = 0;
 
void setup() {
  servo.attach(servoPin);
  pinMode(redLed, OUTPUT);
  pinMode(SW_P,INPUT_PULLUP);
}
 
void loop() {
  m_Angle = map(analogRead(AXIS_X),0,1023,0,180);
  digitalWrite(redLed,digitalRead(SW_P));
  servo.write(m_Angle); 
  delay(100);   
}

변수가 코딩의 절반을 차지 하네요. 정작 로직 코딩은 몇 줄 안되는 데 말이죠.

  digitalWrite(redLed,digitalRead(SW_P));

서비스 코딩으로 스위치 버턴을 누르면 12번에 연결된 Red LED가 꺼지도록 했네요. 조이스틱 스위치 버턴은 내부풀업저항을 이용하기 때문에 초기값은 '1'이 됩니다. 그래서 전원이 공급되면 Red LED에 불이 들어오게 됩니다. 그리고 스위치를 누르면 '0'의 상태가 되어서 Red LED는 불이 꺼지게 됩니다.

4. 결과




5. 추가 내용


위 실험 코딩은 그냥 조정기의 0~1023값을 단적으로 0~180도로 나눠서 조정한 거라서 실제로 뭔가 조정하거나 정밀 제어 할 때는 사용하지 않는 코딩입니다. 단순히 조정기를 움직일 때 그 움직임 값으로 Servo Motor를 움직이게 한 것 뿐이니깐요.

원래는 조정기에 대한 코딩을 제대로 하실려면은 조정기를 움직일 때에 채터링 문제 또는 조정값의 범위라던가 조정기의 수치에 따른 회전 속도라든가 고려해야할 것이 산더미로 많습니다. 거기에 조종기의 중심값의 보정 작업도 추가 해야 합니다.

이런것들을 다 코딩하면 회전 하나의 동작을 하기 위해서 수십 줄의 코딩으로 늘어나게 됩니다. 배보다 배꼽이 더 커지겠죠. 여기서 배워야할 것은 Servo Motor를 조이스틱으로 회전할 수 있는 기초 원리를 배우는게 목적이기 때문에 간단하게 원리만 전달하는 코딩만 한 것이니깐 여러분들이 실제로 뭔가를 제어할려면 많은 부분을 생각하고 코딩 하셔야 합니다.

그리고, 여러분들이 이 기초를 이해 하셨다면 조정기에 필요한 부분이 뭐가 있을지 생각하고 여러가지 상황들을 제시하고 그 문제를 해결하면서 코딩을 늘려가셨으면 합니다.

마무리


조이스틱으로 Servo Motor를 제어할 수 있게 되었네요. 한개의 Servo Motor를 제어 했지만 여기서 Y방향 값도 같이 적용을 하여 Motor 두개를 사용한다면 어떻게 될까요. x와 y방향으로 평면의 좌표로 이동할 수 있게 됩니다. 3D 프린터나 평면 그림을 그리는 프린트기 같은 걸 만들 수 있겠죠. 여기에 사용되는 Motor는 Stepper Motor로 360도를 회전하는 것을 사용합니다. 어찌 되었든 조정기로 뭔가를 움직이는 물체를 제작 가능해 지겠죠.

마지막으로, 뭔가 로봇 같은 관절 제어 쪽으로 상상력을 발휘해 보세요. 재밌는 상상력들을 많이 해 보셨으면 합니다.

예를들어, 로봇 손가락을 상상해 보세요. 자신의 손을 쫙 펴보세요. 손가락 하나에는 3마디로 구성 되어 있잖아요. 이 각 마디와 마디 사이에는 관절이 있습니다. 이 3개의 관절을 Servo Motor가 대신 한다고 상상을 해보세요. 손을 다시 주먹을 쥐어보세요. 한마디가 구부러질 때 몇도의 각도가 되나요. 각 관절이 90도에 가깝게 꺽기게 됩니다. 그러면 Servo모터로 3개의 관절을 동일하게 angle을 0도에서 서서히 90도에 가깝게 일정한 속도로 회전 시키면 어떻게 될까요. 주먹을 쥐는 형태로 자연스럽게 구브러 지겠죠. 다시 펼 때는 90도에서 서서히 0도로 3개의 Servo모터를 회전 시키면 어떻게 될까요 서서히 손가락을 펴는 모습이 되겠죠.
직접 자신의 손을 주먹을 쥐었다 폈다를 반복하면서 머리속에서 손가락 관절의 각도를 그려 보세요. 그 원리를 깨우치게 되면 로봇 손가락 관절 제어를 할 수 있게 됩니다.

일상의 모습을 보고 우리는 그걸 코딩화 할 수 있습니다. 일상의 모습을 코딩화 하기 위해서는 끊임없이 상상력을 끌어 올려야 하고요. 계속 자신에게 어떤 것을 배우고 다룰 수 있게 되면 다음에 뭘 배우지 보다는 이것을 이용하여 뭘 할 수 있지 하고 2개 이상의 응용할 수 있는 상상력 훈련을 하셨으면 합니다.


댓글()

[아두이노] 조이스틱 제어

IOT/아두이노|2019. 4. 19. 09:00

[아두이노] 조이스틱 제어



최근 포스팅 내용이 너무 코딩에 치우친 것 같아서 오늘은 가벼운 마음으로 간단히 실험 할 수 있는 주제로 꾸몄습니다. 가상시뮬레이터에서 제공하지 않지만 설명만으로 대충 이해 할 수 있는 부품이여서 부담 없이 읽으시면 됩니다. 오늘 다룰 주제는 조이스틱으로 꽤 재밌는 모듈입니다. 방향을 조정 할 수 있는 모듈로서 RC카와 같은 뭔가 조정이 필요한 곳에 사용되는 모듈입니다. 아두이노마이크로나 레오라르도보드가 있다면 컴퓨터 마우스를 만들고 싶은 부품입니다. 아무튼 조이스틱의 구조는 복잡하지 않습니다. 조이스틱 모듈에서 만들어내는 신호는 방향을 표현하는 아날로그 신호와 스위치를 표현하는 디지털 신호가 있습니다. 이 둘 신호에 대해 읽기를 할 수 있다면 쉽게 제어가 가능한 부품입니다. 간단한 실험으로 어떻게 동작하는지 살펴보도록 하겠습니다.

1. 조이스틱의 구조

위 사진에서 자세히 보면 Gnd, +5V, VRX, VRY, SW 이렇게 5개의 핀으로 구성되 있습니다. Gnd, +5V은 전원부분에 해당되고 나머지 3개의 핀에서 VRX, VRY은 아날로그 출력핀으로 방향좌표(x,y) 아날로그 신호를 만들어 내고 SW은 디지털 출력핀으로 스위치 버턴 역할로 디지털 신호를 만들어 냅니다. 참고로 위 사진에서 스위치는 방향 조정기의 몸통을 누르거나 옆에 스위치 버턴이 있습니다.

방향(x,y)과 스위치 한개를 누를 수 있는 모듈이구나 정도로 이해하시면 됩니다.

그러면 조이스틱의 방향키를 움직였을 때 아날로그 신호는 어떻게 변하는지 살펴보도록 할까요.

X, Y 좌표를 나눠서 생각하시면 됩니다. 위 그림처럼 가운데 동그라미가 조이스틱 중심 지점이라고 생각 하십시오, 그리고 조이스틱이 그 중심에서 오른쪽, 왼쪽, 위, 아래 그리고 각 대각선으로 나눠서 구분하시면 됩니다.

그러면 간단히 살펴볼까요.

아날로그 신호는 X축으로 0~1023의 값을 읽을 수 있습니다. 그 중심값 512가 됩니다.
아날로그 신호는 Y축으로 0~1023의 값을 읽을 수 있습니다. 그 중심값 512가 됩니다.

그러면 방향조정기를 조절하고 그 신호값을 만들어 낼려할 때 여러분들이 조이스틱 설계자라면 아날로그 신호에서 어디를 기준으로 설계 할 까요. 바로 중심값을 기준으로 잡겠죠. 아날로그 신호가 0~1023값이 고정되어 있으니 X축을 기준으로 좌우의 방향을 나눌려면 어느 위치를 중심으로 해야 할까요. 바로 신호값 512가 되겠죠. 아날로그 신호 512 위치가 중심이 되면 좌측은 X신호값이 0~512사이의 값이 되고 우측은 512~1023사이의 값이 되겠죠. 이 값을 기준으로 좌측과 우측의 이동 신호값으로 표현하면 됩니다. Y축도 이와 같습니다.

처음에 핀을 아두이노에 연결하면 읽은 신호값이 초보분들은 정확히 뭘 의미하는지 이해를 잘 못하지만 위 그림처럼 그림을 그리고 나서 가운데 원이 방향조정기로 움직인다고 상상하면서 좌표를 이해하시면 됩니다.

방향 좌표 수치를 이해가 되셨다면, 중심(X,Y) = (512,512)을 기준으로 신호값이 실제 조정기를 해당 방향으로 움직이면 어떻게 변하는지 이해 하실 수 있을 거라 생각됩니다.

주의사항

현실에서는 중심점(512,512) 값을 가지지 못합니다. 그 이유는 조이스틱이 기계이고 조립과정 또는 환경 요인에 의해서 중심값은 (512,512)가 나오지 않습니다. 같은 모듈이여도 제각각으로 중심값 신호가 나옵니다. 실제 측정한 조이스틱 중심값을 다시 선을 뽑았다 다시 연결하니깐 약간의 수치가 살짜 1정도 다른 값이 나오기도 하더군요. 그래서 초기 기준값을 정하는 보정 작업이 필요 합니다. 오늘은 코딩적인 부분을 다루는 것이 목적이 아니고 간단히 조이스틱이 어떻게 생겼으며 어떻게 값을 읽을지를 알아보는 실험이니깐 코딩에 대해서 깊게 안들어가고 간단히 이해만 해주세요.

만약, 보정작업을 수행하고 싶으시다면 중심값 관련 보정 작업은 중심값의 환경적 요인에 의해 차이가 나니깐 일정 값의 범위를 지정해서 그 값 안에 있으면 중심으로 간주하는 방법으로 보정해 주는게 가장 간단한 보정 할 수 있는 방법이라고 생각 됩니다.

인터넷에서 조이스틱 실험 예제들을 살펴보시면 정확한 수치로 쪼개서 나누지 않고 방향의 범위값을 허용하는 일정 범위를 지정해 놓고 그 범위에 들어오면 방향을 움직이는 걸로 판정을 내리더군요. 그게 가장 간단하면서 문제를 쉽게 해결할 수 있는 방법이라고 저도 생각 되네요.

아무튼 실제로 테스트 한 결과값은 아래 실험에서 자세히 나와 있으니깐 제가 쓰는 조이스틱의 중심값이 어떻게 나왔는지 아래 포스팅 내용을 보시기 바랍니다.

2. 회로도


  • 준비물 : 조이스틱 1개, 아두이노우노
  • 내용 : A0, A1 핀은 x,y 방향 아날로그 신호를 받고 5번핀은 스위치 신호를 받도록 선은 연결 하시오.


[ Fritzing 디자인 ]

조이스틱은 아날로그 핀 2개랑 디지털 핀 1개를 아두이노에서 여러분들이 원하는 핀을 선택하여 연결하시면 됩니다. 따로 복잡한 부분은 없습니다.. 실제로 조이스틱을 구매 안하셔도 이런 부품이 있고 대충 연결은 어떤식으로 이루어지는지만 위 그림을 보시면 됩니다. 조이스틱 모듈에 핀 이름이 다 적혀 있으니깐 위 그림을 안보더라도 쉽게 선을 연결 할 수 있을 거라 생각됩니다.

3. 코딩


  • 사용함수 : pinMode(), analogRead(), digitalRead(), delay(), Serial.begin(), Serial.println()
  • 내용 : 시리얼모니터로 조이스틱 값을 출력하자.

함수

  • pinMode(사용핀, 입/출력모드) : 해당 사용핀을 어떤 모드로 사용할지 결정한다. INPUT_PULLUP모드로 설정(내부저항을 사용할 예정)
  • analogRead(아날로그핀) : 아날로그 신호를 읽음(0~1023)
  • digitalRead(디지털핀) : 디지털 신호를 읽음(0 or 1)
  • delay(시간값) : 시간값만큼 대기한다.

통신

  • Serial.begin(9600) : 시리얼통신 시작
  • Serial.println(값) : 시리얼모니터로 값을 출력함.

[ 소스 ] : 제 블로그에 있던 소스를 가져옴.(수정하기 귀찮아서요)

const int AXIS_X = A0;
const int AXIS_Y = A1;
const int SW_P = 2; 
 
void setup() {
  Serial.begin(9600);
  pinMode(SW_P,INPUT_PULLUP);
}
 
void loop() {

  Serial.print("Axis_X: ");
  Serial.println(analogRead(AXIS_X));

  Serial.print("Axis_Y: ");
  Serial.println(analogRead(AXIS_Y));

  Serial.print("SW_P:  ");
  Serial.println(digitalRead(SW_P));

  delay(500);
}

방향 x, y 값은 아날로그 신호값 => analogRead(AXIS_X), analogRead(AXIS_Y)
스위치 값은 디지털 신호 값 =>digitalRead(SW_P)

참고로 내부풀업저항을 사용하기 때문에 스위치버턴의 초기 값은 1이 됩니다. 스위치를 누르면 디지털 신호는 0이 됩니다.

내부풀업저항이 기억이 안나면 다시 복습해야 겠죠. =>[아두이노] 스위치 버턴 제어

4. 결과


위 사진을 보면 조이스틱의 오차값이 발생합니다. 정확하게 중앙값이 생성되지 않습니다. 조이스틱을 만지지 않을 때 정중앙에 위치하는데 그 값이 X=499이고 Y=529가 표시 되네요. 중앙이니깐 X=512, Y=512 이라고 생각하시면 절대 안돼요. 아날로그 값을 읽을 때는 처음 읽을 때 자신이 생각하는 값이 나오지 않습니다. 그래서 초기 측정값을 기준으로 처음 보정 작업이 필요 합니다. 기준값을 우선 찾은 후 그 기준값으로 아두이노에서 어떻게 제어 할지를 정해야 합니다. 그리고 조이스틱은 같은 제품이여도 X, Y값은 약간씩 차이가 납니다. 그렇기 때문에 이 포스트 보고 포스트에 나온 값에 맞춰서 로직을 짜야지 하면 안되고 우선 자기가 사용하는 부품의 초기값이 몇인지를 체크하시고 나서 로직을 짜셔야 합니다.

마무리


조이스틱을 다루는데 어렵지 않죠. 해당 핀에 아날로그 신호랑 디지털 신호만 읽어오면 됩니다. 여기서, 조이스틱을 움직 일 때 그 움직임에 맞춰서 LED에 불이 들어오게 설계를 했는데 동일한 응용 예제를 누가 이미 다른 블로그에 시연을 해버려서 코드로직도 90% 이상 응용코드가 일치해서 실험은 했는데 포스트에 올리지를 못하게 되었네요. 기본 동작 소스는 90%이상 일치하더라도 이건 어쩔 수 없는 부분이지만 응용편이 90% 일치한 내용이 있다면 사실 올리기 좀 껄꺼롭기 때문에 실험은 했고 영상은 있는데 그냥 포기 했네요.

말로만 설명하자면 X축을 기준으로 if문으로 512을 중앙으로 가정하고 512이하면 0~512 사이면 왼쪽 LED에 불이 들어오고 아니면 512이상이면 512~1023 사이면 오른쪽 LED에 불이 들어오게 하고 Y축은 이와 동일하게 IF 조건문을 세우면 방향에 따른 LED에 불이 들어 오겠죠. 그리고 스위치 부분은 스위치가 눌러지면 LED 하나를 스위치 담당 LED로 해서 해당 LED에 불이 들어오게 표시하면 간단히 조이스틱을 조정하여 시각적으로 LED로 표현할 수 있었는데 너무 동일한 응용 예제라서 만들어 놓고 포스트에는 올릴 수 없었네요.

마지막으로, 가상시뮬레이터에서 제공되지 않는 부품 모듈이지만 한번 조이스틱으로 여러분들은 뭘 만들고 싶은지 상상만 해보셨으면 해요. 다른거로 표현 할 만한게 떠오르면 응용 예제로 올려 보도록 할께요. LED 응용 회로로 다 만들었는데 그냥 분해 했네요.


댓글()