Simple & Versatile Arduino Kitchen Timer with TM1637 Display
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.
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
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.
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.
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.
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.
//----------------------------------------------------- // 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())); }