Working Arduino code for Rotary Encoders

Hey everyone,

I hope it’s ok if I post this here. I have been searching far and wide and have been scratching my head trying to solve the rotary encoder issue. You know, the one where you have to turn your rotary knob a “million times” just to get your Heading, Altitude, VS etc. to increment one step.

Well my search is finally over. I found the video below on YouTube which explains how to code buttons and rotary encoders for a Truck Simulator.
Video Here

The video owner “That Beginner” also referenced this link where you can find the his Code.

You will also need the Arduino rotary library found here and the Arduino elapsedMillis library found here.
I have extracted the rotary portion of the code and added it below.

I am not the original creator of this code, all props go to “That Beginner” I have just edited it for my use.
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//This code starts here
//You need these libraries for this to work

#include <Bounce.h>
#include <elapsedMillis.h>
#include <Rotary.h>

elapsedMillis timeElapsed;
unsigned int t_interval = 200; //interval to hold buttons on for when needed
unsigned int enc_interval = 50; //interval for encoders

//These are the physical pins on your Arduino or Teendy Board
Rotary rotary1 = Rotary(7, 8);
Rotary rotary2 = Rotary(9, 10);
Rotary rotary3 = Rotary(12, 11);

void setup() {

//This is the interup for the rotary pins
attachInterrupt(7, rotate, CHANGE);
attachInterrupt(8, rotate, CHANGE);
attachInterrupt(9, rotate, CHANGE);
attachInterrupt(10, rotate, CHANGE);
attachInterrupt(11, rotate, CHANGE);
attachInterrupt(12, rotate, CHANGE);

}

void loop() {

//This is the button that you what ot send the rotary encoder sign too
//On you Joystick Controller (i.e Joy.cpl)
if (timeElapsed > enc_interval) {
Joystick.button(8,0);
Joystick.button(9,0);
Joystick.button(10,0);
Joystick.button(11,0);
Joystick.button(12,0);
Joystick.button(13,0);
}

}

//This portion of the code handles the rotary knobs
//and only allows on Joystick button to be active per rotary encoder per rotation click
void rotate() {

// HANDLE ENCODER 1
unsigned char result1 = rotary1.process();
if (result1 == DIR_CW) {
timeElapsed = 0;
Joystick.button(8,1);
Joystick.button(9,0);
} else if (result1 == DIR_CCW) {
timeElapsed = 0;
Joystick.button(9,1);
Joystick.button(8,0);
}

// HANDLE ENCODER 2
unsigned char result2 = rotary2.process();
if (result2 == DIR_CW) {
timeElapsed = 0;
Joystick.button(10,1);
Joystick.button(11,0);
} else if (result2 == DIR_CCW) {
timeElapsed = 0;
Joystick.button(11,1);
Joystick.button(10,0);
}

// HANDLE ENCODER 3
unsigned char result3 = rotary3.process();
if (result3 == DIR_CW) {
timeElapsed = 0;
Joystick.button(12,1);
Joystick.button(13,0);
} else if (result3 == DIR_CCW) {
timeElapsed = 0;
Joystick.button(13,1);
Joystick.button(12,0);
}

}

3 Likes

Thanks for sharing.

OP, I really love you for doing this. Saved tons of hours of my life

Hey Kloot8200,

You’re welcome!

Op3ntrap,

You’re welcome. It took a lot of searching and trial n errors. but it works.

Can anyone help an Arduino newbie? I’m getting these errors. Can anyone help? I just copied and pasted the above code. I’m sure there is something else I need to do.

Hel Skdill,

Did you declare the joystick.button in your code also like in example below. Can you post you full code?

Thanks for looking at this for me. I’m still new to coding.

////////////////////////////////////////////////////////////////////////

#include <Bounce.h>
#include <elapsedMillis.h>
#include <Rotary.h>

elapsedMillis timeElapsed;
unsigned int t_interval = 200; //interval to hold buttons on for when needed
unsigned int enc_interval = 50; //interval for encoders

//These are the physical pins on your Arduino or Teendy Board
Rotary rotary1 = Rotary(7, 8);

void setup() {

//This is the interup for the rotary pins
attachInterrupt(7, rotate, CHANGE);
attachInterrupt(8, rotate, CHANGE);

}

void loop() {

//This is the button that you what ot send the rotary encoder sign too
//On you Joystick Controller (i.e Joy.cpl)
if (timeElapsed > enc_interval) {
Joystick.button(8,0);
Joystick.button(9,0);

}

}

//This portion of the code handles the rotary knobs
//and only allows on Joystick button to be active per rotary encoder per rotation click
void rotate() {

// HANDLE ENCODER 1
unsigned char result1 = rotary1.process();
if (result1 == DIR_CW) {
timeElapsed = 0;
Joystick.button(8,1);
Joystick.button(9,0);
} else if (result1 == DIR_CCW) {
timeElapsed = 0;
Joystick.button(9,1);
Joystick.button(8,0);
}

}

Ok, I will check it out.

Hey SKDILL I ran your code and it did not return any errors. Make sure you have the following libraries in you Arduino library folder:

elapsedMillis
Rotary
RotaryEncoder-1.3.0

Also, I’m using a Teensy board and not an Arduino.

The image below is the results that I got after verifying your code.

I double checked the Arduino library and all of those are in there. I am using an Arduino Leonardo. Very weird. No idea why its not working. My search continues. Thanks for the help!

Yeah that is strange.

One more thing, make sure that you have “Flight Sim Controls + Joystick” selected as your “USB Type:”, the correct board and port under the tools tab. Hope this helps.

Huge Thank you for this thread! I purchased a couple usb knobs (Rotary encoder w/ Teensy LC) off etsy and needed to change the function to use for my OBS and heading bug. I knew ZERO about code, programming etc. reading the thread and instructions was perfect and I successfully created a couple great devices that work perfectly (in VR). the only thing I changed was the timing so I can rapidly spin the knob to change heading to the other side of the compass, then slow down to fine tune the degrees. Here are my settings:
/////////////////////////////////////////////////
elapsedMillis timeElapsed;
unsigned int t_interval = 200; //interval to hold buttons on for when needed
unsigned int enc_interval = 200; //interval for encoders
/////////////////////////////////////////////////

Thank you all !

1 Like

@DaedalCone97647 you’re welcome. Checkout this vid that I posted on building you own switchbox. Flight Simulator Switch Panel 2.0 DIY Project - YouTube

1 Like

Hi,

I stumbled across your code and I am trying to implement “elpasedMillis” function into my code to spin the encoders quickly but I am unsuccessful. I cannot use your exact code since I am also using switches and potentiometers. Do you have any experience how to implement it into my code?

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


//DEFINITIONS
#define ENABLE_PULLUPS
#define NUMROTARIES 4 //replace "?" with number of rotary encoders you are using
#define NUMBUTTONS 20 //replace "?"with number of buttong you are using
#define NUMROWS 4 //replace "?" with number of rows you have
#define NUMCOLS 5 //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] = {
  {0,1,2,3,4},
  {5,6,7,8,9},
  {10,11,12,13,14},
  {15,16,17,18,19}
  
 
 
};

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] = {A0,A1,A2,A3}; //change "?" to the pins the rows of your button matrix are connected to
byte colPins[NUMCOLS] = {8,9,10,11,12}; //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,
  30, //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
  true, // rx axis
  true, // 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 RxAxis_ = 0;
int RyAxis_ = 0;   

               
//POTENTIOMETERS  PART 2
//Which pins are your potentiometers connected to?
int potentiometerPin1 = A4; //Change "?" to the pin your potentiometer is connected to
int potentiometerPin2 = A5;
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);
  RxAxis_ = map(currentOutputLevel,0,1023,0,255);
  Joystick.setRxAxis(RxAxis_); 

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


}

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

Is this recognized as a joystick in MSFS, or do you still need some intermediary software?

Hi,

sorry for the long wait @JCSLOVE I did not get a notification.

Yes, in MSFS it is recognized as a button box, like in Window’s “USB Game Controllers”.

I have been experimenting with a Teensy board and some buttons and the sim always freezes when I plug it in and the whole thing feels a little slower. Is anyone else having this problem?

Is your computer recognizing your controller as a joystick/controller? Are you able to run joy.cpl in the windows search on the task bar?

1 Like

My computer recognizes it as a controller. But the sim freezes when I plug it in… it’s probably an issue with my code…