Skip to the content.

Chess Clock

For my project, I built a fully functional chess clock. The timer can be set using a rotary encoder—turning it clockwise adds 30 seconds, and turning it counterclockwise subtracts 30 seconds. The time can be set by pressing down on the encoder. Once set, the buttons on top function like a real chess clock, allowing each player to start and stop their timer.

One of the biggest challenges I faced was working with the code. Initially, I tried to do everything at once, which led to multiple errors and made debugging difficult. My key takeaway from this experience was the importance of working in smaller, manageable chunks. By breaking down the code step by step, I was able to identify problems more easily and complete the project much more efficiently.

Engineer School Area of Interest Grade
Ashwin R International School Computer Hardware Incoming Junior

Ashwin

Final Milestone

Final Milestone Code

For now, it’s the same as the second milestone code. I plan to add an increment and a more precise time setup later.

#include <TM1637Display.h>

// Rotary Encoder Inputs
#define CLKEN 13
#define DT 12
#define SW 11
#define CLK1 5
#define DIO1 6
#define CLK2 8
#define DIO2 9
TM1637Display display1 = TM1637Display(CLK1, DIO1);
TM1637Display display2 = TM1637Display(CLK2, DIO2);
int gameState = 0;
int counter = 0;
int displayTime = 0;
int displayTime1 = 0;
int counter1 = 0;
int formatTime = 0;
int formatTime1 = 0;
int gameTime = 0;
int gameTime1 = 0;
int currentStateCLK;
int lastStateCLK;



int buttonPin = 2;
int button2Pin = 4;
int buzzerPin = 10;
int buttonState = 0;
int button2State = 0;



String currentDir = "";
unsigned long lastButtonPress = 0;

unsigned long lastButtonPress1 = 0;

unsigned long lastButtonPress2 = 0;


bool timer1Active = false;
bool timer2Active = false;




void setup() {

  // Set encoder pins as inputs
  pinMode(CLKEN, INPUT);
  pinMode(DT, INPUT);
  pinMode(SW, INPUT_PULLUP);

  // Setup Serial Monitor
  Serial.begin(9600);

  // Read the initial state of CLK
  lastStateCLK = digitalRead(CLKEN);

  display1.setBrightness(7);
  display2.setBrightness(7);
  display1.clear();
  display2.clear();
  pinMode(buttonPin, INPUT);
  pinMode(button2Pin, INPUT);
  pinMode(buzzerPin, OUTPUT);

}

void loop() {

  counter = constrain(counter, 0, 5999);
  counter1 = constrain(counter1, 0, 5999);
  formatTime = constrain(formatTime, 0, 5999);
  formatTime1 = constrain(formatTime1, 0, 5999);
  buttonState = digitalRead(buttonPin);
  button2State = digitalRead(button2Pin);
  // Read the current state of CLK
  currentStateCLK = digitalRead(CLKEN);
  if (gameState == 0) { 
  // If last and current state of CLK are different, then pulse occurred
  // React to only 1 state change to avoid double count
    if (currentStateCLK != lastStateCLK && currentStateCLK == 1) {

    // If the DT state is different than the CLK state then
    // the encoder is rotating CCW so decrement
      if (digitalRead(DT) != currentStateCLK) {
        counter = counter+30;
        counter1 = counter1+30;
        currentDir = "CCW";
      } else {
      // Encoder is rotating CW so increment
        counter = counter-30;
        counter1 = counter1-30;
        currentDir = "CW";
    }

      Serial.print("Direction: ");
      Serial.print(currentDir);
      Serial.print(" | Counter: ");
      Serial.println(counter);
      gameTime = counter;
      gameTime1 = counter1;
      int minutes = gameTime / 60;
      int seconds = gameTime % 60;
      formatTime = (minutes * 100) + seconds;
      int minutes1 = gameTime1 / 60;
      int seconds1 = gameTime1 % 60;
      formatTime1 = (minutes1 * 100) + seconds1;
      display1.showNumberDecEx(formatTime, 0b11100000, false, 4, 0);
      display2.showNumberDecEx(formatTime1, 0b11100000, false, 4, 0);
      } }

  if (gameState == 1) {
    if (buttonState == HIGH && millis() - lastButtonPress1 > 200) {
    timer1Active = true;
    timer2Active = false;
    lastButtonPress1 = millis();
    Serial.println("Timer 1 started");
  }

  // Check button 2 (start Timer 2)
  if (button2State == HIGH && millis() - lastButtonPress2 > 200) {
    timer2Active = true;
    timer1Active = false;
    lastButtonPress2 = millis();
    Serial.println("Timer 2 started");
  }

  // Timer 1 countdown
  if (timer1Active && gameTime > 0) {
    gameTime--;
    int minutes = gameTime / 60;
    int seconds = gameTime % 60;
    displayTime = (minutes * 100) + seconds;
    display1.showNumberDecEx(displayTime, 0b11100000, false, 4, 0);
    delay(1000);  // Simple blocking delay for countdown
  }

  // Timer 2 countdown
  else if (timer2Active && gameTime1 > 0) {
    gameTime1--;
    int minutes = gameTime1 / 60;
    int seconds = gameTime1 % 60;
    displayTime1 = (minutes * 100) + seconds;
    display2.showNumberDecEx(displayTime1, 0b11100000, false, 4, 0);
    delay(1000);  // Simple blocking delay for countdown
  }

  if ((timer1Active && gameTime == 0) || (timer2Active && gameTime1 == 0)) {
  gameState = 2;
  }

  if (gameState == 2) { 
    tone(buzzerPin, 784);
    delay(5000);
    noTone(buzzerPin);

  }
}

  // Remember last CLK state
  lastStateCLK = currentStateCLK;

  // Read the button state
  int btnState = digitalRead(SW);

  //If we detect LOW signal, button is pressed
  if (btnState == LOW) {
    //if 50ms have passed since last LOW pulse, it means that the
    //button has been pressed, released and pressed again
    if (millis() - lastButtonPress > 50) {
      Serial.println("Button pressed!");
      gameState = gameState + 1;
    }

    // Remember last button press event
    lastButtonPress = millis();
  }

  // Put in a slight delay to help debounce the reading
  delay(1);
}

Second Milestone

For my Second Milestone:

Second Milestone Code

Here’s the code for the second milestone. It should be fully functional, but basic.

#include <TM1637Display.h>

// Rotary Encoder Inputs
#define CLKEN 13
#define DT 12
#define SW 11
#define CLK1 5
#define DIO1 6
#define CLK2 8
#define DIO2 9
TM1637Display display1 = TM1637Display(CLK1, DIO1);
TM1637Display display2 = TM1637Display(CLK2, DIO2);
int gameState = 0;
int counter = 0;
int displayTime = 0;
int displayTime1 = 0;
int counter1 = 0;
int formatTime = 0;
int formatTime1 = 0;
int gameTime = 0;
int gameTime1 = 0;
int currentStateCLK;
int lastStateCLK;



int buttonPin = 2;
int button2Pin = 4;
int buzzerPin = 10;
int buttonState = 0;
int button2State = 0;



String currentDir = "";
unsigned long lastButtonPress = 0;

unsigned long lastButtonPress1 = 0;

unsigned long lastButtonPress2 = 0;


bool timer1Active = false;
bool timer2Active = false;




void setup() {

  // Set encoder pins as inputs
  pinMode(CLKEN, INPUT);
  pinMode(DT, INPUT);
  pinMode(SW, INPUT_PULLUP);

  // Setup Serial Monitor
  Serial.begin(9600);

  // Read the initial state of CLK
  lastStateCLK = digitalRead(CLKEN);

  display1.setBrightness(7);
  display2.setBrightness(7);
  display1.clear();
  display2.clear();
  pinMode(buttonPin, INPUT);
  pinMode(button2Pin, INPUT);
  pinMode(buzzerPin, OUTPUT);

}

void loop() {

  counter = constrain(counter, 0, 5999);
  counter1 = constrain(counter1, 0, 5999);
  formatTime = constrain(formatTime, 0, 5999);
  formatTime1 = constrain(formatTime1, 0, 5999);
  buttonState = digitalRead(buttonPin);
  button2State = digitalRead(button2Pin);
  // Read the current state of CLK
  currentStateCLK = digitalRead(CLKEN);
  if (gameState == 0) { 
  // If last and current state of CLK are different, then pulse occurred
  // React to only 1 state change to avoid double count
    if (currentStateCLK != lastStateCLK && currentStateCLK == 1) {

    // If the DT state is different than the CLK state then
    // the encoder is rotating CCW so decrement
      if (digitalRead(DT) != currentStateCLK) {
        counter = counter+30;
        counter1 = counter1+30;
        currentDir = "CCW";
      } else {
      // Encoder is rotating CW so increment
        counter = counter-30;
        counter1 = counter1-30;
        currentDir = "CW";
    }

      Serial.print("Direction: ");
      Serial.print(currentDir);
      Serial.print(" | Counter: ");
      Serial.println(counter);
      gameTime = counter;
      gameTime1 = counter1;
      int minutes = gameTime / 60;
      int seconds = gameTime % 60;
      formatTime = (minutes * 100) + seconds;
      int minutes1 = gameTime1 / 60;
      int seconds1 = gameTime1 % 60;
      formatTime1 = (minutes1 * 100) + seconds1;
      display1.showNumberDecEx(formatTime, 0b11100000, false, 4, 0);
      display2.showNumberDecEx(formatTime1, 0b11100000, false, 4, 0);
      } }

  if (gameState == 1) {
    if (buttonState == HIGH && millis() - lastButtonPress1 > 200) {
    timer1Active = true;
    timer2Active = false;
    lastButtonPress1 = millis();
    Serial.println("Timer 1 started");
  }

  // Check button 2 (start Timer 2)
  if (button2State == HIGH && millis() - lastButtonPress2 > 200) {
    timer2Active = true;
    timer1Active = false;
    lastButtonPress2 = millis();
    Serial.println("Timer 2 started");
  }

  // Timer 1 countdown
  if (timer1Active && gameTime > 0) {
    gameTime--;
    int minutes = gameTime / 60;
    int seconds = gameTime % 60;
    displayTime = (minutes * 100) + seconds;
    display1.showNumberDecEx(displayTime, 0b11100000, false, 4, 0);
    delay(1000);  // Simple blocking delay for countdown
  }

  // Timer 2 countdown
  else if (timer2Active && gameTime1 > 0) {
    gameTime1--;
    int minutes = gameTime1 / 60;
    int seconds = gameTime1 % 60;
    displayTime1 = (minutes * 100) + seconds;
    display2.showNumberDecEx(displayTime1, 0b11100000, false, 4, 0);
    delay(1000);  // Simple blocking delay for countdown
  }

  if ((timer1Active && gameTime == 0) || (timer2Active && gameTime1 == 0)) {
  gameState = 2;
  }

  if (gameState == 2) { 
    tone(buzzerPin, 784);
    delay(5000);
    noTone(buzzerPin);

  }
}

  // Remember last CLK state
  lastStateCLK = currentStateCLK;

  // Read the button state
  int btnState = digitalRead(SW);

  //If we detect LOW signal, button is pressed
  if (btnState == LOW) {
    //if 50ms have passed since last LOW pulse, it means that the
    //button has been pressed, released and pressed again
    if (millis() - lastButtonPress > 50) {
      Serial.println("Button pressed!");
      gameState = gameState + 1;
    }

    // Remember last button press event
    lastButtonPress = millis();
  }

  // Put in a slight delay to help debounce the reading
  delay(1);
}

First Milestone

First Milestone Code

This code doesn’t have full functionality. Use one of the Milestones above for working code.

#include <Arduino.h>
#include <TM1637Display.h>


#define CLK1 5
#define DIO1 6
#define CLK2 8
#define DIO2 9
#define outputA 13
#define outputB 12
TM1637Display display1 (CLK1, DIO1);
TM1637Display display2 (CLK2, DIO2);
int counter1 = 0;
int counter2 = 0;
int counter3 = 0;
int counter4 = 0;

int aState;
int aLastState;  

int buzzerPin = 10;
int buttonPin = 2;
int button2Pin = 4;
int robuttonPin=11;

int buttonState = 0;
int button2State = 0;

int buttonPresses = 0;
int robuttonState = 0;
int setTime = 1;




void setup() {
  // put your setup code here, to run once:
  display1.setBrightness(5);
  display2.setBrightness(5);
  display1.clear();
  display2.clear();
  
  pinMode(buzzerPin, OUTPUT);
  pinMode(buttonPin, INPUT);
  pinMode(button2Pin, INPUT);
  pinMode(robuttonPin, INPUT);
  pinMode (outputA,INPUT);
  pinMode (outputB,INPUT);
  aLastState = digitalRead(outputA); 
  Serial.begin (9600); 


}

void loop() {
  counter1 = constrain(counter1, 0, 9);
  counter2 = constrain(counter2, 0, 9);
  counter3 = constrain(counter3, 0, 5);
  counter4 = constrain(counter4, 0, 9);
  robuttonState = digitalRead(robuttonPin);
  if (robuttonState == LOW) {
    setTime++; 
    delay(300); 
  }

  aState = digitalRead(outputA);

  if (setTime == 1) {
    Serial.println("setTime1");
    if (aState != aLastState){     
      if (digitalRead(outputB) != aState) { 
        counter1 ++;
      } else {
        counter1 --;
      }
      display1.showNumberDec(counter1,false,1,0);
      delay(500);
    } 
    aLastState = aState;
  }

  if (setTime == 2) {
    Serial.println("setTime2");
    if (aState != aLastState){     
      if (digitalRead(outputB) != aState) { 
        counter2++;
      } else {
        counter2--;
      }
      display1.showNumberDec(counter2 % 10, false, 1, 1);  
      delay(50); 
    }
    aLastState = aState;
  }

  if (setTime == 3) {
    Serial.println("setTime3");
    if (aState != aLastState){     
      if (digitalRead(outputB) != aState) { 
        counter3++;
      } else {
        counter3--;
      }
      display1.showNumberDec(counter3 % 10, false, 1, 2); 
      delay(50); 
    }
    aLastState = aState;
  }

  if (setTime == 4) {
    Serial.println("setTime4");
    if (aState != aLastState){     
      if (digitalRead(outputB) != aState) { 
        counter4++;
      } else {
        counter4--;
      }
      display1.showNumberDec(counter4 % 10, false, 1, 3);  
      delay(50); 
    }
    aLastState = aState;

  }  
  
}

Ino Download: Link

Schematics

Here are my Schematics: Schematics

Fritzing download: Link

Modifications

One of the modifications that I did was making a case for my chess clock. This is my first attempt, so it may not fit perfectly.

CAD Files: Link

STL Files: Link

Bill of Materials

Part Note Price Link
Elegoo Starter Kit Arduino Uno R3, Passive Buzzer, Breadboard, Jumperwires, etc. $44.99 Link
Rotary Encoder Changing the time on the Matrix Displays. $7.99 Link
4 Digit 7 Segment LED Displays Used for displaying the time $7.99 Link
USB C to USB A Adapter Used for connecting the USB A to USB B cable if you only have USB C $2.99 Link
9V battery holder with switch & 5.5mm/2.1mm plug Used to provide external power $3.95 Link
Tactile Buttons An upgrade over the buttons included in the Starter Kit $5.99 Link