Intro
originally published on wordpress.com
This will be a quick explanation of how I built the ping pong scoreboard.
Parts
- 1 x 32x8 Led Matrix Display (5mm) - Red - Link (no longer available)
- 1 x Arduino Nano (I used a cheap imitator from eBay)
- 2 x Arcade push-buttons (momentary on type) - Something like this, although I found mine on eBay
- Mounting method for the push-buttons under the table
- DPST switch (for mode selection)
- 5V Power supply, fed directly into the matrix power input, the Arduino can get the power from here too, but dont run the matrix from the regulator on the Arduino. These Displays can draw a lot of current.
- Other miscellaneous wires, strip board, etc.
Method
- Connect the centre pin of the mode switch to a digital read pin on the Arduino, and GND and +5V to the other two.
- Connect the 3 pins for the matrix to the arduino:
- pinCS1
- pinWR
- pinDATA
- Connect the pushbuttons to the 5V and two digital input pins, with a pull-down resistor to GND on each.
- I added a speaker to give audio feedback when the buttons are pressed.
- Thats about it!
Demo Video
Quick YouTube video demo.
Pictures
Captions explain the pictures.
Code
Notes on the code:
- I believe there might be a small error when the game is run in "to 11" mode, when the game reaches deuce. If anyone finds it let me know.
- It certainly isn't efficient, optimized or anything like that but I think I have commented it enough for people to understand what is happening.
#include <HT1632.h>
#define IMG_ARROW_WIDTH 4
#define IMG_ARROW_HEIGHT 8
char IMG_ARROW_DR [] = {0b0100, 0b0000, 0b1000, 0b0010, 0b0000, 0b0011, 0b1000, 0b0011};
char IMG_ARROW_DL [] = {0b1000, 0b0011, 0b0000, 0b0011, 0b1000, 0b0010, 0b0100, 0b0000};
char IMG_ARROW_UR [] = {0b0000, 0b0010, 0b0100, 0b0001, 0b1100, 0b0000, 0b1100, 0b0001};
char IMG_ARROW_UL [] = {0b1100, 0b0001, 0b1100, 0b0000, 0b0100, 0b0001, 0b0000, 0b0010};
#define DIGIT_WIDTH 6
#define DIGIT_HEIGHT 8
char DIGIT_0 [] = {0b1110, 0b0111, 0b1111, 0b1111, 0b0001, 0b1000, 0b0001, 0b1000, 0b1111, 0b1111, 0b1110, 0b0111, 0b0000, 0b0000, 0b0000, 0b0000};
char DIGIT_1 [] = {0b0000, 0b0000, 0b0010, 0b0000, 0b0010, 0b0000, 0b1111, 0b1111, 0b1111, 0b1111, 0b0000, 0b0000, 0b0000, 0b0000, 0b0000, 0b0000};
char DIGIT_2 [] = {0b0010, 0b1100, 0b0011, 0b1110, 0b0001, 0b1011, 0b1001, 0b1001, 0b1111, 0b1000, 0b0110, 0b1000, 0b0000, 0b0000, 0b0000, 0b0000};
char DIGIT_3 [] = {0b0010, 0b0100, 0b0011, 0b1100, 0b1001, 0b1000, 0b1001, 0b1000, 0b1111, 0b1111, 0b0110, 0b0111, 0b0000, 0b0000, 0b0000, 0b0000};
char DIGIT_4 [] = {0b1000, 0b0011, 0b1100, 0b0011, 0b0010, 0b0010, 0b1111, 0b1111, 0b1111, 0b1111, 0b0000, 0b0010, 0b0000, 0b0000, 0b0000, 0b0000};
char DIGIT_5 [] = {0b1111, 0b0100, 0b1111, 0b1100, 0b1001, 0b1000, 0b1001, 0b1000, 0b1001, 0b1111, 0b0001, 0b0111, 0b0000, 0b0000, 0b0000, 0b0000};
char DIGIT_6 [] = {0b1000, 0b0111, 0b1110, 0b1111, 0b0011, 0b1001, 0b0001, 0b1001, 0b0001, 0b1111, 0b0000, 0b0110, 0b0000, 0b0000, 0b0000, 0b0000};
char DIGIT_7 [] = {0b0001, 0b0000, 0b0001, 0b1100, 0b0001, 0b1111, 0b1101, 0b0011, 0b1111, 0b0000, 0b0111, 0b0000, 0b0000, 0b0000, 0b0000, 0b0000};
char DIGIT_8 [] = {0b0110, 0b0111, 0b1111, 0b1111, 0b1001, 0b1000, 0b1001, 0b1000, 0b1111, 0b1111, 0b0110, 0b0111, 0b0000, 0b0000, 0b0000, 0b0000};
char DIGIT_9 [] = {0b0110, 0b0000, 0b1111, 0b1000, 0b1001, 0b1000, 0b1001, 0b1100, 0b1111, 0b0111, 0b1110, 0b0001, 0b0000, 0b0000, 0b0000, 0b0000};
#define TEXT_WIDTH 32
#define TEXT_HEIGHT 8
char TEXT_Rally [] = {0b1111, 0b1111, 0b1111, 0b1111, 0b0001, 0b0001, 0b0001, 0b0001, 0b0001, 0b0001, 0b1111, 0b1111, 0b1110, 0b1110, 0b0000, 0b0000, 0b0000, 0b1111, 0b1100, 0b1111, 0b1111, 0b0010, 0b1111, 0b0010, 0b1100, 0b1111, 0b0000, 0b1111, 0b0000, 0b0000, 0b1111, 0b1111, 0b1111, 0b1111, 0b0000, 0b1000, 0b0000, 0b1000, 0b0000, 0b1000, 0b0000, 0b0000, 0b1111, 0b1111, 0b1111, 0b1111, 0b0000, 0b1000, 0b0011, 0b1000, 0b0111, 0b1000, 0b1100, 0b0000, 0b1000, 0b1111, 0b1000, 0b1111, 0b1100, 0b0000, 0b0111, 0b0000, 0b0011, 0b0000};
char TEXT_L_WIN [] = {0b0000, 0b0000, 0b0000, 0b0000, 0b0000, 0b0000, 0b1000, 0b0001, 0b1100, 0b0011, 0b1110, 0b0111, 0b0000, 0b0000, 0b0000, 0b0000, 0b1111, 0b0001, 0b1111, 0b0111, 0b0000, 0b1110, 0b0000, 0b1110, 0b1100, 0b0111, 0b1100, 0b0111, 0b0000, 0b1110, 0b0000, 0b1110, 0b1111, 0b0111, 0b1111, 0b0001, 0b0000, 0b0000, 0b1111, 0b1111, 0b1111, 0b1111, 0b0000, 0b0000, 0b1111, 0b1111, 0b1111, 0b1111, 0b1110, 0b0001, 0b1000, 0b0011, 0b0000, 0b1111, 0b1111, 0b1111, 0b1111, 0b1111, 0b0000, 0b0000, 0b0000, 0b0000, 0b0000, 0b0000};
char TEXT_R_WIN [] = {0b0000, 0b0000, 0b0000, 0b0000, 0b0000, 0b0000, 0b1111, 0b0001, 0b1111, 0b0111, 0b0000, 0b1110, 0b0000, 0b1110, 0b1100, 0b0111, 0b1100, 0b0111, 0b0000, 0b1110, 0b0000, 0b1110, 0b1111, 0b0111, 0b1111, 0b0001, 0b0000, 0b0000, 0b1111, 0b1111, 0b1111, 0b1111, 0b0000, 0b0000, 0b1111, 0b1111, 0b1111, 0b1111, 0b1110, 0b0001, 0b1000, 0b0011, 0b0000, 0b1111, 0b1111, 0b1111, 0b1111, 0b1111, 0b0000, 0b0000, 0b0000, 0b0000, 0b1110, 0b0111, 0b1100, 0b0011, 0b1000, 0b0001, 0b0000, 0b0000, 0b0000, 0b0000, 0b0000, 0b0000};
char TEXT_GAME_21 [] = {0b0000, 0b0000, 0b0000, 0b0000, 0b0001, 0b0000, 0b0001, 0b0000, 0b1111, 0b1111, 0b1111, 0b1111, 0b0001, 0b0000, 0b0001, 0b0000, 0b0000, 0b0000, 0b1110, 0b0111, 0b1111, 0b1111, 0b0001, 0b1000, 0b0001, 0b1000, 0b1111, 0b1111, 0b1110, 0b0111, 0b0000, 0b0000, 0b0000, 0b0000, 0b0000, 0b0000, 0b0010, 0b1100, 0b0011, 0b1110, 0b0001, 0b1011, 0b1001, 0b1001, 0b1111, 0b1000, 0b0110, 0b1000, 0b0000, 0b0000, 0b0000, 0b0000, 0b0010, 0b0000, 0b0010, 0b0000, 0b1111, 0b1111, 0b1111, 0b1111, 0b0000, 0b0000, 0b0000, 0b0000};
char TEXT_GAME_11 [] = {0b0000, 0b0000, 0b0000, 0b0000, 0b0000, 0b0000, 0b0001, 0b0000, 0b0001, 0b0000, 0b1111, 0b1111, 0b1111, 0b1111, 0b0001, 0b0000, 0b0001, 0b0000, 0b0000, 0b0000, 0b1110, 0b0111, 0b1111, 0b1111, 0b0001, 0b1000, 0b0001, 0b1000, 0b1111, 0b1111, 0b1110, 0b0111, 0b0000, 0b0000, 0b0000, 0b0000, 0b0000, 0b0000, 0b0010, 0b0000, 0b0010, 0b0000, 0b1111, 0b1111, 0b1111, 0b1111, 0b0000, 0b0000, 0b0000, 0b0000, 0b0010, 0b0000, 0b0010, 0b0000, 0b1111, 0b1111, 0b1111, 0b1111, 0b0000, 0b0000, 0b0000, 0b0000, 0b0000, 0b0000};
char TEXT_DEUCE [] = {0b1111, 0b1111, 0b1111, 0b1111, 0b0001, 0b1000, 0b0011, 0b1100, 0b1110, 0b0111, 0b1100, 0b0011, 0b0000, 0b0000, 0b1111, 0b1111, 0b1111, 0b1111, 0b1001, 0b1000, 0b1001, 0b1000, 0b0001, 0b1000, 0b0000, 0b0000, 0b1111, 0b0111, 0b1111, 0b1111, 0b0000, 0b1000, 0b0000, 0b1000, 0b1111, 0b1111, 0b1111, 0b0111, 0b0000, 0b0000, 0b1110, 0b0111, 0b1111, 0b1111, 0b0001, 0b1000, 0b0001, 0b1000, 0b0011, 0b1100, 0b0010, 0b0100, 0b0000, 0b0000, 0b1111, 0b1111, 0b1111, 0b1111, 0b1001, 0b1000, 0b1001, 0b1000, 0b0001, 0b1000};
char TEXT_R_ADV [] = {0b0000, 0b0000, 0b0000, 0b0000, 0b0000, 0b1111, 0b1100, 0b1111, 0b1111, 0b0010, 0b1111, 0b0010, 0b1100, 0b1111, 0b0000, 0b1111, 0b0000, 0b0000, 0b1111, 0b1111, 0b1111, 0b1111, 0b0001, 0b1000, 0b0011, 0b1100, 0b1110, 0b0111, 0b1100, 0b0011, 0b0000, 0b0000, 0b0011, 0b0000, 0b1111, 0b0001, 0b1100, 0b0111, 0b0000, 0b1110, 0b0000, 0b1110, 0b1100, 0b0111, 0b1111, 0b0001, 0b0011, 0b0000, 0b0000, 0b0000, 0b0000, 0b0000, 0b0000, 0b0000, 0b1110, 0b0111, 0b1100, 0b0011, 0b1000, 0b0001, 0b0000, 0b0000, 0b0000, 0b0000};
char TEXT_L_ADV [] = {0b0000, 0b0000, 0b0000, 0b0000, 0b1000, 0b0001, 0b1100, 0b0011, 0b1110, 0b0111, 0b0000, 0b0000, 0b0000, 0b0000, 0b0000, 0b0000, 0b0000, 0b1111, 0b1100, 0b1111, 0b1111, 0b0010, 0b1111, 0b0010, 0b1100, 0b1111, 0b0000, 0b1111, 0b0000, 0b0000, 0b1111, 0b1111, 0b1111, 0b1111, 0b0001, 0b1000, 0b0011, 0b1100, 0b1110, 0b0111, 0b1100, 0b0011, 0b0000, 0b0000, 0b0011, 0b0000, 0b1111, 0b0001, 0b1100, 0b0111, 0b0000, 0b1110, 0b0000, 0b1110, 0b1100, 0b0111, 0b1111, 0b0001, 0b0011, 0b0000, 0b0000, 0b0000, 0b0000, 0b0000};
int modeswitch = 4;
int speaker = 5;
volatile int buzz;
volatile int l_player;
volatile int r_player;
volatile boolean serve_end;
volatile boolean serve_side;
volatile boolean first_point;
volatile boolean reset;
volatile boolean delay_disp;
volatile int serves_pp;
volatile int target_score;
void setup () {
HT1632.begin(11, 10, 9);
// Where pinCS1, pinWR and pinDATA are the numbers of the output pins
// that are connected to the appropriate pins on the HT1632.
noTone(speaker);
delay_disp = false;
// sets the mode switch pin to input
pinMode(modeswitch, INPUT); // sets the digital pin 8 as input
l_player = 0;
r_player = 0;
//adjust this for rally to serve match
serve_end = true;
serve_side = true;
first_point = true;
reset = false;
if (digitalRead(modeswitch)){
// if toggle is high, game ends at 21 and server changes ever 5 points
target_score = 21;
serves_pp = 5;
HT1632.drawImage(TEXT_GAME_21, TEXT_WIDTH, TEXT_HEIGHT, 0, 0);
HT1632.render();
delay (2000);
}else{
// if toggle is low, game ends at 11 and server changes ever 2 points
target_score = 11;
serves_pp = 2;
HT1632.drawImage(TEXT_GAME_11, TEXT_WIDTH, TEXT_HEIGHT, 0, 0);
HT1632.render();
delay (2000);
}
HT1632.drawImage(TEXT_Rally, TEXT_WIDTH, TEXT_HEIGHT, 0, 0);
HT1632.render();
// creates interrupts for the two player buttons
// when interrupt 0 (pin 2) is triggered (player 1 button gos low) call function increaseplayer1()
attachInterrupt(1, l_player_button, LOW);
// when interrupt 1 (pin 3) is triggered (player 2 button gos low) call function increaseplayer2()
attachInterrupt(0, r_player_button, LOW);
}
void l_player_button(){
// variables used for debounce
static unsigned long last_interrupt_time = 0;
unsigned long interrupt_time = millis();
// If interrupts come faster than 200ms, assume it's a bounce and ignore
if (interrupt_time - last_interrupt_time > 200){
if (reset){asm volatile(" jmp 0");}else{
if (first_point){
first_point = !first_point;
HT1632.clear();
display();
buzz = 1;
}else{
l_player++;
if ((l_player >= target_score)&&(l_player >= (r_player + 2))){
HT1632.drawImage(TEXT_L_WIN, TEXT_WIDTH, TEXT_HEIGHT, 0, 0);
HT1632.render();
buzz = 3;
reset = true;
}else{
serve_brain();
if (!delay_disp){
display();
}
}
}
}
}
// debounce variable reset
last_interrupt_time = interrupt_time;
}
void r_player_button(){
// variables used for debounce
static unsigned long last_interrupt_time = 0;
unsigned long interrupt_time = millis();
// If interrupts come faster than 200ms, assume it's a bounce and ignore
if (interrupt_time - last_interrupt_time > 200){
if (reset){asm volatile(" jmp 0");}else{
if (first_point){
serve_end = false;
first_point = !first_point;
HT1632.clear();
display();
buzz = 1;
}else{
r_player++;
if ((r_player >= target_score)&&(r_player >= (l_player + 2))){
HT1632.drawImage(TEXT_R_WIN, TEXT_WIDTH, TEXT_HEIGHT, 0, 0);
HT1632.render();
buzz = 3;
reset = true;
}else{
serve_brain();
if (!delay_disp){
display();
}
}
}
}
}
// debounce variable reset
last_interrupt_time = interrupt_time;
}
void serve_brain(){
if (!((l_player >= (target_score-1))&&(r_player >= (target_score-1)))||((l_player == r_player) && (r_player == (target_score-1)))){
if (((l_player+r_player)%serves_pp)==0){
serve_end = !serve_end;
serve_side = true;
buzz = 2;
}
else{
buzz = 1;
serve_side = !serve_side;
}
}else{
if (((l_player+r_player)%2)==1){
serve_end = !serve_end;
serve_side = true;
buzz = 2;
}else{
buzz = 1;
serve_side = !serve_side;
}
}
if ((l_player == r_player) && (r_player >= (target_score-1))){
HT1632.drawImage(TEXT_DEUCE, TEXT_WIDTH, TEXT_HEIGHT, 0, 0);
HT1632.render();
delay_disp = true;
}
if ((r_player == (l_player+1)) && (l_player >= (target_score-1))){
HT1632.drawImage(TEXT_R_ADV, TEXT_WIDTH, TEXT_HEIGHT, 0, 0);
HT1632.render();
delay_disp = true;
}
if ((l_player == (r_player+1)) && (r_player >= (target_score-1))){
HT1632.drawImage(TEXT_L_ADV, TEXT_WIDTH, TEXT_HEIGHT, 0, 0);
HT1632.render();
delay_disp = true;
serves_pp = 2;
}
}
void display(){
HT1632.clear();
if (serve_end){
if (serve_side){
HT1632.drawImage(IMG_ARROW_UR, IMG_ARROW_WIDTH, IMG_ARROW_HEIGHT, 14, 0);
}
else{
HT1632.drawImage(IMG_ARROW_DR, IMG_ARROW_WIDTH, IMG_ARROW_HEIGHT, 14, 0);
}
}
else {
if (serve_side){
HT1632.drawImage(IMG_ARROW_DL, IMG_ARROW_WIDTH, IMG_ARROW_HEIGHT, 14, 0);
}
else{
HT1632.drawImage(IMG_ARROW_UL, IMG_ARROW_WIDTH, IMG_ARROW_HEIGHT, 14, 0);
}
}
switch ((l_player/10)){ // update left tens
case 0:
HT1632.drawImage(DIGIT_0, DIGIT_WIDTH, DIGIT_HEIGHT, 0, 0);
break;
case 1:
HT1632.drawImage(DIGIT_1, DIGIT_WIDTH, DIGIT_HEIGHT, 0, 0);
break;
case 2:
HT1632.drawImage(DIGIT_2, DIGIT_WIDTH, DIGIT_HEIGHT, 0, 0);
break;
case 3:
HT1632.drawImage(DIGIT_3, DIGIT_WIDTH, DIGIT_HEIGHT, 0, 0);
break;
case 4:
HT1632.drawImage(DIGIT_4, DIGIT_WIDTH, DIGIT_HEIGHT, 0, 0);
break;
case 5:
HT1632.drawImage(DIGIT_5, DIGIT_WIDTH, DIGIT_HEIGHT, 0, 0);
break;
case 6:
HT1632.drawImage(DIGIT_6, DIGIT_WIDTH, DIGIT_HEIGHT, 0, 0);
break;
case 7:
HT1632.drawImage(DIGIT_7, DIGIT_WIDTH, DIGIT_HEIGHT, 0, 0);
break;
case 8:
HT1632.drawImage(DIGIT_8, DIGIT_WIDTH, DIGIT_HEIGHT, 0, 0);
break;
case 9:
HT1632.drawImage(DIGIT_9, DIGIT_WIDTH, DIGIT_HEIGHT, 0, 0);
break;
}
switch ((l_player%10)){
case 0:
HT1632.drawImage(DIGIT_0, DIGIT_WIDTH, DIGIT_HEIGHT, 7, 0);
break;
case 1:
HT1632.drawImage(DIGIT_1, DIGIT_WIDTH, DIGIT_HEIGHT, 7, 0);
break;
case 2:
HT1632.drawImage(DIGIT_2, DIGIT_WIDTH, DIGIT_HEIGHT, 7, 0);
break;
case 3:
HT1632.drawImage(DIGIT_3, DIGIT_WIDTH, DIGIT_HEIGHT, 7, 0);
break;
case 4:
HT1632.drawImage(DIGIT_4, DIGIT_WIDTH, DIGIT_HEIGHT, 7, 0);
break;
case 5:
HT1632.drawImage(DIGIT_5, DIGIT_WIDTH, DIGIT_HEIGHT, 7, 0);
break;
case 6:
HT1632.drawImage(DIGIT_6, DIGIT_WIDTH, DIGIT_HEIGHT, 7, 0);
break;
case 7:
HT1632.drawImage(DIGIT_7, DIGIT_WIDTH, DIGIT_HEIGHT, 7, 0);
break;
case 8:
HT1632.drawImage(DIGIT_8, DIGIT_WIDTH, DIGIT_HEIGHT, 7, 0);
break;
case 9:
HT1632.drawImage(DIGIT_9, DIGIT_WIDTH, DIGIT_HEIGHT, 7, 0);
break;
}
switch ((r_player/10)){ // update right tens
case 0:
HT1632.drawImage(DIGIT_0, DIGIT_WIDTH, DIGIT_HEIGHT, 19, 0);
break;
case 1:
HT1632.drawImage(DIGIT_1, DIGIT_WIDTH, DIGIT_HEIGHT, 19, 0);
break;
case 2:
HT1632.drawImage(DIGIT_2, DIGIT_WIDTH, DIGIT_HEIGHT, 19, 0);
break;
case 3:
HT1632.drawImage(DIGIT_3, DIGIT_WIDTH, DIGIT_HEIGHT, 19, 0);
break;
case 4:
HT1632.drawImage(DIGIT_4, DIGIT_WIDTH, DIGIT_HEIGHT, 19, 0);
break;
case 5:
HT1632.drawImage(DIGIT_5, DIGIT_WIDTH, DIGIT_HEIGHT, 19, 0);
break;
case 6:
HT1632.drawImage(DIGIT_6, DIGIT_WIDTH, DIGIT_HEIGHT, 19, 0);
break;
case 7:
HT1632.drawImage(DIGIT_7, DIGIT_WIDTH, DIGIT_HEIGHT, 19, 0);
break;
case 8:
HT1632.drawImage(DIGIT_8, DIGIT_WIDTH, DIGIT_HEIGHT, 19, 0);
break;
case 9:
HT1632.drawImage(DIGIT_9, DIGIT_WIDTH, DIGIT_HEIGHT, 19, 0);
break;
}
switch ((r_player%10)){
case 0:
HT1632.drawImage(DIGIT_0, DIGIT_WIDTH, DIGIT_HEIGHT, 26, 0);
break;
case 1:
HT1632.drawImage(DIGIT_1, DIGIT_WIDTH, DIGIT_HEIGHT, 26, 0);
break;
case 2:
HT1632.drawImage(DIGIT_2, DIGIT_WIDTH, DIGIT_HEIGHT, 26, 0);
break;
case 3:
HT1632.drawImage(DIGIT_3, DIGIT_WIDTH, DIGIT_HEIGHT, 26, 0);
break;
case 4:
HT1632.drawImage(DIGIT_4, DIGIT_WIDTH, DIGIT_HEIGHT, 26, 0);
break;
case 5:
HT1632.drawImage(DIGIT_5, DIGIT_WIDTH, DIGIT_HEIGHT, 26, 0);
break;
case 6:
HT1632.drawImage(DIGIT_6, DIGIT_WIDTH, DIGIT_HEIGHT, 26, 0);
break;
case 7:
HT1632.drawImage(DIGIT_7, DIGIT_WIDTH, DIGIT_HEIGHT, 26, 0);
break;
case 8:
HT1632.drawImage(DIGIT_8, DIGIT_WIDTH, DIGIT_HEIGHT, 26, 0);
break;
case 9:
HT1632.drawImage(DIGIT_9, DIGIT_WIDTH, DIGIT_HEIGHT, 26, 0);
break;
}
HT1632.render();
}
void loop () {
switch (buzz){
case 1:
for (int i=2000;i<3000;i=i+10){
tone(speaker, i);
delay(1);
}
noTone(speaker);
buzz = 0;
break;
case 2:
for (int i=1000;i<3000;i=i+10){
tone(speaker, i);
delay(1);
}
for (int i=3000;i>1000;i=i-10){
tone(speaker, i);
delay(1);
}
noTone(speaker);
buzz = 0;
break;
case 3:
for (int j=0;j<5;j++){
for (int i=2000;i<3000;i=i+10){
tone(speaker, i);
delay(1);
}
for (int i=3000;i>2000;i=i-10){
tone(speaker, i);
delay(1);
}
}
noTone(speaker);
buzz = 0;
break;
}
if (delay_disp){
if (buzz==0){
delay(1000);
HT1632.clear();
display();
delay_disp = false;
}
}
}