반응형

아두이노와 키패드, LED, 피에조부저, 서보모터, keypad.h 라이브러리와 EEPROM 라이브러리를 활용하여 디지털 도어락을 구현해 보겠다. 디지털 도어락 코드는 delay()함수의 문제점을 이해하는데 좋은 예제라 생각한다. 

 

앞으로 구현할 디지털 도어락의 작동 개요 및 구성요소를 살펴보자.

 

작동개요(실생활에 사용하는 디지털 도어락의 작동 상태)

1. 초기 비밀번호가 설정되어 있다.

2. 사용자 비밀번호를 입력하여 저장할 수 있다. 

3. 밧데리 교체등 전원을 껐다 켜도 입력된 사용자 비밀번호는 유지된다. 

4. 비밀 번호가 맞게 입력될 경우 문이 열리는 작동과 그 작동을 작동음과 램프로 표시 한다.

5. 비밀 번호가 틀리게 입력될 경우 사용자 인지를 위해 경고음과 램프로 표시한다.  

6. 입력이 잘못될 경우 입력을 지우고 처음부터 입력할 수 있다.

7. 사용자 비밀번호 변경중 변경작업을 종료할 수 있다. 

8. 상기의 작업을 키패드를 통해 제어할 수 있다. 

 

비밀번호 변경방법

1. 초기 비밀번호 또는 사용자 비밀번호를 입력하여 열림상태로 한다.

2. 열림 상태에서 #버튼을 2초간 누른다. (비밀번호 설정 코드로 진입된다)

3. 사용자 비밀번호를 입력하고 #버튼을 눌러 저장한다.

4. 입력이 잘못된 경우 *를 누르면 입력된 값이 지워진다. 

5. 사용자 비밀번호 입력을 취소할 경우 *를 2초간 누른다.(비밀번호 설정 코드를 빠져나간다)

 

작동상태 표시

1. 열림: 빨간색 -> 노란색 -> 파란색 LED 순으로 한번씩 점멸하고, "도 -> 레 -> 미" 열림음이 출력된다. 

2. 사용자 비밀번호 입력: 빨간색, 노란색, 파란색 LED가 동시 점멸하며 입력 완료시 까지 계속된다. .

3. 비밀번호 잘못 입력한 경우: 경고음 1회 발생,  빨간색, 노란색, 파란색 LED가 한번만 동시 점멸

 

위의 기능을 수행하기 위해서는 피에조부저와 LED(빨, 노, 파) 3개, 키패드, 서버모터 가 필요하고 LED용 저항은 필요에 따라 적용하면 된다. 

 

디지털 도어락 구성 및 결선

키패드 R4 - > 아두이노 3번핀

키패드 R3 - > 아두이노 4번핀

키패드 R2 - > 아두이노 5번핀

키패드 R1 - > 아두이노 6번핀

키패드 C1 - > 아두이노 7번핀

키패드 C2 - > 아두이노 8번핀

키패드 C3 - > 아두이노 9번핀   //  키패드 C4 핀은 사용하지 않는다.

서보모터 제어선(노란색) -> 아두이노 10번핀

수동형 피에조 부저 '+' -> 아두이노 11번핀 (수동형 피에조 부저는 밑면에 기판이 보인다)

파란색 LED -> 아두이노 2번핀

노란색 LED -> 아두이노 12번핀

빨간색 LED -> 아두이노 13번핀

 

 

 

 

 

아두이노에서 키패드를 쉽게 사용하기 위해 keypad.h라이브러리를 이용하기로 하자

 

https://github.com/Chris--A/Keypad

 

Keypad.zip
다운로드

 

첨부된 Keypad.zip 파일을 다운로드 받은뒤 압축을 해제하고 keypad 폴더를 아래의 아두이노 라이브러리 폴더에 저장 시킨다.

 

 

 

아두이노 IDE가 실행되어 있다면 종료시키고 다시 실행하여 라이브러리가 등록되도록 한다.

 

 

새로 아두이노 IDE를 실행시키고 예제 -> 모든 보드의 예제 항목 Keypad -> EventKeypad를 불러온다. 

 

 

 

 

아래 코드는 예제 EventKeypad를 단순화 시켜놓은 것이며, 2초동안 누르는 키동작을 위해 키 홀드 시간 설정 옵션을 추가하였다. 

 

keypad.setHoldTime(2000);  

 

참조사이트

https://playground.arduino.cc/Code/Keypad/#Functions

EventKeypad_modify.zip
0.00MB

 

#include <Keypad.h>

 

const byte ROWS = 4; //four rows

const byte COLS = 3; //three columns

char keys[ROWS][COLS] = {

    {'1','2','3'}, // {  S1,  S2,  S3}

    {'4','5','6'}, // {  S5,  S6,  S7}

    {'7','8','9'}, // {  S9, S10, S11}

    {'*','0','#'}  // { S13, S14, S15}

};

byte rowPins[ROWS] = {6, 5, 4, 3}; // {R4, R3, R2, R1}

byte colPins[COLS] = {7, 8, 9};    // {C1, C2, C3, C4} 

 

Keypad keypad = Keypad( makeKeymap(keys), rowPins, colPins, ROWS, COLS );

 

void setup(){

    Serial.begin(9600);

    keypad.addEventListener(keypadEvent); // Add an event listener for this keypad

    keypad.setHoldTime(2000);  // 키 홀드 시간 설정 - 2000 = 2초

}

 

void loop(){

  char key = keypad.getKey();

  if (key) {

    Serial.println(key);

  }

}

 

// Taking care of some special events.

void keypadEvent(KeypadEvent key){

  switch (keypad.getState()){

  case PRESSED:

    if (key == '#') Serial.println("PRESSED");

  break;

  case RELEASED:

    if (key == '*') Serial.println("RELEASED");

  break;

  case HOLD:

    if (key == '*') Serial.println("HOLD");

  break;

  }

}

 

키패드를 아두이노에 연결할 때 C4핀을 연결하지 않았다. C4핀을 연결하지 않게되면 아래의 빨간표시 부분의 키들은 입력이 되지 않게 된다. 

 

코드에서 핀배치 배열(colPins[COLS])의 C4에 해당하는 자리가 비어 있음을 볼 수 있다. 

byte colPins[COLS] = {7, 8, 9};    // {C1, C2, C3, C4}

 

 

상기 코드를 업로드하고 버튼을 차례대로 왼쪽에서 오른쪽으로 또 아래로 내려가면서 눌러보자

 

 

키패드 C4핀에 해당하는 부분은 입력이 안되며 *를 누르면 *가 출력되고 버튼을 떼는 순간 RELEASED가 출력되는 것을 볼수 있다. 또한 # 누르는 순간 PRESSED가 먼저 출력되고 바로 다음에 #이 출력되게 된다. *를 계속 누르고 있으면 * HOLD 가 차례로 나오고 뗄때 RELEASED가 출력되는 것을 볼 수 있다.  keypadEvent() 코드중 case HOLD: 를 사용할 것이다.

 

 

키패드를 통해 숫자를 입력 받을 수 있게 되었다. 이제 입력된 숫자를 저장하고 비교하는 코드를 작성해 보자

 

위에서 살펴본 디지털 도어락 비밀번호 변경방법 절차중 2번과 5번을 구현하기 위해 keypadEvent() 함수를 살펴보자. 

2. 열림 상태에서 #버튼을 2초간 누른다. (비밀번호 설정 코드로 진입된다)

5. 사용자 비밀번호 입력을 취소할 경우 *를 2초간 누른다.(비밀번호 설정 코드를 빠져나간다)

 

2번은 비밀번호 검증이 완료되어 문이 열린 상태(서보모터 열림 위치)에서만 #버튼이 작동되어야 하고 5번은 비밀번호 변경중 설정시간이 초과되어 문이 자동으로 닫혀질 수 있으므로 문이 닫힌 상태(서보모터 닫힘 위치)에서도 작동되어야 한다. 

 

우선, 문이 열리고 닫히는 상태를 표시하기 위해 플래그 doorOpen를 선언하고 초기값은 false로 하자.

 

bool doorOpen = false;

 

2초간 눌렸다는 것을 표시하기 위해 플래그 longPress를 선언하고 초기값은 false로 하자. 

#버튼을 2초간 누르면 longPress = true; 가 되어 비밀번호 설정 코드로 들어가고, *를 2초간 누르면 longPress = false;로 하여 #버튼을 2초간 누른 효과를 무효화 시켜 비밀번호 설정 코드를 빠져 나오게 된다. 

 

bool longPress = false; 

 

위 상황에 따라 아래와 같이 코드를 변경해 주었다. 

 

void keypadEvent(KeypadEvent key){ // 키패드 이벤트 함수

  switch (keypad.getState()){

    case HOLD:

      switch (key){

        case '*': longPress = false;     // 비밀번호 변경코드 빠져나감

                  key = '\0'; // 입력된 '*' 삭제 - 오류방지

                  codeIndex = 0;

        break;

      }

    break;  // HOLD 종료

  }

  if(doorOpen == true) {  // 문 열린 상태에서만 키패드 이벤트 진입

    switch (keypad.getState()){

      case HOLD:

        switch (key){

          case '#': longPress = true;    // 비밀번호 변경코드 진입

                    key = '\0'; // 입력된 '#' 삭제 - 오류방지

                    codeIndex = 0;

          break;

        }

      break;  // HOLD 종료

    }

  }

}

 

비밀번호는 4자리 숫자이다. 이를 위해 아래와 같이 관련 변수를 설정해 주자.

 

char temp[4] = { 0, };          // 비밀번호 설정시 임시 저장 배열

char password[4] = "1234";  // 초기 비밀번호 및 사용자 비밀번호 저장 배열

char inputCode[4] = "0000"; // 검증을 위한 사용자 입력 비밀번호 저장 배열

uint8_t codeIndex = 0;        // 비밀번호 배열 index 지정 변수 (uint8_t: 부호없는 8bit 0 ~ 255 까지의 숫자)

 

 

디지털 도어락의 작동 상황을 두가지 상황을 기준으로 구분할 수가 있는데, 하나는 4자리 비밀번호를 입력받고 검증 후 문을 열고 닫는것이고 다른 하나는 4자리 사용자 비밀번호를 입력받고 저장하여 비밀번호를 변경하는 것이다. 이는 위에서 설정한 longPress 플래그 변수값에 의해 구분 되어 질 수 있다. 

 

if (longPress) { 비밀번호 변경 상황 } else { 비밀번호 검증 후 문을 열고 닫는 상황 }

 

비밀번호 변경 상황

if (key) {                                                          // 키패드 키가 눌려져 key 변수에 값이 있으면

if (key != '*' && key != '#' && codeIndex < 4) { temp[codeIndex] = key; codeIndex++; }

// 만약 키값이 숫자이면, temp 배열에 인덱스 0 ~ 3까지 순서대로 저장(4자리 이상 입력은 무시)

else if(key == '#') {                                           // 만약 키값이 숫자가 아니고 #이면

  for (int i = 0; i < 4; i++) password[i] = temp[i]; // temp 배열의 값을 password 배열에 저장

  longPress = false;                                            // 비밀번호 변경 코드 빠져나간다.

  key = '\0';                                                       // 변수 key값 비우기, 입력된 '#' 삭제 - 오류방지

  codeIndex = 0;                                                // 비밀번호 확인 완료됐으므로 인덱스 번호 초기화

}

else if (key == '*') {                                           // 만약 키값이 숫자가 아니고 *이면

  for (int i = 0; i < 4; i++) temp[i] = '0';  // temp 배열값 모두 0으로 초기화

  key = '\0';                                                       // 변수 key값 비우기, 입력된 '*' 삭제 - 오류방지

  codeIndex = 0;                                                // 처음부터 입력 받기 위해 인덱스 번호 초기화

}

 

비밀번호 검증 후 문을 열고 닫는 상황

if (key) {                                              // 키패드 키가 눌려져 key 변수에 값이 있으면

if (key != '*' && key != '#' && doorOpen != true) { // 만약 키값이 숫자이면, 검증이 완료되고 열린상황에서는 입력 무시

  inputCode[codeIndex] = key;                 // 키값을 비교배열 inputCode의 codeIndex 번호에 저장

  codeIndex++;                                    // 인덱스 번호 증가

  if (codeIndex == 4) {                           // 비교배열 인덱스 3까지(4번째 숫자) 저장후 번호 증가하여 4가 되면

    if (strncmp(password, inputCode, 4) == 0) { Serial.print("door open");  doorOpen = true; codeIndex = 0; } 

    // 비밀번호 검증 후 참이면, 시리얼 모니터에 door open 출력, doorOpen 플래그 true로 변경, 인덱스 번호 초기화

**strcmp(문자열1, 문자열2);  // 비교할 문자열을 넣어주면 결과를 정수로 반환 - 대소문자 구분

   정수값 -1:  ASCII 코드값 기준으로 문자열2가 클 때  //  문자열1 = "aab" < 문자열2 = "aac" // a = 97 b = 98 c = 99

   정수값 0:   ASCII 코드값 기준으로 두 문자열이 같을 때   //  문자열1 = "aaa" = 문자열2 = "aaa"

   정수값 1:   ASCII 코드값 기준으로 문자열1이 클 때   //  문자열1 = "aab" > 문자열2 = "aaa"

    else { Serial.print("wrong_number");  codeIndex = 0; }

    // 검증후 일치하지 않으면, 시리얼 모니터에 wrong number 출력, 처음부터 입력 받기 위해 인덱스 번호 초기화

    for (int i = 0; i < 4; i++) inputCode[i] = '0';           // 비교배열값도 초기화

else if (key == '*') {                                             // 입력값 초기화, 아무때나 눌려도 상관없다.

  Serial.println("CLEAR");

  for (int i = 0; i < 4; i++) inputCode[i] = '0';             // 비교배열값 초기화

  key = '\0';                                                      // 입력된 '*' 삭제 - 오류방지

  codeIndex = 0;                                                // 처음부터 입력 받기 위해 인덱스 번호 초기화

}

 

 

사용자 비밀번호 저장 코드와 비밀번호 검증을 통해 문을 열고 닫는 코드를 살펴보았다. 

아래 코드를 아두이노에 업로드 하고 4자리 숫자를 입력하고 시리얼 모니터를 통해 작동 상황을 확인해 보자.

door_lock_passcode.zip
0.00MB

#include <Keypad.h>

 

const byte ROWS = 4; //four rows

const byte COLS = 3; //three columns

char keys[ROWS][COLS] = {

    {'1','2','3'}, // {  S1,  S2,  S3}

    {'4','5','6'}, // {  S5,  S6,  S7}

    {'7','8','9'}, // {  S9, S10, S11}

    {'*','0','#'}  // { S13, S14, S15}

};

byte rowPins[ROWS] = {6, 5, 4, 3}; // {R4, R3, R2, R1}

byte colPins[COLS] = {7, 8, 9};    // {C1, C2, C3, C4} 

Keypad keypad = Keypad( makeKeymap(keys), rowPins, colPins, ROWS, COLS );  // 키패드 오브젝트 생성

 

bool longPress = false;  // 키패드 이벤트용 및 계속점멸 longPress 플래그

bool doorOpen = false;

 

char temp[4] = { 0, };      // 비밀번호 설정시 임시 저장변수

char password[4] = "1234";  // 초기 비밀번호 및 사용자 비밀번호 저장 변수

char inputCode[4] = "0000"; // 검증을 위한 사용자 입력 비밀번호 저장 배열

uint8_t codeIndex = 0;    // 비밀번호 문자열 배열 index

 

void setup(){

  Serial.begin(9600);

  keypad.addEventListener(keypadEvent); // 키패드 이밴트 관리자 설정

  keypad.setHoldTime(2000);  // 키 홀드 시간 설정 - 2000 = 2초

}

 

void loop(){

  char key = keypad.getKey();

  if (longPress) {  // password 설정

    if (key) {

      if (key != '*' && key != '#' && codeIndex < 4) { // 숫자이면, 4자리 이상 무시

        Serial.print("pass set key: "); Serial.println(key); 

        temp[codeIndex] = key;

        codeIndex++;

      }

      else if(key == '#') { // password 저장

        for (int i = 0; i < 4; i++) password[i] = temp[i];

        longPress = false;

        key = '\0'; // 입력된 '#' 삭제 - 오류방지

        codeIndex = 0;

        doorOpen = false; // 자동으로 잠기는 문 표시

        Serial.println("set new password");

      }

      else if (key == '*') {  // 입력값 초기화

        for (int i = 0; i < 4; i++) temp[i] = '0'; 

        key = '\0'; // 입력된 '#' 삭제 - 오류방지

        codeIndex = 0;

        Serial.println("CLEAR");   

      }

    }

  } else {

    if (key) {

      if (key != '*' && key != '#' && doorOpen == false) { // 숫자만 입력

        Serial.print("key: "); Serial.println(key);

        inputCode[codeIndex] = key;

        codeIndex++;

        if (codeIndex == 4) {

          if (strncmp(password, inputCode, 4) == 0){

            Serial.println("door open");

            doorOpen = true;

            codeIndex = 0;

          } else {

            Serial.println("wrong number");

            codeIndex = 0;

          }

          for (int i = 0; i < 4; i++) inputCode[i] = '0';

        }

      }

      else if (key == '*') {  // 입력값 초기화

        Serial.println("CLEAR");

        for (int i = 0; i < 4; i++) inputCode[i] = '0'; 

        key = '\0'; // 입력된 '#' 삭제 - 오류방지

        codeIndex = 0;

      }

    }

  }

}

 

void keypadEvent(KeypadEvent key){ // 키패드 이벤트 함수

  switch (keypad.getState()){

    case HOLD:

      switch (key){

        case '*': longPress = false;  // 비밀번호 변경코드 빠져나감

                  key = '\0'; // 입력된 '*' 삭제 - 오류방지

                  codeIndex = 0;

                  Serial.println("set pass out");

        break;

      }

    break;  // HOLD 종료

  }

  if(doorOpen == true) {  // 문 열린 상태에서 키패드 이벤트 진입

    switch (keypad.getState()){

      case HOLD:

        switch (key){

          case '#': longPress = true;  // 비밀번호 변경코드 진입

                    key = '\0'; // 입력된 '#' 삭제 - 오류방지

                    codeIndex = 0;

                    Serial.println("set pass in");

          break;

        }

      break;  // HOLD 종료

    }

  }

}

 

 

상기 코드에는 문이 열린 후 몇초 뒤 자동으로 잠기는 상황이 반영되어 있지 않다. 그러므로 문이 열려 doorOpen = true; 가 계속 유지되고 false로 바뀌지 않게 된다. 문제는 doorOpen == false 일때만 숫자가 입력되게 코드 되어 있기때문에 키패드를 통한 숫자 입력이 더이상 되지 않게 되어 추가 확인을 할 수가 없다. 비밀번호 변경 확인을 위해 비밀번호 변경 저장 #버튼을 눌렀을 때 doorOpen = false;로 변경하는 코드를 추가해 주었다. 

 

 

 

키패드로 1234 입력하고 #버튼을 2초간 누른뒤 1111을 입력하고 #버튼을 눌러 비밀번호를 변경하고 다시 변경된 비밀번호 1111을 입력하면 아래와 같이 시리얼 모니터에 출력되는 것을 볼수 있다. 상기 코드 door open 상황에서는 *버튼과 #버튼만을 입력할 수 있다. 

 

 

도어락의 핵심 기능인 키패드를 통해 입력된 4자리 비밀번호를 검증하고 결과를 출력하는 코드는 완성되었다. 이제 사용자 입력에 따라 반응하는 LED와 피에조부저의 작동상태 표시에 관해 살펴보자.

 

LED를 켜고 끄는 작동을 반복하거나 외부 입력없이 내부적으로 제어하기 위해서는 보통 delay()함수를 사용하게 된다. 

 

아두이노 기본 예제인 blink예제를 살펴보자.

basic_Blink.zip
0.00MB

#define LED_BUILTIN 13

 

void setup() {

  pinMode(LED_BUILTIN, OUTPUT);

}

 

void loop() {

  digitalWrite(LED_BUILTIN, HIGH);   // turn the LED on (HIGH is the voltage level)

  delay(1000);                       // wait for a second

  digitalWrite(LED_BUILTIN, LOW);    // turn the LED off by making the voltage LOW

  delay(1000);                       // wait for a second

}

 

상기 코드를 업로드 하면 1초 간격으로 아두이노 기본 LED가 점멸하는 것을 볼 수있다. 

 

이 코드와 위에서 살펴보았던 EventKeypad_modify.ino 코드와 합쳐보자.

EventKeypad_modify_blink.zip
0.00MB

#include <Keypad.h>

 

const byte ROWS = 4; //four rows

const byte COLS = 3; //three columns

char keys[ROWS][COLS] = {

    {'1','2','3'}, // {  S1,  S2,  S3}

    {'4','5','6'}, // {  S5,  S6,  S7}

    {'7','8','9'}, // {  S9, S10, S11}

    {'*','0','#'}  // { S13, S14, S15}

};

byte rowPins[ROWS] = {6, 5, 4, 3}; // {R4, R3, R2, R1}

byte colPins[COLS] = {7, 8, 9};    // {C1, C2, C3, C4} 

 

Keypad keypad = Keypad( makeKeymap(keys), rowPins, colPins, ROWS, COLS );

 

#define LED_BUILTIN 13

 

void setup(){

  Serial.begin(9600);

  pinMode(LED_BUILTIN, OUTPUT);

  keypad.addEventListener(keypadEvent); // Add an event listener for this keypad

  keypad.setHoldTime(2000);  // 키 홀드 시간 설정 - 2000 = 2초

}

 

void loop(){

  char key = keypad.getKey();

  if (key) {

    Serial.println(key);

  }

  digitalWrite(LED_BUILTIN, HIGH);   // turn the LED on (HIGH is the voltage level)

  delay(1000);                       // wait for a second

  digitalWrite(LED_BUILTIN, LOW);    // turn the LED off by making the voltage LOW

  delay(1000);    

}

 

// Taking care of some special events.

void keypadEvent(KeypadEvent key){

  switch (keypad.getState()){

  case PRESSED:

    if (key == '#') Serial.println("PRESSED");

  break;

  case RELEASED:

    if (key == '*') Serial.println("RELEASED");

  break;

  case HOLD:

    if (key == '*') Serial.println("HOLD");

  break;

  }

}

 

상기 코드를 업로드 하고 키패드의 버튼을 클릭해보면 시리얼 모니터에 출력이 안된다. 이때 키패드의 버튼을 빠르게 연타 하거나 또는 계속 누르고 있으면 그때야 비로소 숫자가 입력되고 시리얼 모니터에 출력 되는걸 확인 할 수 있다. 

 

이는 keypad.h라이브러리 문제나 blink예제 코드의 문제는 아니다. 단지 delay()함수의 특성 때문에 발생하는 현상이며 정상적인 반응이다. 

 

아두이노의 작동 방식은 처음 전원이 들어가면 스케치상 위에서 아래로 왼쪽에서 오른쪽으로 코드를 읽어나간다. 또한 전역변수나 기타 메크로등을 순서대로 읽고 그 다음 setup() 함수 그리고 loop()함수로 들어가서 무한 반복하도록 되어 있다. 

 

무한 반복하는 loop()함수에서도 위에서 아래로 왼쪽에서 오른쪽으로 코드를 읽어 나가고, 코드의 명령에 따라 어떤 실행을 하게 된다. 이렇게 loop() 함수안의 전체 코드를 한번 읽는 동작을 "scan한다"라고 얘기를 한다. 그렇다면 아두이노 loop() 함수는 1초에 몇번 scan을 할까? 입력된 코드의 양과 특성에 따라 달라지게 되고 또한 MCU의 속도에 따라 달라지게 되어 정확하게는 측정하기는 어렵지만 아두이노 참조사이트를 통해 유추해 볼 수는 있다. 

 

아두이노 사이트에 있는 참조 페이지의 analogRead() 설명을 살펴보자

지정한 아날로그 핀에서 값을 읽습니다. 아두이노 보드는 6채널(미니와 나노는 8채널, 메가는 16채널), 10비트의 아날로그-디지털 변환기를 가지고 있습니다. 이는, 0에서 5V 사이의 입력 전압을 0에서 1024 사이의 정수 값으로 대응시키는 것을 뜻합니다. 이는 해상도가 5V/1024 혹은 0.0049V (0.49mV) 단위라는 것을 뜻합니다. 입력 범위와 해상도는 analogReference() 를 사용해서 바꿀 수 있습니다.
아날로그 입력을 읽는 데는 약 100마이크로초(0.0001초)가 걸리므로, 최대 읽기 속도는 1초에 약 1만 번입니다. 

위의 얘기는 아두이노 loop()함수에 analogRead()함수만 코딩되어 있을경우 1초에 1만번 읽는다는 얘기일 것이고, 그 얘기는 1초에 1만번 loop()함수가 실행되면서 scan하게 된다는 얘기이다. 따라서 아두이노 loop()함수는 보통 1초에 1만번 정도 스캔한다고 유추를 해도 큰 무리는 없을 것이다. 

 

 

 

delay() 함수가 없는 EventKeypad_modify.ino 스케치에서는 loop()함수가 돌면서 아래 코드에 따라 key 변수에 값이 있어 조건을 만족하는지 안하는지를 1만번 정도 확인하고 그중에 조건을 만족하는 순간 시리얼 모니터에 출력하게 된다. 

 

char key = keypad.getKey(); 

if (key) { 시리얼 모니터 출력 } 

 

하지만 상기의 코드 처럼 delay() 함수가 포함 되게 되면 상기의 1초에 1만번 정도 실행되는 scan 작업을 방해하게 된다.

 

delay() 함수는 내부적으로 while 루프를 사용하여 시간의 delay를 만들게 되는데, while 루프는 어떤 조건이 만족되면 무한 루프하는 코드이다. 아두이노 loop() 함수 안에 delay()함수가 있게 되면 아두이노 loop()함수 안에 또다른 루프가 생성되는 것이고, 그 루프의 조건이 계속 만족된다면 아두이노 loop()는 한바퀴 조차 돌지 못하게 된다. 한바퀴조차 돌지 못한다는 것은 아두이노에 전원은 들어가 작동은 하지만 입력에 대한 반응을 전혀하지 않는 상태인 흔히 "먹통이 되었다"라고 하는 상태가 되게 된다.  물론 delay() 함수가 이런 상황을 유발하는 것은 아니다. 언젠가는 조건이 만족하지 않아 빠져나오도록 코딩되어 있다.

 

아래 delay() 함수 코드를 살펴보면 delay(조건), 조건(ms > 0)을 만족하면 while 루프가 작동을 하고 ms =0이 되면 빠져나가게 되어 있는것을 볼 수 있다. 

 

void delay(unsigned long ms) {    // ms: 밀리초    

  uint32_t start = micros();          // 시작 비교시간 저장 : 마이크로초

  while (ms > 0) {                      // 조건(밀리초)이 0이 아니면

    yield();

    while (ms > 0 && (micros() - start) >= 1000) {  // 밀리초가 0이 아닌 상태에서 1밀리초가 경과되면 

      ms--;                                                            // 조건 밀리초에서 1밀리초를 뺀다

      start += 1000;                                              // 다시 1밀리초를 측정하기 위해 기준값에 1밀리초를 더해준다.

    }

  }

}

 

delay() 함수 코드 참조

https://forum.arduino.cc/index.php?topic=417302.0

 

delay() 함수가 아두이노 loop() 함수의 스캔작업을 방해하는데는 delay() 함수 내부에 while 루프를 사용함으로써 발생하는 현상이다.  while 루프를 사용하여 delay()함수 본연의 목적인 코드와 코드 사이의 실행 시간의 간격을 손쉽게 조정하는 것에는 좋은 방법이지만, 이로 인해 발생하는 loop() 함수의 스캔작업을 방해하는 부작용은 loop()함수 안의 전체 코드의 실행에 안좋은 영향을 미치게 된다. 

 

1초에 1만번 실행되는 loop() 함수에 delay(1000)을 주게 되면 loop() 함수는 1초에 1번만 돌게 되는데, 이때 정작 확인해야할 코드는 만분의 1초라는 짧은 시간 동안 1번만 읽히게 되고, 그 짧은 시간에 우연히 타이밍이 맞아 입력이 확인되면 코드에 대한 출력을 하게 되지만 타이밍이 맞지 않게 되면 출력이 안되게 되어 흔히 됐다 안됐다를 반복하게 된다. 

 

blink 예제의 코드의도에 대해 다시 한번 살펴보자.

 

예제에서 delay(1000); 코드를 두번 사용함으로써 loop()함수를 대략 2초에 1번 scan하도록 코딩됐다는 것을 알수 있고, 그 2초 동안에 LED를 켜고 1초뒤 LED를 끄고 다시 1초뒤 loop()함수의 scan을 종료하고 다시 처음으로 돌아가게 된다. 이는 아두이노 기본 LED가 1초 간격으로 점멸하게 만드는 것으로 작동상 어떠한 문제도 없다. 하지만 보통 코드를 작성할 때 2초마다 한번씩 loop()함수가 scan하도록 의도하지는 않았을 것이다. 단지 LED가 켜지고 꺼지는 시간을 조절해서 1초 간격으로 점멸 시키기위한 의도로 delay() 함수를 사용했을 것이지만 delay() 함수를 사용함으로써 loop()함수의 scan 횟수가 대략 2초에 1회만 실행되는 결과가 초래된 것이다. 그렇다면 1초 간격은 유지하면서 loop()함수의 scan 횟수에도 영향을 미치지 않도록 코딩하려면 어떻게 해야 할까? millis()함수를 이용하여 해결 할 수 있다.   

 

아두이노 사이트에 있는 참조 페이지의 millis() 설명을 살펴보자

아두이노 보드가 현재 프로그램을 돌리기 시작한 후 지난 밀리 초 숫자를 반환한다. 이 숫자는 약 50 일 후에 오버플로우(0으로 돌아감)가 된다.

반환

프로그램 시작 후 지난 시간 (unsigned long)

 

설명을 살펴보면 아두이노에 전원이 들어가면 밀리초가 카운트 되기 시작하여 전원을 끌 때 까지 계속 증가하다가 약 50일 후 오버플로우가 일어나면 다시 0부터 증가한다는 것을 알 수 있다. 추가로 설명하자면 밀리초는 아두이노 기동과 동시에 특정 명령없이 자동 실행되고 그 값이 증가되며, millis() 함수는 단지 현재의 밀리초 값만을 확인할 때 사용한다. 이렇게 시간의 지남에 따라 계속 증가하는 밀리초 값으로 초 시계로 시간간격을 측정하듯 시작시간과 끝시간을 지정하고 끝시간에 프로그램이 실행되게 하면 특정시간에 코드의 출력이 되게 하거나 일정 시간 간격으로 반복 작업을 수행하도록 할 수 있게 된다. 

 

글로만 설명하면 그 구도가 딱 잡히지 않는다. 아래 코드 구성을 살펴보자.

 

unsigned long int start_time = 0;

 

if (millis() - start_time >= 시간간격) { // 시간 간격: 밀리초

  start_time = millis(); // 상기 조건을 만족할때의 밀리초를 다시 start_time에 저장하여 조건 초기화

  실행코드 

}

 

시간간격을 1000 밀리초(1초)로 하고 loop() 함수가 0밀리초부터 작동한다고 가정하면 아래와 같이 동작하게 된다. 

 

0밀리초 : 0 - 0 >= 1000 : 거짓

500 밀리초: 500 - 0 >= 1000 : 거짓

999 밀리초: 999 - 0 >= 1000 : 거짓

1000 밀리초: 1000 - 0 >= 1000 : 참

start_time에 현재 밀리초 1000 저장후 코드 실행

1500 밀리초: 1500 - 1000 >= 1000: 거짓

2000 밀리초: 2000 - 1000 >= 1000: 참 

start_time에 현재 밀리초 2000 저장후 코드 실행

 

이렇게 무한 반복하게 된다. 

 

 

 

주의할 점은 조건에 millis() - start_time = 1000와 같이 '='만을 사용하게 된다면 조건을 건너뛰어 코드를 실행하지 않는 오류를 내포하게 된다. 이는 loop() 함수 안에 코드들의 양이나 코드의 특성에 따라 발생할 수 있는 것으로 코드가 많아 loop()를 도는데 1밀리초 이상되거나 for 루프나 while 루프를 완료하는 시간, Serial 통신시 데이터 수신 및 송신에 걸리는 시간등에 따라 누적된 시간의 차가 정확하게 1000밀리초가 되지 못하고 1001 밀리초가되어 조건을 건너뛰는 상황이 언젠가는 발생할 수 있다. 그러므로 그러한 상황을 방지하기 위해 '>'를 추가해주어야 한다.

 

아래 코드를 업로드해 보자.

basic_Blink_millis.zip
0.00MB

#define LED_BUILTIN 13

 

unsigned long int start_time = 0;

 

void setup() {

  pinMode(LED_BUILTIN, OUTPUT);

}

 

void loop() {

  if (millis() - start_time >= 1000) {

    start_time = millis();

    digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));

  }

}

 

loop() 함수가 0밀리초부터 작동한다고 가정하면 loop()함수에 들어오고 1000 밀리초가 됐을때 코드가 실행되어 LED를 켜고 2000 밀리초가 됐을때 LED를 끄는 것을 반복하게 된다. 1초 간격으로 계속 점멸하면서 loop() 함수는 1초에 약 1만번 돌게 될것이다. 하지만 기본 예제 blink와는 차이가 있게되는데, blink예제는 loop()함수에 들어오자마자 바로 LED를 켜고 millis()를사용하는 상기 예제는 1000밀리초가 지난뒤에 LED를 켜는 차이점이 발생하게 된다. 이점은 millis()함수를 이용하여 시간을 조정하는 방법의 특성으로서 이해를 해야할 것이다. blink예제 처럼 loop()함수시작시 바로 LED가 켜지게 하려면 아래 코드를 추가해 주어야 한다.

 

 

bool first_loop = true; // 처음 시작시 한번만 작동하는 플래그

 

if (first_loop == true) {

  digitalWrite(LED_BUILTIN, HIGH);

  first_loop = false;     // 한번 작동후 재작동 방지위해 플래그 변경

}

basic_Blink_millis_first_loop.zip
0.00MB

#define LED_BUILTIN 13

 

unsigned long int start_time = 0;

bool first_loop = true;

 

void setup() {

  pinMode(LED_BUILTIN, OUTPUT);

}

 

void loop() {

  if (first_loop == true) {

    digitalWrite(LED_BUILTIN, HIGH);

    first_loop = false;

  }

  if (millis() - start_time >= 1000) {

    start_time = millis();

    digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));

  }

}

 

상기 코드를 업로드 하면 기본 blink예제 코드와 시각적으로 같음을 알 수 있다. 

 

millis() 함수를 사용하여 시간 조정함에 있어 추가로 고려해야 할 사항이 있다. 

 

상기 예제에서는 loop() 함수 시작과 동시에 자동으로 millis()함수가 포함된 조건문을 확인하기 시작하여 1000밀리초 후에 코드 실행이 작동하기 시작했지만, 시간이 소요되는 작동에 의해 millis()함수가 포함된 조건문이 시작 될 때에는 millis()함수의 밀리초 값이 조건시간보다 큰상태가 되어 실행코드가 바로 실행되게 된다. 이는 시간조건의 크기와 언제 해당함수가 트리거 되는지에 따라 달라지게 되어 코드 작성시 코드의 작동 상황을 고려하여 millis()함수를 이용한 시간 조정 코드를 적용해야만 된다. 이것에 관한 사항은 앞으로 다룰 비밀번호가 맞을경우 LED 상태표시를 하는 코드에서 확인해 볼 수 있다. 

 

아두이노 millis()함수 설명에 보면 "약 50 일 후에 오버플로우(0으로 돌아감)가 된다"라고 했다. 오버 플로우가 되면 계속 증가하던 값이 0이된다. 상기 코드에서 시간 조건을 검증하기 위해 millis() - start_time >= 1000 코드를 사용했는데 millis()함수가 오버플로우 되면 0 - 이전 millis() 값 >= 1000 이 되어 -값 때문에 오류가 발생할 것처럼 보이지만 발생하지 않는다고 한다. 아래 코드를 업로드하고 실행 해보면 음수값이 나와야 할거 같은 결과같이 양수값으로 나타나는 것을 볼 수 있다. 이러한 결과가 나오는것은 millis() 함수와 start_time 변수 값의 타입이 unsigned 이기 때문에 그렇다고 한다. 아마도 bit 연산을 통해 설명해야 그 원리를 설명하고 이해할 수 있을 듯 하지만 위의 코드 형식을 적용하면 50일이 지나 millis() 함수 값에 오버플로우가 발생해도 오류가 발생하지는 않는다고만 알아두자.   

 

millis_overflow.zip
0.00MB

void setup() {

    Serial.begin(9600);

    unsigned long a = 4294967293;

    unsigned long b = 4294967000;  // startTime

    unsigned long c = 4294967294;

    unsigned long d = 4294967295;   // unsigned long maximum value

    unsigned long e = 0; // overflow

    unsigned long f = 1;

    unsigned long g = 2;

    Serial.println(a-b);

    Serial.println(b-b);

    Serial.println(c-b);

    Serial.println(d-b);

    Serial.print("overflow: "); Serial.println(e-b);

    Serial.println(f-b);

    Serial.println(g-b); 

}

 

void loop() {

}

 

 

참조 사이트

https://www.norwegiancreations.com/2018/10/arduino-tutorial-avoiding-the-overflow-issue-when-using-millis-and-micros/

 
앞서 언급한 디지털 도어락 작동상태 표시에 대한 살펴보자.

1. 열림: 빨간색 -> 노란색 -> 파란색 LED 순으로 한번씩 점멸하고, "도 -> 레 -> 미" 열림음이 출력된다. 

 

LED가 순차적으로 켜졌다 꺼졌다 하게 된다. 이를 millis() 함수를 통해 구현하려면 아래처럼 타이머와 카운터가 결합된 형식을 취하는게 코드를 가장 단순하게 만든다.  우선 시간 측정에 진입하는 플래그(bool timer = false;)를 하나 설정한다. 그리고 if 조건에 "timer == true"를 걸어주고 이함수를 loop() 함수내에 위치하여 scan 할 때마다 해당 조건이 항상 확인되도록 해주면 loop() 함수내에서 timer = true; 라는 코드에 의해 시간측정에 진입하는 사용자 함수 delay_timer()가 트리거되고 실행되게 된다. 그리고 delay_timer()가 실행되고 정해진 시간을 만족하면 미리 설정해 놓은 카운터을 하나씩 올린다. 이렇게 되면 카운터는 시간값을 포함하게 된다.

 

bool timer = false; // 사용자 함수 진입 플래그

 

void delay_timer() { // 250 밀리초 타이머, 타이머 종료: timer = false, time_count = 0;

  if (timer == true) {

    if (millis() - delayTime >= 250) { // time_count == 0일때는 딜레이 없이 바로진입 

      delayTime = millis();

      time_count++;                    // 1부터 250 밀리초 적용

    }

  }

}

 

 

시간값을 포함하는 카운터는 그 자체를 시간으로 계산할 수 있게 된다. 위의 경우에 만약 millis()가 0부터 시작한다면 카운터가 1일경우 250 밀리초가 되게되고 1씩 증가할 때마다 250밀리초가 증가하게 되어 카운터가 4일때는 1초가 된다. 하지만 상기 함수가 작동할 때에는 millis() 함수 값이 0이 될 수가 없다(50일 이내에는). 문이 열렸을 때 작동하는 LED의 제어를 위해 만들어진 함수안에 포함되었기 때문이다. 문이 열리려면 비밀번호를 입력하고 확인하는 시간이 필요한데, 아무리 빨리 입력한다고 해도 무조건 250 밀리초 이상 걸리게 되고 이 함수가 실행되는 순간은 항상 참이되어 250밀리초 이상인 조건을 시간 소요없이 바로 만족하여 카운터를 0에서 1로 바꾸게 된다. 즉, 시작값이 0이 아니라 1이된다. 1부터는 250 밀리초의 시간마다 카운터를 1씩 증가시키게 된다. 참고로 millis() 함수 값이 0이 되는 순간은 millis()함수가 오버플로우 되는 약 50일 이후가 될 것이다. 

 

 

 

이 카운터를 활용하여 LED를 제어하기 위해 아래와 같이 문이 열렸을 때의 플래그 doorOpen = true; 에 의해 작동하는 door_open()라는 사용자함수 코드를 작성하였다. 

 

int arrLed[3] = {13, 12, 2}; // 빨, 노, 파

 

setup() 함수

for (int i = 0 ; i < 3 ; i++) {

  pinMode(arrLed[i], OUTPUT);

  digitalWrite(arrLed[i], LOW);

 

}

 

사용자 함수

void door_open() {  //  빨, 노, 파 순차 점멸

  if (doorOpen == true) {

    if (time_count == 1)  {      // 시작 0 밀리초 경과

      for (int i = 0; i <3 ; i++) digitalWrite(arrLed[i], LOW);  // 

      digitalWrite(arrLed[0], HIGH);

    }

    else if (time_count == 2) { // 250 밀리초 경과

      for (int i = 0; i <3 ; i++) digitalWrite(arrLed[i], LOW);   

      digitalWrite(arrLed[1], HIGH);

    }

    else if (time_count == 3) { // 250 밀리초 경과

      for (int i = 0; i <3 ; i++) digitalWrite(arrLed[i], LOW);   

      digitalWrite(arrLed[2], HIGH);

    }

    else if (time_count == 4) { // 250 밀리초 경과

      for (int i = 0; i <3 ; i++) digitalWrite(arrLed[i], LOW);   

    }

    else if (time_count == 29) { // 문이 자동으로 잠기는 시간(7초)

      Serial.print("door close");

      doorOpen = false;             // 문이 잠겼으므로 플래그 초기화

      timer = false;                   // 상태표시 종료 - 플래그 초기화

      time_count = 0;               // 상태표시 종료 - 카운터 초기화

    }

  }

 

}

door_lock_passcode_LED.zip
0.00MB

LED의 작동에 따라 부저음도 도 -> 레 -> 미 음으로 울리게 되므로 아래 코드를 추가해주자.

 

# define beepPin 11    // tone 핀 설정

 

door_open() 함수

tone(beepPin, 262);

tone(beepPin, 294);

tone(beepPin, 330);

noTone(beepPin);

 

키패드를 누를때도 '도' 음이 울리도록 아래코드를 키패드 입력부 코드에 추가해 주자.

 

tone(beepPin, 262, 50);

 

아두이노 참조 사이트의 tone()함수 설명

핀에 특정 주파수(50% 듀티 사이클)의 구형파를 발생시킵니다. 지속 시간을 정할 수 있으며, 따로 정하지 않는다면 noTone()을 부를 때까지 구형파가 지속됩니다. 핀을 피에조 버저 또는 스피커에 연결하여 tone을 연주할 수 있습니다.

한번에 한 tone만 발생시킬 수 있습니다. 다른 핀에서 tone이 이미 연주되고 있으면, tone()을 새로 불러도 아무 일도 일어나지 않을 것입니다. 같은 핀에서 tone이 연주되고 있으면, 주파수가 새로 설정될 것입니다.

tone() 함수의 사용은 (Mega 이외의 보드에서) 3번과 11번 핀에서의 PWM 출력을 방해할 것입니다. 31HZ보다 낮은 tone을 발생시키는 것은 불가능합니다. 기술적인 세부 사항은, Brett Hagman’s notes를 보십시오.

문법

tone(pin, frequency)

tone(pin, frequency, duration)

매개변수

pin: tone을 발생시킬 핀

frequency: tone의 주파수 (Hz 단위) - unsigned int

duration (옵션) : tone 의 지속 시간 (밀리초 단위) - unsigned long

 

noTone(pin) // tone함수 종료

 

비밀번호가 확인되면 잠금장치가 열리고 닫히므로 서보모터의 작동도 추가해 주자. 

 

#include <Servo.h>

 

Servo myservo;    // create servo object to control a servo

int pos = 0;    // variable to store the servo position

# define servoPin 10

# define servo_open 50

 

# define servo_close 150

 

setup() 함수

myservo.attach(servoPin);    // attaches the servo on pin 10 to the servo object

 

myservo.write(servo_close); // 기동시 잠긴상태

 

door_open() 함수

myservo.write(servo_open);

myservo.write(servo_close);

door_lock_passcode_LED_tone_servo.zip
0.00MB

3. 비밀번호 잘못 입력한 경우: 경고음 1회 발생,  빨간색, 노란색, 파란색 LED가 한번만 동시 점멸

아래 사용자 함수 wrong_blink() 코드를 작성해 주었다. 

 

bool wrongBlink = false;

 

void wrong_blink() {

  if (wrongBlink == true) {

    if (time_count == 1) {

      tone(beepPin, 262, 500);

      for (int i = 0; i <3 ; i++) digitalWrite(arrLed[i], HIGH);

    }

    if (time_count == 3) {

      for (int i = 0; i <3 ; i++) digitalWrite(arrLed[i], LOW);

      timer = false;

      wrongBlink = false;

      time_count = 0;

    }

  }

 

}

 

2. 사용자 비밀번호 입력: 빨간색, 노란색, 파란색 LED가 동시 점멸하며 입력 완료시 까지 계속된다. 

이 코드의 경우 시간값이 포함된 카운터를 사용하여 코드를 작성할 수 없었다. 처음에는 카운터를 이용하여 작성하였으나 표시의 특성상 1번 표시 중에 2번 표시가 동시에 발생하게 되는데, 코드 상으로는 문제가 없어 보이지만 정작 적용하게 되면 서보모터가 닫힘 상태로 가기전에는 제대로 작동하다가 닫힘 상태로 가기 시작했을 때부터 이상작동을 보인다. 아마도 타이머 간섭 문제인듯 싶지만 확인 할 수는 없었고 millis() 함수를 하나더 사용, 독립적으로 작동하도록 하여 해결하였다. 

 

bool inputBlink = false;

 

unsigned long int input_time = 0;

 

void input_blink() {

  if (inputBlink == true) {

    if (millis() - input_time >= 1000) { // time_count == 0일때는 딜레이 없이 바로진입 

      input_time = millis();

      for (int i = 0; i <3 ; i++) digitalWrite(arrLed[i], !digitalRead(arrLed[i]));

    }

  }

}

 

디지털 도어락의 작동상태를 표시하기 위해 사용자 함수를 만들어 주었다. 각 함수는 모두 loop() 함수에 위치하여 매 scan 마다 그 작동 플래그의 확인에 의해 실행 유무를 결정하게 된다. 

 

void loop() {

  delay_timer();

  door_open();

  wrong_blink();

  input_blink();

}

 

또한 각 함수의 작동에 있어서 필수 조건으로 시간값을 포함하는 타이머가 선행 되어져야만 한다. 따라서 코드상에 각각의 상태표시의 시작과 종료의 명령은 아래 코드로 이루어 지게 된다. 

 

door_open() 시작

timer = true;

doorOpen = true;

 

door_open() 종료

doorOpen = false;          // 문이 잠겼으므로 플래그 초기화

timer = false;             // 상태표시 종료 - 플래그 초기화

time_count = 0;            // 상태표시 종료 - 카운터 초기화

 

wrong_blink() 시작

timer = true;

wrongBlink = true;

 

wrong_blink() 종료

timer = false;

wrongBlink = false;

time_count = 0;

 

input_blink() 시작

inputBlink = true;

 

input_blink() 종료

inputBlink = false;

 

door_lock_passcode_state.zip
0.00MB

*** 비밀번호 3 틀리면 3분 기다려야 하게 할려면 어떻게 하면 되나요? 답변코드입니다.

door_lock_passcode_wait.zip
0.00MB

 

이제 마지막으로 작동개요중 아래 사항을 충족시키기 위해 EEPROM을 이용하여 비밀번호를 저장하도록 하자.

3. 배터리 교체등 전원을 껐다 켜도 입력된 사용자 비밀번호는 유지된다. 

 

 

위의 코드를 업로드 하고 테스트 해보면 사용자 비밀번호를 입력하고 저장하면 사용자 비밀번호로만 검증이 되어 작동되는 것을 볼 수 있다. 하지만 아두이노를 리부팅(꺼졌다 켜질때)하게 되면 아래처럼 초기 비번이 변수 초기값으로 설정되어 있어 초기값으로 초기화 되어 리부팅 전에 저장했던 사용자 비밀번호는 더이상 사용할 수 없게 된다.  

 

char password[4] = "1234";  // 초기 비밀번호 및 사용자 비밀번호 저장 변수

 

이를 해결하기 위해 비밀번호 변경시 '#' 버튼을 누를 때 EEPROM에 사용자 비밀번호를 저장시키고 아두이노가 리부팅 되어 password 배열에 저장되었던 사용자 비밀번호가 초기화(1234) 되어도 setup() 함수 실행시 EEPROM에 저장된 사용자 비밀번호를 읽어 password 배열에 다시 저장 시켜 사용자 비밀번호를 계속 사용 할 수 있게 된다. 

 

EEPROM을 사용하게 되면 초기 비밀번호도 EEPROM에 저장을 해주어야한다. 그래야만 setup() 함수 실행시 EEPROM을 읽고 초기 비밀번호를 사용할 수 있게 된다. 초기 비밀번호를 저장을 해주지 않으면 EEPROM의 빈값인 { 0xFF, 0xFF, 0xFF, 0xFF } 값을 입력해야 되는데 키패드로는 입력이 불가하여 비밀번호 검증 자체가 불가능하게 된다. 

 

이를 위해 보통 초기 비밀번호를 한번 저장하기위해 스케치를 업로드하여 EEPROM에 초기 비밀번호를 저장하고 다음에 초기 비밀번호 저장 코드를 주석처리하여 다시업로드 하는 두번의 업로드 과정을 거치게 되는데, 초기비번 플래그를 설정하고 그 플래그를 이용하여 한번만 스케치를 업로드해도 계속 사용할 수 있도록 해보자.

 

상기 코드에 아래 코드를 추가해 주었다. 

 

#include <EEPROM.h>

if(EEPROM.read(4) != 1) {

  for (int i = 0; i < 4; i++) EEPROM.write(i, password[i]);  // (adress(0 ~ 4096), value(0 ~ 255))  

  EEPROM.write(4, 1);

} else {

  for (int i = 0; i < 4; i++) password[i] = EEPROM.read(i);

}

 

작동 방식은 EEPROM의 초기값이 0xFF 이므로 아두이노 초기 기동시 EEPROM.read(4) != 1 조건을 만족하게 되어 password 배열에 저장된 초기 비밀번호를 저장하고 EEPROM 초기비번 플래그 위치에 1을 저장한다. 이렇게 저장된 1때문에 다음 리부팅시 코드 실행을 막게되어 초기기동시 한번만 실행되는 코드가 되었다. 다음 리부팅시에는 setup()함수에서 EEPROM에 저장된 비밀번호값을 읽기만 하게 된다. 

 

하지만 이렇게 되면 스케치를 새로 변경하여 다시 업로드 해도 이미 저장된 EEPROM 초기 비번 플래그 1 때문에 앞서 저장된 비밀번호를 읽기만 하게된다. setup() 함수에서 초기 비밀번호를 EEPROM에 쓰도록 하기 위해서는 EEPROM의 초기 비번 플래그를 1이 아닌 값으로 바꿔줘야 하는데, 시리얼 모니터에서 명령어 입력을 통해 초기화 하도록 해주자. 

 

if(Serial.available()) {

  String temp = Serial.readStringUntil('\n');

  if (temp == "pass1234") EEPROM.write(4, 0);

}

 

아래 풀 코드를 업로드 하고 디지털 도어락을 작동해 보자. 사용자 비밀번호가 저장된 후에는 리부팅 되거나 스케치를 새로 업로드 해도 EEPROM의 비밀번호 값이 남아 있게 된다. EEPROM의 비밀번호를 초기 비밀번호로 초기화 하기 위해서는 시리얼 모니터에서 pass1234를 입력하고 나서 리부팅 시키면 비밀번호가 1234로 초기화 된것을 볼 수 있다.

 

 

door_lock_EEPROM.zip
0.00MB

 

아두이노의 모든 EEPROM 값을 0으로 초기하는 코드

#include <EEPROM.h>

void setup() {
  for (int i = 0 ; i < EEPROM.length() ; i++) EEPROM.write(i, 0); // EEPROM Clear
}

void loop() {

}

ARDUINO_EEPROM_INI.zip
0.00MB

 

 

키패드 대신 블루투스를 연결하고 안드로이드 앱으로 디지털 도어락을 제어해 보았다. 

 

arduino bluetooth controller PWM - 아두이노 원격제어 안드로이드 앱 버전 3.5 다운로드

arduino bluetooth controller PWM 매뉴얼

 

 

  

  

 

 

door_lock_EEPROM_blue_event.ino
다운로드

 

 

키패드와 블루투스 이용 도어락 제어하기

 

키패드와 블루투스를 동시에 이용하기 위해서는 블루투스를 하드웨어 시리얼에 연결을 하여야 한다. 하지만 하드웨어 시리얼에 블루투스를 연결해 놓으면 아두이노 IDE를 통해 스케치 파일을 업로드 할 수 없게 되는데, 이럴 경우 스케치 파일을 업로드 할 때에는 아래 그림과 같이 아두이노 시리얼 RX 핀번호 0번에 연결된 케이블을 뽑아놓은 상태에서 스케치를 업로드하고 업로드 후에 다시 연결을하면 키패드와 블루투스를 통해 제어를 할 수 있게 된다.   

 

door_lock_EEPROM_blue_event_keypad.zip
0.00MB

 

+ Recent posts