Use a rotary encoder as trim wheel for flight simulator

Hello,

What code should I use to make a elevator trim wheel for flight simulators with a rotary encoder?
E.g. clockwise, joystick button 4 should be triggered per encoder click, in the other direction joystick button 5 per click.

This is the orig. code to test the the functionality and it works flawlessly.

int encoderPinA = 6;
int encoderPinB = 7;
int encoderPos = 0;
int encoderPinALast = LOW;
int encoderPinANow = LOW;

void setup() {
  pinMode (encoderPinA, INPUT_PULLUP);
  pinMode (encoderPinB, INPUT_PULLUP);
  Serial.begin (115200);
}

void loop() {
  encoderPinANow = digitalRead(encoderPinA);
  if ((encoderPinALast == HIGH) && (encoderPinANow == LOW)) {
    if (digitalRead(encoderPinB) == HIGH) {
      encoderPos++;
    } else {
      encoderPos--;
    }
    Serial.println(encoderPos);
  }
  encoderPinALast = encoderPinANow;
}

Here I've replaced one line to trigger a joystick button, but it doesn't work. It's just for testing and does not even trigger the button and stay in high state as it should once the encoder condition is met.

//Encoder Trimmer
  encoderPinANow = digitalRead(encoderPinA);
  if ((encoderPinALast == HIGH) && (encoderPinANow == LOW)) {
    if (digitalRead(encoderPinB) == HIGH) {
      Joystick.setButton(4, HIGH);
    } else {
      Joystick.setButton(5, HIGH);
  }
  encoderPinALast = encoderPinANow;
}

I'm still new to Arduino and I'm sure there's something I haven't considered or overlooked.
I am grateful for any help. :slight_smile:

Please post your entire code including the Joystick object declaration.

As I said, this is only a dirty code just for test purposes with a breadboard. So please ignore the other spaghetti codes in there for this moment, they work perfect so far for the other things :slight_smile: . I will sort it later! It's only about the encoder I don't understand.

// Requires Arduino Joystick Library https://github.com/MHeironimus/ArduinoJoystickLibrary
#include <Joystick.h>

//JOYSTICK SETTINGS
Joystick_ Joystick(JOYSTICK_DEFAULT_REPORT_ID,
  JOYSTICK_TYPE_JOYSTICK,
  8, //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
  true, // z axis
  true, // rx axis
  false, // ry axis
  false, // rz axis
  false, // rudder
  false, // throttle
  false, // accelerator
  false, // brake
  false); // steering wheel

 
// put the max and min values from the analogRead in these arrays
// these are translated to a range of 0 - 1023
int axisLimits0[] = {171+40, 878};
int axisLimits1[] = {111, 843-50};
int axisLimits2[] = {0, 1023};
int axisLimits3[] = {0, 1023};

// turn axes on or off by setting these variables
bool a0Used = true;
bool a1Used = true;
bool a2Used = false;
bool a3Used = false;

// setting mode prints the pin value and translated value to the serial monitor
// int setting = -1; // no printing to the serial monitor
// int setting = 2; // values 0 - 3, print the pin values to the serial monitor
int setting = -1;

int currentButtonState0;
int lastButtonState0;
int currentButtonState1;
int lastButtonState1;
int currentButtonState2;
int lastButtonState2;
int currentButtonState3;
int lastButtonState3;
int currentButtonState4;
int lastButtonState4;
int currentButtonState5;
int lastButtonState5;
int currentButtonState6;
int lastButtonState6;
int encoderPinA = 6;
int encoderPinB = 7;
int encoderPos = 0;
int encoderPinALast = LOW;
int encoderPinANow = LOW;

void setup() {
  pinMode(2, INPUT_PULLUP);
  pinMode(3, INPUT_PULLUP);
  pinMode(4, INPUT_PULLUP);
  pinMode(5, INPUT_PULLUP);
  pinMode(6, INPUT_PULLUP);
  pinMode(7, INPUT_PULLUP);
  pinMode(8, INPUT_PULLUP);
  pinMode(9, INPUT_PULLUP); 
  if(a0Used) pinMode(A0, INPUT_PULLUP);
  if(a1Used) pinMode(A1, INPUT_PULLUP);
  if(a2Used) pinMode(A2, INPUT_PULLUP);
  if(a3Used) pinMode(A3, INPUT_PULLUP);
  

// Initialize Joystick Library
  Joystick.begin();
  Joystick.setZAxisRange(0, 1024); 
  Joystick.setRxAxisRange(1024, 0);
  
  if(setting >= 0) Serial.begin(9600);

}

void loop() {

//Encoder Trimmer
  encoderPinANow = digitalRead(encoderPinA);
  if ((encoderPinALast == HIGH) && (encoderPinANow == LOW)) {
    if (digitalRead(encoderPinB) == HIGH) {
      Joystick.setButton(4, HIGH);
    } else {
      Joystick.setButton(5, HIGH);
  }
  encoderPinALast = encoderPinANow;
}  

  int value = 0;
  int pos = 0;

  if(a0Used){
    value = analogRead(A0);
    pos = translateValue(value, axisLimits0[0], axisLimits0[1]);
    Joystick.setZAxis(pos);
    if(setting == 0) settingPrint(value, pos);
  }
  
  if(a1Used){
    value = analogRead(A1);
    pos = translateValue(value, axisLimits1[0], axisLimits1[1]);
    Joystick.setRxAxis(pos);
    if(setting == 1) settingPrint(value, pos);
  }

  if(a2Used){
    value = analogRead(A2);
    pos = translateValue(value, axisLimits2[0], axisLimits2[1]);
    Joystick.setRyAxis(pos);
    if(setting == 2) settingPrint(value, pos);
  }
  
  if(a3Used){
    value = analogRead(A3);
    pos = translateValue(value, axisLimits3[0], axisLimits3[1]);
    Joystick.setRzAxis(pos);
    if(setting == 3) settingPrint(value, pos);
  }

// Thrust Revers
if (analogRead(A0) < 200) { 
  Joystick.setButton(6, HIGH); 
} else {
  Joystick.setButton(6, LOW); 
}
if (analogRead(A1) > 800) { 
  Joystick.setButton(7, HIGH); 
} else {
  Joystick.setButton(7, LOW); 
}


// Read Switches
int currentButtonState0 = !digitalRead(2); // Button 0
  if (currentButtonState0 != lastButtonState0)
  {
  Joystick.setButton(0, currentButtonState0);
  lastButtonState0 = currentButtonState0;
  }



  delay(5);
}

int translateValue(int v, int f1, int f2){
  // translates values to a 0 - 1023 range
  int result = 0;
  int start = 0;
  float range = 0;
  
  if(f1 < f2){
    start = f1;
    range = f2 - f1;
  }
  else{
    start = f2;
    range = f1 - f2;
  }
  
  result = (v - start) * (1023 / range);

  if(result < 0) result = 0;
  if(result > 1023) result = 1023;
  
  return result;
}

void settingPrint(int value, int pos){
  Serial.print(value); 
  Serial.print(" "); 
  Serial.println(pos);
}

I've never used the library you're using so this is probably overcomplicating things.

Is use a state change logic similar to yours but include a flag and timer for each so that after being pressed a button is released a short time later (in this case, 20mS). I don't know if that's too short a press or not.

Anyway, give this a try:

// Requires Arduino Joystick Library https://github.com/MHeironimus/ArduinoJoystickLibrary
#include <Joystick.h>

//JOYSTICK SETTINGS
Joystick_ Joystick( JOYSTICK_DEFAULT_REPORT_ID,
                    JOYSTICK_TYPE_JOYSTICK,
                    8, //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
                    true, // z axis
                    true, // rx axis
                    false, // ry axis
                    false, // rz axis
                    false, // rudder
                    false, // throttle
                    false, // accelerator
                    false, // brake
                    false ); // steering wheel
 
// put the max and min values from the analogRead in these arrays
// these are translated to a range of 0 - 1023
int axisLimits0[] = {171+40, 878};
int axisLimits1[] = {111, 843-50};
int axisLimits2[] = {0, 1023};
int axisLimits3[] = {0, 1023};

// turn axes on or off by setting these variables
bool a0Used = true;
bool a1Used = true;
bool a2Used = false;
bool a3Used = false;

// setting mode prints the pin value and translated value to the serial monitor
// int setting = -1; // no printing to the serial monitor
// int setting = 2; // values 0 - 3, print the pin values to the serial monitor
int setting = -1;

int currentButtonState0;
int lastButtonState0;
int currentButtonState1;
int lastButtonState1;
int currentButtonState2;
int lastButtonState2;
int currentButtonState3;
int lastButtonState3;
int currentButtonState4;
int lastButtonState4;
int currentButtonState5;
int lastButtonState5;
int currentButtonState6;
int lastButtonState6;

int encoderPinA = 6;
int encoderPinB = 7;
//
int encoderPos = 0;
int encoderPinALast = LOW;
int encoderPinANow = LOW;

#define NUM_BUTTONS     2
#define TRIM_UP         0
#define TRIM_DOWN       1

#define BUTTON_PRESS_TIME       20ul            //mS        time period a button is "pressed"

typedef struct
{
    uint8_t     buttonNum;
    bool        pressFlag;
    uint32_t    timePress;
    
}JoyButton_t;

JoyButton_t JoyButtons[NUM_BUTTONS] = 
{
    {
        .buttonNum = 4,
        .pressFlag = false,
        .timePress = 0ul
    },
    {
        .buttonNum = 5,
        .pressFlag = false,
        .timePress = 0ul
    }

};

void setup() 
{
    pinMode(2, INPUT_PULLUP);
    pinMode(3, INPUT_PULLUP);
    pinMode(4, INPUT_PULLUP);
    pinMode(5, INPUT_PULLUP);
    pinMode(6, INPUT_PULLUP);
    pinMode(7, INPUT_PULLUP);
    pinMode(8, INPUT_PULLUP);
    pinMode(9, INPUT_PULLUP);
    
    if(a0Used) 
        pinMode(A0, INPUT_PULLUP);
    if(a1Used) 
        pinMode(A1, INPUT_PULLUP);
    if(a2Used) 
        pinMode(A2, INPUT_PULLUP);
    if(a3Used) 
        pinMode(A3, INPUT_PULLUP);
 

    // Initialize Joystick Library
    Joystick.begin();
    Joystick.setZAxisRange(0, 1024);
    Joystick.setRxAxisRange(1024, 0);
 
    if(setting >= 0) 
        Serial.begin(9600);

}//setup

void loop() 
{
    static uint32_t
        timeEnc;
    uint32_t
        timeNow = millis();

    //read the encoder every 50mS
    if( (timeNow - timeEnc) >= 50ul )
    {
        timeEnc = timeNow;
        
        encoderPinANow = digitalRead( encoderPinA );
        if( encoderPinANow != encoderPinALast )
        {
            encoderPinALast = encoderPinANow;
            if( encoderPinANow == HIGH )
            {
                if( digitalRead( encoderPinB ) == HIGH )
                {
                    //if A has transitioned high and B is high, trim 'up'
                    Joystick.pressButton( JoyButtons[TRIM_UP].buttonNum );
                    //set flag indicating pressed and log the time of the press
                    JoyButtons[TRIM_UP].pressFlag = true;
                    JoyButtons[TRIM_UP].timePress = millis();
                    
                }//if
                else
                {
                    //A has transitioned high and B is low, trim 'down'
                    Joystick.pressButton( JoyButtons[TRIM_DOWN].buttonNum );
                    JoyButtons[TRIM_DOWN].pressFlag = true;
                    JoyButtons[TRIM_DOWN].timePress = millis();
                    
                }//else
                    
            }//if
            
        }//if
                
    }//if

    //each pass check for pressed buttons and it's time they need to be released
    for( uint8_t i=0; i<NUM_BUTTONS; i++ )
    {
        if( JoyButtons[i].pressFlag == true )
        {
            if( (millis() - JoyButtons[i].timePress) >= BUTTON_PRESS_TIME )
            {
                Joystick.releaseButton(JoyButtons[i].buttonNum );
                JoyButtons[i].pressFlag = false;
                
            }//if
            
        }//if
        
    }//for

}//loop

adding more and more code without cleaning it up seems to be a quick progress forward.
In the end it will slow down finishing your project because you need so much time for finding the bugs inside your big code. But it seems that some people enjoy this

best regards Stefan

Blackfin:
I've never used the library you're using so this is probably overcomplicating things.

Is use a state change logic similar to yours but include a flag and timer for each so that after being pressed a button is released a short time later (in this case, 20mS). I don't know if that's too short a press or not.

Anyway, give this a try:

Thank you. Unfortunately, that didn't work either. Both buttons flickers sometimes when turned in both directions. I have also tried different timings.
Maybe it is not a good idea to use an encoder for this task. Delays would have to be built into the code flow.

Give this a try.

Here I use an ISR to count encoder ticks only; I don't try to press and release the buttons within the encoder tick processing. Instead, in the ISR I just bump a "head" counter, one for each direction, and the EncoderTrim() function will execute however many button presses are needed to get a "tail" counter to catch up to the head.

// Requires Arduino Joystick Library https://github.com/MHeironimus/ArduinoJoystickLibrary
#include <Joystick.h>

//JOYSTICK SETTINGS
Joystick_ Joystick( JOYSTICK_DEFAULT_REPORT_ID,
                    JOYSTICK_TYPE_JOYSTICK,
                    8, //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
                    true, // z axis
                    true, // rx axis
                    false, // ry axis
                    false, // rz axis
                    false, // rudder
                    false, // throttle
                    false, // accelerator
                    false, // brake
                    false ); // steering wheel
 
// put the max and min values from the analogRead in these arrays
// these are translated to a range of 0 - 1023
int axisLimits0[] = {171+40, 878};
int axisLimits1[] = {111, 843-50};
int axisLimits2[] = {0, 1023};
int axisLimits3[] = {0, 1023};

// turn axes on or off by setting these variables
bool a0Used = true;
bool a1Used = true;
bool a2Used = false;
bool a3Used = false;

// setting mode prints the pin value and translated value to the serial monitor
// int setting = -1; // no printing to the serial monitor
// int setting = 2; // values 0 - 3, print the pin values to the serial monitor
int setting = -1;

int currentButtonState0;
int lastButtonState0;
int currentButtonState1;
int lastButtonState1;
int currentButtonState2;
int lastButtonState2;
int currentButtonState3;
int lastButtonState3;
int currentButtonState4;
int lastButtonState4;
int currentButtonState5;
int lastButtonState5;
int currentButtonState6;
int lastButtonState6;

int encoderPinA = 6;
int encoderPinB = 7;
//
int encoderPos = 0;
int encoderPinALast = LOW;
int encoderPinANow = LOW;

#define NUM_BUTTONS     2
#define TRIM_UP         0
#define TRIM_DOWN       1

#define K_JOY_BTN_PRESS_DURATION        75ul        //mS        duration of button press
#define K_JOY_BTN_RELEASE_DURATION      100ul       //mS        time after release before allowing another press

enum statesJoy
{
    ST_JOY_IDLE = 0,
    ST_JOY_ASSERT_PRESS,
    ST_JOY_TIME_PRESS,
    ST_JOY_TIME_RELEASE     
};

typedef struct
{
    volatile uint16_t   headCount;
    uint16_t            tailCount;
    uint8_t             buttonNum;
    uint8_t             pressState;
    uint32_t            timePress;
   
}JoyButton_t;

JoyButton_t JoyButtons[NUM_BUTTONS] =
{
    {
        .headCount = 0,
        .tailCount = 0,
        .buttonNum = 4,
        .pressState = statesJoy::ST_JOY_IDLE,
        .timePress = 0ul
    },
    {
        .headCount = 0,
        .tailCount = 0,
        .buttonNum = 5,
        .pressState = statesJoy::ST_JOY_IDLE,
        .timePress = 0ul
    }

};

void setup()
{
    pinMode(2, INPUT_PULLUP);
    pinMode(3, INPUT_PULLUP);
    pinMode(4, INPUT_PULLUP);
    pinMode(5, INPUT_PULLUP);
    pinMode(6, INPUT_PULLUP);
    pinMode(7, INPUT_PULLUP);
    pinMode(8, INPUT_PULLUP);
    pinMode(9, INPUT_PULLUP);
   
    if(a0Used)
        pinMode(A0, INPUT_PULLUP);
    if(a1Used)
        pinMode(A1, INPUT_PULLUP);
    if(a2Used)
        pinMode(A2, INPUT_PULLUP);
    if(a3Used)
        pinMode(A3, INPUT_PULLUP);
 

    // Initialize Joystick Library
    Joystick.begin();
    Joystick.setZAxisRange(0, 1024);
    Joystick.setRxAxisRange(1024, 0);
 
    if(setting >= 0)
        Serial.begin(9600);

    //OP has encoder B on pin 7; this pin is available for attachInterrupt() but is also
    //phase B. May need to change UP/DOWN counts in ISR because this will affect direction
    attachInterrupt( digitalPinToInterrupt(encoderPinB), isrEncoder, RISING );

}//setup

void loop()
{
    EncoderTrims();
    
}//loop

void EncoderTrims( void )
{
    //process each trim button's state machine
    uint16_t
        currHead;
    static uint8_t
        idx = 0;
    static uint32_t
        timeNow = millis();

    noInterrupts();
    //headCount is updated in ISR to halt interrupts and get its current value
    currHead = JoyButtons[idx].headCount;
    interrupts();

    switch( JoyButtons[idx].pressState )
    {
        case    statesJoy::ST_JOY_IDLE:
            //if the head is not equal to the tail, interrupt(s) have occured
            //move to press state
            if( JoyButtons[idx].tailCount != currHead )
                JoyButtons[idx].pressState = statesJoy::ST_JOY_ASSERT_PRESS;
                
        break;

        case    statesJoy::ST_JOY_ASSERT_PRESS:
            //press the button and get a time stamp for duration timing
            Joystick.pressButton( JoyButtons[TRIM_DOWN].buttonNum );
            JoyButtons[idx].timePress = timeNow;
            JoyButtons[idx].pressState = statesJoy::ST_JOY_TIME_PRESS;
                
        break;

        case    statesJoy::ST_JOY_TIME_PRESS:
            //when press time has elapsed...
            if( (timeNow - JoyButtons[idx].timePress) >= K_JOY_BTN_PRESS_DURATION )
            {
                //...release the button and grab time stamp for timing the release duration
                Joystick.releaseButton( JoyButtons[TRIM_DOWN].buttonNum );
                JoyButtons[idx].timePress = timeNow;
                JoyButtons[idx].pressState = statesJoy::ST_JOY_TIME_RELEASE;
                
            }//if            
        break;

        case    statesJoy::ST_JOY_TIME_RELEASE:
            //when released-duration has expired...
            if( (timeNow - JoyButtons[idx].timePress) >= K_JOY_BTN_RELEASE_DURATION )
            {
                //bump the tail count
                JoyButtons[idx].tailCount++;
                //if the tail count == head, we've caught up and can go back to idle state
                //if tail still does not equal head value, go back and press the associated button again
                JoyButtons[idx].pressState = (JoyButtons[idx].tailCount == currHead) ? statesJoy::ST_JOY_IDLE : statesJoy::ST_JOY_ASSERT_PRESS;
                
            }//if
        break;
               
    }//break;

    //each pass through we do one button, indicated by idx
    //bump the idx count for the next button but limit
    //the count to the number of buttons to process
    idx++;
    if( idx == NUM_BUTTONS )
        idx = 0;
    
}//EncoderTrims

void isrEncoder( void )
{
    //note that OPs wiring puts B on pin 7 so we check phase A here
    //may need to switch TRIM_UP and TRIM_DOWN here to get direction right
    if( digitalRead( encoderPinA ) == HIGH )
        //bump head count for associated trim direction
        JoyButtons[TRIM_UP].headCount++;
    else
        JoyButtons[TRIM_DOWN].headCount++;
    
}//isrEncoder

By using a rotary encoder, how will you know when the trim is set to zero?

Paul

Using a 737-800 as an example, the stabilizer trim position is shown with an indicator pointer next to the trim wheel:

When used, the trim wheel just spins (manually or via A/P) to move the pointer as required.

Blackfin:
Give this a try.

Here I use an ISR to count encoder ticks only; I don't try to press and release the buttons within the encoder tick processing. Instead, in the ISR I just bump a "head" counter, one for each direction, and the EncoderTrim() function will execute however many button presses are needed to get a "tail" counter to catch up to the head.

When i turn the encoder with this code, button 5 remains pressed and nothing changes.

jayjoe77:
When i turn the encoder with this code, button 5 remains pressed and nothing changes.

Oops. Copy paste error on my part:

// Requires Arduino Joystick Library https://github.com/MHeironimus/ArduinoJoystickLibrary
#include <Joystick.h>

//JOYSTICK SETTINGS
Joystick_ Joystick( JOYSTICK_DEFAULT_REPORT_ID,
                    JOYSTICK_TYPE_JOYSTICK,
                    8, //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
                    true, // z axis
                    true, // rx axis
                    false, // ry axis
                    false, // rz axis
                    false, // rudder
                    false, // throttle
                    false, // accelerator
                    false, // brake
                    false ); // steering wheel
 
// put the max and min values from the analogRead in these arrays
// these are translated to a range of 0 - 1023
int axisLimits0[] = {171+40, 878};
int axisLimits1[] = {111, 843-50};
int axisLimits2[] = {0, 1023};
int axisLimits3[] = {0, 1023};

// turn axes on or off by setting these variables
bool a0Used = true;
bool a1Used = true;
bool a2Used = false;
bool a3Used = false;

// setting mode prints the pin value and translated value to the serial monitor
// int setting = -1; // no printing to the serial monitor
// int setting = 2; // values 0 - 3, print the pin values to the serial monitor
int setting = -1;

int currentButtonState0;
int lastButtonState0;
int currentButtonState1;
int lastButtonState1;
int currentButtonState2;
int lastButtonState2;
int currentButtonState3;
int lastButtonState3;
int currentButtonState4;
int lastButtonState4;
int currentButtonState5;
int lastButtonState5;
int currentButtonState6;
int lastButtonState6;

int encoderPinA = 6;
int encoderPinB = 7;
//
int encoderPos = 0;
int encoderPinALast = LOW;
int encoderPinANow = LOW;

#define NUM_BUTTONS     2
#define TRIM_UP         0
#define TRIM_DOWN       1

#define K_JOY_BTN_PRESS_DURATION        75ul        //mS        duration of button press
#define K_JOY_BTN_RELEASE_DURATION      100ul       //mS        time after release before allowing another press

enum statesJoy
{
    ST_JOY_IDLE = 0,
    ST_JOY_ASSERT_PRESS,
    ST_JOY_TIME_PRESS,
    ST_JOY_TIME_RELEASE     
};

typedef struct
{
    volatile uint16_t   headCount;
    uint16_t            tailCount;
    uint8_t             buttonNum;
    uint8_t             pressState;
    uint32_t            timePress;
   
}JoyButton_t;

JoyButton_t JoyButtons[NUM_BUTTONS] =
{
    {
        .headCount = 0,
        .tailCount = 0,
        .buttonNum = 4,
        .pressState = statesJoy::ST_JOY_IDLE,
        .timePress = 0ul
    },
    {
        .headCount = 0,
        .tailCount = 0,
        .buttonNum = 5,
        .pressState = statesJoy::ST_JOY_IDLE,
        .timePress = 0ul
    }

};

void setup()
{
    pinMode(2, INPUT_PULLUP);
    pinMode(3, INPUT_PULLUP);
    pinMode(4, INPUT_PULLUP);
    pinMode(5, INPUT_PULLUP);
    pinMode(6, INPUT_PULLUP);
    pinMode(7, INPUT_PULLUP);
    pinMode(8, INPUT_PULLUP);
    pinMode(9, INPUT_PULLUP);
   
    if(a0Used)
        pinMode(A0, INPUT_PULLUP);
    if(a1Used)
        pinMode(A1, INPUT_PULLUP);
    if(a2Used)
        pinMode(A2, INPUT_PULLUP);
    if(a3Used)
        pinMode(A3, INPUT_PULLUP);
 

    // Initialize Joystick Library
    Joystick.begin();
    Joystick.setZAxisRange(0, 1024);
    Joystick.setRxAxisRange(1024, 0);
 
    if(setting >= 0)
        Serial.begin(9600);

    //OP has encoder B on pin 7; this pin is available for attachInterrupt() but is also
    //phase B. May need to change UP/DOWN counts in ISR because this will affect direction
    attachInterrupt( digitalPinToInterrupt(encoderPinB), isrEncoder, RISING );

}//setup

void loop()
{
    EncoderTrims();
    
}//loop

void EncoderTrims( void )
{
    //process each trim button's state machine
    uint16_t
        currHead;
    static uint8_t
        idx = 0;
    static uint32_t
        timeNow = millis();

    noInterrupts();
    //headCount is updated in ISR to halt interrupts and get its current value
    currHead = JoyButtons[idx].headCount;
    interrupts();

    switch( JoyButtons[idx].pressState )
    {
        case    statesJoy::ST_JOY_IDLE:
            //if the head is not equal to the tail, interrupt(s) have occured
            //move to press state
            if( JoyButtons[idx].tailCount != currHead )
                JoyButtons[idx].pressState = statesJoy::ST_JOY_ASSERT_PRESS;
                
        break;

        case    statesJoy::ST_JOY_ASSERT_PRESS:
            //press the button and get a time stamp for duration timing
            Joystick.pressButton( JoyButtons[idx].buttonNum );
            JoyButtons[idx].timePress = timeNow;
            JoyButtons[idx].pressState = statesJoy::ST_JOY_TIME_PRESS;
                
        break;

        case    statesJoy::ST_JOY_TIME_PRESS:
            //when press time has elapsed...
            if( (timeNow - JoyButtons[idx].timePress) >= K_JOY_BTN_PRESS_DURATION )
            {
                //...release the button and grab time stamp for timing the release duration
                Joystick.releaseButton( JoyButtons[idx].buttonNum );
                JoyButtons[idx].timePress = timeNow;
                JoyButtons[idx].pressState = statesJoy::ST_JOY_TIME_RELEASE;
                
            }//if            
        break;

        case    statesJoy::ST_JOY_TIME_RELEASE:
            //when released-duration has expired...
            if( (timeNow - JoyButtons[idx].timePress) >= K_JOY_BTN_RELEASE_DURATION )
            {
                //bump the tail count
                JoyButtons[idx].tailCount++;
                //if the tail count == head, we've caught up and can go back to idle state
                //if tail still does not equal head value, go back and press the associated button again
                JoyButtons[idx].pressState = (JoyButtons[idx].tailCount == currHead) ? statesJoy::ST_JOY_IDLE : statesJoy::ST_JOY_ASSERT_PRESS;
                
            }//if
        break;
               
    }//break;

    //each pass through we do one button, indicated by idx
    //bump the idx count for the next button but limit
    //the count to the number of buttons to process
    idx++;
    if( idx == NUM_BUTTONS )
        idx = 0;
    
}//EncoderTrims

void isrEncoder( void )
{
    //note that OPs wiring puts B on pin 7 so we check phase A here
    //may need to switch TRIM_UP and TRIM_DOWN here to get direction right
    if( digitalRead( encoderPinA ) == HIGH )
        //bump head count for associated trim direction
        JoyButtons[TRIM_UP].headCount++;
    else
        JoyButtons[TRIM_DOWN].headCount++;
    
}//isrEncoder

Sorry, but now both buttons remain HIGH. :slight_smile:

I think I'll abandon the idea of the encoder elevator trimmer and build it with a potentiometer. I don't like the idea of adding unnecessary delay in the loop to achieve this with an encoder.

Thank you very much for your help!

Maybe I've got a crappy encoder with lot of chattering and bounce. I tried with capacitors and interrupt...no success.

I guess with a potentiometer there is still the same problem:
the input must be translated to a number of button-presses where the number depends on the previous position

the "standard-rotary-encoder-libraries fail when mechanical rotary-encoders that do bouncing are used.

If you use a rotary-encoder-library that uses the method table-decoding these encoders work absolute reliably.
I have tested them myself. Such a very cheap mechanial KY40 for $2 works very good with the two code-versions of the code that I have posted in this thread

https://forum.arduino.cc/index.php?topic=717884.0

And aditionally I tested the newEncoder-library from user GVFalvo

here is a democode

/* SingleEncoder.cpp
   Created on: Jul 25, 2018
   Author: GFV
*/
#include "Arduino.h"
#include "NewEncoder.h"

// Pins 2 and 3 should work for many processors, including Uno. See README for meaning of constructor arguments.
// Use FULL_PULSE for encoders that produce one complete quadrature pulse per detnet, such as: https://www.adafruit.com/product/377
// Use HALF_PULSE for endoders that produce one complete quadrature pulse for every two detents, such as: https://www.adafruit.com/product/377
// NewEncoder encoder(IO_Pin_A, IO_Pin_B, MinValue, MaxValue, InitialValue, HALF_PULSE); //HALF_PULSE FULL_PULSE

NewEncoder encoder(2, 3, -20, 20, 0, FULL_PULSE);

int16_t prevEncoderValue;

void setup() {
  int16_t value;

  Serial.begin(115200);
  //delay(2000);
  Serial.println(F("Starting"));
  if (!encoder.begin()) {

    Serial.println(F("Encoder Failed to Start. Check pin assignments and available interrupts. Aborting."));
    while (1) {
    }
  } else {
    value = encoder;
    Serial.print(F("Encoder Successfully Started at value = "));
    Serial.println(value);
  }
}

void loop() {
  int16_t currentValue;
  bool up, down;

  up = encoder.upClick();
  down = encoder.downClick();
  if (up || down) {
    currentValue = encoder;
    if (currentValue != prevEncoderValue) {
      Serial.print(F("Encoder: "));
      Serial.println(currentValue);
      prevEncoderValue = currentValue;
    } else if (up) {
      Serial.println(F("Encoder at upper limit."));
    } else {
      Serial.println(F("Encoder at lower limit."));
    }
  }
}

best regards Stefan

OK, I ran it on my PC with X-Plane running and ironed out a thing or two. Try it now:

// Requires Arduino Joystick Library https://github.com/MHeironimus/ArduinoJoystickLibrary
#include <Joystick.h>

//JOYSTICK SETTINGS
Joystick_ Joystick( JOYSTICK_DEFAULT_REPORT_ID,
                    JOYSTICK_TYPE_JOYSTICK,
                    8, //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
                    true, // z axis
                    true, // rx axis
                    false, // ry axis
                    false, // rz axis
                    false, // rudder
                    false, // throttle
                    false, // accelerator
                    false, // brake
                    false ); // steering wheel
 
// put the max and min values from the analogRead in these arrays
// these are translated to a range of 0 - 1023
int axisLimits0[] = {171+40, 878};
int axisLimits1[] = {111, 843-50};
int axisLimits2[] = {0, 1023};
int axisLimits3[] = {0, 1023};

// turn axes on or off by setting these variables
bool a0Used = true;
bool a1Used = true;
bool a2Used = false;
bool a3Used = false;

// setting mode prints the pin value and translated value to the serial monitor
// int setting = -1; // no printing to the serial monitor
// int setting = 2; // values 0 - 3, print the pin values to the serial monitor
int setting = -1;

int currentButtonState0;
int lastButtonState0;
int currentButtonState1;
int lastButtonState1;
int currentButtonState2;
int lastButtonState2;
int currentButtonState3;
int lastButtonState3;
int currentButtonState4;
int lastButtonState4;
int currentButtonState5;
int lastButtonState5;
int currentButtonState6;
int lastButtonState6;

int encoderPinA = 6;
int encoderPinB = 7;
//
int encoderPos = 0;
int encoderPinALast = LOW;
int encoderPinANow = LOW;

#define NUM_BUTTONS     2
#define TRIM_UP         0
#define TRIM_DOWN       1

#define K_JOY_BTN_PRESS_DURATION        75ul        //mS        duration of button press
#define K_JOY_BTN_RELEASE_DURATION      100ul       //mS        time after release before allowing another press

enum statesJoy
{
    ST_JOY_IDLE = 0,
    ST_JOY_ASSERT_PRESS,
    ST_JOY_TIME_PRESS,
    ST_JOY_TIME_RELEASE     
};

typedef struct
{
    volatile uint16_t   headCount;
    uint16_t            tailCount;
    uint8_t             buttonNum;
    uint8_t             pressState;
    uint32_t            timePress;
   
}JoyButton_t;

JoyButton_t JoyButtons[NUM_BUTTONS] =
{
    {
        .headCount = 0,
        .tailCount = 0,
        .buttonNum = 4,
        .pressState = statesJoy::ST_JOY_IDLE,
        .timePress = 0ul
    },
    {
        .headCount = 0,
        .tailCount = 0,
        .buttonNum = 5,
        .pressState = statesJoy::ST_JOY_IDLE,
        .timePress = 0ul
    }

};

void setup()
{
    pinMode(2, INPUT_PULLUP);
    pinMode(3, INPUT_PULLUP);
    pinMode(4, INPUT_PULLUP);
    pinMode(5, INPUT_PULLUP);
    pinMode(6, INPUT_PULLUP);
    pinMode(7, INPUT_PULLUP);
    pinMode(8, INPUT_PULLUP);
    pinMode(9, INPUT_PULLUP);
   
    if(a0Used)
        pinMode(A0, INPUT_PULLUP);
    if(a1Used)
        pinMode(A1, INPUT_PULLUP);
    if(a2Used)
        pinMode(A2, INPUT_PULLUP);
    if(a3Used)
        pinMode(A3, INPUT_PULLUP);
 

    // Initialize Joystick Library
    Joystick.begin();
    Joystick.setZAxisRange(0, 1024);
    Joystick.setRxAxisRange(1024, 0);
 
    if(setting >= 0)
        Serial.begin(9600);

    //OP has encoder B on pin 7; this pin is available for attachInterrupt() but is also
    //phase B. May need to change UP/DOWN counts in ISR because this will affect direction
    attachInterrupt( digitalPinToInterrupt(encoderPinB), isrEncoder, RISING );

}//setup

void loop()
{
    EncoderTrims();
    
}//loop

void EncoderTrims( void )
{
    //process each trim button's state machine
    uint16_t
        currHead;
    static uint8_t
        idx = 0;
    uint32_t
        timeNow = millis();

    noInterrupts();
    //headCount is updated in ISR to halt interrupts and get its current value
    currHead = JoyButtons[idx].headCount;
    interrupts();

    switch( JoyButtons[idx].pressState )
    {
        case    statesJoy::ST_JOY_IDLE:
            //if the head is not equal to the tail, interrupt(s) have occured
            //move to press state
            if( JoyButtons[idx].tailCount != currHead )
                JoyButtons[idx].pressState = statesJoy::ST_JOY_ASSERT_PRESS;
                
        break;

        case    statesJoy::ST_JOY_ASSERT_PRESS:            
            //press the button and get a time stamp for duration timing
            Joystick.pressButton( JoyButtons[idx].buttonNum );
            JoyButtons[idx].timePress = timeNow;
            JoyButtons[idx].pressState = statesJoy::ST_JOY_TIME_PRESS;
                
        break;

        case    statesJoy::ST_JOY_TIME_PRESS:
            //when press time has elapsed...
            if( (timeNow - JoyButtons[idx].timePress) >= K_JOY_BTN_PRESS_DURATION )
            {
                //...release the button and grab time stamp for timing the release duration
                Joystick.releaseButton( JoyButtons[idx].buttonNum );
                JoyButtons[idx].timePress = timeNow;
                JoyButtons[idx].pressState = statesJoy::ST_JOY_TIME_RELEASE;
                
            }//if            
        break;

        case    statesJoy::ST_JOY_TIME_RELEASE:
            //when released-duration has expired...
            if( (timeNow - JoyButtons[idx].timePress) >= K_JOY_BTN_RELEASE_DURATION )
            {
                //bump the tail count
                JoyButtons[idx].tailCount++;
                //if the tail count == head, we've caught up and can go back to idle state
                //if tail still does not equal head value, go back and press the associated button again
                JoyButtons[idx].pressState = statesJoy::ST_JOY_IDLE;
                
            }//if
        break;
               
    }//break;

    //each pass through we do one button, indicated by idx
    //bump the idx count for the next button but limit
    //the count to the number of buttons to process
    idx++;
    if( idx == NUM_BUTTONS )
        idx = 0;
    
}//EncoderTrims

void isrEncoder( void )
{
    //note that OPs wiring puts B on pin 7 so we check phase A here
    //may need to switch TRIM_UP and TRIM_DOWN here to get direction right
    if( digitalRead( encoderPinA ) == HIGH )
        //bump head count for associated trim direction
        JoyButtons[TRIM_UP].headCount++;
    else
        JoyButtons[TRIM_DOWN].headCount++;
    
}//isrEncoder

Sorry, but that doesn't work either.
Both buttons remain primarily on, but alternately flicker off briefly and are then on again.
Do I need a special library to make this work?

By the way, I'm using the Leonardo Pro Micro.

I stumbled across this code. It allows me to read the encoder and put the trim on a joystick axis. This works great. That's how I'm going to do it. The idea of triggering two buttons is not so good.

Thanks again for your help!

// Robust Rotary encoder reading
//
// Copyright John Main - best-microcontroller-projects.com
//
#define CLK 7
#define DATA 6
#define BUTTON 5
#include <Joystick.h>
Joystick_ Joystick(JOYSTICK_DEFAULT_REPORT_ID,JOYSTICK_TYPE_JOYSTICK,
  0, 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, true,          // No rudder or throttle
  false, false, false);  // No accelerator, brake, or steering
int pin13;
const int16_t MAX_VALUE = 1023;
const int16_t MIN_VALUE = 0;
static uint8_t prevNextCode = 0;
static uint16_t store=0;
int16_t counter = 0;

void setup() {
  pinMode(CLK, INPUT);
  pinMode(CLK, INPUT_PULLUP);
  pinMode(DATA, INPUT);
  pinMode(DATA, INPUT_PULLUP);

  Joystick.setThrottleRange(MIN_VALUE, MAX_VALUE);
  Joystick.begin();
  
  //pinMode(LED, OUTPUT);


}



void loop() {
static int8_t c,val;
  //digitalWrite(LED, HIGH);
  
  

   if( val=read_rotary() ) {
  c +=val;

  if ( prevNextCode==0x0b) {
    
     counter++;
     if(counter > MAX_VALUE){
      counter = MAX_VALUE;
     }
     Joystick.setThrottle(counter);
  }

  if ( prevNextCode==0x07) {
     counter--;
     if(counter < MIN_VALUE){
      counter = MIN_VALUE;
     }
     Joystick.setThrottle(counter);
  }
  
   }
}

// A vald CW or  CCW move returns 1, invalid returns 0.
int8_t read_rotary() {
  static int8_t rot_enc_table[] = {0,1,1,0,1,0,0,1,1,0,0,1,0,1,1,0};

  prevNextCode <<= 2;
  if (digitalRead(DATA)) prevNextCode |= 0x02;
  if (digitalRead(CLK)) prevNextCode |= 0x01;
  prevNextCode &= 0x0f;

   // If valid then store as 16 bit data.
   if  (rot_enc_table[prevNextCode] ) {
  store <<= 4;
  store |= prevNextCode;
  //if (store==0xd42b) return 1;
  //if (store==0xe817) return -1;
  if ((store&0xff)==0x2b) return -1;
  if ((store&0xff)==0x17) return 1;
   }
   return 0;
}

Copy / past programming, using code you don't understand, and hoping to "stumble across" code that works for you are not effective techniques for moving your project along. You'd be much better off studying the code until you understand why it does or does not work and then go from there.

Well, regardless of whether you use it I'm going to get a KY-040 to debug here :slight_smile:

As reported by X-Plane 11 it seemed to work when just poking wires into the pins of my Leonardo R3.

I'm interested to reproduce the failure you report and to understand why it might be doing that. I also wouldn't mind scoping the phases to see just how noisy they are. Can they be worse than jamming wires into pins?