0

I made a scoreboard, and so can you!
Moderators: adafruit_support_bill, adafruit

Please be positive and constructive with your questions and comments.

I made a scoreboard, and so can you!

by Disciple on Wed Dec 26, 2018 4:02 am

Hello and Merry Christmas! It's still Christmas where I live.

Image
I presented my game show LED scoreboard on Show & Tell, and if anyone wants to make one like it, here's what I did.

What I used:Most of my effort went into the code, so the build was unsophisticated. A half-size Perma-Proto board from another project was repurposed. It had some female headers attached to fit a Pro Trinket, and a couple of GPIO pins were extended for connecting to a sensor and NeoPixels, with the recommended resistor and capacitor.
IMG_2735w.jpg
IMG_2735w.jpg (116.97 KiB) Viewed 509 times
I attached little hookup wire extensions to the NeoMatrix, the IR receiver, and the USB cable in order to connect them to extra female sockets on the board. All components get 5V from the USB charger, and 2000mA is not too many when the matrix is at full brightness. GPIO pin 2 receives signal from the IR sensor, and pin 6 sends data to the NeoMatrix.
IMG_2740w.jpg
IMG_2740w.jpg (64.1 KiB) Viewed 512 times
The clear tape was all I used to attach the NeoMatrix to the diffuser sheet. Other types of tape were too visible. The panel was set on a music stand, with the circuit board spring-clamped underneath with a clear line-of-sight to the IR receiver. It could decode signals from 8m away on a sunny day outdoors. Not bad.
IMG_2742w.jpg
IMG_2742w.jpg (49.06 KiB) Viewed 512 times

The game play proceeded like this.
  • Each audience member held a small flag which was used to signal a color choice, yellow or blue.
  • The game had four rounds. Each round would start with a small church skit that would pause at a certain point.
  • The audience was challenged to decide "What would Jesus do?" and was given two possible answers tagged with the two colors.
  • Everyone would indicate their choices with the flags, and numbers would be tallied for each color answer, and posted on the scoreboard.
  • The skit would conclude and the color of the correct answer would get its tally added to the total score.
  • After four rounds, if the total score was greater than the number of audience members playing, victory was declared and prizes awarded (plus a gospel message and prayer). No audience lost the game that day, as intended.
The NeoMatrix scoreboard added an element of glitz that went beyond the traditional dry-wipe scoreboard, and the Pro Trinket sketch that controlled it was this one.
Code: Select all | TOGGLE FULL SIZE
/* FamilyFunScoreboard_ShowTell_006.ino - Detect IR remote codes from DTV clicker using IRLib2.
 *  Display a gameshow-type score with color codes on a NeoMatrix 32x8.
 *  Version 1 - Convert detected remote codes to debounced input.
 *  Version 2 - Replace char array with string object, more functional.
 *  Version 3 - Replace test text with actual tally displays.
 *  Version 4 - Add a display flip function.
 *  Version 5 - Increase 2 color vote tallies to 4 by swapping displayed color pairs.
 *  Version 6 - Add a 0-90 minute countdown timer
 */

#include <Adafruit_GFX.h>
#include <Adafruit_NeoMatrix.h>
#include <Adafruit_NeoPixel.h>

// Parameter 1 = number of pixels in strip
// Parameter 2 = pin number (most are valid)
// Parameter 3 = pixel type flags, add together as needed:
//   NEO_KHZ800  800 KHz bitstream (most NeoPixel products w/WS2812 LEDs)
//   NEO_KHZ400  400 KHz (classic 'v1' (not v2) FLORA pixels, WS2811 drivers)
//   NEO_GRB     Pixels are wired for GRB bitstream (most NeoPixel products)
//   NEO_RGB     Pixels are wired for RGB bitstream (v1 FLORA pixels, not v2)
#define NEOPIN 6
Adafruit_NeoMatrix matrix = Adafruit_NeoMatrix(32, 8, NEOPIN, NEO_MATRIX_TOP + NEO_MATRIX_LEFT + NEO_MATRIX_COLUMNS + NEO_MATRIX_ZIGZAG, NEO_GRB + NEO_KHZ800);

// These are the protocols from IRlib2 needed to decipher signals
// from NEC standard remote controllers like the RC66RX.
// Different remotes may require different components.
// Visit http://tech.cyborg5.com/irlib/ to learn more.
#include <IRLibDecodeBase.h>
#include <IRLib_P01_NEC.h>
#include <IRLib_P10_DirecTV.h>
#include <IRLibCombo.h>     // After all protocols, include this
// All of the above automatically creates a universal decoder
// class called "IRdecode" containing only the protocols you want.
// Now declare an instance of that decoder.
IRdecode myDecoder;

// Include a receiver either this or IRLibRecvPCI or IRLibRecvLoop
#include <IRLibRecv.h>
IRrecv myReceiver(2);  //pin number for the receiver

// These are some of the hex values sent from a RC66RX remote with the satellite TV control switch set.
// Other remotes can be substituted.  Their hex values should replace the values here.
#define RESTART 0xC364
#define JUMP_END 0xC375
#define RIGHT_ARROW   0xC24D
#define LEFT_ARROW    0xC23D
#define SELECT_BUTTON 0xC25E
#define UP_ARROW      0xC21B
#define DOWN_ARROW    0xC22C
#define BUTTON_0 0xC116
#define BUTTON_1 0xC011
#define BUTTON_2 0xC022
#define BUTTON_3 0xC033
#define BUTTON_4 0xC043
#define BUTTON_5 0xC054
#define BUTTON_6 0xC065
#define BUTTON_7 0xC076
#define BUTTON_8 0xC086
#define BUTTON_9 0xC097
#define BUTN_PLY 0xC30F
#define BUTN_REC 0xC353
#define CHANLUP  0xC0DA
#define CHANLDN  0xC0EB
#define BUTTONRD 0xC418
#define BUTTONGR 0xC43A
#define BUTTONYL 0xC429
#define BUTTONBL 0xC44A
#define BUTNDASH 0xC127
#define BTNENTER 0xC138
#define BUTNPREV 0xC0FC
#define BUTNBACK 0xC270
#define BUTNMENU 0xC20A
#define BUTNINFO 0xC2E5

#define BOUNCE_GAP 100
#define FX_DELAY   60
#define ONE_SEC 1000
#define LED_PIN 13

// A string to display as a title to the game, up to 5 chars
#define TITLE_STR "WWJD"

const uint16_t colorTable[4] = {0x7800, 0x03E0, 0x79E0, 0x000F};   // Table of 4 possible color values, 16-bit red green yellow blue
int colorScores[4] = {0, 0, 0, 0};   // Vote tally numbers for each color
String dispString = TITLE_STR;

void setup() {
  pinMode(LED_PIN, OUTPUT);

  matrix.begin(); // Initialize all Neopixels to 'off'
  matrix.setBrightness(20);    // Start with colors at the dimmest

  Serial.begin(38400);
  delay(2000); while (!Serial); //delay for Leonardo
  myReceiver.enableIRIn(); // Start the receiver
}

void loop() {
char typeLetter;
static uint32_t deBounce = 0, newCode, lastCode = 0, effectWait = 0, timeHack = 0, countDown = 0, lastClock = 0;
static int totalScore = 0, keyedVal = 0, keyedSign = 1, activeColor = 0, dispBright = 20, effectStep = 0, effectAdvance = 0, imgFlip = 0;
static boolean keyingInProg = false, animationOn = false, refreshDisplay = true, multiColors = false, swapYB = true, showTimer = false;

  // Continue looping until you receive a complete IR signal
  if(myReceiver.getResults()) {
    if(myDecoder.decode()) {
      newCode = myDecoder.value;  // A remote control click is detected!

      // Debounce and de-repeat RC66RX remote buttons
      if((newCode == lastCode) && (millis() < deBounce)) { // The same code in a very short time?
        newCode = 0;                                       // It's a bounce/repeat.  Clear it.
        deBounce = millis() + BOUNCE_GAP;                  // Restart the time limit.
      } else {
        lastCode = newCode;
        deBounce = millis() + BOUNCE_GAP;                  // Restart the time limit.
        digitalWrite(LED_PIN, HIGH);                       // LED on while we process a button-press
      }

      switch(newCode) {  // A remote button has been pushed.  Which action will happen?
        case RESTART:       refreshDisplay = true; matrix.setRotation(imgFlip ^= 2); break; // Flip the display 180
        case JUMP_END:      refreshDisplay = true; showTellBanner(3); break;  // "As seen on Show & Tell"
        case LEFT_ARROW:    refreshDisplay = multiColors = true; animationOn = false;
                            swapYB = !swapYB; colorRectPush(swapYB); break;  // Animate a swap between showing red/green vs. yellow/blue
        case RIGHT_ARROW:   refreshDisplay = multiColors = true; animationOn = false;
                            swapYB = !swapYB; colorRectPush(swapYB); break;  // Swap again

        case UP_ARROW:      refreshDisplay = true; matrix.setBrightness(dispBright = dispBright > 60 ? 70 : dispBright + 10); break; // Go brighter
        case DOWN_ARROW:    refreshDisplay = true; matrix.setBrightness(dispBright = dispBright < 30 ? 20 : dispBright - 10); break; // Go dimmer

        // These are for keying in a numeric value
        case BUTTON_0:      typeLetter = '0'; break;
        case BUTTON_1:      typeLetter = '1'; break;
        case BUTTON_2:      typeLetter = '2'; break;
        case BUTTON_3:      typeLetter = '3'; break;
        case BUTTON_4:      typeLetter = '4'; break;
        case BUTTON_5:      typeLetter = '5'; break;
        case BUTTON_6:      typeLetter = '6'; break;
        case BUTTON_7:      typeLetter = '7'; break;
        case BUTTON_8:      typeLetter = '8'; break;
        case BUTTON_9:      typeLetter = '9'; break;
        case BUTNPREV:      typeLetter = 'P'; break;
        case BUTNDASH:      typeLetter = 'D'; break;
        case SELECT_BUTTON: typeLetter = 'S'; showTimer = refreshDisplay = true; break; // Finish keyed input for the countdown timer
        case BTNENTER:      typeLetter = 'E'; break; // Finish keyed input for a color vote tally

        case BUTN_PLY:      animationOn = !animationOn; refreshDisplay = true; multiColors = false; break; // Animated highlight of current display
        case BUTN_REC:      colorScores[0] = colorScores[1] = colorScores[2] = colorScores[3] = 0;
                            animationOn = false; multiColors = refreshDisplay = true; break;       // Reset & display color tallies
        case CHANLUP:       dispString = String(totalScore += colorScores[activeColor]); multiColors = false; refreshDisplay = true; break; // Add active color's tally to total score
        case CHANLDN:       dispString = String(totalScore -= colorScores[activeColor]); multiColors = false; refreshDisplay = true; break; // Deduct active color's tally

        case BUTTONRD:      activeColor = 0; colorWipeOut(colorTable[activeColor]);
                            animationOn = swapYB = false; multiColors = refreshDisplay = true; break;  // Change active color to red
        case BUTTONGR:      activeColor = 1; colorWipeOut(colorTable[activeColor]);
                            animationOn = swapYB = false; multiColors = refreshDisplay = true; break;  // Green
        case BUTTONYL:      activeColor = 2; colorWipeOut(colorTable[activeColor]);
                            animationOn = false; swapYB = multiColors = refreshDisplay = true; break;  // Yellow
        case BUTTONBL:      activeColor = 3; colorWipeOut(colorTable[activeColor]);
                            animationOn = false; swapYB = multiColors = refreshDisplay = true; break;  // Blue

        case BUTNBACK:      animationOn = false; multiColors = refreshDisplay = true; break; // Display color tallies
        case BUTNMENU:      dispString = String(TITLE_STR); multiColors = showTimer = false; refreshDisplay = true; break; // Display title text
        case BUTNINFO:      dispString = String(totalScore); multiColors = showTimer = false; refreshDisplay = true; break; // Display total score
        default:            typeLetter = 0;          // Unrecognized input, ignore
      }

      // Manage numeric input
      if((typeLetter >= '0') && (typeLetter <= '9')) {        // Digit pressed?
        if(keyedVal < 3276)                                   // Prevent overflow
          keyedVal = keyedVal * 10 + ((int)typeLetter - '0'); // Make it the next digit in an input number
        keyingInProg = true;
        showTimer = animationOn = false;
        numberInColor(keyedVal * keyedSign, colorTable[activeColor]);  // Display it
      } else if(typeLetter == 'D') {  // Minus key pressed?
        keyedSign *= -1;              // Toggle the sign
        keyingInProg = true;
        showTimer = animationOn = false;
        numberInColor(keyedVal * keyedSign, colorTable[activeColor]);
      } else if(typeLetter == 'P') {  // Previous (backspace) key pressed?
        keyedVal /= 10;               // Knock off the smallest digit
        keyingInProg = true;
        showTimer = animationOn = false;
        numberInColor(keyedVal * keyedSign, colorTable[activeColor]);
      } else if(typeLetter == 'E') {                       // Enter key pressed?
        colorScores[activeColor] = keyedVal * keyedSign;   // Place finished number into color tally array
        keyedVal = 0;
        keyedSign = 1;
        dispString = String(colorScores[activeColor]);
        swapYB = (activeColor > 1);
        keyingInProg = showTimer = animationOn = false;
        refreshDisplay = multiColors = true;
        typeLetter = 0;
      } else if(typeLetter == 'S') {                   // Show/change countdown?
        if(keyedVal * keyedSign > 0) {                 // Positive integer?
          newCode = keyedVal < 9000 ? keyedVal : 9000; // 90 minutes max-ish in a 32-bit variable
          countDown = millis() + (newCode % 100) * ONE_SEC + (newCode / 100) * 60 * ONE_SEC + ONE_SEC;
        }
        keyedVal = 0;
        keyedSign = 1;
        keyingInProg = multiColors = false;
        showTimer = refreshDisplay = true;
        typeLetter = 0;
      } else if(typeLetter != 0) {  // Some other key interrupting?  Abort numeric input
        keyedVal = 0;
        keyedSign = 1;
        keyingInProg = false;
      }

      digitalWrite(LED_PIN,  LOW); // LED off, button-press processing done
    }
    myReceiver.enableIRIn();      // Restart receiver, resume IR listening
  }

  timeHack = millis();
  if(countDown > timeHack) {                     // Countdown clock not expired yet?
    timeHack = (countDown - timeHack) / ONE_SEC; // Compute remaining seconds
    if(timeHack != lastClock) {                  // Has a full second elapsed?
      lastClock = timeHack;
      if(showTimer) {                            // Is countdown currently on display?
        dispString  = String(lastClock / 60);    // Build a string from minutes:seconds remaining
        dispString += String(':');
        dispString += String((lastClock % 60) / 10);
        dispString += String(lastClock % 10);
        refreshDisplay = !animationOn;
      }
    }
  } else
    lastClock = 0;

  if(animationOn && (millis() > effectWait)) { // Animated backdrop in progress with update due?
    effectWait = millis() + FX_DELAY;          // Schedule next update
    ++effectAdvance;

    if(millis() & 2048L) {    // Alternate between 2 patterns
      for(int counter1 = 0; counter1 < 32; ++counter1)
        matrix.drawFastVLine(31 - counter1, 0, 8, colorTable[(effectAdvance + counter1) & 3]); // Marching vertical color stripes
    } else {
      for(int counter1 = 0; counter1 < 8; ++counter1)
        matrix.drawFastHLine(0, 7 - counter1, 32, colorTable[(effectAdvance + counter1) & 3]); // Horizontal stripes
    }

    if(effectAdvance & 14)          // Overprint a flashing text string
      matrix.setTextColor(0xFFFF);  // White on xparent
    else
      matrix.setTextColor(0x0000);  // Black on xparent

    matrix.setTextSize(1);
    matrix.setTextWrap(false);
    matrix.setCursor(16 - dispString.length() * 3, 0); // Center justify, top row
    matrix.print(dispString);
    matrix.show();
    refreshDisplay = false;
  }

  if(refreshDisplay) {   // Shall we repaint the display?
    if(multiColors) {    // with current color tally numbers?
      matrix.setTextColor(0xFFFF);  // Text will be white on xparent
      matrix.setTextSize(1);
      matrix.setTextWrap(false);

      if(swapYB) {       // Are yellow/blue the currently visible pair?
        matrix.fillRect( 0, 0, 16, 8, colorTable[2]);  // Yellow left half BG
        matrix.fillRect(16, 0, 16, 8, colorTable[3]);  // Blue Right half
        dispString = String(colorScores[2]); // Yellow tally
        matrix.setCursor(0, 0);              // Left justified
        matrix.print(dispString);
        dispString = String(colorScores[3]);               // Blue tally
        matrix.setCursor(33 - dispString.length() * 6, 0); // Right justified
        matrix.print(dispString);
      } else {           // Repaint the red/green tally numbers
        matrix.fillRect( 0, 0, 16, 8, colorTable[0]);  // Red left half BG
        matrix.fillRect(16, 0, 16, 8, colorTable[1]);  // Green Right half
        dispString = String(colorScores[0]); // Red tally
        matrix.setCursor(0, 0);              // Left justified
        matrix.print(dispString);
        dispString = String(colorScores[1]);               // Green tally
        matrix.setCursor(33 - dispString.length() * 6, 0); // Right justified
        matrix.print(dispString);
      }
      matrix.show();
      refreshDisplay = false;
      dispString = String(totalScore);
    } else {                        // Show a simple string over black
      matrix.fillScreen(0);         // Black the BG
      matrix.setTextColor(0xFFFF);  // White on xparent
      matrix.setTextSize(1);
      matrix.setTextWrap(false);
      matrix.setCursor(16 - dispString.length() * 3, 0); // Center justified
      matrix.print(dispString);
      matrix.show();
      refreshDisplay = false;
    }
  }
}

void numberInColor(int keyedVal, uint16_t matrixColor) { // Display a decimal number in color over black, left justified
  matrix.fillScreen(0x0000);
  matrix.setTextColor(matrixColor, 0x0000);  // Active color on black
  matrix.setTextSize(1);
  matrix.setTextWrap(false);
  matrix.setCursor(0, 0);
  matrix.print(keyedVal);
  matrix.show();
}

void colorWipeOut(uint16_t matrixColor) { // Two-part motion effect indicating change of active color
int counter1;

  for(counter1 = 0; counter1 < 8; ++counter1) {  // Vertical wipe color in from top
    matrix.drawFastHLine(0, counter1, 32, matrixColor);
    matrix.show();
    delay(10);
  }
  for(counter1 = 0; counter1 < 16; ++counter1) {  // Horiz wipe black from center out
    matrix.drawFastVLine(15 - counter1, 0, 8, 0x0000);
    matrix.drawFastVLine(16 + counter1, 0, 8, 0x0000);
    matrix.show();
    delay(3);
  }
}

void colorRectPush(boolean swapYB) {  // 2 color rectangles push 2 others offscreen
int counter1, colorIndex = 2;

  if(swapYB)
    colorIndex ^= 2;

  if(!swapYB) {
    for(counter1 = 0; counter1 < 32; ++counter1) {  // Left-to-right push
      matrix.fillRect(counter1     , 0, 16, 8, colorTable[colorIndex    ]);  // Departing
      matrix.fillRect(counter1 + 16, 0, 16, 8, colorTable[colorIndex + 1]);  // colors
      matrix.fillRect(counter1 - 31, 0, 16, 8, colorTable[(colorIndex ^ 2)    ]);  // Arriving
      matrix.fillRect(counter1 - 15, 0, 16, 8, colorTable[(colorIndex ^ 2) + 1]);  // colors
      matrix.show();
      delay(1);
    }
  } else {
    for(counter1 = 31; counter1 >= 0; --counter1) {  // Right-to-left push
      matrix.fillRect(counter1 - 31, 0, 16, 8, colorTable[colorIndex    ]);  // Departing
      matrix.fillRect(counter1 - 15, 0, 16, 8, colorTable[colorIndex + 1]);  // colors
      matrix.fillRect(counter1     , 0, 16, 8, colorTable[(colorIndex ^ 2)    ]);  // Arriving
      matrix.fillRect(counter1 + 16, 0, 16, 8, colorTable[(colorIndex ^ 2) + 1]);  // colors
      matrix.show();
      delay(1);
    }
  }
}

void showTellBanner(int pauseTime) {
  int offSet = 152;

  matrix.setTextColor(0x441F);  // Adafruit blue on xparent
  matrix.setTextSize(1);
  matrix.setTextWrap(false);

  for(int counter1 = 32; counter1 > -170; --counter1) { // Draw the Show & Tell icons
    matrix.fillScreen(0x0000);
    matrix.fillRoundRect(counter1         , 0, 11, 7, 2, colorTable[0]);
    matrix.fillRoundRect(counter1 + offSet, 0, 11, 7, 2, colorTable[0]);
    matrix.fillRect(counter1 + 1         , 1, 6, 5, 0xFFFF);
    matrix.fillRect(counter1 + 1 + offSet, 1, 6, 5, 0xFFFF);
    matrix.fillTriangle(counter1 + 7         , 3, counter1 + 9         , 1, counter1 + 9         , 5, 0xFFFF);
    matrix.fillTriangle(counter1 + 7 + offSet, 3, counter1 + 9 + offSet, 1, counter1 + 9 + offSet, 5, 0xFFFF);
    matrix.drawLine(counter1 + 2,          3, counter1 + 5,          3, colorTable[0]);
    matrix.drawLine(counter1 + 2 + offSet, 3, counter1 + 5 + offSet, 3, colorTable[0]);
    matrix.drawLine(counter1 + 4,          2, counter1 + 4,          4, colorTable[0]);
    matrix.drawLine(counter1 + 4 + offSet, 2, counter1 + 4 + offSet, 4, colorTable[0]);
    matrix.drawPixel(counter1 + 3         , 4, colorTable[0]);
    matrix.drawPixel(counter1 + 3 + offSet, 4, colorTable[0]);

    matrix.setCursor(counter1 + 16, 0);
    matrix.print("AS SEEN ON SHOW & TELL");

    matrix.show();
    delay(pauseTime);
  }
}
When the sketch runs, it initializes the NeoMatrix and displays the character string that forms the game title, zeros all counters and awaits remote control commands.
These are the functions of the buttons on a RC66RX:
  • Color buttons - Four color-coded tally numbers are available for counting audience votes. Two can be displayed at any time, either the red/green pair or the yellow/blue pair, but only one color can be active at a time. A color button will display its color tally and make it active.
  • Numeric buttons - These key in a number when needed. They appear in the currently active color.
  • DASH - Change the sign of the number keyed in.
  • PREV - Backspace, remove a digit from the number keyed in.
  • ENTER - Move the keyed-in number into the active color's tally, replacing the previous value.
  • SELECT - Parse the keyed-in number as MM:SS, limiting it to 90 minutes max, move it into the countdown clock and start the countdown. If no number is keyed in, display the latest running countdown, if any.
  • CHAN UP - Add the active color's tally into the total score and display it.
  • CHAN DOWN - Deduct the active color's tally from the total score and display it.
  • BACK - display (but don't change) one of the color tally pairs.
  • MENU - display the game title string.
  • INFO - display (but don't change) the total score.
  • Left arrow & Right arrow - Switch between displaying red/green pair and yellow/blue pair.
  • REC - Reset all tallies to zero and display the most recent color pair.
  • Up arrow - Increase brightness
  • Down arrow - Decrease brightness
  • Play - Start/stop a colorful animated backdrop to the game title, countdown clock, or total score.
  • Loop back - Flip the display 180 degrees
Some tech notes.
  • Entering numbers is a little unusual. The sketch doesn't assemble a string of digits. It adds each digit into a running value and displays that. Backspacing the digits away doesn't clear the number being entered, it just reduces it to zero, so an unwanted zero can be entered by accident sometimes. Be careful.
  • A NeoMatrix of 32x8 pixels has enough width for only 5 characters. More than that will overlap or extend offscreen. Not pretty.
  • The countdown clock just counts to zero and stops. If it's stopped at zero, the SELECT button won't do anything until a new countdown number is entered.
  • The clock runs by counting millis(), which can have quite a range of inaccuracy. If you can determine how many millis() on your hardware are in a true second, you can replace #define ONE_SEC 1000 with your number and possibly make your timer more accurate.
  • The total score has no button to zero it. You can deduct the total amount, or add a negative number to reset the total to zero. (I wanted no accidental resets during a game.)
  • Adapting the sketch to respond to other IR remotes is possible, involving mainly replacing the hex codes for each button. Remotes with fewer buttons may have to sacrifice some functions, like brightness control or some animated effects.
  • Mr. LadyAda challenged me to add an Easter egg to the scoreboard. Can you find it?
Thank you for reading. I'm sure I left out something important, so I'll update this post when I remember it. If you build your own scoreboard, please show it here, especially if you make improvements.

Edit: The code received a little polish, more streamlined, more tidier.

Hallelujah!
Disciple
Last edited by Disciple on Thu Dec 27, 2018 12:39 am, edited 1 time in total.

Disciple
 
Posts: 695
Joined: Tue Jan 06, 2015 8:13 pm

Re: I made a scoreboard, and so can you!

by adafruit_support_bill on Wed Dec 26, 2018 8:28 am

Nice work! Thanks for posting your project.

adafruit_support_bill
 
Posts: 74965
Joined: Sat Feb 07, 2009 10:11 am

Re: I made a scoreboard, and so can you!

by AnneBarela on Wed Dec 26, 2018 1:56 pm

An Adafruit blog post in a couple of minutes highlighting your project - awesome work!
https://blog.adafruit.com/2018/12/26/fr ... -adafruit/

AnneBarela
 
Posts: 732
Joined: Sat Mar 24, 2012 8:56 pm
Location: Mid-Atlantic

Re: I made a scoreboard, and so can you!

by Disciple on Sun Dec 30, 2018 2:23 am

Gentlemen, thank you. You are most kind. I'm honored.
My game show partners are motivated to plan future performances, so the code may see future versions.
It's still a bit sloppy and inconsistent in places, like management of numeric entry, but it ran for seven shows before seven audiences without a hitch. If it gets an update, I'll post it here.

(Apologies for posting a misnomer. "NeoPixel RGB LED Matrix" is the hardware I used. "NeoMatrix" is pburgess' Arduino library that manages it...and maybe a cyberpunk movie.)

Hallelujah!
Disciple

Disciple
 
Posts: 695
Joined: Tue Jan 06, 2015 8:13 pm

Re: I made a scoreboard, and so can you!

by Disciple on Thu Aug 15, 2019 4:18 am

Twice I tried to join Show and Tell on StreamYard. Twice I was shot down in flames. Flames, I say. I and my online machines may be shut out of S&T for the foreseeable future. I guess I'll post my update here.

My scoreboard and team got an invitation to present again this year. If anyone wants to come and say hi and see the setup, it's Saturday Aug. 17th from 10-2 at Faithful Savior Lutheran Church in Portland Oregon.

Great, except for one problem. None of the drama team is available. My only performers are a dad and his kids. So we changed the scripts to depict a daddy having story time with his children and quizzing them with WWJD questions, while the audience plays as well. Good, but not visual enough. Let's add more media.

The drama scene will now include a projection screen showing slides illustrating each story, provided by freebibleimages.org. The images will come from my old Windows laptop, triggered by an Arduino Uno (maybe the last one assembled in the USA) with another IR sensor on pin 2. An extremely simple circuit.
IRduino01_2797.jpg
IRduino01_2797.jpg (92.95 KiB) Viewed 223 times
The Arduino detects codes from the same remote that controls the scoreboard, employing three more unused buttons, but here's the cool thing. It also detects codes from a second, unrelated remote for the daddy to use. Plenty of commercial remotes are designed to control two devices, but only with Adafruit and IRlib2 could I arrange for two odd remotes to control a single device. The DIY solution wins again, or it will this Saturday. Come and see.

Oh, here are the sketches I'll be using for a simple JPG slide show. The Windows laptop will be running Processing 2.

Code: Select all | TOGGLE FULL SIZE
/* FamilyFunSlideshowControl_001.ino - Detect IR remote codes from DTV clicker using IRLib2.
 *  Send serial commands to a host PC to cue a Processing sketch displaying stills.
 *  Borrowing IR code from its sister scoreboard sketch.
 *  Version 1 - Convert IR debounced input to serial message out.
 */

// These are the protocols from IRlib2 needed to decipher signals
// from NEC standard remote controllers like the RC66RX.
// Different remotes may require different components.
// Visit http://tech.cyborg5.com/irlib/ to learn more.
#include <IRLibDecodeBase.h>
#include <IRLib_P01_NEC.h>
#include <IRLib_P10_DirecTV.h>
#include <IRLibCombo.h>     // After all protocols, include this
// All of the above automatically creates a universal decoder
// class called "IRdecode" containing only the protocols you want.
// Now declare an instance of that decoder.
IRdecode myDecoder;

// Include a receiver either this or IRLibRecvPCI or IRLibRecvLoop
#include <IRLibRecv.h>
IRrecv myReceiver(2);  //pin number for the receiver

// Hex values from a RC66RX remote with the satellite TV control switch set.
#define BUTNSTOP 0xC310
#define BUTNREWD 0xC332
#define BUTNFFWD 0xC342
// Hex values from an Insignia CR-280D remote
#define BUTISTOP 0x61A08E71
#define BUTILEFT 0x61A06897
#define BUTIRITE 0x61A0A857
#define BUTIUPPP 0x61A042BD
#define BUTIENTR 0x61A018E7
#define BUTIDOWN 0x61A0C23D

#define BOUNCE_GAP 100
#define LED_PIN 13

void setup() {
  pinMode(LED_PIN, OUTPUT);

  Serial.begin(9600);
  delay(2000); while (!Serial); //delay for Leonardo
  myReceiver.enableIRIn();      // Start the receiver
  Serial.println(F("Ready to receive IR signals"));
}

void loop() {
static uint32_t deBounce = 0, newCode, lastCode = 0;

  // Continue looping until you receive a complete IR signal
  if(myReceiver.getResults()) {
    if(myDecoder.decode()) {
      newCode = myDecoder.value;  // A remote control click is detected!

      // Debounce and de-repeat remote buttons
      if((newCode == lastCode) && (millis() < deBounce)) { // The same code in a very short time?
        newCode = 0;                                       // It's a bounce/repeat.  Clear it.
        deBounce = millis() + BOUNCE_GAP;                  // Restart the time limit.
      } else {
        lastCode = newCode;
        deBounce = millis() + BOUNCE_GAP;                  // Restart the time limit.
        digitalWrite(LED_PIN, HIGH);                       // LED on while we process a button-press
      }

      switch(newCode) {  // A remote button has been pushed.  Which one?
        case BUTNSTOP:      Serial.println("0"); break;  // Send char strings to host computer by USB serial
        case BUTNREWD:      Serial.println("1"); break;
        case BUTNFFWD:      Serial.println("2"); break;
        case BUTISTOP:      Serial.println("0"); break;
        case BUTILEFT:      Serial.println("1"); break;
        case BUTIRITE:      Serial.println("2"); break;
        case BUTIUPPP:      Serial.println("3"); break;
        case BUTIDOWN:      Serial.println("4"); break;
        case BUTIENTR:      Serial.println("5"); break;
        // default:            Serial.println("What?"); break;
      }
      digitalWrite(LED_PIN,  LOW); // LED off, button-press processing done
    }
    myReceiver.enableIRIn();      // Restart receiver, resume IR listening
  }
}


/**  FamilyFunSlideshow002 - Find and display JPGs in response to serial cues from an Arduino Uno. */

/*
import java.util.Date;
import processing.serial.*;

String myString = null;
Serial myPort;  // The serial port

PImage img;  // Declare variable of type PImage
int fileCount = 0, imageIndex = 0, serialVal = 0;
String[] filenames;

void setup() {
  size(1024, 768); // The window matches the size of my JPGs.  Adjust yours to suit.

  String path = sketchPath + "/data";  // Read image filenames from the "/data" subdirectory

  println("Listing all filenames in a directory: ");
  filenames = listFileNames(path);
  println(filenames);
  fileCount = filenames.length;
  println(fileCount + " entries");

  // Open the port you are using at the rate you want:
  myPort = new Serial(this, Serial.list()[1], 9600);  // Number [1] is where my Arduino serial appears.  Yours may vary.
  myPort.clear();
  // Throw out the first reading, in case we started reading
  // in the middle of a string from the sender.
  myString = myPort.readStringUntil('\n');
  myString = null;

  if(fileCount < 1) {
    println("No images found.  Stopping.");
    noLoop();
  } else {
    img = loadImage(filenames[0]);  // Load the first image
    image(img, 0, 0);   // Show it
  }
}

void draw() {
  serialVal = numberFromSer();  // Wait for a number from Arduino Uno serial

  if(serialVal == 0)      // Reset to first image?
    imageIndex = 0;
  else if(serialVal == 2) // Step forward?
    imageIndex = (imageIndex + fileCount + 1) % fileCount;  // Increment image counter
  else if(serialVal == 1) // Step back'ard?
    imageIndex = (imageIndex + fileCount - 1) % fileCount;  // Decrement image counter
  // else reshow the same image again

  img = loadImage(filenames[imageIndex]);  // Load the image into the program
  image(img, 0, 0);  // Displays the image at (0,0)
}

// Daniel Shiffman example has 2 functions:
// 1) List the names of files in a directory
// 2) List the names along with metadata (size, lastModified) of files in a directory
// This function returns all the files in a directory as an array of Strings 
String[] listFileNames(String dir) {
  File file = new File(dir);
  if (file.isDirectory()) {
    String names[] = file.list();
    return names;
  } else {
    // If it's not a directory
    return null;
  }
}

// This function returns all the files in a directory as an array of File objects
// This is useful if you want more info about the file
File[] listFiles(String dir) {
  File file = new File(dir);
  if (file.isDirectory()) {
    File[] files = file.listFiles();
    return files;
  } else {
    // If it's not a directory
    return null;
  }
}

int numberFromSer() {
  while(true) {   // Loop until broken
    myString = myPort.readStringUntil('\n');  // Receive a char string from Arduino
    if (myString != null) {
      return int(trim(myString));             // Convert it to an int, if possible.  Return it.
    }
  }
}
*/
Come by and tell me you read about it here. (c:

Hallelujah!
Disciple

Disciple
 
Posts: 695
Joined: Tue Jan 06, 2015 8:13 pm

Re: I made a scoreboard, and so can you!

by Disciple on Tue Oct 08, 2019 1:25 pm

I see that the image host I relied upon has imploded, and the animated GIF that introduces my project in the top post went down with it. Frowny face. My plan was to edit the post and replace the missing GIF with the copy in the blog entry, but the edit button has vanished as well. What...what's happening to the net as we know it? At least the whole topic wasn't locked.
Could a forum wizard do what I can't do and replace the broken image link? I liked https://cdn-blog.adafruit.com/uploads/2018/12/s2fdk7.gif.
Thank you.

Hallelujah!
Disciple

Disciple
 
Posts: 695
Joined: Tue Jan 06, 2015 8:13 pm

Re: I made a scoreboard, and so can you!

by adafruit_support_bill on Tue Oct 08, 2019 2:09 pm

Hi DIsciple. I updated your post.

We've had some issues with 'stealth spammers' that make seemingly legitimate posts, then go back later and seed them with inappropriate photos and/or links. Our web team has implemented some safeguards to discourage that kind of abuse. We apologize for any inconvenience.

adafruit_support_bill
 
Posts: 74965
Joined: Sat Feb 07, 2009 10:11 am

Please be positive and constructive with your questions and comments.