Animated Ping Pong Scoreboard (updated with videos)
An animated scoreboard with sound that keeps track of score, games won, who is serving and entertains with animation and digitized voices.
Components and supplies
1
Arduino Mega 2560
4
Single Pole Single Throw Toggle Switches
1
AdaFruit RGB 16x32 matrix
1
Adafruit Seven Segment 1.5" LED Red
1
Standard LCD - 16x2 White on Blue
2
Speaker: 0.25W, 8 ohms
4
Pushbutton Switch, Momentary
2
Big Lighted NO Pushbuttons
1
Adafruit FX SoundBoard 16mb
Project description
Code
Ping Pong Scoreboard Code
arduino
The ping pong scoreboard sketch
1 2 3 4/* Ping Pong Scorekeeper 5 Device to take care of scoring, switch of serve, keeping track of games won, etc. 6 Configurable for different rule changes (end of game, switching serves, etc.) 7 Includes sound effects 8 */ 9 10 11#include <Wire.h> 12#include <RGBmatrixPanel.h> 13#include <LiquidCrystal_I2C.h> 14#include <Adafruit_GFX.h> 15#include "Adafruit_LEDBackpack.h" 16 17 18 19 20Adafruit_7segment matrix = Adafruit_7segment(); // Score display 21Adafruit_7segment matrix1 = Adafruit_7segment(); // Games/match display 22LiquidCrystal_I2C lcd(0x27,16,2); // initialize LCD 23 24 25int DEBOUNCE =250; // button debouncer 26int which_button; // return from button query 27const int ScoreToSwitch = A8; // the number of pin for switch for "11 or 21" as end of game 28const int ServeToSwitch = A9; // number of pin for switch for "Serve 2 or Serve 5" before switching 29const int JISwitch = A10; // Switches between Joan and Irwin vs. Player 1 and Player 2 30const int firstbuttonpin = 2; // Use pins 2-7 for reading the scoring buttons 31const int numbuttons = 6; //number of buttons to check 32int lastreading[numbuttons]; // for button read routine 33const int scrolldelay = 500; // time to pause between scrolled text items 34const int delaygamenumber = 2000; // milliseconds to let "Game #xx" linger 35 36// Most of the signal pins for the 16 x 32 RGB matrix are configurable, but the CLK pin has some 37// special constraints. On 8-bit AVR boards it must be on PORTB... 38// Pin 8 works on the Arduino Uno & compatibles (e.g. Adafruit Metro), 39// Pin 11 works on the Arduino Mega. On 32-bit SAMD boards it must be 40// on the same PORT as the RGB data pins (D2-D7)... 41// Pin 8 works on the Adafruit Metro M0 or Arduino Zero, 42// Pin A4 works on the Adafruit Metro M4 (if using the Adafruit RGB 43// Matrix Shield, cut trace between CLK pads and run a wire to A4). 44 45//#define CLK 8 // USE THIS ON ARDUINO UNO, ADAFRUIT METRO M0, etc. 46//#define CLK A4 // USE THIS ON METRO M4 (not M0) 47#define CLK 11 // USE THIS ON ARDUINO MEGA 48#define OE 9 49#define LAT 10 50#define A A0 51#define B A1 52#define C A2 53 54int Red = 0xF800; // Color definitions for RGB panel 55int Black = 0x0000; 56int Yellow = 0xFFE0; 57int Green = 0x07E0; 58int White = 0xFFF; 59int Blue = 0x001F; 60int Cyan = 0x07E0; 61int Magenta = 0xF81F; 62 63 64// Define RGB matrix width and height. 65const int mw = 32; //matrix width in pixels 66const int mh = 16; // total matrix height in pixels 67const int cw = 6; // width of a character (size 1) in pixels 68const int Line1 = 0; // first line of RGB panel (just easier to remember) 69const int Line2 = 1; // second line 70const int scrolldelayc = 100; // time between successive characters in scroll 71const int flashdelay = 1000; // delay for flash display 72const int afterscrolldelay = 2000; // when finished with scroll, leave it up a bit 73 74const int bouncedelay = 45; 75const int bouncesize = 25; 76const int firstx = 3; // x coordinate for first and last ping pong ball position 77const int lastx = 28; // for start up graphic routine 78const int bouncey[bouncesize] = {1,3,5,7,9,11,13,11,9,7,5,3,1,3,5,7,9,11,13,11,9,7,5,3,1}; // y coordinates for each x ball 79const int ballsize = 1; // 2 looks nicer but gives a GIANT ball! 80 81 82RGBmatrixPanel matrixp(A, B, C, CLK, LAT, OE, false); // initialize 16 x 32 matrix 83 84 85/* 86 * Definition of buttons 87 * 1 Reset Match and Game 88 * 2 Force switch of service (but don't reset game) 89 * 3 Score point for Player 1 90 * 4 Unscore point for Player 1 (it was a mistake or disputed,etc.) 91 * 5 Score point for Player 2 92 * 6 Unscore point for Player 2 93 94 */ 95 96int Score[2]; //Score 97int Games[2]; // Games won 98bool Player1isServing; // flag for which play has serve 99int ScoreTo = 21; // game length will be 11 or 21 100int ServeTo = 5; // serve length will be 2 or 5 101bool JI = false; // assume generic players 102int switchread; 103int i; 104const int Player1 = 0; // Position in array for first player/team 105const int Player2 = 1; // Same for "other" player 106 107// Voice/sound files 108/* 109 The sounds are all on the FX Sound board in the form of sequential WAV files 110 that are name T0 through T6 (board has a maximum of 0-10 (11) files in this form (GPIO) 111 */ 112 113const int SoundStartup = 0; // Sound to play at startup Ping Pong ball then Shall We Play a Game 114const int SoundChangeServe = 1; // Simple bell or tone 115const int SoundNewGame = 2; // "New Game" 116const int SoundGameOver = 3; // Game Over + Cheering 117const int SoundWinnerIs1 = 4; // And the Winner is Player 1 118const int SoundWinnerIs2 = 5; // And the Winner is Player 2 119const int SoundUndo = 6; // Point was a mistake 120const int SoundWinnerIsJoan = 7; // And the winner is Joan 121const int SoundWinnerIsIrwin = 8; // same for Irwin 122const int SoundLetsPlay = 9; // Lets Play Ping Pong 123 124 125 126const int FirstSoundPin = 36; // first pin to use for soundboard (0-10 available on soundboard); 127const int SizeSoundPin = 10; // number of pins used (36-45) 128const int fxset = 150; // Time soundboard must be held LOW to register (documentation says 125 ms) 129const int SoundBusyPin = 8; // Soundboard pin that goes LOW when board is active 130const int SoundBusyDelay = 25; // Period to wait between checking to see if sound has finished 131int SoundBusyValue; 132bool SoundBusy; // set true when busy 133 134 135void setup() { 136 137 which_button = 0; 138 for (i = firstbuttonpin; i < firstbuttonpin+numbuttons; i++) { 139 pinMode(i, INPUT_PULLUP); 140 lastreading[i] = HIGH;} // initialize all buttons 'off' 141 pinMode(ScoreToSwitch, INPUT_PULLUP); // These three are toggle switches that change 142 pinMode (ServeToSwitch, INPUT_PULLUP); // the rules of the game (Win Score [11 or 21] and Serve change frequency [2 or 5]) 143 pinMode (JISwitch, INPUT_PULLUP); // and "Joan and Irwin" vs. "Player 1 and Player" 144 pinMode(SoundBusyPin, INPUT_PULLUP); 145 146 for (i=FirstSoundPin; i <FirstSoundPin+SizeSoundPin; i++){ // pins for sound board 147 pinMode(i,OUTPUT); // each an output 148 digitalWrite(i, HIGH); } // and initialize high (off) 149 Player1isServing = true; // default, can be switched with button 2 (switch serve) 150 for (i=0; i <2; i++) { // clear score and matches 151 Score[i] = 0; 152 Games[i] = 0; 153 } 154 Serial.begin (9600); 155 Wire.begin(); // initialize I2C interface 156 matrix.begin(0x70); // initialize Score LED 157 matrix1.begin(0x71); // and Game LED 158 matrixp.begin(); // initialize 16x32 RGB panel 159 matrixp.fillScreen(Black); // and clear it 160 matrix1.setBrightness(10); // mid level brightness for Game display (0-15) 161 lcd.init(); // initialize LCD 162 lcd.backlight(); 163 164 DisplaySetandServe(); // Initial Displays for LCD 165 BeginMatch(); // and begin the match with "Game #" 166 167 168} // End of Setup 169 170void loop() { 171 // put your main code here, to run repeatedly: 172 173 DisplaySetandServe(); // Check the settings of "Play to" and "Each Service" 174 175which_button = check_switches(); 176 if (which_button != 0) { 177 178 switch (which_button) { 179 case 0: // no button pushed 180 break; 181 182 case 1: // Set Zero to Match and Game Score 183 for (i=0; i <2; i++) { 184 Score[i] = 0; 185 Games[i] = 0;} 186 BeginMatch(); 187 break; 188 189 case 2: // Just switch serve and don't reset game 190 Player1isServing = !Player1isServing; 191 PlaySound(SoundChangeServe); 192 DisplayServe(Player1isServing); 193 break; 194 195 case 3: // Point for Player 1 196 ScoreUp(Player1); 197 break; 198 199 case 4: // That was a mistake so 'unscore' 200 if (Score[Player1] > 0) {Score[Player1]--;} 201 PlaySound(SoundUndo); 202 DisplayScore(); 203 break; 204 205 case 5: // Point for Player 2 206 ScoreUp(Player2); 207 break; 208 209 case 6: // That was a mistake so 'unscore' 210 if (Score[Player2] > 0) {Score[Player2]--;} 211 PlaySound(SoundUndo); 212 DisplayScore(); 213 break; 214 215 default: 216 Serial.println("Should never get here!"); 217 break; 218 219} // End of Switch 220 } // end of "if non-zero button" 221 222} // end of Void Loop 223 224void ScoreUp(int player) // Routine to score up a point, check everything 225{ 226 int otherplayer; 227 if (player ==1) {otherplayer = 0;} else otherplayer = 1; 228 Score[player]++; // Give the player a point 229 DisplayScore(); // Show current score 230 if ((Score[player] >= ScoreTo) && (Score[player] - Score[otherplayer] > 1)) // You have to win by 2 points 231 {GameOver(player); // was it a win? 232 } else 233 if ((Score[Player1] + Score[Player2]) % ServeTo == 0){ 234 Player1isServing = !Player1isServing; 235 PlaySound(SoundChangeServe); 236 DisplayServe(Player1isServing); 237 } 238 239} // end of ScoreUp 240 241void DisplayScore(){ // Update displays for score in the game 242 // Show player1 score on the left and player 2 on the right 243 if (Score[Player1] > 9){ matrix.writeDigitNum(0,Score[Player1]/10);} else matrix.writeDigitRaw(0,0); // use leading blank not zero 244 matrix.writeDigitNum(1,Score[Player1]%10); 245 if (Score[Player2] > 9){ matrix.writeDigitNum(3,Score[Player2]/10);} else matrix.writeDigitRaw(3,0); 246 matrix.writeDigitNum(4,Score[Player2]%10); 247 matrix.drawColon(false); // no colon 248 matrix.writeDisplay(); // and push out the LED print 249} // end of DisplayScore 250 251void DisplayGames(){ // Update displays for games in the match 252 if (Games[Player1] > 9 ) {matrix1.writeDigitNum(0,Games[Player1]/10);} else matrix1.writeDigitRaw(0,0); 253 matrix1.writeDigitNum(1,Games[Player1]%10); 254 if (Games[Player2] > 9 ) {matrix1.writeDigitNum(3,Games[Player2]/10);} else matrix1.writeDigitRaw(0,0); 255 matrix1.writeDigitNum(4,Games[Player2]%10); 256 matrix1.drawColon(false); // no colon 257 matrix1.writeDisplay(); // and push out the LED print 258} // end of DisplayGames 259 260void DisplayServe(bool firstplayer){ // Update displays for who is serving 261 int colort; 262 if (firstplayer) {colort = Green;} else colort = Yellow; // change color for each player 263 mclear(Line2); // Clear display lines on 16 x 32 panel 264 PrintPanel("Serve", Line1, colort); 265 Arrows(firstplayer,Line2,colort); // shows to the left (Player1) or right (Player2) 266 // depending on boolean flag 267} // end of DisplayServe 268 269 270void PlaySound(int WhichSound){ // Plays appropriate Sound file 271 SoundBusy = true; // Are we already playing a sound? 272 while (SoundBusy){ // Then wait til its done 273 SoundBusyValue = digitalRead(SoundBusyPin); // as indicated by ACT going High again 274 if (SoundBusyValue == LOW) { 275 delay(SoundBusyDelay); // wait on the soundboard 276 } else 277 SoundBusy = false;} 278 digitalWrite(FirstSoundPin+WhichSound, LOW); //Play the sound 279 delay(fxset); // hold to start the sound 280 digitalWrite(FirstSoundPin+WhichSound, HIGH); //Turn off the sound 281 delay(fxset); // Ensure its set 282} // end of PlaySound 283 284 285void GameOver(int player){ // Someone has won the game 286 int gocolor; 287 mclear(Line1); 288 mclear(Line2); 289 Games[player]++; 290 DisplayGames(); 291 PlaySound(SoundGameOver); 292 Flash ("Game", "Over!"); 293 if (player == Player1) { 294 if (!JI) {PlaySound(SoundWinnerIs1); 295 ScrollText2("Player 1", "Wins ", Yellow, Yellow);} else {PlaySound(SoundWinnerIsJoan); 296 Flash("Joan", "Wins!");} 297 Player1isServing = false;} else 298 { 299 if (!JI) {PlaySound(SoundWinnerIs2); 300 ScrollText2("Player 2", "Wins ", Yellow, Yellow);} else {PlaySound(SoundWinnerIsIrwin); 301 Flash("Irwin", "Wins!");} 302 Player1isServing = true;} 303 Score[Player1] =0; // reset scores and display 304 Score[Player2] = 0; 305 DisplayScore(); 306 if (player == Player1){ 307 if (!JI) {ScrollText2("Player 2", "Serve", Yellow, Yellow);} 308 else ScrollText2("Irwin", "Serve", Yellow, Yellow);} 309 else if (!JI) {ScrollText2("Player 1", "Serve", Yellow, Yellow);} else ScrollText2("Joan", "Serve", Yellow, Yellow); 310 DisplayGameNumber(); // Game number called out 311 PlaySound(SoundNewGame); 312 DisplayServe(Player1isServing); 313} // end of Game Over 314 315byte check_switches() // This will return pin pressed and includes a debounce 316{ 317 318 int reading; 319 int button_pressed = 0; 320 int index; 321 322 for (index = 0; index < numbuttons; index++) { 323 reading = digitalRead(firstbuttonpin + index); 324 if (reading == LOW && lastreading[index] == HIGH) // switch pressed? 325 {button_pressed = index + 1; // calculate return number 326 delay(DEBOUNCE); // debounce a little 327 break; // end "for" loop 328 } 329 lastreading[index] = reading; //update reading 330 } 331 // return switch number (1 - numbuttons) 332 return button_pressed; 333} // end of Check Switches 334 335void Bounce(int bouncetimes) 336{ 337 int x; 338 int y; 339 int jj; 340 matrixp.fillScreen(Black); // clear the screen 341 matrixp.fillRect(2,15,28,1,Green); // draw the table 342 matrixp.fillRect(15,9,1,6,White); // and the net 343 for (int i = 0; i <=bouncetimes; i++) 344 { jj = -1; // reset y index (will increment to 0, first element) 345 for (int j = firstx; j <=lastx; j++) 346 { jj++; // increment y coordinate index 347 x = j; // bounce left to right 348 y = bouncey[jj]; 349 matrixp.fillCircle(x,y,ballsize,Yellow); 350 if (jj == 0){ // display the paddle 351 matrixp.fillRect(0,0,2,6,Green); 352 delay(bouncedelay); 353 } 354 delay(bouncedelay); 355 matrixp.fillCircle(x,y,ballsize,Black); 356 delay(bouncedelay); 357 matrixp.fillRect(0,0,2,6,Black); 358 } 359 jj = bouncesize+1; 360 for (int j = lastx; j >=firstx; j--) // same except in reverse 361 { jj-- ; 362 x = j; 363 y = bouncey[jj]; 364 matrixp.fillCircle(x,y,ballsize,Yellow); 365 delay(bouncedelay); 366 if (jj == bouncesize){ // display the paddle 367 matrixp.fillRect(30,0,2,6,Green); 368 delay(bouncedelay * 3); 369 } 370 matrixp.fillCircle(x,y,ballsize,Black); 371 delay(bouncedelay * 3); 372 matrixp.fillRect(30,0,2,6,Black); 373 } 374} 375} // end of Bounce 376 377void PrintPanel(String ToPrint, int pline, int pcolort) 378{ 379 if (pline == Line1) {matrixp.setCursor(1,0);} else matrixp.setCursor(1,mh/2); 380 mclear(pline); // clear the line out 381 matrixp.setTextColor(pcolort); // set the color 382 matrixp.setTextWrap(true); // shouldn't matter 383 matrixp.print(ToPrint); // print the line 384} // end of PrintPanel 385 386 387void ScrollText(String ToScroll, int pline, int pcolort) { 388 int yaxis; 389 int pixelx = mw; // start scroll at 'right end' 390 if (pline == 0) {yaxis = 0;} else yaxis = mh/2; // y axis varies with line 0 or 1 so set it 391 matrixp.setTextWrap(false); // we don't wrap text so it scrolls nicely 392 int numchars = ToScroll.length(); // number of characters in string 393 for (int ff = 0; ff <= cw*numchars; ff++) { // start appropriately and scroll left 394 matrixp.setCursor(pixelx,yaxis); 395 pixelx-- ; 396 mclear(pline); // clear the line 397 matrixp.setTextColor(pcolort); // set the color 398 matrixp.print(ToScroll); // and print (once for each pixel) 399 delay(scrolldelayc); 400 } 401 delay(afterscrolldelay); 402} // end of display_scrollText 403 404void ScrollText2(String ToScroll1, String ToScroll2, int pcolort1,int pcolort2) { 405 // scrolls two strings at once in parallel 406 int pixelx = mw; // start at right hand side 407 int pixelcount; // will be total (longer) pixel count 408 int pixelcount1; // counter for string 1 409 int pixelcount2; // counter for string 2 410 matrixp.setTextWrap(false); // we don't wrap text so it scrolls nicely 411 pixelcount1 = ToScroll1.length()*cw; // number of characters in line 0 string converted to pixels 412 pixelcount2 = ToScroll2.length()*cw; // number of characters in line 1 string converted to pixels 413 if (pixelcount2 > pixelcount1) {pixelcount = pixelcount2;} else pixelcount = pixelcount1; // set for longer of two strings 414 415 for (int ff = 0; ff <= pixelcount; ff++) { // start appropriately and scroll left (longer of two strings) 416 if (pixelcount1 >= 0) { // Print top line 417 mclear(0); // clear the line 418 matrixp.setCursor(pixelx,0); // position 419 matrixp.setTextColor(pcolort1); // set the color 420 matrixp.print(ToScroll1); // and print (once for each pixel ) 421 pixelcount1--;} // and decrement 422 if (pixelcount2 >= 0) { // Print 2nd line (one may finish before the other!) 423 mclear(1); // clear the line 424 matrixp.setCursor(pixelx,mh/2); // reposition 425 matrixp.setTextColor(pcolort2); // set the color 426 matrixp.print(ToScroll2); // and print (once for each pixel ) 427 pixelcount2--;} // and decrement 428 pixelx-- ; 429 delay(scrolldelayc); 430 } 431 delay(afterscrolldelay); 432} // end of ScrollText2 433 434void mclear(int pline) { // quick clear of either line 0 or line 1 435 int xc; 436 int yc; 437 if (pline == 0) {yc = 0;} else yc = mh/2; 438 matrixp.fillRect(0,yc,mw,mh/2,Black); 439} // end of mclear 440 441void Arrows(bool left, int pline, int pcolort) { 442 int yaxis; // y axis varies with line 0 or 1 443 if (pline == 0) {yaxis = 0;} else yaxis = mh/2; // so set it 444 if (left) { // called with player1 true or not (i.e., player2) 445 matrixp.fillTriangle(1,4+yaxis,4,1+yaxis,4, 7+yaxis, pcolort); // arrows point right or left indicating 446 matrixp.fillRect(5,3+yaxis,6,3,pcolort); // who is serving 447 matrixp.fillTriangle(12,4+yaxis,15,1+yaxis,15, 7+yaxis, pcolort); 448 matrixp.fillRect(16,3+yaxis,6,3,pcolort); 449 matrixp.fillTriangle(23,4+yaxis,26,1+yaxis,26, 7+yaxis, pcolort); 450 matrixp.fillRect(27,3+yaxis,6,3,pcolort); } else 451 { 452 matrixp.fillTriangle(30,4+yaxis,27,1+yaxis,27, 7+yaxis, pcolort); 453 matrixp.fillRect(21,3+yaxis,6,3,pcolort); 454 matrixp.fillTriangle(19,4+yaxis,16,1+yaxis,16, 7+yaxis, pcolort); 455 matrixp.fillRect(10,3+yaxis,6,3,pcolort); 456 matrixp.fillTriangle(8,4+yaxis,5,1+yaxis,5, 7+yaxis, pcolort); 457 matrixp.fillRect(0,3+yaxis,5,3,pcolort); 458 } 459 460} // End of Arrows 461 462void Flash (String ToFlash1, String ToFlash2){ 463 const int flashcolorsize = 9; 464 int flashcolors[flashcolorsize] = {Green, Cyan, Yellow, Magenta, Red, Blue,Green, Red, White}; 465 for (int i = 0; i < flashcolorsize; i++){ 466 matrixp.fillScreen (Black); 467 matrixp.setCursor(0,0); // reposition 468 matrixp.setTextColor(flashcolors[i]); // set the color 469 matrixp.print(ToFlash1); // and print (once for each pixel ) 470 matrixp.setCursor(0,mh/2); // reposition 471 matrixp.print(ToFlash2); // and print (once for each pixel ) 472 delay(flashdelay); // wait after flash 473 } 474} // end of Flash 475 476void BeginMatch(){ 477 DisplayScore(); // show LED Score 478 DisplayGames(); // for game and games/match 479 PlaySound(SoundStartup); // Play startup sound on soundcard 480 Bounce(1); // Play the cute startup graphic 481 PlaySound(SoundLetsPlay); 482 ScrollText2("Let's Ping ","Play Pong ", Yellow, Yellow); // and little warmup 483 PlaySound(SoundNewGame); // New game sound 484 DisplayGameNumber(); // Should be "Game #1" 485 DisplayServe(Player1isServing); // and give initial serve indication (play 1 or 2) 486} // End of Begin Match 487 488 void DisplaySetandServe(){ 489 if (digitalRead(ScoreToSwitch) == HIGH){ScoreTo = 21;} else ScoreTo = 11 ; // Check settings for Score and Serve 490 if (digitalRead(ServeToSwitch) == HIGH){ServeTo = 5;} else ServeTo = 2 ; // in case it changes 491 if (digitalRead(JISwitch) == HIGH){JI = true;} else JI = false ; // Convenient to read JI switch too 492 lcd.setCursor(0, 0); // set LCD column 0, row 0 493 lcd.print("Score of " ); 494 lcd.print(ScoreTo); // print either 21 or 11 495 lcd.print(" wins" ); 496 lcd.setCursor(0, 1); // set LCD column 0, row 1 497 lcd.print("Service:"); 498 lcd.print(ServeTo); // print either 5 or 2 499 lcd.print(" "); 500 if (JI) {lcd.print("J&I ");} else lcd.print("P1/P2"); // indicate whether the Joan & Irwin over-ride is in place or Player 1 and 2 501} // end of Display Set and Serve 502 503void DisplayGameNumber(){ 504 PrintPanel("Game", Line1, Green); 505 mclear(1); 506 matrixp.setCursor((mw/2)-3,mh/2); // 2nd line is which game we are on 507 matrixp.print("# "); 508 matrixp.setCursor((mw/2)+4,mh/2); 509 matrixp.print(Games[0]+Games[1]+1,DEC ); // print the line 510 delay(delaygamenumber); 511} 512
Ping Pong Scoreboard Code
arduino
The ping pong scoreboard sketch
1 2 3 4/* Ping Pong Scorekeeper 5 Device to take care of scoring, switch of serve, keeping track of games won, etc. 6 Configurable for different rule changes (end of game, switching serves, etc.) 7 Includes sound effects 8 */ 9 10 11#include <Wire.h> 12#include <RGBmatrixPanel.h> 13#include <LiquidCrystal_I2C.h> 14#include <Adafruit_GFX.h> 15#include "Adafruit_LEDBackpack.h" 16 17 18 19 20Adafruit_7segment matrix = Adafruit_7segment(); // Score display 21Adafruit_7segment matrix1 = Adafruit_7segment(); // Games/match display 22LiquidCrystal_I2C lcd(0x27,16,2); // initialize LCD 23 24 25int DEBOUNCE =250; // button debouncer 26int which_button; // return from button query 27const int ScoreToSwitch = A8; // the number of pin for switch for "11 or 21" as end of game 28const int ServeToSwitch = A9; // number of pin for switch for "Serve 2 or Serve 5" before switching 29const int JISwitch = A10; // Switches between Joan and Irwin vs. Player 1 and Player 2 30const int firstbuttonpin = 2; // Use pins 2-7 for reading the scoring buttons 31const int numbuttons = 6; //number of buttons to check 32int lastreading[numbuttons]; // for button read routine 33const int scrolldelay = 500; // time to pause between scrolled text items 34const int delaygamenumber = 2000; // milliseconds to let "Game #xx" linger 35 36// Most of the signal pins for the 16 x 32 RGB matrix are configurable, but the CLK pin has some 37// special constraints. On 8-bit AVR boards it must be on PORTB... 38// Pin 8 works on the Arduino Uno & compatibles (e.g. Adafruit Metro), 39// Pin 11 works on the Arduino Mega. On 32-bit SAMD boards it must be 40// on the same PORT as the RGB data pins (D2-D7)... 41// Pin 8 works on the Adafruit Metro M0 or Arduino Zero, 42// Pin A4 works on the Adafruit Metro M4 (if using the Adafruit RGB 43// Matrix Shield, cut trace between CLK pads and run a wire to A4). 44 45//#define CLK 8 // USE THIS ON ARDUINO UNO, ADAFRUIT METRO M0, etc. 46//#define CLK A4 // USE THIS ON METRO M4 (not M0) 47#define CLK 11 // USE THIS ON ARDUINO MEGA 48#define OE 9 49#define LAT 10 50#define A A0 51#define B A1 52#define C A2 53 54int Red = 0xF800; // Color definitions for RGB panel 55int Black = 0x0000; 56int Yellow = 0xFFE0; 57int Green = 0x07E0; 58int White = 0xFFF; 59int Blue = 0x001F; 60int Cyan = 0x07E0; 61int Magenta = 0xF81F; 62 63 64// Define RGB matrix width and height. 65const int mw = 32; //matrix width in pixels 66const int mh = 16; // total matrix height in pixels 67const int cw = 6; // width of a character (size 1) in pixels 68const int Line1 = 0; // first line of RGB panel (just easier to remember) 69const int Line2 = 1; // second line 70const int scrolldelayc = 100; // time between successive characters in scroll 71const int flashdelay = 1000; // delay for flash display 72const int afterscrolldelay = 2000; // when finished with scroll, leave it up a bit 73 74const int bouncedelay = 45; 75const int bouncesize = 25; 76const int firstx = 3; // x coordinate for first and last ping pong ball position 77const int lastx = 28; // for start up graphic routine 78const int bouncey[bouncesize] = {1,3,5,7,9,11,13,11,9,7,5,3,1,3,5,7,9,11,13,11,9,7,5,3,1}; // y coordinates for each x ball 79const int ballsize = 1; // 2 looks nicer but gives a GIANT ball! 80 81 82RGBmatrixPanel matrixp(A, B, C, CLK, LAT, OE, false); // initialize 16 x 32 matrix 83 84 85/* 86 * Definition of buttons 87 * 1 Reset Match and Game 88 * 2 Force switch of service (but don't reset game) 89 * 3 Score point for Player 1 90 * 4 Unscore point for Player 1 (it was a mistake or disputed,etc.) 91 * 5 Score point for Player 2 92 * 6 Unscore point for Player 2 93 94 */ 95 96int Score[2]; //Score 97int Games[2]; // Games won 98bool Player1isServing; // flag for which play has serve 99int ScoreTo = 21; // game length will be 11 or 21 100int ServeTo = 5; // serve length will be 2 or 5 101bool JI = false; // assume generic players 102int switchread; 103int i; 104const int Player1 = 0; // Position in array for first player/team 105const int Player2 = 1; // Same for "other" player 106 107// Voice/sound files 108/* 109 The sounds are all on the FX Sound board in the form of sequential WAV files 110 that are name T0 through T6 (board has a maximum of 0-10 (11) files in this form (GPIO) 111 */ 112 113const int SoundStartup = 0; // Sound to play at startup Ping Pong ball then Shall We Play a Game 114const int SoundChangeServe = 1; // Simple bell or tone 115const int SoundNewGame = 2; // "New Game" 116const int SoundGameOver = 3; // Game Over + Cheering 117const int SoundWinnerIs1 = 4; // And the Winner is Player 1 118const int SoundWinnerIs2 = 5; // And the Winner is Player 2 119const int SoundUndo = 6; // Point was a mistake 120const int SoundWinnerIsJoan = 7; // And the winner is Joan 121const int SoundWinnerIsIrwin = 8; // same for Irwin 122const int SoundLetsPlay = 9; // Lets Play Ping Pong 123 124 125 126const int FirstSoundPin = 36; // first pin to use for soundboard (0-10 available on soundboard); 127const int SizeSoundPin = 10; // number of pins used (36-45) 128const int fxset = 150; // Time soundboard must be held LOW to register (documentation says 125 ms) 129const int SoundBusyPin = 8; // Soundboard pin that goes LOW when board is active 130const int SoundBusyDelay = 25; // Period to wait between checking to see if sound has finished 131int SoundBusyValue; 132bool SoundBusy; // set true when busy 133 134 135void setup() { 136 137 which_button = 0; 138 for (i = firstbuttonpin; i < firstbuttonpin+numbuttons; i++) { 139 pinMode(i, INPUT_PULLUP); 140 lastreading[i] = HIGH;} // initialize all buttons 'off' 141 pinMode(ScoreToSwitch, INPUT_PULLUP); // These three are toggle switches that change 142 pinMode (ServeToSwitch, INPUT_PULLUP); // the rules of the game (Win Score [11 or 21] and Serve change frequency [2 or 5]) 143 pinMode (JISwitch, INPUT_PULLUP); // and "Joan and Irwin" vs. "Player 1 and Player" 144 pinMode(SoundBusyPin, INPUT_PULLUP); 145 146 for (i=FirstSoundPin; i <FirstSoundPin+SizeSoundPin; i++){ // pins for sound board 147 pinMode(i,OUTPUT); // each an output 148 digitalWrite(i, HIGH); } // and initialize high (off) 149 Player1isServing = true; // default, can be switched with button 2 (switch serve) 150 for (i=0; i <2; i++) { // clear score and matches 151 Score[i] = 0; 152 Games[i] = 0; 153 } 154 Serial.begin (9600); 155 Wire.begin(); // initialize I2C interface 156 matrix.begin(0x70); // initialize Score LED 157 matrix1.begin(0x71); // and Game LED 158 matrixp.begin(); // initialize 16x32 RGB panel 159 matrixp.fillScreen(Black); // and clear it 160 matrix1.setBrightness(10); // mid level brightness for Game display (0-15) 161 lcd.init(); // initialize LCD 162 lcd.backlight(); 163 164 DisplaySetandServe(); // Initial Displays for LCD 165 BeginMatch(); // and begin the match with "Game #" 166 167 168} // End of Setup 169 170void loop() { 171 // put your main code here, to run repeatedly: 172 173 DisplaySetandServe(); // Check the settings of "Play to" and "Each Service" 174 175which_button = check_switches(); 176 if (which_button != 0) { 177 178 switch (which_button) { 179 case 0: // no button pushed 180 break; 181 182 case 1: // Set Zero to Match and Game Score 183 for (i=0; i <2; i++) { 184 Score[i] = 0; 185 Games[i] = 0;} 186 BeginMatch(); 187 break; 188 189 case 2: // Just switch serve and don't reset game 190 Player1isServing = !Player1isServing; 191 PlaySound(SoundChangeServe); 192 DisplayServe(Player1isServing); 193 break; 194 195 case 3: // Point for Player 1 196 ScoreUp(Player1); 197 break; 198 199 case 4: // That was a mistake so 'unscore' 200 if (Score[Player1] > 0) {Score[Player1]--;} 201 PlaySound(SoundUndo); 202 DisplayScore(); 203 break; 204 205 case 5: // Point for Player 2 206 ScoreUp(Player2); 207 break; 208 209 case 6: // That was a mistake so 'unscore' 210 if (Score[Player2] > 0) {Score[Player2]--;} 211 PlaySound(SoundUndo); 212 DisplayScore(); 213 break; 214 215 default: 216 Serial.println("Should never get here!"); 217 break; 218 219} // End of Switch 220 } // end of "if non-zero button" 221 222} // end of Void Loop 223 224void ScoreUp(int player) // Routine to score up a point, check everything 225{ 226 int otherplayer; 227 if (player ==1) {otherplayer = 0;} else otherplayer = 1; 228 Score[player]++; // Give the player a point 229 DisplayScore(); // Show current score 230 if ((Score[player] >= ScoreTo) && (Score[player] - Score[otherplayer] > 1)) // You have to win by 2 points 231 {GameOver(player); // was it a win? 232 } else 233 if ((Score[Player1] + Score[Player2]) % ServeTo == 0){ 234 Player1isServing = !Player1isServing; 235 PlaySound(SoundChangeServe); 236 DisplayServe(Player1isServing); 237 } 238 239} // end of ScoreUp 240 241void DisplayScore(){ // Update displays for score in the game 242 // Show player1 score on the left and player 2 on the right 243 if (Score[Player1] > 9){ matrix.writeDigitNum(0,Score[Player1]/10);} else matrix.writeDigitRaw(0,0); // use leading blank not zero 244 matrix.writeDigitNum(1,Score[Player1]%10); 245 if (Score[Player2] > 9){ matrix.writeDigitNum(3,Score[Player2]/10);} else matrix.writeDigitRaw(3,0); 246 matrix.writeDigitNum(4,Score[Player2]%10); 247 matrix.drawColon(false); // no colon 248 matrix.writeDisplay(); // and push out the LED print 249} // end of DisplayScore 250 251void DisplayGames(){ // Update displays for games in the match 252 if (Games[Player1] > 9 ) {matrix1.writeDigitNum(0,Games[Player1]/10);} else matrix1.writeDigitRaw(0,0); 253 matrix1.writeDigitNum(1,Games[Player1]%10); 254 if (Games[Player2] > 9 ) {matrix1.writeDigitNum(3,Games[Player2]/10);} else matrix1.writeDigitRaw(0,0); 255 matrix1.writeDigitNum(4,Games[Player2]%10); 256 matrix1.drawColon(false); // no colon 257 matrix1.writeDisplay(); // and push out the LED print 258} // end of DisplayGames 259 260void DisplayServe(bool firstplayer){ // Update displays for who is serving 261 int colort; 262 if (firstplayer) {colort = Green;} else colort = Yellow; // change color for each player 263 mclear(Line2); // Clear display lines on 16 x 32 panel 264 PrintPanel("Serve", Line1, colort); 265 Arrows(firstplayer,Line2,colort); // shows to the left (Player1) or right (Player2) 266 // depending on boolean flag 267} // end of DisplayServe 268 269 270void PlaySound(int WhichSound){ // Plays appropriate Sound file 271 SoundBusy = true; // Are we already playing a sound? 272 while (SoundBusy){ // Then wait til its done 273 SoundBusyValue = digitalRead(SoundBusyPin); // as indicated by ACT going High again 274 if (SoundBusyValue == LOW) { 275 delay(SoundBusyDelay); // wait on the soundboard 276 } else 277 SoundBusy = false;} 278 digitalWrite(FirstSoundPin+WhichSound, LOW); //Play the sound 279 delay(fxset); // hold to start the sound 280 digitalWrite(FirstSoundPin+WhichSound, HIGH); //Turn off the sound 281 delay(fxset); // Ensure its set 282} // end of PlaySound 283 284 285void GameOver(int player){ // Someone has won the game 286 int gocolor; 287 mclear(Line1); 288 mclear(Line2); 289 Games[player]++; 290 DisplayGames(); 291 PlaySound(SoundGameOver); 292 Flash ("Game", "Over!"); 293 if (player == Player1) { 294 if (!JI) {PlaySound(SoundWinnerIs1); 295 ScrollText2("Player 1", "Wins ", Yellow, Yellow);} else {PlaySound(SoundWinnerIsJoan); 296 Flash("Joan", "Wins!");} 297 Player1isServing = false;} else 298 { 299 if (!JI) {PlaySound(SoundWinnerIs2); 300 ScrollText2("Player 2", "Wins ", Yellow, Yellow);} else {PlaySound(SoundWinnerIsIrwin); 301 Flash("Irwin", "Wins!");} 302 Player1isServing = true;} 303 Score[Player1] =0; // reset scores and display 304 Score[Player2] = 0; 305 DisplayScore(); 306 if (player == Player1){ 307 if (!JI) {ScrollText2("Player 2", "Serve", Yellow, Yellow);} 308 else ScrollText2("Irwin", "Serve", Yellow, Yellow);} 309 else if (!JI) {ScrollText2("Player 1", "Serve", Yellow, Yellow);} else ScrollText2("Joan", "Serve", Yellow, Yellow); 310 DisplayGameNumber(); // Game number called out 311 PlaySound(SoundNewGame); 312 DisplayServe(Player1isServing); 313} // end of Game Over 314 315byte check_switches() // This will return pin pressed and includes a debounce 316{ 317 318 int reading; 319 int button_pressed = 0; 320 int index; 321 322 for (index = 0; index < numbuttons; index++) { 323 reading = digitalRead(firstbuttonpin + index); 324 if (reading == LOW && lastreading[index] == HIGH) // switch pressed? 325 {button_pressed = index + 1; // calculate return number 326 delay(DEBOUNCE); // debounce a little 327 break; // end "for" loop 328 } 329 lastreading[index] = reading; //update reading 330 } 331 // return switch number (1 - numbuttons) 332 return button_pressed; 333} // end of Check Switches 334 335void Bounce(int bouncetimes) 336{ 337 int x; 338 int y; 339 int jj; 340 matrixp.fillScreen(Black); // clear the screen 341 matrixp.fillRect(2,15,28,1,Green); // draw the table 342 matrixp.fillRect(15,9,1,6,White); // and the net 343 for (int i = 0; i <=bouncetimes; i++) 344 { jj = -1; // reset y index (will increment to 0, first element) 345 for (int j = firstx; j <=lastx; j++) 346 { jj++; // increment y coordinate index 347 x = j; // bounce left to right 348 y = bouncey[jj]; 349 matrixp.fillCircle(x,y,ballsize,Yellow); 350 if (jj == 0){ // display the paddle 351 matrixp.fillRect(0,0,2,6,Green); 352 delay(bouncedelay); 353 } 354 delay(bouncedelay); 355 matrixp.fillCircle(x,y,ballsize,Black); 356 delay(bouncedelay); 357 matrixp.fillRect(0,0,2,6,Black); 358 } 359 jj = bouncesize+1; 360 for (int j = lastx; j >=firstx; j--) // same except in reverse 361 { jj-- ; 362 x = j; 363 y = bouncey[jj]; 364 matrixp.fillCircle(x,y,ballsize,Yellow); 365 delay(bouncedelay); 366 if (jj == bouncesize){ // display the paddle 367 matrixp.fillRect(30,0,2,6,Green); 368 delay(bouncedelay * 3); 369 } 370 matrixp.fillCircle(x,y,ballsize,Black); 371 delay(bouncedelay * 3); 372 matrixp.fillRect(30,0,2,6,Black); 373 } 374} 375} // end of Bounce 376 377void PrintPanel(String ToPrint, int pline, int pcolort) 378{ 379 if (pline == Line1) {matrixp.setCursor(1,0);} else matrixp.setCursor(1,mh/2); 380 mclear(pline); // clear the line out 381 matrixp.setTextColor(pcolort); // set the color 382 matrixp.setTextWrap(true); // shouldn't matter 383 matrixp.print(ToPrint); // print the line 384} // end of PrintPanel 385 386 387void ScrollText(String ToScroll, int pline, int pcolort) { 388 int yaxis; 389 int pixelx = mw; // start scroll at 'right end' 390 if (pline == 0) {yaxis = 0;} else yaxis = mh/2; // y axis varies with line 0 or 1 so set it 391 matrixp.setTextWrap(false); // we don't wrap text so it scrolls nicely 392 int numchars = ToScroll.length(); // number of characters in string 393 for (int ff = 0; ff <= cw*numchars; ff++) { // start appropriately and scroll left 394 matrixp.setCursor(pixelx,yaxis); 395 pixelx-- ; 396 mclear(pline); // clear the line 397 matrixp.setTextColor(pcolort); // set the color 398 matrixp.print(ToScroll); // and print (once for each pixel) 399 delay(scrolldelayc); 400 } 401 delay(afterscrolldelay); 402} // end of display_scrollText 403 404void ScrollText2(String ToScroll1, String ToScroll2, int pcolort1,int pcolort2) { 405 // scrolls two strings at once in parallel 406 int pixelx = mw; // start at right hand side 407 int pixelcount; // will be total (longer) pixel count 408 int pixelcount1; // counter for string 1 409 int pixelcount2; // counter for string 2 410 matrixp.setTextWrap(false); // we don't wrap text so it scrolls nicely 411 pixelcount1 = ToScroll1.length()*cw; // number of characters in line 0 string converted to pixels 412 pixelcount2 = ToScroll2.length()*cw; // number of characters in line 1 string converted to pixels 413 if (pixelcount2 > pixelcount1) {pixelcount = pixelcount2;} else pixelcount = pixelcount1; // set for longer of two strings 414 415 for (int ff = 0; ff <= pixelcount; ff++) { // start appropriately and scroll left (longer of two strings) 416 if (pixelcount1 >= 0) { // Print top line 417 mclear(0); // clear the line 418 matrixp.setCursor(pixelx,0); // position 419 matrixp.setTextColor(pcolort1); // set the color 420 matrixp.print(ToScroll1); // and print (once for each pixel ) 421 pixelcount1--;} // and decrement 422 if (pixelcount2 >= 0) { // Print 2nd line (one may finish before the other!) 423 mclear(1); // clear the line 424 matrixp.setCursor(pixelx,mh/2); // reposition 425 matrixp.setTextColor(pcolort2); // set the color 426 matrixp.print(ToScroll2); // and print (once for each pixel ) 427 pixelcount2--;} // and decrement 428 pixelx-- ; 429 delay(scrolldelayc); 430 } 431 delay(afterscrolldelay); 432} // end of ScrollText2 433 434void mclear(int pline) { // quick clear of either line 0 or line 1 435 int xc; 436 int yc; 437 if (pline == 0) {yc = 0;} else yc = mh/2; 438 matrixp.fillRect(0,yc,mw,mh/2,Black); 439} // end of mclear 440 441void Arrows(bool left, int pline, int pcolort) { 442 int yaxis; // y axis varies with line 0 or 1 443 if (pline == 0) {yaxis = 0;} else yaxis = mh/2; // so set it 444 if (left) { // called with player1 true or not (i.e., player2) 445 matrixp.fillTriangle(1,4+yaxis,4,1+yaxis,4, 7+yaxis, pcolort); // arrows point right or left indicating 446 matrixp.fillRect(5,3+yaxis,6,3,pcolort); // who is serving 447 matrixp.fillTriangle(12,4+yaxis,15,1+yaxis,15, 7+yaxis, pcolort); 448 matrixp.fillRect(16,3+yaxis,6,3,pcolort); 449 matrixp.fillTriangle(23,4+yaxis,26,1+yaxis,26, 7+yaxis, pcolort); 450 matrixp.fillRect(27,3+yaxis,6,3,pcolort); } else 451 { 452 matrixp.fillTriangle(30,4+yaxis,27,1+yaxis,27, 7+yaxis, pcolort); 453 matrixp.fillRect(21,3+yaxis,6,3,pcolort); 454 matrixp.fillTriangle(19,4+yaxis,16,1+yaxis,16, 7+yaxis, pcolort); 455 matrixp.fillRect(10,3+yaxis,6,3,pcolort); 456 matrixp.fillTriangle(8,4+yaxis,5,1+yaxis,5, 7+yaxis, pcolort); 457 matrixp.fillRect(0,3+yaxis,5,3,pcolort); 458 } 459 460} // End of Arrows 461 462void Flash (String ToFlash1, String ToFlash2){ 463 const int flashcolorsize = 9; 464 int flashcolors[flashcolorsize] = {Green, Cyan, Yellow, Magenta, Red, Blue,Green, Red, White}; 465 for (int i = 0; i < flashcolorsize; i++){ 466 matrixp.fillScreen (Black); 467 matrixp.setCursor(0,0); // reposition 468 matrixp.setTextColor(flashcolors[i]); // set the color 469 matrixp.print(ToFlash1); // and print (once for each pixel ) 470 matrixp.setCursor(0,mh/2); // reposition 471 matrixp.print(ToFlash2); // and print (once for each pixel ) 472 delay(flashdelay); // wait after flash 473 } 474} // end of Flash 475 476void BeginMatch(){ 477 DisplayScore(); // show LED Score 478 DisplayGames(); // for game and games/match 479 PlaySound(SoundStartup); // Play startup sound on soundcard 480 Bounce(1); // Play the cute startup graphic 481 PlaySound(SoundLetsPlay); 482 ScrollText2("Let's Ping ","Play Pong ", Yellow, Yellow); // and little warmup 483 PlaySound(SoundNewGame); // New game sound 484 DisplayGameNumber(); // Should be "Game #1" 485 DisplayServe(Player1isServing); // and give initial serve indication (play 1 or 2) 486} // End of Begin Match 487 488 void DisplaySetandServe(){ 489 if (digitalRead(ScoreToSwitch) == HIGH){ScoreTo = 21;} else ScoreTo = 11 ; // Check settings for Score and Serve 490 if (digitalRead(ServeToSwitch) == HIGH){ServeTo = 5;} else ServeTo = 2 ; // in case it changes 491 if (digitalRead(JISwitch) == HIGH){JI = true;} else JI = false ; // Convenient to read JI switch too 492 lcd.setCursor(0, 0); // set LCD column 0, row 0 493 lcd.print("Score of " ); 494 lcd.print(ScoreTo); // print either 21 or 11 495 lcd.print(" wins" ); 496 lcd.setCursor(0, 1); // set LCD column 0, row 1 497 lcd.print("Service:"); 498 lcd.print(ServeTo); // print either 5 or 2 499 lcd.print(" "); 500 if (JI) {lcd.print("J&I ");} else lcd.print("P1/P2"); // indicate whether the Joan & Irwin over-ride is in place or Player 1 and 2 501} // end of Display Set and Serve 502 503void DisplayGameNumber(){ 504 PrintPanel("Game", Line1, Green); 505 mclear(1); 506 matrixp.setCursor((mw/2)-3,mh/2); // 2nd line is which game we are on 507 matrixp.print("# "); 508 matrixp.setCursor((mw/2)+4,mh/2); 509 matrixp.print(Games[0]+Games[1]+1,DEC ); // print the line 510 delay(delaygamenumber); 511} 512
Downloadable files
Ping Pong Scoreboard Connections
All the connections used
Ping Pong Scoreboard Connections
Comments
Only logged in users can leave comments