#include <TM1637Display.h>
/* ===== 핀/설정 ===== */
#define TM_CLK 2
#define TM_DIO 3
#define BTN_MODE 4
#define BTN_START 5
#define BTN_UP 6
#define BTN_DOWN 7
#define BUZZER_PIN 9
const uint8_t DISPLAY_BRIGHTNESS = 7; // 0~7
const bool USE_PASSIVE_BUZZER = false; // Passive면 true
const uint16_t DEBOUNCE_MS = 35;
const uint16_t LONG_PRESS_MS = 1200;
const uint16_t RESET_PRESS_MS = 2000;
const uint16_t DEFAULT_TIMER_MIN = 1;
const uint16_t MAX_MINUTES = 99;
/* ===== 전역 ===== */
TM1637Display display(TM_CLK, TM_DIO);
bool running = false;
bool colon_state = true;
uint32_t lastTick = 0;
uint32_t lastBlink = 0;
bool blinkOn = true;
int32_t timer_seconds = DEFAULT_TIMER_MIN * 60;
// 설정 모드 상태
bool inSetting = false;
uint8_t setIdx = 0;
int32_t editing_seconds = 0;
/* ===== 버튼 구조/생성자 ===== */
struct Btn {
uint8_t pin;
bool last;
uint32_t tChange;
bool longFired;
Btn(uint8_t p) : pin(p), last(true), tChange(0), longFired(false) {}
Btn() : pin(0), last(true), tChange(0), longFired(false) {}
};
Btn bMode(BTN_MODE), bStart(BTN_START), bUp(BTN_UP), bDown(BTN_DOWN);
/* ===== 표시 ===== */
void showMMSS_raw(uint16_t mm, uint16_t ss, bool colonOn, int8_t blinkPos = -1, bool blinkState = true){
uint8_t segs[4];
segs[0] = display.encodeDigit((mm/10)%10);
segs[1] = display.encodeDigit(mm%10);
segs[2] = display.encodeDigit((ss/10)%10);
segs[3] = display.encodeDigit(ss%10);
if(colonOn) segs[1] |= 0x80; // 가운데 콜론
if(blinkPos>=0 && !blinkState) segs[blinkPos] = 0x00;
display.setBrightness(DISPLAY_BRIGHTNESS, true);
display.setSegments(segs);
}
void showSeconds(int32_t s, bool colonOn){
if(s<0) s=0;
if(s>(int32_t)(MAX_MINUTES*60+59)) s=MAX_MINUTES*60+59;
uint16_t mm = s/60, ss=s%60;
showMMSS_raw(mm, ss, colonOn);
}
/* ===== 논블로킹 부저 ===== */
enum BeepMode { BEEP_OFF, BEEP_END };
BeepMode beepMode = BEEP_OFF;
uint32_t beepUntil = 0;
uint32_t nextToggle = 0;
bool buzOn = false;
void buzzerOn(){
if (USE_PASSIVE_BUZZER) tone(BUZZER_PIN, 2000);
else digitalWrite(BUZZER_PIN, HIGH);
buzOn = true;
}
void buzzerOff(){
if (USE_PASSIVE_BUZZER) noTone(BUZZER_PIN);
else digitalWrite(BUZZER_PIN, LOW);
buzOn = false;
}
// 종료 패턴(3초: 120ms ON / 80ms OFF 반복)
const uint16_t END_ON_MS = 120;
const uint16_t END_OFF_MS = 80;
void startEndBeep(uint16_t total_ms = 3000){
beepMode = BEEP_END;
uint32_t now = millis();
beepUntil = now + total_ms;
buzzerOn();
nextToggle = now + END_ON_MS;
}
void updateBeep(){
uint32_t now = millis();
if(beepMode == BEEP_END){
if(now >= beepUntil){
buzzerOff();
beepMode = BEEP_OFF;
}else if(now >= nextToggle){
if(buzOn){
buzzerOff();
nextToggle = now + END_OFF_MS;
}else{
buzzerOn();
nextToggle = now + END_ON_MS;
}
}
}
}
/* ===== 설정모드 유틸 ===== */
int clamp01(int v){ if(v<0) return 0; if(v>9) return 9; return v; }
void applyDigitChange(int8_t idx, int8_t delta, int32_t &sec){
int mm = sec/60, ss = sec%60;
int d0 = (mm/10)%10, d1 = mm%10, d2 = (ss/10)%10, d3 = ss%10;
if(idx==0) d0 = clamp01(d0 + delta);
else if(idx==1) d1 = clamp01(d1 + delta);
else if(idx==2){ d2 = clamp01(d2 + delta); if(d2>5) d2=5; }
else if(idx==3) d3 = clamp01(d3 + delta);
mm = d0*10 + d1; if(mm>MAX_MINUTES) mm = MAX_MINUTES;
ss = d2*10 + d3; if(ss>59) ss=59;
sec = mm*60 + ss;
}
/* ===== 버튼 스캐너 ===== */
void scanBtn(Btn &b, void (*onShort)(), void (*onLong)(), uint32_t longMs){
bool now = digitalRead(b.pin);
uint32_t t = millis();
if (now != b.last && (t - b.tChange) > DEBOUNCE_MS) {
b.tChange = t;
bool was = b.last;
b.last = now;
if (now == LOW) {
b.longFired = false;
} else {
if (!b.longFired && onShort) onShort();
}
}
if (b.last == LOW && !b.longFired && (millis() - b.tChange) > longMs) {
b.longFired = true;
if (onLong) onLong();
}
}
/* ===== 버튼 콜백 ===== */
void onModeShort(){
if(running) return;
if(inSetting){
setIdx++;
if(setIdx>3){ timer_seconds = editing_seconds; inSetting=false; }
}
}
void onModeLong(){
if(running) return;
inSetting = !inSetting;
editing_seconds = timer_seconds;
setIdx = 0;
}
void onStartShort(){
if(inSetting){
timer_seconds = editing_seconds;
inSetting=false;
return;
}
running = !running;
if (running) {
// 시작 시점 기준으로 lastTick 리셋
lastTick = millis();
}
}
void onStartLong(){
running = false;
timer_seconds = DEFAULT_TIMER_MIN * 60;
if(inSetting) editing_seconds = timer_seconds;
}
void onUpShort(){
if(inSetting) applyDigitChange(setIdx, +1, editing_seconds);
else if(!running){ int mm = timer_seconds/60; if(mm<MAX_MINUTES) mm++; timer_seconds = mm*60; }
}
void onUpLong(){
if(inSetting){ for(int i=0;i<5;i++) applyDigitChange(setIdx, +1, editing_seconds); }
else if(!running){ int mm = timer_seconds/60; mm = (mm+10>MAX_MINUTES)?MAX_MINUTES:mm+10; timer_seconds = mm*60; }
}
void onDownShort(){
if(inSetting) applyDigitChange(setIdx, -1, editing_seconds);
else if(!running){ int mm = timer_seconds/60; if(mm>0) mm--; timer_seconds = mm*60; }
}
void onDownLong(){
if(inSetting){ for(int i=0;i<5;i++) applyDigitChange(setIdx, -1, editing_seconds); }
else if(!running){ int mm = timer_seconds/60; mm = (mm-10<0)?0:mm-10; timer_seconds = mm*60; }
}
/* ===== setup/loop ===== */
void setup(){
pinMode(BTN_MODE, INPUT_PULLUP);
pinMode(BTN_START, INPUT_PULLUP);
pinMode(BTN_UP, INPUT_PULLUP);
pinMode(BTN_DOWN, INPUT_PULLUP);
pinMode(BUZZER_PIN, OUTPUT);
digitalWrite(BUZZER_PIN, LOW);
display.setBrightness(DISPLAY_BRIGHTNESS, true);
display.clear();
lastTick = millis();
lastBlink = millis();
}
void loop(){
// 버튼 스캔
scanBtn(bMode, onModeShort, onModeLong, LONG_PRESS_MS);
scanBtn(bStart, onStartShort, onStartLong, RESET_PRESS_MS);
scanBtn(bUp, onUpShort, onUpLong, LONG_PRESS_MS);
scanBtn(bDown, onDownShort, onDownLong, LONG_PRESS_MS);
uint32_t now = millis();
// 1초마다 카운트
if(now - lastTick >= 1000){
lastTick += 1000;
if(running && !inSetting){
if(timer_seconds > 0){
timer_seconds--;
if (timer_seconds == 0){
running = false;
startEndBeep(3000);
}
}
}
colon_state = !colon_state;
}
// 설정자리 깜빡임
if(now - lastBlink >= 300){
lastBlink += 300;
blinkOn = !blinkOn;
}
// 표시
if(inSetting){
int32_t s = editing_seconds;
if(s<0) s=0;
if(s>(int32_t)(MAX_MINUTES*60+59)) s=MAX_MINUTES*60+59;
uint16_t mm = s/60, ss = s%60;
showMMSS_raw(mm, ss, true, setIdx, blinkOn);
}else{
showSeconds(timer_seconds, running ? colon_state : true);
}
// 부저 상태 갱신
updateBeep();
}
(주)인투피온
대표:소영삼 사업자등록번호:113-86-29364 [사업자정보확인] 통신판매신고:2015-서울구로-1028
본사 : 서울 구로구 경인로 53길 90 STX W-Tower 1307호
매장 : 서울 구로구 경인로 53길 15 중앙유통단지 가동 3101호
고객상담 팩스번호: 02-6124-4242 이메일: info@intopion.com
* 재고 확인, 배송, 기술문의는 바로 답변이 어려우니, 가급적 카카오톡 플러스친구 [인투피온] 이용 부탁드립니다 *
개인정보관리책임자 : 이성민 / Hosting Provider : ㈜가비아씨엔에스