Please help me get started with rotary encoders

Thanks. I’ve not made much progress. I’ve actually backed off the encoders for now while I try to make something “simpler” work. I have a five position key switch I’m trying to setup as the magneto/starter key in the GA aircraft. This code comes from this video which does use the Joystick.h library. What’s odd is at this point I have the diodes installed between the switches and columns of the matrix (not shown in this video) which I learned of here. In Windows I’m seeing un the USB game controllers sometimes I see switch one remain “on” when it isn’t as I cycle through two, three, four and five. in the sim, the magnetos are mapped and I see the key wobble. It’s not staying in the position selected with the key. I suspect my wiring is an issue. The 1N4148 Switching Diode are oriented with the cathodes (negative pole) connecting to the columns as shown. I also wondered if I may have a bad keyswitch, so I tried plugging a momentary pushbutton to the start position of the Leanardo (pins 13, A5). This produced the same wobble/wagging key in GA aircraft. Any ideas what I may be doing incorrectly? Thanks!

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

//DEFINITIONS
#define ENABLE_PULLUPS
#define NUMROTARIES 2 //replace "?" with number of rotary encoders you are using
#define NUMBUTTONS 5 //replace "?"with number of buttong you are using
#define NUMROWS 1 //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}
};

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

#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] = {A5}; //change "?" to the pins the rows of your button matrix are connected to
byte colPins[NUMCOLS] = {13, 12, 11, 10, 9}; //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 = A0; //Change "?" to the pin your potentiometer is connected to
int potentiometerPin2 = A1;
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");
}

This must be a MSFS issue/“feature”. In DCS I have a P-47 with a magneto switch and it works as expected.

Hello CavemanScorch,
I understood the following:
You are using a 5 position key switch. I suspect this switch closes on each of the 5 positions and in the program you use the “CheckAllButtons ()” function to determine the switch positions.
I suspect that such a switch is permanently closed in the respective position, ie the line “switch (buttbx.key [i] .kstate)” always has the value PRESSED EDIT: or HOLD, so that the line "Joystick.setButton (buttbx.key [ i] .kchar, 1); “ is executed all the time. Most commands in the flight simulator only expect a single signal.
You can test this by inserting a “Serial.println (buttbx.key [i] .kchar)” under this line before the break and watching the output window of the ArduinoIDE.
Replace the 5 position key switch with 5 simple buttons, that could work.
If that is the cause, you have to make sure in the code that the line is only executed once, i.e. only if something changes in the key position.
EDIT: Maybe it is enough if you only ask “PRESSED” but not “HOLD” for the key events. (but becomes an ugly code :sneezing_face:)

I suspect this is because “Joystick.setButton (buttbx.key [i] .kchar, 0);” is never called for the last state when turning the switch.

Thank you. You’ve given me much to digest. My programming knowledge is quite limited and my Arduino knowledge consists of this project. Very interesting in DCS I’m getting the expected results. I’d considered MS Flight Simulator and DCS don’t digest (for lack of a better term) the code in the same ways. I may need to adjust my code for the MS box which is fine. DCS gets its own box eventually. Yes, it’s a 5 position Grayhill key switch (similar to mine, I bought mine in 2014 and it appears to have been discontinued) where all five positions are “on”.

An odd thing I’m noticing in the USB Game Controllers applet in Windows is the switch lights 1 - 5 in this case don’t flicker. However, at times, 1 can remain lit after I switch to 2 - 5. I go back to one and it will works as expected. This is of course intermittent. It does not appear to depend on how fast or slow I turn the key.

I’ll work on getting the Serial.println() added so we can see some text output of what’s going on.

Any other thoughts? Happy to learn better.

Thanks!

Thank you, I´m glad if I can help.

Maybee the signals from the Arduino are so fast, that you can’t see any flicker. To test this you can insert a “Delay (100)” into the “if” statement. (don´t forget to delete it after the test)

You don’t need a second box, you just have to reserve a button with which you tell your program whether it should work in DCS or MSFS mode. Preferably with an LED that indicates the mode. This would be a good practice to get more familiar with Arduino programming.

:wink:Many, but that is going too far here.
Three tips on how I started programming on the Arduino:

  1. When you have a ready-made code, try to understand every single line and write your findings as a comment on the line.
  2. Test, test, test!
    After every change to the code, test what changes during execution. If you change too much at once, it becomes harder and harder to pinpoint the source of the error.
  3. I use Visual Studio together with the Visual Micro extension, in which Arduino programs can be debugged. Very helpful for programming, not very comfortable for debugging, but it works. The familiarization with such an IDE is very time-consuming. You can do without it for the beginning.
    But make yourself familiar with the commands “Serial.print ()” and “Serial.println ()” and incorporate them everywhere in order to follow the change of the values ​​in your program.
    Suggestion: In the “CheckAllButtons” function, whenever the “setButton” command is called, I would output the values ​​of “i”, of “buttbx.key [i] .kchar” and whether a 1 or a 0 is passed. “Kstate” would also be interesting.
    Have fun

Edit: There is another very important point in software development: Keep it simple! (and understandable)

This would be another good projekt, to built easy something with encoders:
But on STM32F103C8 microcontroller hardware: github.com/FreeJoy-Team/

Hi,

Here is some simple code that will work on the Arduino Leonardo, and uses the Joystick and Encoder libraries which can be found here:

https://www.arduino.cc/reference/en/libraries/md_rencoder/

You simply connect the rotary pins to pins 12 & 13 below, and ground the middle pin.
This code will send joystick push’s for right / left rotations, which works well in MSFS.

This is what I’ve been using these libraries for:


// Install the Libraries first
#include <Joystick.h>
#include <MD_REncoder.h>

// set up encoder object
// Define which pins the encoders connect to here i.e Pins 12 & 13

MD_REncoder ROTARY1 = MD_REncoder(12, 13);

// Create the Joystick
Joystick_ Joystick;

void setup() {
  Joystick.begin(); 	//PC now sees Arduino Leo as USB Joystick
  ROTARY1.begin();
}


// the loop function runs over and over again forever
void loop() {


//Scan Rotaries
  uint8_t ROTARY1_IN = ROTARY1.read();

//send a button push when rotary is deteceted - Button 1 = Clockwise, Button2 = Counter_clockwise  
  if (ROTARY1_IN)
  {
    if (ROTARY1_IN == DIR_CW)
    {
            Joystick.setButton(0,1);	// Turn Button 1 on, wait 150ms, then turn off
            delay(150);
            Joystick.setButton(0,0);
    }
    if (ROTARY1_IN == DIR_CCW)
    {
            Joystick.setButton(1,1);	// Turn Button 2 on, wait 150ms, then turn off
            delay(150);
            Joystick.setButton(1,0);
    }
  }

}

For sure. Love Modiflight!

This is the article that help me understand the encoder. It’s such a good explanation that even my old brain could grasp the concept.

1 Like