아두이노 키패드 도어락 - 사용자 정의 코드 지원
부품 및 소모품
| 아두이노 우노 |
× |
1 |
| 4x4 키패드 매트릭스 |
× |
1 |
| 일반 푸시 버튼 |
× |
1 |
| LCD I²C |
× |
1 |
| 리니어 솔레노이드 잠금장치 |
× |
1 |
| IRF510N MOSFET |
× |
1 |
| 1채널 릴레이 |
× |
1 |
필요한 도구 및 기계
| 브레드보드, 170핀 |
| 10개 점퍼 와이어 키트, 5cm 길이 |
프로젝트 소개
안녕하세요, 이 튜토리얼에 오신 걸 환영합니다 (오래됐지만 고전적이죠). 오늘 아두이노 보드, 키패드, LCD I²C 화면으로 도어락 프로젝트를 만드는데, 잠금장치로는 솔레노이드와 릴레이를 쓸 겁니다. DC 모터 쓰는 지문 도어락 튜토리얼도 있으니 참고하세요. 잠금 시스템에 따라 배선, 코드를 골라 적용하시면 됩니다.
참고: 실제 프로젝트라면 솔레노이드 추천 안 합니다. 차라리 전자식/기계식 둘 다 열리는 잠금장치를 해킹해 프로젝트에 맞게 적용하세요.
부품
이 프로젝트에 필요한 부품들입니다. 점퍼 와이어랑 12V 파워 서플라이는 미안하지만 여기엔 안 넣었습니다:
푸시 버튼은 안에서 잠금 해제용입니다. 원치 않으면 빼도 됩니다. 저항은 디바운싱용입니다.
4×4 키패드 썼는데 3×4 써도 됩니다. 단 코드에서 확인용으로 'A'를 쓰는데 '*'나 '#'로 바꿔야 합니다.
솔레노이드는 12V 외부 전원으로 구동하고 IRF510N MOSFET 트랜지스터로 드라이브합니다.
트랜지스터를 스위치로 쓰는데 N채널이 낫습니다. IRF510N은 아두이노랑 쓸 때 꽤 인기 있습니다. 게이트와 소스 사이에 5V 전압을 걸면 드레인과 소스 사이가 닫힌 스위치처럼 되고, 바이폴라 트랜지스터처럼 저항이 필요 없습니다.
전압이 안 걸리면 트랜지스터는 열린 스위치처럼 동작하고, 이렇게 솔레노이드를 제어합니다.
다른 예제에선 1채널 릴레이 모듈을 쓰는데, 3.3V로 작동하고 입력 제어는 트랜지스터 제어랑 같습니다. 유일한 차이는 반전된다는 점입니다(코드에서 확인).
알고리즘 흐름도
이해하기 쉽게 흐름도 그렸습니다. 완전히 상세하진 않지만 코드 개요입니다.
참고: 릴레이로 250VAC까지 어떤 전자 잠금장치든 제어 가능합니다. 솔레노이드 제어용으로도 쓸 수 있습니다.
코드는 완전히 똑같습니다. 잠금 해제할 때 (LOW와 HIGH)만 바꾸면 됩니다.
중요: 처음엔 코드 업로드하고 패스코드 바꾼 뒤 주석 처리된 줄들(라인 62-64, setup 안에서 찾아보세요) 주석 해제하고 다시 업로드해야 EEPROM에서 패스코드 읽을 수 있습니다. 이건 한 번만 하면 됩니다.
코드 길이도 바꿀 수 있습니다. 처음엔 4자리로 만들었는데 기본 코드에서 변경 가능합니다. 코드에서 '4' 대신 sizeof(code)를 써서 그렇습니다. 4자리 코드를 6자리로 바로 바꿀 순 없고, 소스 코드에서 초기 패스코드부터 바꿔야 합니다.
테스트
아래 사진들로 테스트 과정 확인하세요. 넘겨가며 보세요.
솔레노이드든 릴레이든 테스트는 똑같고, 푸시 버튼으로 안에서 열 수 있습니다(원치 않으면 빼세요).
도움 되길 바랍니다. 문제나 질문 있으면 댓글 남겨주세요.
코드
Keypad_doorlock_solenoid.ino 아두이노
처음엔 코드 업로드하고 라인 62-64 주석 해제 후 재업로드하세요. 한 번만 하면 됩니다.
/* 4x4 키패드 매트릭스, LCD I2C, IRF510N 트랜지스터, 푸시 버튼과 작동
* 트랜지스터가 솔레노이드 잠금장치 드라이브, 올바른 코드나 내부 푸시 버튼으로 열림
* 키패드로 직접 코드 변경 가능, 코드 재업로드 불필요
* 코드는 EEPROM에 저장
* 자세한 건 www.surtrtech.com 참조 */
#include <Keypad.h>
#include <EEPROM.h>
#include <LiquidCrystal_I2C.h>
#define Solenoid 11 // 실제로는 솔레노이드 제어하는 트랜지스터 게이트
#define O_Button 10 // 푸시 버튼
#define I2C_ADDR 0x27 // I2C 주소, 먼저 스캔 코드로 주소 확인 (여기선 0x27)
#define BACKLIGHT_PIN 3 // LCD 핀 선언
#define En_pin 2
#define Rw_pin 1
#define Rs_pin 0
#define D4_pin 4
#define D5_pin 5
#define D6_pin 6
#define D7_pin 7
const byte numRows = 4; // 키패드 행 수
const byte numCols = 4; // 키패드 열 수
char keymap[numRows][numCols] = {
{'1', '2', '3', 'A'},
{'4', '5', '6', 'B'},
{'7', '8', '9', 'C'},
{'*', '0', '#', 'D'}
};
char keypressed; // 눌린 키 저장, 자주 변함
char code[] = {'6', '6', '0', '1'}; // 기본 코드, 변경 가능하거나 'n' 자리수로 만들 수 있음
char code_buff1[sizeof(code)]; // 새 키 저장용
char code_buff2[sizeof(code)]; // 새 키 다시 저장해 이전 것과 비교
short a = 0, i = 0, s = 0, j = 0; // 나중에 쓸 변수들
byte rowPins[numRows] = {9, 8, 7, 6}; // 행 0-3, 핀 수정 시 여기도 수정
byte colPins[numCols] = {5, 4, 3, 2}; // 열 0-3
LiquidCrystal_I2C lcd(I2C_ADDR, En_pin, Rw_pin, Rs_pin, D4_pin, D5_pin, D6_pin, D7_pin);
Keypad myKeypad = Keypad(makeKeymap(keymap), rowPins, colPins, numRows, numCols);
void setup() {
lcd.begin(16, 2);
lcd.setBacklightPin(BACKLIGHT_PIN, POSITIVE);
lcd.setBacklight(HIGH); // 백라이트 켜기
lcd.home();
lcd.print("Standby"); // 대기 시 표시 메시지, 변경 가능
pinMode(Solenoid, OUTPUT);
pinMode(O_Button, INPUT);
// for(i = 0; i < sizeof(code); i++) {
// // 처음 코드 업로드할 땐 주석 유지
// EEPROM.get(i, code[i]); // 코드 업로드하고 변경해 EEPROM에 저장
// }
// // 이 for 루프 주석 해제하고 재업로드 (한 번만 함)
}
void loop() {
keypressed = myKeypad.getKey(); // 키 입력 지속 대기
if (keypressed == '*') { // *로 잠금 해제
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Enter code"); // 메시지 표시
GetCode(); // 코드 입력 함수
if (a == sizeof(code)) // GetCode 함수가 a에 값 할당 (코드 배열 크기와 같으면 정답)
OpenDoor(); // 코드 맞으면 잠금 해제
else {
lcd.clear();
lcd.print("Wrong"); // 틀린 코드 메시지
}
delay(2000);
lcd.clear();
lcd.print("Standby"); // 대기 모드 복귀 메시지
}
if (keypressed == '#') { // 코드 변경
ChangeCode();
lcd.clear();
lcd.print("Standby"); // 완료 시 대기 모드 복귀
}
if (digitalRead(O_Button) == HIGH) { // 푸시 버튼으로 열기
digitalWrite(Solenoid, HIGH);
delay(3000); // 3초간 열림, 변경 가능
digitalWrite(Solenoid, LOW);
}
}
void GetCode() {
// 코드 입력 시퀀스
i = 0; a = 0; j = 0; // 모든 변수 0으로
while (keypressed != 'A') { // 사용자가 A 누르면 확인, 아니면 계속 입력
keypressed = myKeypad.getKey();
if (keypressed != NO_KEY && keypressed != 'A') {
// 입력된 문자가 A도 아니고 "아무것도 아님"도 아니면
lcd.setCursor(j, 1); // LCD에 '*' 표시, 위치는 j로 제어
lcd.print("*");
j++;
if (keypressed == code[i] && i < sizeof(code)) {
// 입력 문자가 맞으면
a++; // a와 i 증가시켜 다음 문자 검증
i++;
} else
a--; // 틀리면 a 감소, 코드 배열 크기와 같아질 수 없음
}
}
keypressed = NO_KEY;
}
void ChangeCode() {
// 코드 변경 시퀀스
lcd.clear();
lcd.print("Changing code");
delay(1000);
lcd.clear();
lcd.print("Enter old code");
GetCode(); // 기존 코드 먼저 검증
if (a == sizeof(code)) { // a 값 재검증
lcd.clear();
lcd.print("Changing code");
GetNewCode1(); // 새 코드 입력
GetNewCode2(); // 새 코드 재입력해 확인
s = 0;
for (i = 0; i < sizeof(code); i++) {
// 두 이전 함수에서 받은 배열 1, 2 비교
if (code_buff1[i] == code_buff2[i]) s++; // 일치할 때마다 s 증가
}
if (s == sizeof(code)) { // 정답은 항상 배열 크기
for (i = 0; i < sizeof(code); i++) {
code[i] = code_buff2[i]; // 코드 배열에 새 코드 저장
EEPROM.put(i, code[i]); // EEPROM에 저장
}
lcd.clear();
lcd.print("Code Changed");
delay(2000);
} else {
// 새 코드들 안 맞으면
lcd.clear();
lcd.print("Codes are not");
lcd.setCursor(0, 1);
lcd.print("matching !!");
delay(2000);
}
} else {
// 기존 코드 틀리면 변경 불가
lcd.clear();
lcd.print("Wrong");
delay(2000);
}
}
void GetNewCode1() {
i = 0; j = 0;
lcd.clear();
lcd.print("Enter new code"); // 새 코드 입력하고 A 누르라고 안내
lcd.setCursor(0, 1);
lcd.print("and press A");
delay(2000);
lcd.clear();
lcd.setCursor(0, 1);
lcd.print("and press A"); // 위쪽 줄에 *** 찍히는 동안 'and press A' 계속 표시
while (keypressed != 'A') { // A 누르면 확인하고 루프 탈출
keypressed = myKeypad.getKey();
if (keypressed != NO_KEY && keypressed != 'A') {
lcd.setCursor(j, 0);
lcd.print("*"); // 새 코드 입력 시 *로 표시하거나 keypressed로 바꿔 실제 키 표시 가능
code_buff1[i] = keypressed; // 문자를 배열에 저장
i++; j++;
}
}
keypressed = NO_KEY;
}
void GetNewCode2() {
// GetNewCode1과 똑같지만 이번엔 다른 배열에 저장
i = 0; j = 0;
lcd.clear();
lcd.print("Confirm code");
lcd.setCursor(0, 1);
lcd.print("and press A");
delay(3000);
lcd.clear();
lcd.setCursor(0, 1);
lcd.print("and press A");
while (keypressed != 'A') {
keypressed = myKeypad.getKey();
if (keypressed != NO_KEY && keypressed != 'A') {
lcd.setCursor(j, 0);
lcd.print("*");
code_buff2[i] = keypressed;
i++; j++;
}
}
keypressed = NO_KEY;
}
void OpenDoor() {
// 잠금 해제 함수, 3초간 열림
lcd.clear();
lcd.print("Welcome"); // 메시지 출력
digitalWrite(Solenoid, HIGH);
delay(3000);
digitalWrite(Solenoid, LOW);
}
Keypad_doorlock_relay.ino 아두이노
처음엔 코드 업로드하고 라인 62-64 주석 해제 후 재업로드하세요. 한 번만 하면 됩니다.
/* 4x4 키패드 매트릭스, LCD I2C, 릴레이 모듈, 푸시 버튼과 작동
* 릴레이로 잠금장치 제어, 올바른 코드나 내부 푸시 버튼으로 열림
* 키패드로 직접 코드 변경 가능, 코드 재업로드 불필요
* 코드는 EEPROM에 저장
* 자세한 건 www.surtrtech.com 참조 */
#include <Keypad.h>
#include <EEPROM.h>
#include <LiquidCrystal_I2C.h>
#define Relay 11 // 릴레이 제어
#define O_Button 10 // 푸시 버튼
#define I2C_ADDR 0x27 // I2C 주소, 먼저 스캔 코드로 주소 확인 (여기선 0x27)
#define BACKLIGHT_PIN 3 // LCD 핀 선언
#define En_pin 2
#define Rw_pin 1
#define Rs_pin 0
#define D4_pin 4
#define D5_pin 5
#define D6_pin 6
#define D7_pin 7
const byte numRows = 4; // 키패드 행 수
const byte numCols = 4; // 키패드 열 수
char keymap[numRows][numCols] = {
{'1', '2', '3', 'A'},
{'4', '5', '6', 'B'},
{'7', '8', '9', 'C'},
{'*', '0', '#', 'D'}
};
char keypressed; // 눌린 키 저장, 자주 변함
char code[] = {'6', '6', '0', '1'}; // 기본 코드, 변경 가능하거나 'n' 자리수로 만들 수 있음
char code_buff1[sizeof(code)]; // 새 키 저장용
char code_buff2[sizeof(code)]; // 새 키 다시 저장해 이전 것과 비교
short a = 0, i = 0, s = 0, j = 0; // 나중에 쓸 변수들
byte rowPins[numRows] = {9, 8, 7, 6}; // 행 0-3, 핀 수정 시 여기도 수정
byte colPins[numCols] = {5, 4, 3, 2}; // 열 0-3
LiquidCrystal_I2C lcd(I2C_ADDR, En_pin, Rw_pin, Rs_pin, D4_pin, D5_pin, D6_pin, D7_pin);
Keypad myKeypad = Keypad(makeKeymap(keymap), rowPins, colPins, numRows, numCols);
void setup() {
lcd.begin(16, 2);
lcd.setBacklightPin(BACKLIGHT_PIN, POSITIVE);
lcd.setBacklight(HIGH); // 백라이트 켜기
lcd.home();
lcd.print("Standby"); // 대기 시 표시 메시지, 변경 가능
pinMode(Relay, OUTPUT);
pinMode(O_Button, INPUT);
// for(i = 0; i < sizeof(code); i++) {
// // 처음 코드 업로드할 땐 주석 유지
// EEPROM.get(i, code[i]); // 코드 업로드하고 변경해 EEPROM에 저장
// }
// // 이 for 루프 주석 해제하고 재업로드 (한 번만 함)
}
void loop() {
keypressed = myKeypad.getKey(); // 키 입력 지속 대기
if (keypressed == '*') { // *로 잠금 해제
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Enter code"); // 메시지 표시
GetCode(); // 코드 입력 함수
if (a == sizeof(code)) // GetCode 함수가 a에 값 할당 (코드 배열 크기와 같으면 정답)
OpenDoor(); // 코드 맞으면 잠금 해제
else {
lcd.clear();
lcd.print("Wrong"); // 틀린 코드 메시지
}
delay(2000);
lcd.clear();
lcd.print("Standby"); // 대기 모드 복귀 메시지
}
if (keypressed == '#') { // 코드 변경
ChangeCode();
lcd.clear();
lcd.print("Standby"); // 완료 시 대기 모드 복귀
}
if (digitalRead(O_Button) == HIGH) { // 푸시 버튼으로 열기
digitalWrite(Relay, LOW); // 릴레이는 반전됨
delay(3000); // 3초간 열림, 변경 가능
digitalWrite(Relay, HIGH);
}
}
// GetCode, ChangeCode, GetNewCode1, GetNewCode2 함수는 솔레노이드 버전과 동일
void OpenDoor() {
// 잠금 해제 함수, 3초간 열림
lcd.clear();
lcd.print("Welcome"); // 메시지 출력
digitalWrite(Relay, LOW); // 릴레이는 LOW가 켜짐
delay(3000);
digitalWrite(Relay, HIGH);
}
회로도
회로도 이미지 2개 포함