Thursday, December 25, 2014

Arduino Retro Computer: Joystick Interface

The joystick itself contains no logic - it's just push buttons and potentiometers. The brains of the input reside in the Arduino.

Some very good references on how to read analog and digital input on the Arduino:

http://arduino.cc/en/Tutorial/ReadAnalogVoltage
http://arduino.cc/en/tutorial/button
http://arduino.cc/en/Reference/Constants

Basically, we can't just wire from 5V through a push button then to the data input pin; we must use a pull-up (or pull-down) resistor. The critical piece for the push buttons from those references is as follows: "If you have your pin configured as an INPUT, and are reading a switch, when the switch is in the open state the input pin will be "floating", resulting in unpredictable results. In order to assure a proper reading when the switch is open, a pull-up or pull-down resistor must be used. The purpose of this resistor is to pull the pin to a known state when the switch is open. A 10 K ohm resistor is usually chosen, as it is a low enough value to reliably prevent a floating input, and at the same time a high enough value to not not draw too much current when the switch is closed."

Hardware

9-Position Female D-Sub Connector (Radio Shack #276-1538)
5x 10k Ohm Resistor
Breadboard

Wiring

The wiring for the select button is different than the 4 push buttons. The Joystick Breakout Board shorts Select (S1) to GND when the stick is pressed in. To read this, Select is wired with a pull-up resistor while the others are with pull-down.

Wiring Schematic:

Created with http://www.digikey.com/schemeit

The pin layout for the serial cable is as follows:

#1 S1 (Stick Select Button)
#2 S2 (Right Button)
#3 S3 (Top Button)
#4 S4 (Left Button)
#5 S5 (Bottom Button)
#6 Vcc
#7 Analog Xout
#8 Analog Yout
#9 GND


Code

I created a Joystick Class in my operating system code to ease in interfacing:
class JoystickModel
{
  private:
  int buttonPinSelect;
  int buttonPinRight;
  int buttonPinTop;
  int buttonPinLeft;
  int buttonPinBottom;
  int analogPinX;
  int analogPinY;

  public:
  bool init(int newButtonPinSelect, int newButtonPinRight, int newButtonPinTop, int newButtonPinLeft, int newButtonPinBottom,
            int newAnalogPinX, int newAnalogPinY)
  {
    buttonPinSelect = newButtonPinSelect;
    buttonPinRight = newButtonPinRight;
    buttonPinTop = newButtonPinTop;
    buttonPinLeft = newButtonPinLeft;
    buttonPinBottom = newButtonPinBottom;
    
    pinMode(buttonPinSelect, INPUT);
    pinMode(buttonPinRight, INPUT);
    pinMode(buttonPinTop, INPUT);
    pinMode(buttonPinLeft, INPUT);
    pinMode(buttonPinBottom, INPUT);

    analogPinX = newAnalogPinX;
    analogPinY = newAnalogPinY;
       
    return true;
  }
  
  bool selectPressed()
  {
    // Returns true if button is pressed.
    // Select button uses LOW because the joystick breakout shorts to ground with select.
    return (digitalRead(buttonPinSelect) == LOW);
  }
  
  bool leftPressed()
  {
    // Returns true if button is pressed.
    return (digitalRead(buttonPinLeft) == HIGH);
  }
  
  bool topPressed()
  {
    // Returns true if button is pressed.
    return (digitalRead(buttonPinTop) == HIGH);
  }
  
  bool rightPressed()
  {
    // Returns true if button is pressed.
    return (digitalRead(buttonPinRight) == HIGH);
  }
  
  bool bottomPressed()
  {
    // Returns true if button is pressed.
    return (digitalRead(buttonPinBottom) == HIGH);
  }
  
  float analogX()
  {
    // Returns position of stick between -1 and 1.
    // Take - as X potentiometer is mounted reversed on controller.
    return -(((float)analogRead(analogPinX) / 512.0) - 1.0);
  }
  
  float analogY()
  {
    // Returns position of stick between -1 and 1.
    // Take - as Y potentiometer is mounted reversed on controller.
    return -(((float)analogRead(analogPinY) / 512.0) - 1.0);
  }
};

Global:
const int Joystick1SelectPin = 3;
const int Joystick1RightPin = 4;
const int Joystick1TopPin = 5;
const int Joystick1LeftPin = 6;
const int Joystick1BottomPin = 7;
const int Joystick1AnalogX = 0;
const int Joystick1AnalogY = 1;

const byte numberOfJoysticks = 2;
JoystickModel joysticks[numberOfJoysticks];

Inside setup():
  joysticks[0].init(Joystick1SelectPin, Joystick1RightPin, Joystick1TopPin, 
                    Joystick1LeftPin, Joystick1BottomPin, Joystick1AnalogX, Joystick1AnalogY);

Inside loop():
  float analogX = joysticks[0].analogX();
  float analogY = joysticks[0].analogY();
  char analogXString[10];
  char analogYString[10];
  dtostrf(analogX, 5, 3, analogXString);
  dtostrf(analogY, 5, 3, analogYString);
  Serial.print("Analog X: ");
  Serial.print(analogXString);
  Serial.print(" Analog Y: ");
  Serial.print(analogYString);

  if (joysticks[0].selectPressed())
  {
    Serial.print(" Select Pressed");
  }
  if (joysticks[0].rightPressed())
  {
    Serial.print(" Right Pressed");
  }
  if (joysticks[0].topPressed())
  {
    Serial.print(" Top Pressed");
  }
  if (joysticks[0].leftPressed())
  {
    Serial.print(" Left Pressed");
  }
  if (joysticks[0].bottomPressed())
  {
    Serial.print(" Bottom Pressed");
  }
  Serial.println("");


Output from the console:


Asteroids + Controller


I wanted to play the Asteroids game with my new controller, so I made the following changes to the game code to get it to work:

Replaced controller_init() with:
static void controller_init()
{
  // Configure input pins with internal pullups
  pinMode(6, INPUT);
  pinMode(7, INPUT);
}

Replaced controller_sense() with:
static byte controller_sense(uint16_t clock)
{
  byte r = 0;
  if (digitalRead(6))
  {
    r |= CONTROL_UP;
  }
  if (digitalRead(7))
  {
    r |= CONTROL_DOWN;
  }
  return r;
}

In static void handle_player(byte i, byte state, uint16_t clock), there is a line:
    char rotate = (512 - analogRead(0)) / 400;
Since my stick is reversed, I just had to make that negative:
    char rotate = -(512 - analogRead(0)) / 400;


Copyright (c) 2015 Clinton Kam
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

No comments:

Post a Comment