본문 바로가기

ESP32

ESP32-C6 포모도로 Pomodoro 타이머 만들기 V1

반응형

여러 기능이 들어간 만능 타이머를 만들어 본다. 이 타이머가 유용한 이유는 30초 1분 정도 스쿼트, 플랭크 훈련한다든가, 포모도로 타이머로 집중시간을 지키는 것 등 다양한 용도로 사용할 수 있다.

 

한 번에 완벽한 것은 없다. 일단 시작은 작게 한다. 버튼이 들어간 포모도로 타이머를 제작하기로 한다. 사용한 부품은 다음과 같습니다. 가장 빠르게 시작할 수 있는 방법을 찾는다.

 

 

사용 부품

  • ESP32-C6 개발 모듈 - 마이크로 컨트롤러
  • 버튼 1개 - 스타트, 퍼즈, 브레이크 타임 시작 등
  • 부저 1개 - 알람
  • OLED ssd1306 - 상태 디스플레이
  • 저항 330오옴 3개 - RGB LED 4핀 캐쏘드 타입 

 

회로도 - 온라인 회로 시뮬레이션 사이트 wokwi 사이트에서 작업 5분

 

정확한 연결 핀 번호는 이미지 아래 테이블과 코드를 확인하세요.

 

 

 

 

ESP32-C6을 사용했으므로 핀 번호는 다를 수 있지만 제대로 할당해주면 사용에는 지장이 없습니다.

 

코드에 보면 핀 할당 번호가 나와있는데 아래 표를 참고하세요.

ESP32 부품
22   BUTTON_PIN
13  BUZZER_PIN
10 RGB lED RED
1 RGB lED GREEN
0 RGB lED BLUE
SCL 7
Wire.begin(6, 7);
SDA 6
Wire.begin(6, 7);

 

 

⏱ 포모도로 기법 요약:

 

포모도로 기법은 25분 작업 단위(포모도로)로 업무에 집중한 후 5분간 짧은 휴식을 취하도록 돕습니다. 포모도로 4회 후에는 15~20분간의 긴 휴식을 가집니다. 이 구조는 집중력을 유지하도록 뇌를 훈련시키고, 정신적 피로를 줄이며, 공부나 코딩 세션을 훨씬 효율적으로 만듭니다.

 

특히 학생, 프로그래머, 메이커, 엔지니어 등 미루는 습관을 버리고 성과를 내고자 하는 모든 분에게 탁월한 방법입니다!  

 

이 프로젝트를 사랑하게 될 이유:

 

✅ESP32와 아두이노 애호가를 위한 훌륭한 초보자 프로젝트

✅전자공학 + 생산성 향상 기능을 결합

✅LED, Wi-Fi 동기화, 버튼 등으로 업그레이드 가능

✅코딩 실력과 집중력 습관 모두 향상시켜 줍니다  

 

아래 코드 동작에 관한 설명입니다.

 

1. 프로그램 업로드가 끝나면 초기 상태가 디스플레이 - 동작 안합니다.

2. 버튼을 누르면 부저가 3번 울리고 25분에서 시간이 줄어듭니다.

    - RGB LED는 지금 어떤 상태에 있는지 시각적으로 알려줍니다.

3. 25분씩 동작하고 끝나면 부저소리와 함께 짧은 휴식 5분이 실행합니다. - 정지 상태

4. 버튼을 누르면 짧은 휴식 5분이 시작합니다.

5. 3번과 4번이 3번인가 4번 실행하고 나면 15분 긴 휴식이 마찬가지로 버튼을 누르면 시작합니다.

6. 다시 1번으로 돌아가 포모도로 동작을 시작합니다. 

 

전체 코드입니다. 이 코드를 개선하여

 

1. 버튼을 2~3개 추가하여 사용자 인터페이스를 강화합니다. 모드 세팅, 소리와 RGB 동작 on off 등 

2. 조그 셔틀을 달아 원하는 시간(초, 분 단위)로 줄어들면서 알람을 반복하여 주도록 합니다. 플랭크 시간 등

3. 캐소드 타입 RGB LED 제거하고 보드 위의 WLED 사용

4. 딥 슬립 모드 적용하여 전원 절약 

5. 인터넷 연결 기능 - 실제 시간처럼 동작할 때 NPT 서버에서 시간을 가져와 정확성 유지 목적

7. 충전 니튬 폴리머 배터리로 동작하게

 

 

욕심을 낸다면

 

  • 네트워크 연결 기능 - 프로비전저닝 기능 추가
  • 스마트 폰 블루투스 설정 기능 추가
  • OTA - 원격 펌웨어 업데이트 기능 추가
  • 가속도 센서를 달아 신호 분석하여 동일한 작업 수행시 자동으로 카운팅이 되게 함. 푸쉬업, 턱걸이, 문 열고 닫힌 횟수 등 카운팅 

 

 

2026년이 시작되었어요. 서두르지 마세요. 원하시는 대로 될 겁니다. 늘 즐거운 개발하세요. 감사합니다.

 

 

#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Adafruit_NeoPixel.h>

// OLED display settings
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

// Pin definitions
#define BUTTON_PIN 22      // Connect one side of button here
#define BUZZER_PIN 13     // Buzzer positive

// RGB LED Pins
#define RED_PIN 10
#define GREEN_PIN 1
#define BLUE_PIN 0

// Pomodoro timing constants (in seconds) - CHANGED NAMES
#define POMODORO_DURATION 25 * 60  // 25 minutes
#define SHORT_BREAK_DURATION 5 * 60     // 5 minutes
#define LONG_BREAK_DURATION 15 * 60     // 15 minutes

// led
constexpr uint8_t LED_PIN = 8;
constexpr uint8_t NUM_LEDS = 1;
Adafruit_NeoPixel rgbLed(NUM_LEDS, LED_PIN, NEO_GRB + NEO_KHZ800);
struct RGB {
    uint8_t r, g, b;
};
constexpr RGB COLOR_OFF   = {0, 0, 0};

// States
enum TimerState {
  STOPPED,
  RUNNING,
  PAUSED
};

enum PomodoroPhase {
  POMODORO,
  SHORT_BREAK,
  LONG_BREAK
};

void setColor(const RGB& color, uint8_t brightness = 100) {
    uint16_t scale = (uint16_t)brightness * 255 / 100;
    uint8_t r = (uint8_t)(((uint16_t)color.r * scale) / 255);
    uint8_t g = (uint8_t)(((uint16_t)color.g * scale) / 255);
    uint8_t b = (uint8_t)(((uint16_t)color.b * scale) / 255);
    rgbLed.setPixelColor(0, rgbLed.Color(r, g, b));
    rgbLed.show();
}

// Global variables
TimerState currentState = STOPPED;
PomodoroPhase currentPhase = POMODORO;
unsigned long remainingTime = POMODORO_DURATION;  // CHANGED HERE
unsigned long lastUpdateTime = 0;
int pomodoroCount = 0;
bool buttonPressed = false;
bool lastButtonState = HIGH;

void setup() {

  rgbLed.begin();
  setColor(COLOR_OFF);

  Serial.begin(115200);
  Wire.begin(6, 7);
  // Initialize pins
  pinMode(BUTTON_PIN, INPUT_PULLUP);  // ESP32 internal pull-up
  pinMode(BUZZER_PIN, OUTPUT);
  
  // Initialize RGB LED pins
  pinMode(RED_PIN, OUTPUT);
  pinMode(GREEN_PIN, OUTPUT);
  pinMode(BLUE_PIN, OUTPUT);

  // Initialize OLED
  if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
    Serial.println(F("SSD1306 allocation failed"));
    for(;;);
  }
  
  display.clearDisplay();
  display.setTextColor(WHITE);
  display.setTextSize(1);
  
  Serial.println("Pomodoro Timer Ready!");
  updateRGB(); // Set initial RGB color
  updateDisplay();
}

void loop() {
  handleButton();
  updateTimer();
  delay(50); // Small delay for debouncing
}


// RGB LED Control Function - COMMON ANODE VERSION
void updateRGB() {
  // For Common Anode: HIGH = OFF, LOW = ON
  // Turn off all colors first (set all to HIGH)
  digitalWrite(RED_PIN, HIGH);
  digitalWrite(GREEN_PIN, HIGH);
  digitalWrite(BLUE_PIN, HIGH);
  
  // Set color based on state and phase
  if (currentState == PAUSED) {
    // Blue for paused
    digitalWrite(BLUE_PIN, LOW);  // LOW turns ON for common anode
    Serial.println("RGB: Blue (Paused)");
  } 
  else if (currentState == RUNNING) {
    if (currentPhase == POMODORO) {
      // Red for pomodoro running
      digitalWrite(RED_PIN, LOW);  // LOW turns ON for common anode
      Serial.println("RGB: Red (Pomodoro Running)");
    } else {
      // Green for break running
      digitalWrite(GREEN_PIN, LOW);  // LOW turns ON for common anode
      Serial.println("RGB: Green (Break Running)");
    }
  }
  else { // STOPPED state
    digitalWrite(RED_PIN, HIGH);
    digitalWrite(GREEN_PIN, HIGH);
    digitalWrite(BLUE_PIN, HIGH);
  }
}


void handleButton() {
  bool currentButtonState = digitalRead(BUTTON_PIN);
  static unsigned long lastDebounceTime = 0;
  static bool lastStableState = HIGH;
  
  // Debounce logic
  if (currentButtonState != lastStableState) {
    lastDebounceTime = millis();
  }
  
  if ((millis() - lastDebounceTime) > 50) {
    // State is stable
    if (currentButtonState != lastButtonState) {
      lastButtonState = currentButtonState;
      
      if (currentButtonState == LOW) {
        // Button pressed - handle single click
        if (currentState == STOPPED) {
          startTimer();
        } else if (currentState == RUNNING) {
          pauseTimer();
        } else if (currentState == PAUSED) {
          resumeTimer();
        }
      }
    }
  }
  
  lastStableState = currentButtonState;
  
  // Long press detection (separate from debouncing)
  static unsigned long pressStartTime = 0;
  if (currentButtonState == LOW) {
    if (pressStartTime == 0) {
      pressStartTime = millis();
    } else if (millis() - pressStartTime > 2000) {
      restartTimer();
      pressStartTime = 0;
      // Wait for button release
      while(digitalRead(BUTTON_PIN) == LOW) {
        delay(50);
      }
    }
  } else {
    pressStartTime = 0;
  }
}

void startTimer() {
  currentState = RUNNING;
  lastUpdateTime = millis();
  playBuzzer();
  updateRGB(); // Update RGB color
  Serial.println("Timer started");
}

void pauseTimer() {
  currentState = PAUSED;
  playBuzzerOnce();
  updateRGB(); // Update RGB color
  Serial.println("Timer paused");
}

void resumeTimer() {
  currentState = RUNNING;
  lastUpdateTime = millis();
  playBuzzerOnce();
  updateRGB(); // Update RGB color
  Serial.println("Timer resumed");
}

void restartTimer() {
  currentState = STOPPED;
  resetTimer();
  playBuzzerOnce();
  updateRGB(); // Update RGB color
  Serial.println("Timer restarted");
}

void resetTimer() {
  switch(currentPhase) {
    case POMODORO:
      remainingTime = POMODORO_DURATION;  // CHANGED HERE
      break;
    case SHORT_BREAK:
      remainingTime = SHORT_BREAK_DURATION;  // CHANGED HERE
      break;
    case LONG_BREAK:
      remainingTime = LONG_BREAK_DURATION;  // CHANGED HERE
      break;
  }
  playBuzzer();
  updateDisplay();
}

void updateTimer() {
  if (currentState != RUNNING) return;
  
  unsigned long currentTime = millis();
  
  if (currentTime - lastUpdateTime >= 1000) { // Update every second
    if (remainingTime > 0) {
      remainingTime--;
      lastUpdateTime = currentTime;
      updateDisplay();
    } else {
      // Timer completed
      timerComplete();
    }
  }
}

void timerComplete() {
  currentState = STOPPED;
  playBuzzer();
  
  // Move to next phase
  switch(currentPhase) {
    case POMODORO:
      pomodoroCount++;
      if (pomodoroCount % 4 == 0) {
        currentPhase = LONG_BREAK;
        Serial.println("Pomodoro completed! Time for long break.");
      } else {
        currentPhase = SHORT_BREAK;
        Serial.println("Pomodoro completed! Time for short break.");
      }
      break;
      
    case SHORT_BREAK:
    case LONG_BREAK:
      currentPhase = POMODORO;
      Serial.println("Break completed! Time for next pomodoro.");
      break;
  }
  
  resetTimer();
  updateRGB(); // Update RGB color when phase changes
  updateDisplay();
}

void playBuzzer() {
  // Play completion sound
  for (int i = 0; i < 3; i++) {
    digitalWrite(BUZZER_PIN, HIGH);
    delay(200);
    digitalWrite(BUZZER_PIN, LOW);
    delay(200);
  }
}

void playBuzzerOnce() {
    digitalWrite(BUZZER_PIN, HIGH);
    delay(200);
    digitalWrite(BUZZER_PIN, LOW);
    delay(200);
  }

void updateDisplay() {
  display.clearDisplay();
  
  // Display current phase
  display.setCursor(0, 0);
  display.setTextSize(2);
  
  switch(currentPhase) {
    case POMODORO:
      display.print("POMODORO");
      break;
    case SHORT_BREAK:
      display.print("BREAK");
      break;
    case LONG_BREAK:
      display.print("LONG BREAK");
      break;
  }
  
  // Display timer state
  display.setCursor(0, 17);
  display.setTextSize(1);
  switch(currentState) {
    case RUNNING:
      display.print("RUNNING");
      break;
    case PAUSED:
      display.print("PAUSED");
      break;
    case STOPPED:
      display.print("STOPPED");
      break;
  }
  
  // Display pomodoro count
  display.setCursor(70, 17);
  display.print("Count: ");
  display.print(pomodoroCount);
  
  // Display time
  display.setCursor(0, 26);
  display.setTextSize(3);
  
  int minutes = remainingTime / 60;
  int seconds = remainingTime % 60;
  
  if (minutes < 10) display.print("0");
  display.print(minutes);
  display.print(":");
  if (seconds < 10) display.print("0");
  display.print(seconds);
  
  // Display instructions
  display.setCursor(0, 48);
  display.setTextSize(1);
  display.print("Press:Start/Stop");

    // Display instructions
  display.setCursor(0, 57);
  display.setTextSize(1);
  display.print("Hold:Restart");
  
  display.display();
}

 

 

 

다음 유튜브 영상으로 만든 코드 참고한다. 코드 깃 허브 링크

 

참고 링크

 

 

 

반응형

캐어랩 고객 지원

취업, 창업의 막막함, 외주 관리, 제품 부재!

당신의 고민은 무엇입니까? 현실과 동떨어진 교육, 실패만 반복하는 외주 계약, 아이디어는 있지만 구현할 기술이 없는 막막함.

우리는 알고 있습니다. 문제의 원인은 '명확한 학습, 실전 경험과 신뢰할 수 있는 기술력의 부재'에서 시작됩니다.

이제 고민을 멈추고, 캐어랩을 만나세요!

코딩(펌웨어), 전자부품과 디지털 회로설계, PCB 설계 제작, 고객(시장/수출) 발굴과 마케팅 전략으로 당신을 지원합니다.

제품 설계의 고수는 성공이 만든 게 아니라 실패가 만듭니다. 아이디어를 양산 가능한 제품으로!

귀사의 제품을 만드세요. 교육과 개발 실적으로 신뢰할 수 있는 파트너를 확보하세요.

지난 30년 여정, 캐어랩이 얻은 모든 것을 함께 나누고 싶습니다.

카카오 채널 추가하기

카톡 채팅방에서 무엇이든 물어보세요

당신의 성공을 위해 캐어랩과 함께 하세요.

캐어랩 온라인 채널 바로가기

캐어랩