간편한 아두이노 스톱워치: 버튼 작동 크로노미터 제작하기
구성품 및 부품
| 부품 |
수량 |
비고 |
| Arduino UNO |
1 |
어떤 아두이노든 가능, 아두이노가 아닐 수도 있음 |
| 버튼 (래칭 타입 아님) |
1 |
풀다운 저항 필요 (1kΩ 사용) |
| 저항 10kΩ |
1 |
|
앱 및 온라인 서비스
프로젝트 소개
재미있는 프로젝트를 진행하던 중 버튼을 누를 때마다 작동하는 아두이노 크로노미터가 필요했습니다. 복잡한 건 아니지만, 이 코드가 다른 분들 프로젝트에도 유용할 것 같아 공유합니다. 아마 1시간 정도 코딩 시간을 아껴드릴 겁니다.
EEPROM
전원을 끊거나 보드를 리셋해도 정보를 잃지 않는 아두이노의 비휘발성 메모리입니다. #include <EEPROM.h> 라이브러리를 사용하며 기본 설치되어 있습니다.
인터럽트 (Interrupts)
아두이노 내부에서 작업이 진행 중이어도 특정 핀이 HIGH가 되면 하던 일을 멈추고 ISR(인터럽트 서비스 루틴) 함수를 실행하게 하는 기능입니다. 별도 라이브러리 불필요.
attachInterrupt(digitalPinToInterrupt(interruptPin), refreshTime, CHANGE);로 인터럽트 핀의 로직 레벨이 변할 때마다(LOW→HIGH, HIGH→LOW) 함수가 트리거됩니다. 풀다운 저항 덕분에 평소엔 LOW 상태를 유지합니다.
프로젝트 코드
정의 및 인클루드
#include <EEPROM.h>
#define interruptPin 2
#define eeAddress 0 // 아두이노 EEPROM 주소 (내부 비휘발성 메모리)
float starttime = 0;
float endtime = 0;
float resulttime = 0, oldresulttime = 0;
EEPROM 라이브러리 포함, 버튼을 핀 2에 연결. EEPROM 주소 0에 시간 저장 (float = 4바이트). 아두이노 우노는 1024바이트(ATmega328) EEPROM을 가지며, 정리 차원에서 주소 0을 사용했습니다.
setup()
void setup() {
Serial.begin(9600);
while (!Serial) { ; } // 네이티브 USB 포트용 대기
Serial.println("Serial comms are ready sir!:.. bip bip");
attachInterrupt(digitalPinToInterrupt(interruptPin), refreshTime, CHANGE);
}
loop()
void loop() { } // 여기에 본인 코드 추가
기타 함수들
void refreshTime() {
if (digitalRead(interruptPin)) { // HIGH: 버튼 눌림 시작
starttime = millis();
Serial.print("Start time: ");
Serial.println(starttime);
} else if (digitalRead(interruptPin) == LOW) { // LOW: 버튼 뗌
endtime = millis();
resulttime = endtime - starttime;
Serial.print("End time: ");
Serial.println(endtime);
Serial.print("Result time: ");
Serial.println(resulttime);
WhatsNewEeprom(); // EEPROM에 저장
}
}
void WhatsNewEeprom() {
Serial.println("-----------checking eeprom");
EEPROM.get(eeAddress, oldresulttime);
Serial.print("oldresulttime"); Serial.println(oldresulttime);
Serial.print("resulttime"); Serial.println(resulttime);
if (resulttime != oldresulttime) {
EEPROM.put(eeAddress, resulttime);
Serial.print("-----Ding! New time recorded in eeprom: "); Serial.println(resulttime);
} else {
Serial.println("No new time found, eeprom was not disturbed");
}
}
인터럽트 핀 상태 변화 시 실행되는 함수입니다. 버튼 누름 시작(HIGH) 시점과 떼는 시점(LOW)을 감지해 millis()로 시간 차이를 구하고, EEPROM에 저장된 이전 값과 비교해 새로운 기록일 때만 갱신합니다.
보너스: 버튼 디바운싱
아두이노 처리 속도에 비해 사람은 매우 느려, 버튼 한 번 누르는 동안 신호가 여러 번 HIGH/LOW로 튕깁니다(bouncing). 이를 해결하는 방법:
- 소프트웨어 디바운싱
- 하드웨어 디바운싱: 커패시터 사용! (센스핀과 GND 사이에 220nF 병렬 연결)
커패시터가 빠른 전압 변화를 흡수해 튐 현상을 방지합니다.
코드
아두이노 크로노미터 (Arduino)
// coded by Javier Muñoz 23/10/2017
// questions to javimusama@gmail.com
#include <EEPROM.h>
#define interruptPin 2
#define eeAddress 0
float starttime = 0;
float endtime = 0;
float resulttime = 0, oldresulttime = 0;
void setup() {
Serial.begin(9600);
while (!Serial) { ; }
Serial.println("Serial comms are ready sir!:.. bip bip");
attachInterrupt(digitalPinToInterrupt(interruptPin), refreshTime, CHANGE);
}
void loop() {
// your stuff here
// (eeprom stored) last time measured in milliseconds "float resulttime"
}
void refreshTime() {
if (digitalRead(interruptPin)) {
starttime = millis();
Serial.print("Start time: ");
Serial.println(starttime);
} else if (digitalRead(interruptPin) == LOW) {
endtime = millis();
resulttime = endtime - starttime;
Serial.print("End time: ");
Serial.println(endtime);
Serial.print("Result time: ");
Serial.println(resulttime);
WhatsNewEeprom();
}
}
void WhatsNewEeprom() {
Serial.println("-----------checking eeprom");
EEPROM.get(eeAddress, oldresulttime);
Serial.print("oldresulttime"); Serial.println(oldresulttime);
Serial.print("resulttime"); Serial.println(resulttime);
if (resulttime != oldresulttime) {
EEPROM.put(eeAddress, resulttime);
Serial.print("-----Ding! New time recorded in eeprom: "); Serial.println(resulttime);
} else {
Serial.println("No new time found, eeprom was not disturbed");
}
}
회로도
풀다운 저항을 사용한 버튼 간단 연결