[아두이노] MPU-6050 센서 + processing 연결 제어

IOT/아두이노|2019. 5. 13. 09:00

[아두이노] MPU-6050 센서 + processing 연결 제어



어제 MPU-6050 가속도/자이로센서에 대해 간단히 테스트를 해 보았습니다. 어제는 MPU-6050 라이브러리 없이 I2C 통신으로 센서의 값을 가져왔는데 오늘은 MPU-6050 라이브러리를 가지고 쉽게 제어하는 방법을 살펴보도록 하겠습니다. 직접 식을 만들고 그 식을 통해서 제어하면 좋겠죠. 하지만 입문자나 초보분들은 수학적으로 알아야 할 것이 너무 많습니다. 입문자의 경우는 수학적으로 접근한다면 실제 MPU-6050를 다루기 전에 지쳐버리겠죠. 처음에는 흥미를 가지고 출발해야 좀 더 관심을 가지고 공부하게 됩니다. 수학적인 부분을 모르더라도 누군가 만들어 놓은 라이브러리를 활용하여 MPU-6050 모듈을 사용할 수 있으니 부담없이 처음에 접근하기 바랍니다. 가격이 저렴한 모듈이기에 정교한 제어용으로는 다소 무리가 있습니다. 단지 입문자용으로 부담없이 사용할 수 있는 모듈이라고 생각하시면 되겠습니다.

오늘은 실험에 사용할 라이브러는 인터넷에서 찾아보면 두개의 라이브러리가 있는데 아두이노 IDE 라이브러리관리 창에서 검색한 라이브러리를 설치하여 사용하겠습니다. 그리고, 이 라이브러리 예제를 통해 얻은 값을 테스트 한 후에 이 예제를 통해서 processing으로 만든 box 도형을 MPU-6050 모듈로 움직임을 제어하는 실험을 하겠습니다.


1. MPU-6050 가속도/자이로 센서


1) MPU-6050 가속도/자이로 센서 회로도


  • 준비물 : MPU-6050 모듈, 아두이노우노
  • 내용 : I2C 통신을 위해 A4(SDA), A5(SCL)핀에 연결하시오

지난 시간에 회로도와 동일합니다.


2) 라이브러리 설치


아두이노 IDE 라이브러리관리창에서 "MPU-6050"을 검색하면 아래 그림처럼 tockn님이 만든 라이브러리가 하나만 검색됩니다. 참고로 아래에 MPU6050으로 한개 더 있는데 이건 이미 설치된 라이브러인데 외부 구글검색으로 찾아서 다운받은 후 라이브러리를 추가했기 때문에 2개의 라이브러리가 표시 된 모습입니다.


직접 검색해서 다른 라이브러리를 다운 받으실려면 "mpu-6050 github"라고 구글검색하시면 해당 링크가 나옵니다.

https://github.com/jrowberg/i2cdevlib/tree/master/Arduino/MPU6050

3) 코딩



예제에 가시면 두개의 GetAllData, GetAngle 예제가 있습니다. 둘 다 한번씩 돌려보시기 바랍니다.


여기서, GetAllData 예제은 1초 단위로 전체 데이터를 출력하는 예제이고, GetAngle 예제는 회전각 X, Y, Z 값만 출력하는 예제입니다.

실험은 GetAngle 예제를 사용 하겠습니다. 이걸로 아래 Processing으로 표현한 box 회전에 사용하기 위해서 입니다.

[GetAngle 소스]-tockn님 소스

#include <MPU6050_tockn.h>
#include <Wire.h>

MPU6050 mpu6050(Wire);

void setup() {
  Serial.begin(9600);
  Wire.begin();
  mpu6050.begin();
  mpu6050.calcGyroOffsets(true);
}

void loop() {
  mpu6050.update();
  Serial.print("angleX : ");
  Serial.print(mpu6050.getAngleX());
  Serial.print("\tangleY : ");
  Serial.print(mpu6050.getAngleY());
  Serial.print("\tangleZ : ");
  Serial.println(mpu6050.getAngleZ());
}

소스를 살펴보면,

  Wire.begin();
  mpu6050.begin();
  mpu6050.calcGyroOffsets(true);

세가지 Wire, mpu6050 시작선언과 초기 작업으로 자이로 오프셋값을 계산합니다. 교정작업으로 자이로 오프셋 평균값을 계산하는데 한번 해당 함수들의 로직을 살펴봐 주세요. 이게 나중에 실험할 때 시간을 많이 잡아먹는 부분이더군요. 평균값을 구하기 위해서 자이로 x,y,z 값을 5000번 읽어고 그 값을 5000으로 나눠서 평균값을 계산하니깐 계산시간이 좀 걸려서 실험 시 대기시간이 길어지더군요.

참고로 값을 알고 있다면

mpu6050.setGyroOffsets(1.45, 1.23, -1.32);

이런식으로 값을 지정 할 수 있습니다.

위, 3줄의 명령은 시작 초기화 함수입니다.

loop()에서는 mpu6050.update() 함수로 이전 라이브러리 없이 읽기 요청과 읽는 함수들의 로직이 이 함수안에 다 집어 넣었더군요. 그리고 그 안에서 상보필터로 회전각을 구하는 부분까지 다 포함되어 있는데 관심있는 분들은 링크 걸어놓은 곳에 "MPU6050_tockn.cpp" 가셔서 로직을 살펴보시면 됩니다.

  • 상보 필터 : filtered_angle = (0.02 * accel) + (0.98 * gyro) (tockn님 계산식)

아무튼 총 4개의 함수를 사용하면 입문자분들은 따로 계산식을 걱정할 필요 없이 회전각 mpu6050.getAngleX(), mpu6050.getAngleY(), mpu6050.getAngleZ() 함수를 통해서 값을 얻을 수 있게 됩니다.

라이브러리를 이용하니깐 그렇게 까지 복잡하지 않죠.

[결과]
다음과 같은 결과가 출력됩니다.


2. MPU-6050 + processing 실험


1) MPU-6050 회전각을 시리얼통신을 통해 String으로 전송하기


[아두이노 실험 소스]

#include <MPU6050_tockn.h>
#include <Wire.h>

MPU6050 mpu6050(Wire);

void setup() {
  Serial.begin(19200);
  Wire.begin();
  mpu6050.begin();
  //mpu6050.calcGyroOffsets(true);
    mpu6050.setGyroOffsets(0.75, 0.05, 0.05);
}

void loop() {
  mpu6050.update();  
  Serial.print(mpu6050.getAngleX());
  Serial.print(',');
  Serial.print(mpu6050.getAngleY());
  Serial.print(',');
  Serial.println(mpu6050.getAngleZ());
  delay(50);
}

위 소스를 보시면 getAngle X,Y,Z 사이에 콤마(,)을 붙여서 한줄로 3개의 데이터가 시리얼 통신으로 전송됩니다. delay(50)을 정도를 설정했는데 안써도 상관 없습니다.

2) getAngle 수정(선택 사항)


이상태로 실험하셔도 되지만 좀 더 빠르게 실험하기 위해서 초기값을 주도록 하겠습니다. calcGyroOffsets(true)함수로 초기값을 계산하는게 아니라 직접 처음에 초기값을 주어서 빠르게 동작하도록 변경하겠습니다.

처음 한번은 getAnglem 예제를 실행하여 아래와 같은 X,Y,Z값을 얻게 되면 그 값을 메모장에 적어놓으세요


처음 getAngle 예제를 돌리면 (X,Y,Z) 가 (0.67, 0.10, 0.10)으로 계산 되었네요. 5천번의 MPU-6050 모듈에 측정된 값을 계산하기 때문에 시간이 걸립니다. 시작 위치의 초기값을 먼저 잡아놓으면 다음 계산이 필요 없기 때문에 빠르게 회전각을 얻을 수 있게 됩니다.

변경은 다음과 같은 calcGyroOffsets()을 주석처리하고 setGyroOffsets()함수로 선언해주면 됩니다. 아래의 X,Y,Z 값은 processing과 연결하여 실험 할 당시의 X, Y, Z의 값입니다. 즉, 여러분들이 실험하는 장소와 위치에서 처음 측정된 값으로 초기값으로 할 경우 이 수치는 달라 집니다. 귀찮은 분들은 시간이 걸리더라고 calcGyroOffsets()함수로 통일해도 되겠죠.

mpu6050.setGyroOffsets(0.75, 0.05, 0.05);

어려분들은 둘중 하나를 선택해서 실험 하시면 됩니다.

mpu6050.calcGyroOffsets(true); : 대기 시간이 길어지더라도 계산하겠면 선택
mpu6050.setGyroOffsets(0.75, 0.05, 0.05); : 처음 한번만 계산해 놓고 그 값을 초기값으로 고정하겠다면 선택

2) processing 코딩


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


[참고 소스]

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');
     
  ... 생략 ...
}

조이스틱으로 실험했던 일부 소스입니다. box를 x,y 회전을 시킨 소스의 일부분인데 오늘 사용할 부분만 제외하고 나머지 소스를 지웠습니다.

processing 문자열 읽기

void serialEvent(Serial p) { 
  String inString = myPort.readStringUntil('\n');
     
  ... 생략 ...
}

이렇게 코딩해서 시리얼통신에서 문자열일 들어오면 자동으로 이벤트 호출이 일어납니다. 그리고 해당 시리얼포트로 문자열을 읽게 됩니다. 여기까지는 아두이노에서 코딩하는 방식과 같습니다. 하지만 processing에서는 링크 걸어놓은 String 방식으로 코딩을 하지 않고 간단하게 processing 함수를 이용하여 수정하겠습니다. split()함수가 제공됨으로 쉽게 전체문자열에서 x,y,z 회전각을 분리해 낼 수 있습니다.

String[] inStrings = split(inString, ',');

inString으로 읽은 한줄의 문자열에서 콤마(,)가 기준으로 쪼개집니다. 그리고, 그 값은 문자열 배열 변수 inStrings에 저장되게 됩니다. 3개의 데이터를 전송하니깐

inStrings[0] => X축 회전
inStrings[1] => Y축 회전
inStrings[2] => Z축 회전

이 값들이 저장되어 있게 됩니다. 하지만 이 값은 문자열이기 때문에 숫자형으로 변환해야 하는데 실수형 float()로 변환을 하면 다음과 같습니다.

x=float(inStrings[0]);
y=float(inStrings[1]);
z=float(inStrings[2]);

아두이노에서 시리얼통신으로 보내 온 x,y,z값을 각각 실수형 x,y,z변수에 최종적으로 저장하게 됩니다.

void serialEvent(Serial p) { 
  String inString = myPort.readStringUntil('\n');    
  String[] inStrings = split(inString, ',');
  x=float(inStrings[0]);
  y=float(inStrings[1]);
  z=float(inStrings[2]);  
}

수신 String 중 회전각만 적용하기

문자열 읽기를 그냥 위와 같이 하면 에러가 발생합니다. 그 이유는 MPU6050_tockn 라이브러리는 초기 세팅함수 안에는 다음과 같이 여러 문자열이 출력됩니다.


회전 각이 나오기 전까지 여러문장의 문자열이 출력되는데 단순위 위와같이 코딩하면 문제가 발생하겠죠. MPU6050_tockn라이브러리에 가서 cpp 파일에 해당 함수의 들어있는 print문을 전부 주석처리하면 해결할 수 있지만 라이브러리 파일을 건들면 좀 그렇겠죠.

그래서 회전각 문자열인지 체크하는 제어문을 넣어서 해결하도록 하겠습니다.

String inString = myPort.readStringUntil('\n');   
int separator_ch = inString.indexOf(",");

if(separator_ch!=-1){
  회전각 구하기;
}else{
 print(inString);
}

이렇게 indexOf(',')로 회전각 분리기호의 위치값을 찾습니다. 회전각 문자열에는 분리기호가 있기 때문에 해당 0이상의 숫자값을 갖게 됩니다. 하지만 해당 문자열에 분리기호가 없다면 '-1'이 반환 됩니다. 이 원리를 이용해서 위와 같이 if문으로 분리기호가 있으면 방금 위에서 코딩한 문자열에서 회전각을 분리해내는 로직을 붙이면 됩니다. 분리기호가 없다면 그냥 시리얼모니터 창으로 해당 문자열을 출력하게 하면 간단히 이 문제를 해결하게 됩니다.

box 회전시키기

box 회전을 하려면 이 부분을 수정해야 합니다. rotateX(), rotateY(), rotateZ() box 회전을 시키고 해당 인자값을 아까 String으로 읽은 값을

rotateX(radians(y)); //x축 회전
rotateY(radians(x)); //y축 회전
rotateZ(radians(z)); //y축 회전
    
box(200,100,200); //상자

x,y,z 축으로 회전시켜서 box()을 배치하는 코딩입니다. box()함수 앞에다가 회전함수를 넣으면 최종적으로 회전시킨 곳에 box가 그려지기 때문에 box가 x,y,z 값에 의해서 이동하는 것처럼 보이게 됩니다.

그리기 함수부분은 아래과 같이 되겠죠.

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축 회전
    rotateZ(radians(z)); //y축 회전
    
    box(200,100,200); //상자
    popMatrix(); //End
}

[processing 실험 소스]

import processing.serial.*;

Serial myPort;

float x=0;
float y=0;
float z=0;

void setup() {
    println(Serial.list());
    myPort = new Serial(this, Serial.list()[0], 19200);
    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축 회전
    rotateZ(radians(z)); //y축 회전
    
    box(200,100,200); //상자
    popMatrix(); //End
}
 
void serialEvent(Serial p) { 
  String inString = myPort.readStringUntil('\n');   
  int separator_ch = inString.indexOf(",");
  if(separator_ch!=-1){
    String[] inStrings = split(inString, ',');
    x=float(inStrings[0]);
    y=float(inStrings[1]);
    z=float(inStrings[2]);
  
    print(x);
    print('/');
    print(y);
    print('/');
    println(z);
  }else{
    print(inString);
  }
}

추가로, print문으로 정확히 분리되어 x,y,z값이 나오는지 확인하기 위해서 processing 시리얼 모니터로 출력시키는 코딩을 넣었습니다.

5. 실험 결과


MPU-6050 모듈을 움직일 때 PC로 녹화한 영상입니다.



MPU-6050 모듈을 움직일 때 processing의 box가 어떻게 움직이는지 폰을 촬영한 영상합니다.



마무리


MPU-6050 라이브러리를 이용하면 해당 회전각을 가지고 여러분들이 원하는 표현을 쉽게 할 수 있습니다.

참고로, 주의할 점은 될 수 있으면 MPU-6050 모듈에 핀을 납땜해주시고 실험 해주세요. 그리고 핀을 연결한 선도 단단히 고정시켜주세요. 조금만 흔들려도 MPU-6050이 먹통이 됩니다. 그리고, 회전 각에 크게 변화할 때에도 한번 먹통이 발생하면 더이상 측정하기는 불가능합니다. 그리고 일정시간이 지나면 값의 변화가 생기기 때문에 정교한 제어용으로 쓰기에는 다소 부족합니다. 초보 입문용으로 싼 가격에 자이로센서를 사용하는데 가성비를 최고이지만 혹시 드론 같은 전문적으로 사용할시에는 비싼 자이로센서를 쓰기기 바랍니다.

그리고, 어느정도 재미를 붙이시면 제대로 회전각을 구하는 공식을 공부해주시기 바랍니다. 사실 기존 라이브러리로만 사용해서는 어떤 실험을 할지 모르지만 정교한 제어는 어렵습니다. 그냥 무조건 값을 읽고 계산해서는 정교한 제어가 되지 않습니다. 초기 보정작업과 기타 값의 변화율에 대한 처리부분을 고려해서 코딩해야 합니다. 수학적으로 접근해야 하는 부분들이 많기 때문에 전문서적이나 구글검색을 통해 관련 자료들을 많이 찾으셔서 공부하시기 바랍니다.

저는 기초적인 부분만 다루고 실험했기 때문에 좀 깊은 공부는 아직은 관심이 덜해서 해보지는 않아서 좀 더 자세한 내용을 전달해드리지 못하네요. 제 경우는 라이브러리 코딩쪽이 아니라 측정된 값을 기반으로 제어하는 코딩에 좀 더 관심이 많아서 그냥 라이브러리를 그대로 사용하고 있네요

자이로센서를 전문적으로 다루기 위해서는 배워야 할 부분이 많지만 간단히 실험하고 기초적인 기능만 활용하시더라도 라이브러리를 이용해 원하는 표현은 가능하니깐 MPU-6050 모듈을 배워 보세요.


댓글()

[아두이노] 조이스틱+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 도형을 도형을 움직이게 할 예정 입니다. 키보드로 제어 했던 부분을 오늘 배운 키 값으로 제어하겠다는 의미인 셈이죠. 왜! 키 값인지 아시겠지요.

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

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

댓글()

[아두이노] processing 3D 도형 제어

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

[아두이노] processing 3D 도형 제어 


예전에 잠깐 아두이노와 연동하는 processing을 소개 한 적이 있습니다. 오늘은 아두이노 부분을 제외하고 간단히 processing에서 키보드와 마우스로 도형을 제어하는 원리를 소개하려고 합니다. 이부분을 어느정도 이해하시면 나중에 아두이노에서 제어값을 입력된 값으로 해서 processing 이미지를 제어 할 수 있기 때문입니다. 오늘 다루게 될 키보드와 마우스가 나중에 아두이노로 대체하면 재밌는 표현들을 할 수 있기 때문에 관심을 가져줬으면 하는 마음으로 포스팅을 합니다.

1. processing 3D box() 도형 그리기


[복습]

void setup() {  
  한번만 수행;
}
void draw() { 
  무한 반복 수행;
}

아두이노랑 구조가 비슷하다고 했죠. setup()은 한번만 수행되는 초기화 작업을 코딩하고, draw()은 계속 그리는 무한 작업 코딩을 담당합니다.

void setup() {  
    
    size(600,600,P3D); //창사이즈
    noStroke();  //테두리없음

//  frameRate(30); //초당 프레임
} 
void draw() {  
     background(0); //배경색
}

추가적으로 초기 설정하는 함수들이 있는데 다 생략하고 딱 두개만 사용합니다. 윈도우 창이 하나 생성되는데 size() 함수로 윈도우 창 만듭니다. 여기서, "P3D" 3D Rendering 모드입니다. "OPENGL"로 선언해도 됩니다. 참고로 opengl은 import 선언이 필요하지만요. 그리고 테두리를 그리지 않는 noStroke()함수를 사용합니다. box 그렸는데 테두리가 보기 흉해서 테두리를 지웠네요.

배경색은 background(0)로 검정색으로 했네요. 아래 그림은 위 코딩의 결과입니다.


다음으로,

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

    pushMatrix();  //Start
    fill(0,255,0); //채우기
    translate(width/2,height/2,-100); //이동
    box(200,100,200); //상자
    popMatrix(); //End
}

옆에 주석을 다 달아 놓았습니다. 배경색은 검정색으로 하고 lights() 조명효과를 나타냅니다. 이미지 도형을 만들면 그 이미지에 light를 비추는 효과라고 생각하시면 됩니다.

fill()은 그리는 도형에 어떤 색을 채울것인지 지정하는 함수입니다. 즉, fill() 함수에 의해서 다음에 그려진 도형의 표면의 색이 결정됩니다. 사각형을 그리면 사각형 면의 색이 fill()함수의 색으로 채워진다고 생각하시면 됩니다.

pushMatrix(), popMatrix()함수가 있는데 스텍, 행렬의 의미를 이해하고 있어야 하지만 설명을 해도 쉽게 의미를 전달하기 어렵습니다. 다음으로 약식 꼼수 의미로 이해해 주셨으면 합니다. 저 함수가 나오면 push랑 pop은 한쌍이라고 생각하세요. 그리고 그리는 영역으로 push 시작위치에서 pop은 그리기 끝나는 위치라고 생각하세요. push~pop사이의 그리기 명령함수는 그 영역안에서만 독립적으로 적용된다고 생각하시면 될 듯요.


위 그림으로 다시 설명하면은 draw()에서 한장에 종이에 다가 그림을 그린다고 생각하시면 push~pop함수가 있으면 그 부분은 새로운 종이로 독립적인 영역의 그림을 그린다고 생각하시면 됩니다. 여기서, draw라는 메인 종이 위헤 새로 그린 종이를 붙이는 느낌으로 이해하시면 될 듯요. push~pop 안에 또다른 push~pop이 나오면 밖에 push~pop은 부모라고 생각하시고 부모 종이 위에 자식 종이가 두장 있다고 생각하시면 됩니다. 이때 자식 종이들은 각각 독립된 영역으로 A종이에 그리는 명령은 B종이에 영향을 줄 수 없고, B종이에 그리는 명령은 A종이에 영향을 줄 수 없는 독립된 영역으로 보시면 됩니다. 하지만 부모 종이에서는 자식 A, B가 둘 다 포함되어 있기 때문에 부모 종이에서 그리는 명령은 A, B 종이에 영향을 주게 됩니다.

왜! 이렇게 번거롭게 push~pop를 사용하느냐면 한두개의 도형을 그릴 때는 상관 없지만 다수의 도형을 그리고 배치하고 개별적인 도형들이 움직이거나 변화가 일어나게 코딩할려면 push~pop이 없이 그냥 한다면 어렵습니다. 그리는 명령을 매번 일일히 변화를 줘야 하기 때문이지요. 한장의 종이에 자동차 그림을 그린다면 한번에 전체를 다 그려야 합니다. 엔진, 바퀴, 핸들 등등의 각각의 부품들을 한번에 한장에 다 그려야 하기 때문에 그리고 나면 다시 엔진이 맘에 안든다고 엔진을 바꿀려고 하면 수정하기도 힘들고 하나를 변경하면 그 영향이 전체에 가기 때문에 무척 힘든 작업이 됩니다.

하지만 push~pop은 각각의 엔진이면 엔진 바퀴면 바퀴를 독립적으로 그리고 한장에 종이에 독립적으로 그림 그림을 가져와서 해당 위치에 붙이기만 하면 조립을 쉽게 완성할 수 있습니다. 나중에 엔진을 바꾸고 싶다면 엔진만 다시 그려서 해당 위치에 붙이면 쉽게 변경할 수 있게 됩니다.

한마디로 말하면 각 그림을 캡슐화 한다고 생각하시면 됩니다.

다시 설명드리면,


위 그림을 그리기 위해서 push~pop이 없다면 한개의 좌표계에서 몸체와 2개의 바퀴를 3D공간 좌표계에서 위치 좌표를 일일히 이동시켜서 그 위치에서 그림을 그려야 합니다. 한개라도 변경되는 일이 발생하면 전체에 영향을 주게 됩니다. 그리고 바퀴만 회전시키고 싶을때 제어하기가 무자 까다롭습니다.

하지만 push~pop을 사용하면 독립된 3D좌표계에서 독립적으로 그리게 됩니다.


위 그림처럼 계별적 3D 좌표계에서 도형을 그린 다음에 메인 draw() 이 그림을 push~pop으로 배치만 하면 됩니다. 나중에 원의 색을 바꾸고 싶으면 push~pop안에 fill()값만 바꾸면 색을 쉽게 변경이 됩니다. 바퀴가 두개인데 복제도 가능해지죠. 바퀴를 회전 시키고 싶으면 push~pop에 rotate()함수만 삽입하면 됩니다. draw()함수 내에서 하나의 좌표에게서 push~pop의 개별좌표계로 독립된 표현이 된다고 생각하시면 되겠습니다.

3D 포스트가 아닌데 삼천포로 빠지고 말았네요. 나중에 아두이노와 연동해서 뭔가 화려한 3D 관련 표현을 하실 수 있으니깐 가장 중요한 push~pop을 이야기 하다보니 좀 설명이 길어 졌네요.

    pushMatrix();  //Start
    fill(0,255,0); //채우기
    translate(width/2,height/2,-100); //이동
    box(200,100,200); //상자
    popMatrix(); //End

fill(0,255,0)함수로 다음에 그리게 될 도형의 표면 색을 녹색으로 정했습니다. translate(x,y,z)로 그리게 될 도형이 윈도우창에서 어디쯤 위치할 것인지 이동 시킵니다. 즉, 도형의 색은 녹색이고 특정위치로 이동시킨 곳에 상자가 그려진다고 생각하시면 될 듯 싶네요.

[결과]


위 그림에서는 상자가 녹색인 것은 알 수 있지만 3D 상자인지는 알 수 없네요. 이제 회전을 시켜 볼까요.

2. Mouse 회전


마우스 회전은 간단합니다. 상자가 그려진 윈도우창에 마우스의 좌표를 가져 올 수 있다면 쉽게 해결 됩니다. processing에서는 마우스 좌표를 변수로 실시간 좌표를 가져올 수 있습니다. mouseX, mouseY가 그 변수명입니다. 이 변수를 사용하면 현재 마우스가 가리키는 좌표값을 얻을 수 있습니다. 회전을 시켜볼까요.

회전 rotate()함수는 그냥 회전인데 마우스 좌표로 X, Y 축으로 회전를 해봅시다. processing 에서는 각 축을 기준으로 회전 시키는 함수를 제공합니다.

해당 함수를 살펴보면, ratateX()은 X축 기준으로 회전함수이고, ratateY()은 Y축 기준으로 회전하는 함수입니다. mouseX, mouseY 변수는 size()함수로 만든 윈도우 창의 영역 안에 마우스의 좌표(x,y)값을 실시간으로 가져 올 수 있어 이 값을 회전각으로 하면 쉽게 회전 할 수 있습니다.

처음, 회전각은 mouseX로 했더니 너무 고속으로 회전해서 약간 회전을 느리게 하기 위해서 0.1을 곱해도 너무 빠르고 0.01정도 하니깐 제 컴퓨터에서 이상적으로 회전 하네요.

회전각(mouseX * 0.01)로 회전시켜 보았습니다.

void setup() {  
    
    size(600,600,P3D); //창사이즈
    noStroke();  //테두리없음
//  frameRate(30); //초당 프레임
} 

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

    pushMatrix();  //Start
    fill(0,255,0); //채우기
    translate(width/2,height/2,-100); //이동
    rotateX(mouseY*0.01); //x축 회전
    rotateY(mouseX*0.01); //y축 회전
    box(200,100,200); //상자
    popMatrix(); //End
}

[결과]


나중에 조이스틱 같은걸로 조정한 값을 이 부분을 대신하면 회전이 되겠죠.

2. 키보드 회전



이제는 키보드로 회전 시키는 방식을 살펴보도록 하겠습니다.

위 링크 소스에서 키보드 관련 부분 살펴보면,

void keyPressed() {
    if (key == CODED) {
            if (keyCode == UP) {
                 회전각;
            }   
    }
    else if (key == 'z'){
       명령문;
    }   
}

대충 구조는 이렇습니다. keyPressed()함수는 키보드의 키를 누르면 호출 되는 함수입니다. key는 눌렀을때의 키 값을 가지고 있으면 그 키 값이 CODED인지 채크하게 됩니다. CODED면 keyCode 변수의 키 값이 들어 있고 그 키가 UP, DOWN, LEFT, RIGHT 인지를 체크하게 됩니다. 참고로 화살표 키 값이라고 생각하시면 될 듯요.
일반 키값은 key에 들어 있으면 'a'~'z', 'A'~'Z' 등 등으로 키값을 가질 수 있고 key에 저장 되어 있어서 key로 비교하면 됩니다.

키보드로 회전한다면 방향키로 X, Y축 회전을 시키고 키값 Z, X키로 Z축으로 이동 제어 키로 쭘인/줌아웃을 시킬 예정입니다.

int x=0;
int y=0;
int z=0;

void setup() {  
    
    size(600,600,P3D); //창사이즈
    noStroke();  //테두리없음
        
//  frameRate(30); //초당 프레임
} 

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

    pushMatrix();  //Start
    fill(0,255,0); //채우기
    translate(width/2,height/2,-100+z); //이동
    rotateX(radians(y)); //x축 회전
    rotateY(radians(x)); //y축 회전
    
    box(200,100,200); //상자
    popMatrix(); //End
}

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;
  } 
}

위에서 if문이 조건식이 두개인데 '||'은 둘중 하나만 참이면 참으로 인정하는 기호입니다. 둘개 다 참이여야 참으로 인정하는 기호는 '&&'인데 참고로 알아 두세요. '||'로 'z' or 'Z' 중 Z키가 대소문자 중 아무거나 입력해도 Z로 인정한다는 의미입니다. 나머지는 구지 성명 드릴 필요는 없겠죠.

    rotateX(radians(y)); //x축 회전
    rotateY(radians(x)); //y축 회전

여기서 radians(y)은 라디안 단위로 각도로 변환시키는 함수입니다. 그냥 각도 구하기라고 생각하시면 될 듯 싶네요.

[결과]



화살표 방향키하고 춤인/줌아웃 키인 Z, X 키가 잘 작동하네요.

마무리


processing에 대해서 한번 관심을 가져보시라고 간단히 3D Rendering를 해보았습니다. 참고로 이미 만들어진 box()함수의 도형이지만 좌표계 기준으로 각 좌표로 모형을 그리고 싶다면 공식홈페이지에 도형 그리는 함수들을 찾아보시고 따라서 그려보세요, 충분히 그릴 수 있을 거라 생각됩니다.

참고로 processing 함수들이 다양한 기능들을 제공해주기 기 때문에 위 포스트한 코딩에서 추가 할 코딩 부분이 있지만 다 생략하고 기본만 되는 부분만 코딩한거라 좀 단순한 표현이 되었네요. 그래도 핵심적인 표현은 다 했습니다. 이제는 여러분들이 도형을 그리고 그 도형에 대해서 움직임을 push~pop를 이용해서 제어하시면 됩니다.

갑자기 processing를 포스팅을 한 이유는 최근에 스위치 버턴과 조이스틱으로 조정하는 것을 배웠습니다. 한번 여러분들이 이 코딩을 기반으로 아두이노와 접목하면 좋을 것 같아서 소개하게 되었습니다.

한번 스위치 버턴 or 조이스틱으로 오늘 배운 도형을 컨트롤 해보고 싶지 않으신지요. 제가 답을 바로 드리는 것보다 여러분들이 한번 지난 시간에 processing에서 아두이노와 어떻게 연동을 했는지 상단에 참고 링크 포스트를 읽어보시고 연구를 해보셨으면 합니다.

댓글()

[아두이노] Processing를 이용한 아두이노 제어 III

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

[아두이노] Processing를 이용한 아두이노 제어 III 



세번째 시간으로 이제는 프로세싱과 아두이노 두 곳에서 개별적으로 코딩하고 시리얼통신을 이용해서 데이터를 주고 받아서 제어하는 방법을 살펴보겠습니다. 기본적으로 아두이노는 원래의 코딩 방식으로 코딩하면 됩니다. 변경되는 부분은 없고 단지 시리얼통신을 할 수 있도록 세팅만 해주면 됩니다. 프로세서는 시리얼통신 부분만 코딩해주고 프로세스는 그래픽 코딩만 중점적으로 하면 됩니다. 쉽게말해서 각자의 역할만 하고 단지 시리얼통신이라는 매개체로 연결된다고 생각하시면 됩니다.

1. Processing + Arduino



[ LED 깜박이는 예제 ]

void setup() {
  pinMode(13, OUTPUT);
}

void loop() {
  digitalWrite(13, HIGH);
  delay(1000);             
  digitalWrite(13, LOW);
  delay(1000);   
}

LED 깜박이는 기본 예제를 통해서 프로세싱과 아두이노의 역활을 나눠 보겠습니다. 프로세싱에서는 스위치 역활로 LED가 1초단위로 깜박이도록 명령을 내리고 아두이노는 그 명령을 받아서 LED를 깜박이게 하도록 역활을 나눠 보겠습니다.

아래 그림처럼 프로세싱는 시리얼통신을 통해 아두이노에게 LED 상태값을 보내고 아두이노는 이 값을 읽어서 LED를 깜박이게 하는 합니다. 1초 단위로 깜박이는 제어권은 프로세싱에게 있고 아두이노는 프로세싱의 명령에 따라서 LED의 상태변화만 시키게 됩니다.



[ Prcessing 코딩 ]

import processing.serial.*; 

Serial mPort;

void setup() {  
    println(Serial.list());
    mPort = new Serial(this, Serial.list()[0], 9600);   
} 
void draw() {  
  mPort.write(1);
  delay(1000);
  mPort.write(0);
  delay(1000);
}

시리얼통신 라이브러리를 가져옵니다. 그리고 Serial 클래스를 하나의 객체 변수(mPort)로 선언하고 인스턴트 해야합니다. mPort = new Serial(this, Serial.list()[0],9600) 이렇게 해서 시리얼통신 객체가 만들어 집니다.

이걸 통해서 mPort.write(1)은 byte전송 명령함수인데 print(), println()으로 그동안 써왔지만 이번에는 write()함수를 사용해 봅니다.

이곳에 가셔서 시리얼통신 함수들을 보시고 사용하시면 됩니다.

프로세싱에서 1초 단위로 깜박이는 명령 시리얼통신을 통해서 1(HIGH)과 0(LOW)값을 아두이노로 보냅니다.

[ Arduino 코딩 ]

void setup(){
  Serial.begin(9600);
  pinMode(13,OUTPUT);
}

void loop(){
  byte val;
  
  if(Serial.available()) {  
    val = Serial.read();
    
    digitalWrite(13,val);
  }
}

아두이노는 if 조건문으로 Serial.available()함수로 통신 데이터가 들어왔는지 확인하는 문장입니다. 데이터 들어 왔다면 0이 아니고 데이터가 들어오지 않았다면 이 Serial.available()함수의 반환되는 값은 0으로 if문 이하를 수행하지 않습니다. 쉽게 말해서 통신을 통해 데이터가 도착했다면 참이고 도착하지 않았다면 거짓이다 이걸 체크한다고 생각하세요.

데이터가 도착했으면 Serial.read()함수로 데이터를 읽습니다. 아두이노에서 read라는 단어가 보이면 뭘 읽는 함수구나 하고 생각하시면 됩니다. 프로세싱에서 넘어 온 1과 0의 값이 val 변수에 저장됩니다. 프로세싱에서 byte 단위로 숫자를 전송했고 아두이노에서는 byte변수로 그 값을 받아서 저장하게 됩니다.

실제아두이는 통신을 통해서 1과 0을 읽어 오고 그 값을 기준으로 LED를 On/Off 하게 됩니다.

  digitalWrite(13,val);

이 한문장이 아두이노가 하는 역활입니다. 그러면 반대의 경우도는 온도센서가 있는데 그 온도 값을 프로세싱에 보내서 시각화 하고 싶다면 방금한 시리얼통신 코딩부분을 아두이노에서 보내고 프로세싱에서 읽도록 바꿔서 코딩하면 되겠죠.


3. 간단한 버턴클릭 예제로 아두이노 LED 제어


두번째 시간에는 프로세싱에서 아두이노 동작제어하는 코딩까지 전부 했었습니다. 위에서 프로세싱과 아두이노가 개별적으로 코딩하고 시리얼통신으로 주고 받은 LED 깜박이는 예제처럼 코드를 변경해 보죠.


[ Prcessing 코딩 ]

import processing.serial.*;

Serial mPort;
boolean cnt = false;

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

    // set the background color
    background(255);
    
    // canvas size (Integers only, please.)
    size(600, 600); 
    
    println(Serial.list());
    mPort = new Serial(this, Serial.list()[0], 9600);  
    
} 

void draw() {  // this is run repeatedly.  
    rect(10,10,580,300,10);    
    rect(200,350,200,100,10);    
}

void mousePressed(){
 if(mouseX>200 && mouseX<400 && mouseY>350 && mouseY <450){
     cnt=!cnt;
 
 }
 
 if(cnt==true){
   fill(255,0,0);
   mPort.write(1);
 }
 else{
   fill(0,0,0);
   mPort.write(0);
 }
}

(기본 예제)

boolean cnt = false;

void setup() {  // this is run once.   
    
    // set the background color
    background(255);
    
    // canvas size (Integers only, please.)
    size(600, 600);       
    
} 

void draw() {  // this is run repeatedly.  
    rect(10,10,580,300,10);    
    rect(200,350,200,100,10);    
}

void mousePressed(){
 if(mouseX>200 && mouseX<400 && mouseY>350 && mouseY <450){
     cnt=!cnt;
 }
 if(cnt==true)fill(255,0,0);
 else fill(0,0,0);
}

이 소스에서 프로세싱에서 아두이노 문법을 위에서 설명했으니 기본 예제 소스에다가 아래 코딩 부분을 삽입하시면 됩니다.

import processing.serial.*;

Serial mPort;

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

    println(Serial.list());
    mPort = new Serial(this, Serial.list()[0], 9600);  
    
} 

void draw() {  // this is run repeatedly.  

}

void mousePressed(){
     mPort.write(1); 
 }
}

여기서, mPort.write(값)은 버턴을 눌렀을때 색상을 바꾸는 곳에다 LED 제어한 부분에 아두이노로 데이터를 전송하는 명령을 삽입하면 되겠죠.

if(cnt==true) {
   //arduino.digitalWrite(ledPin,Arduino.HIGH); 
     mPort.write(1);
   fill(255,0,0);
    }
 else{
   //arduino.digitalWrite(ledPin,Arduino.LOW);
     mPort.write(0);
   fill(0,0,0);
     
 }

[ Arduino 코딩 ]

void setup(){
  Serial.begin(9600);
  pinMode(13,OUTPUT);
}

void loop(){
  byte val;
  
  if(Serial.available()) {  
    val = Serial.read();
    
    digitalWrite(13,val);
  }
}

아두이노는 코드가 동일합니다.

결과는 아래 사진처럼 정상적으로 동작 했습니다.


6. 결과


라즈베리파이에 프로세싱과 아두이노 IDE를 설치해어 그곳에서 실험한 과정을 녹화 했네요. 간단히 살펴보도록 하세요.

마무리


프로세싱 툴이 시각화 하기에 재밌는 프로그램 입니다. 그리고 그 프로세싱을 두가지 형태로 아두이노를 접근 할 수 있습니다. 사용하기에 따라 장단점이 있습니다. 첫번째 프로세싱이 아두이노 코딩까지 전부하게 되면 코딩량이 늘어나고 복잡해 질 수 있지만 프로세싱에서 직접 제어하니깐 빠르게 부품 제어와 시각화 코딩을 진행 할 수 있습니다. 하지만 프로세싱과 아두이노가 개별적으로 코딩이 들어가면 매 실험때마다 아두이노는 프로세싱에 맞춰서 코딩하고 아두이노 코딩과 프로세싱 코딩을 맞추는 작업을 계속 해야 하는 단점이 발생 합니다. 프로세싱과 아두이노가 개별적으로 코딩해야한다는 장점이자 단점이 될 수 있습니다.

그러면 프로세싱에서 무조건 코딩을 전부하는게 좋냐고 물으신다면 꼭 그렇지는 않습니다. 아두이노 코딩은 기존의 방식으로 그대로 진행하면 됩니다. 이미 코딩된 형태에서 변경할 필요 없이 시리얼통신 부분만 코딩해주면 서로 통신데이터를 주고 받으면서 제어를 하면 되기 때문에 심풀하게 아두이노코딩만 신경쓰면 됩니다. 프로세싱도 아두이노로 보낼 데이터나 받을 데이터만을 기준으로 시각화 하면 되니깐 구지 아두이노 코딩까지 신경 쓸 필요가 없어집니다. 괜히 한곳에서 합쳐지면 가독성도 떨어지고 복잡해 보이고 불편한 코딩이 될 수 있습니다.

둘다 장단점이 있고 자신이 코딩하기에 편한 걸 선택하셔서 실험 하시면 됩니다.


댓글()

[아두이노] Processing를 이용한 아두이노 제어 II

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

[아두이노] Processing를 이용한 아두이노 제어 II 



두번째 시간으로 프로세싱을 통해 아두이노를 직접 제어하는 방법을 살펴보도록 하겠습니다.

1. Processing 세팅


[1단계] 도구->추가도구 설정 눌러 줍니다.


[2단계] 라이브러리에서 arduino라고 치시면 아래 그림처럼 검색되는데 하당 라이브러리를 선택하시고 Install 하시면 됩니다.


[3단계] 프로세싱 편집기를 종료하시도 다시 실행 시킵니다. 그리고 파일->예제를 누르시면 따로 창이 뜨고 Arduino(Firmata) 가 추가된 것을 보실 수 있을거에요.


아무 예제나 클릭하시면 되는데 좀 코딩량이 많습니다. 그래서 간단히
[4단계] https://playground.arduino.cc/Interfacing/Processing 이곳 주소로 가시면 프로세싱 예제가 있습니다.

프로세싱 편집기에 그래도 코딩하시면 됩니다.


2. 아두이노 세팅


[1단계] 파일->예제->Firmata -> StandardFirmata 를 눌러줍니다.


[2단계] 예제가 뜨고 이걸 아두이노에 이식 시키면 됩니다.


3. Processing + Arduino


아두이노에서 StandardFirmata를 아두이노에 이식 시키고 Processing은 Arduino(firmata) 라이브러리를 설치해서 아두이노 라이브러리를 통해서 실제 아두이노를 직접 제어하게 됩니다.


쉽게말해서 아두이노는 프로세싱을 받을 준비를 해놓고 프로세싱에서는 직접 아두이노 함수를 통해 제어를 하게 된다고 생각하시면 됩니다.

4. Processing에서 아두이노 문법


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

Arduino arduino;
int ledPin=13;

void setup() {  
  println(Arduino.list());
  arduino = new Arduino(this, Arduino.list()[0], 57600);
  arduino.pinMode(ledPin, Arduino.OUTPUT);
}

void draw() {
  arduino.digitalWrite(ledPin,Arduino.HIGH);
  delay(1000);
  arduino.digitalWrite(ledPin, Arduino.LOW);
  delay(1000);  
}

소스로 설명드리겠습니다. 아두이노 LED 깜박이는 기본베이스 소스입니다.

기본적으로

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

통신과 아두이노 라이브러리를 가져오게 와야겠죠. 그리고 아두이노 클래스를 사용할때 클래스 객체를 선언해야합니다.

Arduino arduino;
arduino = new Arduino(this, Arduino.list()[0], 57600);

클래스 객체를 선언하고 객체를 인스턴트 합니다. 쉽게 말해서 객체를 만들고 초기화 한다고 생각하세요.

그다음 아두이노에서 쓰던함수들은 객체명.함수 함수로 객체가 앞에 연결자로 모두 표현해야 합니다. 아두이노 클래스내에 아두이노 함수들이 있으니깐 객체.함수() 해야지 객체속의 함수를 호출할 수 있게 됩니다.

pinMode(13,OUTPUT) => arduino.pinMode(13,Arduino.OUTPUT);

됩니다. 무슨 의미인지 아시겠지요. delay(1000) 이것은 왜 안붙이냐 오타이냐 할 수 있는데. 프로세싱에서도 있는 함수임으로 붙이지 않습니다.

임포턴트하는 것과 객체 선언한것만 이해하시면 간단 합니다.

5. 간단한 버턴클릭 예제로 아두이노 LED 제어


지난시간에 만들어 봤던 작은사각형 클릭했을때 사각형들의 색상을 바꾸는 예제를 아두이노 LED를 On/Off 시키는 스위치 버턴으로 사용하겠습니다.

예제)

boolean cnt = false;

void setup() {  // this is run once.   
    
    // set the background color
    background(255);
    
    // canvas size (Integers only, please.)
    size(600, 600);       
    
} 

void draw() {  // this is run repeatedly.  
    rect(10,10,580,300,10);    
    rect(200,350,200,100,10);    
}

void mousePressed(){
 if(mouseX>200 && mouseX<400 && mouseY>350 && mouseY <450){
     cnt=!cnt;
 }
 if(cnt==true)fill(255,0,0);
 else fill(0,0,0);
}

이 소스에서 프로세싱에서 아두이노 문법을 위에서 설명했으니 해당 위치에 넣으시면 됩니다.

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

Arduino arduino;
int ledPin=13;

boolean cnt = false;

void setup() {  // this is run once.   
    
    // set the background color
    background(255);
    
    // canvas size (Integers only, please.)
    size(600, 600); 
        
    //println(Arduino.list());
  arduino = new Arduino(this, Arduino.list()[0], 57600);
  arduino.pinMode(ledPin, Arduino.OUTPUT);
    
} 

void draw() {  // this is run repeatedly.  
    rect(10,10,580,300,10);    
    rect(200,350,200,100,10);    
}

void mousePressed(){
 if(mouseX>200 && mouseX<400 && mouseY>350 && mouseY <450){
     cnt=!cnt;
 }
 if(cnt==true) {
   arduino.digitalWrite(ledPin,Arduino.HIGH);
   fill(255,0,0);
    }
 else{
   arduino.digitalWrite(ledPin,Arduino.LOW);
   fill(0,0,0);
 }
}

버턴을 눌렀을때 색상을 바꾸는 곳에다 LED 제어해야겠죠.

if(cnt==true) {
   arduino.digitalWrite(ledPin,Arduino.HIGH);
   fill(255,0,0);
    }
 else{
   arduino.digitalWrite(ledPin,Arduino.LOW);
   fill(0,0,0);
 }

딱히 수정할 것은 없습니다. 아두이노 소스를 해당 위치에다만 삽입하면 됩니다.

6. 결과


프로세싱과 아두이노에 코드를 복사하고 실제 동작하는 전과정을 담았습니다. 동영상을 보는게 더 쉽게 와 닿을지 모르겠네요.

마무리


종합해 보면, 아두이노에서 StandardFirmata를 아두이노에 이식 시키고 Processing은 Arduino(firmata) 라이브러리를 설치해서 아두이노 라이브러리를 통해서 StandardFirmata를 이식된 아두이노를 직접 제어를 하게 됩니다.

이 방법은 아두이노에서 코딩을 할 필요가 없습니다. 프로세싱에서 그래픽 시각화와 아두이노 제어를 둘 다 할 수 있습니다.

또 다른 방법은 시리얼통신으로 서로 데이터를 주고 받는 방법이 있습니다. 프로세싱과 아두이노가 데이터를 주고 받으면서 제어할 데이터 또는 시각화 데이터를 서로 시리얼통신으로 통해 주고 받는 방식이 있습니다.
이 경우는 아두이노는 아두이노 코딩만 하면 되고 프로세싱은 프로셋이 코딩만 하면 됩니다. 그 중간 연결을 통신이 대신 하는 것이죠.

우선은 오늘 배운 프로세싱에서 아두이노를 제어하는 방법만 이해해 주시고 다음에 따로 통신을 통해 데이터를 주고 받는 방식을 살펴보도록 하겠습니다.


댓글()

[아두이노] Processing을 이용한 아두이노 제어 I

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

[아두이노] Processing을 이용한 아두이노 제어 I 



오늘은 즐거운 토요일입니다. 매주 토요일이면 재밌는 프로그램을 소개하는 날입니다. 이번주 소개 할 프로그램은 아두이노와 연결해서 재밌게 시각화 실험을 할 수 있는 프로그램입니다. 스크래치나 프로세싱으로 아두이노 제어하거나 결과를 시각화 하는데 많이 사용합니다. 잘 알려진 프로그램 툴이고 컴퓨터 공부를 좀 하신분들은 한번쯤은 다뤄봤을 툴이라고 생각되네요. 아두이노 처음 입문자분들은 아직 프로세싱에 대해서 잘 모르고 프로그램 언어 영역이라서 다루기 불편하실 수 있지만 그래픽 툴로서는 쉬운 편에 속하고 함수만 잘 활용한다면 재밌는 시각화 프로그램을 만들 수 있으니깐 한번 참고하시면 좋을 듯 싶네요.

좀 포스팅 내용이 길어질 수 있어서 나눠서 소개할까 합니다. 오늘은 간단히 프로세싱 소개를 하는 시간으로 채울 듯 싶네요.

1. Processing 다운로드


[ PC에 실치 ]


위에 공식 홈페이지에 다운로드 주소까지 링크 걸려있습니다. 들어가시면 자신의 운영체제에 맞게 다운로드 하시면 됩니다. 압축(zip) 파일을 다운로드 하시면 폴더를 만드시든 그냥 압축 파일명으로 압축된 파일을 푸시면 됩니다. 설치는 따로 사실 필요없이 폴더 안에 들어가서 실행파일을 누르시면 자동으로 실행 됩니다.

[ 라즈베리파이에 설치 ]

$ curl https://processing.org/download/install-arm.sh | sudo sh 

[출처] 라즈베리파이에 프로세싱 설치하기(작성자 코스모스)

라즈베리파이에 설치할때 잘 몰라서 이건 배우는 단계라서 두가 설치법이 있는데 구글링 해서 보니깐 코스모스님이 블로그에 간단하게 설치하는 법을 소개했네요. 이 한줄로 라즈베리파이에 프로세싱을 설치하여 현재는 재밌게 사용하고 있네요.

2. Precessing 웹 편집기


위에 링크된 sketchpad 사이트 가시면 온라인 상에서 프로세싱을 즐길 수 있습니다. 다운 받을 필요도 없고 회원가입을 할 필요도 없이 그냥 코딩 하고 싶을때 가셔서 코딩 하시면 됩니다.


3. 편집창


[ PC 편집기 ]


다운로드 받은후 압출파일을 풀고 실행파일을 실행 시키면 실행됩니다. 간단한 코딩을 넣고 실행한 창입니다.
나중에 아두이노 연결에 이 편집기를 이용할 예정입니다.

[ 온라인 편집기 ]



뭔가 코딩 되어 있으며 실행 버턴을 눌러보세요. 간단한 그래픽 쇼가 보여집니다. 간단한 예제로 시뮬레이터가 돌아간 모습을 보시고 코딩이 어떻게 했는지 한번 잘 살펴보세요.

4. Precessing의 문법 구조


void setup()
{
     초기화 명령이나 한번만 수행하는 명령들을 코딩;
}
void draw(){
    반복적으로 계속 그리는 동작명령들을 코딩;
}

아두이노랑 구조가 같습니다. 아두이노에서는 loop()함수이지만 프로세싱에서는 draw()함수입니다. 프로그램이 종료되기 전까지는 반복 호출되는 함수인 것죠.

5. Precessing의 간단한 버턴 예제


  • 사용함수 : background(), size(), rect(), fill(), mousePressed()
  • 내용 : 버턴 클릭 이벤트로 사각형의 색을 변경한다.

사용함수

  • background(255) : 배경색인데 0~255로 검색에서 흰색사이의 색을 지정해줍니다.
  • size(600,600) : 윈도우 사이즈
  • rect(100,200,100,200,10) : 사각형을 그리기인데 rect(x1,y1,x2,y2,모서리)를 나타냅니다. 시작꼭지점(100,200) 얼마만큼 이동한 끝점(100,200)을 나타냅니다. 그리고 마지막 인자 10은 모서리를 매끄렇게 하는 값인데 안써도 상관없습니다. 그냥 rect(100,200,100,200)이렇게 해도 됨.
  • fill(255,0,0) : 채우기 함수입니다. fill(R,G,B)값으로 생각하시면 됨
  • mousePressed()은 마우스 버턴을 눌렀을 호출하는 함수임(다른 표현도 있지만 이정도만 이해하세요.)

간단한 버턴 클릭 예제

boolean cnt = false;

void setup() {  // this is run once.   
   
   // set the background color
   background(255);
   
   // canvas size (Integers only, please.)
   size(600, 600);       
   
} 

void draw() {  // this is run repeatedly.  
   rect(10,10,580,300,10);    
   rect(200,350,200,100,10);    
}

void mousePressed(){
if(mouseX>200 && mouseX<400 && mouseY>350 && mouseY <450){
    cnt=!cnt;
}

if(cnt==true)fill(255,0,0);
else fill(0,0,0);
}

코딩을 보시면 총 세개로 나눠 졌습니다. 온라인 편집기에 있는 소스를 최대한 안건들고 간단한 버턴 클릭하는 예제를 코딩해 봤습니다.

setup() 함수에서 윈도우 설정을 했는데 배경을 background(255)로 흰색을 지정하고 크기를 size(600,600)으로 600x600 크기로 만들었습니다.

draw()함수에서는 만든 윈도우에서 rect()함수로 사각형을 두개 그렸습니다.


대충 위 그림처럼 윈도우 600x600 크기의 창이 뜹니다. 그리고 그 안에 rect()로 만든 사각형이 2개 보이시죠. 여기서 아래 작은 사각형을 버턴으로 이용할 예정입니다.

작은사각형 이미지를 버턴이라고 생각하고 해당 버턴을 누를려면 마우스 버턴을 클릭했을때 이벤트가 발생해야 겠죠. mousePressed()함수를 사용합니다. 이 함수는 마우스가 클릭했을때 호출되는 함수입니다. 그안에 버턴을 클릭했을때의 이벤트 로직을 작성해야 합니다.

void mousePressed(){
  마우스 클릭했을 때 호출;
}

위 마우스가 클릭했을때 호출되는 함수인데 간단하죠.
작은사각형 이미지가 버턴으로 사용되기 때문에 작은사각형 이미지 안을 마우스로 클릭했을때 이벤트를 실행시키면 됩니다.

쉽게 설계를 하면

  1. 사각이미지를 영역을 마우스로 클릭한다.
  2. 마우스 클릭 이벤트 발생한다.

그러면 사각형을 마우스클 클릭했을때 이벤트가 발생해야 합니다. 여기서 마우스가 아무곳이나 클릭한다고 해서 클릭 이벤트가 발생하면 안됩니다. 마우스를 클릭했을때 mousePressed()함수가 호출된다고 해서 무조건 동작해서는 안되고 사각형 안에서만 클릭하도록 코딩을 짜야 됩니다. 클릭했을때 호출되니깐 그 마우스 클릭한 좌표(mouseX,mouseY)가 작은 사각형버턴 영역에서 클릭했냐로 체크하는 로직을 짜면 쉽게 사각형을 버턴으로 만들 수 있겠죠.

버턴을 마우스 클릭했을때 원리

   rect(200,350,200,100,10);    

이 사각형을 클릭하기 위해서 사각형의 영역을 클릭하는 영역으로 잡아 줘야 합니다.


X축을 기준으로 하면 초록색 범위가 마우스 클릭 할 X축의 영역이 됩니다. 즉, 꼭지점 x1에서 x2까지이죠.
Y축을 기준으로 하면 노란색 범위가 마우스를 클릭 할 Y축의 영역이 됩니다. 즉, 꼭지점 y1에서 y2까지이죠.

그러면 사각형이 그러진 영역은 X축과 Y축이 겹쳐지는 저 영역이 바로 버턴의 영역이 되는 것이죠.

이걸 코딩으로 하면

if(mouseX>x1 && mouseX<x2 && mouseY>y1 && mouseY <y2){
        사각버턴 영역을 클릭 했을때 처리문;
}

코드를 분석으을 해보죠.

먼저 윈도우 상에서 마우스 좌표(mouseX,mousY) 추출 할 수 있습니다. 즉, 이 변수명으로 현재 마우스가 위치를 알 수 있게 됩니다.

if(mouseX>x1 && mouseX < x2)

이게 뭘 말할까요. X축 기준으로 클릭 범위가 초록색 범위여야 하잖아요. 그러면 마우스가 클릭한 mouseX의 값은 x1보다 크고 x2보다는 작을때 위 그림에서의 초록색 영역이 됩니다. 그래서 두 조건식으로 클릭한 마우스 mouseX좌표가 x1보다 크고 x2보다 작은가를 조건문으로 해서 작은사각형 이미지의 X축 영역을 체크하는 것이죠. 마우스 Y좌표도 이와 같습니다.

참고로 '&&' 기호는 "그리고" 이고 '||' 기호는 "또는"라는 뜻입니다.

if(mouseX>x1 && mouseX<x2 && mouseY>y1 && mouseY <y2)

총 4개의 조건식으로 모두 만족한 영영이 바로 X축 영역과 Y축 영역이 겹쳐지는 교집합 영역으로 바로 윈도우창의 작은 사각형의 영역이 됩니다. 이 조건을 만족하면 마우스 좌표는 작은 사각형 영역에 위치해 있다는 의미가 됩니다.

 rect(200,350,200,100,10);    

윈도우에서 작은사각형은 P1(200,400), P2(350,450) 꼭지점의 영역을 갖습니다.
이부분을 햇갈리시는 분들이 있습니다. 시작점 P1은 (200,350) 입니다. P2은 (x1+x2,y1+y2) 입니다. 사각형의 시작점을 기준으로 하는게 아니라 윈도우상에서 작은사각형이 위치좌표는 윈도우좌표에서 접근해야 하기 때문에 윈도우 상에서 작은사각형의 좌표를 지정해야 합니다.

rect()함수는 시작점(x1,y1)을 기준으로 끝점(x2,y2)만큼 크기의 사각형이냐의 표현입니다. 즉, 끝점은 윈도우 좌표에서 시작점 (x1,y1)이 이동했고 거기서 다시 사각형이 끝점으로 이동하기 때문에 시작점까지 이동한 거리를 더해줘야 합니다. 그래서 P2(200,100)이 아니라 (200+200,350+100)해서 (400,450)이 끝점이 되는 것이죠.

그러면 윈도우 상에서 마우스(mouseX,mouseY)가 작은사각형의 좌표 영역에 들어갔는지 if문으로 아래와 같이 표현이 됩니다.

 if(mouseX>200 && mouseX<400 && mouseY>350 && mouseY <450)

마우스를 클릭했을때 mousePressed()함수가 호출되고 위 if문을 통해서 작은 사각형 영역에 마우스 좌표가 현재 위치해 있는지 확인하는 문장이 됩니다. 이렇게 해서 작은사각형 이미지는 버턴의 능력을 갖게 됩니다.

작은사각형을 버턴으로 표현을 했으면 실제로 간단하게 색상을 변경하는 명령을 내려봅시다. 2가지 색을 교대로 바꾸게 할려고 별도로 cnt라는 변수를 하나 선언 했네요. 참/거짓으로 부울변수를 만들어서 참일때 빨강색, 거짓일때 검은색으로 바꿔 볼려고 합니다.

boolean cnt =fasle;

체크변수로 초기값으로 false를 주고 클릭했을때

cnt=!cnt;

이 문장으로 변수값을 반전 시켰습니다. 거짓이면 참으로 참이면 거짓으로 반전 문장입니다. 이코딩은 작은 사각형을 클릭했을때 발동해야 하기 때문에 마우스클 클릭했을때 호출되는 함수에서 다시 if문 사각형 영역에 있는 지 확인 되었을때 그 안에다가 표현 해야겠죠. 간단히 그안에다 이 한줄을 삽입하면 됩니다. 참 쉽죠.

그다음 채우기 fill()함수를 이용해 사각형을 채우는 로직을 if문으로 간단히 표현했습니다.

 if(cnt==true)fill(255,0,0);
 else fill(0,0,0);

if문으로 cnt가 참이면 색채우기를 빨강색으로 fill(255,0,0)으로 cnt가 거짓이면 검정색으로 fill(0,0,0)으로 바꿔줍니다. 색을 작은사각형 버턴을 클릭할때마다 빨강색과 흰색이 계속 바뀌게 됩니다.

이렇게 해서 간단한 로직에 대해서 설명했습니다. 그런데 왜 fill()함수만으로 색이 바꿔지는이 햇갈리는 분들이 아마 있을거에요. draw() 함수에서 매번 호출된다고 했는데 그안에서는 사각형 2개를 그립니다. 그리기 전에 사각형의 채우기 색을 지정해주면 그릴때마다 사각형의 색을 바꿀 수 있게 된다는 원리를 이용한거니 어렵게 생각하지 마세요.

쉽게 말해서,

  • cnt가 true 인 경우
fill(255,0,0)
rect(10,10,580,300,10);    
rect(200,350,200,100,10);  
  • cnt가 false 인 경우
fill(0,0,0)
rect(10,10,580,300,10);    
rect(200,350,200,100,10);  

이렇게 마우스를 클릭할때마다 색이 변경되어 그려진다는 것이죠. 어떤 느낌이신지 아시겠지요.

그래서 마우스클릭 이벤트 함수에서 작은사각형의 이미지를 클릭했다면 그안에다가 fill()함수로 draw()함수로 그리기 전에 채우기 색을 지정하면 쉽게 색상을 바꾸게 되는 것이죠.

결과



이걸로 아두이노랑 연결해서 버턴이미지를 클릭 할 때 빨강색으로 바꾸고 Red LED에 불이 들어오게 하고 다시 클릭 하면 검정색으로 바뀌게 해서 Red LED를 깜박이게 제어할 예정입니다.


마무리


많은 예제 중에서 왜 이런 예제를 설명하냐면요 다음편에 아두이노와 연결해서 이 예제로 간단히 LED를 On/Off 버턴으로 활용하기 위해서 입니다.
작은사각형 이미지를 클릭이벤트를 활용하여 큰 모니터 사각형과 버턴 사각형이 색이 빨강색으로 바뀌면 Red LED에 불이 들어오고 검은색으로 바뀌면 Red LED가 꺼지는 스위치버턴으로 활용하기 위해서이죠.
바로 프로세싱으로 간단히 아두이노를 제어하고 그 제어하는 방법을 이해하면 프로세싱을 통해서 아두이노를 시각화 할 수 있다는 것을 이 예제를 통해 소개하기 위해서 입니다.

오늘은 온라인 상에서 프로세싱을 간단히 다뤄보세요. 구글링 치면 강좌도 많고 간단히 함수들을 불러다가 배치해서 시각화 해보세요.

댓글()