Newbie needs help with encoders

Helly everybody,
I searched a lot about my problem and while I was searching i learned a lot but I still have a small problem. I am using an Arduino Leonardo with CTS288 rotary encoders (2bit gray code). First the sketch only noticed every fourth "click" of the encoder. After hours of testing and searching the internet I made it to a point where the sketch reads every second click of the encoder. So I'm thinking we are in HALF_STEP now reading 1-1 and 0-0 positions of the encoder. Can someoene pls have a look on my code and tell me how it is possible to read all the clicks of my rotary? I am absolutely new to Arduino. The Sketch is for a buttonbox, 10 buttons, 2 rotaries.

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

#define ENABLE_PULLUPS
#define NUMROTARIES 4
#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,10,11,0},
  {2,3,12,13,0},
  {4,5,28,29,0},
  {6,7,30,31,0},
};

#define DIR_CCW 0x10
#define DIR_CW 0x20
#define R_START 0x0

#define 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[7][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},
};


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, 14, 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);
    };
  }
}

even when turning an encoder by hand i found that an interrupt is needed to detect all transitions and track position.

#include <Arduino.h>

#include "encoder.h"
#include "pins.h"

// -----------------------------------------------------------------------------
//  table containing action indexed by inputs BA and state

typedef enum { ___, For, Rev, Skp, ActSize } Act_t;

const char* actStr [] = { "___", "For", "Rev", "Skp", "ActSize" };

Act_t  encAct [ActSize][ActSize] = {
// state  00   01   10   11      inputs
       { ___, Rev, For, Skp },  // 00
       { For, ___, Skp, Rev },  // 01
       { Rev, Skp, ___, For },  // 10
       { Skp, For, Rev, ___ },  // 11
};

int encAst  = 0;
int encBst  = 0;

// ------------------------------------------------
// read brake
inline void readEncoder (
    byte  dt,
    byte  clk,
    int & brkPos,
    int & encSt )
{
    byte  val = digitalRead (dt) << 1 | digitalRead (clk);

    switch (encAct [val][encSt])  {
    case For:
        brkPos++;
        break;

    case Rev:
        brkPos--;
        break;

    default:
        break;
    }

    encSt = val;

 // digitalWrite (LED, ! digitalRead (LED));
}

// -------------------------------------
void IRAM_ATTR isrEncA (void)
{
    readEncoder (Enc_A_Dt, Enc_A_Clk, encApos, encAst);
}

// -------------------------------------
void IRAM_ATTR isrEncB (void)
{
    readEncoder (Enc_B_Dt, Enc_B_Clk, encBpos, encBst);
}

// -------------------------------------
void
encoderInit (void)
{
    // encoders
    pinMode (Enc_A_Clk, INPUT_PULLUP);
    pinMode (Enc_A_Dt,  INPUT_PULLUP);

    attachInterrupt (Enc_A_Clk, isrEncA, CHANGE);
    attachInterrupt (Enc_A_Dt,  isrEncA, CHANGE);

    pinMode (Enc_B_Clk, INPUT_PULLUP);
    pinMode (Enc_B_Dt,  INPUT_PULLUP);

    attachInterrupt (Enc_B_Clk, isrEncB, CHANGE);
    attachInterrupt (Enc_B_Dt,  isrEncB, CHANGE);
}

OK, and how to add these interrupts to my sketch?

see attachInterrupt() in the posted code

I feel so stupid :smiley:
I did this but ending up with an error

void rotary_init() {
  for (int i=0;i<NUMROTARIES;i++) {
    pinMode(rotaries[i].pin1, INPUT);
    pinMode(rotaries[i].pin2, INPUT);

    attachInterrupt (rotaries[i].pin1, CHANGE);
    attachInterrupt (rotaries[i].pin2, CHANGE);
    
    #ifdef ENABLE_PULLUPS
      digitalWrite(rotaries[i].pin1, HIGH);
      digitalWrite(rotaries[i].pin2, HIGH);
    #endif

what is the error?

E:\Private Bilder, Videos, Musik\Lenkradbau\ARDUINO_BUTTON_BOXV2\ARDUINO_BUTTON_BOXV2.ino: In function 'void rotary_init()':
E:\Private Bilder, Videos, Musik\Lenkradbau\ARDUINO_BUTTON_BOXV2\ARDUINO_BUTTON_BOXV2.ino:112:46: warning: invalid conversion from 'int' to 'void (*)()' [-fpermissive]
     attachInterrupt (rotaries[i].pin1, CHANGE);
                                              ^
ARDUINO_BUTTON_BOXV2:112:46: error: too few arguments to function 'void attachInterrupt(uint8_t, void (*)(), int)'
In file included from sketch\ARDUINO_BUTTON_BOXV2.ino.cpp:1:0:
C:\Program Files (x86)\Arduino\hardware\arduino\avr\cores\arduino/Arduino.h:151:6: note: declared here
 void attachInterrupt(uint8_t interruptNum, void (*userFunc)(void), int mode);
      ^~~~~~~~~~~~~~~
E:\Private Bilder, Videos, Musik\Lenkradbau\ARDUINO_BUTTON_BOXV2\ARDUINO_BUTTON_BOXV2.ino:113:46: warning: invalid conversion from 'int' to 'void (*)()' [-fpermissive]
     attachInterrupt (rotaries[i].pin2, CHANGE);
                                              ^
ARDUINO_BUTTON_BOXV2:113:46: error: too few arguments to function 'void attachInterrupt(uint8_t, void (*)(), int)'
In file included from sketch\ARDUINO_BUTTON_BOXV2.ino.cpp:1:0:
C:\Program Files (x86)\Arduino\hardware\arduino\avr\cores\arduino/Arduino.h:151:6: note: declared here
 void attachInterrupt(uint8_t interruptNum, void (*userFunc)(void), int mode);
      ^~~~~~~~~~~~~~~
exit status 1
too few arguments to function 'void attachInterrupt(uint8_t, void (*)(), int)'

attachInterrupt has 3 arguments: pin, isr routine and mode (e.g. CHANGE)

I'v really tried but I have no clue what to insert there

do you see how the code i posted is organized and what the arguments to attachInterrupt() are in that code?

Sorry but i am definitely overwhelmed with that, I think I need some days of learning what is going on before i can go on

it's never clear how "new" a newbie is: new to the arduino, new to the C language, new to programming, new to being able to explain code, new to being able to explain a problem, ...

in the code i posted, encoderInit() initialize the pins and interrupts for two encoders. each encoder as two pins. the pins are configured as inputs with pullups so that as the encoder switches make contact, they pull the input LOW.

each encoder has a separate interrupt service routine (ISR). attachInterrupt() is called for each pin on each encoder. each pin is unique, but the same ISR (isrEncA or isrEncB) is configured for both encoder pins. the interrupt is configured for CHANGE so that the ISR is invoked for when the pin value both rises and falls.

void
encoderInit (void)
{
    // encoders
    pinMode (Enc_A_Clk, INPUT_PULLUP);
    pinMode (Enc_A_Dt,  INPUT_PULLUP);

    attachInterrupt (Enc_A_Clk, isrEncA, CHANGE);
    attachInterrupt (Enc_A_Dt,  isrEncA, CHANGE);

    pinMode (Enc_B_Clk, INPUT_PULLUP);
    pinMode (Enc_B_Dt,  INPUT_PULLUP);

    attachInterrupt (Enc_B_Clk, isrEncB, CHANGE);
    attachInterrupt (Enc_B_Dt,  isrEncB, CHANGE);
}

each ISR invokes a sub-function with parameters unique to each encoder. they include the two pins (dt, clk), the position the encoder is set to (these are brake positions on a model railroad throttle) and the state machine state the encoder is in.

void IRAM_ATTR isrEncA (void)
{
    readEncoder (Enc_A_Dt, Enc_A_Clk, encApos, encAst);
}

// -------------------------------------
void IRAM_ATTR isrEncB (void)
{
    readEncoder (Enc_B_Dt, Enc_B_Clk, encBpos, encBst);
}

while there are perhaps simpler ways of doing this, readEncoder() looks at the encoder pins and it's state to determined if the encoder is advancing forward or reverse and either incrementing or decrementing the position variable. the benefit of the state machine is that it can ignore invalid changes (the problem is what to do if they occur which was an issue in the project this code came from)

inline void readEncoder (
    byte  dt,
    byte  clk,
    int & brkPos,
    int & encSt )
{
    byte  val = digitalRead (dt) << 1 | digitalRead (clk);

    switch (encAct [val][encSt])  {
    case For:
        brkPos++;
        break;

    case Rev:
        brkPos--;
        break;

    default:
        break;
    }

    encSt = val;

 // digitalWrite (LED, ! digitalRead (LED));
}

hopefully you can see how the design breaks things into pieces which is easier to understand.

I am new to arduino like a newborn child. Everything I do is like "try and error" I'm just looking for someone who can change my sketch to read every click of my rotaries. Actually it still does read every second click. It would be great if this is possible with the sketch I am using. To codes you are sending are like hieroglyphs for me. I just started to watch a Arduino for beginners series on youtube....but I actually want to get my project done soon.

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.