Mirko Pavleski
Published © GPL3+

Simple & Versatile Arduino Kitchen Timer with TM1637 Display

This Arduino project delivers a user-friendly, highly customizable kitchen timer with all essential features.

BeginnerFull instructions provided1 hour387
Simple & Versatile Arduino Kitchen Timer with TM1637 Display

Things used in this project

Hardware components

Arduino Nano R3
Arduino Nano R3
×1
TM1637 - 4 digits, 7 segment display
×1
Rotary potentiometer (generic)
Rotary potentiometer (generic)
×1
Buzzer
Buzzer
×1
Pushbutton Switch, Momentary
Pushbutton Switch, Momentary
×3

Software apps and online services

Arduino IDE
Arduino IDE

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)
Solder Wire, Lead Free
Solder Wire, Lead Free

Story

Read more

Schematics

Schematic

...

Code

Code

C/C++
...
//-----------------------------------------------------
// Kitchen Timer with TM1637 Display
// by: mircemk
// License: GNU GPl 3.0
// Created: April 2025
//-----------------------------------------------------


#include <TM1637Display.h>

// Pin definitions
#define CLK 2
#define DIO 3
#define POT_PIN A0
#define START_BUTTON 4
#define RESET_BUTTON 5
#define BUZZER_PIN 6
#define RANGE_BUTTON 7

// Constants
#define QUANTIZE_INTERVAL 30    
#define POT_SMOOTHING 30        
#define POT_READ_DELAY 50       
#define ALARM_FREQ 500          
#define ALARM_ON_TIME 200       
#define ALARM_OFF_TIME 200      
#define COLON_ON_TIME 500      
#define COLON_OFF_TIME 500     
#define RANGE_DISPLAY_TIME 1000 
#define BUTTON_DEBOUNCE_TIME 300

// Timer ranges in seconds
const int TIMER_RANGES[] = {600, 1800, 3600};  // 10min, 30min, 60min
const int NUM_RANGES = 3;

// Display instance
TM1637Display display(CLK, DIO);

// Variables
unsigned long previousMillis = 0;
const long interval = 1000;     
bool timerRunning = false;
int remainingTime = 0;          
bool colonState = true;         
unsigned long lastBuzzTime = 0;
unsigned long lastColonToggle = 0;  
bool alarmOn = false;
bool displayOn = true;          
int lastDisplayedTime = -1;     
unsigned long lastPotRead = 0;
unsigned long lastDisplayFlash = 0;
int currentRangeIndex = 2;      // Start with 60min range (index 2)
unsigned long rangeDisplayStartTime = 0;
bool showingRange = false;      
unsigned long lastRangeButtonPress = 0;

// State enumeration
enum TimerState {
  IDLE,
  SHOWING_RANGE,
  RUNNING,
  ALARMING
};

TimerState currentState = IDLE;

void setup() {
  pinMode(START_BUTTON, INPUT_PULLUP);
  pinMode(RESET_BUTTON, INPUT_PULLUP);
  pinMode(RANGE_BUTTON, INPUT_PULLUP);
  pinMode(BUZZER_PIN, OUTPUT);

  display.setBrightness(0x0a);
  updateDisplay(quantizeTime(readSmoothedPot()));
}

void loop() {
  unsigned long currentMillis = millis();
  
  switch(currentState) {
    case SHOWING_RANGE:
      // Stay in range display mode until time elapsed
      if (currentMillis - rangeDisplayStartTime >= RANGE_DISPLAY_TIME) {
        currentState = IDLE;
        lastDisplayedTime = -1;  // Force pot reading update
      } else {
        // Keep showing range
        displayRange(TIMER_RANGES[currentRangeIndex]);
        return;  // Skip all other processing while showing range
      }
      break;
      
    case IDLE:
      // Handle range button
      if (digitalRead(RANGE_BUTTON) == LOW) {
        if (currentMillis - lastRangeButtonPress >= BUTTON_DEBOUNCE_TIME) {
          currentRangeIndex = (currentRangeIndex + 1) % NUM_RANGES;
          rangeDisplayStartTime = currentMillis;
          lastRangeButtonPress = currentMillis;
          currentState = SHOWING_RANGE;
          displayRange(TIMER_RANGES[currentRangeIndex]);
          return;  // Exit loop immediately after changing to range display
        }
      }
      
      // Handle potentiometer input
      if (currentMillis - lastPotRead > POT_READ_DELAY) {
        lastPotRead = currentMillis;
        int potTime = quantizeTime(readSmoothedPot());
        if (potTime != lastDisplayedTime) {
          colonState = true;
          updateDisplay(potTime);
          lastDisplayedTime = potTime;
        }
      }
      
      // Handle start button
      if (digitalRead(START_BUTTON) == LOW) {
        delay(50);
        if (digitalRead(START_BUTTON) == LOW) {
          remainingTime = quantizeTime(readSmoothedPot());
          currentState = RUNNING;
          previousMillis = currentMillis;
          lastDisplayedTime = -1;
          colonState = true;
          lastColonToggle = currentMillis;
        }
      }
      break;
      
    case RUNNING:
      // Handle colon blinking
      if (colonState && (currentMillis - lastColonToggle >= COLON_ON_TIME)) {
        colonState = false;
        lastColonToggle = currentMillis;
        updateDisplay(remainingTime);
      }
      else if (!colonState && (currentMillis - lastColonToggle >= COLON_OFF_TIME)) {
        colonState = true;
        lastColonToggle = currentMillis;
        updateDisplay(remainingTime);
      }
      
      // Update timer
      if (currentMillis - previousMillis >= interval) {
        previousMillis = currentMillis;
        if (remainingTime > 0) {
          remainingTime--;
          updateDisplay(remainingTime);
        }
        if (remainingTime == 0) {
          currentState = ALARMING;
        }
      }
      
      // Check reset button
      if (digitalRead(RESET_BUTTON) == LOW) {
        delay(50);
        if (digitalRead(RESET_BUTTON) == LOW) {
          resetTimer();
        }
      }
      break;
      
    case ALARMING:
      handleAlarm();
      if (digitalRead(RESET_BUTTON) == LOW) {
        delay(50);
        if (digitalRead(RESET_BUTTON) == LOW) {
          resetTimer();
        }
      }
      break;
  }
}

void displayRange(int rangeInSeconds) {
  int minutes = rangeInSeconds / 60;
  uint8_t segments[4];
  
  segments[0] = display.encodeDigit(minutes / 10);
  segments[1] = display.encodeDigit(minutes % 10) | 0x80;  // Force colon on
  segments[2] = display.encodeDigit(0);
  segments[3] = display.encodeDigit(0);
  
  display.setSegments(segments);
}

int readSmoothedPot() {
  long total = 0;
  for (int i = 0; i < POT_SMOOTHING; i++) {
    total += analogRead(POT_PIN);
    delay(1);
  }
  int average = total / POT_SMOOTHING;
  return map(average, 0, 1023, 0, TIMER_RANGES[currentRangeIndex]);
}

int quantizeTime(int seconds) {
  int quantized = (seconds / QUANTIZE_INTERVAL) * QUANTIZE_INTERVAL;
  return constrain(quantized, 0, TIMER_RANGES[currentRangeIndex]);
}

void updateDisplay(int timeInSeconds) {
  if (currentState == ALARMING && !displayOn) {
    display.clear();
    return;
  }

  int minutes = timeInSeconds / 60;
  int seconds = timeInSeconds % 60;

  uint8_t segments[4];
  
  segments[0] = display.encodeDigit(minutes / 10);
  segments[1] = display.encodeDigit(minutes % 10);
  segments[2] = display.encodeDigit(seconds / 10);
  segments[3] = display.encodeDigit(seconds % 10);
  
  if (colonState) {
    segments[1] |= 0x80;
  }

  display.setSegments(segments);
  lastDisplayedTime = timeInSeconds;
}

void handleAlarm() {
  unsigned long currentMillis = millis();
  
  // Handle display flashing
  if (currentMillis - lastDisplayFlash >= 500) {
    lastDisplayFlash = currentMillis;
    displayOn = !displayOn;
    updateDisplay(0);
  }

  // Handle intermittent alarm sound
  if (currentMillis - lastBuzzTime >= (alarmOn ? ALARM_ON_TIME : ALARM_OFF_TIME)) {
    lastBuzzTime = currentMillis;
    alarmOn = !alarmOn;
    
    if (alarmOn) {
      tone(BUZZER_PIN, ALARM_FREQ);
    } else {
      noTone(BUZZER_PIN);
    }
  }
}

void resetTimer() {
  currentState = IDLE;
  timerRunning = false;
  noTone(BUZZER_PIN);
  alarmOn = false;
  displayOn = true;
  colonState = true;
  lastDisplayedTime = -1;
  updateDisplay(quantizeTime(readSmoothedPot()));
}

Credits

Mirko Pavleski
189 projects • 1458 followers

Comments