49-Way Joystick Conversion Board

From Williams Arcades Wiki
Jump to navigation Jump to search

Custom PCB - Drop-In

This board is a drop-in replacement for the original 49-way(Sinistar/ArchRivals/Pigskin 621 AD).

  • What does it do?
    • Converts 49-Way Joysticks to 8-Way (or other types if desired)
    • Uses all other hardware from the joystick besides the board
    • ALL components on board are new including the Optical Switches
    • Super easy installation
    • Provides user ability to select Joystick type with DIP switches
    • 4 outputs are provided
      • Up
      • Down
      • Left
      • Right
    • Uses Williams Standard 7-Pin connector and Pinout (Bubbles, Spat, Inferno, Joust-with rare 2-way optical sticks)
    • Default setting is for 8-Way that duplicates the dedicated Williams 8-Way Optical Joystick

Connection Information

8-Way Optical Joystick Pinout.png

Programmed Joystick Emulation Modes - 10 Unique

  • Thanks to Sean Riddle for the work he did building an EPROM based translator. Lots of the information I used to develop this was from him. You can visit his site HERE. (Thanks Sean!)

Mode 0: 8-Way | Standard

Mode 1: 8-Way | No Dead Zone

Mode 2: 8-Way | With Dead Zone

Mode 3: 4-Way | Standard

Mode 4: 4-Way | With Dead Zone

Mode 5: 4-Way | Corners Left/Right

Mode 6: 4-Way | Corners Up/Down

Mode 7: 2-Way | Horizontal

Mode 8: 2-Way | Vertical

Mode 9: Diagonal

49-Way Joystick Information

The Williams 49-Way Optical Joystick was advanced for the time. As the device relied on optical interrupter switches, there were no contacts to wear out or adjust.

Original 49-Way

Operation - Pinout
  • 3 Optical Interrupter Switches are used for each direction (Up/Down-Y and Left/Right -X) .Total of 6 switches
    • This provides a total of 7 possible positions in each X and Y axis. (3 Up/1 Center/3 Down - 3 Left/1 Center/3 Right) (7 * 7 = 49 Unique positions)
  • There are 8 unique outputs
    • Opto Swtich 1 (U/D - A)
    • Opto Switch 2 (U/D - B)
    • Opto Switch 3 (U/D - C)
    • Up/Down Direction
    • Opto Switch 4 (L/R - A)
    • Opto Switch 5 (L/R - B)
    • Opto Switch 6 (L/R - C)
    • Left/Right Direction
  • 12 Pin Molex Header Pin-Out
    1. +5 Volts
    2. Opto Switch 1 (U/D - A)
    3. Opto Switch 2 (U/D - B)
    4. Opto Switch 3 (U/D - C)
    5. Up/Down Direction (Up=Low / Down = High)
    6. Opto Switch 4 (L/R - A)
    7. Opto Switch 5 (L/R - B)
    8. Opto Switch 6 (L/R - C)
    9. Left/Right Direction (Right = Low / Left = High)
    10. Key - No Connection
    11. Ground
    12. Ground


Atmel Microprocessor Source Code:


//Program Written By: Brad Raedel
//03242020 - Revision A 
//** All 8 Profiles active
//** Outputs now change to inputs when not active - This allows the pull-up resistors to control the high logic
//03162020 - Revision 1 - Just get it working

//Up
int Opto1 = 2;    //Input Pin 2
int Opto2 = 3;    //Input Pin 3
int Opto3 = 4;    //Input Pin 4
int Up = 9;      //Output to Game - Pin 10
int Dn = 10;      //Output to Game - Pin 11
//Down
int Opto4 = 5;    //Input Pin 5
int Opto5 = 6;    //Input Pin 6
int Opto6 = 7;    //Input Pin 7
int Left = 11;    //Output to Game - Pin 12
int Right = 12;   //Output to Game - Pin 13

//Option Switches - These will be used to select mode/options (0 to 15)
int Op1 = A0; //1
int Op2 = A1; //2
int Op3 = A2; //4
int Op4 = A3; //8
int OpVal = 0; //Default to zero

bool Opto1St;   //Opto States
bool Opto2St;
bool Opto3St;
bool Opto4St;
bool Opto5St;
bool Opto6St;
bool UpSt;      //Set HIGH when stick is UP
bool DnSt;      //Set HIGH when Stick is DOWN
bool LtSt;      //Set HIGH when stick is LEFT
bool RtSt;      //Set HIGH when Stick is RIGHT
bool LockedStUD;//Used to Latch the UP or DOWN position
bool LockedStLR;//Used to Latch the LEFT or RIGH postion
int UpVal = 3;    //Store the Up position (0-2)
int DnVal = 3;    //Store the Down postion (0-2)
int UpDnPos = 3;  //Absolute postion of Up/Down 0 = Top, 6 = Bottom, 3 is Center/Neutral
int LtVal = 3;    //Store the Left position (0-2)
int RtVal = 3;    //Store the Right position (0-2)
int LtRtPos = 3;  //Absolute postion of Left/Right 0 = Left, 6 = Right, 3 is Center/Neutral
int AbsPos = 24;
int i = 0;

//Rows Then Columns - Stored Like this { {Row 0 - 0 to 48},{Row 2 - 0 to 48}...etc..
// 8-Way Standard
const bool  Up8WayStd[49]  =  {1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0} ;
const bool  Dn8WayStd[49]  =  {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1} ;
const bool  Lt8WayStd[49]  =  {1,1,0,0,0,0,0,1,1,0,0,0,0,0,1,1,1,0,0,0,0,1,1,0,0,0,0,0,1,1,1,0,0,0,0,1,1,0,0,0,0,0,1,1,0,0,0,0,0} ;
const bool  Rt8WayStd[49]  =  {0,0,0,0,0,1,1,0,0,0,0,0,1,1,0,0,0,0,1,1,1,0,0,0,0,0,1,1,0,0,0,0,1,1,1,0,0,0,0,0,1,1,0,0,0,0,0,1,1} ;
// 8-Way with NO Dead Zone
const bool  Up8WayNoDZ[49]  = {1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0} ;
const bool  Dn8WayNoDZ[49]  = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1} ; 
const bool  Lt8WayNoDZ[49]  = {1,1,0,0,0,0,0,1,1,0,0,0,0,0,1,1,1,0,0,0,0,1,1,1,0,0,0,0,1,1,1,0,0,0,0,1,1,0,0,0,0,0,1,1,0,0,0,0,0} ;
const bool  Rt8WayNoDZ[49]  = {0,0,0,0,0,1,1,0,0,0,0,0,1,1,0,0,0,0,1,1,1,0,0,0,0,1,1,1,0,0,0,0,1,1,1,0,0,0,0,0,1,1,0,0,0,0,0,1,1} ; 
// 8-Way with Dead Zone
const bool  Up8WayDZ[49]  = {1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0} ;
const bool  Dn8WayDZ[49]  = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1} ;
const bool  Lt8WayDZ[49]  = {1,1,0,0,0,0,0,1,1,0,0,0,0,0,1,1,0,0,0,0,0,1,1,0,0,0,0,0,1,1,0,0,0,0,0,1,1,0,0,0,0,0,1,1,0,0,0,0,0} ;
const bool  Rt8WayDZ[49]  = {0,0,0,0,0,1,1,0,0,0,0,0,1,1,0,0,0,0,0,1,1,0,0,0,0,0,1,1,0,0,0,0,0,1,1,0,0,0,0,0,1,1,0,0,0,0,0,1,1} ;
//4-Way Standard
const bool  Up4WayStd[49]  =  {0,1,1,1,1,1,0,0,0,1,1,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0} ; 
const bool  Dn4WayStd[49]  =  {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,1,1,0,0,0,0,1,1,1,1,0} ;
const bool  Lt4WayStd[49]  =  {0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,1,0,0,0,0,0,1,1,1,0,0,0,0,1,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0} ;
const bool  Rt4WayStd[49]  =  {0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,1,0,0,0,0,1,1,1,0,0,0,0,0,1,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0} ;
//4-Way Corners L/R
const bool  Up4WayLR[49]  = {0,1,1,1,1,1,0,0,1,1,1,1,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0} ;
const bool  Dn4WayLR[49]  = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,1,1,1,1,1,0,0,1,1,1,1,1,0} ;
const bool  Lt4WayLR[49]  = {1,0,0,0,0,0,0,1,0,0,0,0,0,0,1,1,1,0,0,0,0,1,1,1,0,0,0,0,1,1,1,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0} ;
const bool  Rt4WayLR[49]  = {0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,1,1,1,0,0,0,0,1,1,1,0,0,0,0,1,1,1,0,0,0,0,0,0,1,0,0,0,0,0,0,1} ;
//4-Way Corners U/D
const bool  Up4WayUD[49]  = {1,1,1,1,1,1,1,0,1,1,1,1,1,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0} ;
const bool  Dn4WayUD[49]  = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,1,1,1,1,1,0,1,1,1,1,1,1,1} ;
const bool  Lt4WayUD[49]  = {0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,1,0,0,0,0,0,1,1,1,0,0,0,0,1,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0} ;
const bool  Rt4WayUD[49]  = {0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,1,0,0,0,0,1,1,1,0,0,0,0,0,1,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0} ;
// 2-Way Horizontal
const bool  Up2WayH[49]  =  {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0} ;
const bool  Dn2WayH[49]  =  {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0} ;
const bool  Lt2WayH[49]  =  {1,1,1,0,0,0,0,1,1,1,0,0,0,0,1,1,1,0,0,0,0,1,1,1,0,0,0,0,1,1,1,0,0,0,0,1,1,1,0,0,0,0,1,1,1,0,0,0,0} ;  
const bool  Rt2WayH[49]  =  {0,0,0,0,1,1,1,0,0,0,0,1,1,1,0,0,0,0,1,1,1,0,0,0,0,1,1,1,0,0,0,0,1,1,1,0,0,0,0,1,1,1,0,0,0,0,1,1,1} ;
// 2-Way Vertical
const bool  Up2WayV[49]  =  {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0} ;  
const bool  Dn2WayV[49]  =  {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1} ;
const bool  Lt2WayV[49]  =  {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0} ;
const bool  Rt2WayV[49]  =  {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0} ;

bool  UpOut[49] = {}; 
bool  DnOut[49] = {};
bool  LtOut[49] = {};
bool  RtOut[49] = {};
void setup() {
//Serial.begin(57600);    //Disable when not debugging
pinMode(Opto1,INPUT);   //Set Direction of physical inputs/outputs
pinMode(Opto2,INPUT);
pinMode(Opto3,INPUT);
pinMode(Opto4,INPUT);
pinMode(Opto5,INPUT);
pinMode(Opto6,INPUT);
pinMode(Up,INPUT);
pinMode(Dn,INPUT);
pinMode(Left,INPUT);
pinMode(Right,INPUT);
pinMode(Op1,INPUT);
pinMode(Op2,INPUT);
pinMode(Op3,INPUT);
pinMode(Op4,INPUT);

//Let's get our DIP Switch settings (Will = 0 to 15)
if (digitalRead(Op1) == HIGH)
{
  OpVal = 1;
}
if (digitalRead(Op2) == HIGH)
{
  OpVal = OpVal + 2;
}
if (digitalRead(Op3) == HIGH)
{
  OpVal = OpVal + 4;
}
if (digitalRead(Op4) == HIGH)
{
  OpVal = OpVal + 8;
}
//Serial.println(OpVal);
  switch (OpVal) {
  case 0:         //DIP Switch 0 - 8-Way - No Dead zone - This routine copies the array - Only on setup, not during loop/runtime    
  // 8WayStd
    i = 0;
    while( i < 49 ){
    UpOut[ i ] = Up8WayStd[ i ];
    DnOut[ i ] = Dn8WayStd[ i ];
    LtOut[ i ] = Lt8WayStd[ i ];
    RtOut[ i ] = Rt8WayStd[ i ];
    ++i;}
  break;
  case 1:     //DIP Switch 1 -    
  // 8WayNoDZ
    i = 0;
    while( i < 49 ){
    UpOut[ i ] = Up8WayNoDZ[ i ];
    DnOut[ i ] = Dn8WayNoDZ[ i ];
    LtOut[ i ] = Lt8WayNoDZ[ i ];
    RtOut[ i ] = Rt8WayNoDZ[ i ];
    ++i;}
  break;
 case 2:     //DIP Switch 2 -    
  // 8WayDZ
    i = 0;
    while( i < 49 ){
    UpOut[ i ] = Up8WayDZ[ i ];
    DnOut[ i ] = Dn8WayDZ[ i ];
    LtOut[ i ] = Lt8WayDZ[ i ];
    RtOut[ i ] = Rt8WayDZ[ i ];
    ++i;}
  break;
  case 3:     //DIP Switch 3 -    
  // 4WayStd
    i = 0;
    while( i < 49 ){
    UpOut[ i ] = Up4WayStd[ i ];
    DnOut[ i ] = Dn4WayStd[ i ];
    LtOut[ i ] = Lt4WayStd[ i ];
    RtOut[ i ] = Rt4WayStd[ i ];
    ++i;}
  break;
  case 4:     //DIP Switch 4 -    
  // 4WayLR
    i = 0;
    while( i < 49 ){
    UpOut[ i ] = Up4WayLR[ i ];
    DnOut[ i ] = Dn4WayLR[ i ];
    LtOut[ i ] = Lt4WayLR[ i ];
    RtOut[ i ] = Rt4WayLR[ i ];
    ++i;}
  break;
    case 5:     //DIP Switch 5 -    
  // 4WayUD
    i = 0;
    while( i < 49 ){
    UpOut[ i ] = Up4WayUD[ i ];
    DnOut[ i ] = Dn4WayUD[ i ];
    LtOut[ i ] = Lt4WayUD[ i ];
    RtOut[ i ] = Rt4WayUD[ i ];
    ++i;}
  break;
    case 6:     //DIP Switch 6 -    
  // 2WayH
    i = 0;
    while( i < 49 ){
    UpOut[ i ] = Up2WayH[ i ];
    DnOut[ i ] = Dn2WayH[ i ];
    LtOut[ i ] = Lt2WayH[ i ];
    RtOut[ i ] = Rt2WayH[ i ];
    ++i;}
  break;
      case 7:     //DIP Switch 7 -    
  // 2WayV
    i = 0;
    while( i < 49 ){
    UpOut[ i ] = Up2WayV[ i ];
    DnOut[ i ] = Dn2WayV[ i ];
    LtOut[ i ] = Lt2WayV[ i ];
    RtOut[ i ] = Rt2WayV[ i ];
    ++i;}
  break;
  
  }
} 

//Main Program that loops
void loop() {
// Up/Down Logic
Opto1St = digitalRead(Opto1); //Read Optical Switch 1
Opto2St = digitalRead(Opto2); //Read Optical Switch 2
Opto3St = digitalRead(Opto3); //Read Optical Switch 2
if (Opto1St == true && Opto2St == true && Opto3St == true)  //Determine if the joystick is in the center
{
  LockedStUD = false;   //Unlatch to allow Up or Down to assume control
  UpSt = false;         //Clear Up State
  DnSt = false;         //Clear Down State
  UpDnPos = 3;          //Set Vertical position to center
}
//Latch Direction (Determines if up or down
 if (Opto1St == false && LockedStUD == false)   //UP if True
      {
        UpSt = true;          //Set UP State to TRUE
        LockedStUD = true;    //We lock the State so DOWN can't Steal it!
      }
if (Opto3St == false && LockedStUD == false)    //DOWN if True
      {
        DnSt = true;        //Set DOWN State to TRUE
        LockedStUD = true;  //We lock the State so UP can't Steal it!
      }
//We will figure out what row we are in (0 to 6 - 3 is center)
if (UpSt == true) //Up doesn't need any change since adding the Optical Switches is 0 to 2
{
  UpVal = Opto1St + Opto2St + Opto3St;
  UpDnPos = UpVal;
}
if (DnSt == true)     //For Down, we determine how many Optical Switches are Low, add them up then use a look-up table to generate the position 4 to 6
{
  DnVal = Opto1St + Opto2St + Opto3St;
  switch (DnVal) {
  case 0:      //All Optos are LOW
      UpDnPos = 6;
  break;
  case 1:     //Two of the Optos are LOW    
      UpDnPos = 5;
  break;
  case 2:    //One of the Optos are LOW (The others are still High)  
      UpDnPos = 4;
  break;
  }
}

// Left/Right Logic
Opto4St = digitalRead(Opto4); //Read Optical Switch 4
Opto5St = digitalRead(Opto5); //Read Optical Switch 5
Opto6St = digitalRead(Opto6); //Read Optical Switch 6
if (Opto4St == true && Opto5St == true && Opto6St == true)  //Determine if the joystick is in the center
{
  LockedStLR = false;   //Unlatch to allow Left or Right to assume control
  LtSt = false;         //Clear Left State
  RtSt = false;         //Clear Right State
  LtRtPos = 3;          //Set Horizontal position to center
}
//Latch Direction (Determines if Left or Right
 if (Opto4St == false && LockedStLR == false)   //Right if True
      {
        RtSt = true;          //Set Right State to TRUE
        LockedStLR = true;    //We lock the State so Left can't Steal it!
      }
if (Opto6St == false && LockedStLR == false)    //Left if True
      {
        LtSt = true;        //Set Left State to TRUE
        LockedStLR = true;  //We lock the State so Right can't Steal it!
      }
//We will figure out what row we are in (0 to 6 - 3 is center)
if (LtSt == true) //Up doesn't need any change since adding the Optical Switches is 0 to 2
{
  LtVal = Opto4St + Opto5St + Opto6St;
  LtRtPos = LtVal;
}
if (RtSt == true)     //For Down, we determine how many Optical Switches are Low, add them up then use a look-up table to generate the position 4 to 6
{
  RtVal = Opto4St + Opto5St + Opto6St;
  switch (RtVal) {
  case 0:      //All Optos are LOW
      LtRtPos = 6;
  break;
  case 1:     //Two of the Optos are LOW    
      LtRtPos = 5;
  break;
  case 2:    //One of the Optos are LOW (The others are still High)  
      LtRtPos = 4;
  break;
  }
}
//Calculate the absolute position
AbsPos = (UpDnPos * 7) + LtRtPos;
   //Serial.println(AbsPos);
  //Output 
  //UP
   if (UpOut[AbsPos] == true){
    pinMode(Up,OUTPUT); 
    digitalWrite(Up,LOW);
   }
   else {
    pinMode(Up,INPUT);
   }
    //Down
   if (DnOut[AbsPos] == true){
    pinMode(Dn,OUTPUT); 
    digitalWrite(Dn,LOW);
   }
   else {
    pinMode(Dn,INPUT);
   }
   //Left
   if (LtOut[AbsPos] == true){
    pinMode(Left,OUTPUT); 
    digitalWrite(Left,LOW);
   }
   else {
    pinMode(Left,INPUT);
   }
   //Right
   if (RtOut[AbsPos] == true){
    pinMode(Right,OUTPUT); 
    digitalWrite(Right,LOW);
   }
   else {
    pinMode(Right,INPUT);
   }
}