December 27, 2024

디바이스마트 미디어:

[66호] 원하는 색상으로 제어가 가능한 아두이노 IoT 스마트 무드등 키트 -

2021-06-25

★2021 ICT 융합 프로젝트 공모전 결과 발표! -

2021-05-12

디바이스마트 국내 온라인 유통사 유일 벨로다인 라이다 공급! -

2021-02-16

★총 상금 500만원 /2021 ICT 융합 프로젝트 공모전★ -

2021-01-18

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

2020-09-29

[61호]음성으로 제어하는 간접등 만들기 -

2020-08-26

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

2020-08-10

GGM AC모터 대량등록! -

2020-07-10

[60호]초소형 레이더 MDR, 어떻게 제어하고 활용하나 -

2020-06-30

[60호]NANO 33 IoT보드를 활용한 블루투스 수평계 만들기 -

2020-06-30

라즈베리파이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

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

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

 

 

 

 

Leave A Comment

*