IR Remote Controlled Scoreboard :: Member Project
Arduino and Bean Bag Baseball? Yes – match made in heaven!
PEA customer, John Wren, built an IR Remote controlled Arduino powered scoreboard for his senior community bean bag baseball team. It includes tons of NEO pixels and looks great. Learn more about his project below!
Why did you build an Arduino scoreboard?
I live in a senior community and we play bean bag baseball. We keep score on a whiteboard. I thought it would be nice to have a more robust way of keeping score and I thought I might be able to sell some of the scoreboards if it works out the way I plan.
I recently retired from the industrial automation field and was bored and looking for a project, so I started to learn Arduino coding.
How does your project work?
The brains of the scoreboard uses an Arduino Mega. I selected the Mega because originally I was going to use buttons to control the score board.
Currently the scoreboard is controlled by a GE IR remote using NEC codes. The scoreboard has a score for each inning both for home team and visiting team and a total at the end.
The scores are 7 segment displays using neopixels driven from a single output pin from the Arduino.
What was your biggest struggle as you worked through this project?
I am fairly new to micro-controllers, my background is PLCs and HMIs my thought process didn’t match that of the infinite loop of the Arduino. I was able to learn about hardware interrupts (pin 2 interrupt 0 ) and switch case. My current code does not use the loop.
I found that a loop was necessary to make the compiler happy so the loop is there but empty. I have coded in many different languages in the past. When I went to school we programmed in machine language. C++ is new to me and I had a steep learning curve. some examples =vs==, missing{} and ;.
It has been a challenging but fun process!
The project is not yet finished but is coming out close to my original concept. So far the only major change is the interface. Using the IR remote as opposed to buttons.
Was the training at Programming Electronics Academy able to help you build your skill?
Yes!! I truly enjoy the presentation style. I always had questions after the lessons and tested alternate ways of the exercises.
About John:
John has been into electronics since he was in highschool, having picked up some experience from working in his father’s shop (who was a TV repairman). He is currently retired and spent a career working and teaching in the automation field for close to 40 years. He started in saw mills and has worked for independent engineering firms. Most of his career has been in the food manufacturing industry, having done work for companies such as Mars, Starbucks, E&J Gallo and a Micro Brewery in Delaware (Dogfish Head).
Arduino Code:
/* Program written by John wren (Wrengineering) 6/29/2020 This is designed to control a 6 Inning Beanbag baseball scoreboard via IR remote control using a Pure Control universal remotemdlPC 100 and RCA code 1033 remote uses 32 bits in 2 groups of 16 in each group of 16 the lower group of 8 is the inverse of the previous 8 so i utlized loByte to just use the value of the remote button that was pressed The first 16 bits identify the contrller and the second contain the code for the button presed this is sent in as a HEX value I converted lowByte to a BIN and used it to compare for Switch Case It will keep track of strikes, outs, men on base, runs/Inning and toal score Utilizes Interupt 0 on Pin 2 to decode IR remote signals Uses RGB led strips configured as seven segment displays for runs and total score Also uses LED pixels for strikes,outs and men on base Changed Seven_Segment_Pixel.h to match the order of segments on my display, in order tp make wiring simpler Progress as of 7/7/2020 strike increment and decrement. outs increment and decrement. Innings increment and decrement. Able to keep individual score based on Inning number Need to add score decrement. Have only tested with 2 7 seven secment LEDs (need to solder) with 16 7 segmet displays all set to the number 8 with a single color at full brightness the FLA would be 5.6 20mA * (21 LEds per secment * 16 total Sements) the dual Power supply has 6A at 5 volts and 3A at 12 volts at game start all scores set to zero FLa 5.28 I will lower the intensity and that should lower the current draw. Measure current with all scores This program will use a IR remote to control Strikes, Outs,Score, at bat team and inning. The operator will be able to increment and decrement each value witht the remote. (see remote diagram for button info) Currently using an Arduino but may switch to ESP 32 to add vioce and speed ***********************************TO DO NOTES********************************** This is where I kept notes of what need to be completed or fixed need to clear outs and strikes whith inning + or - */ //***************************************************************************************** #include <IRremote.h> #include <Adafruit_NeoPixel.h> #include <Seven_Segment_Pixel.h> //define pixel strip peramaters #define PIN 36 #define NUMPIXELS 112 #define DIGITS 16 #define PIXPERSEG 1 #define NUMDELIMS 0 #define PIXPERDELIM 0 #define PINSO 35//STRIKE AND OUT PIXELS #define NUMPIXELSSO 11//STRIKE, OUTS AND ON BASE PIXELS //Score display 7 segment digits Seven_Segment_Pixel display1 = Seven_Segment_Pixel(DIGITS, PIXPERSEG, NUMDELIMS, PIXPERDELIM, NUMPIXELS, PIN, NEO_BRG + NEO_KHZ800); //************************************************************************ //Strike and Out Pixels Adafruit_NeoPixel pixels = Adafruit_NeoPixel(NUMPIXELSSO, PINSO, NEO_GRB + NEO_KHZ800); int RECV_PIN = 2;// IR input interupt 0 IRrecv irrecv(RECV_PIN); decode_results results; //************************************************************************************ //define Golbal variables int Strikes = 0; int Outs = 0; int Inning = 1; int Score = 0; int gScore[7] {0, 0, 0, 0, 0, 0, 0,}; int hScore [7] {0, 0, 0, 0, 0, 0, 0,}; int gScoreTotal = 0 ; int hScoreTotal = 0; int gScoreTotalOnes = 0 ; int hScoreTotalOnes = 0; int gScoreTotalTens = 0 ; int hScoreTotalTens = 0; int digitIn[7] {0, 1, 2, 3, 4, 5, 6}; int runrR = 255; int runrG = 0; int runrB = 0; byte lowbyte = 0; byte gColor = 25; byte hColor = 25; bool atBat = 0; bool On1st = 0; bool On2nd = 0; bool On3rd = 0; //*************************************************************************************** void setup() { Serial.begin(9600);// rem this and all Sel.prints out after testing //Starts Out and strike Pixels****************************************** pixels.begin(); //turns off all Strike LEDs //**************************************** pixels.setPixelColor(0, 0, 0, 0); pixels.setPixelColor(1, 0, 0, 0); pixels.setPixelColor(2, 0, 0, 0); //*************************************** //turns off all out LEDs pixels.setPixelColor(3, 0, 0, 0); pixels.setPixelColor(4, 0, 0, 0); pixels.setPixelColor(5, 0, 0, 0); //******************************************** //turns off allon base LEDs pixels.setPixelColor(6, 0, 0, 0); pixels.setPixelColor(7, 0, 0, 0); pixels.setPixelColor(8, 0, 0, 0); pixels.setPixelColor(6, 0, 255, 0);//turns on Guest at bat LED pixels.show(); //**********Starts looking for IR signal on PIN 2******************************** attachInterrupt(0, CHECK_IR, CHANGE); //Enables interupt and looks for value change(IR xmit button pushed) irrecv.enableIRIn(); // Start the receiver and test to see if it is operational //********************************************************************************** //Turns on score digits and sets then to dim zero display1.begin();//Starts pixel display // Score Digits //****************************R G B //*****************Digit Value Color R,G,B display1.updateDigit (8, 0 , (gColor * 5), 0 , 0 ); display1.updateDigit (7, 0 , (gColor * 5), 0 , 0 ); display1.updateDigit( 1, 0 , (gColor * 10), 0 , 0 ); display1.updateDigit( 2, 0 , gColor, 0 , 0 ); display1.updateDigit( 3, 0 , gColor, 0 , 0 ); display1.updateDigit( 4, 0 , gColor, 0 , 0 ); display1.updateDigit( 5, 0 , gColor, 0 , 0 ); display1.updateDigit( 6, 0 , gColor, 0 , 0 ); display1.updateDigit (16, 0 , 0, (hColor * 5) , 0 ); display1.updateDigit (15, 0 , 0, (hColor * 5) , 0 ); display1.updateDigit( 9, 0 , 0, hColor , 0 ); display1.updateDigit( 10, 0 , 0, hColor , 0 ); display1.updateDigit( 11, 0 , 0, hColor , 0 ); display1.updateDigit( 12, 0 , 0, hColor , 0 ); display1.updateDigit( 13, 0 , 0, hColor , 0 ); display1.updateDigit( 14, 0 , 0, hColor , 0 ); display1.show(); }//Setup //********************Loop Not Used*********************************************** void loop() {} //******************************************************************************* //Start of Interupt Switch Case from IR Xmit //***************checks to see if IR ewciver has data and pares it down to a byte value byte loByte = 0; void CHECK_IR() { while (irrecv.decode(&results)) { byte loByte = lowByte(results.value);// Switch case irrecv.resume(); delay(20); //************************************************************** //Serial.print Area //***************************************************************** // sets where the switch gets its value loByte is the low byte value from IR xmitter switch (loByte) { //**************************************************************** // Case0 Strike + Case Increases strike count updates display case 147: //147 is the low byte value from IR xmiter when vol + button is pressed if (Strikes < 4) { Strikes = (Strikes + 1); // increases the number of strike by one } if (Strikes == 4) { Strikes = 0;// reset strike count pixels.setPixelColor(0, 0, 0, 0); pixels.setPixelColor(1, 0, 0, 0); pixels.setPixelColor(2, 0, 0, 0); pixels.show(); if (Outs < 3) { Outs = (Outs + 1); //Turns on Out LEDs if (Outs != 0) { pixels.setPixelColor((6 - Outs), 0, 255, 0);// 6 - because of pixel numbering pixels.show(); } } } //Turns on strike LEDs pixels.setPixelColor((Strikes - 1), 125, 255, 0); pixels.show(); irrecv.resume(); break; //*************************************************************************** //Case 1 Strike - Decreases strike count and updates display case 234: //Serial.println("Vol down "); //234 is the low byte value from IR xmiter when vol - button is pressed if (Strikes > 0) { Strikes = (Strikes - 1); // decreases the nuber of strike when button is pushed } //tuns off current strike pixel pixels.setPixelColor((Strikes), 0, 0, 0); pixels.show(); irrecv.resume(); break; //*********************************************************************** //Case 2 Outs + Increases outs count updates display case 39: // 39 is the low byte value from IR xmiter when Ch + button is pressed if (Outs < 4) { Outs = (Outs + 1); // increases the number of outs by one Strikes = 0; //turns off strikes and lights next out LED pixels.setPixelColor(0, 0, 0, 0); pixels.setPixelColor(1, 0, 0, 0); pixels.setPixelColor(2, 0, 0, 0); if ((Outs == 1) || (Outs == 2) || (Outs == 3)) { pixels.setPixelColor((6 - Outs ), 0, 255, 0);///?this line causes strike 3 to turn on when outs are reset pixels.show(); } if (Outs == 4) { Outs = 0;// reset Outs //clears base runner LEDs pixels.setPixelColor(8, 0, 0, 0); pixels.setPixelColor(9, 0, 0, 0); pixels.setPixelColor(10, 0, 0, 0); pixels.show(); if (atBat == 1) { Inning = Inning + 1; // increases Inning at end of Inning digitIn[Inning] = (digitIn[Inning]); } //Set out pixels to off pixels.setPixelColor(3, 0, 0, 0); pixels.setPixelColor(4, 0, 0, 0); pixels.setPixelColor(5, 0, 0, 0); pixels.show(); // switch base runner color to guest team if (atBat == 0) { (atBat = 1); (runrG = 255); (runrR = 0); (runrB = 0); pixels.setPixelColor(7, 255, 0, 0);//Home at bat LED pixels.setPixelColor(6, 0, 0, 0);//Gueds at bat LED pixels.show(); display1.updateDigit ((Inning + 8), 0 , 0, (hColor * 10) , 0 ); display1.show(); } // switch colors to home team else { (atBat = 0); (runrG = 0); (runrR = 255); (runrB = 0); pixels.setPixelColor(7, 0, 0, 0);//Home at bat LED pixels.setPixelColor(6, 0, 255, 0);//Guest at bat LED pixels.show(); display1.updateDigit (Inning, 0 , (gColor * 10), 0 , 0 ); display1.show(); } } } irrecv.resume(); break; //***************************************************************** //Case 3 Outs - Decreases outs count updates display case 110: // 110 is the low byte value from IR xmiter when CH - button is pressed if (Outs > 0) { Outs = (Outs - 1); // decreases the nuber of strike when button is pushed } //tuns off current Outs pixel pixels.setPixelColor((5 - Outs), 0, 0, 0); pixels.show(); irrecv.resume(); break; //**************************************************************** //Case 4 score + Increases Score count, updates LED display case 233: //233 is the low byte value from IR xmiter when #7 button is pressed if (atBat == 0) { gScore[Inning] = (gScore[Inning] + 1);// increases guest score by one if (gScore[Inning] > 9) { gScore[Inning] = 9; } //Adds total Guest Score together and seperate into two digits fpr 1s and 10s gScoreTotal = gScore[1] + gScore[2] + gScore[3] + gScore[4] + gScore[5] + gScore[6]; gScoreTotalOnes = ( gScoreTotal % 10); gScoreTotalTens = ( gScoreTotal / 10); display1.updateDigit (Inning, (gScore[Inning]) , (gColor * 10), 0 , 0 ); display1.updateDigit (8, (gScoreTotalOnes) , 255, 0 , 0 ); display1.updateDigit(7, (gScoreTotalTens) , 255, 0 , 0 ); display1.show(); } else if (atBat == 1) { hScore[Inning] = (hScore[Inning] + 1); if (hScore[Inning] > 9) { hScore[Inning] = 9; } //Adds total Home Score together and seperate into two digits fpr 1s and 10s hScoreTotal = hScore[1] + hScore[2] + hScore[3] + hScore[4] + hScore[5] + hScore[6]; hScoreTotalOnes = ( hScoreTotal % 10); hScoreTotalTens = ( hScoreTotal / 10); display1.updateDigit (16, (hScoreTotalOnes) , 0, 255 , 0 ); display1.updateDigit (15, (hScoreTotalTens) , 0, 255 , 0 ); display1.updateDigit ((Inning + 8), (hScore[Inning]) , 0, (hColor * 10) , 0 ); display1.show(); } irrecv.resume(); break; //******************************************************************** //Case 5 Score - Decreases Score count updates display case 225: // 225 is the low byte value from IR xmiter when #9 button is pressed if (atBat == 0) { gScore[Inning] = (gScore[Inning] - 1);// increases guest score by one if (gScore[Inning] < 1) { gScore[Inning] = 0; } //Adds total Guest Score together and seperate into two digits fpr 1s and 10s gScoreTotal = gScore[1] + gScore[2] + gScore[3] + gScore[4] + gScore[5] + gScore[6]; gScoreTotalOnes = ( gScoreTotal % 10); gScoreTotalTens = ( gScoreTotal / 10); display1.updateDigit (8, (gScoreTotalOnes) , 255, 0 , 0 ); display1.updateDigit(7, (gScoreTotalTens) , 255, 0 , 0 ); display1.updateDigit ((Inning ), (gScore[Inning]) , (gColor * 10), 0 , 0 ); // current inning score display1.show(); } else if (atBat == 1) { hScore[Inning] = (hScore[Inning] - 1); if (hScore[Inning] < 1) { hScore[Inning] = 0; } //Adds total Home Score together and seperate into two digits fpr 1s and 10s hScoreTotal = hScore[1] + hScore[2] + hScore[3] + hScore[4] + hScore[5] + hScore[6]; hScoreTotalOnes = ( hScoreTotal % 10); hScoreTotalTens = ( hScoreTotal / 10); display1.updateDigit (16, (hScoreTotalOnes) , 0, 255 , 0 ); display1.updateDigit (15, (hScoreTotalTens) , 0, 255 , 0 ); display1.updateDigit ((Inning + 8), (hScore[Inning]) , 0, (hColor * 10) , 0 ); display1.show(); } irrecv.resume(); break; //************************************************************************** //Case 6 Inning +Increases Inning count updates display case 85: //85 is the low byte value from IR xmiter when number one button is pressed if ((Inning < 6) && (atBat == 1)) { Inning = (Inning + 1); // increases the number of Inning by one digitIn[Inning] = (digitIn[Inning]); atBat = 0; pixels.setPixelColor(7, 0, 0, 0);//Home at bat LED pixels.setPixelColor(6, 0, 255, 0);//Guest at bat LED pixels.show(); display1.updateDigit (Inning, (gScore[Inning]) , (gColor * 10), 0 , 0 ); display1.show(); (runrG = 0);//colors for base runners (runrR = 255); (runrB = 0); } else { atBat = 1; display1.updateDigit (Inning, (gScore[Inning]) , (gColor * 10), 0 , 0 ); pixels.setPixelColor(7, 255, 0, 0);//Home at bat LED pixels.setPixelColor(6, 0, 0, 0);//Guest at bat LED pixels.show(); display1.updateDigit (Inning, (gScore[Inning]) , (gColor * 10), 0 , 0 ); display1.updateDigit ((Inning + 8), (hScore[Inning]) , 0, (hColor * 10) , 0 ); display1.show(); (runrG = 255);//colors for base runners (runrR = 0); (runrB = 0); } if (Inning >= 6) { (Inning = 6); } //turns off all Strike LEDs //**************************************** pixels.setPixelColor(0, 0, 0, 0); pixels.setPixelColor(1, 0, 0, 0); pixels.setPixelColor(2, 0, 0, 0); //*************************************** //turns off all out LEDs pixels.setPixelColor(3, 0, 0, 0); pixels.setPixelColor(4, 0, 0, 0); pixels.setPixelColor(5, 0, 0, 0); //******************************************** //clears base runner LEDs pixels.setPixelColor(8, 0, 0, 0); pixels.setPixelColor(9, 0, 0, 0); pixels.setPixelColor(10, 0, 0, 0); pixels.show(); Outs = 0; Strikes = 0; irrecv.resume(); break; //******************************************************************** //Case 7 Inning -Decreases Inning count updates display case 185://185 is the low byte value from IR xmiter when number three button is pressed if ((Inning > 0) && (atBat == 1)) { atBat = 0; display1.updateDigit ((Inning + 8), (hScore[Inning]) , 0, (hColor ) , 0 ); display1.show(); } else { atBat = 1; display1.updateDigit ((Inning ), (gScore[Inning]) , (gColor ), 0 , 0 ); display1.show(); Inning = (Inning - 1); } if (Inning <= 1) { (Inning = 1); } //turns off all Strike LEDs //**************************************** pixels.setPixelColor(0, 0, 0, 0); pixels.setPixelColor(1, 0, 0, 0); pixels.setPixelColor(2, 0, 0, 0); //*************************************** //turns off all out LEDs pixels.setPixelColor(3, 0, 0, 0); pixels.setPixelColor(4, 0, 0, 0); pixels.setPixelColor(5, 0, 0, 0); //******************************************** //clears base runner LEDs pixels.setPixelColor(8, 0, 0, 0); pixels.setPixelColor(9, 0, 0, 0); pixels.setPixelColor(10, 0, 0, 0); pixels.show(); Outs = 0; Strikes = 0; irrecv.resume(); break; //******************************************************************************** //remove this and add switch on score board to protect aginist accedental resets //Case 7 reset everything case 37:// 37 is the low byte value from IR xmiter when CC button is pressed //use arrars to do this //Set all Score Digits to 0 and dim display1.updateDigit (1, 0 , 20, 0, 0); display1.updateDigit (2, 0 , 20, 0, 0); display1.updateDigit (3, 0 , 20, 0, 0); display1.updateDigit (4, 0 , 0 , 20, 0); display1.updateDigit (5, 0 , 0, 20, 0); display1.updateDigit (6, 0 , 0, 20, 0); display1.updateDigit (7, 0 , 20, 0, 0); display1.updateDigit (8, 0 , 20, 0, 0); display1.updateDigit (9, 0 , 20, 0, 0); display1.updateDigit (10, 0 , 20, 0, 0); display1.updateDigit (11, 0 , 20, 0, 0); display1.updateDigit (12, 0 , 20, 0, 0); display1.updateDigit (13, 0 , 20, 0, 0); display1.updateDigit (14, 0 , 20, 0, 0); display1.updateDigit (15, 0 , 0, 20, 0); display1.updateDigit (16, 0 , 0, 20, 0); display1.show(); //Reset Strikes, outs and on Base pixels.setPixelColor(0, 0, 0, 0); pixels.setPixelColor(1, 0, 0, 0); pixels.setPixelColor(2, 0, 0, 0); pixels.setPixelColor(3, 0, 0, 0); pixels.setPixelColor(4, 0, 0, 0); pixels.setPixelColor(5, 0, 0, 0); pixels.setPixelColor(6, 0, 0, 0); pixels.setPixelColor(7, 0, 0, 0); pixels.setPixelColor(8, 0, 0, 0); pixels.show(); //Set all score to zero gScore[1] = 0; hScore[1] = 0; gScore[2] = 0; hScore[2] = 0; gScore[3] = 0; hScore[3] = 0; gScore[4] = 0; hScore[4] = 0; gScore[5] = 0; hScore[5] = 0; gScore[6] = 0; hScore[6] = 0; atBat = 0; Inning = 1; Outs = 0; Strikes = 0; // Serial.print(Inning); // Serial.print(atBat); // Serial.print(Inning); irrecv.resume(); break; case 89: //man on 1st Right Arrow Button pressed if (On1st == 0) { On1st = 1; pixels.setPixelColor(10, runrG, runrR, runrB); // reset strike count Strikes = 0; pixels.setPixelColor(0, 0, 0, 0); pixels.setPixelColor(1, 0, 0, 0); pixels.setPixelColor(2, 0, 0, 0); pixels.show(); } else { On1st = 0; pixels.setPixelColor(10, 0, 0, 0); pixels.show(); } irrecv.resume(); break; case 129: //man on second Up arrow pressed if (On2nd == 0) { On2nd = 1; //Set strikes to zero if on base Strikes = 0; pixels.setPixelColor(0, 0, 0, 0); pixels.setPixelColor(1, 0, 0, 0); pixels.setPixelColor(2, 0, 0, 0); pixels.show(); pixels.setPixelColor(9, runrG, runrR, runrB); pixels.show(); } else { On2nd = 0; pixels.setPixelColor(9, 0, 0, 0); pixels.show(); } irrecv.resume(); break; case 168: // man om 3rd Left arrow button pressed if (On3rd == 0) { On3rd = 1; //Set strikes to zero if on base Strikes = 0; pixels.setPixelColor(0, 0, 0, 0); pixels.setPixelColor(1, 0, 0, 0); pixels.setPixelColor(2, 0, 0, 0); pixels.setPixelColor(8, runrG, runrR, runrB); pixels.show(); } else { On3rd = 0; pixels.setPixelColor(8, 0, 0, 0); pixels.show(); } irrecv.resume(); break; }//switch loByte //***************************************************************** //restarts IR recive data irrecv.resume(); }//while ir }//Check IR
Awesome work!!
Nice to see how this works besides its complexity, great job!!!