Yet another scoreboard

The Dot Matrix Display (DMD) is a 32x16 array of high-brightness LEDs for visually striking effects. [Product Page]
Post Reply
mickot
Posts: 6
Joined: Sun Mar 23, 2014 12:01 pm

Yet another scoreboard

Post by mickot » Thu Jul 21, 2016 12:59 pm

Hi all,
If anyone is interested I can write up my scoreboard build using the DMD displays.

Features:
Web enabled - can be controlled by any phone/tablet etc. over wifi
Battery powered - cunning use of golf caddy battery allows over 3 hours of continuous usage (not tested to failure, so who knows how long it will last)
Uses various tools found here for sideways font etc.
Arduino Yun serves interface and then uses a simple serial bus to send messages along to arduinos that control displays.
Scrolls sponsor's message or other communications across the bottom.

I can post the code, hardware details etc .

Here is a photo to whet appetite, although I am on hols for next few weeks so you will have to wait for updates

-micko
Attachments
BareScoreBoardS.jpeg

danieldewaard
Freetronics Staff
Freetronics Staff
Posts: 21
Joined: Thu Jul 14, 2016 2:41 am

Re: Yet another scoreboard

Post by danieldewaard » Fri Jul 22, 2016 12:41 am

Hi Mickot, this looks like a fantastic project! We would love to feature it in our blog / social media! Once you have done a write up send me an email at daniel@freetronics.com and we will feature it :)

Daniel

mickot
Posts: 6
Joined: Sun Mar 23, 2014 12:01 pm

Re: Yet another scoreboard

Post by mickot » Mon Aug 15, 2016 12:30 pm

Battery power

Part of the design brief was that the scoreboard should be portable so we needed to get a suitable battery powered supply. The scoreboard would also be used outdoors and perhaps in bad weather so we looked around to see what might already exist in order to avoid “re-inventing another wheel”. We needed an outdoor battery, with weather proof connectors and a charging system, hhhmmmm – motorised golf trolleys! It also helped that a club member was a service engineer for Mocad trolleys so we had access to cheap (free) batteries and chargers but more importantly his expertise.
Approaching the above member, we managed to get hold of a Mocad battery and charger. Pretty much all motorised golf trolleys use Torberry connectors and long (500mm) connectors can be bought on ebay for 5 USD. A battery can cost about 50 USD and a charger is about 25 USD. If bought in a shop there is less chance of burning down the house when charging the battery and even if it does happen, well you have some come-back. These batteries produce 12 volts and enough amperage to drive a golf trolley up and down hills for 5 hours, so we reckoned they would have enough oomph to keep the DMDs going for the duration of a rugby match.
Torberry.jpg
The Torberry connectors were stripped and connected to electrical screw terminals of the chocolate block type and then distributed to the DMDs through 12v to 5v converters. There are lots of places to purchase these from your usual online merchants. We used these ones
12vto5vConverter.jpg
We used 3 of these, each powering a pair of DMD displays. Ok so now we had plenty of 5v power going into the DMDs.
The next challenge was how to power the Arduinos. As we had 12v from our batteries we just bought a car adaptor that took 12v in and USB power out. The design had 3 Arduinos so we bought a 5 port device. The top tip here is to make sure you buy one that does NOT have a soft on/off switch but automatically comes on when the 12v power is applied. To fit it into our casing we took out the board and stripped the cigar lighter end to give us the bare wires for our chocolate block. We used this one from Anker. You could even take one of the un-used connectors externally with a USB extender to keep the phone/tablet charged during a match but this is not an issue for the duration of a rugby match.
USBPower.jpg
In operation the scoreboard will show 4 digits on 4 DMDs all of the time and whenever the score changes the names of the teams will scroll across the bottom 2 displays. This is repeated after a 5 minute delay. We can also enter a message that will scroll across the bottom 2 panels when sent and this is repeated after a 3 minute delay. Back of an envelope calculations showed that the battery would have no problems providing the power required but we still decided to test. Rather than be embarrassed by a public failure we had a real-time recording of an international rugby match so we set the system up in our living room and changed the scores as they happened and sent random messages to the scoreboard. The battery had no problem but the panels were so bright it detracted from our enjoyment of the match!

I will follow up with other installments in the story later...

mickot
Posts: 6
Joined: Sun Mar 23, 2014 12:01 pm

Re: Yet another scoreboard - Software

Post by mickot » Wed Aug 31, 2016 3:01 pm

Software

A simple message format between Arduinos was designed and uses the REST features of the Yun to send the messages to the Arduino side from the Linux (OpenWRT) side. The messages are then sent over the serial port to the first Arduino which then sends all messages on the next Arduino. After “passing it on” each Arduino then acts on the messages meant for itself. We found that the fastest serial speed we could use consistently was 2400 baud and the DMD Timer1 should be set at 5000.
The software is simple HTML/Javascript and so can run on any tablet, iphone, Android etc device. The built in WiFi and security on the Yun Linux side is used for connectivity. The Yun also supports mDNS for a user friendly IP address. This web app can be saved to the home page of an iphone etc as an icon. We used an external USB WiFi connector for the Yun to enable us to use an external antenna, but after testing this was not required and we can connect to the scoreboard across the width of a rugby pitch just using the on-board antenna with no problems. For reference the external antenna and USB dongle are described here http://www.lucadentella.it/en/2014/11/0 ... -wifi-usb/

Javascript

The Javascript uses just one call from the zepto.js library provided with the Yun. Zepto is a jQuery subset. The code to make this call from Javascript directly is trivial but Zepto is pretty small. It also uses the 7 segment display library from 3quarks to mimic the DMD display on the phone screen found here http://www.3quarks.com/en/SegmentDisplay/
Screenshot is shown on an old iphone 3gs used as the UI. The design does not clear the score fields when they are sent and so you only have to change the new score to update. Also one can scroll the score at any time on the DMD display by pressing the big “Score” button.
Screen1.jpg
Scroll down for the other fields
Screen2.jpg
HTML Code served from the Yun www folder

Code: Select all

<html>
  
  <head>
	<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
	<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>Score Board</title>
	<link media="Screen" href="styles.css" type="text/css" rel="stylesheet" />
	<link media="handheld, only screen and (max-width: 480px), only screen and (max-device-width: 480px)" href="mobile.css" type="text/css" rel="stylesheet" />
  
    <!--[if IE]>
      <script type="text/javascript" src="excanvas.js"></script>
    <![endif]-->
	<script type="text/javascript" src="zepto.min.js"></script> <!--zepto a lightweight jquery compatible library-->	
    <script type="text/javascript" src="segment-display.js"></script> <!--7 segment display from 3Quarks -->	
    <script type="text/javascript">

      var display = new SegmentDisplay("display");
      display.pattern         = "##:##";
	  display.displayAngle    = 6;
      display.digitHeight     = 20;
      display.digitWidth      = 14;
      display.digitDistance   = 2.5;
      display.segmentWidth    = 2;
      display.segmentDistance = 0.3;
      display.segmentCount    = 7;
      display.cornerType      = 3;
      display.colorOn         = "#f3f00f";
      display.colorOff        = "#461e05";
	  
	function sendScore(theScore) {	
		$.get('/arduino/score/' + theScore + '/'); // throw the score message over the wall to the Arduino side
		}
	
      function updateScore() {
//        alert("updateScore");
        var home   = document.getElementById('homescore').value;
		var away = document.getElementById('awayscore').value;
		//alert(isNaN(away));
        var value   = ((home < 10) ? '0' : '') + home
                    + ':' + ((away < 10) ? '0' : '') + away; // put in leading zeros on scores less than 10
		if (isNaN(away) || isNaN(home) || away<0 || home<0)
			{
			alert("Each score must be a number and zero or greater");
			}
		else
			{
			display.setValue(value); // set the 7 segment display locally
//			alert("Score sent");
			// now send score to arduino side value is string "HH:AA"
			sendScore(value);
			}
		}
		
	function updateMessage() {
        var theMessage   = document.getElementById('showIt').value;
        if (theMessage != '') {        
        theMessage.replace(/[\n\r]/gm, ' ');  //18-11-2014 replace newline and carriage return with a space
		$.get('/arduino/message/' + theMessage + '/'); // send to Arduino
		}
		}
		
	function updateVisitors() {
        var theMessage   = document.getElementById('visitors').value;
        if (theMessage != '') {
		$.get('/arduino/visitors/' + theMessage + '/'); // send visiting team name
		}
		}
		
		function updateHometeam() {
        var theMessage   = document.getElementById('hometeam').value;
        if (theMessage != '') {
		$.get('/arduino/hometeam/' + theMessage + '/'); // send the home team name (defaults to Seapoint but added in case we loan scoreboard out)
		}
		}

    </script>
    
  </head>
  
  <body style="background-image:url(seapointskin2.jpg)">
    <div style="padding: 0px 2px">
      <div style="background-color: rgb(36, 30, 30); border: 3px solid #999; width: 300px; height: 188px;">
        <div style="padding:20px">
          <canvas id="display" width="260" height="140"></canvas>
        </div>
      </div>
    </div>
	<form name="input" >
		<b>Home Score:</b> <p/><input type="number" id = "homescore" maxlength="2" size="3" min="0" style="font-size:40px;width:160px" onfocus="this.select()" ><p/>
		<b>Away Score:</b> <p/><input type="number" id ="awayscore" maxlength="2" size="3" min="0" style="font-size:40px;width:160px" onfocus="this.select()"><p/>
		<input type = "button" value="Score" style="height: 100px;width: 160px" onclick = updateScore()>
</form>
	<form name="showThis" >
		<b>Display Message </b><p/><textarea id = "showIt" rows="4" cols="50" onfocus="this.select()"></textarea><p/>
		<input type = "button" value="Message" style="height: 100px;width: 160px" onclick = updateMessage()>
</form>

</form>
	<form name="visitorsName" >
		<b>Todays Visitors </b><p/><textarea id = "visitors" rows="1" cols="50" onfocus="this.select()"></textarea><p/>
		<input type = "button" value="Visitors" style="height: 100px;width: 160px" onclick = updateVisitors()>
</form>

</form>
	<form name="hometeamName" >
		<b>Home team name</b><p/><textarea id = "hometeam" rows="1" cols="50" onfocus="this.select()"></textarea><p/>
		<input type = "button" value="Home team" style="height: 100px;width: 160px" onclick = updateHometeam()>
</form>
</body>
  
</html>
Arduino code for the Yun

Code: Select all

#include <Bridge.h>
#include <YunServer.h>
#include <YunClient.h>
#include <Wire.h>
#include <inttypes.h>
#include <SoftwareSerial.h>     

SoftwareSerial mySerial = SoftwareSerial(7, 8); // SoftwareSerial(rxPin, txPin)

YunServer server;
String textToDisplay, message;


 
 
void setup () {
   
  mySerial.begin(2400);
  Bridge.begin();
  Wire.begin();
  server.listenOnLocalhost();
  server.begin();  
 
   
} // end setup


  
void loop () {
  //*********Read new message from the client**************
  YunClient client = server.accept(); //check new clients
   
  if(client) {
    String command = client.readStringUntil('/');  //read the incoming data name
       textToDisplay = client.readStringUntil('/');
       message = command + ':' + textToDisplay;
       mySerial.print(message);
       mySerial.flush();
       client.stop(); 
       message = "";  
  }  // end if client()
  
   delay(50);
   
} // end loop
Arduino code to display score in big numbers on the top 4 DMDs

Code: Select all

#include <Wire.h>
#include <inttypes.h>
#include "SPI.h"      
#include "DMD.h" 
#include "TimerOne.h" 
#include "ScoreboardFont.h"

#define DISPLAYS_ACROSS 4   
#define DISPLAYS_DOWN 1       
/* change these values if you have more than one DMD connected */
DMD dmd(DISPLAYS_ACROSS,DISPLAYS_DOWN);
String inData, command, homescore, awayscore, showscore, hometeam, visitorteam; 
char digit;
int commaPosition;
long waitForIt;


void ScanDMD()
{ 
  dmd.scanDisplayBySPI();
}

void getScores() // parse out the Home and Away scores from HH:AA
{
  commaPosition = inData.indexOf(':'); // next colan delimits home team score
  homescore = inData.substring(0,commaPosition);
  inData = inData.substring(commaPosition+1, inData.length());
  awayscore = inData; // what remains is the away team score
}

void printScore() //Just prints the numeric scores - one on each panael
{
  dmd.selectFont( ScoreboardFont );
  dmd.clearScreen( true );
  showscore = homescore + "" + awayscore;
  for (int i = 0; i < 4; i++) {
   digit = showscore.charAt(i);
   if (digit == '0' and i == 0) { // replace leading zero with space home score
     digit = 32;
   }
      if (digit == '0' and i == 2) { // replace leading zero with space away score
     digit = 32;
   }

   dmd.drawChar(  int (0+(i*32)), 0, digit, GRAPHICS_NORMAL );

  }
}


void setup () {
   
  Serial.begin(2400);
  Timer1.initialize( 5000 );           
  /*period in microseconds to call ScanDMD. Anything longer than 5000 (5ms) and you can see flicker.*/

  Timer1.attachInterrupt( ScanDMD );  
  /*attach the Timer1 interrupt to ScanDMD which goes to dmd.scanDisplayBySPI()*/
   dmd.clearScreen( true );            
   /* true is normal (all pixels off), false is negative (all pixels on) */
   hometeam = "Seapoint "; // put a space after the team name
   visitorteam = " Visitors "; // put a space after the team name
   inData = "";
   command = "";
   showscore= "";
   homescore = "00";
   awayscore = "00";
   waitForIt = millis(); // initialise full display timer
} // end setup

void loop() {
 
  // main code here, to run repeatedly: 
  while (Serial.available() > 0) {
    char received = Serial.read();
        inData += received; 

        // Process message when new line character is received
        if (received == '\n')
        {
            Serial.print(inData); // pass it on
            Serial.flush(); // out the serial port to the next guy
  
            commaPosition = inData.indexOf(':'); // find first colon - piece before is command
            command = inData.substring(0,commaPosition);
            inData = inData.substring(commaPosition+1, inData.length()); //remove the bit before and including the first colon
            
            if (command == "score") 
            {
              getScores();     // inData now contains HH:AA (Home and Away)
              printScore();
              inData = ""; // Clear received buffer
            }
            else
            {
              inData = ""; // not a score - reset buffer
            }
          inData = "";
        } // end if received = '\n'   

     } // end while Serial.available() > 0

 } // end loop

Arduino code to scroll message and the score with team names on the bottom 2 DMDs

Code: Select all

#include <Wire.h>
#include <inttypes.h>
#include "SPI.h"      
#include "DMD.h" 
#include "TimerOne.h"
#include "Arial_black_16.h"<arial_black_16.h> 

// you can remove the fonts if unused
#define DISPLAYS_ACROSS 2   
#define DISPLAYS_DOWN 1       
/* change these values if you have more than one DMD connected */
DMD dmd(DISPLAYS_ACROSS,DISPLAYS_DOWN);
String inData, command, homescore, awayscore, showscore, hometeam, visitorteam, theMessage;
String scroll;
int commaPosition;
char charBuf[255];
long waitForScore, waitForMessage;

void ScanDMD()
{ 
  dmd.scanDisplayBySPI();
}

void printMessage() //scrolls the free text message across both panels
{
  dmd.selectFont( Arial_Black_16 );
  dmd.clearScreen( true );

  int sLength = theMessage.length()+1;
  theMessage.toCharArray(charBuf, sLength); // display home score on panel 1
 
  dmd.drawMarquee(charBuf, sLength, (32*DISPLAYS_ACROSS)-1,0);
  long start=millis();
  long timer=start;
  boolean ret=false;
  while(!ret){
    if ((timer+30) < millis()) {
      ret=dmd.stepMarquee(-1,0);
      timer=millis();
    }
  }
  memset(charBuf, 0, sizeof(charBuf));

}

void getScores() // parse out the Home and Away scores from HH:AA
{
  commaPosition = inData.indexOf(':'); // next colan delimits home team score
  homescore = inData.substring(0,commaPosition);
  inData = inData.substring(commaPosition+1, inData.length());
  awayscore = inData; // what remains is the away team score
}



void printNamesScore() //Scrolls team names and the score across the display
{
   dmd.selectFont( Arial_Black_16 );
   dmd.clearScreen( true );
   scroll = hometeam + homescore + visitorteam + awayscore;
   int sLength = scroll.length()+1;
   scroll.toCharArray(charBuf, sLength+1);
   dmd.drawMarquee(charBuf, sLength, (32*DISPLAYS_ACROSS)-1,0);
   long start=millis();
   long timer=start;
   boolean ret=false;
   while(!ret){
     if ((timer+30) < millis()) {
       ret=dmd.stepMarquee(-1,0);
       timer=millis();
     }
   }
   memset(charBuf, 0, sizeof(charBuf));
}


void setup () {
   
  Serial.begin(2400);
  Timer1.initialize( 5000 );           
  /*period in microseconds to call ScanDMD. Anything longer than 5000 (5ms) and you can see flicker.*/

  Timer1.attachInterrupt( ScanDMD );  
  /*attach the Timer1 interrupt to ScanDMD which goes to dmd.scanDisplayBySPI()*/
   dmd.selectFont( Arial_Black_16 );
   dmd.clearScreen( true );            
   /* true is normal (all pixels off), false is negative (all pixels on) */
   hometeam = " Seapoint "; // put a space around the default home team
   visitorteam = " Visitors "; // put a space around the default home team
   inData = "";
   command = "";
   showscore= "";
   homescore = "00"; // starting scores
   awayscore = "00";
   theMessage = "";
   memset(charBuf, 0, 0);
   waitForScore = millis(); // initialise full scrolling score timer
   waitForMessage = millis(); // initialise scrolling message timer
} // end setup

void loop() {
 
  // put your main code here, to run repeatedly: 
  while (Serial.available() > 0) {
    char received = Serial.read();
        inData += received; 

        // Process message when new line character is received
        if (received == '\n')
        {
            Serial.print("Arduino Received: ");
            Serial.print(inData);
  
            commaPosition = inData.indexOf(':'); // find first colon - piece before is command
            command = inData.substring(0,commaPosition);
            inData = inData.substring(commaPosition+1, inData.length()); //remove the bit before and the first colon
            
            if (command == "score") 
            {
              getScores();     // inData now contains HH:AA (Home and Away)
              printNamesScore(); //Scrolls team names and the score across the display
              delay(2000);
              inData = ""; // Clear received buffer
              waitForScore = millis(); // reset full display timer
            }
            else if (command == "message")
            {
              theMessage = inData;
              printMessage(); //scrolls the free text message across both panels
              delay(2000);
              inData = ""; // Clear received buffer
              waitForMessage = millis(); // reset message timer
            }
            else if (command = "visitors")
            {
              visitorteam = " " + inData + " ";
              printNamesScore(); //Scrolls team names and the score across the display
              delay(2000);
              inData = ""; // Clear received buffer
              waitForScore = millis(); // reset full display timer
            }
            else if (command = "hometeam")
            {
              hometeam = " " + inData + " ";
              printNamesScore(); //Scrolls team names and the score across the display
              delay(2000);
              inData = ""; // Clear received buffer
              waitForScore = millis(); // reset full display timer
            }
            else
            {
              // do nothing
            }
        }   

     }
 if ((millis() - waitForScore) > 300000) { // every 5 minutes
   printNamesScore(); //Scrolls team names and the score across the display
   delay(2000);
   waitForScore = millis(); // reset full display timer
   }
   
 if ((millis() - waitForMessage) > 180000) { // every 3 minutes
   printMessage(); //Scrolls team names and the score across the display
   delay(2000);
   waitForMessage = millis(); // reset full display timer
   }
 }


danieldewaard
Freetronics Staff
Freetronics Staff
Posts: 21
Joined: Thu Jul 14, 2016 2:41 am

Re: Yet another scoreboard

Post by danieldewaard » Sat Sep 03, 2016 11:08 pm

Hi mickot, Sounds very cool! Have you finished writing what you wanted to write or are you still going? Would you be ok with me posting this on the Freetronics blog when you are done? It would be awesome if you could attach some more pictures of the final display!

Daniel

Post Reply