pinball project (space cadet)

For both LED outputs and button inputs, a matrix will work, but for any detailed advice you'll have to show the circuit diagram of the machine first.

An input matrix most probably will be too slow for a pinball game. Port expanders instead offer an interrupt output, which signals a change in the input lines, so that polling at least can be reduced. In a real ball game the contacts are closed for a short time only, so that hardware support is required for those many inputs.

wvmarle:
but for any detailed advice you'll have to show the circuit diagram of the machine first.

i'm hesitant to post any schemes till i know it works, don't want to spread bad info for others, plus im still sorting through parts. however i plan on having an assembled dev board for it by next week. i'll be happy to post that.
i'm not too concerned with replicating the targets on board as i'm willing to tweak the the actual input signal to be comparable using hardware if needs be (pulse extenders, buffers or inverters, etc(i have ample components to do this))

DrDiettrich:
An input matrix most probably will be too slow for a pinball game. Port expanders instead offer an interrupt output, which signals a change in the input lines, so that polling at least can be reduced. In a real ball game the contacts are closed for a short time only, so that hardware support is required for those many inputs.

i didn't think the matrix would be to slow to keep up but i shall definitely bear that in mind!
port expanders? are they basically iIIc accessed i/o pins?
funny you should mention the interrupt. i was going to ask in my previous post, but didn't. if there was any practicality to making it so every time any button(in game target) is pressed it triggers an interrupt on the rising edge. then have the interrupt "scan" the keys for the falling edge and adjust my ints, scores etc. i was thinking if i could do it this way then i could use my case "loops" for level progression and bonus modifiers and let the interrupt handle scoring?

all criticism is welcome.
I really appreciate these answers guys, thank you!

Port expanders are e.g. the MCP23008/23017 and PCF8574/8575 (8/16 pin). They indeed have a pin change interrupt output available, which is to connect to a separate pin on the Arduino to trigger an interrupt there. The interrupt is for a block of 8 pins (so the MCP23017 and PCF8575 have two interrupt outputs), you then have to check which of the eight triggered the interrupt. You can read all pins of one bank in one go and get a single byte for the status of all 8.

Presses by a bouncing ball are indeed much shorter than a human pressing a button, so indeed may be too slow. A port expander with interrupt should definitely work for that.

aka_daz:
sel1 2 3 each represent three drop down targets to select a level, i want the level chosen either to be the last drop down activated ie, 1, 2 or 3 (in game name) or to be level four if all 3 have been dropped, in the context of the game the other potential combinations aren't needed,

That may be what you want but what you code says is:
"Set the level to 1, 2, or 3 if only one is down. Do nothing if two are down. Set the level to 4 if all three are down."
That will give the effect of setting the level to the FIRST one dropped, ignoring the second one dropped, and switching to level 4 when the third one drops.

johnwasser:
That may be what you want but what you code says is:
"Set the level to 1, 2, or 3 if only one is down. Do nothing if two are down. Set the level to 4 if all three are down."
That will give the effect of setting the level to the FIRST one dropped, ignoring the second one dropped, and switching to level 4 when the third one drops.

nicely spotted, this is down to the custom hardware i'll be implementing essentially each drop will trigger a pulse, latching will only occur when all 3 drop, i hope i'm correct in my assumption that pulses will suffice for the first 3 conditions?

(next part i'm still designing) when all 3 drop i plan to use the latch to send some sort of reset signal to a servo/solenid/generic actuator to reset targets while ignoring any pulses but keep the state in the code, this being analogue and not interfacing with arduino. the code only shows 3 drops at the moment in total there will be 12 split into 4 banks of three. given my extremely high i/o count i figured i best to try getting some things done automatically...

to be honest i actually stopped working out the switching for the time being, i'm currently trying to wrap my head round the port expanders... confused.com comes to mind, trying to find a library that makes use of the interrupt function but having no luck as of yet.

again, thanks guys! :slight_smile:

The libraries for those expanders don't use the interrupt themselves. You are to set the interrupt in your own code, then when an interrupt was received go ahead and read the relevant register.

The described level management is different from the space cadet game, it may deserve some more thoughts.

The port expander interrupt pins do not necessarily force controller interrupts, they also can be treated as sequentially readable "hit" flags for one of their inputs.

Eventually the targets can permanently break a line when hit, which is closed again when the target is restored later. Then it's only required that every hit (state change) is counted, and a single actuator can raise all targets of a group.

DrDiettrich:
The described level management is different from the space cadet game, it may deserve some more thoughts.

i don't understand?

DrDiettrich:
Eventually the targets can permanently break a line when hit, which is closed again when the target is restored later. Then it's only required that every hit (state change) is counted, and a single actuator can raise all targets of a group.

thats kinda what i was trying to say except in my version, they pulse as activated individually, then latch once all 3 fall which then triggers the reset. though i must admit i like the sound of your way. if i have this correct? each "bank" would only require one output which i could instead use to increment a value?

I didn't play the game since a couple of years. Perhaps we talk about different levels, I meant the circle in the center reflecting the player's rank.

The target handling depends on the mechanical implementation. How do you drop and raise your targets?

oh no you are correct! i was going to worry about that aspect later, keeping that side in the code for now, my main objectives at the moment where to get the four cadet levels sorted as individual programs: with that done i'll have targets and basic scoring sorted from there i was going to add the interface you mention, plus other "illuminations" and then merge programs... this is my 3rd day on the project in total i've only spent around 10 hours on this so far but as it stands i've accounted for the following...

  1. added value to represent rank multiplier "scoring" at default it will be 5 as rank progresses this int gets doubled and upon wipeout is reset to 0 then bumped back up to 5 on ball launch
  2. offset irl level numbers with incode to account for the different ranks 1 = cadet 2 = cadet level 1 so on until i reach 6 which is the next ranks selection stage
    3)in furtherance to my previous i realised and am about to account in code for "experience" being what takes us to level 6 (in code tag) by having victory increment an as of yet un made int to be checked when the case breaks back in to the do for level 1 (i really hope that makes sense)
  3. added measures in place to add replay and reflex shots later
    5)on my drawing i have grouped inputs into "banks" and labelled each with it's in code name

there is more. in the mood to code not explain atm :wink: but if u wanna know more by all means ask and i'll reply :slight_smile:

targets... the plate would be spring held on a ledge, ball knocks them off and they drop down, the reset was going to be handled like normal drop targets but i was thinking servo instead of solenoid... not yet worked out how i plan to take the inputs. maybe a limit switch or something...
the "latching" i reffered to was probs bad wording, it would be better to say the reset would of pulsed all 3 on the way up

i was hoping to ask a question, i don't like the look of these external I/O things.. well out of my comfort zone as i've never dabbled before. so i nipped out and got my self a pickit, was thinking i might be better having pics interfacing with my switches and sending the data out to be used, have them handle some of the simpler out puts for me aswell ? i assume the mega having extra rx tx lines is more than happy with this?

either way for now i'm using the mega pins to test and limiting my self to 20in 20 out plus my iIIc for lcd debugging

Argh, killed my essay by pressing a wrong key :frowning:

Perhaps you are ahead of me WRT levels, at least you seem to have a plan :slight_smile:

For the drop targets I'd use light barriers or the like, instead of ordinary switches, so that no debouncing is required.
If all targets of a group go into the same port or port extender byte, a simple pattern match is sufficient to find out when all targets of a group are down.

A matrix for the input switches is fine. That's how all real pinball machines do it. They put a diode in series with each switch to prevent phantom presses when multiple switches are closed, pull the rows up to +12v, then drive the columns low (via a ULN2803) in turn and looking at the rows to see if any are pressed. The rows get monitored by LM339's comparing against 6v reference voltage. (this is how it works on 90's era williams bally ones at least)

I skimmed reading above (not enough time to read essays). Are you making a full sized pinball table with it? If so, I suggest using the mechanisms (like for drop targets and the like) from a real pin, not trying to make your own. The pinball machine environment is incredibly harsh - even these commercial parts, designed by experts in pinball mechanics and that sort of mechanical engineering, routinely get beaten up. In general, use the same parts that are used in real pins whenever possible, rather than reinventing the wheel (unless you like mechanical-wheel-reinvention).

(also, as an aside - if you happen to be located in newengland, and need a pin that you could gut and fill with your new game, I've got a non-working 70's era EM pin that I could cut you a really good deal on cause my roommates are on my ass to either make it work or move it out)

DrAzzy:
(also, as an aside - if you happen to be located in newengland, and need a pin that you could gut and fill with your new game, I've got a non-working 70's era EM pin that I could cut you a really good deal on cause my roommates are on my ass to either make it work or move it out)

unfortunately, no... i'm old England :wink: i really appreciate the thought though!

DrAzzy:
I skimmed reading above (not enough time to read essays).

i was trying to avoid that..lol! got a lot of thinking/planning to do and didn't want to bore everyone! :wink:

@the rest of DrAzzy, yeh i realise the harshness of the pinball machine. however, i am determined to make my own. realistically i'll be doing it in steps. for now... a piece of a3 wood with various controllers and switches with an lcd. "debug mode" then a cheap machine. then upgrade... at least that be the plan :wink: it's the challenge. reinventing the wheel always looked fun!

i shall certainly look into the ULN2803 and LM339 after i have got my basic "level progression" with 20in/out running, thank you!

DrDiettrich:
Argh, killed my essay by pressing a wrong key :frowning:

rubbish when that happens

DrDiettrich:
Argh, killed my essay by pressing a wrong key :frowning:

Perhaps you are ahead of me WRT levels, at least you seem to have a plan :slight_smile:

For the drop targets I'd use light barriers or the like, instead of ordinary switches, so that no debouncing is required.
If all targets of a group go into the same port or port extender byte, a simple pattern match is sufficient to find out when all targets of a group are down.

i have no idea what WRT level is lol. i actually was thinking i'd either use opto or rollover limit tbh. i have ample quad op amps for debouncing if required, it's going to come down to cost at this stage, not really priced either up yet tbh

thanks for your replies guys!! i always come away thinking, appreciated!

if any ones curious i can upload my code samples later? (after i've tested and merged them)

sorry to be a nag, been splicing my codes together and started thinking about lights. originally i was thinking 16F690s but now i'm veering to nanos. if i was to use TX/RX1 off the mega would i be able to send words ie, "bank1_hit" "bank1_reset" which would then activate "void bank1_hit(){blah blah}" on the other side?

in furtherence to that i was thinking of adding a 3rd line just a logic out from the mega going to an input pin on nano, the purpose to signal a priority message(the signal will also be sent to sound once i worked it out) so that it knows to play the latest lighting cycle over what its currently doing(basically i'll have my entire code twice in two while statements) is this possible?

while i'm here, my first attempt at merging buggy as out, actually had to start again from my 4 source files, currently my display is bugging out on the first level reset. i'm sure i'll figure it out :wink:

Warning, code is long lol

#include <Wire.h> //initiailise the script by accessing needed libraries
#include <LiquidCrystal_I2C.h> // adds the iIIc  lcd library

#define wiresda 20  //this pin is for iIIc data
#define wirescl 21  //this pin is for iIIc clock


					//targets start here

					
#define launchdtct 2 //this pin is for detecting launch tube entry into play area
#define missionCon 3 //this pin is connected to launch pad rollover.
#define ballDrain 8  //this pin is for the ball drain opto

#define sel1 22 // this pin is drop target 1a
#define sel2 23 // this pin is drop target 2a
#define sel3 24 // this pin is drop target 3a

#define jkpt1 25 // this pin is the jackpot drop target
#define jkpt2 26 // this pin is the jackpot drop target
#define jkpt3 27 // this pin is the jackpot drop target


					//variables for scoring start here


int ballsRem = 3; //this is the value where balls remaining are stored
int currentscore = 0; // this is the value used to diplay the score

int gamestate = 0; // this bit will indicate wether the game is still running
int currentLevel = 0; // this int is used to store current level tag
int levelSel = 0;  //a variable to be used for the level selection

int scoring = 0; // this will be used as a score modifier when each level advances
int score = 0; // this is where the current score will be stored
int highScore = 0;
int experience = 0;

int cadet1 = 5; // the first cadet level, ramp entry four times




LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE); // assigns the pins for lcd use



void setup() {
  
  
  
  pinMode (launchdtct, INPUT);
  pinMode (missionCon, INPUT);
  pinMode (ballDrain, INPUT);
  
  pinMode (sel1, INPUT);
  pinMode (sel2, INPUT); 
  pinMode (sel3, INPUT);
  pinMode (jkpt1, INPUT);
  pinMode (jkpt2, INPUT); 
  pinMode (jkpt3, INPUT);

  digitalWrite(launchdtct, HIGH);
  digitalWrite(missionCon, HIGH);
  digitalWrite(ballDrain, HIGH); 
  
  digitalWrite(sel1, HIGH);
  digitalWrite(sel2, HIGH);
  digitalWrite(sel3, HIGH);
  
  digitalWrite(jkpt1, HIGH);
  digitalWrite(jkpt2, HIGH);
  digitalWrite(jkpt3, HIGH);
  
  
  lcd.begin(16,2);
} 

void loop() {

 // if (digitalRead(launchdtct) == LOW) {
 //   gamestate++ ; //this will detct the ball and activate the game
 // }

  //if (gamestate >= 1) {
   // currentLevel = 1 ; //after detecting the ball lauch this puts the game into level selection 1
   // gamestate --; //reset the game status ready for next ball release. decrement incase of replays
   // lightUp();
  //}
 currentLevel=1;
  while (currentLevel == 1) {        // this statement tells the do statement when to run 

    if (digitalRead(ballDrain) == LOW) {	//code here to handle ball drain
      
      lcd.setCursor(1,2);
      lcd.clear();
	  ballsRem --;							//removes one life from counter
      delay(5);

      if (ballsRem == 0) {					// code here to handle no live remaining
        lightUp();
        currentLevel = 0;
        scoring = 0;
        if (score > highScore) {
          score = highScore;
        }
      }
      else {								// code to handle lives remaining
        lightUp();
        currentLevel --;
      }
    }
    lcd.setCursor(0,0);
    lcd.print("choose level");

    if (digitalRead(sel1) == LOW) {			//select level cadet1
      levelSel = 2; // this detects level 2 being selected
      lcd.setCursor(1,2);
      lcd.clear();
      lcd.print ("confirmlevel 1");
    }
    if (digitalRead(sel2) == LOW) {			//select level cadet2 (doesn't yet exist, glitches code if confirmed)
      levelSel = 3; // this detects level 3 being selected
      lcd.setCursor(1,2);
      lcd.clear();
      lcd.print ("confirmlevel 2");
    }
    if (digitalRead(sel3) == LOW) {			//select level cadet3, see above
      levelSel = 4; // this detects level 4 being selected
      lcd.setCursor(1,2);
      lcd.clear();
      lcd.print ("confirmlevel 3");
    }
    if (sel1 == LOW && sel2 == LOW && sel3 == LOW) {	//select level cadet4, see above
      levelSel = 5; // this detects level 5 being selected
      lcd.setCursor(1,2);
      lcd.clear();
      lcd.print ("confirmlevel 4");
    }
    if (levelSel != 1 && digitalRead(missionCon) == LOW) {
      
      
      lcd.setCursor(1,2);
      lcd.clear();
      lcd.print ("level ");
      lcd.print (levelSel-1);
      lcd.print (" accepted");
      currentLevel = levelSel; // this confirms level by moving the selected number into the register
    }
 
  } 
  
   while(currentLevel ==2){
       scoring=5;
	if(digitalRead(sel1) == LOW){
		score=score+(2*scoring);
                delay(75);
	}
	if(digitalRead(sel2) == LOW){
		score=score+(2*scoring);
                delay(75);
	}
	if(digitalRead(sel3) == LOW){
		score=score+(2*scoring);
                delay(75);
	}
	
	
	
	if(digitalRead(jkpt1) == LOW){
		score=score+(2*scoring);
                delay(75);
	}
	if(digitalRead(jkpt2) == LOW){
		score=score+(2*scoring);
                delay(75);
	}
	if(digitalRead(jkpt3) == LOW){
		score=score+(2*scoring);
                delay(75);
	}
	
	
	
	if(digitalRead(missionCon) == LOW){			//this detects mission objective being triggered
		cadet1 --;
		score = score+(scoring*2);
		delay(75);		
	}
        
        if(cadet1 == 0){			//this detects victory condition
	        experience= 5+experience;
                cadet1=cadet1+4;
                currentLevel = 1;
				lcd.clear();

			if(experience == 16){	//if 16 or more experience has been gained then rank will be promoted
			currentLevel = 6;
            }
                
		}
        
    
	delay(75);
         lcd.clear();
        lcd.setCursor(7,0);
	lcd.print(score);
        lcd.setCursor(0,0);
        lcd.print("Score:");
        lcd.setCursor(0,1);
        lcd.print("Ramp Remain:");
        lcd.setCursor(13,1);
        lcd.print(cadet1);
        levelSel = 1;
  }
   
    

    //level 2 here
    

    //level 3 here
    

    //level 4 here
  }

aka_daz:
sorry to be a nag, been splicing my codes together and started thinking about lights. originally i was thinking 16F690s but now i'm veering to nanos. if i was to use TX/RX1 off the mega would i be able to send words ie, "bank1_hit" "bank1_reset" which would then activate "void bank1_hit(){blah blah}" on the other side?

You could, but perhaps not as simply as you are hoping. You can send commands over serial, parse them at the recipient's end and then call functions based on what was received. You can't send "bank1_hit" and automagically call the function of that name. You could however have an array of structs containing a string received and a corresponding pointer to a function to call when you get that string.

oh no, i realised it would be complicated. it seemed to good to be to true. my overall design hinges on it though (probs find another way in a pinch), i'll be copy and pasting my sound board from the light and just changing the "events" while keeping the names, so hoping to share the data control to save mega pins

An array of structs you say? i think i should get reading lol

Thank you! :slight_smile:

I would solve that with a long switch/case statement.

Also keep your Serial communications short, one or two values, to keep it fast. Use constants or #define statements to replace it with human readable commands in your code.

If you have less than 255 commands you can do it in a single character on the Serial. You may even be able to do away with start/stop bytes, even faster. Just a single value, every one value has a meaning. If more than 255 you need two bytes, and start/stop bytes (use e.g. 0 or 255 for that).

So you'd get code like this in the slave:

#define BANK1_HIT 1
#define BANK1_RESET 2
#define BANK2_HIT 3
#define BANK2_RESET 4

void loop() {

  // bank 1 hit! Send notification to host.
  Serial.write(BANK1_HIT);

}

And on the receiving end:

#define BANK1_HIT 1
#define BANK1_RESET 2
#define BANK2_HIT 3
#define BANK2_RESET 4

void loop() {

  command = Serial.read();
  switch (command) {
    case 1:
      bank1_hit();
      break;

    case 2:
      bank1_reset();
      break;
  }
}

You can use a #include to keep your list of #defines in sync between the various sketches.

wvmarle:
I would solve that with a long switch/case statement.

Also keep your Serial communications short, one or two values, to keep it fast. Use constants or #define statements to replace it with human readable commands in your code.

If you have less than 255 commands you can do it in a single character on the Serial. You may even be able to do away with start/stop bytes, even faster. Just a single value, every one value has a meaning. If more than 255 you need two bytes, and start/stop bytes (use e.g. 0 or 255 for that).

So you'd get code like this in the slave:

my original thoughts where using while statements pretty much the way your describing. but in the context of your example ending each while statement with a "command = 0;" is there any reasons i'd use the case over the while? or vica versa? and thank you. I think i'm gonna go have a play with that idea :slight_smile:

wvmarle:
You can use a #include to keep your list of #defines in sync between the various sketches.

i have a dedicated file in notepad with all my header and void setups, everytime i add more features or start something new i take what a copy to my new sketc. then at the end go back update the file. in truth i wouldn't be sure how to include other sketches variables, is it the same as adding a library?

aka_daz:
is there any reasons i'd use the case over the while? or vica versa?

The while statement is a loop. The switch/case and if/else statements are selectors. Very different use case.

i have a dedicated file in notepad with all my header and void setups, everytime i add more features or start something new i take what a copy to my new sketc. then at the end go back update the file. in truth i wouldn't be sure how to include other sketches variables, is it the same as adding a library?

Pretty much like adding a library indeed - save your headers in a .h file in your libraries folder, then include that in the top of your sketch and it'll be read at compile time.