MHeironimus / ArduinoJoystickLibrary

An Arduino library that adds one or more joysticks to the list of HID devices an Arduino Leonardo or Arduino Micro can support.
GNU Lesser General Public License v3.0
2.07k stars 403 forks source link

Rotary encoder not working #162

Closed theblackhawk76 closed 3 years ago

theblackhawk76 commented 3 years ago

Hello,

I am working on a project for a six axis throttle quadrant with 23 buttons, 6 potentiometers and 1 rotary encoder. I use the arduino leonardo board and Arduino IDE version 1.8.13.

The code which my project is based on was made by AMstudio: https://github.com/AM-STUDIO/32-FUNCTION-BUTTON-BOX https://github.com/AM-STUDIO/USB-5-AXIS-CONTROLLER

The 6 axis and 23 buttons are working fine, however the rotary encoder is not. The encoder:
https://www.amazon.de/Rotary-Encoder-Schalter-Drehschalter-Keyswitch/dp/B0085I4D5C/ref=sr_1_14?__mk_de_DE=%C3%85M%C3%85%C5%BD%C3%95%C3%91&dchild=1&keywords=rotary+encoder&qid=1598363760&sr=8-14

The code:

/BUTTON BOX 
//USE w ProMicro
//Tested in WIN10 + Assetto Corsa
//AMSTUDIO
//20.8.17

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

#define ENABLE_PULLUPS
#define NUMROTARIES 1
#define NUMBUTTONS 24
#define NUMROWS 5
#define NUMCOLS 5

byte buttons[NUMROWS][NUMCOLS] = {
  {0,1,2,3,4},
  {5,6,7,8,9},
  {10,11,12,13,14},
  {15,16,17,18,19},
  {20,21,22,23},
};

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

rotariesdef rotaries[NUMROTARIES] {
  {0,1,24,25,0},
  //{2,3,26,27,0},
  //{4,5,28,29,0},
  //{6,7,30,31,0},
};

#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

byte rowPins[NUMROWS] = {21,20,19,18,15}; 
byte colPins[NUMCOLS] = {14,16,10,9,8}; 

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

Joystick_ Joystick(JOYSTICK_DEFAULT_REPORT_ID, 
  JOYSTICK_TYPE_JOYSTICK, 32, 0,
  false, false, false, false, false, false,
  false, false, false, false, false);

void setup() {
  Joystick.begin();
  rotary_init();}

void loop() { 

  CheckAllEncoders();

  CheckAllButtons();

}

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) {
   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) {
  for (int i=0;i<NUMROTARIES;i++) {
    unsigned char result = rotary_process(i);
    if (result == DIR_CCW) {
      Joystick.setButton(rotaries[i].ccwchar, 1); delay(50); Joystick.setButton(rotaries[i].ccwchar, 0);
    };
    if (result == DIR_CW) {
      Joystick.setButton(rotaries[i].cwchar, 1); delay(50); Joystick.setButton(rotaries[i].cwchar, 0);
    };
  }
}

I would very much appreciate your help to get the rotary encoder working. Right now, when I put it into RX1, GND and TX1, it does not send any signal!

Thank you!

Tarun-Narain commented 3 years ago

Hi @theblackhawk76 The Encoder here is a 20CPR (same as yours i think), configured for 900 degree steering.```

 #include <Joystick.h>
 #define encoder_outputA 6
 #define encoder_outputB 7
 int encoder_counter = 0; 
 int encoderState;
 int encoderLastState;  

  Joystick_ Joystick(JOYSTICK_DEFAULT_REPORT_ID, 
  JOYSTICK_TYPE_MULTI_AXIS, 1, 0,
  false, false, false, false, false, false,
  false, false, false, false, true);

 void setup() { 
   Serial.begin (9600);
   pinMode (encoder_outputA,INPUT);
   pinMode (encoder_outputB,INPUT);
   encoderLastState = digitalRead(encoder_outputA);   
   Joystick.setSteeringRange(0, 300);
   Joystick.begin();
 } 
 void loop() { 
   encoderState = digitalRead(encoder_outputA);
   if (encoderState != encoderLastState){     
     if (digitalRead(encoder_outputB) != encoderState && encoder_counter<30) { 
       encoder_counter ++;
     } else if (encoder_counter>-30 ){
       encoder_counter --;
     }
     Joystick.setSteering((encoder_counter+30)*5);//-30 to 30 -> 0 to 300 mapping
     Serial.print("Position: ");
     Serial.println(encoder_counter);
   } 
   encoderLastState = encoderState;
 }
theblackhawk76 commented 3 years ago

Hi @Tarun-Narain , your code did sadly not work for me. This is my updated base code, may you try to implement yours, or do you have any idea, what may be the issue? The encoder is not a 20CPR I think.

//Simple buttonbox sketch
//Supports up to 25 buttons and up to 4 encoders
//version 0.1 by TOPMO3
//
//
//Arduino IDE 1.6.6 (or above) !
//
//Joystick library from Matthew Heironimus, https://github.com/MHeironimus/ArduinoJoystickLibrary
//
//Encoders code from Ben Buxton
//More info: http://www.buxtronix.net/2011/10/rotary-encoders-done-properly.html
//
//Thank you guys! :)
//

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

// Create Joystick
Joystick_ Joystick;

int zAxis_ = 0;
int RxAxis_ = 0;
int RyAxis_ = 0;
int RzAxis_ = 0;
int Throttle_ = 0;
int xAxis_ = 0;

const bool initAutoSendState = true;

#define ENABLE_PULLUPS
#define NUMROTARIES 1
#define NUMBUTTONS 24
#define NUMROWS 5
#define NUMCOLS 5

//define the symbols on the buttons of the keypads
byte buttons[NUMROWS][NUMCOLS] = {
{0,1,2,3,4,},
{5,6,7,8,9,},
{10,11,12,13,14,},
{15,16,17,18,19,},
{20,21,22,23,},
};

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

rotariesdef rotaries[NUMROTARIES] {
{0,1,24,25,0},

};

#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

byte rowPins[NUMROWS] = {12,11,10,9,8}; //connect to the row pinouts of the keypad //changed both
byte colPins[NUMCOLS] = {6,5,4,3,2}; //connect to the column pinouts of the keypad

//initialize an instance of class NewKeypad
Keypad buttbx = Keypad( makeKeymap(buttons), rowPins, colPins, NUMROWS, NUMCOLS);

void setup() {
Joystick.begin();
rotary_init();
}

void loop() {

CheckAllEncoders();

CheckAllButtons();

zAxis_ = analogRead(A0);
zAxis_ = map(zAxis_,0,1023,0,255);
Joystick.setZAxis(zAxis_);

RxAxis_ = analogRead(A1);
RxAxis_ = map(RxAxis_,0,1023,0,255);
Joystick.setRxAxis(RxAxis_);

RyAxis_ = analogRead(A2);
RyAxis_ = map(RyAxis_,0,1023,0,255);
Joystick.setRyAxis(RyAxis_);

RzAxis_ = analogRead(A3);
RzAxis_ = map(RzAxis_,1023,0,255,0);
Joystick.setRzAxis(RzAxis_);

Throttle_ = analogRead(A4);
Throttle_ = map(Throttle_,1023,0,255,0);
Joystick.setThrottle(Throttle_);

xAxis_ = analogRead(A5);
xAxis_ = map(xAxis_,1023,0,255,0);
Joystick.setXAxis(xAxis_);

delay (50);

}

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) {
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) {
for (int i=0;i<NUMROTARIES;i++) {
unsigned char result = rotary_process(i);
if (result == DIR_CCW) {
Joystick.setButton(rotaries[i].ccwchar, 1); delay(50); Joystick.setButton(rotaries[i].ccwchar, 0);
};
if (result == DIR_CW) {
Joystick.setButton(rotaries[i].cwchar, 1); delay(50); Joystick.setButton(rotaries[i].cwchar, 0);
};
}
}
Tarun-Narain commented 3 years ago

hi @theblackhawk76 , can you try just reading the rotary values and printing it, and on which platform/game are you testing this code? , I tested my code with euro truck simulator.

i suggest you just try the rotary code seprately first before integrating it in your application.

theblackhawk76 commented 3 years ago

Hello @Tarun-Narain , I dont know how and I dont think it would help....I am using it on DCS and MFS2020, my joystick singals except the rotary are captured by the windows system as joystick buttons. I sadly cannot seperate it from my system anymore.

CraigB-spinner commented 3 years ago

Rotary encoders must use interrupts to catch all pulses, or quadrature edges. You can not poll fast enough for smooth movement since you'll miss encoder pulses. Button are different as they are linked to your slow finger. Drop the use of Digital Reads & Writes as they are upto x 30 slower than Direct Bit Port manipulation with bit-wise logic. (eg. (PIND & 0b00010000) >> 4 ) Watch some Ben Heck YouTube videos showing bit-wise processing with micro-controllers https://www.youtube.com/watch?v=PZsWqOuJFKI&t=2677s There are other videos on rotary encoders and interrupts. Remember, you are writing a device driver, processing speed is critical.

ttait-vantim commented 3 years ago

I recommend using Paul Stoffregen's encoder library, it is quite fast especially if you use at least 1 interrupt input. It works on all Arduino's not just the "teensy":

https://www.pjrc.com/teensy/td_libs_Encoder.html

Also, I have found the mechanical rotary encoders very glitchy unless very expensive, an RC filter may help.

Tim

theblackhawk76 commented 3 years ago

Thank you for the responses! I decided to use MMJoy2 now, since it fits better to my project and gives me much more possibilities regarding the use of rotary encoders.

CraigB-spinner commented 3 years ago

Paul's encoder library would make life easier; however, doing it more direct is still faster. No stack overhead push/pop data to call a function/subroutine. That is the issue with Digital Reads & Writes - overhead. The closer to machine code the faster and nastier things get. Craig On Wednesday, December 30, 2020, 02:40:27 p.m. EST, theblackhawk76 notifications@github.com wrote:

Closed #162.

— You are receiving this because you commented. Reply to this email directly, view it on GitHub, or unsubscribe.