Toby Townsend
Created March 8, 2023 © CC BY-NC-SA

My overengineered score counter

This Arduino-based score counter keeps track of the score and remembers who's turn it is. This was my college EPQ project.

My overengineered score counter

Things used in this project

Hardware components

JLCPCB Customized PCB
JLCPCB Customized PCB
×1
5mm LED - RED
×23
5mm LED - BLUE
×22
5mm LED - YELLOW
×1
5mm LED - GREEN
×1
Texas Instruments TLC 5955 LED Driver
×1
Arduino Nano Every
Arduino Nano Every
×1
DPDT Slide Switch
×1
9V battery (generic)
9V battery (generic)
×1
M3 Hex Nut
×8
M3 x 10mm Socket Head Bolt
×8
9V Battery Clip
9V Battery Clip
×1
Piezo Buzzer
×1
SMT 0603 Resistor (1kΩ)
×14
SMT 0603 Resistor (1MΩ)
×2
Tactile Switch, Top Actuated
Tactile Switch, Top Actuated
×7
LDR, 5 Mohm
LDR, 5 Mohm
×1
SMT 0603 Capacitor (1uF)
×1

Software apps and online services

Arduino IDE
Arduino IDE
Fusion
Autodesk Fusion
Prusa Slicer

Hand tools and fabrication machines

Prusa MINI 3D Printer
Soldering iron (generic)
Soldering iron (generic)
Solder Wire, Lead Free
Solder Wire, Lead Free
Solder Flux, Soldering
Solder Flux, Soldering
Helping Hand Tool

Story

Read more

Custom parts and enclosures

V1 Layout

V2 Layout

V2-A Case

V2-B Case

Schematics

V2.1 Schematic

V2 Schematic

V1 Schematic

V1.5 Schematic

Code

V1 Code

C/C++
/*
Score Counter 1.0
Written by Toby Townsend (T.O.T) for use with Score Counter 1.0

KNOWN ISSUES
* Adds / Takes points too quickly

Updated: ??/??/2022
*/
const int BLUE_ADD = 11;
const int BLUE_TAKE = 12;
const int RED_ADD = 9;
const int RED_TAKE = 10;
const int RESET = 13;
const int MODE_SELECT = 14;

const int BATT = A0;
float BATT_V;
int BATT_Level;
const int LOW_BATT_LED = 3;

int redScore;
int blueScore;
int player;
int mode;

int InputDelay = 200;
int maxScore = 15;
int winFlash = 15; // number of flashes for winning team
int winDelay = 400; // speed of win flashes
int cycleCount;

const int PLAYER_LEDS[4] = {7, 4, 6, 5};                                                // player LEDs in order
const int BLUE_PINS[21] = {24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52}; // blue LEDs in order
const int RED_PINS[21] = {25, 27, 29, 31, 33, 35, 37, 39, 41, 43, 45, 47, 49, 51, 53};  // red LEDs in order
const int Mode_LED[3] = {54,55,56}; // RBG LED pins 

const int Mode_RGB[6][3] = {{255,0,0},{0,255,0},{0,0,255},{255,0,0},{255,0,0},{255,0,0}};

void inputCheck(){
  // Blue Add
  if (digitalRead(BLUE_ADD) == true && blueScore < maxScore) {
    ++blueScore;
    playerSelect(0);
    delay(InputDelay);
  }
  // Blue Take
  if (digitalRead(BLUE_TAKE) == true && blueScore > 0) {
    --blueScore;
    playerSelect(1);
    delay(InputDelay);
  }
  // Red Add
  if (digitalRead(RED_ADD) == true && redScore < maxScore) {
    ++redScore;
    playerSelect(0);
    delay(InputDelay);
  }
  // Red Take
  if (digitalRead(RED_TAKE) == true && redScore > 0) {
    --redScore;
    playerSelect(1);
    delay(InputDelay);
  }
  // Reset
  if (digitalRead(RESET) == true) {
    gameEnd(0);
    delay(InputDelay);
  }
  // Red Win
  if (redScore >= maxScore) {
    gameEnd(1);
  }
  // Red Win
  if (blueScore >= maxScore) {
    gameEnd(2);
  }
  // Mode Select
  if (digitalRead(MODE_SELECT) == true && mode < 5) {
    ++mode;
    delay(InputDelay);
  }
  if (digitalRead(MODE_SELECT) == true && mode == 5) {
    mode = 0;
    delay(InputDelay);
  }

 }
void playerSelect(int i) {
 if(i == 0){ // Next
  player = player < 4 ? player + 1 : 1; // if player < 4, add 1 to player, else set player to 1
 }
 if(i == 1){ // Prev
  player = player > 1 ? player - 1 : 4; // if player > 1, sub 1 from player, else set player to 4
 }
 if(i == 2){ // Rand
  int tempPlayer = player;
  player = rand() % 4 + 1;
  if (player == tempPlayer){ // Reruns random select if the new player is the same as the previous
    playerSelect(2);
  }
 }
 }
void outputCheck() {
  // Blue
  for (int i = 0; i < blueScore; i++) { // iterates over blue LEDs up to and including blue score and sets them to HIGH
    digitalWrite(BLUE_PINS[i], HIGH);
  }
  for (int i = blueScore; i < 21; i++) { // iterates over blue LEDs after blue score and sets them to LOW
    digitalWrite(BLUE_PINS[i], LOW);
  }
  // Red
  for (int i = 0; i < redScore; i++) { // iterates over red LEDs up to and including red score and sets them to HIGH
    digitalWrite(RED_PINS[i], HIGH);
  }
  for (int i = redScore; i < 21; i++) { // iterates over red LEDs after red score and sets them to LOW
    digitalWrite(RED_PINS[i], LOW);
  }
  // Player
  for (int i = 0; i < 4; i++) { // iterates over player LEDs and sets them all to LOW
    digitalWrite(PLAYER_LEDS[i], LOW);
  }
  digitalWrite(PLAYER_LEDS[player - 1], HIGH); // sets current player LED to HIGH
  // Mode
  digitalWrite(Mode_LED[1], Mode_RGB[mode][0]);// sets Red
  digitalWrite(Mode_LED[2], Mode_RGB[mode][1]);// sets Green
  digitalWrite(Mode_LED[3], Mode_RGB[mode][2]);// sets Blue
  }
void gameEnd(int i){
  if (i == 0){ // Manual Reset
  blueScore = 0;
  redScore = 0;
  }
  if(i ==1){ // Red Win
 for (int c = 0; c < winFlash; c++){ 
  redScore = 0; 
  outputCheck();
  delay(winDelay);
  redScore = maxScore; 
  outputCheck();
  delay(winDelay);
  }}
 if(i ==2){ // Blue Win
 for (int c = 0; c < winFlash; c++){ 
  blueScore = 0; 
  outputCheck();
  delay(winDelay);
  blueScore = maxScore; 
  outputCheck();
  delay(winDelay);
  }}

  playerSelect(2); // select random player for next game
  blueScore = 0;
  redScore = 0;
  }
void status(){
  ++cycleCount;
  if (cycleCount == 2000) {
    cycleCount = 0;
    powerCheck();
    Serial.print("Red Score = "); Serial.println(redScore);
    Serial.print("Blue Score = "); Serial.println(blueScore);
    Serial.print("Next to Serve: "); Serial.println(player);
    Serial.print("Mode: "); Serial.println(mode);
    Serial.print("Battery: "); Serial.print(BATT_Level); Serial.println("%");
    Serial.println();
  }
 }
void powerCheck(){
  BATT_V = analogRead(BATT)/ 102.8;
  if (BATT_V < 8){
    BATT_Level = 0;
  }
  else {
    BATT_Level = (BATT_V - 8.5)*100;
  }
  if (BATT_Level > 50){
    digitalWrite(LOW_BATT_LED, LOW);
  }
  else{
    digitalWrite(LOW_BATT_LED, HIGH);
  }
 }
void setup() {
  Serial.begin(9600);

  for (int i = 0; i < 15; i++) { // iterates over blue LEDs and sets their modes to OUTPUT
    pinMode(BLUE_PINS[i], OUTPUT);
  }
  for (int i = 0; i < 15; i++) { // iterates over red LEDs and sets their modes to OUTPUT
    pinMode(RED_PINS[i], OUTPUT);
  }
  for (int i = 0; i < 4; i++) { // iterates over player LEDs and sets their modes to OUTPUT
    pinMode(PLAYER_LEDS[i], OUTPUT);
  }
  for (int i = 0; i < 3; i++) { // iterates over mode LEDs and sets their modes to OUTPUT
    pinMode(Mode_LED[i], OUTPUT);
  }

  pinMode(BLUE_ADD, INPUT);
  pinMode(BLUE_TAKE, INPUT);
  pinMode(RED_ADD, INPUT);
  pinMode(RED_TAKE, INPUT);
  pinMode(RESET, INPUT);

  pinMode(BATT, INPUT);
  pinMode(LOW_BATT_LED, OUTPUT);

  blueScore = 21;
  redScore = 21;
  outputCheck();
  
  for (int i = 0; i < 4; i++) {
    digitalWrite(PLAYER_LEDS[i], HIGH);
  }
  delay(500);
  gameEnd(0);
 }
void loop() {
  inputCheck();
  outputCheck();
  status();
  
 }

V2 Code

C/C++
/*
Score Counter 2.0
Written by Toby Townsend (T.O.T) for use with Score Counter 2.0

KNOWN ISSUES
* Sanity issue when changing to 2-player gamemode while on player 2 or 3 as these players should be disabled
* mode LED disabled
* Low battery indicator is inaccurate
* temp player setting display feature is broken after auto brightness setting is used (remains on) **FIXED (caused by the dynBrightness delays)**
* PCB power is inefficient due to 1k ohm resistors used for input pot div, causing a power loss, will replace with 4.7k as advised

Updated: 02/01/2023
*/

#include <TLC5955.h>
#include <SPI.h>

// User Input Pins
const int BLUE_ADD = A3;
const int BLUE_TAKE = A2;
const int RED_ADD = A4;
const int RED_TAKE = A5;
const int RESET = A1;
const int MODE_SELECT = A6;
const int LDR_EN = 8;
const int SOUND_EN = 7;

// Passive Input Pins
const int LDR = A0;
const int BATT = A7;

// Output Pins
const int BUZZER = 6;
const int Mode_LED[3] = {5,9,10}; // RGB LED pins 

// TLC5955 Setup
#define GSCLK 3
#define LAT 4
#define SPI_MOSI 11
#define SPI_CLK 13

// TLC Setup
const uint8_t TLC5955::chip_count = 1;          // Change to reflect number of TLC chips
float TLC5955::max_current_amps = 10;      // Maximum current output, amps
bool TLC5955::enforce_max_current = false;   // Whether to enforce max current limit
uint8_t TLC5955::_dc_data[TLC5955::chip_count][TLC5955::LEDS_PER_CHIP][TLC5955::COLOR_CHANNEL_COUNT];
uint8_t TLC5955::_rgb_order[TLC5955::chip_count][TLC5955::LEDS_PER_CHIP][TLC5955::COLOR_CHANNEL_COUNT];
uint16_t TLC5955::_grayscale_data[TLC5955::chip_count][TLC5955::LEDS_PER_CHIP][TLC5955::COLOR_CHANNEL_COUNT];
TLC5955 tlc;

// LED Addresses
const int LED_LOCATION[3][21] = {{35,23,34,33,21},{46,45,47,31,30,32,43,42,44,40,39,41,28,27,29,37,36,38,25,24,26},{10,9,11,19,18,20,7,6,8,4,3,5,16,15,17,1,0,2,13,12,14}}; // Other (RBGY & BATT), Red, Blue

// RGB Colour Values
const int Mode_RGB[8][3] = {{100,0,0},{0,100,0},{0,0,100},{100,100,0},{0,100,100},{100,100,100},{0,0,0},{255,255,255}};

// Buzzer Melody Values
const int melody[8][2] = {{500,50},{800,50},{200,50},{1000,500},{1500,50},{200,50},{600,100},{800,500}}; // {tune, duration} // R+, B+, R/B-, Reset, Win, Low Batt, Mode Change / LDR MANUAL, LDR AUTO

// Gamemode Rules
const int GMrules[8][2] = {{4,21},{2,21},{4,15},{2,15},{4,10},{2,10},{4,5},{2,5}}; // {Player Count, Max Score} // 4x21, 2x21, 4x15, 2x15, 4x10, 2x10, 4x5, 2x5

// Useful Variables
int inputDelay = 300; // Delay for input detection (ms)
int maxScore = 21; // Default max score 
int gamemodeCount = 8; // Number of different gamemodes
int playerCount = 4; // Default player count
int player = 1; // Default Player
int winFlash = 20; // Number of flashes for winning team
int winDelay = 10; // Speed of win 
int devMode = 1; // 0 = off, 1 = sound control while on, 2 = 1 + status reports
int brightnessMode = 1; // Default brightness mode
int playerDisplayDelay = 2000; // time in ms that the player count setting will be displayed after a gamemode change

// System Variables
int redScore;
int blueScore;
int mode;
int cycleCount;
int delayCycle = inputDelay + 1;
float battVoltage;
int battLevel;
bool lowBatt;
int lightLevel;
int brightness = 1;
bool sound;
bool modeChangeFlag;

void LED_Update(int Group, int Number, int Value){ // simplifies LED driver interface
 int TARG_LED = LED_LOCATION[Group][Number];
  
  tlc.set_single_channel(TARG_LED, Value);
  tlc.update();
 }
void mode_LED_Update(){ // Mode LED refresh
  analogWrite(Mode_LED[0],(Mode_RGB[mode][0]/100)*(brightness/20)+1); // sets Red
  analogWrite(Mode_LED[1],(Mode_RGB[mode][1]/100)*(brightness/20)+1); // sets Green
  analogWrite(Mode_LED[2],(Mode_RGB[mode][2]/100)*(brightness/20)+1); // sets Blue
 }
void displayUpdate(int displayMode) { // updates the LEDs to display the score (displayMode 0 = normal 1 = temp hold)
  // Player
  if(displayMode == 0){
  for (int i = 0; i < 4; i++) { // iterates over player LEDs and sets them all to LOW
    LED_Update(0, i, 0);
  }
    LED_Update(0, player - 1 , 1); // sets current player LED to HIGH
  }
  if(displayMode == 1){
    for (int i = 0; i < 4; i++) { // iterates over player LEDs and sets them all to LOW
    LED_Update(0, i, 0);
    }
      for (int i = 0; i < playerCount; i++) {
    LED_Update(0, i, 1);
   }
  }
  // Blue
  for (int i = 0; i < blueScore; i++) { // iterates over blue LEDs up to and including blue score and sets them to HIGH
    LED_Update(2, i, 1);
  }
  for (int i = blueScore; i < 21; i++) { // iterates over blue LEDs after blue score and sets them to LOW
    LED_Update(2, i, 0);
  }
  // Red
  for (int i = 0; i < redScore; i++) { // iterates over red LEDs up to and including red score and sets them to HIGH
    LED_Update(1, i, 1);
  }
  for (int i = redScore; i < 21; i++) { // iterates over red LEDs after red score and sets them to LOW
    LED_Update(1, i, 0);
  }

  // Max Score
  for (int i = maxScore; i < 21; i++) {
    LED_Update(1, i, 5);
    LED_Update(2, i, 5);
  }
  
  blink();
  }
void buzzer(int ID){ // buzzer control interface
    if(sound){
    tone(BUZZER, melody[ID][0], melody[ID][1]);
    delay(melody[ID][1]);
    }}
void inputCheck(){ // manages input logic
  // Delay Cycle
  if(delayCycle <= inputDelay){
    delayCycle++;
    }
  // User Inputs //
  // Blue Add
  if (digitalRead(BLUE_ADD) == true && blueScore < maxScore && delayCycle > inputDelay) {
    ++blueScore;
    playerSelect(0);
    delayCycle = 0;
    displayUpdate(0);
    buzzer(1);
  }
  // Blue Take
  if (digitalRead(BLUE_TAKE) == true && blueScore > 0 && delayCycle > inputDelay) {
    --blueScore;
    playerSelect(1);
    delayCycle = 0;
    displayUpdate(0);
    buzzer(2);
  }
  // Red Add
  if (digitalRead(RED_ADD) == true && redScore < maxScore && delayCycle > inputDelay) {
    ++redScore;
    playerSelect(0);
    delayCycle = 0;
    displayUpdate(0);
    buzzer(0);
  }
  // Red Take
  if (digitalRead(RED_TAKE) == true && redScore > 0 && delayCycle > inputDelay) {
    --redScore;
    playerSelect(1);
    delayCycle = 0;
    displayUpdate(0);
    buzzer(2);
  }
  // Reset
  if (digitalRead(RESET) == true && delayCycle > inputDelay) {
    gameEnd(0);
    delayCycle = 0;
    displayUpdate(0);
    buzzer(3);
  }
  // Mode Select
  if (digitalRead(MODE_SELECT) == true && mode < gamemodeCount-1 && delayCycle > inputDelay) {
    ++mode;
    delayCycle = 0;
    gameMode(1);
    buzzer(6);
    modeChangeFlag = true;
  }
  if (digitalRead(MODE_SELECT) == true && mode == gamemodeCount-1 && delayCycle > inputDelay) {
    mode = 0;
    delayCycle = 0;
    gameMode(1);
    buzzer(6);
    modeChangeFlag = true;
  }
  if (modeChangeFlag == true){
    delay(2000);
    playerSelect(3);
    displayUpdate(0);
    modeChangeFlag = false;
  }
  // LDR Mode
  if (digitalRead(LDR_EN) == true && brightnessMode < 3 && delayCycle > inputDelay) {
    ++brightnessMode;
    delayCycle = 0;
    buzzer(6);
    blink();
  }
  if (digitalRead(LDR_EN) == true && brightnessMode == 3 && delayCycle > inputDelay) {
    brightnessMode = 0;
    delayCycle = 0;
    buzzer(7);
    blink();
  }
  // System Inputs //
  // Red Win
  if (redScore >= maxScore) {
    gameEnd(1);
  }
  // Blue Win
  if (blueScore >= maxScore) {
    gameEnd(2);
  }
 }
void playerSelect(int operation) { // controls the player logic
 if(operation == 0){ // Next
  player = player < playerCount ? player + 1 : 1; // if player < playerCount, add 1 to player, else set player to 1
 }
 if(operation == 1){ // Prev
  player = player > 1 ? player - 1 : playerCount; // if player > 1, sub 1 from player, else set player to playerCount
 }
 if(operation == 2){ // Rand
  int tempPlayer = player;
  player = rand() % playerCount + 1;
  if (player == tempPlayer){ // Reruns random select if the new player is the same as the previous
    playerSelect(2);
  }
 }
 if(operation == 3 && ((mode == 1 || mode == 3 || mode == 5 || mode == 7) && (player ==2 ))){ // Sanity Check
    playerSelect(2);
    displayUpdate(0);
 }
 }

void gameEnd(int endmode){ // manages the end of game conditions
  if (endmode == 0 && delayCycle > inputDelay){ // Manual Reset
  blueScore = 0;
  redScore = 0;
  displayUpdate(0);
  delayCycle = 0;
  }
  if(endmode == 1){ // Red Win
 for (int c = 0; c < winFlash; c++){ 
  redScore = 0; 
  displayUpdate(0);
  delay(winDelay);
  redScore = maxScore; 
  displayUpdate(0);
  delay(winDelay);
  buzzer(4);
  }}
 if(endmode == 2){ // Blue Win
 for (int c = 0; c < winFlash; c++){ 
  blueScore = 0; 
  displayUpdate(0);
  delay(winDelay);
  blueScore = maxScore; 
  displayUpdate(0);
  delay(winDelay);
  buzzer(4);
  }}
  playerSelect(2); // select random player for next game
  blueScore = 0;
  redScore = 0;
  }
void status(int period){ // outputs a status report, for dev purposes
  ++cycleCount;
  if (cycleCount == period) {
    cycleCount = 0;
    Serial.print("Red Score = "); Serial.println(redScore);
    Serial.print("Blue Score = "); Serial.println(blueScore);
    Serial.print("Next to Serve: "); Serial.println(player);
    Serial.print("Mode: "); Serial.println(mode);
    Serial.print("Battery: "); Serial.print(battLevel); Serial.println("%");
    Serial.print("Battery Voltage: "); Serial.println(battVoltage);
    Serial.print("Brightness: "); Serial.println(brightness);
    Serial.print("LDR: "); Serial.println(analogRead(LDR));
    Serial.print("brightnessMode: "); Serial.println(brightnessMode);
    Serial.println(delayCycle);
  }
 }
void powerCheck(){ // checks battery voltage and produces an estimated percentage, controls lowBatt LED
  battVoltage = analogRead(BATT)/ 102.8;
  if (battVoltage < 8){
    battLevel = 0;
  }
  else {
    battLevel = (battVoltage / 9.5) * 100;
  }

  if (battLevel >= 20){
    LED_Update(0, 4, 0);
    lowBatt = false;
  }
  else if(battLevel < 20){
    LED_Update(0, 4, 1);
    lowBatt = true;
  }
  /*if(!lowBatt){
    for (int c = 0; c < 5; c++){ 
    buzzer(5);
  }}*/
 }
void dynBrightness(){ // manages LED brightness
  int temp = brightness;
  
  if(brightnessMode == 0){
    lightLevel = analogRead(LDR);
    brightness = (lightLevel/4)+1;
  }
  if(brightnessMode == 1){
    brightness = 1;
  }
  if(brightnessMode == 2){
    brightness = 150;
  }
  if(brightnessMode == 3){
    brightness = 255;
  }

  //mode_LED_Update();

  if(temp != brightness){
    tlc.set_brightness_current(brightness, brightness, brightness);
    tlc.update_control();
  }
 }
void gameMode(int playerDisplayHold){ // controls gamemode rules
  playerCount = GMrules[mode][0];
  maxScore = GMrules[mode][1];
  displayUpdate(playerDisplayHold);
 }
void blink(){ // this function houses all the functions that cause the LED's to blink, causing them to all 'blink' at the same time. Therefore reducing overall blinks
  dynBrightness();
  if(devMode){
  sound = digitalRead(SOUND_EN);
  }
  if(devMode == 2){
  status(1);
  sound = digitalRead(SOUND_EN);
  }
 }
void setup() { // initializes the system
  Serial.begin(9600);
 // TLC Setup
  pinMode(GSCLK, OUTPUT);
  pinMode(LAT, OUTPUT);
  analogWrite(GSCLK, 127); // Adjust PWM timer for maximum GSCLK frequency
  SPI.begin();
  tlc.init(LAT, SPI_MOSI, SPI_CLK, GSCLK);
  tlc.set_all_dc_data(1); // We must set dot correction values, so set them all to the brightest adjustment
  tlc.set_max_current(1, 1, 1); // Set Max Current Values (see TLC5955 datasheet) up to 7
  tlc.set_function_data(true, true, true, true, true); // Set Function Control Data Latch values.Order: DSPRPT, TMGRST, RFRESH, ESPWM, LSDVLT
  tlc.set_brightness_current(brightness, brightness, brightness);
  tlc.update_control();
  tlc.set_rgb_pin_order(0, 1, 2);

 // Input Setup
  pinMode(BLUE_ADD, INPUT);
  pinMode(BLUE_TAKE, INPUT);
  pinMode(RED_ADD, INPUT);
  pinMode(RED_TAKE, INPUT);
  pinMode(RESET, INPUT);
  pinMode(BATT, INPUT);
  pinMode(SOUND_EN, INPUT);

  pinMode(Mode_LED[0], OUTPUT);
  pinMode(Mode_LED[1], OUTPUT);
  pinMode(Mode_LED[2], OUTPUT);

  sound = digitalRead(SOUND_EN);
  if(sound){
  for(int i; i < 8; i++){
    buzzer(i);
    delay(100);
  }}
  displayUpdate(0);
 }
void loop() { // system loop
  inputCheck();
  powerCheck();
 }

Credits

Toby Townsend
1 project • 0 followers

Comments