Forum Rule: Always post complete source code & details to reproduce any issue!
Results 1 to 8 of 8

Thread: Four Rotary Encoders on HID game controller?

  1. #1
    Junior Member
    Join Date
    Dec 2022
    Posts
    15

    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");
    }

  2. #2
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    1,949
    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.

  3. #3
    Junior Member
    Join Date
    Dec 2022
    Posts
    15
    Thank you. I'll give this a shot later - will I need to manually add those libraries?

  4. #4
    Junior Member
    Join Date
    Dec 2022
    Posts
    15
    Quote Originally Posted by luni View Post
    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()
    {
    }

  5. #5
    Junior Member
    Join Date
    Dec 2022
    Posts
    15
    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()
    {
    }

  6. #6
    Junior Member
    Join Date
    Dec 2022
    Posts
    15
    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);
    }

  7. #7
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    1,949
    Can you explain what " I am unable to get it to work" exactly means? What is not working, how to observe it etc.

  8. #8
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    1,949
    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.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •