Reverse Engineering Basketball Score Board

Hey everyone, I was able to get my hands on a High Schools old basketball scoreboard and controller. Both work great and is pretty cool to play around with, but I want more! I want to reverse engineer the one-way serial data coming off the controller to the scoreboard. It uses a standard 1/4" plug with only ground and one data line. Using a logic analyzer I was able to interpret the signal being sent out of the board. The signal goes out a few times a second (may need to figure this out more accurately later) and if they controller is ever unplugged, the scoreboard goes blank. It must have a signal to stay lit. I have attached photos of the logic analyzer data(ignore the multiple lines, its only one signal line) and a photo of the scoreboard with a key to explain what each bit translates to on the board. I have went ahead and broke down the data to show what segments each set of 4 bits (or sometime just 1) control.

Now that I have most of that figured out, my next step is to make my Arduino copy this format so that the scoreboard will listen to the Arduino instead of the controller. This is where I am lost as of now. I am hoping you can give me some help here in getting this part done. After I am able to fully control the scoreboard, my next plan will be to use a RTC to make the period time show the current actual time. The next step after that (which will be quite a stretch) would be to have the board ping current live sports scores and update the real scoreboard in real time with any Sports game.

So Step One.. Lets get an Arduino to talk correctly to the scoreboard! You can see I have labeled most of the bits. Those first two BYTES never change. After that you can see each individual portion. There area a couple of unknown bits that I can not get to change, but I assume this language is for different type of scoreboards and may be used on other models with more displays.


1 Like

Rather than getting an Arduino to control the display, I think I would start by having one read the output from the controller using software serial (or preferably hardware serial if you have several) so you can figure out what's being sent without having to relay on the Saleae bit by bit. I'll guess that's 4800 baud. What voltage is it though?

I attempted to do that using my arduino and serial reader on the software. I got just jumbled garbage no matter what baud rate I used. And it also wouldn't show regular intervals. Just spontaneous data. I believe it's 5v based.

It sounds like it may be some proprietary format then and you'll need to bit bang it. I still think reading is the way to decipher it though. Working with the controller, you should be able to pick one item to manipulate and observe how it changes. Looking at one sample it's a puzzle.

i may be confused by what you are suggesting. I feel like I have done that. If you look at the photo of the breakdown, I viewed the data with the logic analyzer and started sending the screen data with the controller and I was able to read the changes with the analyzer. Thats how I figured how each bit was being used and read.

Using that information, how would you suggest setting up my Arduino to spit out the same information at the same speed?

It looks like a self clocking data format. The initial pulses are 1's or 0's (typically 1's) to sync the clock. Look at Manchester encoding or phase encoding and see if that fits what you are seeing. It could be msb or lsb first so look at both possibilities. Your F digit could be 1111 to indicate a blank.

It looks like it's about 32k baud (about 32 us per bit), which is pretty fast for bit banging on a 8 bit / 16 MHz Arduino, but probably within the realm of possibility.

The coding is a "1" bit is low for ~75% of the bit period then high (say 24 us/8 us) while a "0" is low for ~25% of the bit period then high (8 us/24 us).

The sequence appears to be a stream of "0" (probably to synchronize receiver clocking), 4 bits of "1" (your first "unknown" field (probably a "start of message"), 4 bits "foul player #", and so forth.

As a test, one could code up functions for a "zero_bit" that uses "micros()" to set the output low/high on that period and similarly for "one_bit". String together a series of these and look at them on your logic analyzer. Adjust as necessary to reproduce the signal you've shown us. Smoke test with the display unit . . .

If that works, revisit implementation to do whatever else needs to be done.

On reflection, I think I'd start with putting the Arduino between the controller & display and see if you can make it work by just echoing the bits. Then see what happens if you tweak a few of them.

You should be able to read the signal by connecting it to an interrupt pin and interrupt on a falling edge. In the ISR, delay 16 microseconds and sample the data line. LOW == 1, HIGH == 0.

To output the signal you can bit-bang the output with interrupts disabled:

void SendOne()
{
  digitalWrite(OutputPin, LOW);
  delayMicroseconds(24);
  digitalWrite(OutputPin, HIGH);
  delayMicroseconds(8);
}

void SendZero()
{
  digitalWrite(OutputPin, LOW);
  delayMicroseconds(8);
  digitalWrite(OutputPin, HIGH);
  delayMicroseconds(24);
}

Note: digitalWrite() takes a while so you will probably need to remove 4 to 6 microseconds from each delay time to get the timing right.

Ok.. so here is what I wrote out at the simplest form. Unfortunately, they microsecond delay isn't accurate when it comes to viewing it on the analyzer. But I estimated as much as I could.

I have attached a screen shot of both signals being compared. D0 is the original signal from the controller and D4 is the Arduino. You can tell its just slightly shorter than the original, but having a hard time fine tuning the timing to make it work.



  int oneHigh = 3;
  int oneLow = 17;
  int zeroHigh = 3;
  int zeroLow = 17;
  int byteDelay = 11;



void setup() {
 
  pinMode(14, OUTPUT);
 
  
}


void loop() {




   for (int i = 0; i < 8; i++) {
    
  digitalWrite(14, LOW); 
  delayMicroseconds(oneHigh);                   
  digitalWrite(14, HIGH);  
  delayMicroseconds(oneLow);
   
  }



  delayMicroseconds(byteDelay);



   for (int i = 0; i < 8; i++) {
    
  digitalWrite(14, LOW); 
  delayMicroseconds(oneHigh);                   
  digitalWrite(14, HIGH);  
  delayMicroseconds(oneLow);
   
  }



  delayMicroseconds(byteDelay);

//UNKNOWN
   for (int i = 0; i < 4; i++) {
    
  digitalWrite(14, LOW); 
  delayMicroseconds(oneLow);                   
  digitalWrite(14, HIGH);  
  delayMicroseconds(oneHigh);
   
  }

//Foul Player #
  digitalWrite(14, LOW); 
  delayMicroseconds(oneLow);                   
  digitalWrite(14, HIGH);  
  delayMicroseconds(oneHigh);

  digitalWrite(14, LOW); 
  delayMicroseconds(oneHigh);                   
  digitalWrite(14, HIGH);  
  delayMicroseconds(oneLow);

  digitalWrite(14, LOW); 
  delayMicroseconds(oneHigh);                   
  digitalWrite(14, HIGH);  
  delayMicroseconds(oneLow);

  digitalWrite(14, LOW); 
  delayMicroseconds(oneLow);                   
  digitalWrite(14, HIGH);  
  delayMicroseconds(oneHigh);
  
delayMicroseconds(byteDelay);

// Foul Player TENS
  digitalWrite(14, LOW); 
  delayMicroseconds(oneLow);                   
  digitalWrite(14, HIGH);  
  delayMicroseconds(oneHigh);

  digitalWrite(14, LOW); 
  delayMicroseconds(oneHigh);                   
  digitalWrite(14, HIGH);  
  delayMicroseconds(oneLow);

  digitalWrite(14, LOW); 
  delayMicroseconds(oneHigh);                   
  digitalWrite(14, HIGH);  
  delayMicroseconds(oneLow);

  digitalWrite(14, LOW); 
  delayMicroseconds(oneLow);                   
  digitalWrite(14, HIGH);  
  delayMicroseconds(oneHigh);

// Foul Player ONES  
  digitalWrite(14, LOW); 
  delayMicroseconds(oneLow);                   
  digitalWrite(14, HIGH);  
  delayMicroseconds(oneHigh);

  digitalWrite(14, LOW); 
  delayMicroseconds(oneHigh);                   
  digitalWrite(14, HIGH);  
  delayMicroseconds(oneLow);

  digitalWrite(14, LOW); 
  delayMicroseconds(oneHigh);                   
  digitalWrite(14, HIGH);  
  delayMicroseconds(oneLow);

  digitalWrite(14, LOW); 
  delayMicroseconds(oneLow);                   
  digitalWrite(14, HIGH);  
  delayMicroseconds(oneHigh);

  
delayMicroseconds(byteDelay);

//Visitor Fouls TENS

  digitalWrite(14, LOW); 
  delayMicroseconds(oneLow);                   
  digitalWrite(14, HIGH);  
  delayMicroseconds(oneHigh);

  digitalWrite(14, LOW); 
  delayMicroseconds(oneLow);                   
  digitalWrite(14, HIGH);  
  delayMicroseconds(oneHigh);

  digitalWrite(14, LOW); 
  delayMicroseconds(oneLow);                   
  digitalWrite(14, HIGH);  
  delayMicroseconds(oneHigh);

  digitalWrite(14, LOW); 
  delayMicroseconds(oneLow);                   
  digitalWrite(14, HIGH);  
  delayMicroseconds(oneHigh);

//Visitor Fouls ONES

 digitalWrite(14, LOW); 
  delayMicroseconds(oneHigh);                   
  digitalWrite(14, HIGH);  
  delayMicroseconds(oneLow);

 digitalWrite(14, LOW); 
  delayMicroseconds(oneHigh);                   
  digitalWrite(14, HIGH);  
  delayMicroseconds(oneLow);

    digitalWrite(14, LOW); 
  delayMicroseconds(oneLow);                   
  digitalWrite(14, HIGH);  
  delayMicroseconds(oneHigh);

    digitalWrite(14, LOW); 
  delayMicroseconds(oneLow);                   
  digitalWrite(14, HIGH);  
  delayMicroseconds(oneHigh);



delayMicroseconds(byteDelay);




//Home Fouls TENS

  digitalWrite(14, LOW); 
  delayMicroseconds(oneLow);                   
  digitalWrite(14, HIGH);  
  delayMicroseconds(oneHigh);

  digitalWrite(14, LOW); 
  delayMicroseconds(oneLow);                   
  digitalWrite(14, HIGH);  
  delayMicroseconds(oneHigh);

  digitalWrite(14, LOW); 
  delayMicroseconds(oneLow);                   
  digitalWrite(14, HIGH);  
  delayMicroseconds(oneHigh);

  digitalWrite(14, LOW); 
  delayMicroseconds(oneLow);                   
  digitalWrite(14, HIGH);  
  delayMicroseconds(oneHigh);


//Home Fouls ONES

  digitalWrite(14, LOW); 
  delayMicroseconds(oneHigh);                   
  digitalWrite(14, HIGH);  
  delayMicroseconds(oneLow);

  digitalWrite(14, LOW); 
  delayMicroseconds(oneLow);                   
  digitalWrite(14, HIGH);  
  delayMicroseconds(oneHigh);

  digitalWrite(14, LOW); 
  delayMicroseconds(oneLow);                   
  digitalWrite(14, HIGH);  
  delayMicroseconds(oneHigh);

  digitalWrite(14, LOW); 
  delayMicroseconds(oneLow);                   
  digitalWrite(14, HIGH);  
  delayMicroseconds(oneHigh);
  

delayMicroseconds(byteDelay);

//UNKNOWN
  digitalWrite(14, LOW); 
  delayMicroseconds(oneLow);                   
  digitalWrite(14, HIGH);  
  delayMicroseconds(oneHigh);
//UNKNOWN
   digitalWrite(14, LOW); 
  delayMicroseconds(oneHigh);                   
  digitalWrite(14, HIGH);  
  delayMicroseconds(oneLow);

   digitalWrite(14, LOW); 
  delayMicroseconds(oneHigh);                   
  digitalWrite(14, HIGH);  
  delayMicroseconds(oneLow);
//UNKNOWN

  digitalWrite(14, LOW); 
  delayMicroseconds(oneLow);                   
  digitalWrite(14, HIGH);  
  delayMicroseconds(oneHigh);

    digitalWrite(14, LOW); 
  delayMicroseconds(oneLow);                   
  digitalWrite(14, HIGH);  
  delayMicroseconds(oneHigh);

    digitalWrite(14, LOW); 
  delayMicroseconds(oneLow);                   
  digitalWrite(14, HIGH);  
  delayMicroseconds(oneHigh);

    digitalWrite(14, LOW); 
  delayMicroseconds(oneLow);                   
  digitalWrite(14, HIGH);  
  delayMicroseconds(oneHigh);

    digitalWrite(14, LOW); 
  delayMicroseconds(oneLow);                   
  digitalWrite(14, HIGH);  
  delayMicroseconds(oneHigh);

delayMicroseconds(byteDelay);


//Home Score HUNDREDS

  digitalWrite(14, LOW); 
  delayMicroseconds(oneLow);                   
  digitalWrite(14, HIGH);  
  delayMicroseconds(oneHigh);

//Visitor Score HUNDREDS

  digitalWrite(14, LOW); 
  delayMicroseconds(oneHigh);                   
  digitalWrite(14, HIGH);  
  delayMicroseconds(oneLow);


//Home BONUS

  digitalWrite(14, LOW); 
  delayMicroseconds(oneLow);                   
  digitalWrite(14, HIGH);  
  delayMicroseconds(oneHigh);

//Visitor BONUS

  digitalWrite(14, LOW); 
  delayMicroseconds(oneLow);                   
  digitalWrite(14, HIGH);  
  delayMicroseconds(oneHigh);

//Home POSS

  digitalWrite(14, LOW); 
  delayMicroseconds(oneLow);                   
  digitalWrite(14, HIGH);  
  delayMicroseconds(oneHigh);

//Visitor POSS

  digitalWrite(14, LOW); 
  delayMicroseconds(oneHigh);                   
  digitalWrite(14, HIGH);  
  delayMicroseconds(oneLow);

//UNKNOWN

  digitalWrite(14, LOW); 
  delayMicroseconds(oneHigh);                   
  digitalWrite(14, HIGH);  
  delayMicroseconds(oneLow);

  digitalWrite(14, LOW); 
  delayMicroseconds(oneLow);                   
  digitalWrite(14, HIGH);  
  delayMicroseconds(oneHigh);


delayMicroseconds(byteDelay);

//UNKNOWN

  digitalWrite(14, LOW); 
  delayMicroseconds(oneLow);                   
  digitalWrite(14, HIGH);  
  delayMicroseconds(oneHigh);

  digitalWrite(14, LOW); 
  delayMicroseconds(oneLow);                   
  digitalWrite(14, HIGH);  
  delayMicroseconds(oneHigh);

  digitalWrite(14, LOW); 
  delayMicroseconds(oneLow);                   
  digitalWrite(14, HIGH);  
  delayMicroseconds(oneHigh);

  digitalWrite(14, LOW); 
  delayMicroseconds(oneLow);                   
  digitalWrite(14, HIGH);  
  delayMicroseconds(oneHigh);

  //PERIOD

  digitalWrite(14, LOW); 
  delayMicroseconds(oneHigh);                   
  digitalWrite(14, HIGH);  
  delayMicroseconds(oneLow);

  digitalWrite(14, LOW); 
  delayMicroseconds(oneHigh);                   
  digitalWrite(14, HIGH);  
  delayMicroseconds(oneLow);

  digitalWrite(14, LOW); 
  delayMicroseconds(oneLow);                   
  digitalWrite(14, HIGH);  
  delayMicroseconds(oneHigh);

  digitalWrite(14, LOW); 
  delayMicroseconds(oneLow);                   
  digitalWrite(14, HIGH);  
  delayMicroseconds(oneHigh);


delayMicroseconds(byteDelay);


//Visitor Score TENS
  digitalWrite(14, LOW); 
  delayMicroseconds(oneLow);                   
  digitalWrite(14, HIGH);  
  delayMicroseconds(oneHigh);

  digitalWrite(14, LOW); 
  delayMicroseconds(oneHigh);                   
  digitalWrite(14, HIGH);  
  delayMicroseconds(oneLow);

  digitalWrite(14, LOW); 
  delayMicroseconds(oneHigh);                   
  digitalWrite(14, HIGH);  
  delayMicroseconds(oneLow);


  digitalWrite(14, LOW); 
  delayMicroseconds(oneLow);                   
  digitalWrite(14, HIGH);  
  delayMicroseconds(oneHigh);

  //Visitor Score ONES
  
  digitalWrite(14, LOW); 
  delayMicroseconds(oneLow);                   
  digitalWrite(14, HIGH);  
  delayMicroseconds(oneHigh);

  digitalWrite(14, LOW); 
  delayMicroseconds(oneHigh);                   
  digitalWrite(14, HIGH);  
  delayMicroseconds(oneLow);

  digitalWrite(14, LOW); 
  delayMicroseconds(oneHigh);                   
  digitalWrite(14, HIGH);  
  delayMicroseconds(oneLow);


  digitalWrite(14, LOW); 
  delayMicroseconds(oneLow);                   
  digitalWrite(14, HIGH);  
  delayMicroseconds(oneHigh);

delayMicroseconds(byteDelay);


//Home Score TENS

  digitalWrite(14, LOW); 
  delayMicroseconds(oneHigh);                   
  digitalWrite(14, HIGH);  
  delayMicroseconds(oneLow);


  digitalWrite(14, LOW); 
  delayMicroseconds(oneLow);                   
  digitalWrite(14, HIGH);  
  delayMicroseconds(oneHigh);


  digitalWrite(14, LOW); 
  delayMicroseconds(oneLow);                   
  digitalWrite(14, HIGH);  
  delayMicroseconds(oneHigh);


  digitalWrite(14, LOW); 
  delayMicroseconds(oneLow);                   
  digitalWrite(14, HIGH);  
  delayMicroseconds(oneHigh);


//Home Score ONES


  digitalWrite(14, LOW); 
  delayMicroseconds(oneHigh);                   
  digitalWrite(14, HIGH);  
  delayMicroseconds(oneLow);

  digitalWrite(14, LOW); 
  delayMicroseconds(oneHigh);                   
  digitalWrite(14, HIGH);  
  delayMicroseconds(oneLow);

  digitalWrite(14, LOW); 
  delayMicroseconds(oneLow);                   
  digitalWrite(14, HIGH);  
  delayMicroseconds(oneHigh);

  digitalWrite(14, LOW); 
  delayMicroseconds(oneLow);                   
  digitalWrite(14, HIGH);  
  delayMicroseconds(oneHigh);


delayMicroseconds(byteDelay);

//Seconds TENS

  digitalWrite(14, LOW); 
  delayMicroseconds(oneHigh);                   
  digitalWrite(14, HIGH);  
  delayMicroseconds(oneLow);

  digitalWrite(14, LOW); 
  delayMicroseconds(oneLow);                   
  digitalWrite(14, HIGH);  
  delayMicroseconds(oneHigh);

  digitalWrite(14, LOW); 
  delayMicroseconds(oneHigh);                   
  digitalWrite(14, HIGH);  
  delayMicroseconds(oneLow);

  digitalWrite(14, LOW); 
  delayMicroseconds(oneHigh);                   
  digitalWrite(14, HIGH);  
  delayMicroseconds(oneLow);

//Seconds ONES


  digitalWrite(14, LOW); 
  delayMicroseconds(oneHigh);                   
  digitalWrite(14, HIGH);  
  delayMicroseconds(oneLow);

  digitalWrite(14, LOW); 
  delayMicroseconds(oneHigh);                   
  digitalWrite(14, HIGH);  
  delayMicroseconds(oneLow);  

  digitalWrite(14, LOW); 
  delayMicroseconds(oneLow);                   
  digitalWrite(14, HIGH);  
  delayMicroseconds(oneHigh);

  digitalWrite(14, LOW); 
  delayMicroseconds(oneLow);                   
  digitalWrite(14, HIGH);  
  delayMicroseconds(oneHigh);  


delayMicroseconds(byteDelay);


//Minutes TENS

  digitalWrite(14, LOW); 
  delayMicroseconds(oneLow);                   
  digitalWrite(14, HIGH);  
  delayMicroseconds(oneHigh);

  digitalWrite(14, LOW); 
  delayMicroseconds(oneHigh);                   
  digitalWrite(14, HIGH);  
  delayMicroseconds(oneLow);

  digitalWrite(14, LOW); 
  delayMicroseconds(oneHigh);                   
  digitalWrite(14, HIGH);  
  delayMicroseconds(oneLow);


  digitalWrite(14, LOW); 
  delayMicroseconds(oneLow);                   
  digitalWrite(14, HIGH);  
  delayMicroseconds(oneHigh);

//Minutes ONES

  digitalWrite(14, LOW); 
  delayMicroseconds(oneLow);                   
  digitalWrite(14, HIGH);  
  delayMicroseconds(oneHigh);

  digitalWrite(14, LOW); 
  delayMicroseconds(oneHigh);                   
  digitalWrite(14, HIGH);  
  delayMicroseconds(oneLow);

  digitalWrite(14, LOW); 
  delayMicroseconds(oneHigh);                   
  digitalWrite(14, HIGH);  
  delayMicroseconds(oneLow);


  digitalWrite(14, LOW); 
  delayMicroseconds(oneLow);                   
  digitalWrite(14, HIGH);  
  delayMicroseconds(oneHigh);  


delayMicroseconds(byteDelay);


delay(45);








  

          
}

As noted in post #6, the code looks to be "self clocking" in that the bit rate can be extracted from the signal. Ideally the only thing the receiver cares about is that the low period be longer or shorter than the high period, but we don't know the receiver implementation so as to hazard a guess as to how sensitive it might be to bit rate.

I'll be surprised if what you have isn't good enough.

Legal Disclaimer: I'm frequently wrong about stuff and have (much more rarely) blown stuff up as a consequence.

So my example did NOT work. The board wont even flicker, just sits there like its getting no signal. Pretty frustrating, I thought it would work with that signal. Looks almost identical, just a couple microseconds quicker. I even tried it slower than original signal. Still nothing.

Here is where the data enters the scoreboard..
I need to look up these ICs... But could they just be shift registers?

Here is the full boards with mosfets. You can see that they line up with the order the data is being sent to the board. As for minutes seconds scores ext.


The middle column (under the "IN" of "DATA IN") of ICs are shift register. Presumably the bits of your serial stream get shifted into these.

To their right are a column of 7-segment decoders that take groups of 4 bit info from the shift register bank and convert to display segments.

The left most column of circuitry is probably what is taking the the input stream and extracting the ones and zeros as well as the clock extracted from the input stream to clock the shift registers. On top is an optoisolator to mitigate ground loops, a dual 4 input NAND gate, and two timer chips. It should be pretty straightforward to trace through that logic. My guess is that the 74LS123s are triggered on edge transitions of the input signal and the outputs are somehow combined by the NAND gate to recover the data and the clock to drive the shift register chain.

You'll probably want to go back to your working controller and use the logic analyzer to see what that's supposed to look like.

This is exactly what the board puts out. The Top (D0) is what the working controller is putting out constantly. The one below is what my arduino is putting out.

Am I missing something? I feel like I have all the 1 and 0s correct. I must not be picking up on somthing.

In fact, here is the most recent comparison.. I got it timed out a little more closely. Again, top is the working controller, bottom is the Arduino.

Dont worry about the inaccuracy of the 3rd and 4th byte, that's for player foul and resets itself after 10 seconds, I just didnt take a screenshot before it reset. The rest is identical

Have you looked at a zoomed in sample of a couple bits at your logic analyzer's maximum sampling rate? I guessed at the 25% / 75% pulse width ratios above, but one can't really tell at zoomed out resolution.

If my hypothesis about the receiving circuit is reasonably close, that is it's timing is governed by resistor/capacitor controlled one-shot timers, then the bit rate can't be all that critical, but the pulse widths might be more so.

Here is the first 4 bits as close as I can get.

According to the analyzer the LOWs at 5 microseconds and the HIGHs are 22.5 microseconds.

My guess is that for every falling edge of the input signal the previous bit gets shifted into the shift register chain and some fixed time later the signal is latched to be the next data bit. When the signal goes HIGH for a while the shift registers are latched. The header bits just get shifted off the end before the data latches. That trim pot near U4 may be a trimmer for one of the one-shot timers.

Connecting the logic analyzer to the data pin, the first shift register input pin, the shift register clock pin. and the shift register latch pin, will tell you a lot about how the hardware parses the data.