Project showcase
Build a Pinball Machine

Build a Pinball Machine © GPL3+

Build a fully functional pinball machine with 4 networked Arduinos: Control, Audio, Lights, Score, with this code and my free E-book.

  • 3,487 views
  • 9 comments
  • 13 respects

Components and supplies

About this project

This is the code that I used to control the pinball machine that I built and then exhibited at the Espacio Gallery, London in May 2017. The pinball machine is called "Lost Soul" and can be seen working in a video on my website, pinballdesign.com. On this website is an e_book on how to build a pinball machine, that you may download for free. It has animations of all the devices such as pop bumpers and it contains all the necessary circuit diagrams, circuit board designs and technical information. The code below is in support of this e-book.

I believe the code is written such that it could be readily adapted to control any pinball machine of similar complexity. The machine has a “master” Arduino Mega that controls the firing of the devices' coils. It also controls, via 12C communications, three slave Arduino Unos for: lighting, sound and score. As a result of this electronics/software architecture, it is modular and thus relatively simple to test, fault find and fix.

The picture is of the underside of my pinball machine. Mechanisms that can be seen in this photo include: 3 pop bumpers, 2 flippers, 2 slingshots and 1 ball eject. See my website pinballdesign.com to see video clips of this and the other machines being played.

Code

PinballControl_14.inoArduino
This controls the game. Its triggers are the rollover switches on the playfield and the switches in the devices such as pop bumpers, targets etc. It causes coils within the devices to be energized and also triggers, via 12C comms, actions such as light and sound sequences which are controlled by the slave Arduinos.
#include <arduino.h>
#include <Wire.h>
#include "names.h"
#include "types.h"
#include <PBWire.h>

unsigned long suppressUntilMillis = 0;
/*  May be used to ignore all switches for period
 *  defined by the attribute globalDebounceMs as 
 *  part of the switchPB structure defined in types.h
 */

//Game control variables
int lives = 0;
long hiScore = 0;
unsigned long lifeStart = 0;
byte gameInProgress = 0;
byte newBallIndex = 0;
byte dropTargetIndex = 0;
byte dropTargetRaised = 0;

long score = 0;

void setup() {
  
  Serial.begin(9600);
  Wire.begin();    
  Serial.println("Starting Lost & Found");
  Serial.print("Version: ");
  Serial.println(VERSION);


  switchPbSetup();
  //identify the array elements for drop target and new ball coils
  for(int x = 0; x < TOTAL_SWITCHES; x++){
    if(switchPB[x].coilPin == NEW_BALL_C){
      newBallIndex = x;
    }
    if(switchPB[x].coilPin == DROP_TARGET_C){
      dropTargetIndex = x;
    }
  }  

  //!!Start up lights messages here
  Wire.beginTransmission(MSG_LIGHTS_ADDR);
  //Wire.write(YOUR_MESSAGE_HERE);
  Wire.endTransmission(); 
  #ifdef DEBUG
    Serial.println("Startup message sent to lights: ");
  #endif
}


void loop() {
  //is it time to raise the drop target?
  if(!dropTargetRaised){
    if(millis() > lifeStart + DROP_TARGET_RAISE_MILLIS){
      activateCoil(dropTargetIndex);
      dropTargetRaised = 1;
      
      #ifdef DEBUG
        Serial.println("drop target raised");
      #endif

      Wire.beginTransmission(MSG_LIGHTS_ADDR);
      Wire.write(SPOT_L);
      Wire.endTransmission(); 

      Wire.beginTransmission(MSG_SOUNDBOARD_ADDR);
      //Wire.write(YOUR MESSAGE HERE); 
      Wire.endTransmission();
      
      #ifdef DEBUG
        Serial.println("drop target lights on");
      #endif
    }
  }
  
  //loop through all the switches
  for(int x = 0; x < TOTAL_SWITCHES; x++){
  /*if the coil has already been activated check to see if 
   * it needs to be de-activated*/
    if(switchPB[x].coilState){                                      //   \
      //check the time it was activated agains the system time      //    | Coil is activated -
      if(millis() > (switchPB[x].timeStampHigh + COIL_MILLIS)){     //    | Switches off the
                                                                    //    | coil after the
        digitalWrite(switchPB[x].coilPin, LOW);                     //    | time defined in 
        //reset the values to show the coil is now de-activated     //    | COIL_MILLIS
        switchPB[x].coilState = 0;                                  //    |
      }                                                             //   /
    }
      
    if(!switchPB[x].coilState) {                                              //   \
      //check to see if we're still waiting to debounce the switch            //    | Coil is not activated -
      if(millis() > (switchPB[x].timeStampHigh + switchPB[x].debounceMs)){    //    | Checks to see if the
        switchPB[x].timeStampHigh = 0;                                        //    | switch has debounced
      }                                                                       //    | 

      if(suppressUntilMillis){                    // Ignore switches if a global
        if(suppressUntilMillis < millis()){       // debounce has not yet expired
          suppressUntilMillis = 0;                // (see variable declaration for more info)
        }
      }else{
        //only check for a new trigger if timeHigh is 0                         //    | if the switch has debounced
        int switchState = digitalRead(switchPB[x].switchPin);
        
        if (switchState){switchPB[x].lastState = 1;} // if the switch is open update last state.
        
        if (!switchPB[x].timeStampHigh && !switchState && switchPB[x].lastState){ //    | then takes action
          
          switchPB[x].lastState = 0; //record that the switch is now closed
          //**************************
          //***Game control actions***
          //**************************
          //Delay for ball eject
          if(switchPB[x].switchPin == BALL_EJECT_S){
            Wire.beginTransmission(MSG_SOUNDBOARD_ADDR);
            Wire.write(FAIRGROUND_SND); 
            Wire.endTransmission();
            delay(BALL_EJECT_DELAY_MILLIS);
          }

          //Action for Drain
          if(switchPB[x].switchPin == DRAIN_S){

            if(lives){
              lives--;
            }
            #ifdef DEBUG
              Serial.println("ball in drain");
              Serial.print(lives);
              Serial.print(" lives remaining");
            #endif
            if(lives > 0){
              //need to wait for the ball to reach ball return mechanism
              delay(DRAIN_TO_BALL_RETURN_MILLIS);
              activateCoil(newBallIndex);
            } else {
              //GAME OVER
              gameInProgress = 0;
              #ifdef DEBUG
                Serial.println("game over!");
              #endif
              //send messages to scoreboard, sound etc.
            }     
          }
          
          //Action for NEW_BALL [game]
          if(switchPB[x].switchPin == NEW_BALL_S){
            #ifdef DEBUG
              Serial.println("new ball button pressed");
              Serial.print(lives);
              Serial.println(" lives remaining in current game");
            #endif
            if(!gameInProgress){
              #ifdef DEBUG
                Serial.println("New Game - score reset");
              #endif
              lives = LIVES;
              score = 0;  
              sendScore();
              activateCoil(newBallIndex);
              gameInProgress = 1;        
            }
          }
          //Action for entry lane
          if(switchPB[x].switchPin == ENTRY_LANE_S){
            lifeStart = millis();
            #ifdef DEBUG
              Serial.println("New Life");
            #endif
            
          }
          if(switchPB[x].switchPin == DROP_TARGET_S){
            dropTargetRaised = 0;
            lifeStart = millis();
          }
          //*********************************
          //***end of game control actions***
          //*********************************
          
          if(switchPB[x].coilPin){
              activateCoil(x);           
          }
          if(switchPB[x].soundMsg){
            Wire.beginTransmission(MSG_SOUNDBOARD_ADDR);
            Wire.write(switchPB[x].soundMsg);
            Wire.endTransmission(); 
            #ifdef DEBUG
              Serial.print("Message sent to soundboard: ");
              Serial.println(switchPB[x].soundMsg);
            #endif
          }
          if(switchPB[x].lightsMsg){
  
            Wire.beginTransmission(MSG_LIGHTS_ADDR);
            Wire.write(switchPB[x].lightsMsg);
            Wire.endTransmission(); 
            #ifdef DEBUG
              Serial.print("Message sent to lights: ");
              Serial.println(switchPB[x].lightsMsg);
            #endif
          } 
          if(switchPB[x].rouletteMsg){
  
            Wire.beginTransmission(MSG_ROULETTE_ADDR);
            Wire.write(switchPB[x].rouletteMsg);
            Wire.endTransmission(); 
            #ifdef DEBUG
              Serial.print("Message sent to roulette: ");
              Serial.println(switchPB[x].rouletteMsg);
            #endif
          } 
            if(switchPB[x].scoreIncrement){
            updateScore(switchPB[x].scoreIncrement);
          }                                 
        }
      }
    }
  }
}
functions.inoArduino
This is a “tab” file for PinballControl_14.ino. It maps each of the switches to the set of functions that it has to initiate.
void switchPbSetup(){

  /*Set up the mapping of switches, coils, varaibles for timing and I2S messages.
   *The structure 'switchPB' and array are declared in types.h.
   *Elements are in the following order
   *  1 - switchPin - the pin to which the switch is connected
   *  2 - coilPin - the pin to which the coil is connected (0 if none connected)
   *  3 - coilState - *not set here - used to record whether a coil is currently activated
   *  4 - timeStampHigh - *not set here - used to record the point in time (ms) that a coil was activated
   *  5 - debounceMs - time in milliseconds to debounce the switch
   *  6 - soundMsg - I2C message to be sent to the sound board when switch activates
   *  7 - rouletteMsg - I2C message to be sent to the roulette board
   *  8 - lightsMsg - I2C message to be sent to the sound board
   *  9 - scoreIncrement - I2C message to send the default score for the switch
   *  10 - globalDebounceMs - Number of milliseconds to stop reacting to all switches
   *  *Note* - score may be changed by multipliers in main code.
   *  11 - lastState - used to record the last state of a switch to ensure a switch held closed 
   *  is not repeatedly acted upon. 1 --> Open    0--> Closed
   */
  switchPB[0] = {ENTRY_LANE_S, NOTHING, 0, 0, 0, ORGAN_LOUD_SND, NO_ROULETTE, CURVE_L, 0, 0,1};
  
  switchPB[1] = {GATE_LEFT_S, NOTHING, 0, 0, 0, HAMMOND_SND, NO_ROULETTE, DRINK_L, 1000, 0,1};
  
  switchPB[2] = {GATE_CENTRE_S, NOTHING, 0, 0, 0, HAMMOND_SND, NO_ROULETTE, SEX_L, 5000, 0,1};
  
  switchPB[3] = {GATE_RIGHT_S, NOTHING, 0, 0, 0, HAMMOND_SND, NO_ROULETTE, SMOKE_L, 1000, 0,1};
  
  switchPB[4] = {POP_BUMPER_LEFT_S, POP_BUMPER_LEFT_C, 0, 0, 200, NO_SOUND, NO_ROULETTE, NO_LIGHTS, 3000, 200,1};
  
  switchPB[5] = {POP_BUMPER_CENTRE_S, POP_BUMPER_CENTRE_C, 0, 0, 200, NO_SOUND, NO_ROULETTE, NO_LIGHTS, 3000, 200,1};
  
  switchPB[6] = {POP_BUPER_RIGHT_S, POP_BUMPER_RIGHT_C, 0, 0, 2, NO_SOUND, NO_ROULETTE, NO_LIGHTS, 500, 3000,1};
  
  switchPB[7] = {DROP_TARGET_S, NOTHING, 0, 0, 200, NO_SOUND, NO_ROULETTE, NO_LIGHTS, 100000, 0,0};
  
  switchPB[8] = {SPINNER_S,NOTHING, 0, 0, 0, NO_SOUND, NO_ROULETTE, SPINNER_L, 3000, 0};
  
  switchPB[9] = {BALL_EJECT_S, BALL_EJECT_C, 0, 0, 0, 0, NO_ROULETTE, CURVE_L, 200000, 0,1};
  
  switchPB[10] = {TARGET_LEFT_S, NOTHING, 0, 0, 0, ROULETTE_SND, SPIN_CCW, NO_LIGHTS, 30000, 0,1};
  
  switchPB[11] = {TARGET_RIGHT_S, NOTHING, 0, 0, 0, ROULETTE_SND, SPIN_CW, NO_LIGHTS, 30000, 0,1};
  
  switchPB[12] = {EXIT_LANE_LEFT_S, NOTHING, 0, 0, 0, HAMMOND_SND, NO_ROULETTE, HELL_L, 500, 0,1};
  
  switchPB[13] = {EXIT_LANE_RIGHT_S, NOTHING, 0, 0, 0, NO_SOUND, NO_ROULETTE, HELL_L, 500, 0,1};
  
  switchPB[14] = {SLINGSHOT_LEFT_S, SLINGSHOT_LEFT_C, 0, 0, 500, NO_SOUND, NO_ROULETTE, DRUGS_L, 1000, 0,1};
  
  switchPB[15] = {SLINGSHOT_RIGHT_S, SLINGSHOT_RIGHT_C, 0, 0, 500, NO_SOUND, NO_ROULETTE, DRINK_L, 1000, 0,1};
  
  switchPB[16] = {DRAIN_S, NOTHING, 0, 0, 0, NO_SOUND, NO_ROULETTE, HELL_L, 0, 0,1};
  
  switchPB[17] = {NEW_BALL_S,NOTHING, 0, 0, 0, NO_SOUND, NO_ROULETTE, NO_LIGHTS, 0, 0,1};
  
  switchPB[18] = {NOTHING,NEW_BALL_C, 0, 0, 0, NO_SOUND, NO_ROULETTE, NO_LIGHTS, 0, 0,1}; //special case as no switch assigned

  switchPB[19] = {NOTHING,DROP_TARGET_C, 0, 0, 0, NO_SOUND, NO_ROULETTE, NO_LIGHTS, 0, 0,1};

 

  for (int x = 0; x < TOTAL_SWITCHES; x++){
    #ifdef DEBUG
      Serial.println ("Setting up pins...");
      Serial.print("Pin ");
      Serial.print(switchPB[x].switchPin);
    #endif
    if (switchPB[x].switchPin){
      #ifdef DEBUG
        Serial.println( " Input Pullup");
      #endif
      pinMode(switchPB[x].switchPin, INPUT_PULLUP);
    } else {
      #ifdef DEBUG
        Serial.println (" Not Set!");
      #endif
    }
    
    if (switchPB[x].coilPin) {
      pinMode(switchPB[x].coilPin, OUTPUT);
      digitalWrite(switchPB[x].coilPin, LOW);
    }
  }
}

void updateScore(long scoreIncrement){
  //Serial.println("sending");
  score = score + scoreIncrement;
  sendScore();
}

void sendScore(){
  byte longAsBytes[4];
  longAsBytes[0] = score >>24;
  longAsBytes[1] = score >>16;
  longAsBytes[2] = score >>8;
  longAsBytes[3] = score;

  Wire.beginTransmission(MSG_SCOREBOARD_ADDR);
  for (int x = 0; x < 4; x++){
    Wire.write(longAsBytes[x]);
  }
  Wire.endTransmission();
}

void activateCoil(byte index){
  //takes in the index for the switchPB array and activates the associate coil
  #ifdef DEBUG 
    Serial.print("firing coil connected to pin ");
    Serial.println(switchPB[index].coilPin);
  #endif
  digitalWrite(switchPB[index].coilPin, HIGH);  
  switchPB[index].timeStampHigh = millis();                        
  switchPB[index].coilState = 1;  
  if(switchPB[index].globalDebounceMs){
    suppressUntilMillis = millis() + switchPB[index].globalDebounceMs;  
  }
}
names.inoArduino
this is a “tab” file for PinballControl_14.ino. It defines: the pins allocated to each of the switches; pins that are outputs to device coils; game control constants.
#define VERSION "0.5"
//#define DEBUG //comment out when deploying full version
/* V0.2 - Added score, suppressUntilMillis and switchPB.globalDebounceMs variables
 * Included I2C for scoreboard
 * ability to ignore all switches for a period of time added to fix rf problems with coils
 * 
 * V0.3 - corrected message to scoreboard to separate long into 4 bytes
 * 
 * V0.4 
 * 
 * 
 * Delays have been included in the code for the ball eject and ball return actions. It is
 * assumed that due to the time taken for the ball to travel that all activated coils e.g. 
 * pop bumpers will have been deactivated before the delays occur. If this is not the case 
 * then the coils will remain activated for the duration of the delay.
 * !!!END WARNING!!!
 * 
 * Added delay [BALL_EJECT_MILLIS] for ball eject
 * 
 * New function [activateCoil(byte index)] created to allow the ball eject and drop target
 * coils to be activated independently of the switchPB loop.
 * 
 * New code in setup identifies the elements in the switchPB array that reference the 
 * ball return [newBallIndex] and drop target [dropTargetIndex] coils.
 * 
 * Game control:
 * 
 * Lives are counted - when ball goes down drain, a life is deducted until no lives
 * are left and the ball is automatically returned after a defined period
 * [BALL_EJECT_DELAY_MILLIS].
 * 
 * New ball button is now being used as new game - it is only available if a game is not 
 * in progress [gameInProgress]. When pressed, a new game is initiated with lives [LIVES]
 * and the score is reset.
 *
 * When the entry lane switch is closed a timer now starts that is used to raise the
 * drop target after a defined period [DROP_TARGET_RAISE_MILLIS]
 * 
 * V0.5 - added new lastState value to switchPB to allow checking that the swich has closed
 * before re-triggering.
 * 
 */



/**************************
 * the following are used to 
 * tune the performance of the machine
 * 
 *Duration to hold coils high*/
#define COIL_MILLIS 100
//Debounce values
#define MICROSWITCH_DB 30
#define PLATE_SWITCH_DB 60
/**************************
 */

//Game control constant
#define LIVES 3

#define BALL_EJECT_DELAY_MILLIS 3000
#define DROP_TARGET_RAISE_MILLIS 20000L


/*  The delay to account for the time between 
 *  the drain switch closing and the ball reaching 
 *  the ball return mechanism - tune as appropriate
 */
#define DRAIN_TO_BALL_RETURN_MILLIS 1000

#define NOTHING 0
#define NO_SOUND 0
#define NO_SCORE 0
#define NO_LIGHTS 0
#define NO_ROULETTE 0


#define TOTAL_SWITCHES 20
//Pins allocated to switches
#define ENTRY_LANE_S 31
#define GATE_LEFT_S 30
#define GATE_CENTRE_S 29
#define GATE_RIGHT_S 28
#define POP_BUMPER_LEFT_S 27
#define POP_BUMPER_CENTRE_S 26
#define POP_BUPER_RIGHT_S 25
#define DROP_TARGET_S 22
#define SPINNER_S 34
#define BALL_EJECT_S 35
#define TARGET_LEFT_S 36
#define TARGET_RIGHT_S 37
#define EXIT_LANE_LEFT_S 38
#define EXIT_LANE_RIGHT_S 39
#define SLINGSHOT_LEFT_S 24
#define SLINGSHOT_RIGHT_S 23
#define DRAIN_S 40
#define NEW_BALL_S 42

//Output coil pins
#define POP_BUMPER_LEFT_C 46
#define POP_BUMPER_RIGHT_C 53
#define POP_BUMPER_CENTRE_C 47
#define SLINGSHOT_LEFT_C 49
#define SLINGSHOT_RIGHT_C 50
#define DROP_TARGET_C 52
#define BALL_EJECT_C 51
#define NEW_BALL_C 48
Types.inoArduino
this is a “tab”file for PinballControl_14.ino.
struct switchPB {
  int8_t switchPin; //the pin connected to a switch
  int8_t coilPin; //the associated coil
  int8_t coilState; //1 when activated, 0 when not
  unsigned long timeStampHigh; //the time in millis when coil was activated
  int debounceMs; /* time in millis to debounce the switch
  this is only useful if the debounce needs to be longer than the coil
  is charged (defined as COIL_MILLIS in names.h)*/
  int8_t soundMsg;
  int8_t rouletteMsg;
  int8_t lightsMsg;
  long scoreIncrement;
  int globalDebounceMs; /*Stops all switches being sensed for the number of 
  milliseconds defined (used to set suppressUntilMillis with a time to start listening
  to the switches again).*/
  int8_t lastState;
  
};
switchPB switchPB[TOTAL_SWITCHES];
PinballLights_08.inoArduino
This defines a lighting sequence for each of the sets of lights on the machine
#include <PBWire.h>
#include <Wire.h>

//define pins

#define TORCH_PIN 12
#define GATES_PIN 6
#define CURVE_PIN 4
#define POP_BUMPER_PIN 13
#define ORGAN_PIN 6
#define SPOT_PIN 7
#define SPINNER_PIN 8
#define DRUGS_PIN 9
#define SEX_PIN 10
#define HELL_PIN 11
#define DRINK_PIN 2
#define SMOKE_PIN 5


//buffer for incoming I2C message
byte wireMsg = 0;

void setup(){

  //set up pins as outputs:

   pinMode(TORCH_PIN, OUTPUT);
   pinMode(GATES_PIN, OUTPUT);
   pinMode(CURVE_PIN, OUTPUT);
   pinMode(POP_BUMPER_PIN, OUTPUT);
   pinMode(ORGAN_PIN, OUTPUT);
   pinMode(SPOT_PIN, OUTPUT);
   pinMode(SPINNER_PIN, OUTPUT);
   pinMode(DRUGS_PIN, OUTPUT);
   pinMode(SEX_PIN, OUTPUT);
   pinMode(HELL_PIN, OUTPUT);
   pinMode(DRINK_PIN, OUTPUT);
   pinMode(SMOKE_PIN, OUTPUT);
   
   
  //start I2C with relevant slave address
  Wire.begin(MSG_LIGHTS_ADDR);
  Serial.begin(9600);
  Serial.println("Starting PinballLights");
  Wire.onReceive(wireUpdate);

}

void loop() {
  if(wireMsg== NEW_BALL_L){
    Serial.println("flashing LED");
    for(int c= 0; c<4; c++){
      digitalWrite(CURVE_PIN,HIGH);
      digitalWrite(GATES_PIN,HIGH);
      delay(200);
      digitalWrite(CURVE_PIN,LOW);
      digitalWrite(GATES_PIN,LOW);
      delay(200);
      wireMsg = 0;
    }
  }
   if(wireMsg== CURVE_L){
    Serial.println("flashing LED");
    for(int c= 0; c<10; c++){
      digitalWrite(CURVE_PIN,HIGH);
      delay(100);
      digitalWrite(CURVE_PIN,LOW);
      
      delay(100);
      wireMsg = 0;
    }
  }
 if(wireMsg== SPINNER_L){
    Serial.println("flashing LED");
    {
      digitalWrite(SPINNER_PIN,HIGH);
      delay(4000);
      digitalWrite(SPINNER_PIN,LOW);
      wireMsg = 0;
    }
  } 
  if(wireMsg== SPOT_L){
    Serial.println("flashing LED");
    {
      digitalWrite(SPOT_PIN,HIGH);
      delay(4000);
      digitalWrite(SPOT_PIN,LOW);
      wireMsg = 0;
    }
  } 
 if(wireMsg== DRINK_L){
    Serial.println("flashing LED");
    for(int c= 0; c<12; c++){
      digitalWrite(DRINK_PIN,HIGH);
      digitalWrite(GATES_PIN,HIGH);
      delay(200);
      digitalWrite(DRINK_PIN,LOW);
      digitalWrite(GATES_PIN,LOW);
      delay(200);
      
      wireMsg = 0;
    }
  }
  if(wireMsg== SEX_L){
    Serial.println("flashing LED");
    for(int c= 0; c<12; c++){
      digitalWrite(SEX_PIN,HIGH);
      digitalWrite(GATES_PIN,HIGH);
      delay(200);
      digitalWrite(SEX_PIN,LOW);
      digitalWrite(GATES_PIN,LOW);
      delay(200);
      wireMsg = 0;
    }
  }
  if(wireMsg== SMOKE_L){
    Serial.println("flashing LED");
    for(int c= 0; c<4; c++){
      digitalWrite(SMOKE_PIN,HIGH);
      digitalWrite(GATES_PIN,HIGH);
      delay(200);
      digitalWrite(SMOKE_PIN,LOW);
      digitalWrite(GATES_PIN,LOW);
      delay(200);
      wireMsg = 0;
    }
  }
  if(wireMsg== GATES_L){
    Serial.println("flashing LED");
    for(int c= 0; c<8; c++){
      digitalWrite(GATES_PIN,HIGH);
      delay(200);
      digitalWrite(GATES_PIN,LOW);
      delay(200);
      wireMsg = 0;
    }
  }
  if(wireMsg == DRUGS_L){
     for(int c=0;c<4;c++){
      digitalWrite(DRUGS_PIN, HIGH);
      delay(500);
      digitalWrite(DRUGS_PIN, LOW);
      delay(500);
      wireMsg = 0;
    }
   }
   if(wireMsg == DRINK_L){
     for(int c=0;c<4;c++){
      digitalWrite(DRINK_PIN, HIGH);
      delay(500);
      digitalWrite(DRINK_PIN, LOW);
      delay(500);
      wireMsg = 0;
    }
   }
   
   if(wireMsg == HELL_L){
     for(int c=0;c<40;c++){
      digitalWrite(HELL_PIN, HIGH);
      delay(50);
      digitalWrite(HELL_PIN, LOW);
      delay(50);
      wireMsg = 0;
    }
   }
 }

//handler for incoming I2C message
void wireUpdate(int numBytes){
 Serial.print("got message: ");
  if(numBytes = 1){
    wireMsg = Wire.read();
  }
  Serial.println(wireMsg);
}
Addscore_03.inoArduino
This takes receives a number over I2C and displays
it on the pinScore 7 digit display
#include <PBWire.h>

#include <Wire.h>
/*
 * The arduino takes receives a number over I2C and displays
 * it on the pinScore 7 digit display.
 * 
 * Wiring connections
 * ------------------
 * With the pinscore display facing you and '20' on the left
 * with 'J1' on the right, pin 1 is the leftmost pin
 * 
 * Pin    Arduino Connection
 * 1      +5V
 * 2      8           |   A 4 bit message is sent via this
 * 3      9           |   connection so need to use port
 * 4      10          |   manipulation to set the output
 * 5      11          |   simultaneously
 * 6      12
 * 7      No Pin
 * 8      Gnd
 * 9      Not Connected
 * 10     A0
 * 11     Not Connected
 * 12     2
 * 13     3
 * 14     4
 * 15     5
 * 16     6
 * 17     7
 * 18     Not Connected
 * 19     Not Connected
 * 20     Not Connected
 * 
 * I2C - arduino needs A4 & A5 connected to the same pins
 * on master arduino
 * 
 */

//structure to map a pin to a digit for display
struct pinScore {
  int8_t pin;
  int8_t digit;
};

//array to hold the 7 pins and digits
pinScore scoreMap[9];
  
void setup() {

  Wire.begin(MSG_SCOREBOARD_ADDR);
  Wire.onReceive(wireUpdate);

  Serial.begin(9600);
  
  //Map the pins to the digits on the pinscore
  scoreMap[0].pin = 7;//Analogue pin for highest order digit
  scoreMap[1].pin = 6;
  scoreMap[2].pin = 5;
  scoreMap[3].pin = 4;
  scoreMap[4].pin = 3;
  scoreMap[5].pin = 2;
  scoreMap[6].pin = 14;
  
  //set the pins to output
  for(int x = 0; x <7; x++){
    pinMode(scoreMap[x].pin, OUTPUT);
    scoreMap[x].digit = 1;//initialise the score to 1234567
  }
    
  //set port B pins to output
  DDRB = B00111111 | DDRB; //set to output apart from osc pins
}

void loop() { 
  /*cycles to constantly refresh the pinscore using the current
  score saved in scoreMap*/
  
  for(int x = 0; x < 7; x++){
    //set all the digit id pins to low
    for(int y = 0; y < 7; y++){
      digitalWrite(scoreMap[y].pin,LOW);
    }
    //set the pin for the current digit to high
    digitalWrite(scoreMap[x].pin,HIGH);
    //send the 4 bit integer to pinscore
    PORTB = scoreMap[x].digit;
    //write high to set the message
    digitalWrite(12,HIGH);
    delay(1);
    
  }

  /*if(counter == 9999999L){
    counter = 0;
  }else{
    counter++;
  }
  mapScore(counter);
  */  
}

//takes in an unsigned long over I2C and populates the scoring array 
//with the new number.
void mapScore(unsigned long score){
  unsigned long sumScore=0;
  //extract the digits from the number and populate the scoring array
  for(int x = 0; x < 7; x++){
    if(score>0){
      int mod;
      mod = score % 10; // take the lowest order digit
      scoreMap[x].digit = mod; //copy it to the map
      score = (score-mod)/10; //move all the digits 1 to the left
    }else{
      scoreMap[x].digit = 15;//will display nothing
    }
  }
}

void wireUpdate(int numBytes){
  unsigned long newScore;
  /*
   * The score is received over I2C as 4 individual bytes
   * they need to be put back together into the long by 
   * copying a byte, bit shifting it accross 8 bits and 
   * reading in the next byte.
   */
  if(numBytes == 4){
    newScore = Wire.read();
    for(int x = 0; x < 3; x++){
     newScore = newScore << 8;
     newScore = newScore + Wire.read(); 
    }
  }
  //replace the old score in the scoreMap with the new one
  Serial.println(newScore);
  mapScore(newScore);
}
PinballAudio_01.inoArduino
This is from Spark Fun Electronics 2011 and written by Nathan Seidle
/*
 4-28-2011
 Spark Fun Electronics 2011
 Nathan Seidle
 
 This code is public domain but you buy me a beer if you use this and we meet someday (Beerware license).
 
 This example shows how to play a given track (track001.mp3 for example) from the SD card when a GPIO pin goes low. 
 This code monitors A0 through A5. If A0 is pulled low then track 1 plays. If A5 is pulled low then the track # simply advances.
 
 This code is heavily based on the "MP3 PlayTrack" example code. Please be sure to review it for more comments and gotchas.
 
 While track is playing, Arduino scans to see if pins change. If pins change, stop playing track, check pins again to get 
 trigger # and then decide what new track to play.
 
 */

#include <SPI.h>
#include<Wire.h>
#include "names.h"
#include "mp3_config.h"
#include <PBWire.h>


#define TRUE  0
#define FALSE  1

//Add the SdFat Libraries
#include <SdFat.h>
#include <SdFatUtil.h> 

//Create the variables to be used by SdFat Library
Sd2Card card;
SdVolume volume;
SdFile root;
SdFile track;

//This is the name of the file on the microSD card you would like to play
//Stick with normal 8.3 nomeclature. All lower-case works well.
//Note: you must name the tracks on the SD card with 001, 002, 003, etc. 
//For example, the code is expecting to play 'track002.mp3', not track2.mp3.
char trackName[] = "track001.mp3";
int trackNumber = 0;
int previousTrigger = 1; //This indicates that we've already triggered on 1

long lastCheck; //This stores the last millisecond since we had a trigger

char errorMsg[100]; //This is a generic array used for sprintf of error messages

//MP3 Player Shield pin mapping. See the schematic
#define MP3_XCS 6 //Control Chip Select Pin (for accessing SPI Control/Status registers)
#define MP3_XDCS 7 //Data Chip Select / BSYNC Pin
#define MP3_DREQ 2 //Data Request Pin: Player asks for more data
#define MP3_RESET 8 //Reset is active low
//Remember you have to edit the Sd2PinMap.h of the sdfatlib library to correct control the SD card.

//VS10xx SCI Registers
#define SCI_MODE 0x00
#define SCI_STATUS 0x01
#define SCI_BASS 0x02
#define SCI_CLOCKF 0x03
#define SCI_DECODE_TIME 0x04
#define SCI_AUDATA 0x05
#define SCI_WRAM 0x06
#define SCI_WRAMADDR 0x07
#define SCI_HDAT0 0x08
#define SCI_HDAT1 0x09
#define SCI_AIADDR 0x0A
#define SCI_VOL 0x0B
#define SCI_AICTRL0 0x0C
#define SCI_AICTRL1 0x0D
#define SCI_AICTRL2 0x0E
#define SCI_AICTRL3 0x0F

void setup() {

  Wire.begin(MSG_SOUNDBOARD_ADDR);
  Wire.onReceive(wireUpdate);

  pinMode(MP3_DREQ, INPUT);
  pinMode(MP3_XCS, OUTPUT);
  pinMode(MP3_XDCS, OUTPUT);
  pinMode(MP3_RESET, OUTPUT);

  digitalWrite(MP3_XCS, HIGH); //Deselect Control
  digitalWrite(MP3_XDCS, HIGH); //Deselect Data
  digitalWrite(MP3_RESET, LOW); //Put VS1053 into hardware reset

  Serial.begin(9600); //Use serial for debugging 
  Serial.println("MP3 Player Example using Control");

  //Setup SD card interface
  pinMode(10, OUTPUT);       //Pin 10 must be set as an output for the SD communication to work.
  if (!card.init(SPI_FULL_SPEED, 9))  Serial.println("Error: Card init"); //Initialize the SD card, shield uses pin 9 for SD CS
  if (!volume.init(&card)) Serial.println("Error: Volume ini"); //Initialize a volume on the SD card.
  if (!root.openRoot(&volume)) Serial.println("Error: Opening root"); //Open the root directory in the volume. 

  //We have no need to setup SPI for VS1053 because this has already been done by the SDfatlib

  //From page 12 of datasheet, max SCI reads are CLKI/7. Input clock is 12.288MHz. 
  //Internal clock multiplier is 1.0x after power up. 
  //Therefore, max SPI speed is 1.75MHz. We will use 1MHz to be safe.
  SPI.setClockDivider(SPI_CLOCK_DIV16); //Set SPI bus speed to 1MHz (16MHz / 16 = 1MHz)
  SPI.transfer(0xFF); //Throw a dummy byte at the bus

  //Initialize VS1053 chip 
  delay(10);
  digitalWrite(MP3_RESET, HIGH); //Bring up VS1053

  Mp3SetVolume(20, 20); //Set initial volume (20 = -10dB) LOUD
  //Mp3SetVolume(40, 40); //Set initial volume (20 = -10dB) Manageable
  //Mp3SetVolume(80, 80); //Set initial volume (20 = -10dB) More quiet

  //Now that we have the VS1053 up and running, increase the internal clock multiplier and up our SPI rate
  Mp3WriteRegister(SCI_CLOCKF, 0x60, 0x00); //Set multiplier to 3.0x

  //From page 12 of datasheet, max SCI reads are CLKI/7. Input clock is 12.288MHz. 
  //Internal clock multiplier is now 3x.
  //Therefore, max SPI speed is 5MHz. 4MHz will be safe.
  SPI.setClockDivider(SPI_CLOCK_DIV4); //Set SPI bus speed to 4MHz (16MHz / 4 = 4MHz)

  //MP3 IC setup complete
  trackNumber = 1;

}

void loop(){

 
  //Let's play a track of a given number
  if(trackNumber){
    sprintf(trackName, "track%03d.mp3", trackNumber); //Splice the new file number into this file name
    playMP3(trackName); //Go play trackXXX.mp3
    trackNumber = 0;
  }
}


//PlayMP3 plays a given file name
//It pulls 32 byte chunks from the SD card and throws them at the VS1053
//We monitor the DREQ (data request pin). If it goes low then we determine if
//we need new data or not. If yes, pull new from SD card. Then throw the data
//at the VS1053 until it is full.
void playMP3(char* fileName) {

  if (!track.open(&root, fileName, O_READ)) { //Open the file in read mode.
    sprintf(errorMsg, "Failed to open %s", fileName);
    Serial.println(errorMsg);
    return;
  }

  sprintf(errorMsg, "Playing track %s", fileName);
  Serial.println(errorMsg);

  uint8_t mp3DataBuffer[32]; //Buffer of 32 bytes. VS1053 can take 32 bytes at a go.
  int need_data = TRUE; 

  while(1) {
    while(!digitalRead(MP3_DREQ)) { 
      //DREQ is low while the receive buffer is full
      //You can do something else here, the buffer of the MP3 is full and happy.
      //Maybe set the volume or test to see how much we can delay before we hear audible glitches

      //If the MP3 IC is happy, but we need to read new data from the SD, now is a great time to do so
      if(need_data == TRUE) {
        if(!track.read(mp3DataBuffer, sizeof(mp3DataBuffer))) { //Try reading 32 new bytes of the song
          //Oh no! There is no data left to read!
          //Time to exit
          break;
        }
        need_data = FALSE;
      }
    }

    if(need_data == TRUE){ //This is here in case we haven't had any free time to load new data
      if(!track.read(mp3DataBuffer, sizeof(mp3DataBuffer))) { //Go out to SD card and try reading 32 new bytes of the song
        //Oh no! There is no data left to read!
        //Time to exit
        break;
      }
      need_data = FALSE;
    }

    //Once DREQ is released (high) we now feed 32 bytes of data to the VS1053 from our SD read buffer
    digitalWrite(MP3_XDCS, LOW); //Select Data
    for(int y = 0 ; y < sizeof(mp3DataBuffer) ; y++)
      SPI.transfer(mp3DataBuffer[y]); // Send SPI byte

    digitalWrite(MP3_XDCS, HIGH); //Deselect Data
    need_data = TRUE; //We've just dumped 32 bytes into VS1053 so our SD read buffer is empty. Set flag so we go get more data
  }

  while(!digitalRead(MP3_DREQ)) ; //Wait for DREQ to go high indicating transfer is complete
  digitalWrite(MP3_XDCS, HIGH); //Deselect Data

  track.close(); //Close out this track

  sprintf(errorMsg, "Track %s done!", fileName);
  Serial.println(errorMsg);
}


//Write to VS10xx register
//SCI: Data transfers are always 16bit. When a new SCI operation comes in 
//DREQ goes low. We then have to wait for DREQ to go high again.
//XCS should be low for the full duration of operation.
void Mp3WriteRegister(unsigned char addressbyte, unsigned char highbyte, unsigned char lowbyte){
  while(!digitalRead(MP3_DREQ)) ; //Wait for DREQ to go high indicating IC is available
  digitalWrite(MP3_XCS, LOW); //Select control

  //SCI consists of instruction byte, address byte, and 16-bit data word.
  SPI.transfer(0x02); //Write instruction
  SPI.transfer(addressbyte);
  SPI.transfer(highbyte);
  SPI.transfer(lowbyte);
  while(!digitalRead(MP3_DREQ)) ; //Wait for DREQ to go high indicating command is complete
  digitalWrite(MP3_XCS, HIGH); //Deselect Control
}

//Read the 16-bit value of a VS10xx register
unsigned int Mp3ReadRegister (unsigned char addressbyte){
  while(!digitalRead(MP3_DREQ)) ; //Wait for DREQ to go high indicating IC is available
  digitalWrite(MP3_XCS, LOW); //Select control

  //SCI consists of instruction byte, address byte, and 16-bit data word.
  SPI.transfer(0x03);  //Read instruction
  SPI.transfer(addressbyte);

  char response1 = SPI.transfer(0xFF); //Read the first byte
  while(!digitalRead(MP3_DREQ)) ; //Wait for DREQ to go high indicating command is complete
  char response2 = SPI.transfer(0xFF); //Read the second byte
  while(!digitalRead(MP3_DREQ)) ; //Wait for DREQ to go high indicating command is complete

  digitalWrite(MP3_XCS, HIGH); //Deselect Control

  int resultvalue = response1 << 8;
  resultvalue |= response2;
  return resultvalue;
}

//Set VS10xx Volume Register
void Mp3SetVolume(unsigned char leftchannel, unsigned char rightchannel){
  Mp3WriteRegister(SCI_VOL, leftchannel, rightchannel);
}
void wireUpdate(int numBytes){
  Serial.println("got message");
  if(numBytes = 1){
    trackNumber = Wire.read();
  }
}
mp3_config.hArduino
This is a “tab” file for PinballAudio_01.ino

names.hArduino
This is a “tab” file for PinballAudio_01.ino
/* Board I2C Addresses (except for controller [master])
 * and position in 10 byte array, bytes 6-9 carry the score 
 */
#define CONTROLLER_ID 0
#define MSG_LIGHTS_ID 1
#define MSG_SOUND_ID 2
#define MSG_IGUANA_ID 3 
#define MSG_SCOREBOARD_ID 4

#define DEBOUNCE_TIME 50

#define TOTAL_SWITCHES 17
//Pin IDs for switches
#define NEW_BALL 18
#define ENTRY_LANE 19
#define LEFT_GATE 22
#define CENTRE_GATE 23
#define RIGHT_GATE 24
#define LEFT_POP 25
#define RIGHT_POP 26
#define VOLCANO_SWITCH 27
#define LEFT_TARGET 28
#define CENTRE_TARGET 29
#define RIGHT_TARGET 30
#define DROP_TARGET_SWITCH 31
#define LEFT_SLINGSHOT 32
#define RIGHT_SLINGSHOT 33
#define LEFT_EXIT 34
#define RIGHT_EXIT 35
#define DRAIN 36

//I2C actions
#define AUDIO_1 1
#define IGUANA_FLASH_LONG 1
#define IGUANA_FLASH_SHORT 2

#define MSG_LIGHTS 1
#define MSG_SOUND 2
#define MSG_IGUANA 3

Comments

Similar projects you might like

Arduino Conversion of an EM Pinball Machine

Project showcase by Frogger1108

  • 3,487 views
  • 3 comments
  • 24 respects

Arduino Controlled Pinball Machine

Project tutorial by Bob Blomquist

  • 31,935 views
  • 47 comments
  • 94 respects

Homemade Arduino Pinball Machine

Project showcase by Frogger1108

  • 11,178 views
  • 15 comments
  • 50 respects

Pinball Sounds and Music

Project tutorial by Bob Blomquist

  • 5,909 views
  • 3 comments
  • 17 respects

Homemade Claw Machine

Project tutorial by -MMM-

  • 7,941 views
  • 11 comments
  • 45 respects

PinBall Table on Arduino

by Ivan

  • 5,075 views
  • 4 comments
  • 16 respects
Add projectSign up / Login