프로그래밍 언어 아희를 아시나요? (상당히) 독특한 프로그래밍 방법으로 나름의 명성이 있는데요, 예시 코드를 보고 가겠습니다.

방뿌반탸볃쑹희빠초텨
순처산썩속뱌뮹쏭셩토
붕툐터뱑뫃밙뎌붃벓본
사받본몽성멓샥뗘모볻
아희꿀잼빠썅펴댜셩속

네, 제가 직접 짰던 코드인데요, 이 코드가 하는 일은 무엇일 것 같나요? 무려 백준 1003번 문제의 해답 코드입니다. 수를 입력받아 피보나치 수열 함수의 호출 횟수를 출력하는 문제이죠.


혹시 여기까지만 보시고 채점 페이지에 저걸 제출한다면 "틀렸습니다" 문구를 보게 되실 겁니다. "자바스크립트로 된 아희 처리기"나 "알파희"에서는 잘 되는데, 백준 온라인 저지에서 사용하는 C언어 아희 구현체 caheui가 문제입니다. BOJ 게시판에 해당 구현체가 말썽이라는 언급문제제기가 있었지만 비주류 언어로서 안타깝게도 해결이 되지 않고 있습니다.


그래서 저는 아희 스펙을 완전히 준수하는 자바스크립트 인터프리터를 만들어보고자 이 프로젝트를 시작하게 되었습니다. 사실 위의 채점만이 문제라면 C언어로 짰을텐데요, 저는 더 큰 그림을 그리고자 했습니다. 인터넷 시대에 최첨단 세대들이 웹브라우저로 간편하게 아희를 접하고 이로움을 널리 퍼ㄸ... 아무튼 웹 환경이 아무래도 접근성이 좋고, 여차하면 node.js와도 호환되도록 작성해 쉘로도 실행할 수 있도록 구상했습니다.


앞서 말한 공식 자바스크립트 처리기는 (BOJ의 텍스트 입력과는 다르게) 입력 명령을 만날 때마다 prompt를 띄우는 식으로 입력을 받으며, 디버그 시 유용한 "한 단계만" 실행은 '실행 횟수'로 쓰이는 변수 k정수 입력에서도 사용하여 여러 단계를 건너뛰는 문제점이 있습니다. 그리고 자바스크립트 고유의 유니코드 표현법(Surrogate pair)을 잘 처리하지 못해 문자열 입력 테스트 스니펫에서 이모티콘 입력을 잘 처리하지 못하는 모습을 보였습니다. Aheui.js는 그러한 문제점을 개선하고 가능한 많은 부분을 모듈화하여 유지보수성을 높였으며, 명령어 실행에 사용하는 Aheui 객체에 run(), step(), stop(), reset() 등의 제어 메소드와 on(), once(), off()로 상태('start', 'step', 'stop', 'end'), 동작('reset'), 출력('character', 'integer')과 같은 이벤트 콜백을 두어 쉽고 풍부하게 기능을 활용할 수 있는 기반을 제공하고자 했습니다. 아직 개선할 점은 많지만 우선 깃허브에 코드를 공개해놓아 코드를 보실 수 있습니다.


한번 확인해보세요!

View on GitHub



2018.01.09 추가

caheui의 큐 문제를 수정한 풀리퀘스트를 제출했습니다! 언제쯤 적용이 되려나...

'진행중인 프로젝트' 카테고리의 다른 글

아희 인터프리터 Aheui.js  (0) 2017.09.12
블로그 이미지

예비컴공돌이

각종 프로젝트 진행중! 생각날때마다 블로그 업데이트합니다.

카카오뱅크가 오픈한 게 7월 27일, 난 그 날 밤 카카오뱅크 계좌를 개설했다.

처음 본인인증부터 시작해서,

이런 의미불명의 "알림" 등 계속된 오류 끝에 11시 50분경에서야 끝이 났다.


그 후 체크카드를 개설하려고 약관을 훑어보고 카드 선택 후 발급 신청을 하자, 기적같이 업무시간이 끝나 수십분 후에 다시 시도하라고 한다. 결국 의지로 신청한 콘 체크카드. 카카오프렌즈를 봐왔다면 알겠지만 콘은 초록색 쪼그마한 캐릭터이다. 내가 초록색을 좋아하기도 하고, 다른 캐릭터들이 큼직하게 그려져있을 때 그 사이에 깨알같이 그려진 그 디자인이 깔끔하고 귀여워서 얘로 골랐다. 솔직히 라이언같은 인기 많은거 신청하면 늦게 올까봐도..

그래서 드디어 지난 수요일(8/2)날, 본인 수령을 해야 한다는 연락 이후 도착한 노란 편지 한 통!

봉투도 잘 만든 것 같다. 깔끔한 배경과 재질에 카카오뱅크 마크가 뙇!

내용물은 아래처럼 카드, 웰컴기프트, 약관, 혜택안내 등으로 구성되어 있다.


웰컴기프트로는 깨알같은 카카오프렌즈 스티커 선물이. 어머 스티커를 시켰더니 체크카드가 왔어요 이건 노트북에 붙이는걸로!


앞으로 주력으로 사용할 귀요미 콘 카드.


적어도 오는 학기부터는 주력 은행을 바꿔보려고 한다. 무엇보다 은행앱이 깔끔하게 디자인돼서 빠르게 결제내역 조회, 이체가 가능하다는 점이 마음에 들었다. 학기중에는 밥값처럼 자잘하게 돈을 이체해야 할 일도 많은데, 지금은 이체 수수료도 없고, 상대방 계좌번호를 몰라도 카톡으로 보내는 기능이 마음에 들었다. 또 내년 1월까지 다양한 캐시백 혜택도 있어 쏠쏠히 재미를 볼 수도 있을 것 같다.


끝!

블로그 이미지

예비컴공돌이

각종 프로젝트 진행중! 생각날때마다 블로그 업데이트합니다.

[이전 글]

2017/02/03 - [소프트웨어/JS, Node.js] - 웹 워커(Web Worker) - 1. 개요


웹 워커는 UI 스레드와 별개로 동작한다. UI 스레드 상에서 여러 작업들끼리 시간을 나눠가며 실행될 필요 없이 오직 내 스크립트만을 위한 공간에서 끊김없이 작업을 이어나갈 수 있으며, UI 스레드는 널널해져서 페이지는 사용자 반응을 빠릿하게 처리할 수 있다는 장점이 있다.


웹 워커 사용 예

아래는 시간이 오래 걸리는 작업(1부터 5000000000까지의 합 계산)을 일반 코드 vs 웹 워커 상에서 실행하는 예시이다. 작업이 실행중일 때 일반 코드는 페이지의 반응이 멈추어 아래 버튼이 클릭되지 않는 반면, 웹 워커에서 실행하면 자유롭다.

실행 시간은 두 경우 모두 비슷하지만, 페이지가 멈추기 때문에 창을 새로 고칠 수 없고 클릭에 대한 반응도 할 수 없는 등 전반적으로 좋지 않다.


웹 워커 선언

처음에 페이지에 있는 스크립트에서 워커 객체를 생성한다.

var worker = new Worker("worker.js");

이러면 worker.js 파일을 불러와서 웹 워커 상에서 실행하고, worker 변수를 통해 워커를 제어할 수 있다. worker.js라는 파일을 만들어서 코드를 작성하면 되는 것이다.


그렇다면 그 코드가 실행되는 과정 혹은 결과는 어떻게 알 수 있을까?

웹 워커와의 통신은 이벤트 기반으로 이루어진다.

// worker.js
onmessage = function(e) {
	postMessage(e.data + " World!");
};
// 페이지 스크립트
var worker = new Worker("worker.js");
worker.onmessage = function(e) {
	console.log(e.data);
};
worker.postMessage("Hello");

간단한 웹 워커 사용 예제이다. 스크립트에서 웹 워커를 생성하고 message라는 이벤트의 리스너를 등록하고 값을 보내면, 웹 워커에서는 message 이벤트가 발생하고 e.data 값을 이용해 결과를 원래 창으로 전송한다. 그러면 결과적으로 "Hello" + " World!" = "Hello World!"라는 문자열이 콘솔에 출력될 것이다.


위처럼 가장 기본적인 틀을 바탕으로 느려지는 부분을 웹 워커 상에서 실행하면 된다. 넘겨주는 값은 문자열, 숫자 뿐만 아니라 배열이나 객체도 가능하다.


참고로 웹 워커 스크립트 내에서는 몇몇 작업들(DOM 수정 포함)을 하지 못한다. UI 스레드에서 화면을 그리고 있는데 워커에서 갑자기 내용을 바꾸었을 때의 처리 등 복잡한 문제가 발생한다. 따라서 결과값을 이벤트로 보내고, UI쪽 코드에서 처리해야 한다.

'소프트웨어 > JS, Node.js' 카테고리의 다른 글

웹 워커(Web Worker) - 2. 사용법  (0) 2017.02.23
웹 워커(Web Worker) - 1. 개요  (0) 2017.02.03
블로그 이미지

예비컴공돌이

각종 프로젝트 진행중! 생각날때마다 블로그 업데이트합니다.

자바스크립트에서 웹 워커라는 것이 있다. 단순히 웹 페이지만을 띄우는 것을 넘어 다양한 작업을 하는 웹 앱이 나타남에 따라 작업의 양은 늘고 반응 속도는 빨라져야 한다. 그 일환 중의 하나로 사용할 수 있는 "웹 워커(Web Worker)"를 소개하려고 한다.


우선 이번 글에서는 웹 워커의 필요성 및 개념 소개 정도로 하고 사용법은 다음에 올리려고 한다.



시간이 오래 걸리는 작업

기본적으로 웹페이지는 자바스크립트 실행과 UI 반응(클릭, 스크롤 등)이 한 스레드에서 돌아간다. 그래서 자바스크립트에서 짧은 시간에 끝나지 않는 계산, 혹은 무한루프를 돌려보면 사이트 자체가 응답하지 않는다. 그래서 자바스크립트에서 실행하는 데 시간이 필요한 요청(AJAX)등은 비동기 이벤트를 이용한다.


스크립트 실행 순서도, 한 번에 한 가지 작업만 실행한다. 장시간 스크립트를 실행하면 사용자의 요청을 처리하지 못해 "렉"이 발생하므로(왼쪽의 "대기" 부분) 스크립트는 짧게, 비동기적으로 실행할 수 있는 작업은 비동기로 실행하는 것이 좋다.


그러나 비동기화할 수 없는 작업, 예컨데 데이터 파싱이나 물리엔진 연산 같은 경우도 있다. 일반적인 웹페이지라면 스크립트가 많지 않거나 적당히 빠르게 실행되어 상관없지만, 위와 같은 작업들이 많아지면 사이트의 반응이 느려지고, 사용자는 불편해 할 것이다. 이것을 해결하기 위해 "웹 워커"라는 표준이 만들어져 웹 브라우저들이 지원하고 있다. 안드로이드, 아이폰 등 모바일 환경에서도 지원하고 IE도 10 이상이면 가능한 등 대부분의 브라우저에서 사용할 수 있다고 한다.



웹 워커란?

웹 워커는 "스레드(Thread)" 기능을 웹 상으로 구현한 것이다. 위에서 설명한 내용대로 일반 자바스크립트는 "UI 스레드"라 부르는 기본 스레드 상에서 함께 돌아가서, 화면 처리와 스크립트 실행 등은 서로 기다려가면서 차례대로 실행한다. 이 방법은 DOM 관리 같은 기능을 복잡하게 설계할 필요가 없다는 장점이 있지만, 한 작업이 길어지면 나머지 작업들은 그게 끝날 때까지 기다려야 한다.


앞서 그림에서 흰색 화살표 하나가 UI 스레드를 의미한다. 한 스레드에서는 하나의 작업만 실행할 수 있는데, 웹 워커는 저 화살표를 하나 더 만드는 것이라고 생각하면 된다. 스크립트가 UI 스레드에서 실행되는 시간(주황색 선)이 줄면 사용자 입력에 반응하는 시간(파란색 선)이 늘어나고 더 빠릿빠릿한 페이지를 볼 수 있을 것이다.


웹 워커가 적용된 페이지의 흐름. 오래 걸리는 작업은 따로 빼내어 실행하고 결과만 받아서 표현한다. 만약 저 작업을 그냥 실행한다면 나머지 작업들은 하나도 실행되지 못해서 유저는 멈춰있는 화면만 멍하니 바라보아야 할 것이다. 웹 워커를 이용하면 이런 점에서 이득을 취할 수 있다.


실제로 코드 상에서 이용하는 법은 다음 글에서 보여줄 것이다.



단, 계속 진행하기 전에, 웹 워커가 만병통치약은 아니라는 점을 알아두어야 한다. 페이지가 무겁다고 느낄 경우, 우선은 스크립트로 진행하는 작업들 때문인지 확인하는 것이 우선이다. 단순히 이런 문제가 아닌, 리소스가 너무 많다던가 하는 것일 수도 있으며, 자바스크립트로 물리엔진을 돌리거나 웹페이지를 몇 개씩 받아와서 파싱하는 것처럼 많은 작업이 아니라면 속도 향상이 많지 않을 수도 있다.(심지어 이것들도 잘 처리하면 웹 워커 없이도 자연스럽게 사용할 수 있다.)

'소프트웨어 > JS, Node.js' 카테고리의 다른 글

웹 워커(Web Worker) - 2. 사용법  (0) 2017.02.23
웹 워커(Web Worker) - 1. 개요  (0) 2017.02.03
블로그 이미지

예비컴공돌이

각종 프로젝트 진행중! 생각날때마다 블로그 업데이트합니다.

외국 웹을 돌아다니다 보면 i18n이나 l10n 등의 표현을 종종 발견할 수 있다. leet체 같은건가 싶었는데 실제로는 조금 달랐다.

영단어를 보면 꽤 단어들이 몇몇 있다. 세상에서 제일 긴 단어 같은걸 언급하자는 건 아니고, 위의 것들도 긴 단어를 축약해서 표현한 것이다.


i18n은 internationalization(국제화), l10n은 localization(현지화)을 각각 첫 글자와 마지막 글자 사이의 글자수를 적어 표현한 것이다. 한국어로는 3글자로 끝나는데 영어로 표현하자니 아득하다. 저걸 몇번씩 타자로 치다 보면 키보드가 다 닳아버릴 것만 같다. 그래서 영어권 사람들은 더 간략하게 쓰기 위해 글자수 표현법을 만들어냈다.


이 두 단어는 번역 이야기에 자주 등장하는 단어들이다. 다른 나라에도 맞게 한다는, 얼핏 보면 비슷한 의미인 것 같다. 그 둘의 차이는 무엇일까?

전문적으로 배운 것은 아니지만 내가 이해한 바로는 이렇다.

i18n, 국제화는 한 언어를 여러 언어들에 대응시켜 다른 국가의 사람들에게도 읽힐 수 있게 만드는 작업이다.

반면 l10n, 현지화는 그것보다 좀 더 심화적으로, 단순히 내용이 읽히는 것에 더해서 지역명이나 인물을 바꾸거나, 숙어와 속담, 문화와 생활 양식 등을 대상 국가에 맞춰 치환해서 현지화라는 이름에 걸맞게 해당 문화권에 거부감 없게 다가갈 수 있도록 만드는 작업이라고 생각한다.


궁극적으로는 현지화를 하는 게 이상적이겠지만, 어찌 보면 당연하게도 인력, 지식, 시간, 예산 등 여러 한계가 있을 테니..

일반 스마트폰 앱이나 웹사이트 같은 경우는 i18n 번역 정도로도 충분히 접근성을 확보할 수 있다. 현지화는 게임이나 광고 등 사용자에게 더 감성적으로 다가가고자 하는 경우에 적합할 것이다.

블로그 이미지

예비컴공돌이

각종 프로젝트 진행중! 생각날때마다 블로그 업데이트합니다.

AVR을 프로그래밍할 때 주로 C를 사용하지만, 다른 부품들과 시간을 맞추기 위해 어셈블리를 써서 최대한 성능을 뽑아내야 할 때가 있다.

그 과정 중 자주 사용하는 동작인, 레지스터 하나의 값을 0으로 만드는 방법 34개가 정리된 글을 발견했다. 특정 조건/값에서만 되는 경우도 있고, 할 수 있다는 것에 의의를 두고 굳이 이렇게 쓸 필요는 없겠다 싶은 방법도 있어서 이 글에서 34가지 전부는 아니고 중요한/쓸만한 방법 5개만 추려보았다.


우선 시작하기에 앞서 사실 첫번째 방법이 가장 많이 쓰인다. 나머지 방법들은 상태 레지스터 결과가 다르니 경우에 따라서는 유용할 수도 있다.


지우고자 하는 레지스터의 이름은 "reg"로 표현한다.


1. CLR / EOR

clr reg

r0~r31까지 모든 레지스터에 대해 사용할 수 있는 가장 단순하고 쉬운 방법이다. CLR 명령어는 기본적으로 아래의 EOR(배타적 논리합, XOR)과 동일하다.

eor reg, reg

위 두 가지 명령은 실행하면 상태 레지스터를 변화시킨다. S(부호), V(오버플로우), N(음수) 비트는 0으로 설정하고, Z(제로) 비트는 1이 된다. 나머지 H(절반 캐리), C(캐리) 비트는 전에 설정된 대로 유지된다.


2. LDI

ldi reg, 0

처음에 CLR과 LDI의 차이를 찾아보다가 이 글을 쓰는 계기가 되었다. reg에다가 0 값을 넣는 명령인데, LDI는 CLR과 달리 r16~r31까지 상위 16개의 레지스터에만 사용할 수 있다. 그리고 상태 레지스터는 변하지 않는다! 전에 설정된 상태 그대로 유지되므로 분기 명령 전 레지스터 초기화 상황에는 유용할 듯 하다.


3. SUB

sub reg, reg

자기 자신을 빼서 0으로 만드는 방법이다. 이 방법은 r0~r31까지 모든 레지스터에 대해 사용할 수 있다. 1번과 비슷한데, 결과로 설정되는 상태 레지스터가 살짝 다르다.

S, V, N, Z 비트는 1번과 동일하게 설정되는데, EOR에서는 설정되지 않았던 H, C 비트까지도 SUB을 사용하면 0으로 설정된다.


4. MOV

mov reg, r1

x86 어셈블리를 사용해봤다면 mov reg, 0의 형태에 친숙할 것이다. 그러나 AVR에서는 레지스터간 복사는 MOV, 즉시값은 LDI로 분리되어 있다.

이 방법은 MOV 명령을 이용해 값이 0인 레지스터를 복사해오는 것인데, r0~r31까지 모든 레지스터에 대해 사용할 수 있고, LDI와 마찬가지로 상태 레지스터를 변화시키지 않는다.


r1 레지스터를 사용한 점도 눈여겨볼 수 있다. 순수 어셈블리로만 프로그램을 작성했다면 모를까, avr-gcc를 사용해 C 프로그램을 작성하고 인라인 어셈블리를 사용한다면 r1은 항상 0으로 되어있기 때문이다.(참고) C 인라인 어셈블리에서 r0 레지스터는 아무렇게나 사용해도 되며, r1 레지스터는 사용한 후에 반드시 0으로 돌려놓아야 한다.

물론 어셈블리 코드 내에서는 자유롭게 r1을 사용해도 되고, 중간에 인터럽트가 발생해도 r1에 저장된 값을 유지해주므로 코드가 끝날 때 r1을 0으로 돌려둘 것만 기억하면 된다.(인라인 어셈블리 문법으로 r1을 사용한다고 표시하면 알아서 된다.) 단, 이 MOV 명령 사용 전 r1을 0이 아닌 값으로 바꿨다면 이 방법은 쓸 수 없을 것이다.


5. MUL ([F]MUL[S[U]])

mul r0, r1

여기서도 4번과 마찬가지로 값이 0인 r1 레지스터를 사용했다. 이 방법의 기본적인 원리는 레지스터의 값에 0을 곱해서 0으로 만드는 것이다. 그런데 reg 대신 r0으로 사용한 것을 볼 수 있는데, MUL 계열 명령어의 특성상 곱셈 결과가 r1:r0에 저장되기 때문이다. 따라서 r0을 초기화할 때에만(reg=r0) 사용할 수 있다.


그리고 곱셈 명령어는 2 사이클만큼 소요되고, 상태 레지스터의 H, S, V, N 비트는 전 상태 그대로 유지한다. Z, C 비트는 0으로 설정될 것이다.

'하드웨어 > AVR·Arduino' 카테고리의 다른 글

레지스터를 지우는 방법 5가지  (0) 2017.01.23
블로그 이미지

예비컴공돌이

각종 프로젝트 진행중! 생각날때마다 블로그 업데이트합니다.

회로에서 가장 기본적인 부품에는 저항이 있다.


사진 출처 위키피디아

생김새로는 이렇고, 회로도에서는 아래와 같이 그린다.

아래의 기호는 IEC(국제 전기 표준 회의)의 저항 기호라는데, 주로 그냥 위의 지그재그 기호를 사용한다.


저항의 겉에는 색띠로 저항값을 표시하는데 그걸 읽는 규칙은 이렇다.

비교적 멀찍히 띄워진 띠(주로 금색, 은색)를 오른쪽에 두고, 오른쪽 끝 2개의 띠를 빼고 아래 표의 색깔대로 읽는다.

 검정

 갈색

 빨강

 주황

 노랑

 초록

 파랑

 보라

 회색

 흰색

 0

 1

 2

 3

 4

 5

 6

 7

 8

 9


이걸 10진수로 차례대로 배열해서 숫자를 얻는다.

위의 사진의 저항의 경우 금색을 오른쪽으로 두어서, 왼쪽부터 읽으면 주황, 주황이므로 33이 나온다.


그 다음, 마지막에서 두번째 띠는 위 표의 숫자로 10의 n배 해서, 즉

 검정

 갈색

 빨강

 주황

 노랑

 초록

 파랑

 보라

 1

 10

 100

 1,000(1k)

 10k

 100k

 1,000k(1M)

 10M

를 먼저 구했던 숫자에 곱한다. 물론 위의 표에 더해서 회색, 흰색도 있고 검정 아래로 금색(0.1), 은색(0.01)도 있다. 아무튼 주로 쓰이는 색들은 이렇다.

아까 33을 구했고, 마지막에서 두번째 띠는 갈색이므로 10을 곱하면 330이 나오고, 이게 이 저항의 값이 된다. 즉, 위의 저항은 330옴이다.


마지막 띠는 이 저항의 오차값인데, 주로 쓰이는 색은 이렇다.

 없음

 은색

 금색

 갈색

 보라색

 20%

 10%

 5%

 1%

 0.1%

오차가 작을수록 가격은 비싸지는데, 5%정도면 충분하고 10% 이상 오차가 나는 저항도 거의 없으니 무난하게 5% 짜리면 적당하다.


대략적인 가격은 5% 저항 개당 10원, 1%는 20원 정도이고 더 큰 오차를 갖는 저항은 찾기도 힘들다. 보통 파는 데에서는 같은 저항값끼리 10개 단위로 판매한다.

'하드웨어 > 전자 기초' 카테고리의 다른 글

저항, 저항 읽는 법  (0) 2017.01.07
블로그 이미지

예비컴공돌이

각종 프로젝트 진행중! 생각날때마다 블로그 업데이트합니다.

약 1년 만에(?) 블로그 포스팅을 하는데... 전의 내용에 이어서 최종 결과까지 올리자면 이렇다.

우선 제작 사진으로,

 

이 것이 실제로 시연에 사용된 로봇의 모습이다. 전반적인 뼈대는 EV3를 사용했고, 그 위에 3개의 USB 마이크가 연결된 라즈베리 파이를 얹었다. 라즈베리 파이의 전원으로 EV3의 USB 포트를 사용했고, 서로 블루투스를 이용해 통신을 한다.


라즈베리 파이는 3으로, 내장된 블루투스가 있고 Raspbian Jessie를 사용했다. 프로그램은 systemd에 데몬으로 등록해두어 시스템 시작 시에 같이 시작되도록 했고, ALSA로 마이크로부터 값을 읽어서 계산해서 연결된 EV3로 값을 전송한다.


EV3는 통신으로 방향을 받아 모터를 제어하며, 정면에 설치된 초음파 센서를 이용해 앞 대상까지의 거리를 측정해서 정지 및 장애물 회피 기능도 수행한다. 앞의 물체와의 거리가 일정 이상 가까우면, 마이크로 인식한 주인의 방향이 정면이라면 (앞의 물체가 주인으로 인식해서) 정지하고, 아니라면 장애물이라고 인식해 크게 방향을 전환한다.


아래는 작동하는 모습을 촬영한 영상이다. 약 2미터 정도의 거리까지는 위치를 인식해서 방향을 찾아온다.



'완료된 프로젝트 > Homing Robot' 카테고리의 다른 글

완성품 & 작동 영상  (0) 2017.01.07
프로그램 제작기  (2) 2016.12.10
로봇 따라가기의 기본 원리  (3) 2016.12.09
프로젝트 개요  (5) 2016.12.08
블로그 이미지

예비컴공돌이

각종 프로젝트 진행중! 생각날때마다 블로그 업데이트합니다.

이번 글에서는 앞선 글의 내용들을 이용해 어떻게 프로그램을 짰는지 설명한다.


프로그램의 구조는 아래와 같다.

  1. 백그라운드에서 3개의 마이크에서 음성 신호를 받는대로 고속 푸리에 변환(FFT)을 수행해 특정 주파수 대역의 크기(Magnitude)를 구해 방향을 찾는다
  2. 일정 주기로 1의 결과를 읽어와, EV3가 수신할 수 있는 데이터 포맷으로 인코딩해 전송한다.
  3. EV3 로봇은 그 데이터를 수신해 방향을 향해 회전하고 이동한다.


1, 2, 3번 프로그램은 각각 별개로 실행되고, 서로 데이터를 주고받는 방법을 가지게 한다.



1번 프로그램


우선 1번 프로그램은 시스템 의존적인 부분이 대부분이고, 또 그것들은 C 라이브러리로 제공되기에 C언어로 작성했다.


마이크에서 값을 받아오는 부분은 리눅스 환경의 ALSA를 이용해 녹음하는 예제(링크)를 이용했다. 복잡한 기능 없이 소리만 읽어오면 되므로 위의 간단한 코드로 큰 문제 없이 값을 얻어올 수 있었다.


라즈베리 파이에서 FFT를 수행하기 위해 검색한 결과, 내장된 GPU를 이용해 연산을 수행하는 라이브러리가 공개되어 있었다.(라즈베리 파이 블로그, GPU_FFT 프로젝트 페이지) 이 라이브러리를 이용해 빠르게 계산할 수 있고, 여러 개의 작업을 모아 한번에 수행(batch)하는 기능도 있어 3개의 마이크를 이용하는 데 적합하다고 생각했다.


마지막으로, 계속 연산을 수행하면서도 2번 프로그램과 통신을 하기 위해 pipe와 fork를 사용했다.

1번 프로그램은 실행되면 pipe를 열고 fork를 해서 자식 프로세스를 만든다. 부모 프로세스는 stdin에서 입력을 대기하고, 입력이 들어온 경우 자식 프로세스에 SIGUSR1을 발생시킨 뒤 pipe read를 대기한다.

자식 프로세스는 계속해서 마이크에서 값을 읽어와 연산을 수행하고, SIGUSR1을 받았을 때 pipe로 현재 각도를 전송(write)한다.

그러면 부모 프로세스는 pipe에서 각도를 읽어오게 되고, 그 각도를 stdout으로 출력한다.



2번 프로그램


2번 프로그램은 파이썬으로 되어있는데, 이 프로그램이 맡는 역할은 이렇다.


우선 블루투스 SPP 프로파일이 사용되도록 설정된 라즈베리 파이에서(참고),

이 프로그램은 우선 subprocess 모듈로 "rfcomm watch hci0" 프로세스를 실행해 EV3가 라즈베리 파이에 연결되어  "/dev/rfcomm0" 포트가 생기기를 기다린다.

포트가 생겼다면, serial 모듈로 /dev/rfcomm0 과 연결해 EV3와의 통신 연결을 만든다.


그 후, subprocess로 stdin과 stdout을 PIPE시킨 1번 프로그램을 실행한다.

다음으로 1초 간격으로 루프를 돌면서, 1번 프로그램의 stdin으로 데이터 하나를 입력해서, stdout으로 출력되는 각도를 가져온다.

그 각도(-180 ~ 180)을 EV3 모터 입력으로 들어갈 방향의 범위(-100 ~ 100)로 변환한 뒤, struct 모듈을 이용해 EV3 메시지 포맷에 맞는 형태로 감싸서 serial로 전송한다.(참고, 이 글에서는 EV3로부터의 수신이지만, EV3로의 송신도 가능함을 확인했다. EV3로의 송신 관련해서는 자료가 전무해 힘들었다.)


만약 1번 프로그램이 종료되거나 시리얼 포트로 자료가 전송되지 않았다면, 1번 프로그램에 문제가 발생했거나 EV3와의 연결이 종료된 것으로 판단, 반복문을 빠져나가 /dev/rfcomm0 포트 대기 부분으로 돌아간다.



3번 프로그램


마지막으로 3번 EV3 프로그램은 복잡한 동작은 없어 레고 EV3 블록 프로그래밍 툴로 제작했다.

라즈베리 파이와 블루투스 연결을 하고 블루투스로 각도 정보를 읽어오는 부분, 저장된 각도 정보대로 모터를 조종해 이동하는 부분으로 구성되어있다.



결론


2번 프로그램의 EV3로의 송신 관련해서는 나중에 또 글을 올릴 예정이다. 구글링을 해봐도 자료가 라즈베리 파이에서 수신하는 것 하나만 나와서, 혹시라도 같은 주제로 헤맬 사람이 있을지 몰라 정보를 공유하려고 한다.

'완료된 프로젝트 > Homing Robot' 카테고리의 다른 글

완성품 & 작동 영상  (0) 2017.01.07
프로그램 제작기  (2) 2016.12.10
로봇 따라가기의 기본 원리  (3) 2016.12.09
프로젝트 개요  (5) 2016.12.08
블로그 이미지

예비컴공돌이

각종 프로젝트 진행중! 생각날때마다 블로그 업데이트합니다.

(이전 글) 2016/12/08 - [진행중인 프로젝트/Homing Robot] - 프로젝트 개요


지난 포스트에서 프로그램의 흐름을 말했는데, 여기서는 좀 더 구체적으로 방법을 설명하려고 한다.


우선 소리의 크기는 거리가 멀어질 수록 작아진다는 점에서 착안해, 마이크에 인식되는 소리의 크기는 그 마이크의 주인으로부터의 거리와 비례한다고 가정했다. (여기서 소리의 반사, 2개 이상의 음원, 거리에 따른 역자승 법칙 등은 고려하지 않았다.)


이 때 로봇에 마이크를 3개 장착하는데, 그 마이크들은 서로 120˚의 각도를 두고 각각 전방 왼쪽, 전방 오른쪽, 후방에 배치한다.

그러면 각 위치로부터 들리는 소리의 크기를 마이크 방향 벡터와 곱해서, 그 합이 음원의 위치일 것이라고 생각하고, 식을 계산했다.


이것을 구현할 때, 단순히 소리의 크기로만 계산하면 다른 소리나 잡음에 취약하기도 하고, 주인만을 찾아가기가 힘들 것이라고 생각했다.

그래서 주인은 특정한 신호를 내고, 로봇은 그 특정한 신호만 인식하기 위해 푸리에 변환을 사용했다. 푸리에 변환으로 일정한 주기의 소리를 주파수 대역별 음량으로 변환해서, 그 중 원하는 주파수 대역만 골라 사용하는 것이다.

이걸 이용해 주인, 집, 다른 사람 등 서로 다른 주파수 대역을 사용해 대상을 바꾸는 것도 생각했는데 미처 구현하지는 못하고 주인의 주파수만 따라오도록 했다.


정리하면, 주인이 특정 주파수를 내는 비콘(스마트폰의 사인파 소리 생성기 앱)을 들고있으며, 로봇은 마이크별로 다르게 들려올 그 주파수 대역의 음량을 이용해 방향을 찾는다.


여기서 주인이 내는 주파수는 사람에게 잘 안들리는, 들려도 심하게 거슬리지는 않을 18000Hz로 사용했었는데, 실험해본 결과 마이크 감도 문제인지 방향을 잘 잡지 못해서 15000Hz로 내리게 되었다.

'완료된 프로젝트 > Homing Robot' 카테고리의 다른 글

완성품 & 작동 영상  (0) 2017.01.07
프로그램 제작기  (2) 2016.12.10
로봇 따라가기의 기본 원리  (3) 2016.12.09
프로젝트 개요  (5) 2016.12.08
블로그 이미지

예비컴공돌이

각종 프로젝트 진행중! 생각날때마다 블로그 업데이트합니다.