Pomodoro timer on an Arduino Uno
Twenty-five minutes of focus and five of rest on a four-digit display, with a red lamp for work, a green one for breaks and a chime at every change. The whole build, an Arduino Uno and 8 more parts, runs here in your browser on the firmware below; open it in the editor to change the wiring or the code and run it again.
Teaching? Assign it to your class in one click.
Beginner Runs in your browser. Free, and no account needed.
The parts retail for about $34. On Mokxi they're free.
Step by step, with the wiring and the common mistakes: Arduino TM1637 Display: Library and Countdown
The code
The firmware exactly as the editor opens it. Change a line there and press Run: it compiles in the browser.
// Pomodoro timer: twenty-five minutes of focus, five of rest, on a TM1637.
//
// Wiring:
// focus lamp pin 2 -> 220 ohm -> red LED -> GND
// break lamp pin 3 -> 220 ohm -> green LED -> GND
// start pin 4 -> pushbutton -> GND (press: start or pause, hold: reset)
// skip pin 5 -> pushbutton -> GND (end this session now)
// piezo pin 6 -> piezo -> GND
// TM1637 CLK -> A0 DIO -> A1 VCC -> 5 V GND -> GND
//
// The Pomodoro method: work for twenty-five minutes, rest for five, and after
// four rounds take a longer fifteen-minute break. The display counts down in
// minutes and seconds with the colon blinking while it runs, the red lamp
// means focus and the green lamp means rest, and the piezo chimes at every
// change.
//
// Press start to begin or pause. Hold it for a second to reset to the start
// of a focus session. Press skip to end the current session early.
#include <TM1637Display.h>
const uint8_t FOCUS_LAMP = 2;
const uint8_t BREAK_LAMP = 3;
const uint8_t START_PIN = 4;
const uint8_t SKIP_PIN = 5;
const uint8_t PIEZO_PIN = 6;
const uint32_t FOCUS_S = 25UL * 60;
const uint32_t SHORT_S = 5UL * 60;
const uint32_t LONG_S = 15UL * 60;
TM1637Display display(A0, A1);
enum Phase : uint8_t { FOCUS, SHORT_BREAK, LONG_BREAK };
Phase phase = FOCUS;
uint8_t done = 0; // focus sessions finished
uint32_t left = FOCUS_S; // seconds left in this session
bool running = false;
uint32_t tickAt = 0; // when the next second is due
bool startWas = false, skipWas = false;
uint32_t startDownAt = 0;
bool resetDone = false;
static const char *phaseName()
{
return phase == FOCUS ? "Focus" : (phase == SHORT_BREAK ? "Short break" : "Long break");
}
static void show()
{
int mmss = (int)((left / 60) * 100 + left % 60);
// The colon blinks once a second while it runs and stays on when paused.
bool colon = !running || (millis() % 1000) < 500;
display.showNumberDecEx(mmss, colon ? 0b01000000 : 0, true);
digitalWrite(FOCUS_LAMP, phase == FOCUS ? HIGH : LOW);
digitalWrite(BREAK_LAMP, phase == FOCUS ? LOW : HIGH);
}
static void chime(bool toWork)
{
// Up for rest, down for work, so you can tell them apart without looking.
const uint16_t up[3] = {523, 659, 784};
const uint16_t down[3] = {784, 659, 523};
const uint16_t *notes = toWork ? down : up;
for (uint8_t i = 0; i < 3; i++) {
tone(PIEZO_PIN, notes[i], 150);
delay(180);
}
}
static void nextPhase()
{
if (phase == FOCUS) {
done++;
phase = (done % 4 == 0) ? LONG_BREAK : SHORT_BREAK;
left = phase == LONG_BREAK ? LONG_S : SHORT_S;
} else {
phase = FOCUS;
left = FOCUS_S;
}
Serial.print(phaseName());
Serial.print(" (");
Serial.print(done);
Serial.println(done == 1 ? " session done)" : " sessions done)");
show();
chime(phase == FOCUS);
tickAt = millis() + 1000;
}
void setup()
{
Serial.begin(9600);
pinMode(FOCUS_LAMP, OUTPUT);
pinMode(BREAK_LAMP, OUTPUT);
pinMode(START_PIN, INPUT_PULLUP);
pinMode(SKIP_PIN, INPUT_PULLUP);
noTone(PIEZO_PIN);
display.setBrightness(4);
show();
delay(20);
Serial.println("Pomodoro: press start for 25 minutes of focus");
}
void loop()
{
uint32_t now = millis();
bool start = digitalRead(START_PIN) == LOW;
bool skip = digitalRead(SKIP_PIN) == LOW;
if (start && !startWas) {
startDownAt = now;
resetDone = false;
}
if (start && !resetDone && now - startDownAt >= 1000) {
// held: back to the beginning
running = false;
phase = FOCUS;
left = FOCUS_S;
done = 0;
resetDone = true;
tone(PIEZO_PIN, 330, 200);
Serial.println("Reset");
}
if (!start && startWas && !resetDone) {
running = !running;
tickAt = now + 1000;
tone(PIEZO_PIN, running ? 880 : 440, 60);
Serial.println(running ? "Running" : "Paused");
}
if (skip && !skipWas) {
Serial.println("Skipped");
nextPhase();
}
startWas = start;
skipWas = skip;
if (running && (int32_t)(now - tickAt) >= 0) {
tickAt += 1000;
if (left > 0) {
left--;
}
if (left == 0) {
nextPhase();
}
}
show();
delay(20);
}
Parts list
10 parts, plus the jumper wires. They retail for about $34; on Mokxi they're free.
- 1 × Arduino Uno R3
- 1 × Full-size breadboard
- 2 × Resistor, 220 Ω
- 1 × LED, red
- 1 × LED, green
- 2 × Pushbutton
- 1 × Piezo speaker
- 1 × Four-digit display, TM1637
How it is wired
11 connections, pin by pin, read from the circuit itself. Each line is one set of pins joined together, by a jumper wire or a breadboard strip.
- Ground: Arduino Uno R3 pin GND; LED, red pin C; LED, green pin C; Pushbutton (1) pin 2a; Pushbutton (2) pin 2a; Piezo speaker pin 2; Four-digit display, TM1637 pin GND
- Arduino Uno R3 pin 6; Piezo speaker pin 1
- Arduino Uno R3 pin 5; Pushbutton (2) pin 1a
- Arduino Uno R3 pin 4; Pushbutton (1) pin 1a
- Arduino Uno R3 pin 3; Resistor, 220 Ω (2) pin 1
- Arduino Uno R3 pin 2; Resistor, 220 Ω (1) pin 1
- Arduino Uno R3 pin 5V; Four-digit display, TM1637 pin VCC
- Arduino Uno R3 pin A0; Four-digit display, TM1637 pin CLK
- Arduino Uno R3 pin A1; Four-digit display, TM1637 pin DIO
- Resistor, 220 Ω (1) pin 2; LED, red pin A
- Resistor, 220 Ω (2) pin 2; LED, green pin A
Change it and keep it
Open it in the editor, change the circuit or the code, and keep your version in a free account.