My son was super excited with his junior foosball from Shenzhen. It was easy enough to assemble, and worked well except for the plastic scoreboard. An excellent opportunity for an electronics project!

Actually the electronics are fairly simple:

  • an Arduino Nano
  • an InfraRed sensor: IR LED + IR phototransistor
  • a 7-segment display
  • a buzzer
  • a switch and battery

IMG_20170916_154536 - Edited

Most of the effort went into the design of the wooden box, which i laser cut in 5mm plywood, and assembled with hot glue and screws. I chose to make 2 independent boxes, with no communication or any wires between them, otherwise it would be difficult to make the project kid-proof.

IMG_20170918_094332 - Edited

The disadvantage is that your score is shown on the box behind the opponent’s goal, while traditionally you keep your score behind your own goal. But it doesn’t take long to get used to (and the engraved text in above picture is upside down, i know).

IMG_20170918_094710 - Edited

Above is a picture of the original plastic box that catches the ball, with a plastic slider to keep the score. Below picture shows the inside of my box with its back panel removed; the rectangular cut fits with the opening of the foosball goal. The ball comes through and falls into the vertical pipe, momentarily obstructing the LED’s IR light beam (clear LED on the right) from reaching the IR phototransistor on the left (dark LED shaped component on the left). The IR LED is constant on, with a 220Ω resistor. The phototransistor forms a voltage divider with a 10KΩ resistor, going into an analog input of the Arduino (A0).

IMG_2816 - Edited

IMG_20170918_094348 - Edited

At the start of the program, in the setup() of the sketch, we measure the average IR light received, as a benchmark. Then later on in the loop() we compare this with our current IR light received and check if the difference exceeds a certain value, to trigger the ‘score’ event. Measuring an average in the setup() ensures that the project works in different ambient light conditions. Of course you have to make sure nothing obstructs the pipe when this average is calculated.

IMG_2814 - Edited

The score is displayed on a 7-segment display as above, with common negative (and a 220Ω resistor), and 7 digital outputs of the Arduino. The code needs to compose the different numbers as combinations of the 7 segments. And a simple buzzer using the tone() function.

IMG_2815 - Edited

I used 2x 15pin female headers so i can unplug the Arduino Nano when we don’t use the project anymore.

As i try not to use non-rechargeable batteries, i used a 18650 Lithium cell recovered from a laptop battery pack, with a charger/protection board (the blue PCB in the upper left of above picture of the box).

I did not bother with a reset button; we can just switch off and on again to restart.

Here’s my code:


/*
Arduino scoreboard for foosball

IR sensor on analog A0
7-segment LED common neg, top: 8,9,gnd,7,6 bottom: 12,11,gnd,10,nc
buzzer on D3

BuffaloLabs Sep 2017
*/

int score = 0;
int IRavg; // initial IR
int IRdif = 100; // IR sensitivity

boolean debug = false;

//////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////

void setup() {

if (debug) Serial.begin(9600);
if (debug) Serial.println("Foosball scoreboard");

pinMode(3, OUTPUT); // buzzer

pinMode(6, OUTPUT); // 7 segment LED common negative
pinMode(7, OUTPUT);
pinMode(8, OUTPUT);
pinMode(9, OUTPUT);
pinMode(10, OUTPUT);
pinMode(11, OUTPUT);
pinMode(12, OUTPUT);

for (int i = 0; i < 10; i++) {
show(-1);
tone(3, 1000);
delay(100);
show(score);
tone(3, 2000);
delay(100);
}
noTone(3);
IRavg = calcIR();
if (debug) Serial.print("IRavg = ");
if (debug) Serial.println(IRavg);
}

//////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////

void loop() {

if (checkIR()) { // goal?

sirenUpDown();

if (score == 9) { // goal, game finished

while (1) { // blink forever
tone(3, 2000);
show(8);
delay(100);
noTone(3);
show(-1);
delay(500);
}
}
else { // goal, add score
score++;
for (int i = 0; i < 10; i++) {
show(-2);
delay(100);
show(score);
delay(100);
}
}
}

// delay(100);
}

//////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////

void show(int n) {
if (debug) Serial.print("display: ");
if (debug) Serial.println(n);

switch (n) {
case -2: // all off
digitalWrite(6, LOW);
digitalWrite(7, LOW);
digitalWrite(8, LOW);
digitalWrite(9, LOW);
digitalWrite(10, LOW);
digitalWrite(11, LOW);
digitalWrite(12, LOW);
break;
case -1: // minus sign
digitalWrite(6, LOW);
digitalWrite(7, LOW);
digitalWrite(8, HIGH);
digitalWrite(9, LOW);
digitalWrite(10, LOW);
digitalWrite(11, LOW);
digitalWrite(12, LOW);
break;
case 0:
digitalWrite(6, HIGH);
digitalWrite(7, HIGH);
digitalWrite(8, LOW);
digitalWrite(9, HIGH);
digitalWrite(10, HIGH);
digitalWrite(11, HIGH);
digitalWrite(12, HIGH);
break;
case 1:
digitalWrite(6, HIGH);
digitalWrite(7, LOW);
digitalWrite(8, LOW);
digitalWrite(9, LOW);
digitalWrite(10, HIGH);
digitalWrite(11, LOW);
digitalWrite(12, LOW);
break;
case 2:
digitalWrite(6, HIGH);
digitalWrite(7, HIGH);
digitalWrite(8, HIGH);
digitalWrite(9, LOW);
digitalWrite(10, LOW);
digitalWrite(11, HIGH);
digitalWrite(12, HIGH);
break;
case 3:
digitalWrite(6, HIGH);
digitalWrite(7, HIGH);
digitalWrite(8, HIGH);
digitalWrite(9, LOW);
digitalWrite(10, HIGH);
digitalWrite(11, HIGH);
digitalWrite(12, LOW);
break;
case 4:
digitalWrite(6, HIGH);
digitalWrite(7, LOW);
digitalWrite(8, HIGH);
digitalWrite(9, HIGH);
digitalWrite(10, HIGH);
digitalWrite(11, LOW);
digitalWrite(12, LOW);
break;
case 5:
digitalWrite(6, LOW);
digitalWrite(7, HIGH);
digitalWrite(8, HIGH);
digitalWrite(9, HIGH);
digitalWrite(10, HIGH);
digitalWrite(11, HIGH);
digitalWrite(12, LOW);
break;
case 6:
digitalWrite(6, LOW);
digitalWrite(7, HIGH);
digitalWrite(8, HIGH);
digitalWrite(9, HIGH);
digitalWrite(10, HIGH);
digitalWrite(11, HIGH);
digitalWrite(12, HIGH);
break;
case 7:
digitalWrite(6, HIGH);
digitalWrite(7, HIGH);
digitalWrite(8, LOW);
digitalWrite(9, LOW);
digitalWrite(10, HIGH);
digitalWrite(11, LOW);
digitalWrite(12, LOW);
break;
case 8:
digitalWrite(6, HIGH);
digitalWrite(7, HIGH);
digitalWrite(8, HIGH);
digitalWrite(9, HIGH);
digitalWrite(10, HIGH);
digitalWrite(11, HIGH);
digitalWrite(12, HIGH);
break;
case 9:
digitalWrite(6, HIGH);
digitalWrite(7, HIGH);
digitalWrite(8, HIGH);
digitalWrite(9, HIGH);
digitalWrite(10, HIGH);
digitalWrite(11, HIGH);
digitalWrite(12, LOW);
break;
}
}

//////////////////////////////////////////////////////////////////////////////////

void sirenUpDown() {
for (int i = 1000; i < 2000; i++) { // siren up
tone(3, i);
delay(1);
}
for (int i = 2000; i > 1000; i--) { // siren down
tone(3, i);
delay(1);
}
noTone(3);
}

//////////////////////////////////////////////////////////////////////////////////

int calcIR() {
int reading = 0;
for (int i = 0; i < 10; i++) {
reading += analogRead(A0);
}
return reading / 10;
}

//////////////////////////////////////////////////////////////////////////////////

boolean checkIR() {
int reading = analogRead(A0);
if (debug) Serial.println(reading);

if (abs(reading - IRavg) > IRdif) return true; // no need for de-bounce as will only come back after siren

return false;
}

//////////////////////////////////////////////////////////////////////////////////

Leave a Reply

Your email address will not be published. Required fields are marked *