나는 아이 둘을 키운다. 그 중 막내 아들은 아직 미취학 아동이라서인지 밤에 무서움이 많아, 늘 LED 등을 켜고 잔다. 그런데 당연하게도 잠이 들면 LED 를 끄지 않는다 -_-;;

 

요즘 시간이 많이 남아서, 30분이 지나면 자동으로 꺼지도록 만들어 보았다. 그 과정에서 여러 삽질을 했고, 이를 기록하고자 한다.

 

 

어떤 것들로 만들어 볼까?

 

 

우선 부피가 되도록 작았으면 하는 마음에 구매해 두었던 ATTINY 45 칩을 이용하자. 절대 ARDUINO NANO 나 MICRO 가 아까워서 그런게 아니야... 그리고 베터리가 문제인데, 아무래도 LED 등을 켜야 하므로 집에서 놀고 있던 26650 베터리를 2개 병렬로 이용할 것이다.

 

참고로 LED 는 이렇게 생긴 녀석이다. 

물론 별도로 구매한건 아니고 ㅎ, 문화센터라든지 어디가서 무언가를 만들어 오면서 집에 쌓여있던 것들이다. 보통 길이가 1 미터 정도 되는것 같던데, 3개를 모두 연결하였다. 대략 3 미터?

 

그 외에는 4.2v 의 리튬이온 충전모듈과 저항. 그리고 불의 밝기 조절을 위한 트랜지스터가 필요하다.

 

 

어떤 동작을 할 것인가?

 

간단하다. 버튼은 총 3개면 된다. 끄고 켤 수 있는 전원버튼 하나. 그리고 밝기 조절을 위한 버튼 두개. 근데 그냥 만들면 되는데,, 우리같은 공돌이들은 또 욕심이 있지 않은가? 초저전력으로 구성할 것이다. 사용하지 않을때는 저전력 모드로 진입하여 베터리 사용량을 최저로 해야한다. 자료를 좀 찾아보니 대기전력이 0.25mA 정도밖에 되지 않는다더라. 대략 0.001 와트?!

 

전원버튼이 해야 하는 역할은 다음과 같다. 전원이 꺼져 있는 상태라면, 버튼이 눌렸을때 전원을 켠다. 그리고 TIMER 를 돌리고 30분 뒤에 자동으로 꺼진다. 켜져 있는 상태라면, 버튼이 눌렸을때 전원을 끈다.

이 정도 로직이면, 눈 감고 발로도 만들지 ㅎㅎ

 

 

아니, 왜 안되는데?!!

 

상당한 삽질을 했다. 일단 setup() 함수에서 대기모드에 들어간다. 그리고 전원버튼이 눌리면 interrupt 를 발생하고, sleep mode 에서 빠져나오는 코드를 짠다. 그리고 30분이 지나거나, 다시 전원 버튼이 누르면 sleep mode 로 들어간다.

이 간단한 로직이 제대로 동작하지 않고 개판이다. ATTINY 45 라는 요 녀석은 핀이 너무 없다. SoftwareSerial 로 print 를 찍어가며 디버깅을 해 보려고 해도 핀이 부족하여 불가능하다. 겁나 답답하네.

 

 

우선 ATTINY 45 에서 PB2 를 전원 스위치로 쓸 것이므로, 절전모드에서 빠져나가는 INTERRUPT 설정을 다음과 같이 한다.

  cli();
  GIMSK = 0b00100000;
  PCMSK = 0b00000100;
  sei();

 

PCMSK 의 0번째 비튼은 PB0 을 의미한다. PB2 는 세 번째 비트가 되는 셈이지. 그리고 interrupt 가 발생했을때, 아래 함수가 호출된다.

 

ISR(PCINT0_vect)
{
  // do something
}

 

너무나 화가난다. 의도한대로 동작하지 않는다. 바쁠수록 돌아가라고 했나? 

대기모드와 INTERRUPT 가 어떻게 돌아가는지 확인하기 위해서, 빈 프로젝트를 하나 만들고 해당 코드만 넣었다. 그리고 Serial 로 찍어 보았다. 나는 충격에 빠졌다. 내가 알고 있던것과 동작이 다르다.

 

 

 

내가 잘못 알고 있던것들과 실제 동작

 

첫번째로는 대기 모드로 진입하고, 빠져나가는 로직이 전혀 다르다...

 

  // 대기모드 진입
  sleep_enable();  
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);
  sleep_cpu();

  // woke up
  start(); // 내가 만든 함수

 

 

sleep_cpu() 를 호출하는 순간 대기모드로 빠진다. 80x86 아키텍쳐로 설명하자면, EIP 레지스터가 해당 주소에서 멈춰 있다고 생각하면 쉽다. 그리고 INTERRUPT 가 발생하면, 대기모드가 풀린다. 그 어떤 INTERRUPT 든 발동되면 대기모드가 풀린다. 나의 경우  PCMSK 에서 PB2 만 INTERRUPT 로 정의하였다. 그러면 어떻게 되는가?

코드는 sleep_cpu() 아래 코드부터 시작한다. 마치 '얼음 땡' 과 같다. 그러면 아래 함수는 어떻게 되는가?

 

ISR(PCINT0_vect) {
}

 

이건 대기모드와 전혀 관계없다. 그냥 INTERRUPT 가 발생하였으니 호출된다. 난 이 부분의 동작을 오해하고 있었고, 그로 인해서 원하는대로 돌아가지 않았던것...

 

정리하자면, sleep_cpu() 를 호출함과 동시에 freeze 된다. 이후 어떤 인터럽트라도 발생하면, 프리징이 풀린다. 더불어 당연히 INTERRUPT 가 발생하였으니 ISR CALLBACK 이 호출된다.

이것을 고려해서 모든 코드를 다 뜯어 고치자..ㅠㅠ

 

 

또 다른 함정...

 

잘 돌아 가는것 같은데, 시간이 되어서 LED 가 꺼지고나면 다시 켜지지가 않는다. INTERRUPT 를 발생해도 대기모드에서 빠져나오지를 못한다.

 

삽질한 결과 알아낸 사실은 INTERRUPT 함수의 CONTEXT 에서 대기모드로 들어가면 안된다. 영원히 죽어 버린다. 정확히 분석한것은 아니지만, 현상에 대해서 쉽게 설명하면 다음과 같다.

 

loop() 함수를 호출하는 thread 와 INTERRUPT 함수를 호출하는 thread 는 별개다. 별개의 context 이다. sleep_mode() 를 호출하면, 해당 context 가 freeze 된다. 그러므로 INTERRUPT 함수 내에서 sleep_mode() 를 호출하면, 영원히 멈춘다. 그러면 어떻게 해야 하는가? sleep_mode() 를 loop() 함수의 context 에서 실행해야 한다.

 

요컨데, 특정 버튼을 눌러서 INTERRUPT 를 발생시키고 sleep_mode() 에 진입하게 하려면, 임의의 FLAG 를 하나 만들고 INTERRUPT 핸들러에서는 이 FLAG 값만 수정한다. 이후 loop() 함수에서 FLAG 를 읽고 직접 sleep_mode() 를 호출하게 해야 한다. 그래야 이후 버튼을 다시 눌렀을때 깨어날 수 있다.

 

 

 

 

 

왜 시간이 이상하지?

 

코드를 다 뜯어 고치고 나니, 동작을 잘 한다. 전체 소스코드는 아래에서 확인이 가능하다. 다만 nano 에서 테스트 한 흔적에 주의.

 

https://github.com/und3rs/arduino/blob/main/NightLight2/NightLight2.ino

 

arduino/NightLight2/NightLight2.ino at main · und3rs/arduino

Contribute to und3rs/arduino development by creating an account on GitHub.

github.com

 

난 분명 10초 뒤에 꺼지도록 하였는데, 5초에 꺼진다. 왜 절반???

이유는.. 내가 사용하는 ATTINY 가 부트로더를 올릴 당시에 Clock 을 16 Mhz 로 했었나보다. 그것도 모르고 코드를 올릴때 Clock 을 8 Mhz 로 올렸다. Clock 이 16 Mhz 라는것은 16 백만번 진동해야 1초라는 것이고, 8 Mhz 라는 것은 8 백만번 진동해야 1초라는 것이다. 그러니 이 사단이 난 것.

 

 

드디어 완성

 

이젠 정말 모든 동작이 잘 된다. 오늘도 별것 아니라고 생각한것을 하면서 많은 것을 깨달았다. :)

 

 

ㅅㅂ... 아크릴로 케이스 만드는게 전체 시간의 50%는 사용한것 같다. 1T 짜리인데도 가공이 너무 힘들다...

뭐 좋은 방법 없을까......