Four Rotary Encoders on HID game controller?

Working on a button box for sim games and trying to get four encoders to work in my code, but I have very very little experience.

The box is all wired up, and I have been able to test that the four encoders are working by doing some tests with the serial output. What I am missing is how to get them to be read as button presses.

I want it to be so that each encoder does a momentary button press for each detent, with clockwise and counterclockwise providing different outputs. e.g for Encoder #1, when turning to the left would read as a press of button 1 for each detent, and when turning right it would read as button 2 for each detent.

Hoping someone might be able to share some example code or point me in the right direction.

I am using a Teensy 4.1 and Windows 10.

I was hoping to steal the encoder portion of this code that I saw on a youtube video, but the Joystick.h library isn't supported.

Code:
#include <Keypad.h>
#include <Joystick.h>

//DEFINITIONS
#define ENABLE_PULLUPS
#define NUMROTARIES ? //replace "?" with number of rotary encoders you are using
#define NUMBUTTONS ? //replace "?"with number of buttong you are using
#define NUMROWS ? //replace "?" with number of rows you have
#define NUMCOLS ? //replace "?" with number of columns you have

//BUTTON MATRIX
//first change number of rows and columns to match your button matrix, 
//then replace all "?" with numbers (starting from 0)
byte buttons[NUMROWS][NUMCOLS] = {
  {?,?,?,?},
  {?,?,?,?},
  {?,?,?,?},
  {?,?,?,?}
  
 
 
};

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

//ROTARY ENCODERS
//each line controls a different rotary encoder
//the first two numbers refer to the pins the encoder is connected to 
//the second two are the buttons each click of the encoder wil press 
//do NOT exceed 31 for the final button number
rotariesdef rotaries[NUMROTARIES] {
  {0,1,22,23,0}, //rotary 1
  {2,3,24,25,0}, //rotary 2
  {4,5,26,27,0}, //rotary 3
  {6,7,28,29,0} //rotary 4


};

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

#ifdef HALF_STEP
#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
#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

//BUTTON MATRIX PART 2
byte rowPins[NUMROWS] = {?,?,?}; //change "?" to the pins the rows of your button matrix are connected to
byte colPins[NUMCOLS] = {?,?,?,?}; //change "?" to the pins the rows of your button matrix are connected to

Keypad buttbx = Keypad( makeKeymap(buttons), rowPins, colPins, NUMROWS, NUMCOLS);

//JOYSTICK SETTINGS
Joystick_ Joystick(JOYSTICK_DEFAULT_REPORT_ID,
  JOYSTICK_TYPE_JOYSTICK,
  32, //number of buttons
  0, //number of hat switches
  //Set as many axis to "true" as you have potentiometers for
  false, // y axis
  false, // x axis
  false, // z axis
  false, // rx axis
  false, // ry axis
  false, // rz axis
  false, // rudder
  false, // throttle
  false, // accelerator
  false, // brake
  false); // steering wheel

const int numReadings = 20;
 
int readings[numReadings];      // the readings from the analog input
int index = 0;              // the index of the current reading
int total = 0;                  // the running total
int currentOutputLevel = 0;

//POTENTIOMETERS PART 1
//add all the axis' which are enabled above
int zAxis_ = 0;
int RxAxis_ = 0;   

               
//POTENTIOMETERS  PART 2
//Which pins are your potentiometers connected to?
int potentiometerPin1 = ?; //Change "?" to the pin your potentiometer is connected to
int potentiometerPin2 = ?;
const bool initAutoSendState = true;


void setup() {
  Joystick.begin();
  rotary_init();
  for (int thisReading = 0; thisReading < numReadings; thisReading++) {
    readings[thisReading] = 0;
  }
}

void loop() {

  CheckAllEncoders();
  CheckAllButtons();
  CheckAllPotentiometers();
 
}

//POTENTIOMETERS PART 3
//change the details to match teh details above for each potentiometer you are using
void CheckAllPotentiometers(){
                           
  //potentiometer 1
  currentOutputLevel = getAverageOutput(potentiometerPin1);
  zAxis_ = map(currentOutputLevel,0,1023,0,255);
  Joystick.setZAxis(zAxis_); 

  //potentiometer 2
  currentOutputLevel = getAverageOutput(potentiometerPin2);
  RxAxis_ = map(currentOutputLevel,0,1023,0,255);
  Joystick.setRxAxis(RxAxis_);


}

int getAverageOutput(int pinToRead){
  index = 0;
  total = 0; 
 
  while (index < numReadings){
    readings[index] = analogRead(pinToRead);
    total = total + readings[index];
    index = index + 1;
    //delay (1);
  }
  return total / numReadings;
}


void CheckAllButtons(void) {
      if (buttbx.getKeys())
    {
       for (int i=0; i<LIST_MAX; i++)   
        {
           if ( buttbx.key[i].stateChanged )   
            {
            switch (buttbx.key[i].kstate) { 
                    case PRESSED:
                    case HOLD:
                              Joystick.setButton(buttbx.key[i].kchar, 1);
                              break;
                    case RELEASED:
                    case IDLE:
                              Joystick.setButton(buttbx.key[i].kchar, 0);
                              break;
            }
           }   
         }
     }
}


void rotary_init() {
  for (int i=0;i<NUMROTARIES;i++) {
    pinMode(rotaries[i].pin1, INPUT);
    pinMode(rotaries[i].pin2, INPUT);
    #ifdef ENABLE_PULLUPS
      digitalWrite(rotaries[i].pin1, HIGH);
      digitalWrite(rotaries[i].pin2, HIGH);
    #endif
  }
}


unsigned char rotary_process(int _i) {
  //Serial.print("Processing rotary: ");
  //Serial.println(_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) {
  Serial.println("Checking rotaries");
  for (int i=0;i<NUMROTARIES;i++) {
    unsigned char result = rotary_process(i);
    if (result == DIR_CCW) {
      Serial.print("Rotary ");
      Serial.print(i);
      Serial.println(" <<< Going CCW");
      Joystick.setButton(rotaries[i].ccwchar, 1); delay(50); Joystick.setButton(rotaries[i].ccwchar, 0);
    };
    if (result == DIR_CW) {
      Serial.print("Rotary ");
      Serial.print(i);
      Serial.println(" >>> Going CW");
      Joystick.setButton(rotaries[i].cwchar, 1); delay(50); Joystick.setButton(rotaries[i].cwchar, 0);
    };
  }
  Serial.println("Done checking");
}
 
I can't help you with the Joystick stuff but I can show you how to tackle the encoder part of the problem:

Code:
#include "Arduino.h"
#include "EncoderTool.h"

using namespace EncoderTool;

Encoder e1;

void onEncoderChanged(int value, int delta)
{
    if(delta == 1)
    {
        Serial.println("Encoder up"); // do whatever needs to be done to send a Joystick butto press here
    }

    if(delta == -1)
    {
        Serial.println("Encoder down");// do whatever needs to be done to send a Joystick butto press here
    }
}

void setup()
{
    e1.begin(1, 2);                           // encoder on pin 1 and 2 (you can choose any pin pair you like
    e1.attachCallback(onEncoderChanged);      // attach the function 'onEncoderChanged' to the encoder. It will be called on each change of the encoder value
}

void loop()
{
}

Of course you need to replace the Serial.prints by your code to send the corresponding Joystick button messages.
 
I can't help you with the Joystick stuff but I can show you how to tackle the encoder part of the problem:

Code:
#include "Arduino.h"
#include "EncoderTool.h"

using namespace EncoderTool;

Encoder e1;

void onEncoderChanged(int value, int delta)
{
    if(delta == 1)
    {
        Serial.println("Encoder up"); // do whatever needs to be done to send a Joystick butto press here
    }

    if(delta == -1)
    {
        Serial.println("Encoder down");// do whatever needs to be done to send a Joystick butto press here
    }
}

void setup()
{
    e1.begin(1, 2);                           // encoder on pin 1 and 2 (you can choose any pin pair you like
    e1.attachCallback(onEncoderChanged);      // attach the function 'onEncoderChanged' to the encoder. It will be called on each change of the encoder value
}

void loop()
{
}

Of course you need to replace the Serial.prints by your code to send the corresponding Joystick button messages.

Thanks for this - I got it to send the button presses, but it just holds the button. Any chance you could offer some advice on how to have it do a momentary press? i.e. one tick is one press?



This is what I've done so far:



Code:
#include "Arduino.h"
#include "EncoderTool.h"

using namespace EncoderTool;

Encoder e1;

void onEncoderChanged(int value, int delta)
{
    if(delta == 1)
    {
        Serial.println("Encoder up"); 
        Joystick.button(2, 1);// do whatever needs to be done to send a Joystick butto press here
    }

    if(delta == -1)
    {
        Serial.println("Encoder down");
        Joystick.button(3, 1);// do whatever needs to be done to send a Joystick butto press here
    }
}

void setup()
{
    e1.begin(33, 34);                           // encoder on pin 1 and 2 (you can choose any pin pair you like
    e1.attachCallback(onEncoderChanged);      // attach the function 'onEncoderChanged' to the encoder. It will be called on each change of the encoder value
}

void loop()
{
}
 
I actually managed to get it work by adding a delay. Welcome help on making this nicer or catching any issues:


Code:
#include "Arduino.h"
#include "EncoderTool.h"

using namespace EncoderTool;

Encoder e1;
Encoder e2;
Encoder e3;
Encoder e4;

//Encoder 1
void onEncoderChanged1(int value, int delta)
{
    if(delta == 1)
    {
        Serial.println("Encoder up"); 
        Joystick.button(2, 1);
        delay(30);
        Joystick.button(2, 0);
    }

    if(delta == -1)
    {
        Serial.println("Encoder down");
        Joystick.button(3, 1);
        delay(30);
        Joystick.button(3, 0);
    }
}
//Encoder 2
void onEncoderChanged2(int value, int delta)
{
    if(delta == 1)
    {
        Serial.println("Encoder up"); 
        Joystick.button(4, 1);
        delay(30);
        Joystick.button(4, 0);
    }

    if(delta == -1)
    {
        Serial.println("Encoder down");
        Joystick.button(5, 1);
        delay(30);
        Joystick.button(5, 0);
    }
}
//Encoder 3
void onEncoderChanged3(int value, int delta)
{
    if(delta == 1)
    {
        Serial.println("Encoder up"); 
        Joystick.button(6, 1);
        delay(30);
        Joystick.button(6, 0);
    }

    if(delta == -1)
    {
        Serial.println("Encoder down");
        Joystick.button(7, 1);
        delay(30);
        Joystick.button(7, 0);
    }
}
//Encoder 4
void onEncoderChanged4(int value, int delta)
{
    if(delta == 1)
    {
        Serial.println("Encoder up"); 
        Joystick.button(8, 1);
        delay(30);
        Joystick.button(8, 0);
    }

    if(delta == -1)
    {
        Serial.println("Encoder down");
        Joystick.button(9, 1);
        delay(30);
        Joystick.button(9, 0);
    }
}

void setup()
{
    e1.begin(33, 34);                           // encoder on pin 1 and 2 (you can choose any pin pair you like
    e1.attachCallback(onEncoderChanged1);      // attach the function 'onEncoderChanged' to the encoder. It will be called on each change of the encoder value
    e2.begin(36, 37);                           
    e2.attachCallback(onEncoderChanged2); 
    e3.begin(38, 39);                           
    e3.attachCallback(onEncoderChanged3); 
    e4.begin(40, 41);                           
    e4.attachCallback(onEncoderChanged4); 
}

void loop()
{
}
 
So this works, but I am unable to get it to work within the code that does my buttons and potentiometers.

Code:
//Six rows on pins 0, 1, 2, 3, 4, 5
//Seven columns on pins 23, 22, 21, 20, 19, 18, 17,
//Four rotary encoders, pins (33,34) (36,37) (38,39) (40,41)
//Three potentiometers, pins 24, 25, 26


  #include <Keypad.h>
  #include <Arduino.h>
  #include <EncoderTool.h>
  #define JOYSTICK_SIZE 64 // 12 = normal, 64 = extreme joystick



//BUTTON MATRIX SECTION
  const byte ROWS = 6; //four rows
  const byte COLS = 7; //four columns

  byte rowPins[ROWS] = {0, 1, 2, 3, 4, 5}; //connect to the row pinouts of the keypad
  byte colPins[COLS] = {23, 22, 21, 20, 19, 18, 17,}; //connect to the column pinouts of the keypad

  char keyMap[ROWS][COLS] = {
    {1,2,3,4,5,6,7},
    {8,9,10,11,12,13,14},
    {15,16,17,18,19,20,21},
    {22,23,24,25,26,27,28},
    {29,30,31,32,33,34,35},
    {36,37}
  };

  Keypad kpd = Keypad( makeKeymap(keyMap), rowPins, colPins, ROWS, COLS); 


//Encoder Seciton
using namespace EncoderTool;

Encoder e1;
Encoder e2;
Encoder e3;
Encoder e4;

//Encoder 1
void onEncoderChanged1(int value, int delta)
{
    if(delta == 1)
    {
        Serial.println("Encoder up"); 
        Joystick.button(38, 1);
        delay(30);
        Joystick.button(38, 0);
    }

    if(delta == -1)
    {
        Serial.println("Encoder down");
        Joystick.button(39, 1);
        delay(30);
        Joystick.button(39, 0);
    }
}
//Encoder 2
void onEncoderChanged2(int value, int delta)
{
    if(delta == 1)
    {
        Serial.println("Encoder up"); 
        Joystick.button(40, 1);
        delay(30);
        Joystick.button(40, 0);
    }

    if(delta == -1)
    {
        Serial.println("Encoder down");
        Joystick.button(41, 1);
        delay(30);
        Joystick.button(41, 0);
    }
}
//Encoder 3
void onEncoderChanged3(int value, int delta)
{
    if(delta == 1)
    {
        Serial.println("Encoder up"); 
        Joystick.button(42, 1);
        delay(30);
        Joystick.button(42, 0);
    }

    if(delta == -1)
    {
        Serial.println("Encoder down");
        Joystick.button(43, 1);
        delay(30);
        Joystick.button(43, 0);
    }
}
//Encoder 4
void onEncoderChanged4(int value, int delta)
{
    if(delta == 1)
    {
        Serial.println("Encoder up"); 
        Joystick.button(44, 1);
        delay(30);
        Joystick.button(44, 0);
    }

    if(delta == -1)
    {
        Serial.println("Encoder down");
        Joystick.button(45, 1);
        delay(30);
        Joystick.button(45, 0);
    }
}

void setup() 
{
    Joystick.useManualSend(true);
    e1.begin(33, 34);                           
    e1.attachCallback(onEncoderChanged1);      
    e2.begin(36, 37);                           
    e2.attachCallback(onEncoderChanged2); 
    e3.begin(38, 39);                           
    e3.attachCallback(onEncoderChanged3); 
    e4.begin(40, 41);                           
    e4.attachCallback(onEncoderChanged4); 
}

void loop() {
  Joystick.X(analogRead(24));
  Joystick.Y(analogRead(25));
  Joystick.Z(analogRead(26));
  
  // Because setup configured the Joystick manual send,
  // the computer does not see any of the changes yet.
  // This send_now() transmits everything all at once.
  Joystick.send_now();
  
  if ( kpd.getKeys() )
  {
    for (int i=0; i<LIST_MAX; i++)
    {
      if ( kpd.key[i].stateChanged )
      {
        if ( kpd.key[i].kstate == PRESSED || kpd.key[i].kstate == HOLD)
        {
            Joystick.button(kpd.key[i].kchar, 1);
        } else if ( kpd.key[i].kstate == RELEASED ){
            Joystick.button(kpd.key[i].kchar, 0);
        }
        Joystick.send_now();
      }
    }
  }
  delay(5);
}
 
Can you explain what " I am unable to get it to work" exactly means? What is not working, how to observe it etc.
 
I meanwhile had a closer look at your code. The reason why it doesn't work seems to be that you set the Joystick to manualSend mode in setup(). In this case you should place a Joystick.send_now after all your Joystick.button(x,y) calls in the onEncoderChanged functions to make sure that the button change got sent. Alternatively (and better?) you can just remove the Joystick.useManualSend(true) statement in setup. Both methods work here

Another thing: You should not do long delays in callback functions since these will effectively block the sketch. It works here since you don't do much in your sketch. But it would be much better to just send a button down in the callback and trigger a timer which will send a button up after a given time in the background. If necessary I can show you how to achieve that.
 
Back
Top