Last week:
I built a Pomodoro timer, but it didn’t allow me to change the timer value.
This week:
I upgraded the design to include two modes for setting the timer value:
Button Control → Set the timer directly using physical buttons.
Bluetooth Control → Set the timer remotely using a paired device.
Timer
power connection
A black wire connects the Arduino’s GND pin to the negative (–) rail of the breadboard.
A red wire connects the Arduino’s 5V pin to one side pin of the slide switch.
The common pin of the switch is connected to the positive (+) rail of the breadboard.
First button
One side → Negative (GND) rail
Other side → Arduino A1 pin
Second button
One side → Negative (GND) rail
Other side → Arduino A2 pin
Third button
One side → Negative (GND) rail
Other side → Arduino A3 pin
GND → Negative rail
VCC → Positive rail (5V)
TXD (Bluetooth) → Arduino pin 0 (RX)
RXD (Bluetooth) → Arduino pin 1 (TX)
⚠️ through a voltage divider, because Arduino TX is 5V, but the Bluetooth RX pin only tolerates 3.3V max.
👉 The divider made:
1 resistor 1kΩ (between Arduino TX and Bluetooth RX)
1 resistor 2kΩ (between Bluetooth RX and GND)
This drops the 5V down to ~3.3V, safe for the Bluetooth module.
LCD Connection
GND → Negative rail
VCC → Positive rail
SDL → Arduino pin A5
SDA → Arduino pin A4
Negative (–) pin → Arduino GND / Negative rail of breadboard
Positive (+) pin → Arduino digital pin 11
Outputs
LCD Screen → Displays timer countdown, mode, and status.
Buzzer → Beeps when timer finishes or events occur.
Inputs
Three Push Buttons
Increment Button
Single click → Increment by 1
Long click → Continuous increment
Action Button
Single click → Pause/Resume timer (Manual Mode)
Double click → Change mode
Long click → Reset timer / Start timer
Decrement Button
Single click → Decrement by 1
Long click → Continuous decrement
Wireless Input via Bluetooth Module (HC-05)
Receives commands (set timer, increment, decrement, pause, reset, etc.).
Controls
ON/OFF Switch → Powers the circuit.
Push Button Functions (handled via OneButton library for click/long press/double click).
Bluetooth Remote Commands → Alternative way to set values and control timer.
Power Supply
9V DC Adapter (2A) → Powers Arduino through VIN or power jack.
Full Circuit Link
Timer Class Pseudocode
CLASS Timer
Properties:
endTime: A number representing the future timestamp when the timer will finish.
pauseTime: A number representing the timestamp when the timer was paused.
lastSecondMark: A number representing the timestamp of the last full second passed.
isRunning: A boolean indicating if the timer is active.
isPaused: A boolean indicating if the timer is paused.
Methods:
PROCEDURE Constructor()
BEGIN Set endTime, pauseTime, lastSecondMark to 0. Set isRunning and isPaused to false. END
PROCEDURE start(time)
BEGIN Calculate total duration in milliseconds from the input time structure. Set endTime = current system time + total duration. Set lastSecondMark = current system time. Set isRunning to true. Set isPaused to false. END
PROCEDURE stop()
BEGIN Set isRunning to false. Set isPaused to false. Reset endTime and pauseTime to 0. END
PROCEDURE pause()
BEGIN IF isRunning is true AND isPaused is false THEN Set pauseTime = current system time. Set isPaused to true. END IF END
PROCEDURE resume()
BEGIN IF isRunning is true AND isPaused is true THEN Calculate pausedDuration = current system time - pauseTime. Increase endTime by pausedDuration. Set isPaused to false. END IF END
FUNCTION isFinished() -> returns boolean
BEGIN IF isRunning is false OR isPaused is true THEN RETURN false. END IF RETURN (current system time >= endTime). END
FUNCTION hasOneSecondPassed() -> returns boolean
BEGIN IF isRunning is false OR isPaused is true THEN RETURN false. END IF
IF (current system time - lastSecondMark) >= 1000 THEN
Increase lastSecondMark by 1000.
RETURN true.
END IF
RETURN false.
END
FUNCTION getRemaining() -> returns time structure
BEGIN IF isRunning is false THEN RETURN a time structure of 00:00:00. END IF
DECLARE remainingMs as a number.
IF isPaused is true THEN
Set remainingMs = endTime - pauseTime.
ELSE
IF current system time >= endTime THEN
RETURN a time structure of 00:00:00.
END IF
Set remainingMs = endTime - current system time.
END IF
Convert remainingMs to total seconds.
Calculate hours, minutes, and seconds from total seconds.
RETURN a new time structure with the calculated values.
END END CLASS
Global Variables & Constants
Constants:
BUZZER_PIN = 11
INCREMENT_BUTTON_PIN = A1
ACTION_BUTTON_PIN = A2
DECREMENT_BUTTON_PIN = A3
Objects:
lcd: An object to control the 16x2 I2C LCD screen.
timer: An object from the Timer class to manage countdown logic.
incrementButton, actionButton, decrementButton: Button objects to handle presses, clicks, and long presses.
State Variables:
SetedTimer: A structure holding Hours, Minutes, Seconds for the user-defined time. Default to 00:00:00.
selectedTimeField: Tracks which part of the time is being edited (HOUR, MINUTE, or SECOND). Default to HOUR.
currentStatus: The timer's current state (SETTING_TIMER, TIMER_RUNNING, TIMER_PAUSED, TIMER_FINISHED). Default to SETTING_TIMER.
currentMode: The control mode (BUTTONS or BLUETOOTH). Default to BUTTONS.
needsDisplayUpdate: A boolean to signal when the screen needs to be redrawn. Default to true.
PROCEDURE setup()
BEGIN Initialize Serial communication at 9600 baud. Set BUZZER_PIN as an output and turn it off.
// Assign functions to button events Link incrementButton's click event to incrementClick function. Link incrementButton's long press event to incrementLongPress function.
Link actionButton's click event to actionClick function. Link actionButton's double-click event to actionDoubleClick function. Link actionButton's long press start event to actionLongPressStart function. Link actionButton's long press stop event to actionLongPressStop function.
Link decrementButton's click event to decrementClick function. Link decrementButton's long press event to decrementLongPress function.
Initialize the LCD screen. Turn on the LCD backlight. Display a welcome message ("Arduino Timer", "Ready!") for 2 seconds. END
PROCEDURE loop()
BEGIN // Continuously check for button events Check incrementButton for updates. Check actionButton for updates. Check decrementButton for updates.
// If in Bluetooth mode, check for incoming commands IF currentMode is BLUETOOTH THEN CALL handleBluetoothCommands(). END IF
// Update the display based on the current state SWITCH currentStatus: CASE SETTING_TIMER: IF currentMode is BUTTONS THEN CALL displaySetTimer(). ELSE CALL displayBluetoothSetTimer(). END IF CASE TIMER_RUNNING or TIMER_PAUSED: CALL displayRunningTimer(). CASE TIMER_FINISHED: CALL displayTimerFinished(). END SWITCH END
PROCEDURE handleBluetoothCommands()
BEGIN IF there is data available from Serial port THEN Read the incoming string until a newline character. Remove whitespace and convert the command to uppercase.
IF command is "SET:HH:MM:SS" THEN
IF timer is not already running THEN
Parse Hours, Minutes, Seconds from the string.
IF the time values are valid THEN
Update SetedTimer with new values.
Show a confirmation message on the LCD.
ELSE
Show an "Invalid time" error message.
END IF
ELSE
Show an error message ("Stop timer first").
END IF
ELSE IF command is "START" THEN
IF timer is in SETTING_TIMER state AND SetedTimer > 0 THEN
Start the timer with the value in SetedTimer.
Change currentStatus to TIMER_RUNNING.
Show "Timer Started!" message.
ELSE
Show an error message.
END IF
ELSE IF command is "PAUSE", "RESUME", or "STOP" THEN
Perform the corresponding timer action (pause, resume, stop).
Update currentStatus accordingly.
Show a confirmation message.
ELSE IF command is "HELP" THEN
Print a list of available commands to the Serial monitor.
ELSE
Show an "Unknown command" message on the LCD.
END IF
END IF END
PROCEDURE displayRunningTimer()
BEGIN IF timer.isFinished() is true THEN Set currentStatus to TIMER_FINISHED. Set needsDisplayUpdate to true. RETURN. END IF
IF one second has passed THEN Get remaining time from the timer object. Clear the LCD. Display the current status (e.g., "RUNNING"). Display the remaining time in HH:MM:SS format. END IF END
At first, in the handleBluetoothCommands function, I used substring with fixed index positions to parse commands. Although this worked, I felt there might be a better approach. After asking ChatGPT, I discovered that I could use sscanf and sprintf—just as we normally do with console input and output—but applied to a buffer instead of waiting for input directly from the console. ChatGPT also suggested adding a help command, which turned out to be a useful addition.
I’ve used these functions before, but since I haven’t coded in C for a long time, I had forgotten about them. Recently, I’ve been working mainly with higher-level languages like Python, C#, and Dart, which don’t require the same kind of low-level string handling. Most of my use of AI tools has been for naming conversions and display formatting, rather than for code logic.
PROCEDURE displayTimerFinished()
BEGIN IF needsDisplayUpdate is true THEN Clear the LCD. Display "TIMER FINISHED!". Display "Press any button". Reset buzz counter. Set needsDisplayUpdate to false. END IF
// Buzz periodically for a limited number of times IF it's time to toggle the buzzer THEN Toggle the BUZZER_PIN state (HIGH/LOW). Increment buzz counter. END IF END
PROCEDURE resetFromFinishedState()
BEGIN Turn off the buzzer. Set currentStatus to SETTING_TIMER. Reset SetedTimer to 00:00:00. Set selectedTimeField back to HOUR. Set needsDisplayUpdate to true. END
PROCEDURE incrementClick()
BEGIN IF currentStatus is TIMER_FINISHED THEN CALL resetFromFinishedState(). RETURN. IF currentStatus is not SETTING_TIMER or currentMode is not BUTTONS THEN RETURN.
SWITCH selectedTimeField: CASE HOUR: Increment SetedTimer.hours (wrap around at 24). CASE MINUTE: Increment SetedTimer.minutes (wrap around at 60). CASE SECOND: Increment SetedTimer.seconds (wrap around at 60). END SWITCH Set needsDisplayUpdate to true. END
PROCEDURE actionClick()
BEGIN IF currentStatus is TIMER_FINISHED THEN CALL resetFromFinishedState(). RETURN.
IF currentStatus is SETTING_TIMER and currentMode is BUTTONS THEN Cycle selectedTimeField (HOUR -> MINUTE -> SECOND -> HOUR). ELSE IF currentStatus is TIMER_RUNNING THEN Pause the timer and set currentStatus to TIMER_PAUSED. ELSE IF currentStatus is TIMER_PAUSED THEN Resume the timer and set currentStatus to TIMER_RUNNING. END IF END
PROCEDURE actionDoubleClick()
BEGIN IF currentStatus is SETTING_TIMER THEN Toggle currentMode between BUTTONS and BLUETOOTH. Set needsDisplayUpdate to true. END IF END
PROCEDURE actionLongPressStart()
BEGIN IF currentStatus is SETTING_TIMER THEN Start the timer if time is not zero. ELSE IF currentStatus is TIMER_RUNNING or TIMER_PAUSED THEN Stop the timer and return to SETTING_TIMER state. END IF END
First, we gathered the required components: jumpers, small breadboard, Arduino, on/off switch, push button, Bluetooth module, buzzer ,and 9V adapter .
Connect the buzzer’s negative (–) pin to the Arduino GND and the positive (+) pin to Arduino digital pin 11.
Connect GND and 5V to the breadboard
Use a black wire to connect the Arduino GND pin → negative (–) rail of the breadboard.
Use a red wire to connect the Arduino 5V pin → positive (+) rail of the breadboard.
To safely connect the Arduino TX (5V) to the Bluetooth module RX (3.3V max), I used a voltage divider:
1 kΩ resistor between Arduino TX and Bluetooth RX
2 kΩ resistor (Two resistor 1 kΩ in series) between Bluetooth RX and GND
This drops the 5V signal down to approximately 3.3V, making it safe for the Bluetooth module.
Then, connect the Bluetooth module exactly as the same way we did in our Fritzing simulator.
Then, we connected four wires to the LCD exactly as the same way we did in our Tinkercad simulator.
Then, we connected the yellow (SCL) and orange (SDA ) wires to Arduino pins A4 and A5, just like in the circuit simulation.
Next, upload the code to the Arduino but Before uploading, disconnect the TX and RX pins of the Bluetooth module or the upload will fail.
My peer Dalia faced some issues with the LCD in her assignment.
I helped her solve them by:
Fixing the wiring connections
Setting the correct I2C address
Adding the missing lcd.init(); line
Correcting the object creation for the LiquidCrystal_I2C library (she had used 20 columns × 4 rows instead of 16 columns × 2 rows).
When I tested the button mode, I noticed that after setting the timer ⏲ value, it either finished immediately or ran for a very short time. While debugging, I discovered that the calculation for DurationMS was producing a negative value, which is not logical. This caused the EndTime to be earlier than the current time, making the timer end instantly.
The issue happened because, in C/C++, if the variables in a calculation are integers, the compiler performs the computation in int first. Since the result exceeded the range of int, it overflowed into a negative number. Only afterward was the value assigned to the unsigned long variable.
The solution was to explicitly cast the operands to unsigned long or use the UL suffix (e.g., 60UL, 1000UL). This ensures the calculation is done in the correct type and prevents overflow, allowing the timer to work properly.
Chess clock🕒
This week we learned about wireless communication, which will be very useful for my final project, the chess clock. I plan to add a feature that allows the timer to be set using a phone via Bluetooth, and a nice-to-have feature is receiving and recording the time taken for each move played in the game. These experiences will help me integrate Arduino with sensors, design and prototype components more efficiently, simulate and test ideas before implementation, and solve technical issues with greater confidence.
This week, we built a robot arm controlled entirely by a mobile app 🤖📱
At the end of the week, I teamed up with my amazing colleagues Yasmeen and Dalia:
🧭 I guided as the navigator
🛠️ Dalia Ali handled the coding
🔌 Yasmeen took care of the circuit wiring
Together, we assembled the servo motors with the 3D-printed parts, tested everything… and it worked flawlessly 👍🏻
Finally, we faced the competition — and guess what?
🏆 We won! (with the funniest duck prize ever 🦆😂)
Micro Robot🤖 Arm