#include <FastLED.h>
// GOTTLIEB EM Pinball with a ARDUINO MEGA
// replacing the EM Stuff and LEDS replacing bulbs
// Written by B J Mclaren
// based on experience of building an ARDUINO based Bally Bingo
// This is currenty for FLYING CARPET but will be able to
// play DUOTRON in future (Next release).
// Special thanks to all involved in ARDUINO Pinballs.
// Most of the code is original except some Bumper stuff
// This solution used distributed processing with a MEGA
// as the Master controller
// UNO's as Auxillary Controllers for Chimes etc
// an extra UNO when more than 12 solenoids are required (DUOTRON)
// Processing 3(see processing.org)for Backglass (+ Sound if no Chime Unit)
// A PC, (a Fanless PC is powerfull enough) to run the
// software to drive a 40" LCD TV Backglass.
//
// There are 6 channels on each MOSFET Board.
// PWM Pins used by the driver are 3,5,6,9,10,11
// Board 1 ( MASTER ) mounted on MEGA is for 3 POP Bumper
// and Ball delivery + 2 Spares
// a single MOSFET Driver is used for the 100 points LED
// There are 6 channels on MOSFET Board 2
// Board 2 ( SLAVE 1 )mounted on UNO for chimes, Knocker and flippers
// Board 3 ( SLAVE 2 )future expansion eg DUOTRON
// Board 1 utilises a 12 Volt PSU
// Board 2 utilises a 15 Volt PSU
// A total of 18 solenoid Drivers can be provided
// Comms to Slave 1 and Slave 2 via RS232
// later versions may use I2C (so reserve A4 and A5)
//
// Switch and Targets ARDUINO Pins
#define Fswitch 36
#define Lswitch 37
#define Yswitch 38
#define Iswitch 39
#define Nswitch 40
#define Gswitch 41
//
#define Cswitch 42
#define Aswitch 43
#define Rswitch 44
#define Pswitch 45
#define Eswitch 46
#define Tswitch 47
// Misc Switches and Buttons
#define Redbutton 27
#define Yellbutton 28
#define Tilt 34
#define Slingshot 35 //ALL score slingshots are in parallel
//
#define B1_Bumper_Switch 29
#define B1_MOSFET_Driver 5
//
#define B2_Bumper_Switch 31
#define B2_MOSFET_Driver 3
//
#define B3_Bumper_Switch 30
#define B3_MOSFET_Driver 6
//
#define Deliver_Ball_MOSFET_Driver 9
//
#define BlueLed_MOSFET_Driver 4 // Lit when 100 points
#define Spare1_MOSFET_Driver 10
#define Spare2_MOSFET_Driver 11
//
// Array for LEDs for playfield
#define NUM_LEDS 70 //Space for up to 70 LEDS
#define DATA_PIN A0
//
#define Ball_Lights 41 // Offset for Balls played LEDS
#define On_Time 40 // Solenoids ON Time in ms
//
CRGB leds[NUM_LEDS];
// This section defined the index into the LED arrays
// LED's for "FLYING CARPET" in centre of playfield
int FCLamps [13] =
{
21, 22, 24, 25, 27, 28, 34, 33, 32, 31, 30, 29, 0
};
// switch LED's on Playfield
int FCswitchLamps [13] =
{
17, 8, 57, 5, 35, 50, 54, 6, 13, 7, 49, 36, 0
};
// Target LED's on Playfield
int FCTargetLamps [13] =
{
59, 20, 9, 51, 11, 60, 12, 52, 14, 19, 61, 10, 0
};
// Index for special targets
#define SR1 16 //Special R Target 1
#define SY1 23 //Special Y Target 1
#define SR2 26 //Special R Target 2
#define SY2 56 //Special Y Target 2
//
// 12 slots to indicate "FLYING CARPET" Targets/Rollovers hit
int FC[13]; // slots 0 and 13 not used
int Balls = 0;
int RandNumber = 0; // Lucky Matching number
int BallsPerGame = 5;
int GameOver = 1;
int FCcount = 0; // count of numbers of FC lights ON
int SpecialON = 0; // Special ON
int BlueBumper = 0; // Blue Score 100 if set to 1
int Phit = 0; // set if P rollover or target hit
int Ahit = 0; // set if A rollover or target hit
// if both hit Blue bumper score 100
int Yspecial = 0;
int Rspecial = 0;
int replays = 0;
int units = 0; // keep track of units for Lucky Number
int switch_timer = 0; // determine if multi reports of a target hit
int debounce = 50; // switch debounce in mills
// Variables for target handling
unsigned long System_Base_Time = 0;
unsigned long Target_Hit_Time = 0;
unsigned long Old_Target_Hit_Time = 0;
unsigned long diff = 0;
// Bumper timing stuff
unsigned long B1_previousMillis = 0;
unsigned long B1_currentMillis = 0;
unsigned long B1_ON_Time = 0;
//
int B1_Coil_Trigger = 0;
int B1_Coil_State_last = LOW;
int B1_Coil_State;
int B1_difference;
//
unsigned long B2_previousMillis = 0;
unsigned long B2_currentMillis = 0;
unsigned long B2_ON_Time = 0;
//
int B2_Coil_Trigger = 0;
int B2_Coil_State_last = LOW;
int B2_Coil_State;
int B2_difference;
//
unsigned long B3_previousMillis = 0;
unsigned long B3_currentMillis = 0;
unsigned long B3_ON_Time = 0;
//
int B3_Coil_Trigger = 0;
int B3_Coil_State_last = LOW;
int B3_Coil_State;
int B3_difference;
//
unsigned long end_time = 0; // used for timing
unsigned long total_time = 0;
// Max time before switching off bumpers to stop coil burnup
int Max_On_Time = 1000; //
void setup()
{
pinMode(Deliver_Ball_MOSFET_Driver, OUTPUT);
analogWrite(Deliver_Ball_MOSFET_Driver, 0);
//
pinMode(Spare1_MOSFET_Driver, OUTPUT);
analogWrite(Spare1_MOSFET_Driver, 0);
//
pinMode(Spare2_MOSFET_Driver, OUTPUT);
analogWrite(Spare2_MOSFET_Driver, 0);
//
pinMode(BlueLed_MOSFET_Driver, OUTPUT);
digitalWrite (BlueLed_MOSFET_Driver, 0);
// Pop Bumpers
pinMode(B1_Bumper_Switch, INPUT_PULLUP);
pinMode(B1_MOSFET_Driver, OUTPUT);
analogWrite(B1_MOSFET_Driver, 0);
//
pinMode(B2_Bumper_Switch, INPUT_PULLUP);
pinMode(B2_MOSFET_Driver, OUTPUT);
analogWrite(B2_MOSFET_Driver, 0);
//
pinMode(B3_Bumper_Switch, INPUT_PULLUP);
pinMode(B3_MOSFET_Driver, OUTPUT);
analogWrite(B3_MOSFET_Driver, 0);
// LEDS
FastLED.addLeds<WS2811, DATA_PIN, BRG>(leds, NUM_LEDS);
FastLED.setBrightness(100);
FastLED.show();
// Switches and Targets
pinMode(Fswitch, INPUT_PULLUP);
pinMode(Lswitch, INPUT_PULLUP);
pinMode(Yswitch, INPUT_PULLUP);
pinMode(Iswitch, INPUT_PULLUP);
pinMode(Nswitch, INPUT_PULLUP);
pinMode(Gswitch, INPUT_PULLUP);
//
pinMode(Cswitch, INPUT_PULLUP);
pinMode(Aswitch, INPUT_PULLUP);
pinMode(Rswitch, INPUT_PULLUP);
pinMode(Pswitch, INPUT_PULLUP);
pinMode(Eswitch, INPUT_PULLUP);
pinMode(Tswitch, INPUT_PULLUP);
//
pinMode(Slingshot, INPUT_PULLUP);
pinMode(Tilt, INPUT_PULLUP);
// Game Start Stuff
pinMode(Redbutton, INPUT_PULLUP);
pinMode(Yellbutton, INPUT_PULLUP);
//
Serial.begin (9600); // -> Tv Monitor via Processing I3 on PC or PI
Serial1.begin (9600); // chime unit, ball delivery anf flippers
}
//
void Lucky()
// Are we Lucky today?
// match number for replay
// For Fun Only as we are a Free Play Machine
{
int u = units - (int(units / 10) * 10);
int r = random (0, 9);
if (r == u)
{
replay();
}
}
//
void timer ()
// Used to determine if a Target is bouncing causing
// multiple reporting of a hit
// this routine determines the time in milli seconds
// since the last target hit
// Currenty set to 200 ms - unlikely 2 targets can be
// hit within 200ms
{
Target_Hit_Time = millis();
diff = Target_Hit_Time - Old_Target_Hit_Time;
Old_Target_Hit_Time = Target_Hit_Time;
if (diff < 200 )switch_timer = 1; // adjust time to suit machine
}
//
void waiton (int pin)
{
delay (debounce); // let switch settle
while (digitalRead(pin) == 0 )
{
delay (debounce);
}
}// end of waiton
//
void TopRipple(int score)
// routine to light top 5 lights in sequence
// if score then send add 10 command to score
// and chime unit
{
for (int i = 0; i < 5; i = i + 1)
{
if (score == 1 )
{
Serial.print('b');
Serial1.print ('b');
}
leds [ i ] = CRGB::Black;
FastLED.show();
delay (50);
//
leds [ i ] = CRGB::White;
FastLED.show();
}
}
//
void replay()
{
replays++;
Serial.print (replays);
Serial1.print ('K'); // activate knocker
}
void reset()
{
// Light all the LEDS
for (int i = 0; i < 70; i = i + 1)
{
leds[i] = CRGB::White;
}
FastLED.show();
// switch off LEDs that are spare underneath playfield
leds [58] = CRGB::Black;
leds [9] = CRGB::Black;
//
FastLED.show();
// switch of 4 special lights
leds [SY1] = CRGB::Black;
leds [SY2] = CRGB::Black;
leds [SR1] = CRGB::Black;
leds [SR2] = CRGB::Black;
// now switch OFF central FLYING CARPET Lights
for (int i = 0; i < 13; i = i + 1)
{
int FC_Lights = FCLamps[i];
leds[FC_Lights] = CRGB::Black;
}
// Switch off Ball in play and Game Over LEDS
for (int i = 0; i < 6; i = i + 1)
{
leds[i + Ball_Lights] = CRGB::Black;
}
FastLED.show();
digitalWrite(BlueLed_MOSFET_Driver, 0);
BallsPerGame = 5;
Balls = 0;
units = 0;
FCcount = 0;
Phit = 0;
Ahit = 0;
Yspecial = 0;
Rspecial = 0;
GameOver = 0;
SpecialON = 0; // = 1 if 'Y' target and -1 if R target
BlueBumper = 0; // = 1 to light; set to 2 when alight
for (int i = 0; i < 12; i++)
{
FC[i] = 0;
}
timer(); // intialise timer
}
void diag()
// Diagnostic routine to determine if switch stuck
// and all lights are working
{
// Light all the LEDS
for (int i = 0; i < 70; i = i + 1)
{
leds[i] = CRGB::White;
}
FastLED.show();
delay (2000); // all LEDs ON
// all LEDS OFF
for (int i = 0; i < 70; i = i + 1)
{
leds [ i ] = CRGB::Black;
}
FastLED.show();
delay (1500);
// Check coil switch state
// Normal start state for Coil Switch is HIGH
// Error codes are 1, 10, 100 for coil faults
if (digitalRead(B2_Bumper_Switch) == 0)
{
Serial.print ('a'); // indicate 1
GameOver = 1;
}
if (digitalRead(B1_Bumper_Switch) == 0)
{
Serial.print ('b'); // indicate 10
GameOver = 1;
}
if (digitalRead(B3_Bumper_Switch) == 0)
{
Serial.print ('c'); // indicate 100
GameOver = 1;
}
// check if any switches or targets are closed
// inputs on pins 36 to 47
for (int i = 36; i >= 47; i = i - 1)
{
int Switch = digitalRead(i);
if (Switch == 0 )
{
// light faulty rollover or target letter in playfield centre
leds[i - 15] = CRGB::White;
GameOver = 1;
}
}
}
//
void check_for_special(int letter)
// completing F-L-Y-I-N-G C-A-R-P-E-T sequence lights
// 'Y' Target and switch
// alternatively
// 'R' Target and switch
{
FC [letter] = 1;
int Lmp = FCLamps[letter];
leds[Lmp] = CRGB::White;
int Tgt = FCTargetLamps[letter];
leds[Tgt] = CRGB::Black;
int roll = FCswitchLamps[letter];
leds[roll] = CRGB::Black;
FastLED.show();
//check if special to be switched ON
FCcount = 0;
for (int i = 0; i < 12; i++)
{
if (FC[i] == 1)
{
FCcount = FCcount + 1;
}
}
// check for specials in LED Array
// and light special if 'Flying Carpet' fully lit
if (FCcount == 12)
{
SpecialON = !SpecialON;
if (SpecialON == 0 )
{
Rspecial = 1;
Yspecial = 0;
leds[SY1] = CRGB::White;
leds[SY2] = CRGB::White;
leds[SR1] = CRGB::Black;
leds[SR2] = CRGB::Black;
FastLED.show();
}
if (SpecialON == 1 )
{
Yspecial = 1;
Rspecial = 0;
leds[SR1] = CRGB::White;
leds[SR2] = CRGB::White;
leds[SY1] = CRGB::Black;
leds[SY2] = CRGB::Black;
FastLED.show();
}
}
}
//
void Check_Bumpers()
{
GameOver = 0;
if (( B1_Coil_Trigger == LOW) && (B1_Coil_State_last == HIGH))
{
B1_difference = B1_currentMillis - B1_ON_Time;
if (B1_difference >= Max_On_Time)GameOver = 1;
}
//
if (( B2_Coil_Trigger == LOW) && (B2_Coil_State_last == HIGH))
{
B2_difference = B2_currentMillis - B2_ON_Time;
if (B2_difference >= Max_On_Time)GameOver = 1;
}
if (( B3_Coil_Trigger == LOW) && (B3_Coil_State_last == HIGH))
{
B3_difference = B3_currentMillis - B3_ON_Time;
if (B3_difference >= Max_On_Time)GameOver = 1;
}
}
//
void B1_Bumper_Hit()
{
B1_currentMillis = System_Base_Time; //Start Time
B1_Coil_State = digitalRead(B1_Bumper_Switch); //Variable for Coil Switch
if ((B1_Coil_State == LOW) && (B1_Coil_State_last == LOW))
{
//Coil has been triggered
B1_ON_Time = B1_currentMillis;
B1_Coil_State_last = HIGH;
B1_previousMillis = millis();
analogWrite(B1_MOSFET_Driver, 200); //Coil Solenoid activated !
B1_Coil_Trigger = 1 ;
}
else if ((B1_Coil_State == HIGH) && (B1_Coil_State_last == HIGH))
{
//Coil released
B1_Coil_State_last = LOW;
}
B1_difference = B1_currentMillis - B1_previousMillis;
if ((B1_difference >= debounce) && (B1_Coil_Trigger == 1))
{
B1_previousMillis = B1_currentMillis;
B1_Coil_Trigger = 0;
analogWrite(B1_MOSFET_Driver, 0); //Coil Solenoid deactivated
if (BlueBumper > 0 )
{
Serial.print ('c');
Serial1.print ('c'); // to Chime 100
}
else
{
Serial.print ('b');
Serial1.print ('b'); // to Slave Chime 10
}
}
}
//
void B2_Bumper_Hit()
{
B2_currentMillis = System_Base_Time; //Start Time
B2_Coil_State = digitalRead(B2_Bumper_Switch); //Variable for Coil Switch
if ((B2_Coil_State == LOW) && (B2_Coil_State_last == LOW))
{
//Coil has been triggered
B2_ON_Time = B2_currentMillis;
B2_Coil_State_last = HIGH;
B2_previousMillis = millis();
analogWrite(B2_MOSFET_Driver, 200); //Coil Solenoid activated !
B2_Coil_Trigger = 1 ;
}
else if ((B2_Coil_State == HIGH) && (B2_Coil_State_last == HIGH))
{
//Coil released
B2_Coil_State_last = LOW;
}
B2_difference = B2_currentMillis - B2_previousMillis;
if ((B2_difference >= debounce) && (B2_Coil_Trigger == 1))
{
B2_previousMillis = B2_currentMillis;
B2_Coil_Trigger = 0;
analogWrite(B2_MOSFET_Driver, 0); //Coil Solenoid deactivate
if (BlueBumper > 0 )
{
Serial.print ('c');
Serial1.print ('c'); // to Chime 100
}
else
{
Serial.print ('b');
Serial1.print ('b'); // to Slave Chime 10
}
}
}
//
void B3_Bumper_Hit()
{
B3_currentMillis = System_Base_Time; //Start Time
B3_Coil_State = digitalRead(B3_Bumper_Switch); //Variable for Coil Switch
if ((B3_Coil_State == LOW) && (B3_Coil_State_last == LOW))
{
//Coil has been triggered
B3_ON_Time = B3_currentMillis;
B3_Coil_State_last = HIGH;
B3_previousMillis = millis();
analogWrite(B3_MOSFET_Driver, 200); //Coil Solenoid activated !
B3_Coil_Trigger = 1 ;
}
else if ((B3_Coil_State == HIGH) && (B3_Coil_State_last == HIGH))
{
//Coil released
B3_Coil_State_last = LOW;
}
B3_difference = B3_currentMillis - B3_previousMillis;
if ((B3_difference >= debounce) && (B3_Coil_Trigger == 1))
{
B3_previousMillis = B3_currentMillis;
B3_Coil_Trigger = 0;
analogWrite(B3_MOSFET_Driver, 0); //Coil Solenoid deactivated
Serial.print ('c');
Serial1.print ('c');
}
}
//
void loop()
{
System_Base_Time = millis();
if (GameOver == 1)
// Game Over light ON all others OFF
// set Gameover to 2 since we only want it to
// set the lights once
{
for (int i = 0; i < 70; i = i + 1)
{
leds [ i ] = CRGB::Black;
}
leds [ 41 ] = CRGB::White;
FastLED.show();
GameOver = 2;
Serial1.print ('D'); // switch off aux processor
}
// timings are as follows:
// NOTE Targets can bounce for up to 200 milliseconds
// around 'loop'; approx 2 milli second for approx 20
// switch / target reads
// Score output 2 milliseconds (longer for 50 score)
// Pop Bumpers approx 2 milliseconds
// FastLED.show takes approx 2 to 3 milli seconds
// Flipper not applicable since not controlled by the MEGA board
// only commands to switch ON or OFF at start and end of game
// sent to Slave 1 UNO.
//
if (digitalRead(Redbutton) == 0)
// used to start game if no replays left
// add 1 to get a free game
{
waiton (Redbutton);
diag(); // error codes displayed on backglass
if (replays == 0)
{
replay(); // give a free replay
Serial1.print ('K'); // to AUX 1 GameOver
} // activate Knocker
replays = replays - 1; // reduce replays by 1
Serial.print (replays); // update backglass
Serial.print ('g'); // reset backglass score to "0000"
Serial1.print ('D'); // to AUX 1 GameOver
delay(1000); // allow a reset on slave processors
Serial1.print ('E'); // -> AUX 1 New Game
reset();
// Deliver 1st ball ready for new game start
Balls = 1;
leds[46] = CRGB::White;
// activate solonoid to kick ball into shooter lane
analogWrite(Deliver_Ball_MOSFET_Driver, 200);
// give the right amount of kick,delay here no problem
// since nothing else happening
delay (50);
analogWrite(Deliver_Ball_MOSFET_Driver, 0);
TopRipple(0);
}
// Everything OK; so Game ON
if (GameOver == 0)
{
B1_Bumper_Hit();
B2_Bumper_Hit();
B3_Bumper_Hit();
Check_Bumpers();
switch_timer = 0;
if (digitalRead(Yellbutton) == 0)
{
waiton(Yellbutton);
leds[47 - Balls] = CRGB::Black;
Balls++;
leds[47 - Balls] = CRGB::White;
// If Game Over so tell backglass
if (Balls > BallsPerGame)
{
Serial.print ('x');
Serial1.print ('X'); // to Slave 1 GameOver
GameOver = 1;
Balls = 0; // stops multiple Game Over
}
else
{
TopRipple(0);
// activate solonoid to kick ball into shooter lane
analogWrite(Deliver_Ball_MOSFET_Driver, 200);
// give the right amount of kick
delay (50);
analogWrite(Deliver_Ball_MOSFET_Driver, 0);
}
}
//
if (digitalRead(Tilt) == 0)
{
//light 'TILT' Lamp
waiton (Tilt); // wait for TILT sensor to stabalize
Serial.print ('t'); // inform backglass software
GameOver = 1;
}
//
if ((Phit == 1) && (Ahit == 1))
{
if (BlueBumper == 0)BlueBumper = 1;
}
//
// Sling Shots
if (digitalRead(Slingshot) == 0)
{
waiton (Slingshot); // wait for button release
Serial.print ('a');
Serial1.print ('a'); //to Chime 1
units++;
}
// switchs and Targets
// These are connected in parallel so no need for seperate code
// NOTE LEDS Lights for FLYING CARPET are numbered from 0 to 11
if (digitalRead(Fswitch) == 0)
{
waiton (Fswitch); // wait for button release
TopRipple(1);
// light appropriate light
check_for_special(0);
}
//
if (digitalRead(Lswitch) == 0)
{
waiton (Lswitch); // wait for button release
TopRipple(1);
// light appropriate light
check_for_special(1);
}
//
if (digitalRead(Yswitch) == 0)
{
waiton (Yswitch); // wait for button release
TopRipple(1);
// light appropriate light
check_for_special(2);
if (Yspecial == 1 )
{
// although Replays are awarded, they are for
// AMUSEMENT ONLY
// The display software will ingores replays above 9
replays++;
Serial.print (replays);
Serial1.print ('R');
}
}
//
if (digitalRead(Iswitch) == 0)
{
waiton (Iswitch); // wait for button release
TopRipple(1);
//light appropriate light
check_for_special(3);
}
//
if (digitalRead(Nswitch) == 0)
{
waiton (Nswitch); // wait for button release
TopRipple(1);
//light appropriate light
check_for_special(4);
}
//
if (digitalRead(Gswitch) == 0)
{
waiton (Gswitch); // wait for button release
TopRipple(1);
//light appropriate light
check_for_special(5);
}
if (digitalRead(Cswitch) == 0)
{
waiton (Cswitch); // wait for button release
timer();
if (switch_timer == 0)
{
Serial.print ('c');
Serial1.print ('c');
//light appropriate light
check_for_special(6);
}
}
//
if (digitalRead(Aswitch) == 0)
{
waiton (Aswitch); // wait for button release
timer();
if (switch_timer == 0)
{
Serial.print ('c');
Serial1.print ('c');
Ahit = 1;
//light appropriate light
check_for_special(7);
}
}
//
if (digitalRead(Rswitch) == 0)
{
waiton (Rswitch); // wait for button release
timer();
if (switch_timer == 0)
{
Serial.print ('c');
Serial1.print ('c');
//light appropriate light
check_for_special(8);
if (Rspecial == 1 )
{
// although Replays are awarded, they are for
// AMUSEMENT ONLY
replay();
}
}
}
//
if (digitalRead(Pswitch) == 0)
{
waiton (Pswitch); // wait for button release
timer();
if (switch_timer == 0)
{
Serial.print ('c');
Serial1.print ('c');
//light appropriate light
check_for_special(9);
Phit = 1;
}
}
//
if (digitalRead(Eswitch) == 0)
{
waiton (Eswitch); // wait for button release
timer();
if (switch_timer == 0)
{
Serial.print ('c');
Serial1.print ('c');
//light appropriate light
check_for_special(10);
}
}
//
if (digitalRead(Tswitch) == 0)
{
waiton (Tswitch); // wait for button release
timer();
{
if (switch_timer == 0)
{
Serial.print ('c');
Serial1.print ('c');
}
//light appropriate light
check_for_special(11);
}
}
}
}
Comments