49-Way Joystick Conversion Board
Jump to navigation
Jump to search
Contents
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
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
- +5 Volts
- Opto Switch 1 (U/D - A)
- Opto Switch 2 (U/D - B)
- Opto Switch 3 (U/D - C)
- Up/Down Direction (Up=Low / Down = High)
- Opto Switch 4 (L/R - A)
- Opto Switch 5 (L/R - B)
- Opto Switch 6 (L/R - C)
- Left/Right Direction (Right = Low / Left = High)
- Key - No Connection
- Ground
- 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);
}
}