Close

The Controller

A project log for Virtual Pinball

Another Virtual Pinball machine, using scrapped parts.

cees-meijerCees Meijer 05/18/2023 at 20:500 Comments

Adding the buttons. I've done this before. First when I built my Arcade cabinet back in 2013, and later when building a RetroPie Handheld.

The keys that need to be pressed for Virtual Pinball:

I'll just focus on the Visual Pinball keys, where the essential ones are :

Left Shift, Right Shift, Enter, 1, 5 and Q

This is quite similar to what I used on my Arcade cabinet, so lets start from that. But when I looked at the code ( which was written now almost 10 years ago) it looked almost alien to me. Fortunately the text in the blog indicated that it was based it on someone elses code. So I did not really figure this out all by myself, and thats probably the reason I forgot all about it. As it was written for an original Arduin UNO it was necessary to generate all keyboard codes manually. Something that is no longer required for a Arduino Pro Micro as this supports the 'Keyboard' commands which make sending key codes much easier.

The nudge and tilt buttons are trickier. It would be great if I could just trigger these from the actual movement of the cabinet, using an accelerometer. Now there's only a Polulu AltIMU 9 DOF Sensor module in my box, which contains (among other things) a LSM303D Accelerometer. Reading its values is easy using the standard library, but it is a bit trickier to actually convert the movement to a key-press. That is to say: a single key press for either a left or right nudge when moving the sensor. The issue with acceleration measurements is that any acceleration in any direction is always immediately followed by one in the opposite direction (if not, your pinball machine would quickly be launched into space..) So simple threshold detection for a left or right push will not work. Therefore I added a counter that starts counting down after the event, and ignores all other measurements during a certain short period. Next problem will be to filter out the static acceleration, caused by gravity. Just tilting the sensor will immediately change the base-value. So I added a high-pass filter which only passes sudden changes and then quickly returns to a value around 0.
This is the Arduino code (for now) . I will probably have to fine-tune the threshold values once the sensor is mounted in the housing, but thats for later.

/*
Virtual Pinball interface
Read the buttons  and send as keypress.
Read the LSM303D Accelerometer and use it as a 'nudge 'sensor
Emulate keyboard presses using an Arduino Micro
*/ 
// This code is in the public domain.
// ****************************************************************************
#include <Wire.h>
#include <LSM303.h>
#include "Keyboard.h"

#define ENT            176
#define UP            218
#define DWN            217
#define LFT            216
#define RGT            215
#define LSH            129
#define RSH            133


/*
Keyboard key         Function               Code
--------------------------------------------------------
5                    Insert coin            53
1, 2                 Start (players 1, 2)   49,50
Arrow keys (U,D,L,R) Move Joystick          218,217,216,215
Enter                Button 1/Launch Ball   176
Left Shift           Button 2/Left Flipper  129
Right Shift          Button 3/Right Flipper 133
Q                    Button 4/Quit          113  
Z                    Nudge from Left
/                    Nudge from Right
Space                Nudge Forward
*/

const int NR_BUTTONS = 6;
const int ACCELERATION_THRESHOLD = 10000;

int B = 0;
int btn_val =0;
long X_Acc,Y_Acc, Z_Acc;
long X_Filt;
long Y_Filt;
int X_State,Y_State,_Z_State;
int button_pin[6]=   {10 ,16 ,14 ,15 ,18 ,19 }; // All the Arduino Pin numbers that are used for the buttons
char button_key[6] = {'5','1','Q',ENT,LSH,RSH}; // The keys they should press
int buttonState[6]=  {0,0,0,0,0,0};
int solenoidPin = 20;
char acc_key[3] = {'Z','/',' '};
int accState[3]= {0,0,0};
int accKeyPressed[3] ={0,0,0};
int WaitTimer=0;
int i=0;
float gain = 0.2;
bool TEST = false;

float xfilt_state=0;float yfilt_state=0;

LSM303 compass;

char report[80];

void setup()
{
  Serial.begin(9600);

   for ( B = 0;B<NR_BUTTONS;B++)
  {
    pinMode( button_pin[B], INPUT_PULLUP );
  }
  pinMode(solenoidPin,OUTPUT);
  Wire.begin();
  if (  compass.init(LSM303::device_D,LSM303::sa0_high) )
  {
   compass.enableDefault();
  }
  else {Serial.println("Compass Fail.");}
  Serial.println("Start...");
  Keyboard.begin();
}


void loop()
{
  
//Check all buttons, and if the state changed emulate a keyboard key-press.
  for( B = 0;B<NR_BUTTONS;B++)
  {
    btn_val = digitalRead( button_pin[B] );
    // Send a command when button state has changed/
    if(buttonState[B] != btn_val)
    {
      buttonState[B] = btn_val;
      if (btn_val == 0){Keyboard.press( button_key[B] );}
      else{Keyboard.release( button_key[B] );}
    }
  }
 //*******************************************************************************************
 // Check the accelerometer. If an acceleration exceeds a certain threshold,
 //  emulate the button-press that is associated with the 'Nudge'. 
 // Then start a timer to ignore any additional detection of movements for a few miliseconds.
//*******************************************************************************************
  compass.read();
//Send the acceleration through a High pass filter so only sudden changes are detected
    Y_Filt = highpass_y((float)compass.a.y); 
    X_Filt = highpass_x((float)compass.a.x);

    //snprintf(report, sizeof(report), "%6d, %6d, %6d, %6d, ",compass.a.x, compass.a.y, compass.a.z);
    //Serial.print(report);
    // Serial.print(+20000);Serial.print(", ");
    // Serial.print(-20000);Serial.print(", ");
    // Serial.print(X_Filt);Serial.print(", ");
    // Serial.println(Y_Filt);

    
    if(WaitTimer>0) {WaitTimer-=1;}
    else
    {
    if(fabs(Y_Filt) > ACCELERATION_THRESHOLD)
     {
       if(Y_Filt>ACCELERATION_THRESHOLD)
       {
        if(TEST){Serial.println("LEFT_NUDGE");}
        else{Keyboard.press( acc_key[0] );accKeyPressed[0]=1;}
        WaitTimer=50;
       }else
       {

        if(TEST){Serial.println("RIGHT_NUDGE");}
        else{Keyboard.press( acc_key[1] );accKeyPressed[1]=1;}   
        WaitTimer=50;
       }
     }
    if(fabs(X_Filt) > ACCELERATION_THRESHOLD)
     {
      if(TEST){Serial.println("FORWARD");}
      else{Keyboard.press( acc_key[2] );accKeyPressed[2]=1;}   
      WaitTimer=50;
     }
    }
 

 for(i=0;i<3;i++)
 {
   if(accKeyPressed[i]>0)
   {
     accKeyPressed[i]=0;
     Keyboard.release( acc_key[i] );
     WaitTimer=50;
   }
 }
  delay(10);
}

float highpass_x(float input) {
    float retval = input - xfilt_state;
    xfilt_state += gain * retval;
    return retval;
}


float highpass_y(float input) {
    float retval = input - yfilt_state;
    yfilt_state += gain * retval;
    return retval;
}

Discussions