Maker.io main logo

Simple & Versatile Arduino Kitchen Timer with TM1637 Display

15

2025-05-27 | By Mirko Pavleski

License: General Public License Real Time Clocks (RTCs) Arduino

A kitchen timer is a simple, user-friendly device that counts down a set time and alerts the user with an audible or visual signal when cooking or baking tasks are complete.

bvh

Most commercial kitchen timers are relatively complex to set up and operate, as more attention is paid to many unnecessary options and functions, while neglecting the simplicity of operation that should be a basic feature for a timer with this purpose.

In this project, I will show you a very simple way to make a kitchen timer that is extremely easy to operate yet has almost all the features of a modern commercial timer.

 

To make the device, you only need a few components:

- Arduino Nano microcontroller board

- TM1637 - 4 digits, 7-segment display

- 10K Potentiometer

- Three buttons

- and Buzzer

njk

This project is sponsored by Altium 365. Altium 365 is a cloud-based platform designed for electronics design and engineering. It provides a suite of tools for PCB design, including requirements management, supply chain, library management, ECAD connectivity, co-design and integration, and a manufacturing portal.

First, I will explain how to operate the device and its functions and capabilities. For simplicity, the time is set with a simple potentiometer, in three ranges: 0 to 60 min, 0 to 30 min, and 0 to 10 min range. The green button starts the countdown, the yellow button resets the device at any time, and the red button selects the desired range.

When the device is turned on, the time appears on the screen, which is determined by the position of the potentiometer.

jbk

The default range when turned on is 60 minutes. Now simply and quickly set the desired time with the potentiometer, for example, 7 min, and press the start button. The timer starts counting down, and the two dots in the middle of the display start flashing. If at any time during the process we press the reset button, the time returns to the original set state. The end of the set time is signaled by an intermittent tone of the buzzer, as well as alternating flashing of the numbers (in this case, four zeros) on the display. This lasts until we press the reset button, after which the device is ready for operation again. Let's also see the function of the RANGE button.

njkl

By pressing the button, the set range appears on the screen for two seconds, and then the timer value in that range is displayed. With this option, we can set shorter intervals much more precisely and easily. As you can see, in the explanation, the resolution for setting this timer is currently 30 seconds, because for a kitchen timer, that is quite enough. However, depending on the needs, it can be 10, 5, or even one second and is set in the code in the line:

#define QUANTIZE_INTERVAL 30 // (1 to 60 sec)

As for the code, I tried to make it as flexible and easy to modify as possible, so with minimal changes we can create a custom timer to suit our own requirements.

mnk

In short, as you can see, all the timer parameters can be changed. For example, the previously mentioned QUANTIZE_INTERVAL, POT_SMOOTHING, POT_READ_DELAY, Alarm Frequencies, as well as ON/OFF time, number and duration of timer ranges, and even the brightness of the display.

And finally, a short conclusion. In summary, this Arduino project delivers a user-friendly, highly customizable kitchen timer with all essential features, proving that effective timekeeping doesn’t require unnecessary complexity.

cv

Copy Code
//-----------------------------------------------------
// 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()));
}
Mfr Part # A000005
ARDUINO NANO ATMEGA328 EVAL BRD
Arduino
$204.45
View More Details
Mfr Part # U146
RED 7-SEGMENT DIGIT CLOCK UNIT
M5Stack Technology Co., Ltd.
Mfr Part # 270X232A103B2B1
POT 10K OHM 1/4W CARBON LOG
CTS Electrocomponents
Mfr Part # RP3502MAGRN
SWITCH PUSH SPST-NO 3A 120V
E-Switch
Mfr Part # RP3502MAYEL
SWITCH PUSH SPST-NO 3A 120V
E-Switch
Mfr Part # RP3502MARED
SWITCH PUSH SPST-NO 3A 120V
E-Switch
Add all DigiKey Parts to Cart
Have questions or comments? Continue the conversation on TechForum, DigiKey's online community and technical resource.