Using 600P/R encoders for Gamepad

Hello all,

Please excuse my ignorance as I am still very new to programming for Arduinos.

What I am trying to achieve is using my 600P/R encoders as an axis for a gamepad. The gamepad interfaces with a 6 axis robot and ideally I would like to move the individual axis with each encoder.

I have been able to successfully move an axis with the following sketch:

/* Simple HSI Knobs Sketch for Falcon BMS / DCS / FSX
 *  for Arduino Micro/Leonardo / Sparkfun Pro Micro or equiv. clones
 * by SemlerPDX June2019
 * VETERANS-GAMING.COM
 * ( https://veterans-gaming.com/blogs/entry/32-diy-custom-game-controller-2-dial-hsi-course-and-heading-knobs/ )
 *  
 *  Pins:
 *  Rotary Encoder 1 - (OUTA-OUTB-SW) = Arduino Pins (0,1,15)
 *  Rotary Encoder 2 - (OUTA-OUTB-SW) = Arduino Pins (2,3,6)
 *  
 *  Encoder Library
 * http://www.pjrc.com/teensy/td_libs_Encoder.html
 * 
 *  Joystick Library 
 * by Matthew Heironimus
 * https://github.com/MHeironimus/ArduinoJoystickLibrary
 */

#define ENCODER_USE_INTERRUPTS
#define ENCODER_OPTIMIZE_INTERRUPTS
#include <Encoder.h>
#include <Joystick.h>

//Tell the Encoder Library which pins have encoders
Encoder axisXRotation(0, 1);
Encoder axisYRotation(2, 3);

//Rotary Encoder Push Button Pins
int buttonArray[2] = {15, 6};

//Rotary Encoder Interrupt Pins
int EncoderPin0 = 0;
int EncoderPin1 = 1;
int EncoderPin2 = 2;
int EncoderPin3 = 3;

//Delay Time between loops
int debounceDelay = 0;

//Variables to compare current to old values
int oldX = 0;
int oldY = 0;
int RxAxis_Value = 1;
int RyAxis_Value = 1;

//Intervals for Jump/Warp Speed Rotations
int JumpSpeed = 18;
int WarpSpeed = 36;

//Set generic joystick with id 42 with 2 buttons and 2 axes
Joystick_ Joystick(0x42, 
  0x04, 2, 0,
  true, true, false, true, true, false,
  false, false, false, false, false);  


void setup() { 

  //Set Encoder Pins as Pullups
  pinMode(EncoderPin0, INPUT_PULLUP);
  pinMode(EncoderPin1, INPUT_PULLUP);
  pinMode(EncoderPin2, INPUT_PULLUP);
  pinMode(EncoderPin3, INPUT_PULLUP);

  //Loop through buttons and set them as Pullups
  for (int x = 0; x < sizeof(buttonArray); x++) {
    pinMode(buttonArray[x], INPUT_PULLUP);
  }

  //Set Range of custom Axes
  Joystick.setRxAxisRange(0, 359);
  Joystick.setRyAxisRange(0, 359);
  
  // Initialize Joystick Library
  Joystick.begin(false);

}


void loop() {

  // Loop through button pin values & set to Joystick
  for (int x = 0; x < sizeof(buttonArray); x++) {
    byte currentButtonState = !digitalRead(buttonArray[x]);
    Joystick.setButton(x, currentButtonState);
  }


  // Read "Heading" X Axis Rotation Encoder Knob
  int newX = axisXRotation.read();
  if (newX > oldX) {
    //Determine speed of increment & set output
    int difX = newX - oldX;
    RxAxis_Value = speedVal(difX, RxAxis_Value, 1);
    Joystick.setRxAxis(RxAxis_Value);
    axisXRotation.write(newX);
    oldX = newX;

  }else if (newX < oldX) {
    //Determine speed of decrement & set output
    int difX = oldX - newX;
    RxAxis_Value = speedVal(difX, RxAxis_Value, 0);
    Joystick.setRxAxis(RxAxis_Value);
    axisXRotation.write(newX);
    oldX = newX;
  }


  // Read "Course" Y Axis Rotation Encoder Knob
  int newY = axisYRotation.read();
  if (newY > oldY) {
    //Determine speed of increment & set output
    int difY = newY - oldY;
    RyAxis_Value = speedVal(difY, RyAxis_Value, 1);
    Joystick.setRyAxis(RyAxis_Value);
    axisYRotation.write(newY);
    oldY = newY;

  }else if (newY < oldY) {
    //Determine speed of decrement & set output
    int difY = oldY - newY;
    RyAxis_Value = speedVal(difY, RyAxis_Value, 0);
    Joystick.setRyAxis(RyAxis_Value);
    axisYRotation.write(newY);
    oldY = newY;
  }


  //Send Joystick info through USB
  Joystick.sendState();
  delay(debounceDelay);
}


//Function to set Rotation value adjusted for the turning speed
int speedVal(int dif, int val, int dir) {
  if (dif >= WarpSpeed) {
    if (dir == 1) {
      val = val + WarpSpeed;
    }else{
      val = val - WarpSpeed;
    }
  }else if (dif >= JumpSpeed) {
    if (dir == 1) {
      val = val + JumpSpeed;
    }else{
      val = val - JumpSpeed;
    }
  }else{
    if (dir == 1) {
      val = val + 1;
    }else{
      val = val - 1;
    }
  }
  //Correct Rotation within 360 deg.
  if (val < 0) {
    val = val + 0;
  }else if (val >= 360) {        
    val = val - 0;
  }
  return val;
}

The issue I am having is with this program, the axis will continue to move in either direction unless I set the encoder to its 0 point. I reached out to the devs for the software I am using to interface the robot and they made this suggestion:

"In general the robot software expects the joystick axis value being inputed to be representative of the velocity with which the robot should move. As you mentioned, with a joystick this works as the joystick will spring back to 0 when released thus causing the robot to stop moving.

In your case, I’m guessing the behavior you want is for the robot to move based on the speed at which you are rotating your handwheel. As such, the value you should feed to the robot software is actually the speed of your encoder and not it’s position. This can be obtained by taking the difference between the current and last encoder position in the loop on your Arduino and then scaling it to the [-1,1] range. This will make it so that the robot will move whenever you move the handwheel (Difference between current and last position will be positive or negative based on the direction of rotation) and stop when the handwheel is stopped (As the difference between current and last position will now be 0)"

I have searched the forum but have not yet found a solution for this, and was hoping someone might have some insight on how I can achieve this?

Thanks!

It sounds like robot software people gave you the solution. Why don't you implement that?

Thanks for your reply. Let me clarify in saying that I don't know how to program this to make to proper changes.

Would you be able to help me with that?

I notice you've changed the debounceDelay to 0...

When I wrote that sketch, and before writing the speedVal() function, I noticed that in the flight simulator PC game I was playing, each turn of the encoder would equal 1 degree turn on the 1-360 degree HSI display in the cockpit. So, if I was on 15 degrees and wanted to change my course to 180 degrees, it would be turning that dial over and over and over again (165 detents). That's why I wrote the speedVal() function for it to notice that I'm turning the encoder faster, and to act accordingly. I'm honestly not sure if that function would be appropriate for you use as a robot controller, you may wanna cull that out for a 1-per-1 move vs. detent, and you could therefore lower the timing between loops and therefore increase the polling rate of this sketch. As set, with speedVal(), the polling rate or "debounceDelay" should not be changed.

While this sketch does produce a game controller with two rotational axes, they will never go back to a zero-point and even could potentially leave range (if never unplugged, computer never restarted, and continually turned one direction for days/weeks). I never bothered with serious exception handling in that realm.

If you DO want this to work in a manner that resets to 0, for each read of "Heading" and "Course", you could add an "else" that sets the Rx and RyAxis_Value and oldX/Y values to 0.

ex.

  }else if (newX < oldX) {
    //Determine speed of decrement & set output
    int difX = oldX - newX;
    RxAxis_Value = speedVal(difX, RxAxis_Value, 0);
    Joystick.setRxAxis(RxAxis_Value);
    axisXRotation.write(newX);
    oldX = newX;
  }else{
    Joystick.setRxAxis(0);
    axisXRotation.write(0);
    oldX = 0;
  }

...and the same for the Y axis code portion, of course. Please note, as stated before, this would not work with a debounceDelay of 0

If you have any other questions, feel free to ask. :beers:

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.