Mike Tan CT
Published © GPL3+

Wireless Scoreboard Display for Drone Soccer or Other Games

A portable, wireless LED display panel for showing scores of a Drone Soccer match or any typical match involving two teams or players.

IntermediateWork in progress1,017
Wireless Scoreboard Display for Drone Soccer or Other Games

Things used in this project

Hardware components

RF 433MHz Receiver & Transmitter Modules
×1
RF 433MHz Remote Controller
×2
USB A Power Cable
×1
Raspberry Pi Pico
Raspberry Pi Pico
×1
Arduino UNO
Arduino UNO
×1

Software apps and online services

Arduino IDE
Arduino IDE

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)
Hot glue gun (generic)
Hot glue gun (generic)

Story

Read more

Custom parts and enclosures

Optional build files for Control Console

STL files for 3D printing and SVG files for laser cutting parts of the enclosure.

Code

LED Wireless Scoreboard Display for Drone Soccer

Arduino
For the Raspberry Pico Compatible RP2040 Core board that runs the LED Display Panel.
#include <RCSwitch.h>
#include <millisDelay.h>
#include <FastLED.h>

//FASTLED
#define SECT_per_STRIP      2   // number of sections to divide the LED strip into
#define DIGITS_per_SECT     2   // number of digits that each section of LEDs will display 
#define SEG_per_DIGIT       7   // number of segments to form any digit from 0 to 9
#define LED_per_SEG         7   // numer of LED pixels that comprises one segment of a digit 
#define LED_PIN             7   // GPIO pin used by LED strip
#define BRIGHTNESS          150 // global brightness per colour (max 255)
#define FRAMES_PER_SECOND   60
#define ARRAY_SIZE(A) (sizeof(A) / sizeof((A)[0]))

//  -- Change the values according to desired color for each team --

//Team A Color
#define RED1    150
#define GREEN1  0
#define BLUE1   0
//Team B Color
#define RED2    0
#define GREEN2  0
#define BLUE2   150

// ---- Change above values to set desired color for each team ---

// Change these values to follow the actual transmitters' signal values from goalpost remotes and control cpnsole buttons into these constants accordingly
const int RED_remote_A_ON = 4313784;   // signal from goalpost A remote (red team)
const int BLUE_remote_B_ON = 2445608;  // signal from goalpost B remote (blue team)
const int SWAP_AB_switch = 7590951;    // signal from SWAP switch when slide from right to left
const int SWAP_BA_switch = 7590959;    // signal from SWAP switch when slide from left to right
const int MINUS_button = 7590954;      // signal from MINUS button to enter Minus mode
const int MINUS_1_button = 7590950;    // signal from -1 button 
const int RESET_button = 7590952;      // signal from RESET button
const int MODE_button = 7590948;       // signal from MODE button to toggle pattern & scores


// total number of LEDs in the LED strip attached
const int totalNumLEDs = LED_per_SEG * SEG_per_DIGIT * DIGITS_per_SECT * SECT_per_STRIP;

// the number of LEDs in one section of the LED strip. Used when need to get first LED position of the next section of the strip.
const int sect_offset = totalNumLEDs/SECT_per_STRIP; 

// the number of LEDs in one digit. Used when need to get first LED position of the next digit.
const int digit_offset = LED_per_SEG * SEG_per_DIGIT;

// Define the array of leds in FastLED
CRGB led[totalNumLEDs];

// set segments on/off state for each digit from 0~9
const int digit[][SEG_per_DIGIT]= { 
{1,1,1,0,1,1,1}, // digit 0
{1,0,0,0,1,0,0}, // digit 1
{1,1,0,1,0,1,1}, // digit 2
{1,1,0,1,1,1,0}, // digit 3
{1,0,1,1,1,0,0}, // dogot 4
{0,1,1,1,1,1,0}, // digit 5
{0,1,1,1,1,1,1}, // digit 6
{1,1,0,0,1,0,0}, // digit 7
{1,1,1,1,1,1,1}, // digit 8
{1,1,1,1,1,0,0}  // digit 9
};

const int letter[][SEG_per_DIGIT]= { // alphabet to show when swapping sections
{1,1,0,1,1,1,1}, // letter a
{0,0,1,1,1,1,1}, // letter b
};

// for toggling scores to diplay between the separate sections of LED strip
int sectPos[] = {1,0}; // e.g. 0 is for first section on strip.

// for storing current section positions to check if need to swap sections when swap signal is received
int currPos[] = {}; // checked in loop1 when show_swapSect is true

// for storing score values to display
int scoreCount[] = {0,0};

// minus score state, 0=off, 1 for scoreCount[0], 2 for scoreCount[1]
int minusToggle = 0;


bool patternPlay = false; // toggles pattern playing mode
bool set_scoreDigits = false; // for displaying scores one time in loop1 when switching from patterns
bool show_swapSect=false; // set state to display swapped sections letters
bool show_Reset=false; // display reset mode

//RF switch
RCSwitch rfSwitch = RCSwitch();

// Timers used to precvent effecting score change at short intervals (i.e.when long pressing remote buttons) 
millisDelay scoreDelay1;
millisDelay scoreDelay1_1;
millisDelay scoreDelay2;
millisDelay scoreDelay2_1;
millisDelay resetDelay;

void setup() 
{
  
  Serial.begin(115200);
  pinMode(25, OUTPUT); //LED on processor board 
  rfSwitch.enableReceive(0);  // Receiver on interrupt 0 => that is pin #2
  patternPlay=false; // starts the scoreboard status to be in pattern mode

}

void setup1() { 

  FastLED.addLeds<WS2812, LED_PIN, GRB>(led, totalNumLEDs).setCorrection(CRGB(120, 200, 255)); 
  Serial.begin(115200);
  FastLED.clear();
  setDigit(scoreCount[0],sectPos[0], RED1,GREEN1,BLUE1);
  setDigit(scoreCount[1],sectPos[1], RED2,GREEN2,BLUE2);
  FastLED.setBrightness(BRIGHTNESS);
}
  

// FastLED list of patterns to cycle through.  Each is defined as a separate function below.
typedef void (*SimplePatternList[])();
SimplePatternList gPatterns = {  sinelon, juggle, bpm, rainbow, rainbowWithGlitter, confetti , shiftingAlternating};
uint8_t gCurrentPatternNumber = 0; // Index number of which pattern is current
uint8_t gHue = 0; // rotating "base color" used by many of the patterns


///// FIRST LOOP TO RECEIVE RF SIGNAL AND SET STATE
void loop() {

  currPos[0]=sectPos[0]; //keep track of section position

  if (rfSwitch.available()) { //signal received by RF receiver
    int receivedValue = rfSwitch.getReceivedValue();
    Serial.print(" Rcv ");
    Serial.println( receivedValue );

    if (patternPlay==false) { // allow only when not playing pattern

      switch (receivedValue) {
        
        case RED_remote_A_ON: //goalpost A add score
          if (!scoreDelay1.isRunning() && show_Reset==false) {
            scoreCount[0]++;
            setDigit(scoreCount[0], sectPos[0], RED1,GREEN1,BLUE1);
            scoreDelay1.start(500);
            digitalWrite(25, HIGH); //board LED indicator
          } else {
            scoreDelay1.restart();
          }
        break;

        case BLUE_remote_B_ON://goalpost B add score
          if (!scoreDelay2.isRunning() && show_Reset==false) {
            scoreCount[1]++;
            setDigit(scoreCount[1], sectPos[1], RED2,GREEN2,BLUE2);
            scoreDelay2.start(500);
            digitalWrite(25, HIGH); //board LED indicator
          } else {
            scoreDelay2.restart();
          }
        break;
        
        case SWAP_AB_switch: // swap section slider switch  to left
          Serial.println("Swap A-B");
          minusToggle=0; // exit minus mode in case this happens in minus mode
          sectPos[0] = 0;
          sectPos[1] = 1;
          if (currPos[0]!=sectPos[0] && minusToggle==0 ) { // allow only when section position is not same and not in modes: minus
            show_swapSect = true;
            delay(500); // prevent continuoud transmit from causing multiple toggles on button press
          }
        break;

        case SWAP_BA_switch: // swap section slider switch to right
          Serial.println("Swap B-A");
          minusToggle=0; // exit minus mode in case this happens in minus mode
          sectPos[0] = 1;
          sectPos[1] = 0;
          if (currPos[0]!=sectPos[0] && minusToggle==0 ) { // allow only when section position is not same and not in modes: minus
            show_swapSect = true;
            delay(500); // prevent continuoud transmit from causing multiple toggles on button press
          }
        break;

      }

      if (minusToggle==0) { // allow only when not in minus mode
        
        switch (receivedValue) {

          case MINUS_button: // enter minus mode
            Serial.println("Minus mode button - from out");
            if (show_swapSect==false && show_Reset==false && (scoreCount[1]+scoreCount[2]>0)) { // allow only when not in modes: Swap, Reset, AND scores not zero
              minusToggle=1;
              delay(500); // prevent continuoud transmit from causing multiple toggles on button press
            }
          break;

          case RESET_button: //reset score
            Serial.println("Reset button");
            if (minusToggle==0 && show_swapSect==false && (scoreCount[1]+scoreCount[2]>0)) { // allow only when not in modes: minus , swap, AND scores not zero 
              if (show_Reset==false){
                show_Reset=true;
                resetDelay.start(3000);
                delay(500); // prevent continuoud transmit from causing multiple toggles on button press
              }
              else if (show_Reset==1 && resetDelay.isRunning() ) { //reset scores
                for (int i = 0; i < SECT_per_STRIP; i++) {
                  scoreCount[i] = 0;
                  setDigit(scoreCount[i], sectPos[i], 0, 0, 0 );
                }
                show_Reset=false;
                resetDelay.finish();
                delay(500); // prevent continuoud transmit from causing multiple toggles on button press
              }
            }
          break;
        }
      }
      else { // in minus mode, i.e minusToggle not zero

        switch (receivedValue) {

          case MINUS_button: // minus button
            Serial.println("Minus mode button - from in");
            if (minusToggle==1) {
              minusToggle=2;
              delay(500); // prevent continuous transmit from causing multiple toggles on button press
            } 
            else if (minusToggle==2) { // minusToggle=2
              minusToggle=0;
              delay(500); // prevent continuous transmit from causing multiple toggles on button press
            }
          break;

          case MINUS_1_button: // minus score value
            Serial.println("-1 button");
            if (minusToggle==1) {
              if (!scoreDelay1_1.isRunning()) {
                if (scoreCount[0] > 0) {
                  scoreCount[0]--;
                  setDigit(scoreCount[0], sectPos[0], RED1,GREEN1,BLUE1);
                  scoreDelay1_1.start(250);
                  digitalWrite(25, HIGH); //board LED indicator
                }
              } else {
                scoreDelay1_1.restart();
              }
            }
            if (minusToggle==2) {
              if (!scoreDelay2_1.isRunning()) {
                if (scoreCount[1] > 0) {
                  scoreCount[1]--;
                  setDigit(scoreCount[1], sectPos[1], RED2,GREEN2,BLUE2);
                  scoreDelay2_1.start(250);
                  digitalWrite(25, HIGH); //board LED indicator
                }
              } else {
                scoreDelay2_1.restart();
              }
            }
          break;
        }
      }
    }
  }

  if (rfSwitch.getReceivedValue()==MODE_button) { // pattern mode button received
    if (minusToggle==0 && show_swapSect==false && show_Reset==false){ //only allow when not in modes: minus, reset, swap
      patternPlay= 1-patternPlay; // toggle state
      if (patternPlay==0){ // if toggled to score mode
        set_scoreDigits=1; // to set score display in loop1 to execute once
      }
      Serial.print ("Pattern Play State= ");
      Serial.println (patternPlay);
      FastLED.clear();
      delay(500); //  prevent continuoud transmit from causing multiple toggles on button press
    }
  }

  rfSwitch.resetAvailable();

  if (scoreDelay1.justFinished() || scoreDelay1_1.justFinished() || scoreDelay2.justFinished() || scoreDelay2_1.justFinished()) {
    digitalWrite(25, LOW); //LED on board
  }
  if (resetDelay.justFinished()) {
    show_Reset=false;
    set_scoreDigits=1;
  }
}
///// END OF FIRST LOOP TO RECEIVE RF SIGNAL AND SET STATE


///// SECOND LOOP TO SET LED MODE BASED ON STATE SET
void loop1() {

  if (patternPlay==true){ // play patterns if toggle is true
    gPatterns[gCurrentPatternNumber]();
    // do some periodic updates
    EVERY_N_MILLISECONDS(1) { gHue++; } // slowly cycle the "base color" through the rainbow
    EVERY_N_SECONDS( 5 ) { nextPattern(); } // change patterns periodically 
  }

  else { // play pattern is false, so display the score related info
    
    if (show_swapSect==true) { // swap section needs to be displayed
    // show section position
      for (int i=0; i<15; i++){
        FastLED.clear();
        setLetter(0);
        setLetter(1);
        FastLED.show();
        FastLED.delay(50);
        FastLED.clear();
        FastLED.show();
        FastLED.delay(15);
      }
      //reset swap state
      show_swapSect=false;
      // set scores
      setDigit(scoreCount[0],sectPos[0], RED1,GREEN1,BLUE1);
      setDigit(scoreCount[1],sectPos[1], RED2,GREEN2,BLUE2);
    }    
    
    if (set_scoreDigits==1) { //triggered when switching from patterns
      // set scores
      setDigit(scoreCount[0],sectPos[0], RED1,GREEN1,BLUE1);
      setDigit(scoreCount[1],sectPos[1], RED2,GREEN2,BLUE2);
      set_scoreDigits=0;
    }

    if (minusToggle>0) { // show minus mode
      Serial.println(minusToggle);
      flashMinus(minusToggle);
    }

    if (show_Reset==true){
      setDigit(scoreCount[0],sectPos[0], RED1,GREEN1,BLUE1);
      setDigit(scoreCount[1],sectPos[1], RED2,GREEN2,BLUE2);
      FastLED.show();
      FastLED.delay(20);
      setDigit(scoreCount[0],sectPos[0], 0,0,0);
      setDigit(scoreCount[1],sectPos[1], 0,0,0);
      FastLED.show();
      FastLED.delay(10);
    } 
  }
  FastLED.show();
  // insert a delay to keep the framerate modest
  FastLED.delay(1000/FRAMES_PER_SECOND); 

}
/////// END OF SECOND LOOP TO SET LED MODE BASED ON STATE SET

//// Function to flash digits when in minus mode
void flashMinus(int val){
  int sideToFlash= val-1; // to give 0 or 1
  setDigit(scoreCount[sideToFlash],sectPos[sideToFlash], 0,0,0);
  FastLED.show();
  FastLED.delay(150);
  if (sideToFlash == 0) {
    setDigit(scoreCount[sideToFlash],sectPos[sideToFlash], RED1,GREEN1,BLUE1);
  }
  else if (sideToFlash==1) {
    setDigit(scoreCount[sideToFlash],sectPos[sideToFlash], RED2,GREEN2,BLUE2);
  }
  FastLED.delay(150);
}

//// Function for swapping A-B score positions. used in second loop 
void setLetter (int sect_Pos) { 
  // set display for oneth digit
  for (int j=0; j<SEG_per_DIGIT; j++){ // set for each digit's segment light status
    for (int i=0; i<LED_per_SEG; i++){ // set for each segment's LED pixel
      led[(sectPos[sect_Pos]*sect_offset)+(j*LED_per_SEG)+i] = (CRGB(BRIGHTNESS, BRIGHTNESS, BRIGHTNESS))*letter[sect_Pos][j];
    }
  }
}

//// Function to set the digit for displaying scores
void setDigit(int scoreValue, int sect_Pos, int R, int G, int B) {
  int oneth_val = scoreValue-(10*round(scoreValue/10));
  int tenth_val = round(scoreValue/10);

  Serial.print(tenth_val);  
  Serial.print(oneth_val);  

  // set display for oneth digit
  for (int j=0; j<SEG_per_DIGIT; j++){ // set for each digit's segment light status
    for (int i=0; i<LED_per_SEG; i++){ // set for each segment's LED pixel
      led[(sect_Pos*sect_offset)+(j*LED_per_SEG)+i] = (CRGB(R, G, B))*digit[oneth_val][j];
      //Serial.print((sect_Pos*sect_offset)+(j*LED_per_SEG)+i);
      //Serial.print(", ");  
    }
    //Serial.println( " -> ");
  }

  // set display for tenth digit
  for (int j=0; j<SEG_per_DIGIT; j++){ // set for each digit's segment light status
    for (int i=0; i<LED_per_SEG; i++){ // set for each segment's LED pixel
      led[(sect_Pos*sect_offset)+digit_offset+(j*LED_per_SEG)+i] = (CRGB(R, G, B))*digit[tenth_val][j];
      //Serial.print((sect_Pos*sect_offset)+digit_offset+(j*LED_per_SEG)+i);
      //Serial.print(", ");  
    }
    //Serial.println( " -> ");
  }
}



//////// FastLED functions ///////////////////

void nextPattern()
{
  // add one to the current pattern number, and wrap around at the end
  gCurrentPatternNumber = (gCurrentPatternNumber + 1) % ARRAY_SIZE( gPatterns);
}

void sinelon()
{
  // a colored dot sweeping back and forth, with fading trails
  fadeToBlackBy( led, totalNumLEDs, 250);
  int pos = beatsin16( 28, 0, totalNumLEDs-1 );
  led[pos] += CHSV( gHue, 255, 255);
}

void juggle() {
  // eight colored dots, weaving in and out of sync with each other
  fadeToBlackBy( led, totalNumLEDs, 50);
  uint8_t dothue = 0;
  for( int i = 0; i < 6; i++) {
    led[beatsin16( i+5, 0, totalNumLEDs-1 )] |= CHSV(dothue, 200, 255);
    dothue += 32;
  }
}

void bpm()
{
  // colored stripes pulsing at a defined Beats-Per-Minute (BPM)
  uint8_t BeatsPerMinute = 60;
  CRGBPalette16 palette = RainbowStripeColors_p;
  uint8_t beat = beatsin8( BeatsPerMinute, 4, 30);
  for( int i = 0; i < totalNumLEDs; i++) { //9948
    led[i] = ColorFromPalette(palette, gHue+(i*2), beat-gHue+(i*2));
  }
}

/*void rainbow() //orihinal
{
  // FastLED's built-in rainbow generator
  fill_rainbow( led, totalNumLEDs, gHue, 0.5);
  fadeToBlackBy( led, totalNumLEDs, 220);
}*/
void rainbow() {
  fadeToBlackBy(led, totalNumLEDs, 5);
  for (int i = 0; i < totalNumLEDs; i++) {
    led[i] = CHSV(gHue + beatsin8(8, 0, 255), 255, (i < totalNumLEDs) ? (BRIGHTNESS/2) : 0);
  }
}

void rainbowWithGlitter() 
{
  // built-in FastLED rainbow, plus some random sparkly glitter
  rainbow();
  addGlitter(500);
}

void addGlitter( fract8 chanceOfGlitter) 
{
  if( random8() < chanceOfGlitter) {
    led[ random16(totalNumLEDs) ] += CRGB::White;
  }
}

void confetti() 
{
  // random colored speckles that blink in and fade smoothly
  fadeToBlackBy( led, totalNumLEDs, 12);
  int pos = random16(totalNumLEDs);
  led[pos] += CHSV( gHue + random8(255), 255, 255);
}


void shiftingAlternating() {
  static int limit = random(1,60);
  static uint8_t pixelShift = 0; 
  static unsigned long lastUpdateTime = millis();  // Stores the time of the last update
  static int static_value = limit;
  static int direction = 1;

  fadeToBlackBy(led, totalNumLEDs, 1);
  for (int i = 0; i < totalNumLEDs; i++) {
    if ((i + pixelShift) % 11 == 0) {
      led[i] = CHSV(gHue + (i * 5) % 255, 255, 255);
    } else {
      led[i] = CHSV(0, 0, 0);
    }
  }

  // Update only if enough time has passed since the last update
  const unsigned long MIN_UPDATE_INTERVAL = static_value;  // Adjust for desired speed (milliseconds)
  if (millis() - lastUpdateTime >= MIN_UPDATE_INTERVAL) {
    lastUpdateTime = millis();  // Reset update time
    
    static_value += direction;
    if (static_value <= 0) {
      direction =1;
      limit = random(1,60);
      Serial.print("limit changed to ");
      Serial.println(limit);
    }
    if (static_value >= limit) {
      direction = -1;
    }

    pixelShift++;
    if (pixelShift >= totalNumLEDs) {
      pixelShift = 0;
    }
  }
}
//////////////////// End of FastLED functions ///////////////////////////////

(Optional) - Control Console

Arduino
For use if building a custom Control Console.
#include <RCSwitch.h>
#include <millisDelay.h>

RCSwitch mySwitch = RCSwitch();

// pin names
int ledPin = 13; // onbpoard LED
int input6Pin = 8;
int input5Pin = 7;
int input4Pin = 6;
int input3Pin = 5;
int input2Pin = 4;
int input1Pin = 3;

bool swapTriggered =false;
bool prevSliderState;


void setup() {

  // declare LED as output
  pinMode(ledPin, OUTPUT);   

  // set GPIO pins use asinputs
  pinMode(input6Pin, INPUT); 
  pinMode(input5Pin, INPUT); 
  pinMode(input4Pin, INPUT);
  pinMode(input3Pin, INPUT);
  pinMode(input2Pin, INPUT);
  pinMode(input1Pin, INPUT);
  
  Serial.begin(115200);
  
  // RF transmitter is connected to Arduino Pin #10  
  mySwitch.enableTransmit(10);

  mySwitch.send(10794372, 24);//toggle pattern button

  prevSliderState=digitalRead(3); // store startup slider state
  if (prevSliderState==1){
    mySwitch.send(1000001, 24);
  } 
  else if (prevSliderState==0){
    mySwitch.send(1000002, 24);
  }
}

void loop() {

  for(int i=3 ; i<9; i++){
      checkPush (i);
  }

  if (digitalRead(3)!=prevSliderState) { // i.e. if SWAP slider state has changed
    bCast_sliderPos();
    prevSliderState=digitalRead(3);
  }
}

void checkPush(int pinNumber)
{
  int pushed = digitalRead(pinNumber);  // read input value
  if (pushed == HIGH) {// check if the input is HIGH (button released)
    digitalWrite(ledPin, HIGH);  // turn LED OFF
    switch (pinNumber) {

      case 4: // minus 1 button to reduce score
        mySwitch.send(66666666, 24);
        Serial.println("minus 1");
        break;
      case 5: // PLAY-PAUSE button
        mySwitch.send(99999999, 24);
        Serial.println("play pause");
        break;
      case 6: // MODE button for pattern - score toggle
        mySwitch.send(88888888, 24);
        bCast_sliderPos();
        Serial.println("mode change");
        break;
      case 7: // MINUS button
        mySwitch.send(55555555, 24);
        Serial.println("minus sel");
        break;
      case 8: // RESET button
        mySwitch.send(77777777, 24);
        Serial.println("reset");
        break;
    } //end switch
  } else {
    digitalWrite(ledPin, LOW);  // turn LED ON
  }
}


// function to transmit signal of SWAP switch state
void bCast_sliderPos () {
  if (digitalRead(3)==1){
    mySwitch.send(33333333, 24);
    Serial.println("change a-b");
  } 
  else if (digitalRead(3)==0){
    mySwitch.send(44444444, 24);
    Serial.println("change b-a");
  }
}

Credits

Mike Tan CT

Mike Tan CT

1 project • 0 followers

Comments