My project is a custom digital chess clock.
Purpose: make chess clocks affordable and available in Egypt.
Commercial clocks are often too expensive or outdated.
I care because I’m passionate about chess and want to help the local community.
It’s also a chance to learn electronics, coding, and design.
Introduction to Fusion 360
Fusion 360 is a 3D design software by Autodesk that combines CAD, CAM, and CAE tools in one platform. It’s used to create 2D sketches, 3D models, and prepare files for manufacturing like 3D printing or laser cutting
Designing the Front Face
I used Fusion 360 to design the front face of the glasses by creating a 2D sketch. After finishing the sketch, I exported it as a DXF file to be used for laser cutting.
Designing the Arms
Next, I designed the arms of the glasses in 3D. Once complete, I saved the model as a mesh (STL file), which was then prepared for 3D printing using Cura.
Software:
To prepare our 3D designs for printing, we rely on UltiMaker Cura 5.10.1. This software acts as our "slicer," taking our 3D models and translating them into the G-code that's sent directly to the 3D printer.
Machines:
Creality Ender 3 3D Printer
Materials:
PLA: it is the most common material and made from organic materials, easy to print with as it doesn't need special environment and available in many colors.
Software:
Our design process involved LaserWorkV6, which we used to prepare the 2D designs, convert DXF files into the necessary SLD format, and fine-tune the laser cutter settings.
Machine:
El Malky laser cutter machine.
Material:
For this project, we selected 3mm plywood, a material known for its ease of cutting and durability.
First Step
I searched for projects similar to mine to use as a reference and found this suitable project .
I started by making a 2D sketch of the button plate. Then, I extruded it by
Next, I created the side plates. I designed one side first, then copied and paste new component. This ensured I had two separate, unconnected parts.
Next, I created the back plate. I based its design on the other plates, using the 'Project' tool to copy their tabs.
Next, I created the front plate. I based its design on the other plates, using the 'Project' tool to copy their tabs.
Next, I created the Top plate. I based its design on the other plates, using the 'Project' tool to copy their tabs.
Next, I created the Display plate. I based its design on the other plates, using Project tool to copy their tabs.
Next, I inserted the electronic components to check their fit within the enclosure. However, the battery was too large, so I had to scale the sketch.
I designed the base for the chess clock buttons by creating a 2D sketch and then extruding it symmetrically from a mid-plane.
With the base complete, I then modeled the button on top of it.
In order to mount the limit switch to the base, I first designed a bracket.
Then, I did a section analysis to check if the movement was working properly.
After designing the chess clock in Fusion 360, I exported the 2D sketch as a DXF file and imported it into LaserWorkV6 for job setup. This critical stage involved verifying the design and assigning the machine parameters for two distinct operations. For all perimeter cuts, I set the parameters to 65% power and 15 mm/s speed. For the surface engraving, I used a Scan setting of 35% power and 300 mm/s speed. Once all toolpaths were configured, I converted the file to the required RLD format, making it ready for the machine.
After finalizing the design of the chess clock button in Fusion 360, I exported the model as an STL mesh. I then imported this file into Ultimaker Cura to configure the slicer settings for printing. To achieve a good balance between strength, speed, and material use, I set the layer height resolution to 0.2mm and the infill density to 20%. Once the printing profile was fine-tuned, I sliced the model and saved the resulting G-code for the 3D printer.
import our project to the machine (DXFformat)
make sure the plywood sheet is aligned
Make sure the focus length is appropriate.
set the origin
click frame to check the size
click start to cut the project
put the g.code file on the printer's SD Card
make sure the build Plate is clear .
pre-heat the nozzle
make sure the filament is pointed
select the file on the printer.
start printing
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
Add Player Buttons
First button: one side → GND, other side → pin 2, with an external pull-up resistor to 5V.
Second button: one side → GND, other side → pin 3, with an external pull-up resistor to 5V.
Inputs
Two Player Buttons
One for White player
One for Black player
Three Control 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/Start timer
Decrement Button
Single click → Decrement by 1
Long click → Continuous decrement
Wireless Input (Bluetooth HC-05
Receives commands to: set timer, increment, decrement, apply bonus, apply delay until bonus, pause, reset, etc.
Outputs
LCD Screen → Displays timer countdown, mode, and status.
Buzzer → Beeps when the timer finishes or when specific events occur.
Controls
ON/OFF Switch → Powers the circuit.
Push Buttons → Managed via the OneButton library for single, double, and long presses.
Bluetooth Remote Commands → Alternative way to configure and control.
Power Supply
9V Battery → Powers Arduino through VIN pin or power jack.
Full Circuit Link
First, we gathered the required components: jumpers, small breadboard, Arduino, on/off switch, push button, limit switch ,Bluetooth module, buzzer ,and battery .
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 Fritzing simulator.
Then, we connected the yellow (SCL) and orange (SDA ) wires to Arduino pins A4 and A5, just like in the circuit simulation.
Next, For each pushbutton, one leg will connect to this ground rail on the breadboard
Next, I'll wire push buttons between an individual analog pin and Ground, while the remaining one will connect to a digital pin and Ground.
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.
The Arduino is powered through the VIN pin or the power jack using a rechargeable Lithium-ion battery. We can rely on the Arduino’s onboard voltage regulator since none of the connected components require higher voltage or current.
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
CLASS ChessTimer INHERITS FROM Timer
PROTECTED MEMBERS: fischerIncrementSeconds: INTEGER, default 0 bonusMinutes: INTEGER, default 0 movesUntilBonus: INTEGER, default 0
PRIVATE METHODS: METHOD increase(seconds: INTEGER) // Adds the given number of seconds to the end time. EndTime = EndTime + (seconds * 1000) END METHOD
PUBLIC METHODS: METHOD setTimeControls(movesForBonus: INTEGER, bonusMins: INTEGER, incrementSecs: INTEGER) // Sets the parameters for the time control. movesUntilBonus = movesForBonus bonusMinutes = bonusMins fischerIncrementSeconds = incrementSecs END METHOD
METHOD pause() OVERRIDE
// Apply Fischer increment.
increase(fischerIncrementSeconds)
// Decrement move counter for bonus time.
movesUntilBonus = movesUntilBonus - 1
// Apply bonus time if the required number of moves has been reached.
IF movesUntilBonus IS 0 THEN
increase(bonusMinutes * 60)
END IF
// Call the pause method of the parent Timer class.
CALL SUPER.pause()
END METHOD
CLASS ChessClock
PRIVATE MEMBERS:
WhitePlayer: ChessTimer
BlackPlayer: ChessTimer
isWhiteTurn: BOOLEAN
PUBLIC METHODS:
constructor()
// Initializes timers and sets turn to White.
setTimeControls(moves, bonus, increment)
// Applies time settings to both player timers.
SetWhite(time), SetBlack(time)
// Sets the starting time for a player (starts paused).
StartWhite()
// Begins the game by resuming White's timer.
switchTurn()
// Pauses the current player's timer.
// Resumes the other player's timer.
// Toggles the isWhiteTurn flag.
getWhiteRemainingTime() -> returns time
getBlackRemainingTime() -> returns time
// Returns the time left for the specified player.
isWhiteTimeFinished() -> returns boolean
isBlackTimeFinished() -> returns boolean
// Checks if the specified player's time has run out.
pause(): Pauses the currently active timer.
resume(): Resumes the currently active timer.
stop(): Stops both timers completely.
reset(): Resets timers and turn to the initial state.
END CLASS
// --- 1. GLOBAL VARIABLES AND STATES ---
// Define hardware pins for buttons, buzzer, etc. // Define global objects: LCD screen, Chess Clock logic, Button handlers.
// Define clock state variables:
// - WhiteSetTimer, BlackSetTimer (to hold time during setup)
// - selectedTimeField (which part of the time is being edited: H, M, or S)
// - selectedTcField (which time control is being edited: increment, bonus)
// - currentStatus (SETTING, READY, RUNNING, PAUSED, GAME_OVER) // - currentMode (BUTTONS or BLUETOOTH)
// - fischerIncrementSeconds, bonusMinutes, movesUntilBonus
// - needsDisplayUpdate (a flag to refresh the screen)
// --- 2. SETUP PROCEDURE --- PROCEDURE SETUP: Initialize Serial communication. Initialize LCD screen and turn on backlight. Display a startup message ("Chess Clock", "Ready!").
// Link button events to their handler functions:
Player Buttons:
Attach Click event for White Player Button to WhitePlayerButtonClick.
Attach Click event for Black Player Button to BlackPlayerButtonClick.
Setting Buttons:
Attach Click and Long Press events for Increment, Action, and Decrement buttons to their respective handler functions. END PROCEDURE
// --- 3. MAIN LOOP --- PROCEDURE MAIN_LOOP: // This loop runs continuously.
// 1. Check for button presses. Poll all buttons (Player buttons, Increment, Action, Decrement).
// 2. Check for Bluetooth commands if in the correct mode. IF currentMode is BLUETOOTH THEN CALL handleBluetoothCommands(). END IF
// --3 - Display Logic Pseudocode
// --- Helper Functions for LCD Output ---
PROCEDURE printTimeToLcd(WhiteTime, BlackTime, rowNumber): // Formats and prints the time for both players on a specific LCD row. Create a formatted string: "H:MM:SS H:MM:SS" using the hours, minutes, and seconds from WhiteTime and BlackTime. Set the LCD cursor to column 0 on the given rowNumber. Print the formatted string to the LCD. END PROCEDURE
PROCEDURE showLcdMessage(line1, line2, duration): // Displays a temporary message on the LCD for a set duration. Clear the LCD screen. Print line1 on the first row. Print line2 on the second row. Print the same message to the Serial monitor for debugging. Pause execution for the specified duration in milliseconds. Set the global 'needsDisplayUpdate' flag to TRUE to force a screen refresh afterward. END PROCEDURE
// --- Main Display State Handlers ---
PROCEDURE displayRunningTimer: // Handles the screen updates while the clock is ticking or paused. IF either the White player's time or Black player's time has run out THEN Set 'whiteLost' variable based on whose time is finished. Set 'needsDisplayUpdate' flag to TRUE. Change the global 'currentStatus' to GAME_OVER. CALL displayGameOver(). EXIT PROCEDURE. END IF
// To prevent screen flicker, only update the display ~4 times per second. IF 250 milliseconds have passed since the last display update THEN Record the current time as the 'lastDisplayUpdate' time. CALL printTimeToLcd with the current remaining time for both players.
IF the global 'currentStatus' is PAUSED THEN
Display the text "(PAUSED)" on the second line of the LCD.
END IF
END IF END PROCEDURE
PROCEDURE displayGameOver: // Handles the screen and buzzer when one player's time runs out.
// This section runs only ONCE when the game ends. IF the 'needsDisplayUpdate' flag is TRUE THEN Clear the LCD screen. IF 'whiteLost' is TRUE THEN Display "Black Wins!" ELSE Display "White Wins!" END IF Display "Press any button" on the second line. Reset the 'buzzCount' to 0. Set 'needsDisplayUpdate' flag to FALSE. END IF
// This section runs repeatedly to make the buzzer sound. IF 'buzzCount' is less than 10 AND 500 milliseconds have passed since the last buzz THEN Toggle the buzzer's power (turn it on if it was off, and off if it was on). Record the current time as the 'lastBuzzTime'. Increment the 'buzzCount'. ELSE IF 'buzzCount' has reached 10 THEN Make sure the buzzer is turned off. END IF END PROCEDURE
// --- Flashing/Blinking Effect Handlers ---
PROCEDURE LCDSettingFlash: // Creates a blinking effect for the time digit currently being edited.
// Every 500 milliseconds, toggle a local 'needsUpdate' flag. IF 500 milliseconds have passed since the last update THEN Invert the 'needsUpdate' flag (TRUE becomes FALSE, FALSE becomes TRUE). // Also toggle the global flag to force the main loop to redraw the screen. Invert the global 'needsDisplayUpdate' flag. END IF
// If the 'needsUpdate' flag is TRUE, temporarily erase the selected digit. IF 'needsUpdate' is TRUE THEN SWITCH on the currently 'selectedTimeField': CASE White's Hours: Print a blank space over the hours digit for White. CASE White's Minutes: Print blank spaces over the minutes digits for White. CASE White's Seconds: Print blank spaces over the seconds digits for White. CASE Black's Hours: Print a blank space over the hours digit for Black. CASE Black's Minutes: Print blank spaces over the minutes digits for Black. CASE Black's Seconds: Print blank spaces over the seconds digits for Black. END SWITCH END IF END PROCEDURE
PROCEDURE LCDTcSettingFlash: // Creates a blinking effect for the time control value currently being edited. // This logic is identical to LCDSettingFlash but uses the 'selectedTcField'.
IF 500 milliseconds have passed since the last update THEN Invert a local 'needsUpdate' flag. Invert the global 'needsDisplayUpdate' flag. END IF
IF 'needsUpdate' is TRUE THEN SWITCH on the currently 'selectedTcField': CASE Increment: Print blank spaces over the increment value. CASE Bonus Minutes: Print blank spaces over the bonus minutes value. CASE Moves for Bonus: Print blank spaces over the moves value. END SWITCH END IF END PROCEDURE
// --4- Bluetooth Command Handler Pseudocode
PROCEDURE handleBluetoothCommands: // This function is called repeatedly in the main loop if the clock is in BLUETOETOOTH mode.
// 1. Read Input IF data is available on the Serial port THEN Read the incoming line of text into a string variable 'input'. Trim any leading/trailing whitespace from 'input'. Convert 'input' to all uppercase letters to make commands case-insensitive.
// If the line was empty, do nothing.
IF 'input' has no length THEN
EXIT PROCEDURE.
END IF
// 2. Process Commands
// Use an IF/ELSE IF structure to check the command.
// --- SET Command (sets time for BOTH players) ---
IF 'input' starts with "SET:" THEN
IF the 'currentStatus' is not SETTING THEN
showLcdMessage("Error:", "Stop timer first").
EXIT PROCEDURE.
END IF
Attempt to parse hours, minutes, and seconds from the 'input' string.
IF parsing is successful AND the time values are valid (e.g., minutes < 60) THEN
Set WhiteSetTimer to the parsed time.
Set BlackSetTimer to the parsed time.
Update the LCD to show the new time settings.
ELSE
showLcdMessage("Error:", "Invalid time format/value").
END IF
// --- SET_WHITE Command ---
ELSE IF 'input' starts with "SET_WHITE:" THEN
IF the 'currentStatus' is not SETTING THEN
showLcdMessage("Error:", "Stop timer first").
EXIT PROCEDURE.
END IF
Attempt to parse hours, minutes, and seconds.
IF parsing is successful AND time values are valid THEN
Set WhiteSetTimer to the parsed time.
Update the LCD.
ELSE
showLcdMessage("Error:", "Invalid time format/value").
END IF
// --- SET_BLACK Command ---
ELSE IF 'input' starts with "SET_BLACK:" THEN
// (Logic is the same as SET_WHITE, but for BlackSetTimer)
IF the 'currentStatus' is not SETTING THEN
showLcdMessage("Error:", "Stop timer first").
EXIT PROCEDURE.
END IF
Attempt to parse hours, minutes, and seconds.
IF parsing is successful AND time values are valid THEN
Set BlackSetTimer to the parsed time.
Update the LCD.
ELSE
showLcdMessage("Error:", "Invalid time format/value").
END IF
// --- START Command ---
ELSE IF 'input' is exactly "START" THEN
IF 'currentStatus' is SETTING or READY THEN
IF the timer values are valid (not zero) THEN
Apply the time control settings (increment, bonus) to the clock.
Set the final times for both players in the chess clock logic.
Change 'currentStatus' to RUNNING.
showLcdMessage("Timer Started!", "").
ELSE
showLcdMessage("Error:", "Set time first").
END IF
ELSE
showLcdMessage("Error:", "Already running").
END IF
// --- PAUSE Command ---
ELSE IF 'input' is exactly "PAUSE" THEN
IF 'currentStatus' is RUNNING THEN
Pause the chess clock.
Change 'currentStatus' to PAUSED.
showLcdMessage("Timer Paused", "").
ELSE
showLcdMessage("Not running", "").
END IF
// --- RESUME Command ---
ELSE IF 'input' is exactly "RESUME" THEN
IF 'currentStatus' is PAUSED THEN
Resume the chess clock.
Change 'currentStatus' to RUNNING.
showLcdMessage("Timer Resumed", "").
ELSE
showLcdMessage("Not paused", "").
END IF
// --- STOP Command ---
ELSE IF 'input' is exactly "STOP" THEN
IF 'currentStatus' is RUNNING, PAUSED, or GAME_OVER THEN
Stop and reset the chess clock.
Change 'currentStatus' to SETTING.
showLcdMessage("Timer Stopped", "").
ELSE
showLcdMessage("Not running", "").
END IF
// --- SET_INC Command (Fischer Increment) ---
ELSE IF 'input' starts with "SET_INC:" THEN
Attempt to parse the increment value (in seconds) from 'input'.
IF the value is valid (e.g., 0-59) THEN
Set the 'fischerIncrementSeconds' variable.
showLcdMessage("Increment Set", "Per move: [value]s").
ELSE
showLcdMessage("Error:", "Invalid increment").
END IF
// --- SET_DELAY Command (Bonus Time) ---
ELSE IF 'input' starts with "SET_DELAY:" THEN
Attempt to parse "MOVES=X,MINS=Y" format.
IF parsing is successful AND values are valid THEN
Set 'movesUntilBonus' and 'bonusMinutes' variables.
showLcdMessage("Delay Set OK", "Add [Y] min after [X]").
ELSE
showLcdMessage("Error:", "Invalid format/values").
END IF
// --- HELP Command ---
ELSE IF 'input' is exactly "HELP" THEN
Print a list of all available commands to the Serial monitor.
showLcdMessage("Commands sent to", "Serial Monitor").
// --- Unknown Command ---
ELSE
showLcdMessage("Unknown command", "Send 'HELP'").
END IF
END IF END PROCEDURE
// --5- Button Callback Logic Pseudocode
// --- Player Button Functions ---
PROCEDURE switchPlayerTurn(isWhiteButtonPressed): // Handles the logic when either the left or right player button is pressed.
IF 'currentStatus' is RUNNING THEN // Game is active, so switch turns. IF isWhiteButtonPressed is the same as the current player's turn THEN // Ignore the press if it's the same player pressing their button again. EXIT PROCEDURE. END IF Call chessClock.switchTurn().
ELSE IF 'currentStatus' is READY THEN // This is the first move of the game. IF isWhiteButtonPressed is TRUE THEN // This allows players to swap sides before the game starts. Swap the physical button objects (whitePlayerButton and blackPlayerButton). Swap the player timers inside the chess clock logic by calling chessClock.SwapPlayers(). END IF Change 'currentStatus' to RUNNING. Start the White player's timer. END IF
Update the LCD to show whose turn it is to move. Set 'needsDisplayUpdate' flag to TRUE. END PROCEDURE
PROCEDURE WhitePlayerButtonClick: // Wrapper for the left button press. Print "WhitePlayerButtonClick" to Serial for debugging. CALL switchPlayerTurn(TRUE). Set 'isWhite' flag to TRUE. END PROCEDURE
PROCEDURE BlackPlayerButtonClick: // Wrapper for the right button press. Print "BlackPlayerButtonClick" to Serial for debugging. CALL switchPlayerTurn(FALSE). Set 'isWhite' flag to FALSE. END PROCEDURE
// --- General & Settings Button Functions ---
PROCEDURE resetFromFinishedState: // Resets the clock to its initial state after a game is over. Clear the LCD. Reset the chess clock logic. Turn off the buzzer. Change 'currentStatus' to SETTING. Reset the 'selectedTimeField' to the first option (White's Hours). Set 'needsDisplayUpdate' flag to TRUE. END PROCEDURE
// --- Increment Button (+) Callbacks ---
PROCEDURE incrementClick: IF 'currentStatus' is GAME_OVER THEN CALL resetFromFinishedState(). EXIT PROCEDURE. END IF
IF 'currentMode' is not BUTTONS THEN EXIT PROCEDURE.
IF 'currentStatus' is SETTING THEN // Increment the currently selected time field (H, M, or S for White/Black). SWITCH on 'selectedTimeField': CASE Hours: Increment hours, wrap around from 9 to 0. CASE Minutes: Increment minutes, wrap around from 59 to 0. CASE Seconds: Increment seconds, wrap around from 59 to 0. END SWITCH ELSE IF 'currentStatus' is SETTINGS_TIMECONTROL THEN // Increment the currently selected time control value. SWITCH on 'selectedTcField': CASE Increment: Increment 'fischerIncrementSeconds', wrap from 59 to 0. CASE Bonus Minutes: Increment 'bonusMinutes', wrap from 59 to 0. CASE Moves for Bonus: Increment 'movesUntilBonus', wrap from 99 to 0. END SWITCH END IF Set 'needsDisplayUpdate' flag to TRUE. END PROCEDURE
PROCEDURE incrementLongPress: // Called repeatedly by the button library during a long press. CALL incrementClick(). Delay for 50 milliseconds to control the speed. END PROCEDURE
// --- Action/Mode Button (Middle) Callbacks ---
PROCEDURE actionClick: // Handles a single short press of the middle button. IF 'currentStatus' is GAME_OVER THEN CALL resetFromFinishedState(). EXIT PROCEDURE. END IF
IF 'currentStatus' is SETTING AND 'currentMode' is BUTTONS THEN // Cycle through the time fields to edit (H:M:S for White, then H:M:S for Black). Advance 'selectedTimeField' to the next value in the enum. ELSE IF 'currentStatus' is SETTINGS_TIMECONTROL AND 'currentMode' is BUTTONS THEN // Cycle through the time control fields to edit (Increment, Bonus Mins, Bonus Moves). Advance 'selectedTcField' to the next value in the enum. ELSE IF 'currentStatus' is RUNNING THEN // Pause the game. Pause the chess clock. Change 'currentStatus' to PAUSED. ELSE IF 'currentStatus' is PAUSED THEN // Resume the game. Resume the chess clock. Change 'currentStatus' to RUNNING. END IF Set 'needsDisplayUpdate' flag to TRUE. END PROCEDURE
PROCEDURE actionDoubleClick: // Handles a double press of the middle button. IF 'currentStatus' is SETTING or SETTINGS_TIMECONTROL THEN // Switch between BUTTONS and BLUETOOTH modes. Toggle the 'currentMode'. Print the new mode name to the Serial monitor for debugging. Set 'needsDisplayUpdate' flag to TRUE. END IF END PROCEDURE
PROCEDURE actionLongPressStart: // Handles a long press of the middle button to confirm settings or stop the game. IF 'currentStatus' is SETTING THEN IF TimervaluesIsRight() is TRUE THEN // Advance from setting time to setting time controls. Change 'currentStatus' to SETTINGS_TIMECONTROL. Print "Set time control." to Serial. ELSE Print "Cannot set: Timer is zero." to Serial. END IF ELSE IF 'currentStatus' is SETTINGS_TIMECONTROL THEN // Confirm all settings and get ready to play. Apply time control settings to the chess clock. Set the final times for White and Black. Pause the clock immediately (game starts when a player makes the first move). Change 'currentStatus' to READY. Print "Time set. Ready to start." to Serial. ELSE IF 'currentStatus' is RUNNING, PAUSED, or READY THEN // Acts as a "STOP" or "RESET" button during a game. Clear the LCD. Reset the chess clock logic. Change 'currentStatus' back to SETTING. Print "Timer stopped." to Serial. END IF Set 'needsDisplayUpdate' flag to TRUE. END PROCEDURE
// --- Decrement Button (-) Callbacks ---
PROCEDURE decrementClick: // Logic is the inverse of incrementClick. IF 'currentStatus' is GAME_OVER THEN CALL resetFromFinishedState(). EXIT PROCEDURE. END IF
IF 'currentMode' is not BUTTONS THEN EXIT PROCEDURE.
IF 'currentStatus' is SETTING THEN // Decrement the currently selected time field. SWITCH on 'selectedTimeField': CASE Hours: Decrement hours, wrap around from 0 to 9. CASE Minutes: Decrement minutes, wrap around from 0 to 59. CASE Seconds: Decrement seconds, wrap around from 0 to 59. END SWITCH ELSE IF 'currentStatus' is SETTINGS_TIMECONTROL THEN // Decrement the currently selected time control value. SWITCH on 'selectedTcField': CASE Increment: Decrement 'fischerIncrementSeconds', wrap from 0 to 59. CASE Bonus Minutes: Decrement 'bonusMinutes', wrap from 0 to 59. CASE Moves for Bonus: Decrement 'movesUntilBonus', wrap from 0 to 99. END SWITCH END IF Set 'needsDisplayUpdate' flag to TRUE. END PROCEDURE
PROCEDURE decrementLongPress: // Called repeatedly during a long press. CALL decrementClick(). END PROCEDURE
// --- Utility Functions ---
FUNCTION TimervaluesIsRight: // Checks if the time set for both players is valid to start a game. isWhiteTimeValid = (WhiteSetTimer.hours > 0) OR (WhiteSetTimer.minutes > 0) OR (WhiteSetTimer.seconds > 1). isBlackTimeValid = (BlackSetTimer.hours > 0) OR (BlackSetTimer.minutes > 0) OR (BlackSetTimer.seconds > 1). RETURN (isWhiteTimeValid AND isBlackTimeValid). END FUNCTION
Pulse 50% of this code is based on my assignments from weeks 7 and 8, while the remaining portion has been customized to implement the timer functionality for the chess clock. Most of my use of AI tools has been for naming conversions and display formatting. I also occasionally use them to remove unused functions and clean up the code, but not for core logic. I do not use AI to generate code.
My peer, Youssef, ran into a common fabrication issue where the machine was cutting the external outline of his part before cutting the internal features. This caused the piece to drop out of the main sheet of material, making it impossible for the machine to then cut the inner holes and lines accurately.
I suggested he adjust the order of operations in the software. The correct method is to program the machine to cut all internal features first, and only after they are complete, cut the final external outline last. This ensures the part remains stable until all the detailed work is finished.
On the front and display plate, the angle between them is not 90 degrees, which creates an intersection. My initial plan was to use sandpaper to fix this, but my instructor, Manar, suggested a better solution: printing a bracket with the same angle between the two plates, as shown in the second image.
I faced an error while uploading the code to Arduino because Timer.h was included in both the ChessTimer file and the Chess_clock_code.ino file. I resolved it by removing the duplicate include from Chess_clock_code.ino.
I faced another issue with the displayGameOver() function. It was supposed to show the winner on the screen and play a sound. The sound worked fine, but the display didn’t update. The problem occurred because in displayRunningTimer, the first step was to check if the game had ended and then determine the winner. At that point, needsDisplayUpdate was set to true. However, when control returned to the display update routine, needsDisplayUpdate was reset to false again, preventing the screen from refreshing.
To fix this, I called displayGameOver() directly inside displayRunningTimer right after setting needsDisplayUpdate = true. This ensured the display updated correctly and showed the winner as intended.
In the third image, the function should flash the active setting section, but instead it hides it. The issue comes from writing:
needsUpdate != needsUpdate;
This line only compares the variable with itself, which always returns false, and the result is never stored. In other words, it does nothing.
What I should have written is:
needsUpdate = !needsUpdate;
This correctly assigns the opposite of the current value back to the variable, toggling it between true and false each time. That way, the flashing effect works as intended.
I had a problem where, if a player clicked the button multiple times, the switchPlayerTurn function was called each time. What I actually wanted was for it to be called only once per turn.
To solve this, I first needed a way to know which player was calling the function. I created a separate function for each player, and inside those functions, I called switchPlayerTurn with a parameter that indicates which player triggered it. Then, I introduced an isWhite variable to track whose turn it was.
If the wrong player tries to call the function, nothing happens. This ensures the turn only switches when the correct player clicks, preventing multiple unwanted calls.
Problem: The first version of the display plate was cut as a mirror image, with the left and right sides reversed.
Solution: I corrected the orientation by flipping the design horizontally in the software and then re-cut the plate successfully.
Problem: I initially forgot to include a slot for the on/off power switch in the design.
Solution: I adjusted the digital file to add the necessary cutout and re-cut the part to accommodate the switch.
Problem: On the third piece, a pre-cut hole intended for a mounting bracket was in the wrong position.
Solution: To fix this efficiently, I made a manual adjustment by drilling a new hole in the correct location.
if i have more time later, i will work on detecting movements on every single square to be more precise and turn the physical board into a digital board so you can watch live matches from away city or country.