If you grew up in the early 1980’s and were into video games, you probably had an Atari 2600, ColecoVision, or similar game console. The controllers or joysticks for each of these systems had a distinct feel that is different from today’s game consoles or PC game controllers. If you find yourself longing to plug your old ColecoVision or Atari 2600 joystick into your modern PC, but are not sure how to go about it, this article is for you. This article describes how to use an Arduino Leonardo (or similar) card to make your classic console joystick look like a USB keyboard.
I designed this Classic Joystick to USB Keyboard Adapter with the ADAMEm ColecoVision and Coleco ADAM emulator in mind (http://www.komkon.org/~dekogel/adamem.html, which can be run in Microsoft Windows using Virtual ADAM http://www.sacnews.net/adamcomputer/downloads/). However it will work with most emulators and any game or program that can use the keyboard as input. I have also tested it with the following emulators:
- blueMSX (ColecoVision, MSX, and others) -
http://www.bluemsx.com/http://bluemsx.msxblue.com/ - Stella (Atari 2600) -
http://stella.sourceforge.net/https://stella-emu.github.io/
Default Keyboard Mappings
By default this classic console joystick to USB keyboard adapter uses the following mappings (these are the default mappings used by the ADAMEm emulator):
Joystick Action | Keyboard Mapping |
Up | Up Arrow Key |
Down | Down Arrow Key |
Left | Left Arrow Key |
Right | Right Arrow Key |
Left Fire Button | Left Alt Key |
Right Fire Button | Left Ctrl Key |
Purple Fire Button | Left Shift Key |
Blue Fire Button | z |
Keypad 0 | 0 |
Keypad 1 | 1 |
Keypad 2 | 2 |
Keypad 3 | 3 |
Keypad 4 | 4 |
Keypad 5 | 5 |
Keypad 6 | 6 |
Keypad 7 | 7 |
Keypad 8 | 8 |
Keypad 9 | 9 |
Keypad * | - |
Keypad # | = |
These mappings can be changed by altering the Arduino sketch file provided (see the Keyboard Keys section).
Atari 2600 Joysticks
The Atari 2600 joysticks are the simplest of the controllers supporter by this adaptor. They have a D-Subminiature Female 9 Position connector. The following table describes the function of each pin:
Pin Number | Description |
1 | Up |
2 | Down |
3 | Left |
4 | Right |
6 | Fire |
8 | Ground |
ColecoVision Controllers
ColecoVision Controller
|
ADAM Controller
|
ColecoVision Super Action Controller
|
The ColecoVision and ADAM joysticks are much more complicated. In addition to the four direction control stick, they have two fire buttons (or 4 in the case of the Super Action Controller) and a 12 button keypad. Like the Atari 2600 joystick, they have a D-Subminiature Female 9 Position connector, but the pin outs have two different modes. When pin 8 is grounded, pins 1-4 are the four directions and pin 6 is the left fire button. When pin 5 is grounded, pins 1-4 are a keypad scan code and pin 6 is the right fire button. The table below lists the pin outs for the ColecoVision and ADAM controllers.
Pin Number | Pin 8 Grounded | Pin 5 Grounded |
1 | Up | Bit 0 |
2 | Down | Bit 2 |
3 | Left | Bit 3 |
4 | Right | Bit 1 |
5 | Keypad Mode | Keypad Mode |
6 | Left Fire | Right Fire |
7 | Spinner | Spinner |
8 | Control Stick Mode | Control Stick Mode |
9 | Spinner | Spinner |
The keypad scan codes are listed below:
Keypad Value
|
Bit 3 / Pin 3
|
Bit 2 / Pin 2
|
Bit 1 / Pin 4
|
Bit 0 / Pin 1
|
Decimal Value
|
Hex Value
|
1
|
0
|
0
|
1
|
0
|
2
|
2
|
2
|
1
|
0
|
0
|
0
|
8
|
8
|
3
|
0
|
0
|
1
|
1
|
3
|
3
|
4
|
1
|
1
|
0
|
1
|
13
|
D
|
5
|
1
|
1
|
0
|
0
|
12
|
C
|
6
|
0
|
0
|
0
|
1
|
1
|
1
|
7
|
1
|
0
|
1
|
0
|
10
|
A
|
8
|
1
|
1
|
1
|
0
|
14
|
E
|
9
|
0
|
1
|
0
|
0
|
4
|
4
|
0
|
0
|
1
|
0
|
1
|
5
|
5
|
*
|
0
|
1
|
1
|
0
|
6
|
6
|
#
|
1
|
0
|
0
|
1
|
9
|
9
|
Purple Fire Button
|
0
|
1
|
1
|
1
|
7
|
7
|
Blue Fire Button
|
1
|
0
|
1
|
1
|
11
|
B
|
The solution I have developed will support Atari 2600 joysticks, ColecoVision and ADAM controllers, and most of the ColecoVision Super Action Controllers. This solution does not currently support the Spinner located below the keypad on the Super Action Controllers.
What You Need
- Arduino Leonardo - http://arduino.cc/en/Main/ArduinoBoardLeonardo
or Arduino Micro - http://arduino.cc/en/Main/ArduinoBoardMicro - D-Subminiature Male 9 Position Connector (for example - http://www.jameco.com/webapp/wcs/stores/servlet/Product_10001_10001_15748_-1 or
http://www.radioshack.com/product/index.jsp?productId=2102497)
Hardware
The pins of the D-Sub Male 9 Position connector should be connected to the pins of the Arduino Leonardo or Arduino Micro as shown in the table below:
Arduino Pin | Male D-Sub Pin | Description |
2 | 6 | Fire Button |
3 | 1 | Up |
4 | 2 | Down |
5 | 3 | Left |
6 | 4 | Right |
7 | 5 | Keypad Mode |
8 | 8 | Joystick Mode |
9 | 7 | Spinner (not used) |
10 | 9 | Spinner (not used) |
Software
The Arduino sketch file listed below should be compiled and uploaded into the Arduino Leonardo or Arduino Micro.
// ColecoVision / ADAM Joystick to PC Keyboard Converter // for the Arduino Leonardo // 2014-08-24 //---------------------------------------------------------------------------------- // Joystick Pins const byte gcFirePin = 2; const byte gcUpPin = 3; const byte gcDownPin = 4; const byte gcLeftPin = 5; const byte gcRightPin = 6; const byte gcModeAPin = 7; const byte gcModeBPin = 8; const byte gcFireMonitorPin = 13; const byte gcBit0Pin = 3; const byte gcBit2Pin = 4; const byte gcBit3Pin = 5; const byte gcBit1Pin = 6; // Keyboard Keys const char gcUpKey = KEY_UP_ARROW; const char gcDownKey = KEY_DOWN_ARROW; const char gcLeftKey = KEY_LEFT_ARROW; const char gcRightKey = KEY_RIGHT_ARROW; const char gcLeftFireKey = KEY_LEFT_ALT; const char gcRightFireKey = KEY_LEFT_CTRL; const char gcPurpleButtonKey = KEY_LEFT_SHIFT; const char gcBlueButtonKey = 'z'; const char gcAsteriskKey = '-'; const char gcPoundKey = '='; // Current Frame Joystick Status byte gLeftFireButton = 0; byte gRightFireButton = 0; byte gUp = 0; byte gDown = 0; byte gLeft = 0; byte gRight = 0; char gNumPadValue = ' '; byte gPurpleButton = 0; byte gBlueButton = 0; byte gBit0 = 0; byte gBit1 = 0; byte gBit2 = 0; byte gBit3 = 0; // Last Frame Joystick Status byte gLastLeftFireButton = 0; byte gLastRightFireButton = 0; byte gLastUp = 0; byte gLastDown = 0; byte gLastLeft = 0; byte gLastRight = 0; char gLastNumPadValue = ' '; byte gLastPurpleButton = 0; byte gLastBlueButton = 0; // Line Read Variables const unsigned int gcThreashold = 4; unsigned int gLeftFireCount = 0; unsigned int gRightFireCount = 0; unsigned int gUpCount = 0; unsigned int gDownCount = 0; unsigned int gLeftCount = 0; unsigned int gRightCount = 0; unsigned int gBit0Count = 0; unsigned int gBit1Count = 0; unsigned int gBit2Count = 0; unsigned int gBit3Count = 0; // Frame Variables const int gcFrameLength = 10; unsigned int gLoopsPerFrame = 0; unsigned long gLastFrameStart = 0; // Shows the status of the joystick. void ShowJoystickStatus() { // Debug Information // Serial.println(gLoopsPerFrame); digitalWrite(gcFireMonitorPin, gLeftFireButton | gRightFireButton); } void SendKeyboardStateToPc() { if ((gLastNumPadValue != ' ') && (gLastNumPadValue != gNumPadValue)) { Keyboard.release(gLastNumPadValue); } if ((gNumPadValue != ' ') && (gLastNumPadValue != gNumPadValue)) { Keyboard.press(gNumPadValue); } SendLineStateToPc(gLastLeftFireButton, gLeftFireButton, gcLeftFireKey); SendLineStateToPc(gLastRightFireButton, gRightFireButton, gcRightFireKey); SendLineStateToPc(gLastUp, gUp, gcUpKey); SendLineStateToPc(gLastDown, gDown, gcDownKey); SendLineStateToPc(gLastLeft, gLeft, gcLeftKey); SendLineStateToPc(gLastRight, gRight, gcRightKey); SendLineStateToPc(gLastPurpleButton, gPurpleButton, gcPurpleButtonKey); SendLineStateToPc(gLastBlueButton, gBlueButton, gcBlueButtonKey); } void SendLineStateToPc(byte lastState, byte currentState, char keyUsedForLine) { if ((lastState == 1) && (currentState == 0)) { Keyboard.release(keyUsedForLine); } if ((lastState == 0) && (currentState == 1)) { Keyboard.press(keyUsedForLine); } } unsigned int CheckJoystickLine(byte pin) { return (digitalRead(pin) == LOW); } void CheckJoystickLines() { const int cLineDelay = 50; // Put Joystick in Direction Mode digitalWrite(gcModeAPin, HIGH); digitalWrite(gcModeBPin, LOW); delayMicroseconds(cLineDelay); gLeftFireCount += CheckJoystickLine(gcFirePin); gUpCount += CheckJoystickLine(gcUpPin); gDownCount += CheckJoystickLine(gcDownPin); gLeftCount += CheckJoystickLine(gcLeftPin); gRightCount += CheckJoystickLine(gcRightPin); // Put Joystick in Keypad Mode digitalWrite(gcModeAPin, LOW); digitalWrite(gcModeBPin, HIGH); delayMicroseconds(cLineDelay); gRightFireCount += CheckJoystickLine(gcFirePin); gBit0Count += CheckJoystickLine(gcBit0Pin); gBit1Count += CheckJoystickLine(gcBit1Pin); gBit2Count += CheckJoystickLine(gcBit2Pin); gBit3Count += CheckJoystickLine(gcBit3Pin); } void ResetFrameVariables() { // Copy Current Frame to Last Frame gLastNumPadValue = gNumPadValue; gLastLeftFireButton = gLeftFireButton; gLastRightFireButton = gRightFireButton; gLastUp = gUp; gLastDown = gDown; gLastLeft = gLeft; gLastRight = gRight; gLastPurpleButton = gPurpleButton; gLastBlueButton = gBlueButton; // Reset Frame Loop Counter gLoopsPerFrame = 0; // Reset Joysick State gLeftFireCount = 0; gLeftFireButton = 0; gRightFireCount = 0; gRightFireButton = 0; gUpCount = 0; gUp = 0; gDownCount = 0; gDown = 0; gLeftCount = 0; gLeft = 0; gRightCount = 0; gRight = 0; gBit0Count = 0; gBit0 = 0; gBit1Count = 0; gBit1 = 0; gBit2Count = 0; gBit2 = 0; gBit3Count = 0; gBit3 = 0; gNumPadValue = ' '; gPurpleButton = 0; gBlueButton = 0; } byte DetermineJoystickLineValue(unsigned int pressCount) { return (pressCount >= gcThreashold); } void DetermineJoystickValues() { const char cKeypadValueLookup[16] = { ' ', '6', '1', '3', '9', '0', gcAsteriskKey, ' ', '2', gcPoundKey, '7', ' ', '5', '4', '8', ' '}; gLeftFireButton = DetermineJoystickLineValue(gLeftFireCount); gRightFireButton = DetermineJoystickLineValue(gRightFireCount); gUp = DetermineJoystickLineValue(gUpCount); gDown = DetermineJoystickLineValue(gDownCount); gLeft = DetermineJoystickLineValue(gLeftCount); gRight = DetermineJoystickLineValue(gRightCount); gBit0 = DetermineJoystickLineValue(gBit0Count); gBit1 = DetermineJoystickLineValue(gBit1Count); gBit2 = DetermineJoystickLineValue(gBit2Count); gBit3 = DetermineJoystickLineValue(gBit3Count); int keypadCode = (gBit3 << 3) + (gBit2 << 2) + (gBit1 << 1) + gBit0; gNumPadValue = cKeypadValueLookup[keypadCode]; // Check for SuperAction Controller Buttons. if (keypadCode == 7) { gPurpleButton = 1; } if (keypadCode == 11) { gBlueButton = 1; } } void setup() { // Setup Serial Monitor // Serial.begin(19200); // Setup Joystick Pins pinMode(gcFirePin, INPUT_PULLUP); pinMode(gcUpPin, INPUT_PULLUP); pinMode(gcDownPin, INPUT_PULLUP); pinMode(gcLeftPin, INPUT_PULLUP); pinMode(gcRightPin, INPUT_PULLUP); pinMode(gcModeAPin, OUTPUT); pinMode(gcModeBPin, OUTPUT); pinMode(gcFireMonitorPin, OUTPUT); } void loop() { unsigned long currentTime; currentTime = millis(); if (currentTime >= (gLastFrameStart + gcFrameLength)) { // Do Joystick Value Commit Logic DetermineJoystickValues(); // Send Values to Monitor ShowJoystickStatus(); // Send Keyboad State to the PC SendKeyboardStateToPc(); // Reset Frame Variables ResetFrameVariables(); // Time to start next frame gLastFrameStart = currentTime; } else { // Check the value of the input lines and make note of them. gLoopsPerFrame++; CheckJoystickLines(); } }
Setup Function
The setup function is used to initialize the Arduino pins. Pins 2-6 are setup as input pins with pull-up resistors. Pins 7 and 8 are setup as output pins to toggle between Keypad Mode and Controller Mode. Pin 13 is setup as an output pin. On most Arduino boards pin 13 is connected to an LED. This LED is used to monitor the left and right fire buttons.
Loop Function
The main loop function is setup to report the value of the various joystick elements as either keydown or keyup events at a rate defined by the gcFrameLength constant. The main loop will read the state of all input pins in both the Keypad and Controller modes as many times as possible within a frame. If the number of times a pin was low exceeds the gcThreashold value, the pin is considered low for that frame. This is how each input is debounced.
Debugging
The Arduino COM port can be used for debugging by uncommenting out the call to Serial.begin in the setup function and adding debug output to the ShowJoystickStatus function.
Possible Future Enhancements
- Add support for the ColecoVision Super Action Controller’s Spinner. This would map to the X-axis of the mouse.
- Add support for ColecoVision Expansion Module #2 (the driving module).
- Add support for the ColecoVision Roller Controller.
- Add support for the Atari 2600 Paddle Controllers.
- Create a version that presents the classic console controller as USB Game Controller instead of a USB Keyboard.
12 comments:
So if I just have my old classic Atari joystick, and my Atari emulator I can have the stick emulate the keyboard, I see.
What connections from the db9 to the arduino can I leave off since I'm just doing the Atari stick? I think you cover more than I need.
I guess I could cut out parts of the script too then? I'm brand new to arduino but can program and am a tech-head! I just got an arduino uno kit.....walked thru a few samples from the book.....thanks for any help. -Bob
zabagar - You asked:
> What connections from the db9 to the arduino can I leave off since I'm just doing the Atari stick?
A: For the Atari 2600 controllers, you only need to connect the following pins: 1 (Up), 2 (Down), 3 (Left), 4 (Right), 6 (Fire), and 8 (Ground).
> I guess I could cut out parts of the script too then?
A: Yes, there is some stuff you can cut out of the sketch file if you are just using Atari 2600 controllers, but it will not hurt to leave the code as it is. Let me know if you would like specifics on what can be taken out.
Thanks so much! I'll post after I set it up! I'll probably have the chance over the weekend to test it!!
-Bob
Oh now I see that the Arduino UNO does not understand the keyboard calls - that's an Arduino Leonardo improvement....oh well...maybe I'll but a Leonardo. I'm new to Arduino so I didn't (and still don't) really know the difference between all of the different models...there are so many variations.
Wow, just trying to find the "right" Arduino Leonardo is difficult.
In your opinion does this look like it's what I should get?
http://www.amazon.com/Arduino-A000057-Leonardo-with-Headers/dp/B008A36R2Y/ref=sr_1_1?s=electronics&ie=UTF8&qid=1441469476&sr=1-1&keywords=arduino+leonardo&pebp=1441469490834&perid=00VDY5C4G6VNANK7RA12
Yes, the link you posted is for an Arduino Leonardo, which is one of the ones that will work for this project. The other one that will work with this project is the Arduino Micro (http://www.amazon.com/Arduino-A000053-Micro/dp/B00AFY2S56).
Cool, thanks. Being able to use keyboard functions will be great for other little projects as well. I will get such a kick out of playing my old atari 800 and atari 2600 games on an emulator, on my vista laptop, with an actual atari joystick. Being the computer geek I still actually have a working atari 800xl and 2600 too!! I'm so glad I found your post. I picked up an arduino knowing I was going to have some fun with it, then it hit me that I should be able to use it the way you've already done. So I found your page & code doing a google search. Glad somebody figured it out already, so I didn't have to fumble through it! Thanks a million - - - Bob Z.
I'm playing River Raid at the moment with my original Atari stick via Leonardo. Thanks again. If Karma exists you have a bunch on its way. Thank you!
That's cool. I am glad to hear it worked for you.
Hi,
I know this thread is a couple of years old but could you please help? I want to use my Arduino Micro to connect Atari 2600 paddles. Did you ever develop the code to work with 2600 paddles?
Thanks in advance,
Alan.
@robofruit - This is possible to do and writing up an example is in my TODO list, but...
If you want to try to do something on your own, you could try using the latest version of the Arduino Joystick Library (see https://github.com/MHeironimus/ArduinoJoystickLibrary).
You can find the pinouts for the Atari 2600 Paddle Controller at http://old.pinouts.ru/Inputs/atari_paddle_pinout.shtml.
Hi me again some years later. I am revisiting old projects as I am in lockdown like so many others. I managed to make an adapter back in 2018 and it worked great with the Atari Joysticks and to an extent with the paddles using the following code which uses your library... https://github.com/nomdeclume/AtariJoysticksPlus
The problem I had was getting the paddles to work without being ultra sensitive. I replaced the original Pots, tried multiple emulators adjusting sensitivity options but nothing helped. I managed to contact the guy who wrote the script and he wasn't sure why they were so sensitive.
Anyway I am now in possession of another board, a Teensy 3.2 which boasts extra bus speeds and I was thinking would this help? It can take the Arduino code so should not be too hard to get going.
Did you ever write any code for the paddles ? If so did you run into similar issues with sensitivity?
Thanks in advance.
Post a Comment