How 2-Axis Joystick Works & Interface with Arduino + Processing

When you hear the word thumb joystick, the first thing that comes to mind is a game controller. Although they are most commonly used for gaming, you can do a lot of cool things with them if you’re into DIY electronics, such as controlling a robot or rover or, controlling camera movement.

Let’s take a deeper dive into the joystick module and see how it works.

Hardware Overview

Those who are familiar with the PS2 (PlayStation 2) controller will notice that this joystick is strikingly similar to the one used in that device. It is a self-centering spring-loaded joystick, which means that when you release it, it will center itself. It also has a comfortable cup-type knob/cap that feels like a thumb-stick.

Potentiometers

The basic idea behind a joystick is to convert the stick’s position on two axes — the X-axis (left to right) and the Y-axis (up and down) — into an electrical signal that a microcontroller can process. This is accomplished by incorporating two 5K potentiometers (one for each axis) connected with a gymbal mechanism that separates “horizontal” and “vertical” movements.

PS2 Joystick Module 2 Potentiometers Internal Structure

The two gray boxes on either side of the joystick are the potentiometers. If you move the joystick while keeping an eye on the potentiometer, you’ll notice that each potentiometer only detects movement in one direction. We’ll go over how they work in more detail later.

Momentary Pushbutton switch

PS2 Joystick Module Push Button Switch Internal Structure

This joystick also has a momentary pushbutton switch (a small black box on one side of the joystick) that is actuated when you press the joystick knob. When the knob is pressed down, you’ll notice a lever pressing down on the switch’s head. It is designed in such a way that the lever operates regardless of the position of the joystick.

How does the Thumb Joystick Module Work?

It is truly remarkable how a joystick can translate every tiny motion of your fingertips. This is made possible by the joystick’s design, which consists of two potentiometers and a gimbal mechanism.

Gimbal Mechanism

When you move the joystick, a thin rod that sits between two rotatable-slotted-shafts (Gimbal) moves. One of the shafts allows movement along the X-axis (left and right), while the other allows movement along the Y-axis (up and down).

analog joystick internal gimbal structure

When you move the joystick back and forth, the Y-axis shaft pivots. When you move it left to right, the X-axis shaft pivots. And when you move it diagonally, both shafts pivot.

2-Axis Joystick Working Gimbal Mechanism

Each shaft is connected to a potentiometer so that moving the shaft rotates the corresponding potentiometer’s wiper. In other words, pushing the knob all the way forward will cause the potentiometer wiper to move to one end of the resistive track, and pulling it back will cause it to move to the opposite end.

Potentiometer Working In Joystick Module

By reading these potentiometers, the knob position can be determined.

Reading analog values from Joystick

The joystick outputs an analog signal whose voltage varies between 0 and 5V. When you move the joystick along the X axis from one extreme to the other, the X output changes from 0 to 5V, and the same thing happens when you move it along the Y axis. And, when the joystick is centered (rest position), the output voltage is approximately half of VCC, or 2.5V.

This output voltage can be fed to an ADC on a microcontroller to determine the physical position of the joystick.

Because the Arduino board has a 10-bit ADC resolution, the values on each analog channel (axis) can range from 0 to 1023. Therefore, when the joystick is moved from one extreme to the other, it will read a value between 0 and 1023 for the corresponding channel. When the joystick is centered, the vertical and horizontal channels will both read 512.

The figure below depicts the X and Y axes as well as how the outputs will respond when the joystick is moved in different directions.

PS2 Joystick Module Movement Analog Values on Arduino

Thumb Joystick Module Pinout

Let’s take a look at the pinout of the Thumb Joystick module.

Pinout PS2 Joystick Module

GND is the ground pin.

VCC provides power to the module. Connect this to your positive supply (usually 5V or 3.3V depending on your logic levels).

VRx is the horizontal output voltage. Moving the joystick from left to right causes the output voltage to change from 0 to VCC. The joystick will read approximately half of VCC when it is centered (rest position).

VRy is the vertical output voltage. Moving the joystick up and down causes the output voltage to change from 0 to VCC. The joystick will read approximately half of VCC when it is centered (rest position).

SW is the output from the pushbutton switch. By default, the switch output is floating. To read the switch, a pull-up resistor is required so that when the joystick knob is pressed, the switch output becomes LOW, otherwise it remains HIGH. Keep in mind that the input pin to which the switch is connected must have the internal pull-up enabled, or an external pull-up resistor must be connected.

Wiring a Thumb Joystick Module to an Arduino

Now that we know everything about the joystick module, let’s hook it up to the Arduino.

To begin, connect VRx to Arduino’s analog pin A0 and VRy to analog pin A1. To detect whether the joystick is pressed, we connect the joystick’s SW pin to Arduino digital pin D8.

Finally, connect the VCC pin to the 5V terminal and the GND pin to the Arduino’s ground terminal.

Arduino Wiring Fritzing Connections with PS2 2-axis Joystick Module

That’s it. It’s time to put your joystick skills to the test.

Arduino Example 1 – Reading the Joystick

Here’s a simple Arduino sketch that configures the microcontroller to read the inputs and then continuously print the values to the serial monitor.

// Arduino pin numbers
const int SW_pin = 8; // digital pin connected to switch output
const int X_pin = 0; // analog pin connected to X output
const int Y_pin = 1; // analog pin connected to Y output

void setup() {
  pinMode(SW_pin, INPUT);
  digitalWrite(SW_pin, HIGH);
  Serial.begin(9600);
}

void loop() {
  Serial.print("Switch:  ");
  Serial.print(digitalRead(SW_pin));
  Serial.print(" | ");
  Serial.print("X-axis: ");
  Serial.print(analogRead(X_pin));
  Serial.print(" | ");
  Serial.print("Y-axis: ");
  Serial.print(analogRead(Y_pin));
  Serial.println(" | ");
  delay(200);
}

After uploading the sketch, you should see the following output on the serial monitor.

PS2 Joystick Module Arduino Sketch Output on serial window

Code Explanation:

As the first step of the sketch, the joystick module’s connections are declared.

// Arduino pin numbers
const int SW_pin = 8; // digital pin connected to switch output
const int X_pin = 0; // analog pin connected to X output
const int Y_pin = 1; // analog pin connected to Y output

In the setup section, we configure the SW pin to behave as an INPUT and pull it HIGH. We also begin serial communication with the PC.

  pinMode(SW_pin, INPUT);
  digitalWrite(SW_pin, HIGH);
  Serial.begin(9600);

In the loop section, we simply use the digitalRead() function to read the button’s state and the analogRead() function to read the VRx and VRy pins. The values are then printed to the serial monitor.

  Serial.print("Switch:  ");
  Serial.print(digitalRead(SW_pin));
  Serial.print(" | ");
  Serial.print("X-axis: ");
  Serial.print(analogRead(X_pin));
  Serial.print(" | ");
  Serial.print("Y-axis: ");
  Serial.print(analogRead(Y_pin));
  Serial.println(" | ");
  delay(200);

Arduino Example 2 – Animating Joystick Movements In Processing IDE

Let’s create an Arduino project to demonstrate how a joystick module can be used to control animations in the Processing IDE.

This is what the output looks like.

Arduino Project - Animating Joystick in Precessing IDE
Joystick movement animation in Precessing IDE

Of course, this project can be expanded to animate characters, conduct surveillance, or pilot unmanned aircraft.

Arduino Code

This example sends the state of the pushbutton and two analog outputs serially to the computer. The associated Processing sketch reads the serial data to animate the joystick position.

The sketch is identical to the one above, except that the values printed on the serial monitor are separated by commas. The reason for separating values with commas is to make data transfer easier. The concept here is to send the values as a comma-separated string that we can parse in the Processing IDE to retrieve the values.

Upload the sketch below to your Arduino.

int xValue = 0 ; // read value of the X axis	
int yValue = 0 ; // read value of the Y axis	
int bValue = 0 ; // value of the button reading	

void setup()	
{	
	Serial.begin(9600) ; // Open the serial port
	pinMode(8,INPUT) ; // Configure Pin 2 as input
	digitalWrite(8,HIGH);	
}	

void loop()	
{	
	// Read analog port values A0 and A1	
	xValue = analogRead(A0);	
	yValue = analogRead(A1);	

	// Read the logic value on pin 2	
	bValue = digitalRead(8);	

	// We display our data separated by a comma	
	Serial.print(xValue,DEC);
	Serial.print(",");
	Serial.print(yValue,DEC);
	Serial.print(",");
	Serial.print(!bValue);

	// We end with a newline character to facilitate subsequent analysis	
	Serial.print("\n");

	// Small delay before the next measurement	
	delay(10);	
}

Processing Code

After uploading the program to Arduino, we can begin animating the joystick position in the Processing IDE. Keep your Arduino plugged in and run the Processing code below.

import processing.serial.*; //import the Serial library
Serial myPort;

int x; // variable holding the value from A0
int y; // variable holding the value from A1
int b; // variable holding the value from digital pin 2
PFont f; // define the font variable
String portName;
String val;

void setup()
{
  size ( 512 , 512 ) ; // window size
  
  // we are opening the port
   myPort = new Serial(this, Serial.list()[0], 9600);
  myPort.bufferUntil('\n'); 
  
  // choose the font and size
  f = createFont("Arial", 16, true); // Arial, 16px, anti-aliasing
  textFont ( f, 16 ) ; // size 16px
}

// drawing loop
void draw()
{
  fill(0) ; // set the fill color to black
  clear() ; // clean the screen
  
  fill(255) ; // set the fill color to white
  
  if (b == 1) // check if the button is pressed
  {
    // draw a larger circle with specified coordinates
    ellipse(x/2,y/2, 50, 50);
  } 
  else
  {
    // we draw a circle with a certain coordinates
    ellipse(x/2,y/2, 25, 25);
  }
  
  // we display data
  text("AnalogX="+(1023-x)+" AnalogY="+(1023-y),10,20);
}


// data support from the serial port
void serialEvent( Serial myPort) 
{
  // read the data until the newline n appears
  val = myPort.readStringUntil('\n');
  
  if (val != null)
  {
        val = trim(val);
        
    // break up the decimal and new line reading
    int[] vals = int(splitTokens(val, ","));
    
    // we assign to variables
    x = vals[0];
    y = vals[1] ;
    b = vals[2];

  }
}

Code Explanation:

Let’s quickly analyze the code. First, we import the serial library in order to read values from the serial port.

import processing.serial.*; //import the Serial library
Serial myPort;

The variables that will hold the x-axis, y-axis, and button state values are then declared.

int x; // variable holding the value from A0
int y; // variable holding the value from A1
int b; // variable holding the value from digital pin 2
PFont f; // define the font variable
String portName;
String val;

In the setup function, we first set the window size to 512×512. Next, we use Serial.list()[0] to open the first available serial port. If this does not work, select the port to which Arduino is connected. We also select a font for displaying our values on the window.

  size ( 512 , 512 ) ; // window size
  
  // we are opening the port
   myPort = new Serial(this, Serial.list()[0], 9600);
  myPort.bufferUntil('\n'); 
  
  // choose the font and size
  f = createFont("Arial", 16, true); // Arial, 16px, anti-aliasing
  textFont ( f, 16 ) ; // size 16px

At the start of the draw function, the window’s background color is set to black. Then we choose a white color to draw a small circle to represent the position of the joystick. To show that the pushbutton has been pressed, we make the circle bigger.

fill(0) ; // set the fill color to black
  clear() ; // clean the screen
  
  fill(255) ; // set the fill color to white
  
  if (b == 1) // check if the button is pressed
  {
    // draw a larger circle with specified coordinates
    ellipse(x/2,y/2, 50, 50);
  } 
  else
  {
    // we draw a circle with a certain coordinates
    ellipse(x/2,y/2, 25, 25);
  }

The x- and y-axis values are then printed in the upper left corner of the window.

  // we display data
  text("AnalogX="+(1023-x)+" AnalogY="+(1023-y),10,20);

The custom function serialEvent(Serial myPort) helps us parse a comma-separated list of values.

void serialEvent( Serial myPort) 
{
  // read the data until the newline n appears
  val = myPort.readStringUntil('\n');
  
  if (val != null)
  {
        val = trim(val);
        
    // break up the decimal and new line reading
    int[] vals = int(splitTokens(val, ","));
    
    // we assign to variables
    x = vals[0];
    y = vals[1] ;
    b = vals[2];

  }
}