October 27, 2020

디바이스마트 미디어:

디바이스마트 온라인 매거진 전자책(PDF)이 무료! -

2020-09-29

디바이스마트 자체제작 코딩키트 ‘코딩 도담도담’ 출시 -

2020-08-10

GGM AC모터 대량등록! -

2020-07-10

라즈베리파이3가 드디어 출시!!! (Now Raspberry Pi 3 is Coming!!) -

2016-02-29

MoonWalker Actuator 판매개시!! -

2015-08-27

디바이스마트 레이저가공, 밀링, 선반, 라우터 등 커스텀서비스 견적요청 방법 설명동영상 입니다. -

2015-06-09

디바이스마트와 인텔®이 함께하는 IoT 경진대회! -

2015-05-19

드디어 adafruit도 디바이스마트에서 쉽고 저렴하게 !! -

2015-03-25

[29호] Intel Edison Review -

2015-03-10

Pololu 공식 Distributor 디바이스마트, Pololu 상품 판매 개시!! -

2015-03-09

[칩센]블루투스 전 제품 10%가격할인!! -

2015-02-02

[Arduino]Uno(R3) 구입시 37종 센서키트 할인이벤트!! -

2015-02-02

[M.A.I]Ahram_ISP_V1.5 60개 한정수량 할인이벤트!! -

2015-02-02

[디웰전자] 전류센서 파격할인!! 50% 할인이벤트!! -

2015-02-02

[에스엔에스] 신상품 대량 입고기념 할인이벤트!! -

2015-02-02

[Uni-Trend] 인기상품 10종 할인이벤트!! -

2015-02-02

신학기 기념 니덱서보모터 할인이벤트!! -

2015-02-02

UDT와 유나이티드 콤프레샤 인기모델 15종 최대15% 할인!! -

2015-02-02

[OWON] 오실로스코프 VDS시리즈 최대12% 특별할인!! -

2015-02-02

보조배터리 최대 35% 할인이벤트!! -

2015-02-02

[30호] 너무 쉬운 아두이노 DIY ② – 카멜레온 반지 + 빨노파 게임기 만들기

30 아두이노 카멜레온

30 아두이노 카멜레온

             글 ㅣ 신상석 ssshin@jcnet.co.kr

 

  여러분, 안녕하세요. 앞으로 1년에 걸쳐(6번 정도) [너무 쉬운 아두이노 DIY(Do It Yourself)] 강의를 진행할 강사 신상석입니다.
제 소개 간단히 드립니다.
· 서울대 제어계측공학과(학사) →, KAIST 전산과(석사) → KAIST 전산과(박사 수료)
· ETRI 책임연구원 → 해동정보통신 연구소장 → 욱성전자 연구소장 → (현재) 제이씨넷 연구소장, 상명대학교 컴퓨터시스템공학과 겸임교수, 임베디드홀릭 카페(http://cafe.naver.com/lazydigital) 부매니저, 아두이노 / AVR 강사
· 자격증 : 전자계산기 기술사
· 취미 : 영화보기, 바둑두기, 책읽기, 글쓰기(?), 여행하기, 이야기하기
· 연락처 : ssshin@jcnet.co.kr, 있는그대(cafe.naver.com/lazydigital)이 강의는 아두이노를 가지고 간단하게 생활에 필요한 용품을 만들어 보는 강의입니다. 뚝딱뚝딱 뭔가 자신만의 DIY 용품을 만들어 보는 쏠쏠한 재미가 있는 강의라고나 할까요? 이미 주변에 아두이노와 관련한 많은 책이 출간되었고 카페나 블로그를 통하여 강의가 진행된 경우도 꽤나 많이 있는데도 불구하고, 이 지면을 통하여 강의를 개설한 이유는 다음과 같습니다. 

1. 아두이노의 초보자들을 위한 쉽고 재미있는 강의가 거의 없는 것 같아, 가능하면 초등학생(?)까지도 함께 해 볼 수 있는 그런 강의를 한 번 해보고 싶어서…
2. 아두이노를 가지고 뭔가 조금은 다른, 자신만의 창의적인(?) DIY를 할 수 있는 자리를 만들어주고 싶어서…
3. 디바이스마트매거진은 임베디드와 관련된 독자들이 많고, 발행 부수도 많아, 저와 제가 속한 회사(제이씨넷) 그리고 임베디드홀릭 카페의 홍보에 도움이 될 것 같아서…

아두이노 계획표현재 구상하고 있는 회차별 내용을 간략하게 정리해 보면 다음과 같습니다. (변경될 수 있습니다.) 앞으로 즐겁고 알찬 강의가 될 수 있도록 최선을 다할 것을 약속 드리며, 이 강의를 보는 독자들도 메일이나 카페를  통하여 Q&A(Question & Answer)나 피드백을 주셔서, 함께 정감을 나눌 수 있는 계기가 되기를 기대해 봅니다.

   여러분, 안녕하세요. 신상석 강사입니다. 이번 회에서는 컬러 LED를 가지고 만들 수 있는 DIY 작품을 2가지를 만들어 보겠습니다. 하나는 여러가지 색깔로 변하는 ‘카멜레온 반지’이고, 또 하나는 가위 바위 보 게임과 비슷한 ‘빨노파 게임기’입니다. 간단하고 재미있는 DIY이므로, 이를 약간씩 응용하여 최종적으로는 각자 자신만의 개성있는 DIY 작품을 만들어 보면 좋겠습니다.

 

 ■ 카멜레온 반지                                                                                            

   카멜레온(chameleon)을 아시나요? ‘땅 위의 사자’라는 뜻을 가진 그리이스어에서 유래한 카멜레온은 주변의 색에 따라 아주 비슷한 보호색으로 변하는 능력을 가진 재주꾼입니다. 자신의 감정을 표현하기 위하여 자신이 원하는 색깔로 변할 수도 있다고 하니, 세상에는 인간 말고도 오묘한 동물이 정말 많은 것 같네요.

30 카멜레온반지 02

 그런데 “카멜레온 반지”가 뭐냐구요? 아마도 다들 모르실텐데요. 왜냐하면 제가 얼마 전에 작명한 이름이어서요. 카멜레온 반지는 카멜레온처럼 반지의 색깔이 자유자재로 변하는 반지를 말합니다. 가만히 있으면 저절로 색깔이 변하는 반지라고나 할까요?.

 

아래 그림은 12개의 달을 상징하는 보석으로 탄생석입니다. 에머랄드, 다이아몬드, 루비… 참 아름답지요. 무엇보다도 알록달록한 색깔이 압권입니다.

 

30 카멜레온반지 03

 

   그래서 오늘은 이렇게 멋진 보석이 콕 박혀있는 반지를 상상하며 12개의 보석으로 변신이 가능한 카멜레온 반지를 만들어볼까 합니다. 반지의 링은 준비가 되었다고 가정하고 보석 부분만 카멜레온처럼 색깔이 변하도록 만들면 될 것 같네요. 그런데, 어떻게 만드냐구요? 예. 컬러 LED로 만들지요.

 ■ 컬러 LED                                                                                              

   컬러 LED는 빨강, 녹색, 파랑 LED 3개를 하나로 모아 1개의 캡슐로 씌운 LED입니다. 그러니까 3개의 LED를 바짝 붙여서 한 개의 전구 속에 넣은 것이지요. 요렇게요.

30 카멜레온반지 04

다리가 가장 긴 것이 공통 애노드(Anode, +), 또는 공통 캐소드(Cathode, -)로 공통 애노드 타입의 경우는 (+) 핀을 High(VCC, 5V)에 연결하고, R, G, B 핀에는 Low(GND, 0V) 값을 연결하는 경우에만 불이 들어오고, 공통 캐소드 타입의 경우는 반대로 (-) 핀을 Low(GND, 0V)에 연결하고, R, G, B 핀에는 High(VCC, 5V) 값을 연결하는 경우만 불이 들어옵니다. (물론, 지난번 설명처럼 직렬 저항은 연결한다고 가정하였습니다.) 여기서는 아래와 같이 공통 캐소드 타입의 컬러 LED를 사용하여 제작해 보는 것으로 하겠습니다.

30 카멜레온반지 05

 

(53N RGB 262C-9001, 참조 : 디바이스마트)

 

  어 그런데, LED가 3개면 다리가 6개라야 되는 것 아닌가요? 다리가 4개 밖에 없는데…

  예, 원래는 6개지만 (-)에 해당되는 다리는 공통이니까(공통 캐소드), 빨강, 녹색, 파랑, 공통 (-), 이렇게 다리를 4개로 줄일 수 있겠습니다. 지난번 3색 신호등을 만들 때 각 LED의 (-)에 해당되는 것은 모두 GND로 함께 연결한 것을 생각해 보면 쉽게 이해가 갈 겁니다..

  그렇다면, 이 컬러 LED는 빨강, 녹색, 파랑으로 색깔이 고정되어 있는데, 어떻게 카멜레온처럼 여러가지 오묘한 색깔을 나타낼 수 있을까요?

  이 질문에 대한 해답은 바로 빛의 혼합에 있습니다.

  아래 그림은 누구나 초등학교 시절에 한 번은 보았을 것입니다.

30 카멜레온반지 06

 

  왼쪽은 빛의 삼원색(빨강(Red), 녹색(Green), 파랑(Blue), RGB)이고, 오른쪽은 색의 삼원색(자홍(Magenta), 청록(Cyan), 노랑(Yellow))입니다. 우리는 LED를 가지고 색깔을 만들 예정이므로 빛의 3원색의 경우만 보면, 3개의 빛이 합해지는 부분에 다른 색이 나타나는 것을 볼 수 있습니다.

 

   ■ 빨강 + 녹색 = 노랑

   ■ 녹색 + 파랑 = 청록

   ■ 파랑 + 빨강 = 자홍

   ■ 빨강 + 녹색 + 파랑 = 흰색

 

  오. 그렇네요. 이렇게 하니까 4개의 색이 더 만들어졌습니다. 하지만, 탄생석과 같이 알록달록한 여러가지 색은 아직도 만들어지지 않았는데 이것은 어떻게 만들 수 있을까요?

  이것은 광량(빛의 양)에 해답이 있습니다.

  즉, 빨강과 녹색, 파랑을 각각 100%씩 섞으면 흰색이 나오지만, 예를 들어 빨강 100% + 녹색 75% + 파랑 75% 로 빛을 섞으면 핑크(Pink)색이 나오는 원리입니다.

  결국 관건은 우리가 3가지 LED의 광량을 강하게 했다 약하게 했다 조절할 수 있느냐 하는 것인데 결론은 “할 수 있다” 입니다.

  아래 그림을 보시지요.

30 카멜레온반지 07

 

  원래 디지털 값은 High(1, 5V, 100%), Low(0, GND, 0%)의 2가지 값밖에 존재하지 않지만, 위 그림과 같이 어떤 핀의 값을 High ▶ Low ▶ High ▶ Low … 형태로 상태를 변환시키게 되면, 출력 전압의 평균(%)은 빨강색 점선과 같은 값을 갖게 됩니다. 예를 들어 High를 유지하는 시간과 Low를 유지하는 시간의 비율이 3:7이라면 평균값은 High값의 30%가 된다고 말할 수 있는 것이지요. 이렇게 주기적으로 High ▶ Low를 반복하는 신호를 펄스(Pulse)라고 하는데, 이 펄스(Pulse)의 폭(Width)을 조절(Modulation)하여 평균값을 조절하는 방법을 PWM(Pulse Width Modulation)이라고 합니다. 즉, PWM을 이용하면 디지털 출력인 High(1)와 Low(0)을 가지고 0.3(30%), 0.85(85%)와 같은 아날로그 값을 만들 수 있게 되는 것입니다. 핑크색을 컬러 LED로 표현해 본다면 아래와 같이 되겠네요.

    ■ 핑크 = 빨강 PWM 100% + 녹색 PWM 75% + 파랑 PWM 75%

 

 

 ■ PWM 출력을 만드는 방법                                                                   

  이제 PWM 출력을 만드는 방법을 알아봅시다. 아두이노에서는 다음과 같은 analogWirte( ) 라이브러리 함수를 제공하는데 이것을 이용하면 PWM 0% ~ PWM 100% 까지의 펄스를 아주 쉽게 만들 수 있습니다. 단, analogWrite( ) 함수를 사용할 수 있는 핀은 핀 번호 앞에 틸드(~) 표시가 있는 핀으로 한정되어 있다는 점은 주의하셔야 합니다.

 

    ■ analogWrite(pin, value)

    ■ pin : 입출력핀 번호에 해당되는 숫자, 틸드(~) 표시가 되어 있는 핀만 가능

    ■ value : 0~255까지의 값으로 0이면 PWM 0%, 255이면 PWM 100%를 의미함

 

  예를 들어 “analogWrite(5, 128)”으로 프로그램 한다면, 핀 5번의 출력을 PWM 50%(128/256 = 0.5 = 50%)로 출력한다는 의미가 되겠습니다.

  왜 value 값으로 알기 쉽게 0~100을 사용하지 않고 0~255를 사용하게 되었을까요? 사실 그렇게 라이브러리를 만들 수도 있겠지만 디지털 세계는 2진법으로 이루어져 있어, 1, 2, 4, 8, 16, 32, 64, 128, 256 이렇게 만들어지는 수를 더욱 좋아한답니다. 아두이노 내부적으로 본다면 Atmega328, 타이머, ADC(Analog Digital Converter) 등 좀 더 자세하게 알아야 할 것이 많지만 우리는 그냥 이 정도만 알고 넘어가는 것으로 하겠습니다.

  이제 색의 배합을 %로 할 수 있다는 것을 배웠으므로, 간단히 한 번 연습해 보지요.

  노랑과 흰색, 핑크를 R, G, B의 value 값으로 표현해 보세요. 아래와 같이 나오면 정답!.

 

   ▶ 노랑 : R(255), G(255), B(0)

   ▶ 흰색 : R(255), G(255), B(255)

   ▶ 핑크 : R(255), G(192), B(192) // 192/256 = 0.75 = 75%

 

 ■ 컬러 LED 연결                                                                    

 

  자, 그럼 이제 기본 원리는 모두 이해했으니, 프로그램을 하기 전에 컬러 LED를 아두이노와 연결해 보겠습니다. 컬러 LED는 시중에서 구할 수 있는 것 아무거나 구하셔도 됩니다.  R, G, B 및 (-) 핀을 아래와 같이 연결하면 되겠네요.

 

    ■ R (가장 왼쪽) ←→ 330오옴 저항 ←→ 아두이노 6번핀

    ■ (-) (왼쪽에서 2번째, 길이가 가장 긴 다리) ←→ 아두이노 GND

    ■ G (오른쪽에서 2번째) ←→ 330오옴 저항 ←→ 아두이노 5번핀

    ■ B (가장 오른쪽) ←→ 330오옴 저항 ←→ 아두이노 3번핀

 

  우리가 연결한 아두이노 6번, 5번, 3번 핀의 옆쪽에 실크로 쓰여진 숫자에는 틸드(~) 표시가 되어 있는데, 이것은 아두이노 핀 중에서 analog_write( ) 함수로 PWM 신호를 만들어 낼 수 있는 핀이라는 것은 앞에서 설명하였습니다. 우리는 3개 핀에 모두 PWM을 사용하여야 하므로 반드시 틸드(~) 표시가 연결된 핀을 R, G, B에 연결해야 합니다. 아래의 그림처럼 연결이 되겠네요.

 30 카멜레온반지 08

 

 

 ■ 연습 프로그램 작성                                                                   

  이제 연결은 끝났으니 프로그램을 작성하여 실행할 차례입니다.

  최종 목표는 카멜레온 반지이지만 기왕 회로를 꾸몄으니 지난번 배운 것도 복습할 겸 7가지 색(빨강, 녹색, 파랑, 노랑, 청록, 자홍, 흰색)을 먼저 만들어 보도록 하겠습니다.

  아래는 기능 규격입니다.

 

[7가지 색 만들기 기능 규격]

  1. LED가 ON되는 순서는 빨강 ▶ 녹색 ▶ 파랑 ▶ 노랑 ▶ 청록 ▶ 자홍 ▶ 흰색 ▶ 빨강 ▶ … 으로 무한 반복됨

  2. LED는 어떤 한 순간 한가지 색깔만 표시함

  3. 표시된 색깔은 1초 동안 켜진 상태를 유지함

 

  위의 7가지 색 중 노랑, 청록, 자홍은 동시에 2개의 LED를 ON 하면 얻을 수 있고 흰색은 3개의 LED를 동시에 ON하면 얻을 수 있으므로 analogWrite( ) 함수를 사용하지 않고 digitalWrite( ) 함수만 사용하여도 충분히 구현이 가능하겠습니다. 스스로 혼자 구현해 보신 후 10분 후에 함께 해보도록 하지요. (10분간 실시!)

(1분), (2분), ……, (10분)

모두 잘 되셨으리라 생각합니다. 함께 해 보겠습니다.

#define RED_LED 6

#define GREEN_LED 5

#define BLUE_LED 3

void setup()

{

 pinMode(RED_LED, OUTPUT);

 pinMode(GREEN_LED, OUTPUT);

 pinMode(BLUE_LED, OUTPUT);

}

void loop()

{

 digitalWrite(RED_LED, HIGH);     digitalWrite(GREEN_LED, LOW);   digitalWrite(BLUE_LED, LOW); // R=ON, G=OFF, B=OFF –> 빨강(Red)

 delay(1000);

 digitalWrite(RED_LED, LOW);     digitalWrite(GREEN_LED, HIGH);   digitalWrite(BLUE_LED, LOW); // R=OFF, G=ON, B=OFF –> 녹색(Green)

 delay(1000);

 digitalWrite(RED_LED, LOW);     digitalWrite(GREEN_LED, LOW);   digitalWrite(BLUE_LED, HIGH); // R=OFF, G=OFF, B=ON –> 파랑(Blue)

 delay(1000);

 digitalWrite(RED_LED, HIGH);     digitalWrite(GREEN_LED, HIGH);   digitalWrite(BLUE_LED, LOW); // R=ON, G=ON, B=OFF –> 노랑(Yellow)

 delay(1000);

digitalWrite(RED_LED, LOW);     digitalWrite(GREEN_LED, HIGH);   digitalWrite(BLUE_LED, HIGH); // R=OFF, G=ON, B=ON –> 청록(Cyan)

 delay(1000);

digitalWrite(RED_LED, HIGH);     digitalWrite(GREEN_LED, LOW);   digitalWrite(BLUE_LED, HIGH); // R=ON, G=OFF, B=ON –> 다홍(Magenta)

 delay(1000);

digitalWrite(RED_LED, HIGH);   digitalWrite(GREEN_LED, HIGH);    digitalWrite(BLUE_LED, HIGH); // R=ON, G=ON, B=ON –> 흰색(White)

 delay(1000);

}

약간 긴 듯하지만 알고리즘은 지난번과 거의 비슷하고 간단합니다.

자, 컴파일 ▶ 업로드 ▶ 실행해 봅시다. 그리고 결과는 ?

동영상에서는 LED 빛이 너무 밝아서 색깔 구별이 또렷하지 않지만, 실제 눈으로 보면 아주 예쁜 7가지 색깔이 나옵니다. 이 정도면 만족스럽네요.

 ■ 컬러 및 컬러값 선택                                                                   

  연습으로 몸을 풀었으니, 이제 본격적으로 카멜레온 반지를 D.I.Y.하러 가겠습니다. 우리가 할 일은 12개 탄생석의 대표색을 찾아서 이것이 R, G, B의 어떤 값(어떤 세기, %)으로 표현되는지를 알아낸 다음 이것을 analog_write( ) 함수를 이용하여 구현하면 될 것 같습니다.

  자, 이제 탄생석의 색깔에 알맞은 R, G, B의 값을 찾아내면 되겠는데… 2가지 방법이 있습니다.

1. 색상표를 이용하는 방법

  아래와 같이 색상표가 있으므로 이것을 보고 탄생석과 가장 비슷한 색깔을 찾아 그 코드값을 추출하는 방법입니다. 6개의 숫자는 16진법으로 나타낸 코드 값으로 앞의 2개는 R(Red), 중간 2개는 G(Green), 마지막 2개는 B(Blue)에 해당되는 값을 나타냅니다. (혹, 2진법, 10진법, 16진법의 표기법이나 변환이 익숙하지 않은 분은 이 부분에 대하여 따로 각자 공부하신 후 다시 오시기 바랍니다.) 가장 왼쪽 줄의 위에서 6번째 색상이 노랑인데, 이 값을 보면 FFFF00 으로 표기되어 있으니까 [R(FF), G(FF) B(00)] ▶ [R(255), G(255), B(0)]가 되어 노랑색 PWM 표기값과 동일하다는 것을 확인할 수 있습니다.

 

30 카멜레온반지 10

2. 컬러 추출 응용프로그램을 이용하는 방법

  PC 상에서 특정 컬러에 대한 코드값을 추출해 내는 응용프로그램을 실행시켜서 원하는 색상을 클릭하여 코드값을 추출하는 방법입니다.

 

30 카멜레온반지 11

   탄생석은 코드표로 똑 떨어지는 색상이 아니고 우리가 색상을 직접 추출해보는 것도 재미있을 것 같으니까 우리는 두번째 방법을 사용하도록 하지요. 여러가지 프로그램이 있겠지만 여기서는 그냥 간단하게 실행시킬 수 있는 컬러캅(ColorCop)이라는 무료 프로그램을 사용하는 것으로 하겠습니다. (인터넷에서 찾아 다운로드받아 실행하면 됩니다.) 아래와 같은 화면이 나오는데요. 중간 왼쪽에 있는 스포이드처럼 생긴 아이콘을 드래그(클릭한 후 끌기)하여 원하는 컬러 위치에 가져다 놓으면 10진수로 표시된 R, G, B 값이 나타나게 됩니다. 참 쉽네요.

  자, 그러면 지금부터는 탄생석의 대표색에 대한 코드값를 추출해 보는 시간입니다. 색깔도 감상해 가면서 즐거운 마음으로 표를 작성해 보면 더욱 좋겠네요.

  코드 추출은 혼자서도 가능하겠지요? 12분 드립니다.

 

1분, 2분, …, 11분, 12분

 

  제가 직접 찍어서 추출한 값은 아래와 같습니다. (각자 다를 수 있습니다.)

한글 이름 영어 이름 R G B
1 가넷 garnet 254 26 27
2 자수정 amethyst 179 117 180
3 아쿠아마린 aquamarine 211 146 251
4 다이아몬드 diamond 254 155 255
5 에메랄드 emerald 14 219 133
6 진주 pearl 246 241 237
7 루비 ruby 206 3 163
8 페리도트 peridot 167 212 31
9 사파이어 sapphaire 26 27 216
10 오팔 opal 203 200 149
11 토파즈 topaz 254 198 40
12 터키석 turquoise 58 197 189

참, 가넷이라는 보석은 얼마전 종료된 ‘지니어스’라는 TV 프로에 이름이 소개되었던 짙은 자주색계통의 보석이지요. 꽤나 재미있었던 시리즈물이었는데 여러분들도 기회가 되면 꼭 한 번 시청해 보시기 바랍니다.

 

 

 ■ 카멜레온 반지 프로그램 작성                                                                          

 

  모든 준비가 끝났으니 이제 목표 프로그램을 작성하여 실행할 차례입니다. 우리가 원하는 기능 규격을 작성해 보지요.

 

[12가지 탄생석의 대표색을 디스플레이하는 카멜레온 반지 기능 규격]

  1. 컬러 LED로 12개 탄생석의 대표색 12개를 차례대로 ON하며, 무한 반복된다.

  2. 한 가지의 색은 1초 동안 켜진 상태를 유지한다.

 

  지난번에는 digitalWrite( ) 함수를 사용했지만 이번에는 analogWrite( )를 사용하고 작성된 표를 참조하여 R, G, B에 해당하는 PWM value값을 정해주는 것만 조금 다를 것 같습니다.

  같이 한 번 작성해 보시지요.

#define RED_LED 6

#define GREEN_LED 5

#define BLUE_LED 3

void setup()

{

 pinMode(RED_LED, OUTPUT);

 pinMode(GREEN_LED, OUTPUT);

 pinMode(BLUE_LED, OUTPUT);

}

void loop()

{

 analogWrite(RED_LED, 254);  analogWrite(GREEN_LED, 26);  analogWrite(BLUE_LED, 27);  // 가넷

delay(1000);

 analogWrite(RED_LED, 179);  analogWrite(GREEN_LED, 117);  analogWrite(BLUE_LED, 180);  // 자수정

delay(1000);

 analogWrite(RED_LED, 211);  analogWrite(GREEN_LED, 246);  analogWrite(BLUE_LED, 251);  // 아쿠아마린

delay(1000);

 analogWrite(RED_LED, 254);  analogWrite(GREEN_LED, 255);  analogWrite(BLUE_LED, 255);  // 다이아몬드

delay(1000);

 analogWrite(RED_LED, 14);  analogWrite(GREEN_LED, 219);  analogWrite(BLUE_LED, 133);  // 에메랄드

delay(1000);

 analogWrite(RED_LED, 246);  analogWrite(GREEN_LED, 241);  analogWrite(BLUE_LED, 237);  // 진주

delay(1000);

 analogWrite(RED_LED, 206);  analogWrite(GREEN_LED, 3);  analogWrite(BLUE_LED, 163);  // 루비

delay(1000);

 analogWrite(RED_LED, 167);  analogWrite(GREEN_LED, 212);  analogWrite(BLUE_LED, 31);  // 페리도트

delay(1000);

 analogWrite(RED_LED, 26);  analogWrite(GREEN_LED, 27);  analogWrite(BLUE_LED, 216);  // 사파이어

delay(1000);

 analogWrite(RED_LED, 203);  analogWrite(GREEN_LED, 200);  analogWrite(BLUE_LED, 149);  // 오팔

delay(1000);

 analogWrite(RED_LED, 254);  analogWrite(GREEN_LED, 198);  analogWrite(BLUE_LED, 40);  // 토파즈

delay(1000);

 analogWrite(RED_LED, 58);  analogWrite(GREEN_LED, 197);  analogWrite(BLUE_LED, 189);  // 터키석

delay(1000);

}

  음. 조금 많이 길긴 하지만…

  일단, 컴파일 ▶ 업로드 ▶ 실행해 봅시다. 결과는?

  카메라에 찍힌 것은 색깔 구별이 또렷하지는 않은데, 실제로 보면 아름다운 색깔이 구분되어 나타납니다. 보석에 비할 수는 없지만 그래도 아주 색상이 화려하고 아름답습니다.

  그런데 프로그램 짜면서 조금 마음에 걸리는 것이 있습니다.

  무엇이냐구요? 비슷한 내용이 12번이나 반복되고 코드 값을 일일이 입력해 주는 것이 조금 불편한 듯 하네요. 그래서, 12번 반복하는 것은 for 문을 이용하여 수정하고, 코드 값은 미리 어레이에 지정해 놓았다가 사용하도록 하여 위 코드를 조금 단순화시켜 보겠습니다. 아래와 같이 될 것 같습니다.

#define RED_LED 6

#define GREEN_LED 5

#define BLUE_LED 3

void setup()

{

 pinMode(RED_LED, OUTPUT);

 pinMode(GREEN_LED, OUTPUT);

 pinMode(BLUE_LED, OUTPUT);

}

void loop()

{

 analogWrite(RED_LED, 254);  analogWrite(GREEN_LED, 26);  analogWrite(BLUE_LED, 27);  // 가넷

delay(1000);

 analogWrite(RED_LED, 179);  analogWrite(GREEN_LED, 117);  analogWrite(BLUE_LED, 180);  // 자수정

delay(1000);

 analogWrite(RED_LED, 211);  analogWrite(GREEN_LED, 246);  analogWrite(BLUE_LED, 251);  // 아쿠아마린

delay(1000);

 analogWrite(RED_LED, 254);  analogWrite(GREEN_LED, 255);  analogWrite(BLUE_LED, 255);  // 다이아몬드

delay(1000);

 analogWrite(RED_LED, 14);  analogWrite(GREEN_LED, 219);  analogWrite(BLUE_LED, 133);  // 에메랄드

delay(1000);

 analogWrite(RED_LED, 246);  analogWrite(GREEN_LED, 241);  analogWrite(BLUE_LED, 237);  // 진주

delay(1000);

 analogWrite(RED_LED, 206);  analogWrite(GREEN_LED, 3);  analogWrite(BLUE_LED, 163);  // 루비

delay(1000);

 analogWrite(RED_LED, 167);  analogWrite(GREEN_LED, 212);  analogWrite(BLUE_LED, 31);  // 페리도트

delay(1000);

 analogWrite(RED_LED, 26);  analogWrite(GREEN_LED, 27);  analogWrite(BLUE_LED, 216);  // 사파이어

delay(1000);

 analogWrite(RED_LED, 203);  analogWrite(GREEN_LED, 200);  analogWrite(BLUE_LED, 149);  // 오팔

delay(1000);

 analogWrite(RED_LED, 254);  analogWrite(GREEN_LED, 198);  analogWrite(BLUE_LED, 40);  // 토파즈

delay(1000);

 analogWrite(RED_LED, 58);  analogWrite(GREEN_LED, 197);  analogWrite(BLUE_LED, 189);  // 터키석

delay(1000);

}

  예. 요렇게 작성해서 다시 실행시켜보면… 처음 프로그램하고 똑같이 실행되는 것을 알 수 있습니다. 이것도 당근 성공이겠죠?

  그럼, 마지막으로… 이 반지를 진짜 반지처럼 한 번 연출해 볼까요?

  손가락에 켜보는 형태로 흉내를 내보지요.

  컬러 LED를 뽑아서 4개의 다리에 선을 연결하고 이선을 원래 LED가 위치했던 브레드보드의 핀 위치에 꼽으면 원래 회로와 똑같은 회로입니다. 이것을 반지 모양으로 손가락 앞쪽으로 LED만 보이게 만들고 아두이노에 전원을 넣으면?

30 카멜레온반지 14

30 카멜레온반지 15

 

  야호~~~ 카멜레온 반지가 완성되었습니다.

  소품을 이용하여 잘 만들면 어느 정도 쓸만한 것도 만들 수 있을 것 같은데, 이것은 여러분들이 D.I.Y. 해보시기 바랍니다. 잘 만들어졌으면 주변에 자랑도 한 번 해 보시구요.

  시간나실 때 아래 과제도 한 번 해보시면 더욱 좋겠죠?

 [과제-카멜레온반지-1]

 R, G, B 색이 임의로 변하는 카멜레온 링을 만들어 보세요.

 [과제-카멜레온반지-1]

 R, G, B 각각이 0~255까지 짧은 시간 내에 계속 변화하면서 모든 색상을 디스플레이할 수 있는 카  멜레온링을 만들어 보세요. 총 256 x 256 x 256 = 16,777,226 가지의 색깔을 만들수 있을까요?

 

 

 

 ■ 스위치 연결                                                                                

  바로 전 강의까지 우리는 아두이노로 할 수 있는 2가지 기초 기능을 다루어 보았습니다. 디지털 출력(digitalWrite())과 아날로그 출력(analogWrite())이지요. 여기서의 아날로그 출력은 엄밀히 말하면 디지털 출력을 PWM을 이용하여 만든 유사 아날로그 출력이지만요.

 

  기본적인 출력을 2가지 해보았으니 이번에는 기본적인 입력을 이용하여 ‘빨노파 게임기’ D.I.Y.에 도전해 보겠습니다. 빨노파게임기란 빨강 ▶ 노랑 ▶ 파랑 순으로 LED 색깔이 상당히 빠르게(0.1초 이내) 변하다가 스위치를 누르는 순간 빨강, 노랑, 파랑 중의 1가지로 약 5초 정도 고정되는 LED 디스플레이어입니다. 아주 빠르게 색깔이 변하므로 사람이 임의로 색깔의 변화를 읽어서 스위치를 누른다는 것은 불가능하다고 볼 수 있으므로 임의의 색깔이 나타나는 상황이 됩니다. 그러므로 가위바위보 게임과 비슷하게 빨강은 파랑을 이기고, 노랑은 빨강을 이기고, 파랑은 노랑을 이긴다고 룰을 정한 후 두 사람이 스위치를 순서대로 눌러서 나온 색깔로 승부를 가린다면 간단한 게임기가 될 수 있겠습니다.

  아 참, 이건 여담인데, 빨노파… 하니까 생각나는게 하나 있네요. ‘개그콘서트’라는 TV 프로의 ‘나는 킬러다’ 코너에 등장하는 빨노파 3형제…  ‘일소일소(一笑一少)’라는 말이 있는데 한 번 웃으면 한 번 젊어진다고 하니, 여러분도 틈나는 대로 개그 프로 열심히 보면서 많이 웃으시기 바랍니다.

  LED가 대표적인 기본 디지털 출력 부품이라면, 스위치는 대표적인 기본 디지털 입력 부품입니다. 스위치를 모르는 사람은 천지(天地)에 없겠지만, 기왕 말이 나왔으니까 많이 쓰는 스위치 2-3가지만 조금 살펴보고 가는 것도 나쁘지는 않을 듯 합니다.

  우리가 제일 흔하게 많이 보고 많이 쓰는 스위치는 무엇일까요?

  요렇게 생긴 것이겠죠? 전자기기 전원 껐다 켰다 하는 스위치, 형광등 껐다 켰다 스위치.

30 카멜레온반지 16

(KCD1-101A RED, 상품코드: 1790)

  실생활에서 가장 많이 쓰이는 스위치로 이름은 로커(Locker) 스위치입니다. 잠긴(lock) 것처럼 ON이거나 OFF이거나 한가지 상태로 존재하지요.

  또, 어떤 스위치가 있을까요? 아래와 같이 생긴 슬라이드(Slide) 스위치도 많이 사용됩니다. 왼쪽이나 오른쪽으로 옮기면 각각 가운데 신호가 왼쪽이나 오른쪽에 연결된 신호와 연결되는 구조를 가진 스위치입니다. 요건 2단으로 되어 있어 2개의 서로 다른 신호가 동시에 왼쪽 또는 오른쪽으로 연결되는 형태네요.

30 카멜레온반지 17

 

(MSL-1C2P(듀얼)-2mm, 상품코드: 30530)

  이번 강의에서 사용할 스위치는 아래와 같이 생긴 택트(Tact) 스위치입니다. 이것도 굉장히 많이 사용됩니다. 아마도 contact에서 이름이 유래(?)된 듯한데, 손가락으로 누르면(contact 되면) ON, 떼면 OFF 상태가 되는 스위치이지요.

  아래 것은 다리가 4개가 있어서 2쌍의 신호가 한꺼번에 연결되거나(ON), 끊어지거나(OFF) 하는 스위치가 되겠습니다.

30 카멜레온반지 18

(ITS-1105-5mm, 상품코드: 34555)

  자, 스위치가 결정되었으니 그럼 이제 스위치를 연결해 볼까요?

  스위치 심볼은 보통 아래와 같은 2가지 형태를 취합니다. 스위치가 눌려지면 왼쪽과 오른쪽에 연결된 전선이 연결되는 것이지요. 스위치를 놓으면 양쪽의 연결은 끊어지는 것이구요.

30 카멜레온반지 19

30 카멜레온반지 20

  스위치가 디지털 입력이라고 하였고, 스위치는 양쪽 끝이 있으니까 한 쪽은 당연히 아두이노의 디지털 신호선 중 하나에 연결하여야 할 것이고, 다른 한쪽은 어디에 연결해야 할까요? 스위치를 눌렀을 때 ‘1’(HIGH)이나 ‘0’(LOW)이 입력되어야 하므로, ‘1’이 연결되려면 VCC(+5V)가 연결되어야 하고, ‘0’이 입력되려면 GND(0V, Ground)가 연결되어야 할 것입니다.

  그러면 이렇게 연결이 되겠네요.

 30 카멜레온반지 21

 

30 카멜레온반지 22

 

  그런데 가만히 보니까, 스위치를 눌렀을 때는 연결된 상태에 따라서 +5V 또는 0V(GND)가 연결되지만 스위치가 눌리지 않았을 때는 선이 끊어진 상태(floating 상태)가 되는데… 어라, 이렇게 되면 아두이노는 이 핀의 값을 HIGH(1)로 판단할까요? 아니면, LOW(0)로 판단할까요? 조금 어려운 문제인데 이것은 아두이노의 내부 로직과 상태에 따라 값이 결정될 수 있으므로 정확하게 HIGH 또는 LOW라고 단정지어 말할 수 없습니다. 즉, 상황에 따라 HIGH가 될 수도 있고 LOW로 될 수도 있다는 뜻인데, 이렇게 되면 값이 확정되지 않으므로 프로그램을 작성하는 사람은 대략난감(大略難堪, 이러지도 저러지도 못하는 당황스러운 상황)하게 됩니다. 그래서, 이렇게 선이 끊어진 상태가 될 때는 아두이노가 HIGH 또는 LOW 중, 하나의 값으로만 결정되도록 만들어 주는 조치가 필요합니다. 즉, 스위치를 눌렀을 때 HIGH가 되는 회로는 눌러지지 않았을 때 LOW가 되도록 조치해 놓아야 하고, 반대로 스위치를 눌렸을 때 LOW가 되는 회로는 눌러지지 않았을 때는 HIGH가 되도록 조치해 놓아야 하겠습니다. 아래와 같이 될 것 같습니다.

30 카멜레온반지 23

30 카멜레온반지 24

  하지만 위 그림도 아직은 문제가 있어 보입니다. 왜냐하면 스위치가 눌려졌다고 생각하면 위 2개 회로 모두가 +5V와 GND가 직접 연결되는 형상이 되어 버리니까요.

  뿌직~~~ 번쩍! 푸쉬푸쉬~~~

  무엇인가 터지던지… 불꽃이 튀던지… 연기가 나던지… 뭔가 문제가 생길 것 같습니다. 그래서 이런 경우를 방지하기 위한 대비책이 필요한데 이 역할을 수행하는 것은 저항입니다. 아래와 같이 스위치가 눌렸을 때와 눌려지지 않았을 때의 값이 서로 다르게 입력되는 위치에 저항을 달면 문제가 해결되겠습니다. (저항값은 보통 1K~10K 정도를 사용합니다.) 참고로 GND에 저항이 연결되는 것을 풀다운(pull down) 저항이라고 하고, VCC(+5V)에 연결되는 것을 풀업(pull up) 저항이라고 합니다.

30 카멜레온반지 25

 

30 카멜레온반지 26

 

 

  하나 하나 설명하다보니 조금 장황해졌는데요. 어쨌든 이제 연결 방법은 알았으니, 실제로 회로를 아두이노에 연결해 봅시다. 조금 전에 만들었던 카멜레온반지 회로에 스위치 한 개를 추가하여 2번핀(D2)에 연결하는 것으로 하겠습니다. 아래와 같이 되겠네요.

 30 카멜레온반지 27

  오, 그럴듯하게 잘 꾸며진 것 같습니다. 만족? 만족!

 

 

 ■ 신호등 게임기 프로그램 작성                                                  

 

  이제 프로그램을 작성하여야 하는데요. 언제나 마찬가지로 일단 원하는 기능 규격을 작성해 봅시다.

 

[신호등 게임기 기능 규격]

  1. 컬러 LED,의 색깔은 0.1초 마다 빨강 ▶ 노랑 ▶ 파랑 ▶ 빨강 ▶ 노랑 ▶ 파랑… 의 순으로 계속 바뀐다..

  2. 스위치를 누르는 순간 5초 동안만 현재의 LED 색이 유지되었다가 다시 ‘1’번을 수행한다.

 

  컬러 LED의 색깔이 바뀌는 것은 지난번에 했으니까 별 문제가 없을 것이고, 스위치 값을 읽어서 그 값이 0(LOW, 스위치 눌림)인지 1(HIGH, 스위치 눌리지 않음)인지를 검사할 수만 있으면 쉽게 해결될 것 같습니다. 이런 경우에 대비해서 아두이노에서는 digitalRead(pin)라는 기능의 함수를 제공하므로 이것을 이용하도록 합니다.

 

digitalRead(pin)

  ■  pin : 핀 번호

  ■  return 값 : pin을 통하여 들어온 디지털 값으로 0 또는 1

 

  이제 기능 규격을 만족할 수 있는 프로그램의 알고리즘을 만들어 보지요. 아래를 보기 전에 각자 먼저 5분 정도 생각해 보시구요.

… (1분) … (2분) … (3분) … (4분) … (5분)

  아래와 같은 모습이 될 것 같습니다.

30 카멜레온반지 28

  이제 프로그램을 함께 해볼까요?

int SW=2; // #define을 이용해도 되지만 이와 같이 변수로 선언하는 것도 방법

int BLUE_LED=3

int GREEN_LED=5;

int RED_LED=6;

void setup()

{

 pinMode(SW, INPUT); // 2번핀은 스위치 입력

 pinMode(BLUE_LED, OUTPUT); // 3번핀은 파랑 LED 출력

 pinMode(GREEN_LED, OUTPUT); // 5번핀은 녹색 LED 출력

 pinMode(RED_LED, OUTPUT); // 6번핀은 빨강 LED 출력

}

void loop()

{

analogWrite(RED_LED, 255);  analogWrite(GREEN_LED, 0);  analogWrite(BLUE_LED, 0); // 빨강

if (digitalRead() == 0)

   delay(5000); // 스위치를 눌렀으면 5초 대기

else

delay(100); // 스위치를 누르지 않았으면 0.1초만 대기

analogWrite(RED_LED, 255);  analogWrite(GREEN_LED, 255);  analogWrite(BLUE_LED, 0);   // 노랑

if (digitalRead() == 0)

   delay(5000);

else

delay(100);

analogWrite(RED_LED, 0);  analogWrite(GREEN_LED, 0);  analogWrite(BLUE_LED, 255); // 파랑

if (digitalRead() == 0)

   delay(5000);

else

delay(100);

}

  digitalRead()를 수행하여 값이 ‘0’인 상태가 나타나면 스위치가 눌려진 것이니까 그 상태에서 delay(5000); (5초 동안 아무것도 하지 않음)을 수행하게 되므로 LED가 5초 동안 한가지 색깔을 유지하게 됩니다. 자신의 색깔이 결정되는 것이지요.

  작성이 다 되었으면… 컴파일 ▶ 업로드 ▶ 실행! 잘 나오나요?

 30 카멜레온반지 29

  옆에 있는 가족/친구/상사/동료와 간단히 게임 한 번 해 보시지요! 나는 빨강, 상대는 파랑, 야호~ 내가 이겼다. 밥 먹으러 갑시다.

  오늘은 여기까지입니다. 다음 시간에는 FND(Flexible Numeric Display)를 가지고 007 영화에 항상 등장하는 카운트다운 계수기 D.I.Y.에 도전해 보겠습니다. 아래 과제는 짬을 내서 해보시고 다음 강의에서 예쁜 얼굴로 또 만나겠습니다. 감사합니다.

 [과제-빨노파게임기-1]

 스위치를 누르면 ‘1’이 되도록 연결하고 프로그램해 보세요.

 [과제-빨노파게임기-2]

 스위치를 2개 사용하여 더 재미있는 DIY 작품을 만들어 보세요.

 

 

 

아두이노 구매하러 가기 : 클릭!

디바이스마트 온라인 매거진 전자책(PDF)이 무료!

캡처

안녕하세요 ★

디바이스마트의 대표 콘텐츠

업계유일 전자부품 업계의 다양한 소식과

정보를 전달해주는 디바이스마트 매거진의

새로운 소식을 가져왔습니다~~!

디바이스마트 매거진은

2010년 4월 26일 창간되어

올해로 벌써 10주년을 맞이하였는데요.

 

기존 만원이상 구매자에게 무료로 제공되었으나

10주년 기념, 구독자분들의 많은 관심에 힘입어

올해 2월부터는 무료로 정기구독 서비스를 함께 제공하고 있습니다.

정기구독 신청 바로가기

하 / 지 / 만

정기구독만으로는

더 많은 분들에게 좋은 정보를

제공하는데 한계가 있었기에

온라인 서비스를 신설하게 되었습니다! 

 

이제 언제 어디서든

디바이스마트 매거진을 손쉽게 만나실 수 있습니다~~

짝짝짝!!!!

  

★전자책방 사용방법 ★

 

원하는 매거진을 클릭! PDF 다운로드를 누르면 웹상에서

매거진을 볼 수 있습니다.

 

전자책방은 회원전용 서비스로

로그인을 해야지만 PDF를 다운로드 받으실 수 있습니다.

아쉽게도 이전 자료는 소실되어

PDF 매거진은 30호 부터 제공이 가능하며,

 

매거진 내용중 특정 콘텐츠는

저작권 관계상 PDF에서 확인이 불가능한 부분이 있습니다.

 

이부분은 인쇄본 매거진에서

확인하실 수 있습니다!ㅠㅠ!

잡지 증정 이벤트로 배포되는

최신 매거진은 이벤트가

끝나는날에 맞춰서 디바책방에 업로드되오니

 

모든 콘텐츠를 다 보시고 싶으신분은

*만원이상 제품 선택 > 주문/결제 창에서

매거진 사은품을 선택하시기바랍니다~!

 

 

 

항상 디바이스마트는

구독자분들의 의견에 귀기울이며

편리하고, 만족도 높은

매거진 발행에 최선을 다하고있사오니

많은 사랑 부탁드립니다♥

 

앗!! 그리고 디바 전자책방은

여기서 끝이아닌!!!

 

앞으로 아두이노 프로젝트,DIY

교육자료, 다양한 읽을거리가 계속

추가될 예정이오니 많은 이용부탁드립니다.

[61호]시각장애인을 위한 음성 안내 버스표지판

61 음성인식 썸네일

Cap 2020-10-23 16-30-02-020

2020 ICT 융합 프로젝트 공모전 최우수상

시각장애인을 위한 음성 안내 버스표지판

글 | 인하대학교 박민아, 이상우, 이승화

1. 심사평
칩센 데모에서도 인천시 Bus API를 사용하였듯이 기존 시스템과 연계를 하기 위해서는 보완해야 하는 사항이 많아 보이지만, 이러한 부분을 제외한 장치 자체로는 명확한 목적성을 가진 작품으로 생각됩니다. 기획한 의도에 따른 동작을 보여주긴 하였으나, 비콘등의 무선 장치가 절대적인 데이터를 가지거나, 정확한 거리 측정 등을 할 수가 없으므로 이를 보완할 수 있는 방안을 장치에 어떻게 도입할지에 대한 고민이 필요해 보입니다.
펌테크 세심한 관찰력이 반영된 실생활과 밀접한 아이디어와 실용성이 우수한 작품이라고 생각합니다. 기획 의도에 맞게 전체 시스템을 안정적이고 완성도 높게 구현하였고 제출된 보고서 구성 내용도 명확하고, 충실했다고 생각이 듭니다. 전체적으로 기획 의도, 기술 구현도, 완성도 등에서 상당히 뛰어나고 상업적으로 손색이 없는 우수한 작품으로 생각됩니다.
위드로봇 실제 필요한 기능을 잘 구현한 작품입니다. 좀 더 다듬으면 실제 상품화도 가능할 것으로 보입니다.

2. 작품 개요
2.1. 배경 및 목적
21세기에 들어 장애인과 같은 교통약자들의 사회참여 욕구 및 기회가 증가하면서 이들을 위한 이동수단의 확보와 교통정보제공 문제가 사회적 이슈로 대두되고 있다.[1] 그러나 장애인을 위한 대중교통 서비스는 많은 불편함을 호소하며 해결되지 않은 문제로 남아있다.

Cap 2020-10-23 16-35-30-903

<표 1>에 따르면 시각장애인은 통행 중에 다른 통행자들에게 도움을 받는 비중이 가장 큰 것으로 나타났다. 시각장애인은 스스로 버스정류장의 정보를 알아내기에 많은 제약이 있는 것을 확인할 수 있었다. 또한 <표 2>에 따르면 대중교통의 안전도 또한 불만족의 경우가 많았다. 지하철의 경우와 비교할 때 만족도의 차이가 매우 컸으며 버스정류장 및 버스 시설의 개선이 이루어져야 할 것으로 확인된다.

Cap 2020-10-23 16-35-39-871
MBC 뉴스에서도 실제 버스를 이용하는데 어려움을 겪는 사례를 소개하였다. 시각장애인은 버스 정류시설을 이용하며 도착 버스를 확인하지 못해 혼동을 느끼고 있었으며 이렇게 버스 탑승에 어려움을 겪기 때문에 보통 30~40분의 시간을 허비한다고 대답하였다.
이를 통해 일반 버스정류시설은 시각장애인에게 많은 불편함을 야기시키고 있으며 이를 해결할 시스템이 필요하다고 생각되었다.

Cap 2020-10-23 16-35-45-887

2.2. 문제인식
한 시각장애인 단체에서 청와대 국민청원에 시각장애인의 버스 탑승 문제에 있어 개선이 필요하다는 청원을 발의하였다.[3] 이 청원에 따르면 시각장애인 버스 탑승에 있어 문제점은 크게 두가지이다.

(1) 버스정류장 정보를 알 수 없다.
시각장애인이 버스정류장에 도착했을 때 해당 정류장이 어느 정류장인지 확인하기 어렵고, 이 때문에 도착하는 버스 정보를 쉽게 파악하기 어렵다.
(2) 버스번호를 파악할 수 없다.
시각장애인이 버스 도착 정보를 알 수가 없어 버스 탑승에 어려움을 겪는다. 정류장에서 ‘–번 버스가 곧 도착합니다.’라는 안내음이 송출된다고 할지라도 버스가 안내된 순서대로 진입하지 않거나, 많은 버스가 한꺼번에 오는 경우 때문에 특정 버스에 탑승하기 어렵다.

2.3. 해결방안
앞서 제시된 문제를 해결하기 위해 음성인식 기반 버스표지판을 제시하였다. 현재 대부분의 버스정류장에는 이미 음성안내 시스템이 존재하지만 기존 시스템의 문제점은 사용자가 알 수 있는 정보가 오직 잠시 후 도착하는 버스 안내밖에 없다는 것이다. 정류장에 정차하는 버스 정보나 버스가 몇분 후 도착하는지는 시각적인 정보로만 제공되기 때문에 시각장애인이 이용하는 데에는 어려움이 존재한다.
따라서 기존 버스정류장 안내 시스템의 한계를 극복하고 시각장애인이 버스탑승 시 느끼는 불편함을 해결하기 위해 음성인식을 기반으로 시각장애인에게 원하는 정보를 제공하는 시스템을 택하였다. 이 시스템에 대한 간략한 도식은 다음과 같다.

Cap 2020-10-23 16-35-55-137

음성안내 버스표지판이 동작하는 방식은 먼저 시각장애인이 비콘을 소지한 상태에서 버스 정류장에 다가가면 버스 음성 안내가 시작된다. 이후 탑승하려는 버스 번호를 마이크에 말하면 이를 인식해 해당 정류장에 정차하는지 여부를 알려주고 남은 시간이 몇분인지 알려준다. 이 탑승버스 번호는 LED에 점등되어 버스 운전기사가 인식하여 시각장애인이 대중교통 서비스를 타는것을 돕도록 한다. 이 정보는 버스기사 뿐만 아니라 주위 사람들도 의식적으로 시각적인 확인이 가능하기 때문에 현재처럼 버스가 오더라도 탑승하지 못하는 문제를 해결할 수 있을 것으로 보인다.

3. 작품 설명
3.1. 주요 동작 및 특징

Cap 2020-10-23 16-36-01-994

그림3. <전체 시스템 동작 순서도>

(1) 사용자 접근 : 하드웨어 사용 전력을 최소화하고 시스템의 남용을 방지하기 위하여 사용자(비콘) 접근시에만 하드웨어가 켜지도록 한다. 사용자가 3m이내에 있는 경우에는 하드웨어에서 “–정류장 입니다.” 소리가 발생하여 정류장 위치를 알리도록 한다.
(2) 탑승 버스 인식 : 표지판에 접근하여 탑승하려는 버스 번호를 마이크에 말하면 버스번호와 도착 예상 시간을 안내한다. 또한 이렇게 인식된 버스 번호는 LED에 표시된다.
(3) 버스 안내 : 버스가 정류장에 도착 예정시 잠시후 도착한다는 안내 메세지를 출력한다. 버스기사는 LED를 통해 탑승하려는 사용자가 있는것을 인지하여 사용자의 탑승을 돕는다.

3.2. 전체 시스템 구성

Cap 2020-10-23 16-37-42-886

라즈베리파이는 서버로부터 버스정보를 받아오고 음성 텍스트 변환 및 텍스트 음성 변환을 제어한다. 텍스트->음성 변환 파일은 라즈베리파이 상에 저장되므로 스피커는 라즈베리파이에서 출력되도록 한다.
비콘은 단뱡항 통신으로 페어링이 필요하지 않고 비교적 넓은 범위에서부터 인식가능 하고, 비용적인 측면을 고려해서 통신모듈로 사용하였다. 단, 비콘과 LCD에 대해서는 효율적인 구성 코드 효율적인 사용을 위해 아두이노를 이용하여 제어하였다.
버스 정보 데이터셋은 공공데이터포털(www.data.go.kr)에서 제공하는 인천광역시 버스 정보를 사용하였다. 내부 알고리즘을 간소화하기 위해 데이터를 DB화 하지 않고 필요시에 요청하는 형식을 선택하였다.

4. 단계별 제작 과정
4.1. 간트 차트

Cap 2020-10-23 16-37-48-304

4.2. 제작 과정
4.2.1. 하드웨어
아래 첨부한 사진 순서대로 스피커, 마이크 그리고 라즈베리파이를 표지판 내부에 넣고 외부에는 시각장애인 마크 및 LED만 확인할 수 있도록 만들었다.

Cap 2020-10-23 16-37-57-070 Cap 2020-10-23 16-38-05-537

4.2.2. 소프트웨어 개발환경
Raspberry Pi B
- 개발 언어: Python
- Tool: terminal
- 사용 시스템
1) Google Cloud Platform (Sound To Text & Text To Sound)
2) 인천 Bus API

5. 기대효과
사회적 측면
대중교통 또는 여객시설은 누구나 이용할 수 있는 공공재이다. 하지만 현재 국내의 대중교통 시설과 건물들은 비장애인들에게 초점이 맞춰져 있기 때문에 장애인들은 교통 수단 이용에 많은 제약을 받고 있다. 이동권의 제한은 개인적, 사회적 욕구를 원천적으로 제거시키는 것으로까지 이어진다. 따라서 이동권 관련 문제는 단순히 물리적 제약과 관련 된 것이 아니라 인간의 기본적인 권리와도 연관된 사회적인 문제로 볼 수 있다.[4] 따라서 시각장애인을 위한 음성 안내 버스표지판을 통해 시각장애인의 기본적 권리를 보장하며 사회참여 기회를 증진시킬 수 있을 것이다.

경제적 측면
하나의 표지판 당 기기의 개발비용은 약 10만원 전후로 생산이 가능할 것으로 예상된다. 비용 사용 목록은 다음과 같다. 라즈베리파이3 (Model B+ 기준 40,000원) + LED 표시등 + 비콘(HM-10 Module 기준 8000원)로 측정할 수 있다. 이때 공공재 안내 표지판 및 표지판을 설치하는 비용은 지방 정부나 정부의 보조를 생각하여 제외하고 예상 비용을 측정했다. 동시에 많은 기기를 만들어 내면 더욱 저렴한 금액으로 시각장애인에게 쉽게 도움이 될 수 있다.
인천시 버스 정류장의 개수는 5908개(출처: https://www.data.go.kr/, 인천광역시 버스노선별 정류장 현황, 통계 기준일 2020년 1월)이 기기 한개 생산 단가를 약 10만원으로 가정할 때 5억 908만원으로 한 해의 버스재정지원액의 0.0023%를 넘지 않을 것으로 예측할 수 있었다. 이를 확인하더라도 충분한 발전가능성과 효율을 가지고 있다고 할 수 있다. (출처:https://www.incheon.go.kr/open/OPEN030101)

6. 시연영상

Cap 2020-10-23 16-38-21-713

7. 개선방안
시각장애인을 위한 음성안내 표지판에는 몇 가지 개선사항이 존재한다.
1) 라즈베리파이3를 이용하여 표지판을 만들기 때문에 전력의 사용이 원할해야 한다. 마이크로 5핀을 통해 전력을 충분히 끌어 낼 수 있어야 하기 때문에 기존 버스정류장 내의 전력을 이용할 수 있다면 상용화하는데 더 수월할 것이라고 생각한다.
2) LED 제작에 있어서 비용적인 측면에 제한이 있었다. 하지만 비용적인 제한이 없다면 즉, 상용화시킨 표지판에서는 표시등을 더욱 크게하여 시각적으로 잘 보이게 할 수 있을 것이다. 이 방법을 통해서 더욱 완성도가 높은 제품을 만들 수 있을 것으로 기대된다.
3) iBeacon의 인식거리, 음성인식의 정확도, 버스api의 정확도 등 제품의 전체적인 정확도를 향상시킨다면 보다 정확한 결과를 기대할 수 있다.

8. 소스코드
8.1. 메인 코드

import gspeech
import threading
import time
import re
import sys

import requests
import argparse
from luma.led_matrix.device import max7219
from luma.core.interface.serial import spi, noop
from luma.core.render import canvas
from luma.core.legacy import text
from luma.core.legacy.font import proportional, LCD_FONT

import urllib.request
from urllib.request import urlopen
from urllib.parse import urlencode
from urllib.parse import quote_plus
from urllib.parse import unquote
from bs4 import BeautifulSoup
import bs4

from Hi import sound_play
from blesca import find

from google.cloud import texttospeech

#전역변수 선언
busrouteID = [] buslistinform = [] buslist = [] bstopnm = [] target_buses = [] led_tag=0

def demo(w, h, block_orientation, rotate, bus1, bus2):
serial = spi(port=0, device=0, gpio=noop())
print(“Created device”)
device = max7219(serial, width=w, height=h, rotate=rotate, block_orientation=block_orientation)

with canvas(device) as draw:
text(draw, (1, 0), bus1, fill=”white”, font=proportional(LCD_FONT))
text(draw, (1, 8), bus2, fill=”white”, font=proportional(LCD_FONT))

def led_control() :
global led_tag
led_tag = 1

# 정류소 경유노선 목록조회
# 공공데이터포털(data.go.kr)으로 url요청을 해 데이터를 받는다
# 받아오는 정보는 정류장 이름, 정류장에 정차하는 버스의 노선 명칭, 노선 고유 번호
# 서버에 데이터를 받아오지 않고 받아온 html에서 크롤링 하는 방식
# 리턴값은 정차 버스 리스트, 정류장 이름, (2차원 리스트)버스 노선 명칭과 고유번호 이다
# 버스 정류장 고유 정보이므로 최초 한번만 받아옴

def get_busstopinfor(buslistinform, bstopId):
decode_key = unquote(”) #개인 고유 발급 키
url = ‘http://apis.data.go.kr/6280000/busStationService/getBusStationViaRouteList’
queryParams = ‘?’ + urlencode({quote_plus(‘ServiceKey’): decode_key,
quote_plus(‘pageNo’): ’1′,
quote_plus(‘numOfRows’): ’10′,
quote_plus(‘bstopId’): bstopId})
request = urllib.request.Request(url + queryParams)
request.get_method = lambda: ‘GET’
response_body = urlopen(request)
a = response_body.read()
soup = BeautifulSoup(a, ‘html.parser’)
num = soup.find(‘numofrows’)
num = int(num.get_text())

table = soup.find(‘serviceresult’)
for tr in table.find_all(‘msgbody’):
tds = list(tr.find_all(‘itemlist’))

blank = 0
for itemlist in tds:
routeno = itemlist.find(‘routeno’).text
routeid = itemlist.find(‘routeid’).text
getbstopnm = itemlist.find(‘bstopnm’).text
buslistinform.append([routeno, routeid, blank])
buslist.append(routeno)
bstopnm.append(getbstopnm)

return buslist, bstopnm, buslistinform

# 버스 도착정보 목록조회
# 받아오는 정보는 버스 노선 고유번호, 버스 도착시간
# 이 데이터셋에서는 버스 고유번호를 제공하지 않는다. 따라서 버스 노선 고유번호를 받아와 앞서 정류소 경유노선 목록조회에서 받아온
# 2차원 리스트 버스 고유번호 / 버스 노선 고유번호에서 버스 노선 고유번호를 비교해 같은 리스트에 도착시간을 추가한다
# 따라서 2차원 리스트 buslistinform의 요소는 [(버스 고유번호, 버스 노선 고유번호, 버스 도착시간)]

def get_businfor(bustopid):
decode_key = unquote(”) #개인 고유 발급 키
url = ‘http://apis.data.go.kr/6280000/busArrivalService/getAllRouteBusArrivalList’
queryParams = ‘?’ + urlencode({quote_plus(‘ServiceKey’): decode_key,
quote_plus(‘pageNo’): ’1′,
quote_plus(‘numOfRows’): ’10′,
quote_plus(‘bstopId’): bustopid})
request = urllib.request.Request(url + queryParams)
request.get_method = lambda: ‘GET’
response_body = urlopen(request)
a = response_body.read()

soup = BeautifulSoup(a, ‘html.parser’)
table = soup.find(‘serviceresult’)
for tr in table.find_all(‘msgbody’):
tds = list(tr.find_all(‘itemlist’))

for itemlist in tds:
routeid = itemlist.find(‘routeid’).text
arrivalestimatetime = itemlist.find(‘arrivalestimatetime’).text
for i in range(0, len(buslistinform)):
if (buslistinform[i][1] == routeid):
buslistinform[i][2] = arrivalestimatetime

return buslistinform

# 버스 도착시간을 리턴하는 함수. parameter는 버스 번호이며 buslistinform에서 버스 번호를 조회해 버스번호를 리턴함.
def get_bus_arrivetime(busnum):
for i in range(0, len(buslistinform)):
if buslistinform[i][0] == busnum:
return buslistinform[i][2] def main():

gsp = gspeech.Gspeech()
gsp.pauseMic()
global target_buses
global led_tag
global buslistinform

while True:

find()
gsp.resumeMic()

compare_numbers = [] choosen_bus = []

# Google TTS를 이용하여 정류장 이름+음성안내를 합성해 송출한다
client = texttospeech.TextToSpeechClient()
bus_stop_notice = ‘정류장입니다. 확인할 버스 번호를 말씀해주세요’
bus_stop_name = bstopnm[0] synthesis_input = texttospeech.types.SynthesisInput(text=bus_stop_name + bus_stop_notice)

voice = texttospeech.types.VoiceSelectionParams(
language_code=’ko-KR’,
ssml_gender=texttospeech.enums.SsmlVoiceGender.NEUTRAL)

audio_config = texttospeech.types.AudioConfig(
audio_encoding=texttospeech.enums.AudioEncoding.MP3)

response = client.synthesize_speech(synthesis_input, voice, audio_config)

bus_notice = ‘bus.mp3′
with open(bus_notice, ‘wb’) as out:
out.write(response.audio_content)
gsp.pauseMic()
sound_play(bus_notice)
gsp.resumeMic()

# 음성인식 될 때까지 대기. 받아온 음성은 stt에 저장된다

stt = gsp.getText()
if stt is None:
break
# 받아온 음성에서 버스번호만 찾아온다. ex) 511번 알려줘 ==> get_numbers = ['511'] get_numbers = re.findall(“\d+”, stt)

# 구글 STT로 인식된 번호가 정류장 정차 버스 리스트에 존재하는지 확인. 확인후 존재한다면 compare_numbers에 입력
compare_numbers = list(set(buslist).intersection(get_numbers))
# 리스트에 존재한다면 구글 TTS로 음성변환
all_check_num = “번 버스”.join(compare_numbers)

# 일치하는 번호가 없는경우 “해당 버스 번호는 없습니다” 음성 인식 출력
if len(compare_numbers) == 0:
mismatch_notice = “/home/pi/mismatch.mp3″
gsp.pauseMic()
sound_play(mismatch_notice)
gsp.resumeMic()

# 물어본 버스가 하나 이상 존재하는 경우 버스번호와 도착시간 음성 인식 변환 후 출력
else:
for i in range(0, len(compare_numbers)):
target_bus = compare_numbers[i] # 타겟버스 번
target_bus_arrivalestimate = get_bus_arrivetime(target_bus)
minute=int(int(target_bus_arrivalestimate) / 60)
second=int(target_bus_arrivalestimate)-(60*minute) #
synthesis_input_match = texttospeech.types.SynthesisInput(text=target_bus
+ “번 버스 약 ”
+ str(minute)
+ “분”
+ str(second)
+ “초 후 도착합니다” )
response_match = client.synthesize_speech(synthesis_input_match, voice, audio_config)
match_notice = ‘match.mp3′

with open(match_notice, ‘wb’) as out:
out.write(response_match.audio_content)
gsp.pauseMic()

sound_play(match_notice)
gsp.resumeMic()
time.sleep(1)

# 물어본 버스 번호가 1개일 경우
if (len(compare_numbers) == 1):
synthesis_input_match = texttospeech.types.SynthesisInput(text=compare_numbers[0] + “번 버스 도착 시 bus_choice = “몇 번 버스로 안내해드릴까요??”
synthesis_input_match = texttospeech.types.SynthesisInput(text=bus_choice)
response_match = client.synthesize_speech(synthesis_input_match, voice, audio_config)
choice_notice = ‘choice.mp3′

with open(choice_notice, ‘wb’) as out:
out.write(response_match.audio_content)
sound_play(choice_notice)
stt = gsp.getText()
get_choice = re.findall(“\d+”, stt)
choosen_bus = list(set(compare_numbers).intersection(get_choice))

while (len(choosen_bus) == 0) :
time.sleep(0.5)
stt = gsp.getText()
get_choice = re.findall(“\d+”, stt)
choosen_bus = list(set(compare_numbers).intersection(get_choice))
if len(choosen_bus) > 0 :
break

# 선택한 버스 안내 음성
for i in range(0, len(choosen_bus)):
synthesis_input_match = texttospeech.types.SynthesisInput(text=choosen_bus[i] + “번 버스 도착 시 안내해드리겠습니다.”)
response_match = client.synthesize_speech(synthesis_input_match, voice, audio_config)
final_notice = ‘final.mp3′

with open(final_notice, ‘wb’) as out:
out.write(response_match.audio_content)안내해드리겠습니다.”)
response_match = client.synthesize_speech(synthesis_input_match, voice, audio_config)
one_notice = ‘one.mp3′

with open(one_notice, ‘wb’) as out:
out.write(response_match.audio_content)
gsp.pauseMic()

sound_play(one_notice)
gsp.resumeMic()
target_buses.append(compare_numbers[0]) # 타겟 버스 리스트에 추가.
led_control()
print(“led on”)

else: # 물어본 버스가 2개 이상인 경우 선택 음성 안내

gsp.pauseMic()
sound_play(final_notice)
gsp.resumeMic()
target_buses.append(choosen_bus[i]) # 타겟 버스 리스트에 추가
led_control()
print(“led on”)

# 타겟 버스(탑승하려는 버스) 도착시간 확인

while (len(target_buses) > 0 ):
gsp.pauseMic()
for i in range(0, len(target_buses)):
flag = check_bus_arrive(target_buses[i])
# 버스 잔여 시간이 100 초 이하인지 판단
time.sleep(1)
if flag == True: # 100초 미만일경우 버스 도착안내메세지
synthesis_input_match = texttospeech.types.SynthesisInput(text=target_buses[i] + “번 버스 잠시 후 도착합니다”)
response_match = client.synthesize_speech(synthesis_input_match, voice, audio_config)
arrive_notice = ‘arrive.mp3′
with open(arrive_notice, ‘wb’) as out:
out.write(response_match.audio_content)
gsp.pauseMic()
sound_play(arrive_notice)

led_control()

time.sleep(7)
target_buses.remove(target_buses[i])
led_control()
break

if (len(target_buses) == 0):
break
continue

# 버스 url송출해 실시간으로 도착정보 가져오는 함수
def get_realtime_businform():
while True:
global buslistinform
get_businfor(163000167) time.sleep(10)

# parameter 값에 대해 도착시간이 90초 미만인지 확인해주는 함수
def check_bus_arrive(target_bus):
target_bus_arrivalestimate = int(get_bus_arrivetime(target_bus))
if (target_bus_arrivalestimate < 90): # 90초 미만이면 True, else False
return True
else:
return False

# led 제어하는 함수
def led_controller():
global target_buses
global led_tag

while True:
parser = argparse.ArgumentParser(description=’matrix_demo arguments’,
formatter_class=argparse.ArgumentDefaultsHelpFormatter)

parser.add_argument(‘–width’, type=int, default=32, help=’Width’)
parser.add_argument(‘–height’, type=int, default=16, help=’height’)
parser.add_argument(‘–block-orientation’, type=int, default=-90, choices=[0, 90, -90],
help=’Corrects block orientation when wired vertically’)
parser.add_argument(‘–rotate’, type=int, default=0, choices=[0, 1, 2, 3], help=’Rotation factor’)

args = parser.parse_args()
# led tag= 1 일때마다 led 표시 값 갱신
if (led_tag==1) :

if len(target_buses) == 1 :
demo(args.width, args.height, args.block_orientation, args.rotate,target_buses[0], “”)
elif len(target_buses) > 1 :
demo(args.width, args.height, args.block_orientation, args.rotate,target_buses[0], target_buses[1])
else :
demo(args.width, args.height, args.block_orientation, args.rotate, “”, “”)
led_tag=0

if __name__ == “__main__” :
get_busstopinfor(buslistinform, 163000167)
t = threading.Thread(target=main)
t2 = threading.Thread(target=get_realtime_businform)
t3 = threading.Thread(target=led_controller)
t.start()
t2.start()
t3.start()

8.2. find() : 비콘 검색함수

from bluepy.btle import Scanner, DefaultDelegate

class ScanDelegate(DefaultDelegate):
def __init__(self):
DefaultDelegate.__init__(self)
def handleDiscovery(self, dev, isNewDev, isNewData):
if isNewDev:
print (“Discovered device”, dev.addr)
elif isNewData:
print (“Received new data from”, dev.addr)
def find():
beacon = 1
scanner = Scanner()

while (beacon ==1):
devices = scanner.scan(3.0)
for dev in devices:
print (“Device %s (%s), RSSI=%d dB” % (dev.addr, dev.addrType, dev.rssi))
for (adtype, desc, value) in dev.getScanData():
print (“%s = %s” % (desc, value))
if(value == ‘Helper’and dev.rssi > -52):
beacon = 0
break
else:
beacon = 1
if beacon ==0:
break

if __name__ == ‘__main__’:
find()


8.3. sound_play() : 음성안내 실행 함수

import pygame
import time

def sound_play(name):
file = name
pygame.mixer.init()
pygame.mixer.music.load(file)
pygame.mixer.music.play()
clock = pygame.time.Clock()
while pygame.mixer.music.get_busy():
clock.tick(3)
pygame.mixer.quit()

8.4. Gspeech module : 구글에서 제공하는 샘플코드 사용
· TTS : https://cloud.google.com/text-to-speech/docs/samples?hl=ko
· STT : https://cloud.google.com/speech-to-text/docs/samples?hl=ko

9. 참고문헌
[1] 김원호, 이유화, 김시현, 이유화, 김시현. “시각장애인 대중교통 이용실태 분석 및 대중교통시설 내 보행지원 시스템 구축방안.” 서울도시연구 10 no.3 (2009): 97-114.
[2] MBCNEWS. (2019, 12 25). [소수의견] 버스 탄 시각장애인 봤나요…타고 내리는 게 전쟁 [비디오파일]. 검색경로 https://www.youtube.com/watch?v=saoX-lR-iJ0&feature=emb_title
[3] “시각장애인도 버스를 탈 수 있게 해주세요.” (2020년 3월 1일). 청와대. n.d. 수정, https://www1.president.go.kr/petitions/583770.
[4] “인간공학적 시선으로 본 장애인의 이동권.” LG챌린저스. n.d. 수정, 2020년 3월 20일 접속, http://www.lgchallengers.com/wp-content/uploads/global/global_pdf/S0410.

 

 

 

[61호]Can You See Me?(랜덤 배열을 활용한 투명 도어락)

61 캔유시미 썸네일

61 feature 캔유시미 (1)

2020 ICT 융합 프로젝트 공모전 대상

Can You See Me?(랜덤 배열을 활용한 투명 도어락) 

글 | 광운대학교 나영은 선아라 신동빈 조수현 최희우

심사평
칩센 훌륭한 작품을 개발하였습니다. 작품의 의도, 개발 과정, 보고서 모두 대단하다는 말밖에 할 수 없을 듯 합니다. 여러 가지 기능을 구현하고, 비밀번호 노출에 대한 강화를 위해 블루투스를 이용하여 스마트폰을 거치는 과정이 있었을 듯합니다. 다만 스마트폰을 반드시 이용해야하는 것이 제약이 될수 있으므로 도어락 패드를 display 터치패드 형태로 구성하여 랜덤하게 위치 이동이 이루어지면 지원자(팀)가 원하는 소기의 목적은 달성 가능하지 않을까 합니다.
펌테크 실생활에 사용이 될 수 있는 실용성, 아이디어, 창의성이 돋보이는 작품으로 출품된 작품은 충분히 실생활에 바로 적용이 가능할 수 있는 상업성이 뛰어난 작품이라고 생각됩니다. 전체적으로 기획의도에 맞게 시스템을 꼭 필요한 기능으로 목적에 맞는 최적의 시스템으로 잘 구성하였고 기술 구현도, 완성도 등에서도 상당히 뛰어나고 훌륭한 작품으로 생각됩니다.
위드로봇 재미있는 아이디어입니다만, 실용성 측면에서 좀 더 고민이 필요합니다.

1. 작품 개요
기존의 도어락이나 비밀번호 인증 방식은 키패드가 보이는 출력부와 비밀번호를 입력하는 입력부가 일체형으로 되어있다. 이러한 특징 때문에 지문의 흔적 등으로 인해 제삼자에게 비밀번호가 해킹을 당할 위험이 다분하다. 이러한 점에서 본 제품은 입력부를 투명한 키패드로, 출력부를 안드로이드로 나누어 배치하여 그런 위험을 사전에 방지하는 것에 의의가 있다.
출력부에서 키패드의 위치 출력 시 시도할 때마다 그 배치를 랜덤으로 배치하여 보안성을 기하급수적으로 높인다.
배터리 시장이 커지고 있는 사회 흐름에 맞춰 본 제품도 배터리 효율을 극대화할 수 있는 방법을 고려했다. 제품의 특성상 사용자가 사용하는 시간보다 사용하지 않는 대기 모드 시간이 더 길기 때문에, 대기 시간에 저전력 모드를 활용해 사용하는 전력을 최소화하였다.
본 제품의 경우, 매번 키패드에 매핑된 숫자와 전송버튼의 위치가 달라지기 때문에 무수한 경우의 수가 도출된다. 따라서 본 제품의 비밀번호가 해킹될 확률은 프로그램 시뮬레이션을 통하여 도출된 통계적인 확률로 제시를 한다.
기본적인 통신방식은 보안성이 높은 블루투스 4.0을 기반으로 사용하며 본 제품의 MCU는 Atmega128을 활용한다.

2. 작품 설명
2.1. 주요 동작 및 특징
2.1.1. 투명 터치 키 패드
비밀번호 입력키 패드는 투명 터치 필름을 이용하였다. 투명 터치 필름을 이용하게 되면 각각의 키 패드 각각의 숫자가 노출되지 않아 Door lock에 접근하는 사람은 어디 위치에 어떤 숫자나 기호가 있는지 알 수 없다. 각 자리 값에 해당하는 숫자는 오직 안드로이드 어플에서 확인할 수 있으며 매번 갱신된다. 그렇기 때문에 보안성 측면에서 더욱 우수하다.

2.1.2. 안드로이드 앱을 통한 키 배열 위치 확인 및 비밀번호 검사
키 배열 위치(이하, 고유 자리값이라고 하겠다.)는 터치 키패드의 12-KEY 각각의 자리를 의미한다. 안드로이드 앱에서는 키패드의 위치가 매번 랜덤으로 바뀐다.

61 feature 캔유시미 (2)

61 feature 캔유시미 (3)

도어락과 블루투스 연결 시도 후, 연결이 완료되면 도어락과 데이터를 주고받으며 본격적인 기능이 활성화된다.
사용자는 위 그림1-2와 같이 안드로이드 앱에서 숫자와 기호의 위치를 확인한 뒤, 이에 맞는 터치 센서의 고유 자리 값을 눌러 비밀번호를 입력한 뒤, Send 위치를 누르게 되면 비밀번호 고유 자리 값 버퍼가 안드로이드 앱으로 전송이 된다.
비밀번호 고유 자리 값 버퍼와 기존에 설정된 비밀번호를 안드로이드 앱에서 비교한 뒤, 다시 도어락으로 성공 혹은 실패 여부를 전송해 준다.
전송 버튼(Send) 위치 또한 매번 갱신되기 때문에 외부인의 경우 비밀번호를 뚫는 것이 매우 힘들다. 비밀 번호 변경은 안드로이드 앱에서만 가능하다.

2.1.3. 액추에이터
모터
비밀번호가 일치했다는 신호를 블루투스 모듈로부터 받은 경우, 모터를 활성화시켜 문 잠금을 해제한다. 이후 리드 스위치 인식에 따라 문이 열렸다가 닫힘이 인식되면 다시 문을 잠그고, 모터를 비활성화 시킨다. 모터의 비활성화는 서보모터에 공급되는 PWM 신호를 끊어주는 행위이다. 이는 실내에서 문을 직접 열 수 있도록 하기 위함 이기도 하며, 예기치 못하게 모터의 혼(Horn)이 걸려서 Stall Current가 흐르는 상황을 방지하기 위함도 있다.
비밀번호가 일치하지 않으면 모터는 초기 상태를 유지한다.
부저
현재 도어락에서 발생하는 모든 상황(이하, Status)을 이용자가 손쉽게 파악하고자 하는 목적으로 각 음계의 주파수를 모두 계산하여 이 제품에서 사용되는 효과음들을 모두 직접 제작했다. 여러가지 기기의 상태에 따른 부저 음을 구현하였는데, 그 내용은 아래와 같다.

61 feature 캔유시미 (4)

(구현에 있어, 자세한 과정은 제작 과정에 명시하였다.)

RGB LED
현재의 Status를 확인하는 데 있어 부저 다음으로 중요한 요소이다. 도어락에서 소리 나는 것이 불쾌한 이용자가 있다면, 부저를 음소거모드(MUTE)로 전환하고 현재의 Status를 부저 말고도 다른 요소로 파악할 수 있어야 하기 때문이다.
부저와 마찬가지로 여러 상황에 따른 효과를 만들었는데, 아래와 같다.

61 feature 캔유시미 (5)

(구현에 있어, 자세한 과정은 제작 과정에 명시하였다.)

2.1.4. 저전력
현대 사회에 가장 이슈화되는 주제로 배터리를 들 수 있다. 이런 시대의 흐름에 맞추어 본 제품에서도 배터리의 성능을 극대화하기 위하여 저전력 설계를 기반으로 제품을 만들었다. 저전력 설계에서 빠질 수 없는 중요한 기능이 슬립 모드이다. 거의 모든 MCU에선 슬립모드 기능을 제공하며, 클럭을 차단하거나 일부 Peripheral을 제한하는 것을 통해 전력을 절약하는 모드를 말한다.
도어락에서 30초 간 별 다른 동작(터치, 어플 자리 값 갱신 등)을 하지 않을 시, 슬립모드(POWER-DOWN)로 진입하도록 구현하였으며, 또한 슬립모드로 진입할 때, 거의 모든 주변회로들이 OFF되도록 하였다.
하지만 슬립모드에서도 여전히 저전력 제품이라고 할 수 없을 정도의 전류(~20mA)가 흐르고 있으며, 자세한 내용은 아래 고찰에서 다루었다.

2.1.5. 키패드 입력
무음모드 설정
Mute 위치를 더블 클릭(연속으로 두 번 TOUCH)하게 되면 무음 진입 효과음과 함께 무음모드로 진입한다. 같은 과정을 다시 진행할 경우 벨소리 진입 효과음과 함께 벨소리 모드로 진입한다.
비밀번호 성공 시
키패드에서 비밀번호를 입력하고 안드로이드 앱 상의 Send 위치를 누르게 되면 설정한 비밀번호와 비교하게 된다. 그리고 자릿값과 비밀번호가 일치할 경우, 부저와 RGB가 SUCCESS로 반응하고 서보모터가 돌아가면서 잠금이 해제된다.
이때, 문을 열게 되면 리드스위치가 문이 열렸음을 감지하며, 이후 다시 닫을 경우 또한 문이 닫힘을 감지하여 다시 RGB, 부저가 닫힘 반응을 한 뒤, 일정 시간 후 서보모터가 돌아 잠금이 활성화 된다.
비밀번호 실패 시
키패드에 입력한 비밀번호가 잘못된 비밀번호 일시 성공했을 때와는 다르게 나타난다. 우선 모터가 돌아가지 않고 RGB와 BUZZ 가 FAIL에 반응하고 서보모터가 돌아가지 않아 문이 열리지 않는다.
키패드의 입력한 비밀번호가 틀릴 때에는 실패 신호와 동시에 앱에서는 셔플이 되며, 도어락에서는 실패 Stack을 쌓는다. 그리고 이 비밀번호 실패 Stack이 5회가 되면, SIREN 소리와 RGB의 빨간불이 나타나면서 반응하게 된다. 그리고 도어락을 제한 한 후, 안드로이드로 제한 신호를 전송하게 되어 일정시간이 지난 후 슬립모드로 들어가게 된다.

2.2. 전체 시스템 구성

61 feature 캔유시미 (6)

 

61 feature 캔유시미 (7)

위 그림2-1와 같이 전체적인 시스템을 플로우 차트를 통해 간략히 표현하였다. 적색으로 표현된 텍스트는 ‘주요 동작 및 특징’ Part에서 액추에이터 부분(RGB, BUZZER)에 대한 표현이다.
그림 2-2에선 현재 핀에서의 입출력 기능들을 설명한다. 다음과 같다.
· PA0~2의 경우 RGB LED와 연결되어 PWM 출력 핀으로 사용하였다.
· PA5의 경우 Reed Switch 입력 핀으로 사용한다.
· PA7의 경우 릴레이 스위치 제어 용도로 사용한다.
· PB5의 경우 서보모터 PWM 출력 핀으로 사용한다.
· PC0의 경우 디버그 포트로 사용한다.
· PD0의 경우 터치 키패드 입력 이벤트에 대한 인터럽트 입력 핀으로 사용한다.
· PD2의 경우 UART1의 Rx핀, PD3의 경우 UART1의 Tx핀으로 사용함.
· PE0의 경우 UART0의 Rx핀, PE1의 경우 UART0의 Tx핀으로 사용한다.
· PE3의 경우 부저의 CTC 출력 핀으로 사용한다.

2.3. 개발 환경
- 개발 언어: JAVA, C
- 개발 Tool: Android Studio, Atmel Studio
- 디버깅 Tool: Beyond Compare(코드 병합 및 디버깅), Saleae Logic(uart 데이터 포맷 분석), TeraTerm(통신 디버깅 및 장치 설정), 안드로이드 디바이스
도어락 장치부의 펌웨어를 작성하기 위한 개발환경으로 Atmel Studio를 사용하였다.
안드로이드 기반 어플리케이션을 제작하고자 Android Studio에서 개발작업을 진행하였다.
원활한 개발을 위해, 디버깅 관련 Tool들을 적극적으로 이용했다. 코드 보수 및 병합하는 과정에서 에러가 발생할 경우 Beyound Compare을 통해 이전 코드와 비교하여 에러 혹은 버그를 빠르게 파악할 수 있었다. Saleae Logic 툴을 통해 시리얼 통신 데이터 및 PWM 신호 등을 관측하였다.

3. 단계별 제작 과정
3.1. 서보모터 구동을 위한 50Hz PWM 신호 만들기 (Timer1)
서보모터를 제어하는 데 있어, 20ms주기를 갖는 PWM 신호를 만들어 주어야 한다. 보통 16-bit 분해능을 갖는 타이머 카운터를 이용하여 서보모터를 제어하는 것이 일반적이다. 8-bit 타이머 카운터를 이용하게 되면 분해능이 낮아 정확한 각도 제어가 16-bit 타이머 카운터에 비해 쉽지 않다.
그렇기 때문에 16-bit 분해능을 갖는 Timer/Counter1을 이용하였다.
추후에 고찰에서 설명하겠지만, 초기에는 시스템 클럭 16MHz 기준으로 코드를 작성했지만, 이후 8MHz 크리스탈로 변경하여 Timer 관련 레지스터 설정들을 모두 변경하였다. 설정은 아래와 같이 하였다.

TCCR1A & TCCR1B Register

COM: 10 ▶ default HIGH, compare match LOW
WGM: 1110 ▶ FAST PWM, TOP: ICR1
CS: 010 ▶ 8 Pre-scaling.

ICR1 Register
ICR1=19999 ▶ ICR1H = (19999>>8), ICR1L=19999&0xff

50Hz짜리 PWM 신호를 만들기 위한 레지스터 세팅은 위와 같으며, OCR1 레지스터(OCR1H, OCR1L)를 통해 TCNT레지스터와 비교매치를 발생시켜 PWM 신호를 생성하면 된다.
코드에서 사용하기 쉽도록 함수화 하였는데, 아래와 같다.

61 feature 캔유시미 (9)

TimerCounter1에서 제공하는 PWM 출력 채널은 A, B, C 3개 를 제공하는데, A채널(PB5)을 이용하였다. OCR값을 바꿔 줌에 따라 Duty Cycle이 바뀌게 된다. 결과는 아래 그림 4와 같다.

61 feature 캔유시미 (10)

원하는 Angle을 손 쉽게 사용하기 위해 함수화 하였는데, 아래 그림5와 같다.

61 feature 캔유시미 (11)

0도일 경우 2.5%의 Duty를 갖는 신호를, 180도일 경우 12.5%의 Duty를 갖는 PWM신호를 공급하도록 했다. 서보모터 제조사마다 Duty에 따른 각도가 다를 수 있으며, 아래 자료를 참고하여 만들었다.

61 feature 캔유시미 (12)

convertServoAngle(angle)이라는 함수를 사용하면 converted된 값을 return해준다. 이 값을 OCR에 넣어주면 작동하도록 구현하였다.

3.2. RGB LED 각각 밝기 제어를 하기 위한 PWM 출력 채널 구현(Timer0, GPIOA)
RGB LED는 3개의 LED가 내장되어 있어 PIN OUT을 확인해보면, GND는 공통이며, R, G, B 각각 입력 핀을 갖고 있다. 그렇기 때문에 RGB LED를 통해 색을 표현하고 싶다면 PWM 신호를 통해 구현이 가능하다. 다만, 현재 사용하는 타이머 개수 제한으로 인해 무작정 사용할 수 없다. Timer1의 경우, SERVO 모터용으로 사용하고 있으며, TIMER3의 경우 BUZZER를 사용할 것이므로 CTC모드를 사용해야 한다. 그리고 나머지 8-bit 타이머의 경우 PWM 출력 채널이 1 개 밖에 없으므로, PWM 출력 핀을 사용하는 것은 좋은 생각이 아니다. 그리하여, Tiemr0에서 발생하는 인터럽트 루틴에서 GPIO로 PWM 신호를 만들어 주는 방법을 고안했다. 설정은 아래와 같이 하였다.

TCCR0

COM: 00 ▶ OC0 Disconnected.
WGM: 10 ▶ CTC Mode.
CS: 001 ▶ No Pre-scaling.

TIMSK
OCIE0: 1 ▶ Output compare Interrupt Enable.

OCR0 = 250

마찬가지로, 16MHz 크리스탈을 8MHz로 변경했으므로, 이와 맞는 코드로 수정된 상태이다.
분주를 하지 않으므로 8MHz를 Timer Clock으로 사용하며, CTC 모드에서 비교매치 발생 시, Clear됨과 동시에 비교매치 인터럽트를 허용해주었으므로, TCNT와 OCR이 같아지는 지점에서 비교매치 인터럽트가 발생한다. 계산해보면 ISR로 진입하는 데 32kHz마다 진입을 하게 된다. (기존 16MHz 크리스탈로 설정했을 땐 16kHz 속도로 ISR에 진입했었다.) 16kHz를 설정하여 코드를 작성할 예정이므로 ISR을 2번 진입했을 때 1번 동작하도록 Flag 처리를 따로 해주었다.
구현 코드는 아래와 같다.

ISR(TIMER0_COMP_vect) //32khz 속도로 들어옴.
{
static unsigned char ticks=0;
static int twice=0;
twice++;
if(twice%=2) return;
//16khz 속도로 들어옴.
if(ticks!=255) {
if(ticks == led_R) PORTA &= ~(0×01);
if(ticks == led_G) PORTA &= ~(0×02);
if(ticks == led_B) PORTA &= ~(0×04);

}
else {
led_B=led_B_buf;
led_G=led_G_buf;
led_R=led_R_buf;
PORTA|=( ((led_B!=0?1:0)<<2) | ((led_G!=0?1:0)<<1) | ((led_R!=0?1:0)<<0) );
}

. . . 생략 . . .
ticks++; //255 이후 Clear.

}

원하는 색상을 출력하기 위해 아래와 같은 함수를 만들어 사용하였다.

61 feature 캔유시미 (16)

setRGB(r,g,b) 함수를 사용하여 PA0,1,2 핀의 PWM 출력 상태를 확인해봤을 때, 아래 그림8과 같다.

61 feature 캔유시미 (17)

PWM 주기는 16.13ms이며, 이는 ISR상에서 ticks 변수가 255까지 카운팅되고 Clear되는 시간이다. (16kHz ▶ 62.5us*256 ≒ 16ms)
setRGB(r,g,b)에 들어가는 파라미터 값은 0~255를 넣어주면 되며, 0을 넣어주면 PWM신호가 출력되지 않도록 코딩해두었다.

3.3. 부저 구동을 위해 주파수 가변 함수 구축(Timer3)
사람의 인체에서 귀와 눈을 비교해볼 때 귀가 더욱 민감하다. 그렇기 때문에 RGB LED 대비 BUZZER의 출력 주파수에 대한 분해능은 매우 정밀할 필요가 있다. 그렇기 때문에 16-bit 카운터를 이용하여 구현하였다.
TIMER3을 이용하였으며 아래와 같이 설정하였다.

TCCR1A & TCCR1B Register

COM: 01 ▶ Toggle (in CTC)
WGM: 1100 ▶ CTC, TOP: ICR3
CS: 010 ▶ 8 Pre-scaling.

설정해둔 ICR값에 TCNT가 도달하게 되면 즉시 Clear가 되며, PWM 출력핀은 Toggle하게 된다.
즉, 원하는 주파수에 대한 ICR값을 찾아주게 되면 원하는 부저음을 낼 수 있게 된다.

61 feature 캔유시미 (38)
setICR3(num)함수에 1706을 넣어주게 되면 853값이 ICR3H,L 레지스터로 나뉘어 들어가게 되며 출력은 결국 대략 1,172Hz로 토글하게 되며, 부저 출력은 586Hz 출력이 나가게 된다. 해당 출력은 아래 그림과 같다.

61 feature 캔유시미 (17)

여러 소리들을 직접 찾아내어 아래와 같이 모두 Define하였다.

61 feature 캔유시미 (18)

3.4. Tick Timer 구축(Timer0)
임베디드 시스템에서 Tick Timer는 필수적이다. 해당 프로젝트에선 RGB LED 구현, 여러 액추에이터 동시 제어, 센서 데이터 갱신 속도, 더블클릭 등을 구현하기 위해 아주 중요한 역할을 한다. 위 3.2에서 Timer0에 대한 레지스터 세팅에 의해 32kHz의 속도로 ISR에 진입한다. twice라는 flag를 만들어 16kHz마다 Tick 관련 코드에 진입할 수 있도록 구성하였다. 아래 코드에서 Tick 변수들은 각각 액추에이터나 기능들을 제어하는 데 있어 필수적으로 사용되는 타이머들이다.

61 feature 캔유시미 (19)

3.5. BUZZER 동작과정
BUZZER는 다음과 같이 구성되었다.

61 feature 캔유시미 (20)

BUZZER가 실험됨과 동시에 TICK.buzz_1ms가 카운터 되면서 setSoundClip()함수에 있는 switch-case문이 실행된다.
도어락이 특정 동작을 할 때 원하는 부저음을 case별로 만든 후, 이를 실행시켜 소리가 나도록 구성한다.
TICK.buzz_1ms가 카운트됨과 동시에 if-else문을 사용하여 시간별로 짜 놓았던 (TICK.buzz_1ms==(지정된시간))이 되면 setSound()로 정의해 놓았던 소리가 나오도록 한다. 모든 소리가 재생이 끝나면 TICK.buzz_1ms=0으로 해주어 다음 부저 소리단계를 실행시킬 수 있도록 한다.

3.6. RGB LED동작과정
RGB는 다음 틀과 같이 구성되었다.

61 feature 캔유시미 (21)

RGB가 실행됨과 동시에 위에서 만들어 주었던 TICK.rgb_1ms가 카운터 되면서 RGB_Drive();에 있는 switch-case문이 실행된다. 도어락이 특정 동작을 할 때, 원하는 LED를 case별로 만든 후, 이를 실행시켜 RGB가 빛이 나도록 구성한다.
TICK.rgb_1ms가 카운트 됨과 동시에 if-else문을 (TICK.rgb_1ms==(지정시간))이 되면 set(R,G,B)가 실행되어 색이 시간별로 바뀌거나 빛을 낸다. BUZZER와 마찬가지로 TICK.rgb_1ms=0으로 셋해주어 다음 RGB가 반응할 때, 다시 1부터 카운터 되도록 한다.

3.7. Motor 동작과정
MOTOR는 다음과 같은 구성으로 동작한다.

61 feature 캔유시미 (22)

키패드를 이용해 비밀번호 버퍼를 채워 나가고 이 버퍼가 기존 비밀번호와 일치하게 되면, 부저와 RGB가 동시에 반응함과 동시에 MOTOR가 돌아가 사용자가 문을 열수 있게 된다. 그리고 리드스위치가 자석이랑 떨어짐으로써 HIGH로 인식하게 된다. 사용자가 문을 닫지 않는 이상 이는 변함이 없다.
사용하자 문을 닫게 되면, 리드스위치가 자석과 닿아 LOW로 신호가 바뀌어 MOTOR가 다시 돌아가 도어락이 닫히게 된다.
반대로, 입력한 버퍼가 기존 지정해 놓았던 비밀호와 다를 때에는 모터가 돌아가지 않아 사용자가 문을 열 수 없게 된다. 그리고 1초 뒤, PWM 신호를 끄게 되어 처음부터 비밀번호 버퍼를 입력할 수 있게 된다.

3.8. 슬립모드 구현
저전력으로 구동되는 모든 제품에 있어 슬립모드가 들어가는 건 대부분 필수 불가결하다. 도어락 역시 해당 기능이 포함되어야 하며, 현재 사용하고 있는 MCU(ATMEGA128)에서도 여러 가지 슬립모드를 제공한다. 그 중, 모든 Clock을 끊어 거의 모든 Peripheral 기능들을 차단(외부 인터럽트와 I2C Address match만 활성화되며 나머지는 모두 Block.)하여 가장 전력 소모가 낮은 POWER-DOWN 모드를 사용하였다.
기본 구성은 다음과 같다. 터치 입력 혹은 블루투스로부터 받는 신호. 즉 UART 수신 데이터가 없는 이상 sleep관련 Tick Timer를 계속 Count한다. Tick에 의해 30초가 지난 것을 판단하게 되면 자동으로 슬립모드로 빠지게 하였다. 만일 UART 수신 데이터 아무 것이든 감지가 되면 Tick을 Clear한다.

#include <avr/sleep.h>
. . . 생략 . . .
int main(void){
. . . 생략 . . .
while (1) {
. . . 생략 . . .
if(TICK.sleep_tim==29000) setSoundClip(BUZZ_SLEEPOUT); //슬립모드 빠질 준비
else if(TICK.sleep_tim>=30000){
gotoSleep();//슬립모드 진입
. . . 생략 . . .
}
}
. . . 생략 . . .
void gotoSleep(){
sleep_flag=1;
PORTA &= ~(0×80); //릴레이 OFF
PORTA &= ~(0×07); //RGB OFF

set_sleep_mode (SLEEP_MODE_PWR_DOWN); //only interrupt
sleep_enable ();
sei();
sleep_cpu();
. . . 생략 . . .
IsWakeup=1;

}
void sleep_stack_clear(){
TICK.sleep_tim=0;
}

sleep_cpu() 함수가 호출되면 그 즉시 슬립모드로 진입하며, 그 전까지 갖고 있던 메모리 데이터들은 모두 유지되어 있다. 외부 인터럽트 등에 의해 Wakeup하게 되면, 기존 데이터는 모두 유지하며, sleep_cpu()함수 이후 라인에서 마저 진행하게 된다.

61 feature 캔유시미 (23)

결론적으로는 슬립모드에서 마저 20mA라는 큰 전류가 흐르고 있다. 이는, wakeup에 대한 외부 인터럽트 신호를 주기 위하여 터치 센서를 활성화(대기 전류 10mA)시켰으며, 3.3V 레귤레이터(소모전류 10mA)에서 소모하는 전류까지 포함하여 20mA 정도가 소비되었다.

3.9. UART 문자, 문자열 송수신 함수 구현
현재 UART1, UART0를 사용하는 데 있어 문자열을 주고받는 함수가 구현되어야 함은 필수적이다.
수신부, 송신부 모두 Interrupt 방식으로 데이터를 주고받도록 구현했다. (polling 방식의 경우 예기치 못한 비효율적인 상황들이 발생할 가능성이 크다고 판단했음.)
수신의 경우 수신완료 인터럽트가 발생되면 임시 버퍼에 우선 저장하는 방식으로 구현한 뒤, 필요할 때 가져다 쓰는 방식으로 구현했으며, 문자열이 들어오는 상황인 경우 배열 버퍼를 만들어 데이터를 계속해서 수신받고 문자의 끝 신호가 들어오게 되면, 기존에 만들어 둔 수신완료 Flag를 띄워 문자열이 수신되었음을 판단하도록 코드를 구성하였다.
송신의 경우 평소에는 송신완료 인터럽트를 꺼두었으며, 송신 명령을 내리면 그때 송신완료 인터럽트를 켜주는 방식으로 구현을 했다. 문자열의 경우 매번 이후 인덱스를 연속적으로 전송하도록 구현했으며, 문자열 마지막에 붙는 NULL문자로 문자의 끝을 확인한 뒤, 송신완료 인터럽트를 다시 끄도록 했다. 구현한 코드는 아래와 같다.

//수신부 문자열 수신 구현
ISR(USART0_RX_vect) // { <, X, Y, Z, > }
{//5Byte Fixed data.
static u8 cnt=0;
u8 _buff=UDR0; //fifo 방식에 대한 예방 차원으로 버퍼사용

switch(cnt){
case 0: if(_buff==’<’){ //문자열의 첫 데이터??
TOUCH.sensorData[cnt]=_buff;
cnt++; //다음 요소 수신 충족
} break;
case 4: if(_buff==’>’){ //문자열의 마지막 데이터??
TOUCH.sensorData[cnt]=_buff;
cnt=0; //인덱스 초기화
UCSR0B &= ~(0×80); //잠시 수신 인터럽트 끔
TOUCH.receive_cplt_sensor=1; // 문자열 수신완료 플래그 HIGH
}break;
default: //data 부분
TOUCH.sensorData[cnt]=_buff;
cnt++;
break;
}
}

위 코드의 경우 터치 키패드(UART0) 데이터 패킷를 문자열의 형태로 받는 루틴이다. 받는 데이터의 패킷이 고정되어 있으므로 위와 같이 구현된다. 따로 특정 함수를 사용할 필요 없이 데이터를 사용하고자 할 때 TOUCH.receive_cplt_sensor Flag만 잘 처리해주면 된다. 만일 데이터가 고정되어 있지 않다면, 문자의 시작부분과 문자의 끝부분을 미리 규약하여 데이터를 받으면 된다.
송신부에 대한 구현은 아래와 같이 진행했다.

//송신부 문자열 송신 구현
. . . 생략 . . .
ISR(USART1_TX_vect){
if(BT.tx1Buf[BT.tx1Cnt]==”) //문자열 마지막부분은 보내지 않는 것이 특징.
{
UCSR1B &= ~0×40; //Tx 송신완료 interrupt disabled.
memset((char*)BT.tx1Buf,0,sizeof(BT.tx1Buf));
}
else UDR1 = BT.tx1Buf[BT.tx1Cnt++]; // 0은 이미 보냈고 1이 보내지고 2로 증가, 2보내지고 3으로 증가되고 다음 루틴에서 인터럽트 비활성화
}
. . . 생략 . . .
void uart1_BT_tx_string(char * data){
int _len=strlen(data); //문자열 길이 반환
strncpy((char*)BT.tx1Buf,data,_len); //데이터를 잠시 버퍼에 저장
while(!(UCSR1A & (1<<UDRE1))); //UDR1레지스터가 비워질 때까지 대기
UDR1=BT.tx1Buf[0]; //버퍼의 첫 주소에 있는 문자 전송
BT.tx1Cnt=1; //ISR상에서 사용할 cnt 시작 값
BT.tx1CntMax=_len+1; //ISR상에서 사용할 cnt max 값 (문자열 뒤에 붙는 도 포함)
UCSR1B |= (1<<TXCIE1);//송신완료 인터럽트 활성화
}

평소에는 송신 완료 인터럽트는 비활성화 되어 있으며, uart1_BT_tx_string() 함수에 문자열을 담아 호출할 경우, 송신 완료 인터럽트가 활성화되어 문자열의 첫 주소의 문자를 송신한 뒤에, 이후에는 ISR에서 데이터를 모두 송신한다. 마지막으로 데이터를 모두 보냈을 경우(NULL을 만났을 경우) 다시 송신 완료 인터럽트를 비활성화 시키는 방식으로 구현을 했다.
아래 3.10 ~ 3.14가 UART 관련 제작 과정이며, 문자열 송, 수신부를 구현한 것으로 계속 사용한다.

3.10. 터치 키패드 센서 테스트
터치 키패드를 이용하는 데 있어 컨트롤러를 이용하면 수월하다. UART로 데이터를 주고받으면 손 쉽게 데이터를 얻어낼 수 있다.
UART0 채널에서 터치 센서 컨트롤러로 ‘R’을 전송하면 컨트롤러에서는 “<ABC>”와 같은 형태의 데이터를 UART0 채널로 송신한다. 아래 그림 17을 보면, ‘A’는 첫번째 열, ‘B’는 두번째 열, ‘C’는 세번째 열을 뜻한다. 수신받은 데이터 중 ‘<’는 데이터의 시작 byte, ‘A’, ’B’, ‘C’의 경우 터치된 데이터이다. ‘>’는 수신받은 데이터의 끝을 알려주는 byte이다.

61 feature 캔유시미 (1)

위 수신 받은 데이터를 parsing해주는 과정은 다음과 같다. 가령, 위 그림에서 6이라고 적은 위치의 키패드가 눌렸다면 그 위치에 Mapping되는 비트는 1이 되며, 아니라면 0이 된다. 즉 ‘B’와 ‘C’는 아무것도 눌리지 않았기 때문에 Binary로 0000 데이터가 들어오며, ‘A’는 Binary로 0100가 들어오는 것으로 해석할 수 있다. 하지만 사용자 입장에서 가독성을 고려하여 데이터를 받을 땐 아스키의 형태로 수신된다. 즉, ‘4’(0×34), ‘0’(0×30), ‘0’(0×30)이라는 문자 형태의 데이터를 송신하여 결국 패킷은 ‘<’, ‘4’, ‘0’, ‘0’, ‘>’. 총 5 Byte가 String 형태로 들어오게 된다.
그림 17에서의 6이라고 적힌 자리를 누르게 되면 아래 그림18과 같은 데이터가 수신된다.

61 feature 캔유시미 (24)

펌웨어에선 20ms마다 센서 데이터를 요청하도록 구현하였으며, 위 그림19와 같이 데이터가 갱신되어 들어오는 것을 확인할 수 있다. (터치 센서가 연결되는 컨트롤러의 메탈 노드를 터치한 사진이다.)

3.11. 터치 데이터 수신 및 파싱
위 3.10에서 진행한 데이터 패킷 수신이 정상적으로 이루어졌으므로, 이제 데이터 패킷을 Parsing하는 과정을 진행해야 한다. 키패드가 눌림에 따라 ‘<’, ‘>’를 제외한 3byte의 데이터를 12byte 배열 각각에 문자 ‘1’ 또는 ‘0’을 넣어주도록 하였다. 20ms마다 데이터를 갱신하며, 키패드가 눌리기 전에는 계속해서 000000000000가 string 형태의 문자열로 반환된다. 키패드가 눌리면 그 곳에 Mapping되는 위치에 해당하는 자리만 1이 되어 “010000000000”과 같은 문자열을 반환한다.
유의해야 하는 점은, “<ABC>” 패킷을 수신할 떄 A,B,C 각각은 Hexadecimal이 아닌 Ascii이다. 즉, 패킷의 인덱스를 그대로 따와서 코드를 작성하면 안되며 매번 비교하여 Hexadecimal로 변환한 값을 이용해야 한다.
이후 해당 값을 통해 원하는 위치를 클릭했을 때 배열에 값이 들어가도록 Parsing을 진행한다.
구현하는데 있어 작성한 코드는 아래와 같다.

int main(void){
. . . 생략 . . .
while (1) {
. . . 생략 . . .
if(TICK.t_1ms>=10){ // check per 20ms
TICK.t_1ms=0;
uart0_sensor_tx_char(‘R’);
if(TOUCH.receive_cplt_sensor){ // 데이터 버퍼에 정상적으로 데이터가 들어왔을 때
TOUCH.receive_cplt_sensor=0;
dataParsing();
}//parsing end
}//tick end
. . . 생략 . . .
}//while end
}
void dataParsing(){
for(int i=0; i<3; i++){
switch(TOUCH.sensorData[i+1]) {
//Ascii Data를 Hexa decimal 데이터로 바꿔주는 과정
case ’0′: TOUCH.DataBuff[i+1]=0×00;
break;
case ’1′: TOUCH.DataBuff[i+1]=0×01;
break;
case ’2′: TOUCH.DataBuff[i+1]=0×02;
break;
case ’3′:TOUCH.DataBuff[i+1]=0×03;
break;
case ’4′:TOUCH.DataBuff[i+1]=0×04;
break;
case ’5′:TOUCH.DataBuff[i+1]=0×05;
break;
case ’6′:TOUCH.DataBuff[i+1]=0×06;
break;
case ’7′:TOUCH.DataBuff[i+1]=0×07;
break;
case ’8′:TOUCH.DataBuff[i+1]=0×08;
break;
case ’9′:TOUCH.DataBuff[i+1]=0×09;
break;
case ‘A’:TOUCH.DataBuff[i+1]=0x0a;
break;
case ‘B’:TOUCH.DataBuff[i+1]=0x0b;
break;
case ‘C’:TOUCH.DataBuff[i+1]=0x0c;
break;
case ‘D’:TOUCH.DataBuff[i+1]=0x0d;
break;
case ‘E’:TOUCH.DataBuff[i+1]=0x0e;
break;
case ‘F’:TOUCH.DataBuff[i+1]=0x0f;
break;
}
}
//16진수로 변환한 데이터를 토대로 패킷 데이터를 Parsing.
TOUCH.KEY[9]=((TOUCH.DataBuff[1]&0b1000)!=0) ?PRESS:RELEASE; //PRESS : ‘1’
TOUCH.KEY[6]=((TOUCH.DataBuff[1]&0b0100)!=0) ?PRESS:RELEASE; //RELEASE : ‘0’
TOUCH.KEY[3]=((TOUCH.DataBuff[1]&0b0010)!=0) ?PRESS:RELEASE;
TOUCH.KEY[0]=((TOUCH.DataBuff[1]&0b0001)!=0) ?PRESS:RELEASE;
TOUCH.KEY[10]=((TOUCH.DataBuff[2]&0b0001)!=0) ?PRESS:RELEASE;
TOUCH.KEY[7]=((TOUCH.DataBuff[2]&0b0010)!=0) ?PRESS:RELEASE;
TOUCH.KEY[4]=((TOUCH.DataBuff[2]&0b1000)!=0) ?PRESS:RELEASE;
TOUCH.KEY[1]=((TOUCH.DataBuff[2]&0b100)!=0) ?PRESS:RELEASE;
TOUCH.KEY[11]=((TOUCH.DataBuff[3]&0b0001)!=0) ?PRESS:RELEASE;
TOUCH.KEY[8]=((TOUCH.DataBuff[3]&0b0010)!=0) ?PRESS:RELEASE;
TOUCH.KEY[5]=((TOUCH.DataBuff[3]&0b0100)!=0) ?PRESS:RELEASE;
TOUCH.KEY[2]=((TOUCH.DataBuff[3]&0b1000)!=0) ?PRESS:RELEASE;
TOUCH.KEY[12]=’\n’; //dummy code (터미널프로그램에서 확인하기 쉽도록)
TOUCH.KEY[13]=0;
UCSR0B |=(1<<RXCIE0);
}

위 함수는 20ms마다 호출되도록 하였으며, 문자열 수신 완료 flag(TOUCH.receive_cplt_sensor)가 활성화된 뒤에 Parsing 작업을 진행한다. (데이터가 충돌하는 것을 방지)
파싱은 크게 두 과정으로, Hexa data로 변환한 뒤에 12byte 버퍼에 저장을 하게 된다. 해당 버퍼를 터미널로 출력해보면 아래와 같다.

61 feature 캔유시미 (25)

이제 그림 20에서의 데이터를 가지고 키 입력을 인식하며, 이를 통해 비밀번호 입력 로직을 구현하면 된다.
진행하기 앞서, 터치 센서는 정전용량식으로 구현된 센서이며, 민감도 조절은 필수적이다.
아크릴 판 안쪽에 터치 센서를 부착할 계획이므로 더욱 민감하게 센서 인식이 되도록 수정해주어야 한다.
이는 MCU에서 처리해주지 않았으며, 터미널 프로그램에서 직접 터치 센서 컨트롤러에 명령을 내려 제어하는 방식으로 진행했다. USB to Serial 케이블로 PC와 센서를 직접 연결하여 진행하면 된다.

61 feature 캔유시미 (26)

Sensitivity는 100이면 가장 둔감하며, 0에 가까울수록 민감해진다. 0의 경우 터치 센서가 커패시턴스 성분에 의해 접근만 해도 터치 인식을 하게 된다. 적당한 사이 값을 찾는 과정을 아래 그림 22와 같이 진행했다.

61 feature 캔유시미 (27)

민감도가 높으면 터치 센서에 닿지 않고 가까워지기만 해도 인식이 되고, 민감도가 낮으면 터치 센서가 아닌 버튼과 다름이 없다. 키보드의‘R 키를 눌러 데이터를 전송받으며 터치 센서 민감도를 설정했다. 민감도를 30으로 설정하여 아크릴 판 위에 놓고 테스트해보니 터치가 원활하게 인식했다.

3.12. 블루투스 모듈 테스트
처음에는 HC-06를 사용하여 구현을 거의 마친 상태에서 갑작스럽게 고장이 났다. (모터에 의한 역전압으로 추정된다. 그래서 레귤레이터를 통해 동일한 전원으로 공급되지 않도록 모터만 분리하였다.) 어쩔 수 없이 수중에 남아있던 HC-06 모듈을 사용했다.
Default 세팅은 – BAUD: 38400bps – 8 Bit, 1 Stop, No Parity이다. PIN: 1234 NAME: HC-06
기존에 Parsing해둔 센서 데이터를 터미널 프로그램에서 확인하지 않고 블루투스 터미널 프로그램으로 확인해보았다.

61 feature 캔유시미 (28)

UART1 채널에 대해 구현해둔 문자열 송신 함수를 사용하여 데이터를 송신해보면 위 그림 23처럼 출력이 된다. (위 그림 23은 HC-05를 사용했을 당시 촬영했던 사진자료이다.)

3.13. 데이터 프로토콜 규약
블루투스와 데이터를 주고받는 데 있어 한 가지 신호만 오가는 것이 아니므로, 데이터에 대한 규약이 필요하다. 본 제품의 경우, 앱에서 UART1채널로 데이터를 보낼 때 특정 id를 규약하여 데이터를 받아냈다.

61 feature 캔유시미 (29)

앱에서 펌웨어로 데이터를 송신하는 경우, 문자를 송신하는데, 1Byte에서 앞 뒤로 4bit씩 쪼개 앞 4bit는 id. 뒤 4bit는 data로 정의하여, (비밀번호 성공, 실패)/(Send버튼 위치)/(Mute버튼 위치)에 대한 데이터들을 모두 구분했다. 펌웨어 단에서 데이터를 수신 받을 때 id로 먼저 어떤 데이터들이 들어오는지 필터링하여 엉뚱한 데이터가 들어오지 않도록 하였다.
앱에서 펌웨어 데이터를 수신하는 경우, 문자열을 수신해야 하는데, 펌웨어 단에서는 (입력한 비밀번호 byte수 +2 byte)만큼을 전송한다. 앱에서는 ‘<’와 ‘>’를 감지하여 그 사이 데이터를 꺼내오도록 규약하였다.

3.14. 비밀번호 자리 값 버퍼 입력 및 전송 로직 구현 (블루투스 문자열 송신)
비밀번호 버퍼 넣는 과정에선 센서 파싱 데이터에서 터치가 발생하는 경우. 특히, 키패드가 눌리는 Rising edge가 발생하는 순간의 문자만 비밀번호 입력으로 인정하도록 코드를 작성해야 한다.
뿐만 아니라 12개의 키패드 중, 2개는 전송버튼과 무음모드 버튼이다. 그러므로 터치에 대한 이벤트가 크게 3가지로 나뉘어 지도록 구현해야 한다.
위와 같은 몇 가지 고려사항을 토대로 코드를 구현하면 되는데, 구현한 코드는 아래와 같다.

int main(void)
{
. . . 생략 . . .
while (1) {
. . . 생략 . . .

for(int i=0; i<12;i++){//각 키 위치마다 버튼 눌림 여부 체크 (Rising edge 체크)
if(TOUCH.KEY[i]==PRESS){ //눌려있는 상태인지 체크
if(pressed_flag[i]==0){ //바로 이전 데이터가 안눌려있던 상태였는지 체크. 맞다면 Rising edge
switch(i){ //해당 반복문 인덱스에 case 접근
//블루투스로부터 Mute버튼과 Send 버튼의 위치를 수신받아
//해당 위치의 경우 음소거진입버튼 혹은 전송버튼으로 사용. 아닌 경우 일반 입력 버튼
case 0: if(BT.MuteBtnLoc==0×00) MuteMode_toggle();
else if(BT.SendBtnLoc==0×00)SendPW();
else ClickSensor(’0′);
break;
case 1: if(BT.MuteBtnLoc==0×01) MuteMode_toggle();
else if(BT.SendBtnLoc==0×01)SendPW();
else ClickSensor(’1′);
break;
case 2: if(BT.MuteBtnLoc==0×02) MuteMode_toggle();
else if(BT.SendBtnLoc==0×02)SendPW();
else ClickSensor(’2′);
break;
case 3: if(BT.MuteBtnLoc==0×03) MuteMode_toggle();
else if(BT.SendBtnLoc==0×03)SendPW();
else ClickSensor(’3′);
break;
case 4: if(BT.MuteBtnLoc==0×04) MuteMode_toggle();
else if(BT.SendBtnLoc==0×04)SendPW();
else ClickSensor(’4′);
break;
case 5: if(BT.MuteBtnLoc==0×05) MuteMode_toggle();
else if(BT.SendBtnLoc==0×05)SendPW();
else ClickSensor(’5′);
break;
case 6: if(BT.MuteBtnLoc==0×06) MuteMode_toggle();
else if(BT.SendBtnLoc==0×06)SendPW();
else ClickSensor(’6′);
break;
case 7: if(BT.MuteBtnLoc==0×07) MuteMode_toggle();
else if(BT.SendBtnLoc==0×07)SendPW();
else ClickSensor(’7′);
break;
case 8: if(BT.MuteBtnLoc==0×08) MuteMode_toggle();
else if(BT.SendBtnLoc==0×08)SendPW();
else ClickSensor(’8′);
break;
case 9: if(BT.MuteBtnLoc==0×09) MuteMode_toggle();
else if(BT.SendBtnLoc==0×09)SendPW();
else ClickSensor(’9′);
break;
case 10: if(BT.MuteBtnLoc==0x0a) MuteMode_toggle();
else if(BT.SendBtnLoc==0x0a)SendPW();
else ClickSensor(‘A’);
break;
case 11: if(BT.MuteBtnLoc==0x0b) MuteMode_toggle();
else if(BT.SendBtnLoc==0x0b) SendPW();
else ClickSensor(‘B’);
break;

} //if(pressed_flag[i]==0) end

pressed_flag[i]=1; //눌려 있는 상태임을 표시. 해당 루프에 다시 접근할 경우 눌려 있는 상태라면
// 위 case 구문에 접근하지 못하도록 Rising edge 신호에서만 처리함.
}
}// if(TOUCH.KEY[i]==PRESS) end
else pressed_flag[i]=0; //버튼을 뗐을 경우에만 다시 Clear하여 재 접근이 가능하도록
} // for(int i=0; i<12;i++) end

. . . 생략 . . .
}// while end
}//main end

Case 내 총 3가지의 이벤트가 존재하며 각각에 대한 함수는 아래와 같다.

//터치 시 비밀번호를 버퍼에 저장하는 함수
void ClickSensor(char number){
TOUCH.PW[pw_i]=(unsigned char)number;
setSoundClip(BUZZ_BEEP);
setStateRGB(RGB_TOUCHED)
pw_i++;
mode_change_stack=0;
}
//터치 시 어플로 전송하는 함수
void SendPW(){

TOUCH.PW[0]=’<’;
TOUCH.PW[pw_i]=’>’;
uart1_BT_tx_string((char *)TOUCH.PW);
memset((char*)TOUCH.PW,0,sizeof(TOUCH.PW));
pw_i=1;
mode_change_stack=0;

}
//더블 클릭 시 음소거모드 <-> 벨소리모드 진입하는 함수
void MuteMode_toggle() //500ms 안에 연속 두번 들어온다면 그때 모드 전환이 이뤄짐.
{

if(TICK.doubleClick_tick_1ms>500) {
mode_change_stack=0;
mode_change_stack++;
clickTimerON_SW=1;
} //평상시, 혹은 0.5초 이후 터치 시도
else mode_change_stack++; //0.5초 내로 추가 터치

if(clickTimerON_SW==1){ //첫 터치 시작 여부 감지
clickTimerON_SW=0;
TICK.doubleClick_tick_1ms=0;
} //start count tick

if(mode_change_stack==2){
if(MuteModeFlag==MUTE_ENABLE){setSoundClip(BUZZ_SILENCE);MuteModeFlag=MUTE_DISABLE;}
else if(MuteModeFlag==MUTE_DISABLE){setSoundClip(BUZZ_BELLIN);MuteModeFlag=MUTE_ENABLE;}
wait_1s_flag=1; //효과음 재생하고나서 무음모드, 벨소리모드 진입.
}
}

입력한 비밀번호를 버퍼에 저장한다. 전송 버튼을 누르면 SendPW()가 호출되어 저장된 비밀번호 앞에 ‘<’를, 뒤에 ‘>’를 넣고 블루투스로 전송한다.
아래 그림 24는 UART 1번 채널의 Tx 라인을 Logic Analyzer로 측정한 결과이다.

61 feature 캔유시미 (30)

데이터가 UART1 채널을 타고 정상적으로 전송되는 것을 확인할 수 있다. 블루투스 터미널에서도 같은 방식으로 진행하면 된다.

3.15. 문 열림 닫힘 신호 감지
잠금이 해제되고 문을 열고나서 다시 닫을 때 모터가 다시 돌아 잠금모드로 복귀해야 한다. 문이 열리고 닫힘을 인식하기 위해선 홀센서와 같이 자기장을 감지하는 센서가 필요했다. 자기적으로 스위칭을 해주는 Reed Switch를 사용하였으며, 자기장에 영향을 받는 상황이면 스위치가 닫히며, 아닌 경우 스위치가 열리게 된다. Pull-up 저항이 달려 있는 모듈을 사용하므로 따로 풀 업 여부는 고려해주지 않아도 됐다.
다만 문제점은, 자기장에 의한 기계적인 스위칭으로 인한 채터링 노이즈를 해결해야 정상적으로 동작한다. 만일 이러한 문제에 대한 케어가 없다면, 작성한 코드 한에서 정상적인 동작을 할 확률이 매우 낮아진다.
코드로 해결하는 방법도 있으며, 회로적으로 해결하는 방법도 있다. 우리의 경우 센서 출력단에 디 커플링 커패시터(10uF 정도 달았음.)를 달아주는 것을 통해 기계적인 채터링 노이즈를 그라운드 쪽으로 바이패스했다.
노이즈는 해결되었으며, 아래는 동작을 위한 코드이다.

void motor_drive()
{//door_open_flag
int reedSW_buff=(PINA&0×20); //문이 열리면 HIGH, 닫히면 LOW
if((reedSW_buff)==0×20) door_open_flag=DOOR_OPEN_STATE;
else if((reedSW_buff)!=0×20)door_open_flag=DOOR_CLOSE_STATE;

if(motor_flag)
{ //문닫혀있는 상태 : DOOR CLOSE STATE > 잠금 풀리고 > 문 열리면 DOOR OPEN STATE
if(door_open_flag==DOOR_OPEN_STATE){ // ____—- LOW ▶ HIGH
reedSW_state=HIGH;
}
//falling edge일 때 잠금모드 진입해야지
else if(door_open_flag==DOOR_CLOSE_STATE) {
if(reedSW_state==HIGH)// —-____ HIGH ▶ LOW일때만
{
reedSW_state=LOW;
command_door_lock=1;
TICK.motor_1ms=0;
//문닫아명령 tick=0부터 이제 3초 세기 시작, 그리고 sleep 타이머도 세팅
}
}
}

if(command_door_lock==1)
{
if(TICK.motor_1ms==1500)setServoAngle(CLOSE); //close the door
else if(TICK.motor_1ms==2000)setSoundClip(BUZZ_CLOSEDOOR);
else if(TICK.motor_1ms==2500)
{
TCCR1A&=~(1<<COM1A1); //모터 PWM 신호 끊음.
command_door_lock=0;
motor_flag=0;
TICK.sleep_1ms=25000; //25초로 세팅하여 5초 뒤에 슬립모드에 빠지도록 설정.
}
}

}

3.16. 회로제작과정

61 feature 캔유시미 (31) 61 feature 캔유시미 (32)

3.17. 제품제작과정

 61 feature 캔유시미 (2) 61 feature 캔유시미 (3)

61 feature 캔유시미 (33) 61 feature 캔유시미 (34)

4. 기타
4.1. 회로도 (PSpice)

61 feature 캔유시미 (35)

61 feature 캔유시미 (36)

4-2. 안드로이드 코드

4-3. MCU 펌웨어 코드

5. 고찰
5.1. 저전력 관련 문제
프로젝트 초기에 하드웨어 설계 중, 부품 선정에 있어 MCU 및 모듈 등에 대한 선정에 부족함이 있었던 것 같다. 블루투스 모듈의 경우 MCU가 슬립모드 상태가 돼도 계속 활동해야 했는데, 본 제품의 경우 슬립모드가 되면 블루투스 모듈 또한 전원 공급을 차단했다. 대기 전류가 너무 많이 흘렀기 때문이다. BLE 타입의 모듈을 사용하여 대기 전력은 매우 낮은 상태를 유지하도록 해야 하는 것이 상황에 가장 적절한 것 같다. 전원 스위칭하는 목적으로 릴레이 모듈을 사용했는데, 이 또한 목적에 적합하지 않은 선택이었다고 생각한다. 릴레이 모듈은 고전력 회로를 스위칭하는 용도가 주된 이유이지만 해당 제품에선 사용할 이유가 없다고 생각했다. 차라리 PNP BJT를 이용하여 스위칭을 했다면 적합했을 것 같다.
레귤레이터는 LM2576-3.3 스위칭 레귤레이터를 사용했으며, 리니어 레귤레이터에 비해 전력 소모가 적다는 이유에서 사용하였다. 처음에는 5V 출력인 레귤레이터를 사용했지만, 전력을 소모하는 대부분의 모듈이(Atmega128, Touch Controller, Reed SW, Relay SW, BlueTooth Module, RGB, BUZZ) 3.3V로 동작하는 것으로 확인하여 전력 절약을 목적으로 3.3V로 바꾸게 되었다.
하지만 레귤레이터만을 연결하여 소모하는 전류를 측정해본 결과, 10mA정도가 흐르는 것으로 확인하였다. 사실상 저전력 제품에서 10mA가 계속 흐른다는 것은 인정될 수 없는 것이라고 생각한다. 뿐만 아니라 본 제품에서 사용하고 있는 터치센서 또한 슬립모드에서 활성화되어 있기 때문에 컨트롤러 자체에서 10mA를 소모하여, 도합 20mA가 슬립모드에서 흐르고 있는 것을 확인되었다.
이것에 대한 해결책은 다음과 같다.
레귤레이터의 경우 ‘Low Quiescent Current LDO’를 찾아볼 것. ▶ST730의 경우 3.3V 출력에 최대 300mA까지 출력함. 우리 제품의 경우 서보모터의 Stall Current를 배제하면 300mA는 인정 범위에 들어올 것으로 생각이 된다. 다만, 해당 LDO는 DIP타입이 아니기 때문에 우리와 같이 만능기판에 납땜하여 만들기는 힘들다. PCB로 제작할 기회가 생긴다면 제품 크기 축소화와 동시에 전력 감축까지 가능할 것 같다는 생각이 들었다.
3.3V로 낮춰주면서 발생하는 문제는 Atmega128에서는 3.3V에서 16MHz 클럭을 보장해주지 못한다는 데이터시트에서의 말이 있다. Overclock으로 통신에 있어 큰 문제가 생길 수도 있다는 말로 해석을 했다. 이에 대한 해결책으로는 아래 그래프를 보면 금방 알 수 있다.

61 feature 캔유시미 (37)

결론적으로는 외부 크리스탈을 8MHz로 교체하면 된다. 일석이조로 클럭에 대한 안정화를 얻어감과 동시에 클럭을 낮춤으로 동작 전력을 낮출 수 있다는 장점까지 얻어간다. 본 제품의 경우 대부분의 Peripheral들에 대해 클럭을 분주해서 사용하므로, 16MHz 속도의 퍼포먼스까지는 필요 없다. 8MHz로 교체함으로 두 가지 장점을 가져왔다.
마지막으로, 저전력 회로를 설계함에 있어 사용하지 않는 모든 핀들 PULL UP 시켜주어야 한다. 따로 설정되지 않은 상태라면 쓸모 없는 누설전류가 흐를 가능성이 생겨 전력 소모가 발생할 수 있다.

6. 참고 문헌
· Atmega128 Data sheet (ATMEL): Unconnected pins (p70)
· Atmega128 Data sheet (ATMEL): Typical Characteristics (p333)
· Atmega128 Data sheet (ATMEL): Electrical Characteristics-Speed Grades (p320)
· https://kogun.tistory.com/33
· https://m.blog.naver.com/PostView.nhn?blogId=ansdbtls4067&logNo=220640400445&proxyReferer=https%3A%2F%2Fwww.google.com%2F
· https://binworld.kr/36
· https://miobot.tistory.com/23
· http://www.makeshare.org/bbs/board.php?bo_table=arduinomotor&wr_id=5
· https://blog.naver.com/6k5tvb/120055584295
· https://mjk90123.tistory.com/22

 

 

 

 

[61호]파파고 API를 이용한 점자 번역기

61 점자번역기 썸네일

Cap 2020-10-23 16-55-48-488

2020 ICT 융합 프로젝트 공모전 최우수상

파파고 API를 이용한 점자 번역기

글 | 국민대학교 이동윤

1. 심사평
칩센 점자 변환 장치는 다수 본 경험이 있지만, 이 작품처럼 번역이 가능한 장치는 처음 접하는 듯 합니다. 온전히 새로운 것을 만드는 것도 대단하지만, 작품과 같이 기존의 보유 시스템의 융합을 통해 새로운 것을 내보이는 것 또한 굉장한 것으로 생각합니다. 사용 대상자가 될 시각 장애인이 인지 가능한 단위로 점자판의 요철을 촘촘히 가져간다면 충분히 멋진 제품으로 진행이 가능할 것으로 보입니다.
펌테크 작품의 아이디어와 창의성이 돋보이며 추후 작품 완성도를 높일 경우 실제 시각장애인을 위한 의사소통 장치로도 충분히 활용이 가능할 작품이라고 생각합니다. 작품의 기획의도, 기술 구현도, 완성도 등에서 우수한 작품으로 생각됩니다.
위드로봇 높은 완성도를 보이는 작품입니다. 점자를 표시하는 부분은 실제 시각 장애인이 사용하기에는 부족하지만 별도의 햅틱 장치와 연동이 된다면 더욱 완성도가 올라갈 것으로 보입니다.

2. 작품 개요
2.1. 제작동기
시각장애인이 세상과 소통하면서 가장 의존하고 있는 수단이 점자이다. 그로 인해 많은 책들이 점자책으로 출판되기도 했고 시각장애인을 위해 점자 키보드가 나오기도 하였다.
시각장애인을 위해 점자를 활용한 다양한 제품이 발명되고 출시되고 있지만, 아직 비장애인들과 같은 편리함을 느끼지 못하는 것이 현실이다. 특히 영어 점자와 한글 점자의 체계가 다르기 때문에 영어 점자에 대해 따로 배우지 않는다면 전혀 다른 의미로 해석하는 등 많은 불편함이 존재했다.
그래서 영어 점자로 만들어진 점자를 한글 점자로 바꾸는 프로젝트를 구상하게 되었다.
우리나라의 시각장애인의 수는 2018년 통계청에서 발표한 자료에 따르면 252,957명에 이른다. 하지만 시각장애인에 대한 교육방식은 점자책과 오디오를 통해 이루어지고 있다. 특히 언어교육에서 영어점자와 한글점자의 체계가 다르기 때문에 비장애인과는 다르게 영어점자와 영어를 함께 배워야하는 이중고를 겪어 많은 시각장애인들이 어려움을 겪고 있다.
더욱 심각한 것은 대학이나 대학원 과정의 점자책은 소설이나 교양서적에 비하여 턱없이 부족하다는 것이다. 또한 전공서적을 점자책으로 만들기 위해서는 3~4개월의 시간이 들기 때문에 시각장애인의 향학열을 꺾고 있다.

Cap 2020-10-23 16-56-03-180

이러한 상황에서 비교적 우리나라 보다 점자책의 폭이 넓고 수요가 많은 외국서적을 직접 번역해 주는 점자 번역기는 시각장애인들의 전문지식을 넓히는 데 도움이 될 것이라고 생각한다.
또한 점자 번역기는 학업에만 적용되지 않고 다양한 분야에서 적용될 것으로 보인다.
현재 여행객들을 위한 번역기나 스마트폰 애플리케이션이 많이 개발되고 있다. 반면에 시각장애인들은 소리로 번역된 언어를 듣는 방법 외에는 대안이 없다. 하지만 표지판이나 손잡이 심지어 화장실에도 점자로 표시되어 있어 소리로만 모든 정보를 얻는 데 어려움이 있다. 이러한 상황에서 점자 번역기를 사용한다면 비장애인들의 도움 없이 여행이 가능할 것으로 보인다.

2.2. 관련 특허 분석

Cap 2020-10-23 16-56-09-717

본 발명은 점자를 변환하여 출력하는 장치에 있어서, 사용자의 조작에 의 해 한국어 단어/문장에 해당하는 키 또는 명령을 입력받은 후 한국어 점자 패턴을 생성하는 점자 입력부; 오디오 데이터를 가청음으로 변환하여 출력하는 오디오 출력부; 및 상기 장치의 전반적인 동작을 제어하는 제어 수단으로서, 상기 점자 입력부로부터 상기 한국어 점자 패턴을 입력받아 인식한 후 상기 한국어 특정 단어/문장으로 변환하며, 상기 한국어 특정 단어/문장을 기 설정된 외국어로 번역하여 외국어 단어/문장을 생성하고, 상기 외국어 단어/문장을 외국어 오디오 데이터 변환한 후 상기 오디오 출력부로 출력하도록 제어하는 제어부를 포함하는 것을 특징으로 하는 한글에 대한 점자를 외국어로 번역하여 오디오로 출력하는 장치를 제공한다.

Cap 2020-10-23 16-56-16-429

본 발명은 지능형 점자 번역 장치에 관한 것이다. 보다 상세하게는 시각 및 청각장애인들이 쉽고 편리한 점자문자사용을 할 수 있도록 지능형 점자 번역 장치를 제공하여 점자를 스캔하는 경우, 점자를 인식한 후 점자를 DB에 저장된 점자정보와 매칭하여 음성으로 변환하여 출력함으로써, 시각 및 청각장애인들이 보다 편리하게 점자정보를 읽을 수 있도록 하는 지능형 점자 번역 장치에 관한 것이다.

3. 작품 설명
3.1. 주요 동작 및 특징
· 영문점자를 카메라로 영상을 받아 노트북으로 전송한다.

Cap 2020-10-23 16-56-21-328

· 노트북에서 영상처리를 통해 영문점자를 영문으로 변환한다.
· 파파고 API를 이용해 영문을 한글로 번역한다.
· 한글을 각각의 자모에 맞게 변환을 한다.
· 변환한 데이터를 노트북에서 아두이노로 시리얼통신을 이용해 전송한다.
· 전송받은 데이터를 이용해 아두이노가 서보모터를 제어한다.

영상처리에서의 점자 판독부
· 요철확인 : 점자 명도영상 획득, 잡음제거, 점자판독
· 좌표 가로줄 정렬 : 컨투어, 컨투어 좌표 중심값, 가로줄 정렬 및 기울기 교정
· 점자판별 : 세로줄 정렬 및 근사화, 6점 단위 구성

영상처리 결과

Cap 2020-10-23 16-56-28-445

3.2. 전체 시스템 구성
3.2.1. 제어부
카카오 api를 이용하여 점자를 해석한 후 이를 pc가 시리얼 통신으로 아두이노에 정보를 넘겨준다. 아두이노에서 수신된 값에 해당하는 소모터를 동작시킴으로써 점자를 표기한다.
3d 모델링을 통해 서보모터로 요철을 표현하였다.

Cap 2020-10-23 16-56-33-563

3.2.2. 작품 결과

Cap 2020-10-23 16-56-40-328

3.2.3. 기능 및 제한 사항
· 테블릿의 영문 점자를 한글 점자로 변환하여 물리적으로 표현할 수 있다.
· 한 번에 카메라에 들어오는 영상의 제한이 있기 때문에 문장 단위로 해석이 가능하다.

Cap 2020-10-23 16-56-54-110

3.2.4. 개발환경
· Python , C 언어 사용
· Arduino : 점자 표현
· Visual: 기본적인 영상처리 툴
· PyCham: 파이선 에디터
· pyqt5 desinger: UI 제작 툴
· inventor :3D 모델링
· cura : 3D 모델 출력
· 사용 노트북 사양 : CPU : i5-7200, RAM : 8GB, OS : Windows

4. 단계별 제작과정
4.1. 일자별
2019년 11월 14일 : 영상처리를 이용하여 사회적으로 많은 도움이 될 작품을 구상했다. 영상처리 기술을 사용하여 고가의 점자 번역기를 저렴하게 구현하기로 했다. 파이썬 기반으로 영상처리를 하고 아두이노와 시리얼 통신을 하기로 했다.
2019년 11월 21일 : 조사한 내용을 바탕으로 실제 작품을 구현할 수 있는지 Flow Chart를 만들어 보았다. 파파고 API를 노트북과 연동하였다.
2019년 11월 26일 : 시리얼 통신을 할 때, 아스키코드를 이용하여 상황별 분류를 좀 더 간단히 하였다.
2019년 11월 28일 : 3D 프린터를 이용하여 하드웨어를 제작했다. 하드웨어는 고장 시, 점검에 용의하도록 개패식으로 제작하였다. 수월한 영상처리가 가능하게 하도록 UI에서 ROI가 보이도록 수정했다.
2019년 11월 30일 : 사람이 손으로 점자를 인식하기 좋게 모터 값을 조절했다.
2019년 12월 3일 : 영상처리 시, 점과 점 사이의 간격을 계산하는 기준 값을 조절하여 영어 번역을 할 때 띄어쓰기가 되지 않는 문제를 해결했다. 최적화를 위해 한글 점자에서 사용하지 않는 자음 ‘ㅇ’는 생략하도록 데이터를 처리했다.

4.2. 제작 고려요소 및 변경사항
· 원가 : 기존의 점자 정보 단말기는 450만원을 호가할 정도로 비싸다. 이를 비교적 저렴한 MCU와 모터를 사용하고 영상처리 기술을 이용하여 경제성이 있는 결과물을 향상시켰다.
· 신뢰성 : 점자를 인식하기 쉽게 점자 블록의 튀어나오는 정도를 조절했다. 각 서보모터의 내구도와 성능의 차이가 있었기 때문에 서보모터의 각도를 모터마다 설정해주었다.
· 하드웨어를 만들 때 3D프린터를 사용하여 좀 더 정밀하고 깔끔한 외관을 만들었다.

Cap 2020-10-23 17-04-40-920

· 노트북 전원으로 아두이노와 6개의 서보모터를 동작할 때, 전력이 부족할 것을 우려했지만 동작하는데 무리가 없어 별도의 전원장치는 사용하지 않기로 했다.
· 영어 점자를 해석하는 과정에서 띄어쓰기한 부분이 인식이 잘 되지 않아 띄어쓰기의 여부를 결정하는 기준 값을 조절하여 띄어쓰기 인식률을 높혔다.

Cap 2020-10-23 17-04-50-571

· 알고리즘의 속도를 높이기 위해서 불필요한 형변환 부분을 삭제하였다.

Cap 2020-10-23 17-04-56-553

· 점자에서 자음 ‘ㅇ’은 존재하지 않기 때문에 이를 무시하는 방향으로 알고리즘을 수정했다.

elif(korean_to_be_englished(response_text [cnt:cnt+1]).pop(O).pop(pp)==’o’):
print(‘넘기기’)

5. 기타
5.1. 아두이노 코드
5.1.1. 셋업부

void setup() {
Serial.begin(9600); // 보오레이트를 9600으로 설정한다.

myservo2.attach(2); // 서보ㅅ모터를 동작할 수 있게 설정한다. 2,3,4,5,6,7 같은 방식으로 지정
ser2(1); // 서보모터의 각도를 정해주는 함수이다.
pinMode(8,OUTPUT); // 셋업이 되었으면 8번 핀에 빛을 낸다.
}

5.1.2. 서보모터 각도 설정부

void ser2(int a){
if(a==0)
myservo2.write(100); // low값이 들어오면 서보모터의 각도를 100으로 설정한다.
else
myservo2.write(75); // high값이 들어오면 서보모터의 각도를 75으로 설정한다.
}// 위와 같은 방식으로 나머지 서보모터도 지정

5.1.3. 동작부

void loop() {
if(Serial.available()) // 시리얼 통신의 신호가 들어오면 동작한다.
{
int swt = (int)Serial.read(); // swt 변수에 시리얼 통신으로 수신된 값을 저장한다.
if(swt==97) // 수신된 값이 ‘a’라면 아래와 같이 서보모터를 동작한다.
{

ser2(0);
ser3(1);
ser4(0);
ser5(0);
ser6(0);
ser7(0);
}

// 이후 수신된 값에 따라 서보모터를 동작하게 한다.

}

5.2. Python 코드
5.2.1. 영상처리부

img_gray = cv.cvtColor(img_color, cv.COLOR_BGR2GRAY) #gray 이미지 변경
ret, img_binary = cv.threshold(img_gray, 127, 255, 0) #이미지 이진화
Contours, hierarchy = cv.findContours(img_binary, cv.RETR_LIST, cv.CHAIN_APPROX_SIMPLE) #컨투어 찾기
Str=[] Contours.pop()
For cnt in contours:
cv.drawContours(img_color, [cnt], 0, (255, 0, 0), 3) #컨투어 그리기 blue

times=0
STACK=[] #location _stack
x_dot=[] #x _location stack
y_dot=[] #y _location stack
for cnt in contours:

M = cv.moments(cnt) #중심모멘트
cx = int(M['m10']/M['m00'])
cy = int(M['m01']/M['m00'])
STACK.append([cx,cy])
x_dot.append(cx) #x 중심 모멘트
y_dot.append(cy) #y 중심 모멘트
cv.circle(img_color, (cx, cy), 10, (0,0,255), -1)
times=times+1

y_max=STACK[len(STACK)-1][1] y_min=STACK[0][1] y_gap=y_max-y_min # 판단을 위한 거리측정
y_avg=int(y_max+y_min)/2
x_dot=list(set(x_dot))
x_dot.sort()
no_need_x=[] change_x=[] x_gap=int(y_gap/2) #x_gap 구함
last_gap=int(y_gap*10/12) #단어 사리 거리 구함

for count in range(0 ,len(x_dot)) : #X 좌표 보정
if x_dot[count]-x_dot[count-1]<x_gap/3 and count>0 : # 차이 얼마 안나는 x값
no_need_x.append(x_dot[count]) # 대체 해 줄 x값 저장
change_x.append(x_dot[count-1])
if count+1<len(x_dot)-2 :
if x_dot[count+1]-x_dot[count-1]<x_gap/3 :
no_need_x.append(x_dot[count+1])
change_x.append(x_dot[count-1])

y_top=[] y_middle=[] y_bottom=[]

for count in range(0 ,times) : #Y 값에 따라 X 좌표 저장
if STACK[count-1][1]<y_avg-y_gap/4: #TOP
if no_need_x.count(STACK[count-1][0])>0:
location=no_need_x.index(STACK[count-1][0])
y_top.append(change_x[location])
if x_dot.count(STACK[count-1][0])>0:
x_dot.remove(STACK[count-1][0])
else :
y_top.append(STACK[count-1][0])
elif STACK[count-1][1]>y_avg+y_gap/4: #BOTTOM
if no_need_x.count(STACK[count-1][0])>0:
location=no_need_x.index(STACK[count-1][0])
y_bottom.append(change_x[location])
if x_dot.count(STACK[count-1][0])>0:
x_dot.remove(STACK[count-1][0])
else :
y_bottom.append(STACK[count-1][0])
else :
if no_need_x.count(STACK[count-1][0])>0: #MIDDLE
location=no_need_x.index(STACK[count-1][0])
y_middle.append(change_x[location])
if x_dot.count(STACK[count-1][0])>0:
x_dot.remove(STACK[count-1][0])
else :
y_middle.append(STACK[count-1][0])

for count in range(1 ,len(x_dot)) : # X값들 사이의 거리를
if x_dot[count-1]<x_gap: #단어 시작점 구함 first_line.append(x_dot[count-1])
cv.rectangle(img_color,(int(x_dot[count-1]),int(y_min)),(int(x_dot[count-1]+x_gap),int(y_max)),(255,0,0),1)
elif x_dot[count]-x_dot[count-1]<(last_gap+x_gap)/2:
first_line.append(x_dot[count-1])
cv.rectangle(img_color,(int(x_dot[count-1]),int(y_min)),(int(x_dot[count]),y_max),(255,0,0),1)
elif x_dot[count]-x_dot[count-1]<last_gap+(x_gap)/2:
first_line.append(x_dot[count])
cv.rectangle(img_color,(int(x_dot[count]),int(y_min)),(int(x_dot[count]+x_gap),int(y_max)),(255,0,0),1)
cv.rectangle(img_color,(int(x_dot[count-1]-x_gap),int(y_min)),(int(x_dot[count-1]),int(y_max)),(255,0,0),1)
elif (first_line.count(x_dot[count-1])>0) and (x_dot[count]-x_dot[count-1] <last_gap+3*(x_gap)/2):
first_line.append(x_dot[count])
cv.rectangle(img_color,(int(x_dot[count]),int(y_min)),(int(x_dot[count]+x_gap),int(y_max)),(255,0,0),1)

first_line=list(set(first_line))
first_line.sort()

second_line=set(x_dot)-set(first_line) #시작 줄 아니면 2번째 줄
second_line=list(second_line)
second_line.sort()

for count in range(0 ,len(first_line)) :
if count==0 :
if(first_line[count+1]>second_line[index_second]):
number=int(y_top.count(first_line[count]))*100000+int(y_top.count(second_line[index_second]))
*10000+int(y_middle.count(first_line[count]))*1000+int(y_middle.count(second_line[index_second]))
*100+int(y_bottom.count(first_line[count]))*10+int(y_bottom.count(second_line[index_second]))
dot_num.append(number)
index_second+=1
else :
number=int(y_top.count(first_line[count]))*100000+int(y_middle.count(first_line[count]))
*1000+int(y_bottom.count(first_line[count]))*10
dot_num.append(number)
elif( count< len(first_line)-1):
if (first_line[count]-first_line[count-1]>+x_gap*2+last_gap*2) :
dot_num.append(-1)
if(first_line[count+1]>second_line[index_second]):
number=int(y_top.count(first_line[count]))*100000+int(y_top.count(second_line[index_second]))
*10000+
int(y_middle.count(first_line[count]))*1000+int(y_middle.count(second_line[index_second]))
*100+int(y_bottom.count(first_line[count]))*10+int(y_bottom.count(second_line[index_second]))
dot_num.append(number)
index_second+=1
else :
number=int(y_top.count(first_line[count]))*100000+int(y_middle.count(first_line[count]))
*1000+int(y_bottom.count(first_line[count]))*10
dot_num.append(number)
else :
last_in=x_dot.pop()
if(last_in==first_line[count]) :
number=int(y_top.count(first_line[count]))*100000+int(y_middle.count(first_line[count]))
*1000+int(y_bottom.count(first_line[count]))*10
dot_num.append(number)
else :
number=int(y_top.count(first_line[count]))*100000+int(y_top.count(second_line[index_second]))
*10000+int(y_middle.count(first_line[count]))*1000+int(y_middle.count(second_line[index_second]))
*100+int(y_bottom.count(first_line[count]))*10+int(y_bottom.count(second_line[index_second]))
dot_num.append(number)
index_second+=1
Alpa=[100000,101000,110000,110100,100100,111000,11100,101100,11000,11100,100010,101010,110010,110110,
100110,111010,111110,101110,11010,11110,100011,101011,11101,110011,110111,100111] sen_str=[] for count_dot_num in range(0,len(dot_num)):
for count_alpa in range(0 ,len(Alpa)) :
if dot_num[count_dot_num]==Alpa[count_alpa]:
dot_num[count_dot_num]=Alpa.index(Alpa[count_alpa])
if(dot_num[count_dot_num]==-1) :
sen_str.append(‘ ‘)
elif(dot_num[count_dot_num]==0) :
sen_str.append(‘a’)
elif(dot_num[count_dot_num]==1) :
sen_str.append(‘b’)
elif(dot_num[count_dot_num]==2) :
sen_str.append(‘c’)
elif(dot_num[count_dot_num]==3) :
sen_str.append(‘d’)

5.3. 동작부

/* 초성,중성,종성의 리스트를 만들어준다. */
CHOSUNG_LIST = ['ㄱ', 'ㄲ', 'ㄴ', 'ㄷ', 'ㄸ', 'ㄹ', 'ㅁ', 'ㅂ', 'ㅃ', 'ㅅ', 'ㅆ', 'ㅇ', 'ㅈ', 'ㅉ', 'ㅊ', 'ㅋ', 'ㅌ', 'ㅍ', 'ㅎ'] JUNGSUNG_LIST = ['ㅏ', 'ㅐ', 'ㅑ', 'ㅒ', 'ㅓ', 'ㅔ', 'ㅕ', 'ㅖ', 'ㅗ', 'ㅘ', 'ㅙ', 'ㅚ', 'ㅛ', 'ㅜ', 'ㅝ', 'ㅞ', 'ㅟ', 'ㅠ', 'ㅡ', 'ㅢ', 'ㅣ'] JONGSUNG_LIST = [' ', 'ㄱ', 'ㄲ', 'ㄳ', 'ㄴ', 'ㄵ', 'ㄶ', 'ㄷ', 'ㄹ', 'ㄺ', 'ㄻ', 'ㄼ', 'ㄽ', 'ㄾ', 'ㄿ', 'ㅀ', 'ㅁ', 'ㅂ', 'ㅄ', 'ㅅ', 'ㅆ', 'ㅇ', 'ㅈ', 'ㅊ', 'ㅋ', 'ㅌ', 'ㅍ', 'ㅎ']

def korean_to_be_englished(korean_word): // 번역함수입니다.
r_lst = [] for w in list(korean_word.strip()):
## 영어인 경우 구분해서 작성함.
if ‘가’<=w<=’힣’:
## 588개 마다 초성이 바뀜.
ch1 = (ord(w) – ord(‘가’))//588
## 중성은 총 28가지 종류
ch2 = ((ord(w) – ord(‘가’)) – (588*ch1)) // 28
ch3 = (ord(w) – ord(‘가’)) – (588*ch1) – 28*ch2
r_lst.append([CHOSUNG_LIST[ch1], JUNGSUNG_LIST[ch2], JONGSUNG_LIST[ch3]])
else:
r_lst.append([w])
return r_lst

PORT = ‘COM6′ // 포트를 설정해줍니다.
BaudRate = 9600 // 보오레이트를 9600으로 설정합니다.
ARD= serial.Serial(PORT,BaudRate) // ARD를 시리얼통신 변수로 설정한다.
client_id = “P6ynPpUq#######” // NAVER API의 ID를 입력해준다.
client_secret = “g##########0″ // NAVER API의 PassWard를 입력해준다.

/* Naver API로 영어 문자를 전송하고 변역한다. */
encText = urllib.parse.quote(str_alpa)
data = “source=en&target=ko&text=” + encText
url = “https://openapi.naver.com/v1/papago/n2mt”
request = urllib.request.Request(url)
request.add_header(“X-Naver-Client-Id”,client_id) request.add_header(“X-Naver-Client-Secret”,client_secret)
response = urllib.request.urlopen(request, data=data.encode(“utf-8″))
rescode = response.getcode()

/* 번역된 수신값에 필요한 값만 뽑아낸다. */
if(rescode==200):
response_body = response.read()
response_text = response_body.decode(‘utf-8′)
response_text = json.loads(response_text)
response_text = response_text['message']['result']['translatedText'] print(response_text)
t = 0
cnt = 0
pp = 0
send = ‘a’
while(t == 0):
pp = 0
tim = 2
/* 번역된 값이 필요없는 값이면 넘겨준다 */
if(response_text[cnt:cnt+1]==’ ‘):
print(‘넘기기’)
elif(response_text[cnt:cnt+1]==’,'):
print(‘넘기기’)
elif(response_text[cnt:cnt+1]==’.'):
print(‘끝’)
t=1
elif(response_text[cnt:cnt+1]==’!'):
print(‘끝’)
t=1
else:
/* 이후 뽑아진 초성 값에 따라 정해진 알파벳을 전송한다. */
if(korean_to_be_englished(response_text[cnt:cnt+1]).pop(0).pop(0)==’ ‘):
pp+=1
print(‘넘기기’)

elif(korean_to_be_englished(response_text[cnt:cnt+1]).pop(0).pop(0)==”):
pp+=1
print(‘넘기기’)
elif(korean_to_be_englished(response_text[cnt:cnt+1]).pop(0).pop(pp)==’ㅇ’):
pp+=1
print(‘넘기기’)

else:
print(korean_to_be_englished(response_text[cnt:cnt+1]).pop(0).pop(pp))

if(korean_to_be_englished(response_text[cnt:cnt+1]).pop(0).pop(pp)==’ㄱ’):
send = ‘a’
pp+=1

time.sleep(2) // 2초의 delay를 넣는다.
Trans = send
#Trans = str(Trans)
Trans = Trans.encode(‘utf-8′)
ARD.write(Trans) // utf-8로 인코딩하여 전송한다.
/* 이후 뽑아진 종성 값에 따라 정해진 알파벳을 전송한다. */ if(korean_to_be_englished(response_text[cnt:cnt+1]).pop(0).pop(pp)==”):
pp+=1
print(‘넘기기’)
else:
print(korean_to_be_englished(response_text[cnt:cnt+1]).pop(0).pop(pp))

time.sleep(2)
Trans = send
#Trans = str(Trans)
Trans = Trans.encode(‘utf-8′)
ARD.write(Trans)
/* 이후 뽑아진 종성 값에 따라 정해진 알파벳을 전송한다. */
if(korean_to_be_englished(response_text[cnt:cnt+1]).pop(0).pop(pp)==”):
pp+=1

else:
print(korean_to_be_englished(response_text[cnt:cnt+1]).pop(0).pop(pp))
if(korean_to_be_englished(response_text[cnt:cnt+1]).pop(0).pop(pp)==’ㄱ’):
send = ‘o’
#pp+=1

time.sleep(tim)
Trans = send
#Trans = str(Trans)
Trans = Trans.encode(‘utf-8′)
ARD.write(Trans)

cnt+=1

else:
print(“Error Code:” + rescode)