아두이노 나노로 구동하는 자동 슬라이딩 차고문 제어기
부품 및 소모품
| 아두이노 나노 R3 |
× |
1 |
| 브레드보드 (나노 및 OLED 장착용) |
× |
1 |
| Adafruit OLED 디스플레이 (0.96", 128x64, SPI) |
× |
1 |
| 릴레이 모듈 4채널 (3개 사용) |
× |
1 |
| 브레드보드 파워 서플라이 5V/3.3V (220V→5V) |
× |
1 |
| 푸시버튼 스위치 12mm (리셋/알람 해제용) |
× |
1 |
프로젝트 배경
기존 차고문 리모컨이 작동하지 않아 모터 커버를 열어보니, 도마뱀이 220V 단자 사이에서 감전돼 제어 보드가 완전히 타버린 것을 발견했습니다. 이를 계기로 아두이노 기반으로 제어 유닛을 완전히 재설계했습니다.
도마뱀 감전으로 탄 원본 제어 보드
⚠️ 안전 경고
이 프로젝트는 220V(또는 110V) 고전압을 직접 다룹니다. 숙련되지 않은 경우 위험할 수 있습니다. 반드시 시뮬레이션 후 실제 모터 회로에 연결하세요.
시스템 구성
아두이노 나노 기반 메인 제어 유닛이 다음을 담당합니다:
- 모터 정/역 회전 제어 (열림/닫힘)
- 신호등(램프) 제어
- 리미트 스위치(완전 열림/닫힘) 감지
- 안전 포토셀(장애물 감지) 감지
- 무선 리시버의 "열림" 명령 수신
- OLED 디스플레이로 상태 모니터링
핀아웃 요약
| 핀 |
기능 |
비고 |
| D2 |
포토셀 (장애물 감지, INPUT_PULLUP) |
활성 HIGH |
| D3 |
닫힘 리미트 스위치 (INPUT_PULLUP) |
닫힘 시 HIGH |
| D4 |
무선 리시버 "열림" 명령 (INPUT_PULLUP) |
명령 시 LOW |
| D5 |
릴레이 1: 신호등 (BOMBETA) |
LOW = ON |
| D6 |
릴레이 2: 모터 열림 (MOTOR_OBRIR) |
LOW = ON |
| D7 |
열림 리미트 스위치 (INPUT_PULLUP) |
열림 시 HIGH |
| D8 |
OLED RESET |
|
| D9 |
OLED DC |
|
| D10 |
OLED CS |
|
| D11 |
OLED MOSI |
|
| D12 |
릴레이 3: 모터 닫힘 (MOTOR_TANCAR) |
LOW = ON |
| D13 |
OLED CLK |
|
OLED 디스플레이 표시 정보
- 현재 실행 단계 (메인 루프, 열림 시퀀스, 닫힘 시퀀스, 대기 중...)
- 주요 동작 경과 시간 (열림, 대기, 닫힘)
- 포토셀 상태, 리미트 스위치 상태, 열림 명령 수신 여부
- 고장(AVARIA) 플래그 상태
플로우차트 기반 로직
세 개의 주요 상태 시퀀스로 구성:
- BUCLE_PRINCIPAL (메인 루프): "열림" 명령 대기, 센서 상태 표시, 고장 시 신호등 점멸
- SEQUENCIA_OBERTURA (열림 시퀀스): 모터 정회전, 열림 리미트 감지 시 PORTA_OBERTA로, 최대 시간 초과 시 고장 플래그 설정, 신호등 느린 점멸
- PORTA_OBERTA (완전 열림 대기): 30초 대기 후 자동 닫힘 시퀀스 진입, 열림 명령 재수신 시 즉시 닫힘, 신호등 특수 패턴 점멸
- SEQUENCIA_TANCAMENT (닫힘 시퀀스): 모터 역회전, 닫힘 리미트 감지 시 메인 루프로, 포토셀 감지 시 즉시 열림 시퀀스로 복귀, 열림 명령 수신 시 열림 시퀀스로, 최대 시간 초과 시 고장, 신호등 빠른 점멸
코드 (영문 주석 버전)
/*********************************************************************
* 자동 슬라이딩 도어 제어기 - English Version
*********************************************************************/
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
// 제어 핀 정의
int FOTOCELULA = 2; // 포토셀
int BOMBETA = 5; // 릴레이 1: 신호등
int TANCAT = 7; // 닫힘 리미트
int OBERT = 3; // 열림 리미트
int MOTOR_OBRIR = 6; // 릴레이 2: 모터 열림
int MOTOR_TANCAR = 12; // 릴레이 3: 모터 닫힘
int OBRIR = 4; // 무선 리시버 열림 명령
// OLED 핀 (SPI)
#define OLED_MOSI 11
#define OLED_CLK 13
#define OLED_DC 9
#define OLED_CS 10
#define OLED_RESET 8
Adafruit_SSD1306 display(OLED_MOSI, OLED_CLK, OLED_DC, OLED_RESET, OLED_CS);
// 타이밍 변수
unsigned long TEMPS_PORTA_OBERTA = 20000; // 열림 유지 시간
unsigned long TEMPS_MAXIM_RECORREGUT = 15000; // 최대 이동 시간
int interval = 1000;
int TEMPS_DISPLAY = 1;
int DURADA = 300;
unsigned long previousMillis = 0;
unsigned long currentMillis = 0;
unsigned long TEMPS_LLUM = 1000;
boolean LLUM = false;
boolean AVARIA = false;
void setup() {
// 모터/램프 OFF (릴레이 HIGH = OFF)
digitalWrite(MOTOR_OBRIR, HIGH);
digitalWrite(MOTOR_TANCAR, HIGH);
digitalWrite(BOMBETA, HIGH);
// 핀 모드 설정
pinMode(FOTOCELULA, INPUT_PULLUP);
pinMode(TANCAT, INPUT_PULLUP);
pinMode(OBERT, INPUT_PULLUP);
pinMode(OBRIR, INPUT_PULLUP);
pinMode(BOMBETA, OUTPUT);
pinMode(MOTOR_OBRIR, OUTPUT);
pinMode(MOTOR_TANCAR, OUTPUT);
// OLED 초기화
Serial.begin(9600);
display.begin(SSD1306_SWITCHCAPVCC);
display.display(); delay(1000);
display.clearDisplay(); display.display();
display.setTextSize(1); display.setTextColor(WHITE);
}
void loop() {
BUCLE_PRINCIPAL:
delay(2000);
previousMillis = millis();
// 열림 명령 대기 중 센서 상태 표시
while (digitalRead(OBRIR) == HIGH) {
updateDisplay("BUCLE PRINCIPAL");
if (AVARIA && (millis() - previousMillis < 60000)) digitalWrite(BOMBETA, LOW);
else digitalWrite(BOMBETA, HIGH);
}
goto SEQUENCIA_OBERTURA;
SEQUENCIA_OBERTURA:
interval = 28000; // 최대 열림 시간 (28초)
previousMillis = millis();
digitalWrite(MOTOR_OBRIR, LOW); // 모터 열림 ON
digitalWrite(BOMBETA, LOW); // 신호등 ON
delay(1000);
A:
if (digitalRead(OBERT) == HIGH) goto PORTA_OBERTA; // 열림 완료
// 열림 명령 재수신 시 즉시 닫힘 시퀀스로
if (digitalRead(OBRIR) == LOW) {
digitalWrite(MOTOR_OBRIR, HIGH); digitalWrite(BOMBETA, HIGH);
delay(1000);
while (digitalRead(OBRIR) == HIGH) {}; // 명령 해제 대기
goto SEQUENCIA_TANCAMENT;
}
// 시간/디스플레이 업데이트
currentMillis = millis();
TEMPS_DISPLAY = (currentMillis - previousMillis) / 1000;
updateDisplay("Obrint porta", TEMPS_DISPLAY);
// 타임아웃 → 고장
if ((currentMillis - previousMillis) >= interval) {
digitalWrite(MOTOR_OBRIR, HIGH); AVARIA = true;
}
// 신호등 느린 점멸 (1초 주기)
if ((currentMillis - TEMPS_LLUM) >= 1000) {
LLUM = !LLUM; digitalWrite(BOMBETA, LLUM); TEMPS_LLUM = currentMillis;
}
goto A;
SEQUENCIA_TANCAMENT:
interval = 28000; // 최대 닫힘 시간
previousMillis = millis();
digitalWrite(MOTOR_TANCAR, LOW); digitalWrite(BOMBETA, LOW);
if (digitalRead(TANCAT) == LOW) delay(1000);
B:
if (digitalRead(TANCAT) == HIGH) { // 닫힘 완료
digitalWrite(MOTOR_TANCAR, HIGH); digitalWrite(BOMBETA, HIGH);
goto BUCLE_PRINCIPAL;
}
// 열림 명령 수신 → 열림 시퀀스
if (digitalRead(OBRIR) == LOW) {
digitalWrite(MOTOR_TANCAR, HIGH); digitalWrite(BOMBETA, HIGH);
delay(1000);
while (digitalRead(OBRIR) == HIGH) {};
goto SEQUENCIA_OBERTURA;
}
// 포토셀 감지 → 즉시 열림 시퀀스 (안전)
if (digitalRead(FOTOCELULA) == HIGH) {
digitalWrite(MOTOR_TANCAR, HIGH); delay(1000);
goto SEQUENCIA_OBERTURA;
}
// 시간/디스플레이 업데이트
currentMillis = millis();
TEMPS_DISPLAY = (currentMillis - previousMillis) / 1000;
updateDisplay("Tancant porta", TEMPS_DISPLAY);
// 타임아웃 → 고장
if ((currentMillis - previousMillis) >= interval) {
digitalWrite(MOTOR_TANCAR, HIGH); AVARIA = true; goto BUCLE_PRINCIPAL;
}
// 신호등 빠른 점멸 (300ms 주기)
if ((currentMillis - TEMPS_LLUM) >= 300) {
LLUM = !LLUM; digitalWrite(BOMBETA, LLUM); TEMPS_LLUM = currentMillis;
}
goto B;
PORTA_OBERTA:
interval = 30000; // 열림 유지 30초
previousMillis = millis();
digitalWrite(MOTOR_OBRIR, HIGH); delay(500);
C:
if (digitalRead(OBRIR) == LOW) goto SEQUENCIA_TANCAMENT; // 즉시 닫힘
// 시간/디스플레이 업데이트
currentMillis = millis();
TEMPS_DISPLAY = (currentMillis - previousMillis) / 1000;
updateDisplay("Porta Oberta completament", TEMPS_DISPLAY);
// 30초 경과 → 자동 닫힘
if ((currentMillis - previousMillis) >= interval) goto SEQUENCIA_TANCAMENT;
// 특수 점멸 패턴 (짧은 ON 100ms, 긴 OFF 2초)
if (!LLUM) DURADA = 100; else DURADA = 2000;
if ((currentMillis - TEMPS_LLUM) >= DURADA) {
LLUM = !LLUM; digitalWrite(BOMBETA, LLUM); TEMPS_LLUM = currentMillis;
}
goto C;
}
// 디스플레이 업데이트 헬퍼 함수 (실제 코드에선 인라인으로 전개됨)
void updateDisplay(const char* status, int seconds) {
display.clearDisplay();
display.setTextSize(1); display.setCursor(0, 0); display.println(status);
display.setTextSize(2); display.setCursor(90, 0); display.println(seconds);
display.setTextSize(1); display.setCursor(0, 20);
display.print("Fotocel.lula: "); display.println(digitalRead(FOTOCELULA));
display.print("Sensor TANCAT: "); display.println(digitalRead(TANCAT));
display.print("Sensor OBERT: "); display.println(digitalRead(OBERT));
display.print("Ordre OBRIR: "); display.println(!digitalRead(OBRIR));
display.display();
}
회로도 및 참고자료
- 프리칭(Fritzing) 회로도 파일 제공
- 핀아웃 상세 텍스트 파일
- 하드웨어 추가 회로도 (릴레이 드라이버 보강 등)
개선 포인트: 원본 상용 제어기에 없던 안전 기능(포토셀 기반 즉시 역주행, 이동 타임아웃 감지, 상태 표시)을 추가했습니다. OLED는 내부 커버 안에 장착돼 평소엔 보이지 않으며, 설정/튜닝 시에만 활용됩니다.