Hardware components | ||||||
| × | 1 | ||||
| × | 5 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 |
Nowadays we're used to the modern, mushroom-shape, thumb-driven joystick and the old school, big stick, chunky '80s joystick seems odd (and today hard to find, sometimes).
Although I still own some old joysticks, they don't work fine like they did. But, since I had few Arduino Nano clones and a couple of joystick modules, I decided to make an Arduino Joystick for my Commodore 64 and VIC-20.
As I suspected, a direct drive by Arduino doesn't work, so I opted for a transistor-driven joystick: five cheap NPN transistors were enough to do the magic. I used BC547 because I have hundreds of them here, but probably it will work fine with 2N3904... or (almost) any other NPN transistor.
I made this (rather bad and ugly) schematics to show how to connect the Arduino (I used an Arduino Nano clone, but any Arduino - Nano, Uno, etc. - should be fine if it works with +5V -don't use an Arduino Due or Arduino Mini/Mini Pro with 3.3v power).
The Joystick module is analog, so it's connected to the analog lines on Arduino. The fire button could be connected to a digital line (and at first I did it) but it suffered of some strange behavior, so I opted to connect it to an analog line.
As you can see, there's also an autofire switch, that is optional: you can simply omit it if you don't like the autofire lazyness.
The wiring is soooo easy that it doesn't deserve a dedicated PCB; a simple prototype board is more than enough. I used an old, broken printer cable that lied in my toolbox as joystick cable.
I then decided to make one of my notorious TinkerCad project and 3D print an ugly but pratical shell. I opted for transparent filament so I can see the Arduino LEDs. I glued everything and voilà!
the worst and ugliest joystick on Earth, but it works like a charm!
Arduino code
Arduino/**********************************************************************************************************
*
* ARDUINO BASED, ATARI 2600 COMPATIBLE JOYSTICK - SUITABLE FOR COMMODORE 64 & VIC-20 (TESTED ON C=64)
*
* Schematics:
*
*-------------------------------------------|
* Joystick port [1] | Arduino Nano/Uno/etc. |
* ----------------- | --------------------- |
* 1 up [NPN] D2 | [NPN] means one NPN transistor like BC547 (see below)
* 2 down [NPN] D4 |
* 3 left [NPN] D6 |
* 4 right [NPN] D8 |
* 5 paddle Y | (Not Connected) |
* 6 fire [NPN] D10 |
* 7 +5v 100mA | +5V |
* 8 GND | GND |
* 9 paddle X | (Not Connected) |
* | |
* Analog Joystick | |
* (movement) | |
* ----------------- | |
* Y axis | A0 |
* X axis | A1 |
* Switch | (Not Connected [2])|
* +5V | +5V |
* GND | GND |
* | |
* Momentary switch | |
* (fire button) | |
* ----------------- | |
* 1 | A2 |
* 2 | GND |
* | |
* switch 2 way [3] | |
* (autofire on/off) | |
* ----------------- | |
* 1 | D12 |
* COM | GND |
* 2 | (Not Connected) |
*-------------------------------------------|
*
* Notes
* [1]: DON'T CONNECT THE JOYSTICK PORT DIRECTLY TO THE ARDUINO PINS (EXCEPT FOR +5V AND GND)!!!!
* Every connection (up, down, left, right, fire) is driven by an NPN transistor (see below)
*
* [2]: at first I connected the analog joystick switch to the other switch, parallel, So there were
* two switches doing the same thing. Ufortunately it didn't work fine for me: it often had
* strange behaviors and returned wrong values to Arduino, so I decided to leave it unconnected.
*
* [3]: if you don't need an autofire switch, simply doesn't connect any switch and the autofire
* will be disable by default
*
* --------------------------------------------------------------------------------------------------
*
* You can't connect the joystick port directly to Arduino, it doesn't work. You need to drive it
* using an NPN transistor (I used five BC547) connecting it as shown here.
* Note: you must to replicate it for every connection between the joystick port and Arduino;
* it means you will need 5 BC547 transistors (2N3904 should be ok too, but I didn't test them)
*
* NPN transistor
*
* Commodore 64 Joystick pin 1 (UP) ---- ----C-. .-E--> -------- GND
* \ /
* --B--
* |
* Arduino pin D2 ------------------------------+
*
*
*********************************************************************************************************/
//defining where any input and output is connected on the Arduino pins
//analog ports for the analog joystick and the fire button
#define JOY_Y A1
#define JOY_X A0
#define JOY_B A3
//autofire switch (optional)
#define JOY_AUTOFIRE D12
//digital output, to the C=64 joystick port (through transistor)
#define JOY_UP D2
#define JOY_DOWN D4
#define JOY_LEFT D6
#define JOY_RIGHT D8
#define JOY_FIRE D10
int joy_x_value = 0;
int joy_y_value = 0;
int joy_b_value = 0;
int internalledstatus = 0; //this is just for the Arduino onboard LED (that will blink at every movement of fire)
//declaring the threshold as int value (instead of a #define constant) so we can add a calibration function later
//these values are the thresholds for the X and Y values and the button. You can change them in case your
//joystick doesn't work in some position or the axis or the button activate too soon.
//in the setup() and loop() function there are some code lines you can activate to read these values
//using the serial monitor. Search for "CALIBRATE" inside this code
int joy_x_l_ths = 600; //low X threshold
int joy_x_h_ths = 4000; //high X threshold
int joy_y_l_ths = 600; //low Y threshold
int joy_y_h_ths = 4000; //high Y threshold
int joy_b_l_ths = 10; //analog button threshold
//autofire variables
float autofiredelay = millis();
bool autofiretoggle = false;
int autofirespeed = 50; //millisec
//===================================================================================================================================================
void setup() {
pinMode(LED_BUILTIN, OUTPUT); //setting the Arduino LED as an output
digitalWrite(LED_BUILTIN, HIGH); //switching the Arduino LED on
delay(4000); //before to do anything, just wait some time for the Commodore 64 / VIC-20 boot sequence (without it, the C=64 doesn't boot if the autofire is on... strange...)
digitalWrite(LED_BUILTIN, LOW); //switching the Arduino LED off so we know when the joystick starts to work
pinMode(JOY_AUTOFIRE, INPUT_PULLUP); //the autofire switch
//declaring the output pins
pinMode(JOY_UP ,OUTPUT);
pinMode(JOY_DOWN ,OUTPUT);
pinMode(JOY_LEFT ,OUTPUT);
pinMode(JOY_RIGHT ,OUTPUT);
pinMode(JOY_FIRE ,OUTPUT);
//------------------------------------------------------------------------------------------------------------------------------------------------------\-
//CALIBRATE - activate the following lines, compile and run the Serial Monitor from inside the Arduino IDE (menu Tools -> Serial Monitor or Ctrl+Shift+M)
//Note: if the Serial Monitor doesn't work (shows garbage, etc.) check the serial speed in the Serial Monitor is set to 115200
/*
Serial.begin(115200);
Serial.println("Start --- ");
*/
//------------------------------------------------------------------------------------------------------------------------------------------------------/-
}
//===================================================================================================================================================
void loop() {
//reading the analog values from the joystick axis and the button
joy_x_value = analogRead(JOY_X);
joy_y_value = analogRead(JOY_Y);
joy_b_value = analogRead(JOY_B);
//------------------------------------------------------------------------------------------------------------------------------------------------------\-
//CALIBRATE - activate the following lines, compile and run the Serial Monitor from inside the Arduino IDE (menu Tools -> Serial Monitor or Ctrl+Shift+M)
//Note: if the Serial Monitor doesn't work (shows garbage, etc.) check the serial speed in the Serial Monitor is set to 115200
/*
Serial.print("-- joy_x_value = "); Serial.print(joy_x_value);
Serial.print("-- joy_y_value = "); Serial.print(joy_y_value);
Serial.print("-- joy_b_value = "); Serial.print(joy_b_value);
Serial.println("");
delay(100);
*/
//------------------------------------------------------------------------------------------------------------------------------------------------------/-
//reset the internal led status. It starts with a zero value that will be incremented at any joystick movement or fire.
//at the end of the loop function, if the value is >0 we will switch the internal led on.
//NOTE: the internal led is only used as feedback to know the joystick is working.
internalledstatus = 0;
//-----------------------------------------------------------------------------------------------------------------------------------\-
//fire button.
//-autofire OFF - begin --------------------------------------------------------------------------\-
if (digitalRead(JOY_AUTOFIRE) == HIGH) {
//if the autofire is off, we just fire if the button is pressed.
if (joy_b_value < joy_b_l_ths) { digitalWrite(JOY_FIRE ,HIGH) ; internalledstatus++; }
else { digitalWrite(JOY_FIRE ,LOW); }
}
//-autofire OFF - end ----------------------------------------------------------------------------/-
//-autofire ON - begin --------------------------------------------------------------------------\-
else {
if (joy_b_value >=joy_b_l_ths) { digitalWrite(JOY_FIRE ,LOW); autofiretoggle = false; } //the button is not pressed.
else { //fire button pressed -- //sequence: ON - 50 millisec - OFF - 50 millisec - ON
if (!autofiretoggle) {
if (autofiredelay + autofirespeed < millis()) { //at least <autofirespeed> milliseconds passed from the last change of status (ON to OFF). Time to switch ON
autofiretoggle = true; //at least <autofirespeed> milliseconds passed from the last change of status (was ON to OFF). Time to toggle the switch ON
autofiredelay = millis(); //resetting the milliseconds counter
{ digitalWrite(JOY_FIRE ,HIGH); internalledstatus++; } //fire ON!
}
}
else {
if (autofiredelay + autofirespeed < millis()) { //at least <autofirespeed> milliseconds passed from the last change of status (OFF to ON). Time to switch OFF
autofiretoggle = false; //at least <autofirespeed> milliseconds passed from the last change of status (was OFF to ON). Time to toggle the switch OFF
autofiredelay = millis(); //resetting the milliseconds counter
{ digitalWrite(JOY_FIRE ,LOW); } //fire OFF!
}
}
}
}
//-autofire ON - end ----------------------------------------------------------------------------/-
if (joy_y_value < joy_y_l_ths) { digitalWrite(JOY_DOWN ,HIGH); internalledstatus++; } else { digitalWrite(JOY_DOWN ,LOW); } //the Y value is below the lower Y threshold, activating the DOWN movement
if (joy_y_value > joy_y_h_ths) { digitalWrite(JOY_UP ,HIGH); internalledstatus++; } else { digitalWrite(JOY_UP ,LOW); } //the Y value is above the higher Y threshold, activating the UP movement
if (joy_x_value > joy_x_h_ths) { digitalWrite(JOY_RIGHT,HIGH); internalledstatus++; } else { digitalWrite(JOY_RIGHT,LOW); } //the X value is below the lower X threshold, activating the RIGHT movement
if (joy_x_value < joy_x_l_ths) { digitalWrite(JOY_LEFT ,HIGH); internalledstatus++; } else { digitalWrite(JOY_LEFT ,LOW); } //the X value is above the higher X threshold, activating the LEFT movement
//if you don't want the internal led blinks, just comment the following line.
if (internalledstatus > 0) digitalWrite(LED_BUILTIN, HIGH); else digitalWrite(LED_BUILTIN, LOW); //activating the Arduino internal LED if there was some movement / fire
delay(50); //some little delay. You can try to remove it and test in your own environment; but in my case the joystick seems to work better (the C=64 is quite slow compared with an Arduino...)
}
Comments