My relationship with focus is... complicated to say the least, I've got approximately 70 browser tabs open at any given point in time, half of which I do not remember what are for, But I'd argue that it's not entirely my fault...
This world is full of distractions, every pixel on any device you own begs for your attention, So what do we do?
Well there's a lot of methods and solutions, but my favorite is the Pomodoro technique, a time management method developed by Francesco Cirillo in the late 1980s.
it breaks work into intervals of 25 minutes separated by short breaks, Very simple, but very effective.
TickTime Pomodoro Timer Inspiration
Why not just set a timer/download an app on your phone?
Good question, It's like having infinite slices of cake next to your kitchen counter, Every single time you go there, you will be tempted, you'll eventually take a slice, then two, then three.
The irony I kept running into with time management apps on my phone, is how easy it is to get distracted, The mere act of picking up my phone to set/check/stop a timer would inevitably lead to "just checking that notification" or "quickly answering that message." The very tool meant to help me focus became the gateway to distraction, and I bet you have the same problem.
And thus it came to me in a dream... (No it didn't, but wouldn't that be cool?)
A physical manifestation that represents your focus state. It sits on your desk while you work, designed for absolute simplicity, you sit down and plug it in, it works immediately, once your work session ends, you flip the cube to either side to get either a 5 minute or a 15 minute break, and it starts immediately, or pause it by flipping it upside down if life gets busy, no distractions.
This concept seems to already exist, commercial products like TickTime caught my eye, and were a significant inspiration, but I wanted to build something that is uniquely mine, Small, elegant, simple, beautiful, smart.
So what if instead of manually setting the timer, it sensed its own orientation through a Gyrosensor, and set itself automatically? that would further eliminate the friction needed to set a timer. One can argue that it takes less time to make a timer with my cube than doing so on any phone app, It can also be a work buddy, your little companion: MOMO™.
Main program used to sketch, design, and model 2D and 3D parts for laser cutting and 3D printing.
There were many ideas of what MOMO could look like and what its dimensions would be, but ultimately, I settled on making it out of laser cut 3mm wooden boards, and connecting the parts using T-slots and Bracket with Captive Nuts.
As for its dimensions, I wanted it to be as small as could possibly be, and after many failed attempts, 8x8x8 cm was large enough to hold its components but small enough to remain subtle in your work space, and this was the end result:
NOTE: All extruded text are for visuals only, we will use engraving for all text later on in the fabrication process, the cube is fully 2.5D
Now let's get into the design process...
6 CUBE SIDES ⌬
The actual cube body, we will use T-Slots to hold the sides together, and extrude cut for components.
A BRACKET ⌞
To firmly secure the top part to the rest of the cube.
OLED HOLDER ⃢
A small part to secure the OLED screen to the front face
The front face of the cube, which will contain cutouts for our components (RGB LED, OLED Display, Push button)
I took measurements of the components from real life and designed it as such.
I also Included a small part to hold the OLED screen using screws and nuts
As well as a T-Slot at the bottom to secure the front face.
Extruded 2.5D Front Face Part
Extruded 2.5D Holder Part
The bottom face, this is where we'll put the T-Slot cutouts to hold all four faces together.
It will also have a screw cutout to securely hold the Arduino Nano in the middle.
I put the text in a separate sketch on the same part, for clarity purposes.
Extruded 2.5D Base Part, Upright & upside down.
Used Rectangles, Circles, Arcs, & The mirror tool to make, fully defined.
A simple face, It will have a T-Slot cutout and an M3 screw cutout to secure our Gyrosensor to it
Text on a separate sketch for clarity purposes.
Extruded 2.5D Right Side Part
Used Rectangles, Circles, An arc, & The mirror tool to make, fully defined.
Exactly the same as the right side, but without the Gyrosensor screw cutout, and the text is different.
Text on a separate sketch for clarity purposes.
Extruded 2.5D Left Side Part
Used Rectangles, Circles, An arc, & The mirror tool to make, fully defined.
This is where we'll put the name of our little companion, MOMO.
Along with a couple expressive shapes and a M3 screw cutout for a bracket
Text & Shapes on a separate sketch for clarity purposes, rotated 180° to for better readability.
Extruded 2.5D Top Part
Used Rectangles, Circles, The mirror tool, & projection to make, fully defined.
This is where the user will power the cube from, I measured the DC Jack Barrel Connector dimensions from real life and cutout a circle exactly from the middle, It is important to do it this way so when the user rotates the cube, the wire won't resist rotation.
I also made a cutout for bracket's M3 screw, and a T-Slot cutout at the bottom.
Text on a separate sketch for clarity purposes.
Extruded 2.5D Back Part
Used Rectangles, Circles, & The mirror tool to make, fully defined.
I used many tools throughout the design process, and many techniques to hold the cube together.
New Component: Started a separate part to keep the design organized.
Create Sketch: Opened a new sketch on a face or plane to begin drawing features.
Extrude: Turned 2D sketches into 2.5D parts and cut features.
Rectangle: Used to quickly define the base outline of the part.
Circle: Added circular features like holes or round ends.
Arc: Created curved edges where a full circle wasn’t needed.
Text: Placed engraved/embossed labels directly into the sketch.
Mirror: Duplicated sketch geometry across a centerline to save time and keep symmetry.
Project: Brought existing edges into the sketch to reference and align new features.
Sketch Dimension: Applied precise sizes and constraints to fully define the sketch.
Elevation view of a T-Slot
Plan view of a T-Slot
A very simple setup that allows you to connect any two parts using a perpendicular intersection joint, I altered it a bit to give it a more seamless look.
A simple part that is used to connect two parts
El Malky ML64 Laser Cutter
The El Malky ML64 gave clean results with proper power/speed settings to cut out the cube's parts
LaserCAD
LaserCAD was used to import and prepare the DXF file.
3mm Boards
3mm wood is a good balance between durability and ease of cutting.
Creality Ender-3 Pro
Ender-3 Pro was used to print the bracket.
UltiMaker Cura
I used Cura to slice the STL model and set print parameters.
PLA Filament
PLA was chosen for its ease of use and availability
To laser cut the cube's parts, I first exported the faces individually as .dxf files, then put them together in one .dxf file to print them in one go.
Using these parameters:
Cut: Speed: 10, Power: 65
Engrave: Speed: 250, Power: 60, Direction: X Uni-Direction
I also adjusted the position of the items for space efficiency like so:
Now that we have our ready .dxf file, We'll export it to the laser cutter, adjust the origin point, and hit Start
As for the holder, I laser cut it from an earlier (failed) cut, and re-used it for the final print.
As you can see, the cut turned out really well!
the "h" didn't engrave in the bottom face for some reason but that's okay, it's easily fixable later on.
As for the bracket, it was a very simple process, just export the mesh as an STL file, Import it to Cura, adjusted the slicing parameters:
Layer Height: 0.2 mm
Infill: 10%
Supports: Not needed as print is stable.
Brim: Not needed as contact surface is large.
Orientation: Default orientation was flat and printable
Printing Temperature: 215°C
After saving the .gcode from Cura, I saved it onto a memory card and inserted it into the 3D Printer, selected it, and hit Start.
So far so good!, We have all of the parts, and I would like to give it some flair before the assembly...
Now, I am by no means an artist, But I tried my best at coloring the project, Take a look!
Fully assembled
Used to design the complete circuit.
Used to export code to the Arduino hardware
Consists of several components working together to create a smart timer system:
Brain:
Arduino Nano - The microcontroller that runs the whole system, processing sensor data and controlling all outputs.
Input Components:
MPU6050 Gyroscope/Accelerometer - This is the "orientation detector" that determines which face of the cube is pointing upward. It communicates with the Arduino via I2C protocol, sending acceleration data that gets translated into cube orientation.
Push Button - Provides a manual reset function for the current timer. This gives the user control to restart a session without needing to reorient the cube.
Action Components:
OLED Display (SSD1306) - A 128x64 pixel display that shows the current mode (WORK, BREAK, etc.) and the remaining time. The display orientation automatically rotates based on which face is up, ensuring readability.
RGB LED - Provides visual feedback about the current mode using color coding:
White: Work mode (25 min)
Green: Short break (5 min)
Blue: Long break (15 min)
Yellow: Pause mode
Buzzer - Delivers audio alerts when timers expire or modes change. I programmed different tones for different events.
Circuit Connections:
The MPU6050 connects to the Arduino's I2C pins (A4/SDA and A5/SCL)
The OLED display also uses the I2C bus, sharing connections with the MPU6050
RGB LED connects to digital pins 9 (red), 10 (green), and 11 (blue)
Buzzer connects to digital pin 7
Reset button connects to digital pin 6
The system works by continuously monitoring the MPU6050 sensor data. When it detects a stable orientation change (after a period of 1.5 seconds), it determines which face is up and switches to the appropriate mode. The Arduino then updates the display, changes the LED color, and plays the corresponding sound alert.
It may look complicated, but I assure you the wiring is simple, Since our cube is small, we are forced to also use a small breadboard and reduce the size of the wiring as much as possible, I will leave the project files made in Fritzing down below if you wish to explore it further.
Choosing the right power source was trickier than I expected, my first instinct was batteries, it would make the device portable, but would drastically increase the size and weight of the cube, and of course the batteries might run out mid-session, so it wasn't ideal.
Then I thought why not just use a 5V adapter directly? Well, the Arduino Nano expects regulated power, feeding it raw 5V, which from my testing with the Avometer actually gave out 5.44V (which is higher than the recommended range), was certainly not a good idea for a device running continuously for a long period of time.
My last idea was to use the Arduino Nano's own onboard voltage regulator (using the VIN pin), which means I can plug in any range of 7-12V, and the Nano would regulate it down to a stable 5V. The components are relatively low power so it wouldn't draw too much current and thus wouldn't produce a lot of heat.
This would allow me to use my 9V adapter, which is the recommended voltage for this project.
I weighed out the pros and cons for each approach and ultimately decided to use the onboard voltage regulator, which means I'll use my 9V adapter to power it (goes directly to VIN pin).
I also wrote out a helpful "9V DC RECOMMENDED, RANGE 7-12V" next to the DC barrel jack connector, so that users don't accidentally plug in anything lower or higher.
This one single component made me rage quit a couple of times, But alas I made it work in the end!
So, How does MOMO's code work? It might seem complicated at first, But It's quite straightforward, I'll explain it in detail shortly.
Since my project relies on mode states and transition conditions, I will use a simple finite state machine system for it, an FSM is a technique I've utilized many times in different languages, and I'm quite comfortable with it, so I'll use a VERY simple version of it to control mode states.
I'll also add a debounce period to safeguard against accidental triggers.
NOTE: Debouncing, in the context of programming, means to discard operations that occur too close together during a specific interval, and consolidate them into a single invocation.
IMPORTANT NOTE: If at any point you feel confused/unsure about how to do something or want more details in programming, ALWAYS visit the documentation first!
I know It is tempting to simply browse the internet or ask AI for answers, But trust me, from experience, What you're looking for is almost always in the docs.
https://docs.arduino.cc/programming/
INSPIRATION SOURCES
THESE RESOURCES WERE USED TO QUICKLY PROTOTYPE AND TEST THE LIBRARIES
Adafruit's GFX Graphics Library
MPU6050 Accelerometer & Gyroscope Tutorial
How to Program MPU6050 With Arduino
Parts of my own code were also reused from previous Weeks (Week 7, Week 8)
We start by including the core libraries our project needs: Adafruit_GFX and Adafruit_SSD1306 for drawing graphics and text on the OLED display, and MPU6050 for reading the gyrosensor's data.
Then we define the display’s resolution (128×64 pixels) and create a display object that handles all OLED operations through the I²C bus (Wire).
Finally, we create an mpu object, which will later be used to initialize and read motion/orientation data from the MPU6050 sensor.
These variables control the countdown timer logic.
timerDuration stores how long the timer should run (in seconds).
remainingTime tracks how many seconds are left until it finishes.
timerRunning is a flag that tells us if the timer is currently active or paused.
lastUpdate records the last time the timer was updated (using millis()), so we can correctly subtract seconds as real time passes.
Here we set up how the cube’s orientation translates into different modes, and add some safeguards so it doesn’t flip instantly.
enum Mode defines the possible states: WORK (25-min session), BREAK5 (short rest), BREAK15 (longer rest), and PAUSE.
currentMode keeps track of the active mode.
candidateMode holds a “proposed” mode whenever the cube tilts, it won’t become active until the delay passes.
candidateSince remembers the time (in millis()) when that candidate was first detected.
orientationDelay sets the debounce threshold (1 second) so small shakes or accidental tilts don’t trigger mode changes.
Finally, started is a flag to tell us if the user has pressed the start button yet, so we only show the welcome screen until they’re ready.
Here we handle all the audio feedback using the buzzer.
NOTE_F, NOTE_J, NOTE_K define frequencies for simple tones we’ll use in our sound patterns
beep(note, duration) plays a single tone:
tone() starts the buzzer at the given frequency.
delay(duration * 1.3) keeps it on slightly longer.
noTone() stops the sound.
Then we define small helper functions that string beeps together into recognizable alerts:
soundWork() → short chime sequence signaling a work session start.
soundBreak() → different two-tone pattern for breaks.
soundPause() → one long tone to indicate pause.
soundFinish() → descending three-tone signal when a timer ends.
soundReset() → two quick beeps for reset/startup confirmation.
These two functions are utility helpers for drawing text centered on the OLED screen.
drawCentered:
Takes any string (text), position (y), size (textSize), and screen rotation (rotation).
Uses getTextBounds to measure the width/height of the text
Calculates the horizontal offset so the text is perfectly centered based on whether the screen is rotated (128px wide vs 64px wide).
Moves the cursor and prints the text.
drawCenteredNumber:
Almost identical, but specifically intended for the countdown timer.
Together, these ensure the display always looks always matches the current mode orientation.
This helper function simply controls the RGB LED by turning its color channels on or off, we used it plenty of times before.
It takes three booleans (r, g, b) that represent whether the red, green, or blue pin should be active.
Each channel is written as HIGH if true, or LOW if false.
By mixing the three, we can easily create different LED colors to match the current mode.
This function handles switching between modes whenever the cube changes orientation.
currentMode is updated to the new mode.
For WORK, BREAK5, and BREAK15:
The session length is set (25, 5, or 15 minutes).
If we didn’t come from PAUSE, the timer resets to a full session.
The timer starts running (timerRunning = true) and lastUpdate is refreshed with millis().
A sound cue is played to confirm the change.
The RGB LED is set to a unique color for that mode.
For PAUSE:
The timer is stopped (timerRunning = false).
A different pause sound is played.
The LED is set to a distinct pause color.
In short, this function resets timers, plays sounds, and updates LED feedback to clearly indicate which mode the device just entered.
This is the initialization block that runs once when the device powers up.
Serial & I²C setup:
Serial.begin(9600) opens a serial connection for debugging.
Wire.begin() starts I²C communication for the OLED and MPU6050.
OLED setup:
display.begin(...) initializes the screen at address 0x3C.
If initialization fails, the program stops with an error message.
The display is cleared and text color set to white for later drawing.
MPU6050 setup:
mpu.initialize() configures the gyrosensor.
mpu.testConnection() checks communication, if it fails, it also stops with an error message.
Pins setup:
The RGB pins are set as outputs, the button as input.
setColor(false, false, false) ensures the LED starts off.
Basically, this function gets all hardware ready: display, gyrosensor, buzzer, RGB LED, and button input.
This is the main control loop, running continuously after setup. It ties everything together.
1. Button + startup screen
Reads the button state each cycle.
If the cube hasn’t been started yet, it shows a “Welcome” message on the OLED.
On first button press, it flips started = true and jumps straight into WORK mode.
2. Orientation detection
Reads raw acceleration values from the MPU6050.
Normalizes them (fax, fay, faz).
Rounds to integers to simplify detection.
Checks orientation to assign a Mode:
Upright = WORK
Tilt one way = BREAK5
Tilt opposite = BREAK15
Upside down = PAUSE
NOTE: The checking values came from real life tests with the gyrosensor, If you wish to do the same, simply println fax, fay, faz continuously and move the sensor, note the readings you get, and assign values accordingly.
Uses debounce: only if the new orientation is held steady for at least orientationDelay does it trigger switchMode().
3. Timer countdown
If a timer is running, checks if 1 second passed since lastUpdate.
Decrements remainingTime by one second.
If time hits zero, stops the timer and plays the finish sound.
4. Button reset
If the button is pressed during any mode, resets that mode’s timer back to full duration and plays the reset sound.
5. Screen rotation + drawing
Sets the display rotation depending on the current mode, so the text aligns with cube orientation.
Clears the screen.
Displays the current mode text (WORK / BREAK / PAUSE).
Formats remainingTime into MM:SS.
Centers the countdown using either a large font (normal) or adjusted position (rotated).
Updates the OLED.
Ends with a short delay(100) to smooth updates and avoid spamming the screen too quickly.
And that's it, It can definitely be improved and enhanced further but I'm quite happy with how the code turned out.
Now that we have all parts of our project ready, Let's begin assembly, we'll first assemble the circuit, upload the code and test its functionality.
Connecting the push button.
Connecting the jack barrel.
Connecting the RGB LED.
Connecting the MPU6050.
Connecting the Buzzer.
Connecting the OLED Display.
Full Circuit
Everything seems to be working well, Let's move on.
Now we need to mount each component to a specific area in the cube's chassis
All connected and secured, now let's put the cube together with M3 screws
Perfect! Our cube is now fully assembled, we just need to turn it on and hope everything works as intended.
Success! The device performs the Minimum, Complete, and Nice to have features!
Originally I had only planned for three modes, Work, Break, & Pause, this was mainly because I could not figure out how to make it so when the user flips the cube upside down to pause the timer, It doesn't automatically switch modes (because Break mode is in between Work & Pause).
And I was genuinely convinced that this was unsolvable, it was a compromise I had to make.
"Oh, you can just a debounce timer so actions only trigger each second or two, which will give the user enough time to settle on a mode, and also prevent accidental mode switches" -My friend said casually, upon knowing I had given up on it.
I was stunned for a second at how incredibly simple that approach was, completely solving the problem AND adding additional safeguards against accidents, Brilliant.
As for actual Implementation, It took me a while but I figured out a system where you basically assign a possible candidate mode when the orientation changes, then it waits for a specific orientation delay, If exactly one second has passed and the candidate mode did not change, that means that the user has settled on a mode they want to switch to, becoming currentMode.
No seriously, working with it was really humbling, I thought it'd be easy, just get X, Y, Z, and figure out current orientation, simple right?
This mindset of "It should be easy, this won't take long" was exactly the problem, It took a couple of days to get it working reliably, Had I taken a step back and looked at the complete picture of what was going on and how exactly it works, I wouldn't have had this much trouble.
So, what exactly were my mistakes?
There were plenty, but the main ones are:
Not looking at proper documentation / tutorial beforehand
Not coming up with a plan to work with it
Repeatedly trying the same thing over and over hoping it'll work
Not testing the component's features.
Not properly debugging what was happening and why it was happening.
All of those mistakes stemmed from the fact I was convinced it was easy and wasn't going to take much time.
The truth is, it was easy, but my approach made it unnecessarily difficult.
What was my approach?
I wanted to record the ranges in which each orientation had, then use these ranges to dictate transitional conditions.
Basically, Imagine that when X is lower than 0.40 and higher than 0.10, the orientation is upright, and so on and so forth for all other cases.
This could probably work, but it needed a lot of calibration and edge case testing.
How did I rectify my mistakes?
I began to actually think about what I need to do, the sensor checks acceleration data on all X, Y, and Z axes, We need to check for one plane's rotation, the same plane as the front face, which happened to be the X-Z plane.
Now, there are four possible orientations, Let's see what each orientation outputs in terms of acceleration data, after testing I got:
Upright: X = -1, Z = 0
Left: X = 0, Z = 1
Right: X = 0, Z = -1
Upside down: X = 1, Z = 0
This data makes perfect sense, and Y was of course always 0 so its data was irrelevant.
But it did have a lot of noise, which means I'll need to do complex checking for ranges (ex: 0.9 < X < 1.1 && -0.1 < Z < 0.1)
But what If I rounded the noisy data FIRST, then simply checked for complete integers? That'd be a much simpler approach.
And it worked flawlessly.
If I had more time, I’d add the ability to adjust the timer duration manually, so the user isn’t limited to just 25/15/5 minutes. Since the system already has an OLED screen, it would be straightforward to design a simple GUI for this, and I could even add a dial, like a potentiometer, as an input method. Or better yet, add two buttons to increase or decrease the duration by one minute.
I’d also enhance the GUI further by adding animations, borders, and visual effects, since the OLED display is more than capable of handling fast and complex visuals.