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 |
Final Milestone
- I was able to make the case for my chess clock in CAD.
- My biggest challenges at Bluestamp Engineering were my code and the CAD. However, I was able to triumph over them by breaking them down into smaller tasks. I coded each part separately before integrating them all into my code. This reduced the amount of errors in my code and made it easier to problem solve. For my CAD, I worked on each part separately before putting them all into an assembly so that I would be sure that they all worked together.
- I learned about circuits, C++, and Arduinos during my time at Bluestamp.
- I hope to be able to make a project using an Arduino by myself, using the knowledge that I have gained from Bluestamp.
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:
- I was able to rewrite my chess clock’s setup, game state, and end state code so that it works properly.
- I have been surprised by how quickly I can learn new skills. Once I got the hang of something, I was able to work on it quickly.
- I was able to overcome the difficulty of getting my code to work by working on each component separately. This allowed me to identify the exact problems in my code, rather than receiving a multitude of errors at once. Before my final milestone, I need to complete the CAD design for my chess clock’s housing. I also need to finish wiring up the tactile buttons.
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
- My project needs an Arduino Uno, 2 7-segment displays, a buzzer, and a rotary encoder. They will be able to integrate to set the time on the display using the rotary encoder and the Arduino. Then, the Arduino can count the time down. Finally, the buzzer will go off once the time reaches zero.
- So far, I have been able to set the time on the first display. I have also been able to get each component of my clock working independently.
- One challenge I’ve been facing is that the rotary encoder is not able to set values past 2.
- My plan to complete my project is to rewrite my time-setting code, as I know both parts are working. From there, I plan to work on the code for the game state and the ending state. Once I have both of those done, I hope to be able to make a case for my chess clock using CAD.
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:
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 |