고장난 무드등 심폐소생술: ATtiny85와 AI로 나만의 스마트 조명 만들기
https://s.click.aliexpress.com/e/_c4OdkTwL
Table Lamp Bedroom Room Lamps USB Plug LED Mushroom Table Lamp Living Room Dining Room Ambient Lighting (Not Glass)
Smarter Shopping, Better Living! Aliexpress.com
www.aliexpress.com
1. 발단: 뽑기 실패? 오히려 좋은 DIY의 시작
얼마 전 쿠팡에서 예쁜 버섯 모양의 LED 실내 무드등을 하나 구매했습니다. (나중에 알고 보니 알리익스프레스에서 파는 제품을 쿠팡에서 판매하는 것이더군요.)
https://s.click.aliexpress.com/e/_c4OdkTwL
Table Lamp Bedroom Room Lamps USB Plug LED Mushroom Table Lamp Living Room Dining Room Ambient Lighting (Not Glass)
Smarter Shopping, Better Living! Aliexpress.com
www.aliexpress.com

유선 리모컨 버튼을 누르면 2가지 색상의 LED가 켜지는 심플하고 예쁜 조명이었습니다. 그런데 사용한 지 얼마 되지 않아 리모컨이 고장 나버렸습니다. 흔히 말하는 '뽑기 실패'였죠. 알리에서 직접 샀다면 바로 환불했겠지만, 쿠팡 구매 건이라 처리도 애매하고 그냥 버릴까 고민했습니다.
하지만 멀쩡한 조명 본체를 버리긴 너무 아까웠습니다. 그래서 "고장 난 버튼에 내 손으로 직접 새 생명을 불어넣어 보자!"는 생각으로 DIY를 결심했습니다.
2. 부품 수급 및 목표 설정
집에 굴러다니는 부품통을 뒤져보니 적당한 재료들이 나왔습니다.
- MCU: ATtiny13이 있었으면 딱 좋았겠지만, 아쉬운 대로 가지고 있던 ATtiny85를 사용하기로 했습니다.
- 기타 부품: 저항 몇 개, 푸시버튼 1개
이왕 고치는 김에 기존보다 더 똑똑한 기능을 넣기로 목표를 세웠습니다.
- 짧게 누름 (모드 변경): 노란색 LED ➔ 흰색 LED ➔ 노란색/흰색 동시 켜짐 ➔ 꺼짐
- 길게 누름 (타이머 기능): 한 번 길게 누르면 30분 타이머 ➔ 한 번 더 길게 누르면 60분 타이머 작동 (시간이 되면 자동 종료)
3. 하드웨어(HW) 설계: AI로 초고속 골격 만들기
요즘은 기초 설계부터 머리를 쥐어뜯을 필요가 없습니다. AI에게 구현하고자 하는 기능을 설명하면 뼈대가 되는 H/W 구성과 S/W 코드를 뚝딱 만들어주기 때문입니다. 개발 속도를 획기적으로 끌어올려 주는 참 고마운 세상입니다.
처음 AI가 제안해 준 ATtiny85의 기본 핀 맵핑과 회로 구성은 다음과 같았습니다.
- Pin 1 (PB5): RESET
- Pin 2 (PB3): 대기시간 표시용 LED 1 (30분) --> PB2로 변경
- Pin 3 (PB4): 대기시간 표시용 LED 2 (60분) --> PB0로 변경
- Pin 5 (PB0): 포토커플러 1 (흰색등) --> PB1로 변경
- Pin 6 (PB1): 포토커플러 2 (노란색등) --> PB4로 변경
- Pin 7 (PB2): 푸시버튼 (내부 풀업 사용) --> PB3로 변경
- Pin 4 / 8: GND / 5V VCC
하지만 실제 PCB 보드를 설계하려고 보니, AI가 짜준 핀 배열 그대로는 배선(Routing)이 꼬여서 기판이 복잡해지는 문제가 있었습니다. 그래서 PCB 패턴을 최대한 단순하고 깔끔하게 뽑기 위해 제 입맛에 맞춰 핀 번호를 재배치했습니다.(위의 변경된 핀번호로)
4. PCB 및 3D 모델링 설계
재배치한 회로도를 바탕으로 KiCad를 이용해 PCB를 설계했습니다. 기존 등록한 회로가 생각한 것으로 그려서, 실제 만들다보니, 배선 잘못된 것을 확인하고 수정했습니다. (26.9.6, PC817 LED부분)

이 회로도를 기초로, 만든 PCB는 아래 그림과 같다. 이 회로도 조금씩 바꿨습니다. (9.6)
뒷면

앞면

PCB 출력(동판 에칭용)

3D모델링 결과



최종 PCB 만든 것.. 이렇게 만든 거버파일을 PCB제조사에 보내면 3D모델과 같이 깔끔한 PCB를 얻을수 있는데...
1장 만 만드는 것이라, 레이저 프린트와 다리미신공으로 아래 처럼, 만들었다. 실제 에칭액 몇 만원, 시간 소비 등을 생각하면 PCB제조사를 통해서 만드는 것이 가격도 싸고 정신 건강에도 좋겠다.

부품 꽂을 구멍도 뚫었다. 잘못 뚫은 부분이 보인다.
5. 소프트웨어(SW) 구현: 하드웨어에 맞춘 코드 최적화
PCB 설계를 단순화하기 위해 핀 번호를 변경했으므로, S/W의 핀 설정도 그에 맞춰 수정해 주었습니다. AI가 짜준 뼈대 코드에 변경된 핀 맵핑만 살짝 얹어주면 끝입니다. (아직 최종 테스트 전이라 추후 업데이트될 수 있습니다.)
// =========================================================
// 디버그 모드 설정 (시리얼 출력 토글)
// =========================================================
//#define DEBUG_MODE // 이 줄을 주석 처리(//)하면 모든 시리얼 출력이 비활성화됩니다.
#ifdef DEBUG_MODE
#define DEBUG_BEGIN(x) Serial.begin(x)
#define DEBUG_PRINT(x) Serial.print(x)
#define DEBUG_PRINTLN(x) Serial.println(x)
#else
#define DEBUG_BEGIN(x)
#define DEBUG_PRINT(x)
#define DEBUG_PRINTLN(x)
#endif
// =========================================================
// 핀 설정 (PCB 라우팅 최적화 반영)
const int PIN_OPTO_1 = 1;
const int PIN_OPTO_2 = 4;
const int PIN_BTN = 3;
const int PIN_T_LED_1 = 2;
const int PIN_T_LED_2 = 0;
int lightMode = 0;
int timerMode = 0;
// 테스트용 5초 세팅 (최종 시 30UL * 60UL * 1000UL 로 복구)
#define TIMER_DURATION 30UL * 60UL*1000UL
// 테스트 시 깜빡임 시작 구간을 1/5 지점(1초 남았을 때)으로 설정
#define BLINK_THRESHOLD 180000UL
unsigned long timerStartTime = 0;
unsigned long timerDuration = 0;
bool lastBtnState = HIGH;
unsigned long btnPressTime = 0;
bool isLongPressHandled = false;
// 시리얼 중복 출력 방지용 이전 상태 저장 변수
int lastLightMode = -1;
int lastTimerMode = -1;
void setup() {
pinMode(PIN_OPTO_1, OUTPUT);
pinMode(PIN_OPTO_2, OUTPUT);
pinMode(PIN_T_LED_1, OUTPUT);
pinMode(PIN_T_LED_2, OUTPUT);
pinMode(PIN_BTN, INPUT_PULLUP);
// 매크로를 통한 시리얼 초기화
DEBUG_BEGIN(115200);
updateLights();
updateTimerLEDs();
}
void loop() {
unsigned long currentMillis = millis();
checkButton(currentMillis);
handleTimer(currentMillis);
}
void checkButton(unsigned long currentMillis) {
bool currentBtnState = digitalRead(PIN_BTN);
if (currentBtnState != lastBtnState) {
currentBtnState = digitalRead(PIN_BTN);
if (currentBtnState == LOW) {
btnPressTime = currentMillis;
isLongPressHandled = false;
} else {
if (!isLongPressHandled && (currentMillis - btnPressTime) > 50) {
DEBUG_PRINTLN("\n[EVENT] Short Button Pressed");
handleShortPress();
}
}
}
if (currentBtnState == LOW && !isLongPressHandled && (currentMillis - btnPressTime) >= 1000) {
DEBUG_PRINTLN("\n[EVENT] Long Button Pressed");
handleLongPress();
isLongPressHandled = true;
}
updateLights();
updateTimerLEDs();
lastBtnState = currentBtnState;
}
void handleShortPress() {
lightMode++;
if (lightMode > 3) lightMode = 0;
if (lightMode == 0) timerMode = 0;
}
void handleLongPress() {
if (lightMode > 0) {
timerMode++;
if (timerMode > 2) timerMode = 0;
if (timerMode == 1) {
timerDuration = TIMER_DURATION;
timerStartTime = millis();
} else if (timerMode == 2) {
timerDuration = TIMER_DURATION * 2;
timerStartTime = millis();
}
}
}
void updateLights() {
if (lightMode != lastLightMode) {
#ifdef DEBUG_MODE
switch (lightMode) {
case 0: digitalWrite(PIN_T_LED_2, HIGH); delay(1000); digitalWrite(PIN_T_LED_2, LOW); delay(1000);
break;
case 1: digitalWrite(PIN_T_LED_2, HIGH); delay(1000); digitalWrite(PIN_T_LED_2, LOW); delay(1000);
digitalWrite(PIN_T_LED_2, HIGH); delay(1000); digitalWrite(PIN_T_LED_2, LOW); delay(1000);
break;
case 2: digitalWrite(PIN_T_LED_2, HIGH); delay(1000); digitalWrite(PIN_T_LED_2, LOW); delay(1000);
digitalWrite(PIN_T_LED_2, HIGH); delay(1000); digitalWrite(PIN_T_LED_2, LOW); delay(1000);
digitalWrite(PIN_T_LED_2, HIGH); delay(1000); digitalWrite(PIN_T_LED_2, LOW); delay(1000);
break;
case 3: digitalWrite(PIN_T_LED_2, HIGH); delay(1000); digitalWrite(PIN_T_LED_2, LOW); delay(1000);
digitalWrite(PIN_T_LED_2, HIGH); delay(1000); digitalWrite(PIN_T_LED_2, LOW); delay(1000);
digitalWrite(PIN_T_LED_2, HIGH); delay(1000); digitalWrite(PIN_T_LED_2, LOW); delay(1000);
digitalWrite(PIN_T_LED_2, HIGH); delay(1000); digitalWrite(PIN_T_LED_2, LOW); delay(1000);
break;
}
#endif
switch (lightMode) {
case 0: digitalWrite(PIN_OPTO_1, LOW); digitalWrite(PIN_OPTO_2, LOW); break;
case 1: digitalWrite(PIN_OPTO_1, HIGH); digitalWrite(PIN_OPTO_2, LOW); break;
case 2: digitalWrite(PIN_OPTO_1, LOW); digitalWrite(PIN_OPTO_2, HIGH); break;
case 3: digitalWrite(PIN_OPTO_1, HIGH); digitalWrite(PIN_OPTO_2, HIGH); break;
}
DEBUG_PRINT("EXT_LED = ");
if (lightMode == 0) DEBUG_PRINTLN("- , -");
else if (lightMode == 1) DEBUG_PRINTLN("ON, -");
else if (lightMode == 2) DEBUG_PRINTLN("- , ON");
else if (lightMode == 3) DEBUG_PRINTLN("ON, ON");
lastLightMode = lightMode;
}
}
void updateTimerLEDs() {
if (timerMode == 0) {
digitalWrite(PIN_T_LED_1, LOW); digitalWrite(PIN_T_LED_2, LOW);
} else if (timerMode == 1) {
digitalWrite(PIN_T_LED_1, HIGH); digitalWrite(PIN_T_LED_2, LOW);
} else if (timerMode == 2) {
digitalWrite(PIN_T_LED_1, HIGH); digitalWrite(PIN_T_LED_2, HIGH);
}
if (timerMode != lastTimerMode) {
DEBUG_PRINT("INT_LED = ");
if (timerMode == 0) DEBUG_PRINTLN("- , -");
else if (timerMode == 1) DEBUG_PRINTLN("ON, -");
else if (timerMode == 2) DEBUG_PRINTLN("ON, ON");
lastTimerMode = timerMode;
}
}
void handleTimer(unsigned long currentMillis) {
if (timerMode > 0 && lightMode > 0) {
unsigned long elapsedTime = currentMillis - timerStartTime;
unsigned long timeLeft = (elapsedTime > timerDuration) ? 0 : (timerDuration - elapsedTime);
if (timeLeft == 0) {
DEBUG_PRINTLN("\n[EVENT] Timer Expired!");
lightMode = 0; timerMode = 0;
updateLights(); updateTimerLEDs();
} else if (timeLeft <= BLINK_THRESHOLD) {
if ((currentMillis / 500) % 2 == 0) {
digitalWrite(PIN_T_LED_1, LOW); digitalWrite(PIN_T_LED_2, LOW);
} else {
if (timerMode == 1) digitalWrite(PIN_T_LED_1, HIGH);
else if (timerMode == 2) { digitalWrite(PIN_T_LED_1, HIGH); digitalWrite(PIN_T_LED_2, HIGH); }
}
} else {
if ((currentMillis / 500) % 2 == 0) {
updateTimerLEDs();
}
}
}
}
6. 펌웨어 업로드: ATtiny85에 생명 불어넣기
ATtiny85 칩에 위의 코드를 구워 넣으려면, 먼저 칩에 부트로더(Bootloader)를 올려야 합니다. 별도의 전용 프로그래머가 없어도 괜찮습니다. 집에 굴러다니는 Arduino Pro Mini 보드를 'ISP(In-System Programmer)'로 설정하여 ATtiny85와 연결하면 훌륭한 라이터기가 됩니다.
(아두이노 Pro Mini를 이용한 ISP 결선 사진 추가 예정)
7. 향후 계획
버려질 뻔했던 무드등이 타이머 기능까지 갖춘 스마트한 조명으로 재탄생할 준비를 마쳤습니다. 부품 실장 및 최종 동작 테스트가 완료되면 내용과 영상을 추가로 업데이트하겠습니다.
추후, KiCad로 작성한 회로도, PCB 파일, Gerber 파일도 함께 공유할 예정입니다. 뻘짓을 하려는 분이 계시다면.... !
8. 현재의 문제점. 추가 보완 필요.
생각지 않았던 부분이.... 포토커플러에 고휘도 LED 연결하면 안됨. 작은 LED정도만 가능.
다른 방식을 더 추가해야겠다..
'전자회로 > 전자회로 일반' 카테고리의 다른 글
| FTDI 와 Arduino pro mini 를 연결할 때, DTR핀이 없는 것 해결 방법 (0) | 2026.08.17 |
|---|---|
| 노부모님을 위한 DIY SOS 비상벨 만들기 (ESP32 + RF433Mhz) (1) | 2026.03.30 |
| [잡담] 다리미를 이용한 PCB만드는 꿀팁 및 제일 좋은 방법 (0) | 2020.02.09 |
| 태양전지 모듈 연결 (0) | 2019.08.18 |