Game Plan Star Trip: Interfacing with the switches

Switches

In this first detail blog on bringing my Star Trip Pinball back to life I will cover the pinball switches.

The switches are the only item that connected directly to the original MCU-1 Board without a driver board in between. As with most non-modern pinballs the switches are wired as a matrix. You can read more about switch matrixes here.

I recreated part of the original MCU board, in order to interface with the switches, in the same way the old MCU did. While it may have been possible to detect the switches using a different/simpler circuit I knew that if I replicate the circuit from the MCU it should work just fine without changing the existing wiring and switches.

The Circuit


The original circuit was using two LM339 voltage comparators to read the lines and a single 74HC154 to drive the strobes. Both of these ICs are still available at the local electronics store.


The circuit is pretty simple when you boil it down. Lets take a deeper look into the parts.

First 5V power is brought in via the Arduino to power the circuit.


The 74HC154 is used to drive the strobe lines. In all honesty this chip probably could have been skipped and just use the Arduino pins to drive the strobes, however I already had the parts and it allows the Arduino to just be communicating with ICs just like the other driver boards. By setting the ABCD pins on the 154 the Arduino is able to set one of the 4 strobe lines low at any one point in time.


The four strobe lines are pulled high by a 1K resistor.


A diode clamp is applied to the four strobe lines. Which helps protect the ICs from over voltage. (see: https://electronics.stackexchange.com/questions/243422/can-anyone-tell-me-what-is-the-purpose-of-diodes-at-the-output-of-a-chip?noredirect=1&lq=1 and https://en.wikipedia.org/wiki/Clamper_(electronics))
The cabinet has two wiring harnesses that connect the switch matrixes. Two of the strobe lines are shared across both harnesses with the return lines of both harnesses tied together. In reality once the two headers used to connect to the harnesses are wired together you can ignore the fact the cables are spit out onto two harnesses as the rest of the circuit just interfaces with 4 strobe lines and 8 return lines.

Two LM339 ICs are used to provide eight voltage comparators. The return lines are then connected to the IN- pins on the LM339.


The return lines on the harness are floating when the switches are inactive so the circuit pulls them high using a 1K resistor to 5V. When a switch column is strobed (pulled low) any switches that are pressed/activated will have their corresponding row line pulled to ground also.

The voltage comparator compares the return line voltage to a fixed voltage coming from a voltage divider using 100K and 220K resistors, which gives about a 1.5V output.


While a switch is open the voltage comparator is comparing 5V (via the pull up resistor) vs 1.5V (voltage divider) and sets the output to 0. When a switch is active it gets pulled to GND (via the strobe line) and the voltage comparator is comparing 0V vs 1.5V and sets the output to 1.

The hardware

So I designed the following board layout to connect the switch harness from the cabinet onto my Arduino Mega. The two cabinet connectors will plug in on the righthand side header and the Arduino Mega will plug into the header shown on the top. I have used colours to show what will be wired to what.


The finished product is a little harder to follow with the wires running over the top of everything. If I was going to make another one of these I would seriously consider either etching my own PCB or designing and ordering a print from online.

The Code

I have created this test code to show the minimum switch code. The way it works after the initial variable setup is to set one of the strobe pins LOW on the 74HC154 leaving the rest HIGH. Then it will read each of the return switch lines that are connected to the voltage comparators. 

If the switch has changed state since the last time we read this switch and it has been enough (debounce) time for the switch to settle into its new state we then record the new state, the time we set the new state (to start the debounce timer) then call a handler function that would update our game (score, fire a coil, play a sound, etc).


const int NUM_SWITCHES = 32;
const int STROBE_0 = 8;  // The Arduino Pin connected to the A pin on the 74HC154
const int STROBE_1 = 9;  // The Arduino Pin connected to the B pin on the 74HC154
const int STROBE_2 = 10; // The Arduino Pin connected to the C pin on the 74HC154
const int STROBE_3 = 11; // The Arduino Pin connected to the D pin on the 74HC154
const int SWITCH_LINE_0 = 30; // The Arduino Pin connected to the first voltage comparator on the LM339s
const int SWITCH_LINE_1 = 31; // The Arduino Pin connected to the second voltage comparator on the LM339s
const int SWITCH_LINE_2 = 32; // The Arduino Pin connected to the third voltage comparator on the LM339s
const int SWITCH_LINE_3 = 33; // The Arduino Pin connected to the forth voltage comparator on the LM339s
const int SWITCH_LINE_4 = 34; // The Arduino Pin connected to the fifth voltage comparator on the LM339s
const int SWITCH_LINE_5 = 35; // The Arduino Pin connected to the sixth voltage comparator on the LM339s
const int SWITCH_LINE_6 = 36; // The Arduino Pin connected to the seventh voltage comparator on the LM339s
const int SWITCH_LINE_7 = 37; // The Arduino Pin connected to the eighth voltage comparator on the LM339s
const int SWITCH_LINES[] = {SWITCH_LINE_0,
                           SWITCH_LINE_1,
                           SWITCH_LINE_2,
                           SWITCH_LINE_3,
                           SWITCH_LINE_4,
                           SWITCH_LINE_5,
                           SWITCH_LINE_6,
                           SWITCH_LINE_7};
const int SWITCH_STROBES[] = {STROBE_0,
                             STROBE_1,
                             STROBE_2,
                             STROBE_3};
unsigned int _switchDebouceTime[NUM_SWITCHES];
unsigned long _switchDebouceTimer[NUM_SWITCHES];
bool _switchStates[NUM_SWITCHES];

void setup() {
  for(int switch_number = 0; switch_number < NUM_SWITCHES; switch_number++) {
    _switchStates[switch_number] = false; // Set each switch to off
    _switchDebouceTime[switch_number] = 20; // Set each switch to 20ms debounce
    _switchDebouceTimer[switch_number] = millis(); // Record initial state change as now
  }
}

void loop() {
  bool switchState = false;
  int switchNumber = 0;
  for(int j = 0; j < 4; j++) {
    // For each of the strobes 0-3.
    _switchStrobe(j);

    for(int pin = 0; pin < 8; pin++) {
      // Read each of the Return lines 0-7
      switchNumber = pin+(j*8); // The switches are given numbers 0-7 for the first strobe, 8-15 for the second etc...
      switchState = digitalRead(SWITCH_LINES[pin]); // Is the switch closed or open.
      
      if(_switchStates[switchNumber] != switchState) {
        // The switch is in a different state since we last saw it.
        
        // Next we make sure its been long enough since we last updated our state for switch debouncing.
        if(millis() - _switchDebouceTimer[switchNumber] > _switchDebouceTime[switchNumber]) {
          // We have found a switch in a new state.
          // Update our state records
          _switchStates[switchNumber] = switchState; 

          // Record when we did this so we can start ignoring changes on this switch until it debounces.
          _switchDebouceTimer[switchNumber] = millis(); 
          
         // Finally we should handle this switch change.
          _switchEvent(switchNumber, switchState);
        }
      }
    }
  }
}

void _switchStrobe(int strobeNumber) {
  // The strobes are mapped to specific pins on the 74HC154. To abstract this
  // I have made this helper to set the ABCD pins connected to the 74HC154 to the correct
  // state to strobe one of the lines.
  
  switch (strobeNumber) {  
   case 0:  
    digitalWrite(SWITCH_STROBES[0], LOW);  
    digitalWrite(SWITCH_STROBES[1], HIGH);  
    digitalWrite(SWITCH_STROBES[2], HIGH);  
    digitalWrite(SWITCH_STROBES[3], HIGH);  
    break;  
   case 1:  
    digitalWrite(SWITCH_STROBES[0], LOW);  
    digitalWrite(SWITCH_STROBES[1], LOW);  
    digitalWrite(SWITCH_STROBES[2], HIGH);  
    digitalWrite(SWITCH_STROBES[3], HIGH);  
    break;  
   case 2:  
    digitalWrite(SWITCH_STROBES[0], HIGH);  
    digitalWrite(SWITCH_STROBES[1], HIGH);  
    digitalWrite(SWITCH_STROBES[2], LOW);  
    digitalWrite(SWITCH_STROBES[3], HIGH);  
    break;  
   case 3:  
    digitalWrite(SWITCH_STROBES[0], HIGH);  
    digitalWrite(SWITCH_STROBES[1], LOW);  
    digitalWrite(SWITCH_STROBES[2], HIGH);  
    digitalWrite(SWITCH_STROBES[3], HIGH);  
    break;  
  }  
  delayMicroseconds(100); //AllowTime for the strobes to settle.  
 }

void _switchEvent(int switchNumber, bool switchState) {
 switch (switchNumber) {
   // Lots of case statements here....
   case 5:
     if(switchState) {
       // Switch 5 is pressed in here
     } else {
       // Switch 5 is released here
     }
     break;
  }
}

You will see how these code blocks all get put together to formulate the full game in a future blog post.

Stay tuned for interfacing with the next part of the pinball machine.

Comments

Popular posts from this blog

Pinball Compute Setup

MPF Unittest automation with Bitbucket Pipelines

Bringing a Game Plan Star Trip back from the dead