Joystick functionality issue with encoders

Hi all,

I'm using a Leonardo to interface with my computer as a joystick, I've assigned a few buttons (buttons 1-12) which are working great I've also added in 3 rotary encoders (Buttons 13-18).

The desired functionality is when I turn the encoder clockwise or counterclockwise it registers the movement as 1 triggering of the button.

Example:

Turn encoder #1 clockwise 3 detentes should register 3 triggers of the 13 button. Likewise, turning it counterclockwise would register the 14 button.

The problem I am having is that the rotaries are just constantly triggering and showing all the rotary buttons as being triggered.

I've included my code below, it compiles fine, windows interprets it as an 18 buttonjoystick, like intended, the pushbuttons work fine as well.

//include joystick library
#include <Joystick.h>


//Map Button name alias to pins Syntax= define <Name\Pin>
#define NUM_BUTTONS 18
#define B1 8
#define B2 9
#define B3 10
#define B4 11
#define B5 12
#define B6 13
#define B7 18
#define B8 19
#define B9 20
#define B10 21
#define B11 22
#define B12 23

//Rotary definition
#define NUMROTARIES 3
#define ENABLE_PULLUPS


// Set Map Button names/pins to button numbers
const byte buttonPins[NUM_BUTTONS] = {B1, B2, B3, B4, B5, B6, B7, B8, B9, B10, B11, B12};


//Setup joystick functions
Joystick_ Joystick(JOYSTICK_DEFAULT_REPORT_ID,JOYSTICK_TYPE_GAMEPAD,
  NUM_BUTTONS, 0,        // Button Count, Hat Switch Count
  false, false, false,   // X and Y, but no Z Axis
  false, false, false,   // No Rx, Ry, or Rz
  false, false,          // No rudder or throttle
  false, false, false);  // No accelerator, brake, or steering
  

//Testing Rotaries
struct rotariesdef {
  byte pin1;
  byte pin2;
  int ccwchar;
  int cwchar;
  volatile unsigned char state;
};

rotariesdef rotaries[NUMROTARIES] {
  {6,7,13,14,0},
  {2,3,15,16,0},
  {4,5,17,18,0},
};

#define DIR_CCW 0x10
#define DIR_CW 0x20
#define R_START 0x0

#ifdef HALF_STEP
// Use the half-step state table (emits a code at 00 and 11)
#define R_CCW_BEGIN 0x1
#define R_CW_BEGIN 0x2
#define R_START_M 0x3
#define R_CW_BEGIN_M 0x4
#define R_CCW_BEGIN_M 0x5
const unsigned char ttable[6][4] = {
  // R_START (00)
  {R_START_M,            R_CW_BEGIN,     R_CCW_BEGIN,  R_START},
  // R_CCW_BEGIN
  {R_START_M | DIR_CCW, R_START,        R_CCW_BEGIN,  R_START},
  // R_CW_BEGIN
  {R_START_M | DIR_CW,  R_CW_BEGIN,     R_START,      R_START},
  // R_START_M (11)
  {R_START_M,            R_CCW_BEGIN_M,  R_CW_BEGIN_M, R_START},
  // R_CW_BEGIN_M
  {R_START_M,            R_START_M,      R_CW_BEGIN_M, R_START | DIR_CW},
  // R_CCW_BEGIN_M
  {R_START_M,            R_CCW_BEGIN_M,  R_START_M,    R_START | DIR_CCW},
};
#else
// Use the full-step state table (emits a code at 00 only)
#define R_CW_FINAL 0x1
#define R_CW_BEGIN 0x2
#define R_CW_NEXT 0x3
#define R_CCW_BEGIN 0x4
#define R_CCW_FINAL 0x5
#define R_CCW_NEXT 0x6

const unsigned char ttable[7][4] = {
  // R_START
  {R_START,    R_CW_BEGIN,  R_CCW_BEGIN, R_START},
  // R_CW_FINAL
  {R_CW_NEXT,  R_START,     R_CW_FINAL,  R_START | DIR_CW},
  // R_CW_BEGIN
  {R_CW_NEXT,  R_CW_BEGIN,  R_START,     R_START},
  // R_CW_NEXT
  {R_CW_NEXT,  R_CW_BEGIN,  R_CW_FINAL,  R_START},
  // R_CCW_BEGIN
  {R_CCW_NEXT, R_START,     R_CCW_BEGIN, R_START},
  // R_CCW_FINAL
  {R_CCW_NEXT, R_CCW_FINAL, R_START,     R_START | DIR_CCW},
  // R_CCW_NEXT
  {R_CCW_NEXT, R_CCW_FINAL, R_CCW_BEGIN, R_START},
};
#endif

void setup() {
  // Change pins to input and enable pullup resistors
  pinMode(B1, INPUT_PULLUP);
  pinMode(B2, INPUT_PULLUP);
  pinMode(B3, INPUT_PULLUP);
  pinMode(B4, INPUT_PULLUP);
  pinMode(B5, INPUT_PULLUP);
  pinMode(B6, INPUT_PULLUP);
  pinMode(B7, INPUT_PULLUP);
  pinMode(B8, INPUT_PULLUP);
  pinMode(B9, INPUT_PULLUP);
  pinMode(B10, INPUT_PULLUP);
  pinMode(B11, INPUT_PULLUP);
  pinMode(B12, INPUT_PULLUP);
  pinMode(2, INPUT_PULLUP);
  pinMode(3, INPUT_PULLUP);
  pinMode(4, INPUT_PULLUP);
  pinMode(5, INPUT_PULLUP);
  pinMode(6, INPUT_PULLUP);
  pinMode(7, INPUT_PULLUP);
  digitalWrite(2, HIGH);
  digitalWrite(3, HIGH);
  digitalWrite(4, HIGH);
  digitalWrite(5, HIGH);
  digitalWrite(6, HIGH);
  digitalWrite(7, HIGH);
  



// Initialize Joystick Library
  Joystick.begin();

}

void loop() {

  CheckAllEncoders();
  
  // Read Button
  bool buttonVals[NUM_BUTTONS];
  for (int index =0; index < NUM_BUTTONS; index++){
      buttonVals[index] = !digitalRead(buttonPins[index]);
  }
    for(int i = 0; i < NUM_BUTTONS; i++) {
      Joystick.setButton(i, buttonVals[i]);
    }
     delay(50);
}


/* Read input pins and process for events. Call this either from a
 * loop or an interrupt (eg pin change or timer).
 *
 * Returns 0 on no event, otherwise 0x80 or 0x40 depending on the direction.
 */
unsigned char rotary_process(int _i) {
   unsigned char pinstate = (digitalRead(rotaries[_i].pin2) << 1) | digitalRead(rotaries[_i].pin1);
  rotaries[_i].state = ttable[rotaries[_i].state & 0xf][pinstate];
  return (rotaries[_i].state & 0x30);
}

void CheckAllEncoders(void) {
  for (int i=0;i<NUMROTARIES;i++) {
    unsigned char result = rotary_process(i);
    if (result == DIR_CCW) {
      Joystick.setButton(rotaries[i].ccwchar, 1); delay(50); Joystick.setButton(rotaries[i].ccwchar, 0);
    };
    if (result == DIR_CW) {
      Joystick.setButton(rotaries[i].cwchar, 1); delay(50); Joystick.setButton(rotaries[i].cwchar, 0);
    };
  }
}

You can see in this image the buttons assigned to the rotary encoders are lit up, currently nothing is connected to the pins these are assigned to but they are still being triggered by something.

If anyone has any ideas please let me know I am tearing my hair out.

Thanks in advance

Have you written a simple test program to read the encoders and just print the results on the Serial Monitor?

...R

Robin2:
Have you written a simple test program to read the encoders and just print the results on the Serial Monitor?

...R

Yep, I used one of the examples in the encoder library to read the output on serial. I believe it's called "Basic". Worked fine there.

sonofeevil:
Yep, I used one of the examples in the encoder library to read the output on serial. I believe it's called "Basic". Worked fine there.

So what's different between the working and the non-working program?

Can you post the working program?

...R

Edit: Deleted all the old message here

I'm going back to square one on the encoder using the Encoder library and what I've written is fairly basic but it does the trick to determine the direction of the encoder which is all I need

#include <Encoder.h>
Encoder myEnc(2, 3);

void setup() {
  Serial.begin(9600);
  Serial.println("Basic Encoder Test:");
}
long oldPosition  = -999;

void loop() {
  long newPosition = myEnc.read();
  if (newPosition > oldPosition +2 || newPosition < oldPosition -2) {
    if (newPosition > oldPosition) {
    Serial.println("Clockwise");
    oldPosition = newPosition;
  }
  else if (newPosition < oldPosition) {
    Serial.println("CounterClockwise");
    oldPosition = newPosition;
  }
  }
}

So now that I can get the direction is there a way I can make that direction trigger a button press? Something like this

if (direction = clockwise)
   trigger button 14
else
   trigger button 15

sonofeevil:
So now that I can get the direction is there a way I can make that direction trigger a button press? Something like this

if (direction = clockwise)

trigger button 14
else
  trigger button 15

That certainly should be possible. What is the code needed to create a button press?

What have you tried?

...R

PS you need == for an IF test

Robin2:
That certainly should be possible. What is the code needed to create a button press?

What have you tried?

...R

This is what I'm using right now to detect a button press

  bool buttonVals[NUM_BUTTONS];
  for (int index =0; index < NUM_BUTTONS; index++){
      buttonVals[index] = !digitalRead(buttonPins[index]);
  }
    for(int i = 0; i < NUM_BUTTONS; i++) {
      Joystick.setButton(i, buttonVals[i]);

I'm currently trying to wrap my head around which part is the actual "press" and I could really use some guidance on this but what I have written is

void loop() {
 long newPosition = myEnc.read();
  if (newPosition > oldPosition +2 || newPosition < oldPosition -2) {
    if (newPosition > oldPosition) {
    Serial.println("Clockwise");
    oldPosition = newPosition;
    for(int i = 0; i < B13; i++) {
      Joystick.setButton(i, buttonVals[i]);
      
    }    
  }
  else if (newPosition < oldPosition) {
    Serial.println("CounterClockwise");
    oldPosition = newPosition;
  }
  } 
}

and unfortunately this doesn't work

sonofeevil:
This is what I'm using right now to detect a button press

  bool buttonVals[NUM_BUTTONS];

for (int index =0; index < NUM_BUTTONS; index++){
     buttonVals[index] = !digitalRead(buttonPins[index]);
 }
   for(int i = 0; i < NUM_BUTTONS; i++) {
     Joystick.setButton(i, buttonVals[i]);




I'm currently trying to wrap my head around which part is the actual "press" and I could really use some guidance on this

That is actually two separate pieces of code that has not been clearly presented. Look at it like this

bool buttonVals[NUM_BUTTONS];
for (int index =0; index < NUM_BUTTONS; index++){
        buttonVals[index] = !digitalRead(buttonPins[index]);
}


for(int i = 0; i < NUM_BUTTONS; i++) {
    Joystick.setButton(i, buttonVals[i]);
}

The first part reads the Arduino buttons and stores their states into an array

The second part iterates through that array and creates a joystick button according to the state that was recorded for the Arduino button.

It's a neat way to separate the two parts of the code. If it was my project I would have them in two separate functions with meaningful names - maybe like this

bool buttonVals[NUM_BUTTONS]; // make this a global array

void readArduinoButtons() {
    for (int index =0; index < NUM_BUTTONS; index++){
        buttonVals[index] = !digitalRead(buttonPins[index]);
    }
}

void updateJoystickButtons() {
    for(int i = 0; i < NUM_BUTTONS; i++) {
        Joystick.setButton(i, buttonVals[i]);
    }
}

with readArduinoButtons() and updateJoystickButtons() both called from loop()

Then when your encoder code detects a new move all it needs to do is write the appropriate value into the appropriate element in the array buttonVals[]

Note. however, that you probably need to restrict the range of I/O pins that are checked in readArduinoButtons() so that it does not do anything to the elements in buttonVals[] that belong to the encoder.

...R