Motion-Controlled Pomodoro Timer

by nilutpolkashyap in Circuits > Clocks

48 Views, 0 Favorites, 0 Comments

Motion-Controlled Pomodoro Timer

IMG_20251123_142427615.jpg
IMG_20251123_142445372.jpg

NOTE: I am yet to receive the 3D-printed case for the Pomodoro timer. I will update the pictures once I shift the project to the new 3D-printed case. I didn't have enough time before the contest submission deadline.


Build a completely portable, motion-controlled Pomodoro timer that runs on battery power! This innovative productivity tool responds to device orientation to select timer durations (5, 10, 15, 30, 45, or 60 minutes) with automatic screen rotation for optimal viewing. The integrated battery system with charging capability makes it perfect for use anywhere without being tethered to a power outlet.


What makes this special:-

  1. 🎯 Motion-controlled timer selection (no buttons needed!)
  2. 🔄 Automatic screen rotation based on orientation
  3. 🎵 Audio feedback and completion alerts
  4. 👆 Touch sensor for easy control
  5. 📱 Professional LCD display with smooth animations

Key Features:

  1. 🔋 Portable battery-powered operation with USB charging
  2. 🎯 Motion-controlled timer selection (no buttons needed!)
  3. 🔄 Automatic screen rotation based on orientation
  4. 🎵 Audio feedback and completion alerts
  5. 👆 Touch sensor for easy control
  6. 📱 Professional 1.69" LCD display with smooth animations
  7. âš¡ Built-in charging system with status indication

Perfect for:

  1. Study sessions away from power outlets
  2. Outdoor work or reading
  3. Meeting rooms and classrooms
  4. Travel and mobile productivity
  5. Anywhere you need distraction-free timing

Supplies

Required Components

  1. ESP32 Development Board (30-pin)
  2. Waveshare 1.69" LCD Module (240×280) (ST7789V2)
  3. MPU6050 IMU Sensor
  4. TTP223 Touch Sensor
  5. Passive Buzzer
  6. 3.7V 500mAh LiPo Battery
  7. TP4056 Charging Module
  8. MT3608 Step-Up Converter
  9. Wires
  10. PCB Prototype Board

Tools Required

  1. Soldering iron and solder
  2. Wire strippers
  3. Multimeter
  4. Computer with Arduino IDE
  5. Micro USB cable
  6. Small screwdriver set

Set Up Development Environment

Arduino IDE Configuration

1. Download and install Arduino IDE from [arduino.cc](https://arduino.cc)

2. Add ESP32 board support:

- File → Preferences

- Add URL: `https://dl.espressif.com/dl/package_esp32_index.json`

- Tools → Board → Boards Manager → Install "ESP32"

Install Required Libraries

  1. TFT_eSPI (by Bodmer) - v2.4.0+
  2. Adafruit MPU6050 - v2.2.0+
  3. Adafruit Sensor - v1.1.0+
  4. Wire (built-in for I2C)
  5. SPI (built-in for display)

Configure TFT_eSPI for Waveshare Display

Navigate to Arduino libraries folder, find `TFT_eSPI/User_Setup.h` and configure:

#define ST7789_DRIVER
#define TFT_WIDTH 240
#define TFT_HEIGHT 280
#define TFT_MOSI 23
#define TFT_SCLK 18
#define TFT_CS 5
#define TFT_DC 2
#define TFT_RST 4
#define TFT_BL 15

Connections

IMG_20251123_143011014.jpg
IMG_20251123_142939119.jpg
IMG_20251123_142931391.jpg

Power System Wiring

Connect the LiPo Battery to TP4056

LiPo Battery → TP4056 Charger

Red (+) → B+

Black (-) → B-


Connect TP4056 to MT3608 Boost Converter

TP4056 Output → MT3608 Input

OUT+ → IN+

OUT- → IN-


Configure MT3608 Output Voltage

1. Connect multimeter to MT3608 output (OUT+ and OUT-)

2. Turn potentiometer clockwise to increase voltage

3. Adjust until output reads exactly **5.0V**

4. Double-check voltage before connecting to ESP32


Connect MT3608 to ESP32

MT3608 Output → ESP32

OUT+ → VIN (5V input pin)

OUT- → GND


LCD Display (SPI Interface):

Waveshare 1.69" LCD → ESP32

VCC → 3.3V

GND → GND (shared)

DIN → GPIO 23 (MOSI)

CLK → GPIO 18 (SCLK)

CS → GPIO 5

DC → GPIO 2

RST → GPIO 4

BLK → GPIO 15


MPU6050 IMU Sensor (I2C Interface):

MPU6050 → ESP32

VCC → GPIO 13 (power output)

GND → GND (shared)

SDA → GPIO 21

SCL → GPIO 22


TTP223 Touch Sensor:

TTP223 → ESP32

VCC → 3.3V (shared with LCD)

GND → GND (shared)

I/O → GPIO 16


Passive Buzzer:

Buzzer → ESP32

+ → GPIO 14

- → GND (shared)



Code

#include <TFT_eSPI.h>
#include <SPI.h>
#include <Adafruit_MPU6050.h>
#include <Adafruit_Sensor.h>
#include <Wire.h>

#define MPU_33V 13
#define TOUCH_PIN 16
#define BUZZER_PIN 14

TFT_eSPI tft = TFT_eSPI();
Adafruit_MPU6050 mpu;

#define BLACK 0x0000
#define WHITE 0xFFFF
#define RED 0xF800
#define GREEN 0x07E0
#define BLUE 0x001F
#define YELLOW 0xFFE0
#define CYAN 0x07FF

struct TimerMode {
int minutes;
int rotationDegrees;
String name;
};

TimerMode timerModes[6] = {
{10, 0, "10 min"},
{5, 60, "5 min"},
{45, 120, "45 min"},
{60, 180, "60 min"},
{15, 300, "15 min"},
{30, 240, "30 min"}
};

float accelX, accelY, accelZ;
int currentMode = -1;
unsigned long timerStartTime = 0;
unsigned long timerDuration = 0;
bool timerRunning = false;
bool timerPaused = true;
bool timerFinished = false;
bool buzzerPlaying = false;
unsigned long buzzerStartTime = 0;
unsigned long lastUpdate = 0;
unsigned long lastOrientationCheck = 0;
bool mpuInitialized = false;

void setup() {
Serial.begin(115200);
pinMode(15, OUTPUT);
digitalWrite(15, HIGH);
pinMode(TOUCH_PIN, INPUT);
pinMode(BUZZER_PIN, OUTPUT);
tone(BUZZER_PIN, 1000, 200);
tft.init();
tft.setRotation(0);
tft.fillScreen(BLACK);
pinMode(MPU_33V, OUTPUT);
digitalWrite(MPU_33V, HIGH);
delay(1000);
Wire.begin(21, 22);
Wire.setClock(100000);
if (mpu.begin()) {
mpu.setAccelerometerRange(MPU6050_RANGE_8_G);
mpu.setGyroRange(MPU6050_RANGE_500_DEG);
mpu.setFilterBandwidth(MPU6050_BAND_21_HZ);
mpuInitialized = true;
Serial.println("MPU6050 ready");
} else {
Serial.println("MPU6050 failed");
}
}

void loop() {
if (millis() - lastUpdate > 100) {
if (mpuInitialized) {
readIMUData();
updateDisplay();
}
lastUpdate = millis();
}
if (digitalRead(TOUCH_PIN) && timerFinished) {
buzzerPlaying = false;
noTone(BUZZER_PIN);
timerFinished = false;
tone(BUZZER_PIN, 2000, 50);
}
if (millis() - lastOrientationCheck > 300) {
checkOrientation();
lastOrientationCheck = millis();
}
if (buzzerPlaying && millis() - buzzerStartTime > 2000) {
buzzerPlaying = false;
noTone(BUZZER_PIN);
timerFinished = false;
}
delay(10);
}

void readIMUData() {
sensors_event_t a, g, temp;
mpu.getEvent(&a, &g, &temp);
accelX = a.acceleration.x / 9.81;
accelY = a.acceleration.y / 9.81;
accelZ = a.acceleration.z / 9.81;
}

void checkOrientation() {
int newMode = currentMode;
if (accelX > 0.5 || accelX < -0.5) {
if (!timerPaused) {
timerPaused = true;
timerRunning = false;
newMode = -1;
}
} else if (accelX <= 0.3 && accelX >= -0.3) {
timerPaused = false;
if (accelY > 0.85) newMode = 1;
else if (accelY > 0.6) newMode = 2;
else if (accelY > 0.0) newMode = 0;
else if (accelY >= -0.6) newMode = 3;
else if (accelY >= -0.85) newMode = 4;
else newMode = 5;
if (newMode != currentMode && newMode != -1) {
currentMode = newMode;
int tftRotation = (timerModes[currentMode].rotationDegrees / 90) % 4;
tft.setRotation(tftRotation);
tft.fillScreen(BLACK);
startTimer(currentMode);
}
}
}

void startTimer(int mode) {
timerDuration = timerModes[mode].minutes * 60UL * 1000;
timerStartTime = millis();
timerRunning = true;
timerFinished = false;
tone(BUZZER_PIN, 1200, 200);
}

void updateDisplay() {
if (timerPaused) {
displayPauseScreen();
} else if (timerRunning) {
unsigned long elapsed = millis() - timerStartTime;
if (elapsed >= timerDuration && !timerFinished) {
timerFinished = true;
timerRunning = false;
buzzerPlaying = true;
buzzerStartTime = millis();
tone(BUZZER_PIN, 1500);
}
if (timerFinished) {
displayFinished();
} else {
displayTimer();
}
}
}

void displayPauseScreen() {
tft.setRotation(2);
tft.fillScreen(BLACK);
tft.setTextColor(CYAN, BLACK);
tft.setTextSize(2);
tft.setCursor(30, 60);
tft.println("POMODORO");
tft.fillRect(90, 120, 20, 40, YELLOW);
tft.fillRect(130, 120, 20, 40, YELLOW);
tft.setTextColor(WHITE, BLACK);
tft.setTextSize(1);
tft.setCursor(50, 180);
tft.println("Tilt to start");
}

void displayTimer() {
unsigned long remaining = timerDuration - (millis() - timerStartTime);
unsigned long minutes = remaining / 60000;
unsigned long seconds = (remaining % 60000) / 1000;
int centerX = tft.width() / 2;
int centerY = tft.height() / 2;
tft.fillRect(5, 5, tft.width() - 10, tft.height() - 10, BLACK);
tft.setTextColor(CYAN, BLACK);
tft.setTextSize(1);
tft.setCursor(centerX - 20, 15);
tft.println(timerModes[currentMode].name);
tft.setTextColor(WHITE, BLACK);
tft.setTextSize(3);
String timeStr = String(minutes) + ":" + (seconds < 10 ? "0" : "") + String(seconds);
tft.setCursor(centerX - 45, centerY - 15);
tft.println(timeStr);
float progress = (float)(millis() - timerStartTime) / timerDuration;
int barWidth = 160;
int barX = centerX - 80;
int barY = centerY + 25;
tft.drawRect(barX, barY, barWidth, 8, WHITE);
int fillWidth = (int)(progress * (barWidth - 2));
if (fillWidth > 0) {
uint16_t color = progress > 0.8 ? RED : (progress > 0.5 ? YELLOW : GREEN);
tft.fillRect(barX + 1, barY + 1, fillWidth, 6, color);
}
}

void displayFinished() {
static bool flashState = false;
static unsigned long lastFlash = 0;
if (millis() - lastFlash > 400) {
flashState = !flashState;
lastFlash = millis();
}
int centerX = tft.width() / 2;
int centerY = tft.height() / 2;
tft.fillRect(10, 10, tft.width() - 20, tft.height() - 20, BLACK);
if (flashState) {
tft.setTextColor(GREEN, BLACK);
tft.setTextSize(2);
tft.setCursor(centerX - 60, centerY);
tft.println("FINISHED!");
tft.setTextColor(WHITE, BLACK);
tft.setTextSize(1);
tft.setCursor(centerX - 40, centerY + 30);
tft.println("Touch to stop");
}
}

How It Works

IMG_20251123_142659389.jpg
IMG_20251123_142654197.jpg
IMG_20251123_142648458.jpg
IMG_20251123_142458010.jpg
IMG_20251123_142451797.jpg
IMG_20251123_142445372.jpg
IMG_20251123_142427615.jpg
IMG_20251123_143110778.jpg

Motion Control:

  1. Tilt left/right (X > 0.5 or X < -0.5) → PAUSE
  2. Hold vertical (X between -0.3 and 0.3) → Active timer selection

Timer Selection (Y-axis tilt):

  1. Y > 0.85 → 5 minutes
  2. Y 0.6 to 0.85 → 45 minutes
  3. Y 0.0 to 0.6 → 10 minutes
  4. Y -0.6 to 0.0 → 60 minutes
  5. Y -0.85 to -0.6 → 15 minutes
  6. Y < -0.85 → 30 minutes

Features:

  1. Auto-rotating display based on orientation
  2. Touch to stop timer alarm
  3. 2-second buzzer when timer completes
  4. 3-4 hour battery life