본문 바로가기
프로젝트

아두이노 레이더 프로젝트 - 02. 레이더 꾸미기

by Bugwhale 2020. 2. 1.

이 글은 아두이노 레이더 프로젝트 - 02. 레이더 꾸미기에 대해 설명합니다.

1. 개요

프로젝트 1편에서 시리얼 통신을 통하여 아두이노로부터 프로세싱으로 송수신 하는 과정을 진행하였다. 여기서는 수신받은 값을 이용하여 레이더 꾸미기를 진행한다.

2. 문자열인 각도값과 거리값 분리

아두이노로부터 수신받은 값을 이용하여 레이더를 꾸밀 것인데 그 값을 바로 사용하기에는 문제가 있다. 송수신한 값은 문자열이고 우리는 각도 값과 거리 값을 문자열이 아닌 각각의 값으로 사용할 예정이기 때문이다. 아래 코드는 문자열인 각도값과 거리값을 분리하여 각각 정수값으로 변수에 넣어주는 코드이다.

import processing.serial.*;
 
// 앞으로 사용할 각도와 거리값을 저장할 변수
int iAngle, iDistance;
 
void setup() {
	size(500, 500);
	Serial myPort;
	// 통신 포트 번호 항상 확인하자
	myPort = new Serial(this, "COM3", 9600);
	// 이 함수(메소드)를 사용함으로 인하여 serialEvent() 함수를 사용할 수 있게 됨
	myPort.bufferUntil('\n');
}
 
void draw() {
	background(0, 0, 0);
	fill(30, 255, 30);
	// serialEvent() 함수를 통해서 가져온 값을 출력한다.
	text("Angle : " + iAngle, 20, 40);
	text("Distance : " + iDistance, 20, 60);
}
 
// 간단하게 생각하면 아두이노에서 if(Serial.available() > 0) 조건이 만족하면
// 이 함수가 실행된다.
void serialEvent(Serial myPort) {
	// sData 문자열에 받은 데이터를 넣는다.
	String sData, sAngle, sDistance;
	sData = myPort.readStringUntil('\n');
	// 문자열에 마지막 문자 '\n' 를 빼고 sData 문자열에 다시 넣는다.
	sData = sData.substring(0, sData.length()-1);
	// 문자 'a' 가 있는 위치를 알아낸다. indexOf 메소드는 해당 위치를 반환한다.
	int seperatorIndex = sData.indexOf("a");
	// 처음부터 seperatorIndex 전까지, 즉 각도값을 sAngle에 넣어준다.
	sAngle = sData.substring(0, seperatorIndex);
	// seperatorIndex 부터 마지막까지, 즉 거리값을 sDistance에 넣어준다.
	sDistance= sData.substring(seperatorIndex+1, sData.length());
	// 가져온 값은 문자열임으로 int 자료형으로 바꿔서 각각 변수에 넣어준다.
	iAngle = int(sAngle);
	iDistance = int(sDistance);
}

보이다 싶이 이전 소스코드와는 많이 달라졌다. 그래도 주석을 달아놨으니 이해하는데는 크게 어려움이 없을 것이다. 그렇다면 왜 이렇게 바꾸었는지 설명하자면 결론은 소스코드를 보기 쉽게(관리하기 쉽게)하고 레이더를 꾸미는 부분과 통신하는 부분을 나누려고하기 때문이다. 사실 초심자 분들에게는 이게 더 복잡해 보일거라 생각이 든다. draw() 에서는 최대한 시각적인 표현을 하는데 중점을 두고 serialEvent() 에서는 통신 부분을 처리할 것이다. 코드를 실행시켜보면 아래와 같은 화면이 생성될 것이다. 이제 각도 값과 거리 값이 확실하게 눈에 들어온다. 이 값을 사용하여 레이더를 꾸며보자.

3. 레이더 꾸미기

import processing.serial.*;

int iAngle, iDistance;

void setup() {
	size(500, 500);
	Serial myPort;
	myPort = new Serial(this, "COM3", 9600);
	myPort.bufferUntil('\n');
}

void draw() {
	background(0, 0, 0);
	fill(30, 255, 30);
	text("Angle : " + iAngle, 20, 40);
	text("Distance : " + iDistance, 20, 60);
	
	//**************************** 레이더를 그려주자
	// 글에서 따로 설명하겠다.
	pushMatrix();
	// 새로 생긴 매트릭스의 바탕화면은 빈화면으로 설정
	noFill();
	// translate(x, y) x, y 좌표로 이동한다.
	translate(250, 250);
	// 앞으로 그릴 도형의 굵기는 1 픽셀
	strokeWeight(1);
	// 앞으로 그릴 도형의 색상은 80, 230, 25 녹색
	stroke(80, 230, 25);
	// 100 픽셀마다 반호를 그려주자
	// arc(a, b, c, d, e, f) ab:기준점, c:높이, d:폭, e:호 시작지점, f:호 끝날지점
	for(int i = 1; i <= 4; i ++) {
		arc(0, 0, 100*i, 100*i, PI, TWO_PI);
	}
	// 15도 마다 선을 하나씩 그려주자.
	// line(a, b, c, d) a:x시작점, b:y시작점, c:x끝나는점, d:y끝나는점
	for(int i = 0; i <= 180; i += 15) {
		line(0,0,-200*cos(radians(i)),-200*sin(radians(i)));
	}
	// 글에서 따로 설명하겠다.
	popMatrix();
}

void serialEvent(Serial myPort) {
	String sData, sAngle, sDistance;
	sData = myPort.readStringUntil('\n');
	sData = sData.substring(0, sData.length()-1);
	int seperatorIndex = sData.indexOf("a");
	sAngle = sData.substring(0, seperatorIndex);
	sDistance= sData.substring(seperatorIndex+1, sData.length());
	iAngle = int(sAngle);
	iDistance = int(sDistance);
}

pushMatrix()과 popMatrix()의 개념을 여기서 설명하기 어렵다. 그래도 간단히 집고 넘어가자면, 우리가 새 창을 여는순간 수학적인 관점에서는 X, Y축을 가진 좌표평면이 생성된다. 앞으로 도형(선 포함)들을 그릴때 좌표를 입력해야 하는데, 그림 관련 함수는 항상 기준점을 가지고 있고 그리고 나서는 항상 다시 돌아와야하는 번거로움이 있다. 물론 상대좌표로 계속 이동하는 방법이 있지만 현재 좌표를 계속 계산해야하니 다음 도형을 그리는데 원치 않은 좌표가 들어갈 가능성이 높다. 그래서 우리는 도형을 그릴때마다 pushMatrix()을 통해 matrix라는 좌표평면을 만들어 여기서 도형을 그리고 popMatrix()통해 좌표평면을 지우면 원래있던 좌표로 돌아오게 된다. 즉, 여기서는 도형을 그리는데 필요한 좌표계산을 쉽게하기 위해서라는 정도로만 이해하면 되겠다. 실행 시켜보면 다음과 같은 화면이 생성될 것이다.

4. 레이더 침 그리기

import processing.serial.*;

int iAngle, iDistance;

void setup() {
	size(500, 500);
	Serial myPort;
	myPort = new Serial(this, "COM3", 9600);
	myPort.bufferUntil('\n');
}

void draw() {
	background(0, 0, 0);
	fill(30, 255, 30);
	text("Angle : " + iAngle, 20, 40);
	text("Distance : " + iDistance, 20, 60);
	
	//**************************** 레이더를 그려주자
	pushMatrix();
	noFill();
	translate(250, 250);
	strokeWeight(1);
	stroke(80, 230, 25);
	for(int i = 1; i <= 4; i ++) {
		arc(0, 0, 100*i, 100*i, PI, TWO_PI);
	}
	for(int i = 0; i <= 180; i += 15) {
		line(0,0,-200*cos(radians(i)),-200*sin(radians(i)));
	}
	popMatrix();
	//**************************** 레이더 침을 그려주자
	// Matrix 만들어주고
	pushMatrix();
	// 빈 바탕화면 설정
	noFill();
	// 좌표 이동
	translate(250, 250);
	// 레이더 침 굵기는 4
	strokeWeight(4);
	// 앞으로 그릴 도형의 색상은 80, 255, 25 진한녹색
	stroke(80, 255, 25);
	// 각도에 따라 레이더 침을 그려준다.
	line(0, 0, 200*cos(radians(iAngle)), -200*sin(radians(iAngle)));
	// Matrix 폭파
	popMatrix();
}

void serialEvent(Serial myPort) {
	String sData, sAngle, sDistance;
	sData = myPort.readStringUntil('\n');
	sData = sData.substring(0, sData.length()-1);
	int seperatorIndex = sData.indexOf("a");
	sAngle = sData.substring(0, seperatorIndex);
	sDistance= sData.substring(seperatorIndex+1, sData.length());
	iAngle = int(sAngle);
	iDistance = int(sDistance);
}

5. 레이더에 감지된 물체 그리기

import processing.serial.*;

int iAngle, iDistance;

void setup() {
	size(500, 500);
	Serial myPort;
	myPort = new Serial(this, "COM3", 9600);
	myPort.bufferUntil('\n');
}
void draw() {
	background(0, 0, 0);
	fill(30, 255, 30);
	text("Angle : " + iAngle, 20, 40);
	text("Distance : " + iDistance, 20, 60);
	
	//**************************** 레이더를 그려주자
	pushMatrix();
	noFill();
	translate(250, 250);
	strokeWeight(1);
	stroke(80, 230, 25);
	for(int i = 1; i <= 4; i ++) {
		arc(0, 0, 100*i, 100*i, PI, TWO_PI);
	}
	for(int i = 0; i <= 180; i += 15) {
		line(0,0,-200*cos(radians(i)),-200*sin(radians(i)));
	}
	popMatrix();
	//**************************** 레이더 침을 그려주자
	pushMatrix();
	noFill();
	translate(250, 250);
	strokeWeight(4);
	stroke(80, 255, 25);
	line(0, 0, 200*cos(radians(iAngle)), -200*sin(radians(iAngle)));
	popMatrix();
	//**************************** 감지된 물체 그려주자
	// Matrix 만들어주고
	pushMatrix();
	// 빈 바탕화면 설정
	noFill();
	// 좌표 이동
	translate(250, 250);
	// 레이더 침 굵기는 4
	strokeWeight(4);
	// 앞으로 그릴 도형의 색상은 255, 0, 0 빨강색
	stroke(255, 0, 0);
	
	// 각도와 거리에 따라 감지된 물체 그려주자
	// ellipse(a, b, c, d) a:x좌표, b:y좌표, c:폭, d:높이
	if(iDistance < 60) { // 60cm 미만일 때만
		// 총 200 픽셀을 60cm 비율로 마춰준다.
		float objectDis = 200.0*(iDistance/60.0);
		// 계산된 값으로 각각 x, y 길이를 얻어온다.
		float x = objectDis*cos(radians(iAngle));
		float y = -objectDis*sin(radians(iAngle));
		ellipse(x, y, 5, 5);
	}
	// Matrix 폭파
	popMatrix();
}

void serialEvent(Serial myPort) {
	String sData, sAngle, sDistance;
	sData = myPort.readStringUntil('\n');
	sData = sData.substring(0, sData.length()-1);
	int seperatorIndex = sData.indexOf("a");
	sAngle = sData.substring(0, seperatorIndex);
	sDistance= sData.substring(seperatorIndex+1, sData.length());
	iAngle = int(sAngle);
	iDistance = int(sDistance);
}

다음과 같이 감지된 거리에 따라 빨간 물체가 출력된다. ellipse(x, y, 5, 5); 에서 5, 5 부분을 바꾸면 감지된 물체 크기를 바꿀 수 있다.

6. 레이더 추가 정보 그리기

import processing.serial.*;

int iAngle, iDistance;

void setup() {
	size(500, 500);
	Serial myPort;
	myPort = new Serial(this, "COM3", 9600);
	myPort.bufferUntil('\n');
}

void draw() {
	background(0, 0, 0);
	fill(30, 255, 30);
	text("Angle : " + iAngle, 20, 40);
	text("Distance : " + iDistance, 20, 60);
	if(iDistance > 60)
		text("Object Not Detected", 20, 80);
	else
		text("Object Detected", 20, 80);
	
	//**************************** 레이더를 그려주자
	pushMatrix();
	noFill();
	translate(250, 250);
	strokeWeight(1);
	stroke(80, 230, 25);
	for(int i = 1; i <= 4; i ++) {
		arc(0, 0, 100*i, 100*i, PI, TWO_PI);
	}
	for(int i = 0; i <= 180; i += 15) {
		line(0,0,-200*cos(radians(i)),-200*sin(radians(i)));
	}
	popMatrix();
	//**************************** 레이더 침을 그려주자
	pushMatrix();
	noFill();
	translate(250, 250);
	strokeWeight(4);
	stroke(80, 255, 25);
	line(0, 0, 200*cos(radians(iAngle)), -200*sin(radians(iAngle)));
	popMatrix();
	//**************************** 감지된 물체 그려주자
	pushMatrix();
	noFill();
	translate(250, 250);
	strokeWeight(4);
	stroke(255, 0, 0);
	if(iDistance < 60) {
		float objectDis = 200.0*(iDistance/60.0);
		float x = objectDis*cos(radians(iAngle));
		float y = -objectDis*sin(radians(iAngle));
		ellipse(x, y, 5, 5);
	}
	popMatrix();
	//**************************** 레이더 정보 그려주자
	// Matrix 만들어주고
	pushMatrix();
	// 빈 바탕화면 설정
	noFill();
	// 좌표 이동
	translate(250, 250);
	for(int i = 1; i <= 4; i ++) {
		text(i*15+"cm", (50*i)-5, 20);
	}
	for(int i = 0; i <= 180; i += 15) {
		text(i+"°", -220*sin(radians(i-90))-9, -220*cos(radians(i-90))+5);
	}
	// Matrix 폭파
	popMatrix(); 
}
	
void serialEvent(Serial myPort) {
	String sData, sAngle, sDistance;
	sData = myPort.readStringUntil('\n');
	sData = sData.substring(0, sData.length()-1);
	int seperatorIndex = sData.indexOf("a");
	sAngle = sData.substring(0, seperatorIndex);
	sDistance= sData.substring(seperatorIndex+1, sData.length());
	iAngle = int(sAngle);
	iDistance = int(sDistance);
}

 위와 같이 레이더에 추가 정보가 출력된다. draw() 함수 시작부분과 레이더 그리주기 전에 if(iDistance > 60) 부분도 추가되어 레이더에 물체가 감지되었는지 안되었는지 문자도 띄워준다. 이제 기본적인 기능의 구현은 전부 끝났다. 다음 포스트에서는 코드 최적화 및 꾸민 레이더를 다듬어 보는 시간을 갖도록 하겠다.

5. 관련 블로그 글

5.1 프로젝트 관련

[series_list_posts] [series_list_related]

5.2 프로젝트 카테고리

프로젝트

6. 참조

아두이노 홈페이지 프로세싱 홈페이지

댓글