Hacking STEMJen Fox
Published © MIT

HackingSTEM Joystick

Build a simple 3-axis joystick!

BeginnerProtip1.5 hours3,407
HackingSTEM Joystick

Things used in this project

Hardware components

Arduino UNO
Arduino UNO
×1
USB-A to B Cable
USB-A to B Cable
×1
copper tape
×1
stranded wire, 11cm
×2
Jumper wires (generic)
Jumper wires (generic)
×7
Male/Female Jumper Wires
Male/Female Jumper Wires
×7
paper plate
×1
wooden dowel, 6mm diameter, 12mm long
×1
cardboard!
×1
paper plate
×1
foam tape square
×1

Software apps and online services

Arduino IDE
Arduino IDE
microsoft excel
Microsoft Data Streamer

Hand tools and fabrication machines

scissors
ruler
Hot glue gun (generic)
Hot glue gun (generic)
Wire Stripper & Cutter, 30-20 AWG / 0.25-0.81mm Capacity Wires
Wire Stripper & Cutter, 30-20 AWG / 0.25-0.81mm Capacity Wires
Plier, Long Nose
Plier, Long Nose
Tape, Clear
Tape, Clear

Story

Read more

Schematics

Arduino Joystick Schematic

Complete Arduino Schematic for the HackingSTEM joystick.

Code

BBC Joystick Arduino Code

Arduino
This project relies upon the construction of a simple joystick controller illustrating pitch, roll, and yaw. For each axis there are 2 switches. All switches are set HIGH and there is a common GND. When a switch is activated the switch goes LOW. For each pair of switches the code emits 1, 0, or -1 indicating direction.
/*
 * 

  This code works with the How Do Sharks Swim workbook and lesson plan
  available from the Microsoft Education Workshop at http://aka.ms/hackingSTEM   
   
  This projects uses an Arduino UNO microcontroller board. More information can
  be found by visiting the Arduino website: https://www.arduino.cc/en/main/arduinoBoardUno 
  See https://www.arduino.cc/en/Guide/HomePage for board specific details and tutorials.

  This project relies upon the construction of a simple joystick controller illuststrating pitch, roll, and yaw. 
  For each axis there are 2 switches. All switches are set HIGH and there is a common GND. When a switch is
  activated the switch goes LOW. For each pair of switches the code emits 1, 0, or -1 indicating direction. 

  David Myka, 2018 Microsoft Education Workshop
  For issues with this code visit: https:aka.ms/hackingstemsupport
  
 *
 */

#include <Servo.h>

Servo pitch_servo;  // create servo object for pitch
Servo yaw_servo;  // create servo object yaw
Servo roll_servo;  // create servo object roll

//servo positions 0 to 180 (90 in the middle)

float pitch_pos = 90;
float yaw_pos = 90;
float roll_pos = 90;
const float POSITION_STEP = .015; //smaller steps means slower movement
const float PITCH_MAX = 140; //limits pitch, value 0 to 180 
const float PITCH_MIN = 25; //limits pitch, value 0 to 180

// Variables to track the 3 axis
int pitch; 
int yaw; 
int roll;

// SWITCH STATES
// x-axis
int roll_ccw; 
int roll_cw;

// y-axis
int pitch_ccw; 
int pitch_cw;

// z-axis
int yaw_ccw;
int yaw_cw; 
//-------------

// Game variables from Excel
String X;
String Y;
String Z;
String hitTime;
int reset = 0;
//------------------

// This should be 6 for a 3 axis joystick
const int numberOfInputPins = 6;




/*
 * -------------------------------------------------------------------------------------------------------
 * SERIAL DATA VARIABLES----------------------------------------------------------------------------------
 * -------------------------------------------------------------------------------------------------------
 */

 //Incoming Serial Data Array
const byte numberOfChannelsFromExcel = 6;  // ******>>>This must be equal to or greater then the number of channels set in Data Streamer Settings worksheet
String incomingSerialData[numberOfChannelsFromExcel];

const String DELIMITER = ",";           // Cordoba expects a comma delimeted string of data
char DOUBLEQUOTES =   char(34);         // Any fields containing commas are escaped with double quotes
String inputString = "";                // String variable to hold incoming data
boolean stringComplete = false;         // Variable to indicate the string is complete (newline found)
const int serialInterval = 60;          // Interval between serial writes
unsigned long serialPreviousTime;       // Timestamp to track interval



/*
 * ARDUINO INITIALIZATION CODE-----------------------------------------------------
 */

void setup() {
  Serial.begin(9600);  
  

  // Set all pins up to numberOfPins HIGH starting with startPin
  int startPin = 2;
  for(int i=0; i < numberOfInputPins; i++)
  {
    pinMode((i + startPin), INPUT);
    digitalWrite((i + startPin), HIGH);
  }

  yaw_servo.attach(9);   // attaches the yaw servo on pin 9 to the servo object
  pitch_servo.attach(10);  // attaches the pitch servo on pin 10 to the servo object
  roll_servo.attach(11);  // attaches the roll servo on pin 11 to the servo object

  pitch_servo.write(pitch_pos); //initialize servo position 
  yaw_servo.write(yaw_pos);     //initialize servo position 
  roll_servo.write(roll_pos);   //initialize servo position 

}

/*
 * START OF MAIN LOOP -------------------------------------------------------------
 */ 
void loop()
{
  // Process sensors
  processSensors();
 
  // Read Excel commands from serial port
  processIncomingSerial();

  // Test for reset trigger 
  if (reset==1)
  {
    resetGame();    // if we have a trigger then reset the game variables
  }

  // Process and send data to Excel via serial port
  processOutgoingSerial();  
}

/*
 * RESET GAME CODE-----------------------------------------------------------------------------------
 */

void resetGame()
{
  // Reset state variables for shark game in Excel
  X = "";
  Y = "0";
  Z = "-10.5";
  hitTime = "0";
}

/*
 * SENSOR INOUT CODE-----------------------------------------------------------------------------------
 */
void processSensors()
{
  // Read joystick poistions from digital pins
  // x-axis
  roll_ccw   = digitalRead(2);
  roll_cw    = digitalRead(3);

  // y-axis
  pitch_ccw  = digitalRead(4);
  pitch_cw   = digitalRead(5);  

  //z-axis
  yaw_ccw    = digitalRead(6); 
  yaw_cw     = digitalRead(7);  

  //PROCESS SWITCHES
  //roll----------------------------------
  if(roll_ccw==LOW)
  {
    DecrementRollPosition();
    roll = 1;
  } 
  else if(roll_cw==LOW)
  {
    IncrementRollPosition();
    roll = -1;
  }
  else
  {
    CenterRollPosition();
    roll = 0;
  }
  
  //pitch----------------------------------
  if(pitch_ccw==LOW)
  {
    IncrementPitchPosition();
    pitch = 1;
  } 
  else if(pitch_cw==LOW)
  {
    DecrementPitchPosition();
   
    pitch = -1;
  }
  else
  {
    CenterPitchPosition();
    pitch = 0;
  }

  //yaw----------------------------------
  if(yaw_ccw==LOW)
  {
    IncrementYawPosition();
    yaw = 1;
  } 
  else if(yaw_cw==LOW)
  {
    DecrementYawPosition();
    yaw = -1;
  }
  else
  {
    CenterYawPosition();
    yaw = 0;
  }
}


/*
 * INCOMING SERIAL DATA PROCESSING CODE-------------------------------------------------------------------
 */

void processIncomingSerial()
{
  getSerialData();
  parseSerialData();
}

/*
 * getSerialData()
 * 
 * Gathers bits from serial port to build inputString
 */
void getSerialData()
{
  while (Serial.available()) {
    char inChar = (char)Serial.read();    // get new char
    inputString += inChar;                // add it to input string
    if (inChar == '\n') {                 // if we get a newline... 
      stringComplete = true;              // we have a complete string of data to process
    }
  }
}

/*
 * parseSerialData() 
 * 
 * Parse all program control variables and data from Excel  
 */
void parseSerialData()
{
  if (stringComplete) { // process data from inputString to set program variables. 
    
    //Build an array of values from comma delimited serial string from Data Streamer
    BuildDataArray(inputString);

    //Set values based on array index - Data Out column A5 = 0, B5 = 1, C5 = 2, etc.
    X   = incomingSerialData[0];    // Data Out column A5
    Y   = incomingSerialData[1];    // Data Out column B5
    Z   = incomingSerialData[2];    // Data Out column C5
    hitTime = incomingSerialData[3];    // Data Out column D5
    reset   = incomingSerialData[4].toInt();    // Data Out column E5

    inputString = "";                         // reset inputString
    stringComplete = false;                   // reset stringComplete flag
  }
}


// Increases pitch position variable by one and send new position to servo
void IncrementPitchPosition() {
  if (pitch_pos < PITCH_MAX) {
    pitch_pos = pitch_pos + POSITION_STEP;
  }
  pitch_servo.write(pitch_pos);
}

void DecrementPitchPosition() {
  if (pitch_pos > PITCH_MIN) {
    pitch_pos = pitch_pos - POSITION_STEP;
  }
  pitch_servo.write(pitch_pos);
}

void CenterPitchPosition() {
  if (pitch_pos > 90) {
       pitch_pos = pitch_pos - POSITION_STEP;
  } else if (pitch_pos < 90) {
      pitch_pos = pitch_pos + POSITION_STEP;
  }  
  pitch_servo.write(pitch_pos);
}


void IncrementRollPosition() {
  if (roll_pos < 180) {
    roll_pos = roll_pos + POSITION_STEP;
  }
  roll_servo.write(roll_pos);
}

void DecrementRollPosition() {
  if (roll_pos > 0) {
    roll_pos = roll_pos - POSITION_STEP;
  }
  roll_servo.write(roll_pos);
}

void CenterRollPosition() {
    if (roll_pos > 90) {
       roll_pos = roll_pos - POSITION_STEP;
  } else if (roll_pos < 90) {
      roll_pos = roll_pos + POSITION_STEP;
  }  
  roll_servo.write(roll_pos);
}


void IncrementYawPosition() {
  if (yaw_pos < 180) {
    yaw_pos = yaw_pos + POSITION_STEP;
  }
  yaw_servo.write(yaw_pos);
}

void DecrementYawPosition() {
  if (yaw_pos > 0) {
    yaw_pos = yaw_pos - POSITION_STEP;
  }
  yaw_servo.write(yaw_pos);
}

void CenterYawPosition() {
    if (yaw_pos > 90) {
       yaw_pos = yaw_pos - POSITION_STEP;
  } else if (yaw_pos < 90) {
      yaw_pos = yaw_pos + POSITION_STEP;
  }  
  yaw_servo.write(yaw_pos);
}





/*
 * BuildDataArray()
 * 
 * Takes the comma delimited string from Data Streamer and splits the fields into an array
 * Follows CSV spec and encodes fields as follows: 
 *    Fields containing commas are wrapped in doublequotes. This allows for comma decimal separators in field values.
 *    Doublequotes in fields are escaped with a second doublequote.
 */
 
void BuildDataArray(String data)
{
  return ParseLine(data);
}

/*
 * ParseLine - parses a single string of comma delimited values terminated by a line ending character
 */
void ParseLine(String data) 
{
    int i = 0;    // tracks the character we are looking at in the data from Excel (data)
    int arrayIndex = 0;

    // For each index array that we are expecting from Excel
    while(arrayIndex < numberOfChannelsFromExcel)
    {
        String field = ParseNextField(data, i);   // parse the next field of data
        incomingSerialData[arrayIndex] = field;   // add field to incomingSerialDataArray
        arrayIndex++;   // increment index
    }
}

/*
 * ParseNextField - parses the next value field in between the comma delimiters
 */
String ParseNextField(String data, int &i)
{
    if (i >= data.length() )    // if character length is greater than data string length
    {
      return ""; //end of data
    }
    
    String field = "";
    bool hitDelimiter = false;    // flag for delimiter detection 
    while (hitDelimiter == false)   // loop through characters until we hit a delimiter
    {
        if (i >= data.length() )    // if character length is greater than data string length
        {
          break; //end of data
        }

        if (String(data[i]) == "\n")    // if character is a line break
        {
          break; // end of data
        }
        
       if(String(data[i]) == DELIMITER)   // if we hit a delimiter
        {
          hitDelimiter = true;  // flag the delimiter hit
          i++; // set iterator after delimiter so we skip the comma
          break;
        }
        else
        {        
          field += data[i];   // add character to field string
          i++;    // increment to next character in data
        }
    }
    return field;
}

/*
 * OUTGOING SERIAL DATA PROCESSING CODE-------------------------------------------------------------------
 */
void processOutgoingSerial()
{
  if((millis() - serialPreviousTime) > serialInterval)  // Enter into this only when interval has elapsed
  {
    serialPreviousTime = millis(); // Reset interval timestamp
    sendDataToSerial(); 
  }
}

void sendDataToSerial()
{
  Serial.print(roll);
 
  Serial.print(DELIMITER);
  Serial.print(pitch);
   
  Serial.print(DELIMITER);
  Serial.print(yaw);
  
  // Excel passthrough variables
  Serial.print(DELIMITER);
  Serial.print(X);

  Serial.print(DELIMITER);
  Serial.print(Y);
  
  Serial.print(DELIMITER);
  Serial.print(Z);
  
  Serial.print(DELIMITER);
  Serial.print(hitTime);
  
  Serial.println(); // Line ending character
}

Credits

Hacking STEM

Hacking STEM

10 projects • 74 followers
Build affordable inquiry and project-based activities to visualize data across STEM curriculum.
Jen Fox

Jen Fox

34 projects • 139 followers
Dabbled in dark matter, settled into engineering w/ a blend of inventing and education! Sr.PM @ MicrosoftFounder/CEO of FoxBot Industries

Comments