Arduino 2 - Rotary Encoder Reading

Hello,

I am trying to use some rotary encoders with Arduino 2 but I have some problem with the readings.
I am using the plain encoder without breakout board, directly connecting:
Out A --> PIN 6
GND --> GND
Out B --> PIN 7
I don't use the other two pins as I don't need the button function.

Here is the connection scheme I followed:


(I have also tried connecting directly the Out A and Out B to Arduino Pins but it works even worse).

I have tried different scripts but nothing seems to work properly: every time, for each click, my variable is increased by 4 or by 2, with all the middle values in between (screenshots later), but never only by 1.

Code 1:

int encoderPin1 = 6;
int encoderPin2 = 7;
 
volatile int lastEncoded = 0;
volatile long encoderValue = 0;
 
long lastencoderValue = 0;
 
int lastMSB = 0;
int lastLSB = 0;
 
void setup() {
  Serial.begin (9600);
 
  pinMode(encoderPin1, INPUT_PULLUP);
  pinMode(encoderPin2, INPUT_PULLUP);
 
  attachInterrupt(0, updateEncoder, CHANGE);
  attachInterrupt(1, updateEncoder, CHANGE);
 
}
 
void loop(){
  Serial.println(encoderValue); 
}
 
void updateEncoder(){
  int MSB = digitalRead(encoderPin1); //MSB = most significant bit
  int LSB = digitalRead(encoderPin2); //LSB = least significant bit
 
  int encoded = (MSB << 1) |LSB; //converting the 2 pin value to single number
  int sum  = (lastEncoded << 2) | encoded; //adding it to the previous encoded value
 
  if(sum == 0b1101 || sum == 0b0100 || sum == 0b0010 || sum == 0b1011) encoderValue++;
  if(sum == 0b1110 || sum == 0b0111 || sum == 0b0001 || sum == 0b1000) encoderValue--;
 
  lastEncoded = encoded; //store this value for next time
}

For every "click" of the encoder it increments the variable by 4 (instead of 1) passing through the values in between, I attach a screenshot of the Serial.

Code 2:

#define outputA 6
#define outputB 7

int counter = 0;
int aState;
int aLastState;

void setup() {
pinMode (outputA,INPUT);
pinMode (outputB,INPUT);

Serial.begin (9600);
// Reads the initial state of the outputA
aLastState = digitalRead(outputA);
}

void loop() {
aState = digitalRead(outputA); // Reads the "current" state of the outputA
// If the previous and the current state of the outputA are different, that means a Pulse has occured
if (aState != aLastState){
// If the outputB state is different to the outputA state, that means the encoder is rotating clockwise
if (digitalRead(outputB) != aState) {
counter ++;

} else {
counter --;

}
Serial.print("Position: ");
Serial.println(counter);

}
aLastState = aState; // Updates the previous state of the outputA with the current state

}

In this case the variable is increased by 2 for every click of the encoder:
Here is a screenshot of the Serial.

Finally I have also tried to use this library (encoder_master).
Here is the example script and the resulting Serial I get for 1 click.

/* Encoder Library - Basic Example
 * http://www.pjrc.com/teensy/td_libs_Encoder.html
 *
 * This example code is in the public domain.
 */

#include <Encoder.h>

// Change these two numbers to the pins connected to your encoder.
//   Best Performance: both pins have interrupt capability
//   Good Performance: only the first pin has interrupt capability
//   Low Performance:  neither pin has interrupt capability
Encoder myEnc(6, 7);
//   avoid using pins with LEDs attached

void setup() {
  Serial.begin(9600);
  Serial.println("Basic Encoder Test:");
}

long oldPosition  = -999;

void loop() {
  long newPosition = myEnc.read();
  if (newPosition != oldPosition) {
    oldPosition = newPosition;
    Serial.println(newPosition);
  }
}

Schermata 2020-05-01 alle 11.31.55.png

If any good sould could give me some hints about what I am doing wrong I would be very grateful, it's been two days that I am looking online but it seems like I cannot solve the mistery myself.

Thank you very much,

Alessandro

Encoder-master.zip (17.1 KB)

Schermata 2020-05-01 alle 11.31.55.png

do you need to debounce the inputs?
how many interrupts occur for each "click"?

How can you follow a diagram that says "clock" and "data", when the outputs of the encoder say "Out A" and "Out B"?

Sorry guys,

I am pretty new to this so I hope I understood your questions correctly...

gcjr: I guess I have to debounce the input somehow but I wouldn't know how to...
This scheme could work?


Also, as I always get 4 or 2 increments I thought it had something to do with the code rather than debouce.

What do you mean by "how many interrupts occur for each click"?
By click I mean when I rotate the rotary encoder just one "step" forward / backward and you feel that it has reached its following natural position. I wouldn't know how else to explain...

PaulMurrayCbr: this works better than any other scheme I have experimented, I am not an electrical engineer so I simply tried...
Rotary Encoder, at least the one I've got, have 5 pins, 3 on side, 2 on the other. My assumption has simply been that, being rotary encoders, being extremely similar to the ones in the pictures of the article, the two authors simply used different names for the same thing. Maybe the one who wrote Out A and Out B didn't know that it is actually called Clock and Data or maybe the other way around? Also because, on the encoders I own, nothing is written if it is Clock and Data or Out A and Out B. I don't know, I simply tried and it worked better than anything else. But maybe I am missing the point of your question.

Thank you!

debouncing is usually a software step. See Taming under Rotary Encoder

first, you need to confirm that it is the issue. i suggested you count the # of interrupts

Also, as I always get 4 or 2 increments I thought it had something to do with the code rather than debouce.

If you are using a quadrature encoder - it's impossible to tell from your crappy pictures - there ARE 4 pulses per step, so incrementing by 4 between serial prints is perfectly normal.

@ gcjr THANKS
I tried to use this first sketch and it work perfectly:

#define CLK_PIN  2
#define DATA_PIN 7
#define YLED A1

////////////////////////////////////////////////////
void setup() {
   pinMode(CLK_PIN,INPUT);
   pinMode(DATA_PIN,INPUT);
   pinMode(YLED,OUTPUT);

   Serial.begin(9600);
   Serial.println("Rotary Encoder KY-040");
}

////////////////////////////////////////////////////
void loop() {
static uint16_t state=0,counter=0;

    delayMicroseconds(100); // Simulate doing somehing else as well.

    state=(state<<1) | digitalRead(CLK_PIN) | 0xe000;

    if (state==0xf000){
       state=0x0000;
       if(digitalRead(DATA_PIN))
         counter++;
       else
         counter--;
       Serial.println(counter);
    }
}

I still didn't understand this "interrupt" thing: how shall I count them?
I trie to use the sketch "Rotary Encoder Quality Test Program" from the same link you shared but when I launch it and click on the pushbutton on the rotary encoder I only get weird signs on the Serial, something like this: ޤ⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮C⸮B⸮⸮⸮⸮⸮⸮⸮⸮8u⸮Я⸮⸮⸮⸮⸮#⸮⸮⸮⸮⸮I<=⸮⸮⸮⸮⸮

@PaulS: I really don't know actually. This are the encoders I am using. I know they are cheap but I needed a tons and this is what I could afford... :slight_smile:

Ale_V:
I still didn't understand this "interrupt" thing: how shall I count them?

i would add a counter to the interrupt that is displayed in loop() (keep track of it's last value and only print when changed). then "click" the encoder and observe the reported count and see if it jives. These changes are only for this test and serve no purpose in the functional code.

i think it's important to learn how to test things and collect objective information rather than basing beliefs on "professional" opinion (i've obviously has some experiences)

The first one is working correctly, except the output is multiplied by 4?
So use the first one and divide result by 4? Or put it another way ignore any value which is not modulo 4.

I'm not sure this is working incorrectly, just different from what you expect. I believe for every detent rotated there are 4 state changes as outputs A and B both go HIGH then LOW just offset 90 degrees in phase.

An illustration of pcbbc's suggestion:

/* Encoder Library - Basic Example
   http://www.pjrc.com/teensy/td_libs_Encoder.html

   This example code is in the public domain.
*/

#include <Encoder.h>

// Change these two numbers to the pins connected to your encoder.
//   Best Performance: both pins have interrupt capability
//   Good Performance: only the first pin has interrupt capability
//   Low Performance:  neither pin has interrupt capability
//   avoid using pins with LEDs attached
Encoder myEnc(A0, A1);

int counts = -1;
int countsOverFour;

void setup() {
  Serial.begin(115200);
  Serial.println("Basic Encoder Test:");
}

long oldPosition  = -999;

void loop() {
  long newPosition = myEnc.read();
  if (newPosition != oldPosition) {
    if (newPosition > oldPosition) counts++;
    else counts--;
    oldPosition = newPosition;
    countsOverFour = counts >> 2; // Divide counts by four by right shifting
    Serial.print(counts);
    Serial.print("\t");
    Serial.println(countsOverFour);
  }
  if (counts > 99) counts = 0;
}

Sorry guys,

I have been busy with work and then I needed some time to test.

Thank you very much all for your suggestions, eventually, the best way I found to make it work is this one:

// Robust Rotary encoder reading
//
// Copyright John Main - best-microcontroller-projects.com
//
#define CLK 2
#define DATA 7

void setup() {
  pinMode(CLK, INPUT);
  pinMode(CLK, INPUT_PULLUP);
  pinMode(DATA, INPUT);
  pinMode(DATA, INPUT_PULLUP);
  Serial.begin (115200);
  Serial.println("KY-040 Start:");
}

static uint8_t prevNextCode = 0;
static uint16_t store=0;

void loop() {
static int8_t c,val;

   if( val=read_rotary() ) {
      c +=val;
      Serial.print(c);Serial.print(" ");

      if ( prevNextCode==0x0b) {
         Serial.print("eleven ");
         Serial.println(store,HEX);
      }

      if ( prevNextCode==0x07) {
         Serial.print("seven ");
         Serial.println(store,HEX);
      }
   }
}

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

Which is literally dark magic for me but it works really well: it doesn't miss any step and it is increadibly fast (while the other would take some time to process for whatever reason).

Thank you!

1 Like

Sorry guys,

I am looking for a way to shrink all this in a method so that I can easily repeat it (I have 43 encoders...).

This part of the code:

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

Fall outside both of loop() and setup(), how is that possible?
I have tried to include it in the loop cycles but it gives an error...

Thank you very much!

Alessandro

Fall outside both of loop() and setup(), how is that possible?

what does "Fall outside loop()" mean

since read_rotatary() has 2 state variables, they can be maintained in an array and passed by reference to the modified read_rotary() below.

consider the following (tested in simulation on laptop)

#include "sim.h"

#define DATA    10
#define CLK     11

// -----------------------------------------------------------------------------
// A vald CW or  CCW move returns 1, invalid returns 0.
int8_t
read_rotary (
    int  & store,
    int  & prevNextCode,
    byte data,
    byte clk )
{
    static int8_t rot_enc_table[] = {
        0,1,1,0,   1,0,0,1,
        1,0,0,1,   0,1,1,0 };

    prevNextCode <<= 2;
    prevNextCode  |= data << 1;
    prevNextCode  |= clk;
    prevNextCode  &= 0x0F;

    // If valid then store as 16 bit data.
    if  (rot_enc_table [prevNextCode] ) {
        store <<= 4;
        store  |= prevNextCode;
        store  &= 0xFF;

        if (store == 0x2b) return -1;
        if (store == 0x17) return 1;
    }

    return 0;   // invalid input
}

// -----------------------------------------------------------------------------
#define N_ENC   5
int  encState [N_ENC] = {};
int  encInp   [N_ENC] = {};

void
loop (void)
{
    byte data = digitalRead (DATA);
    byte clk  = digitalRead (CLK);

    int  res = read_rotary (encState [0], encInp [0], data, clk);

    char s [80];
    sprintf (s, " %20s %02X %2d", "", encState [0], res);
    Serial.println (s);
}

// -----------------------------------------------------------------------------
void
setup (void)
{
}

Hello gcjr,

thank you very much for your reply.

I have to be honest, I haven't understood much of it, I don't have such a deep knowledge of coding, I am just at the beginning. :-[

At my novice state this is what I have done for the first 3 rotary encoder:

static uint8_t prevNextCode = 0;
static uint16_t store=0;
static uint8_t prevNextCode1 = 0;
static uint16_t store1=0;
static uint8_t prevNextCode2 = 0;
static uint16_t store2=0;

#define CLK_PIN_0 22
#define DATA_PIN_0 23
#define CLK_PIN_1 24
#define DATA_PIN_1 25
#define CLK_PIN_2 26
#define DATA_PIN_2 27

void setup() {
  pinMode(CLK_PIN_0, INPUT);
  pinMode(CLK_PIN_0, INPUT_PULLUP);
  pinMode(DATA_PIN_0, INPUT);
  pinMode(DATA_PIN_0, INPUT_PULLUP);
  pinMode(CLK_PIN_1, INPUT);
  pinMode(CLK_PIN_1, INPUT_PULLUP);
  pinMode(DATA_PIN_1, INPUT);
  pinMode(DATA_PIN_1, INPUT_PULLUP);
  pinMode(CLK_PIN_2, INPUT);
  pinMode(CLK_PIN_2, INPUT_PULLUP);
  pinMode(DATA_PIN_2, INPUT);
  pinMode(DATA_PIN_2, INPUT_PULLUP);

}

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

//  RGB HIGHLIGHTS INPUT
void encoder0() {
static int8_t c,val;

   if( val=read_rotary() ) {
      c +=val;
      
      if ( prevNextCode==0x0b) {
         ccw_counting(0);
         Keyboard.begin();
         Keyboard.press(ctrlkey);
         Keyboard.press(altkey);
         Keyboard.press(commandkey);
         Keyboard.press ('r');
         Keyboard.releaseAll();
         Keyboard.end();               
      }
      if ( prevNextCode==0x07) {
         cw_counting(0);
         Keyboard.begin();
         Keyboard.press(ctrlkey);
         Keyboard.press(altkey);
         Keyboard.press(commandkey);
         Keyboard.press ('e');
         Keyboard.releaseAll();       
         Keyboard.end();
      }    
      previous_event = event;
      display_level("RGB CHANNEL", "Highlights Input", 0);
   }
}

// 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_PIN_0))  prevNextCode |= 0x02;
  if (digitalRead(CLK_PIN_0))   prevNextCode |= 0x01;
  prevNextCode &= 0x0f;

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

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

//  RGB GAMMA INPUT
void encoder1() {
static int8_t c,val;

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

      if ( prevNextCode1==0x0b) {
         ccw_counting(1);
         //Serial.print("RE 1 CCW");
         Keyboard.begin();
         Keyboard.press(ctrlkey);
         Keyboard.press(altkey);
         Keyboard.press(commandkey);
         Keyboard.press ('y');
         Keyboard.releaseAll();
         Keyboard.end();
      }

      if ( prevNextCode1==0x07) {
         cw_counting(1);
         //Serial.print("RE 1 CW");
         Keyboard.begin();
         Keyboard.press(ctrlkey);
         Keyboard.press(altkey);
         Keyboard.press(commandkey);
         Keyboard.press ('t');
         Keyboard.releaseAll();       
         Keyboard.end();
      }
      previous_event = event;
      display_level("RGB CHANNEL", "Gamma Input", 1);
   }
}

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

  prevNextCode1 <<= 2;
  if (digitalRead(DATA_PIN_1)) prevNextCode1 |= 0x02;
  if (digitalRead(CLK_PIN_1)) prevNextCode1 |= 0x01;
  prevNextCode1 &= 0x0f;

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

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

//  RGB SHADOW INPUT
void encoder2() {
static int8_t c,val;

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

      if ( prevNextCode2==0x0b) {
         ccw_counting(2);
         Serial.println("RE 2 CCW");
         Keyboard.begin();
         Keyboard.press(ctrlkey);
         Keyboard.press(altkey);
         Keyboard.press(commandkey);
         Keyboard.press ('u');
         Keyboard.releaseAll();
         Keyboard.end();
      }

      if ( prevNextCode2==0x07) {
         cw_counting(2);
         Serial.println("RE 2 CW");
         Keyboard.begin();
         Keyboard.press(ctrlkey);
         Keyboard.press(altkey);
         Keyboard.press(commandkey);
         Keyboard.press ('i');
         Keyboard.releaseAll();       
         Keyboard.end();
      }
      previous_event = event;
      display_level("RGB CHANNEL", "Shadows Input", 2);
   }
}

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

  prevNextCode2 <<= 2;
  if (digitalRead(DATA_PIN_2)) prevNextCode2 |= 0x02;
  if (digitalRead(CLK_PIN_2)) prevNextCode2 |= 0x01;
  prevNextCode2 &= 0x0f;

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

//////////////////////////////////////////////////////////////////////////////////
void loop(){
  encoder0();
  encoder1();
  encoder2();
}

Basically I have multiplied all the various things that had to be changed for each rotary encoder with progressive names...(plus a bunch of other things that are not related to the encoders).

I didn't understand the code you wrote for me (but thanks a lot!) or how I shall implement it in my sketch.
Could you explain me a bit more in details?

Thank you very much!

Alessandro

i don't believe duplicating functions (read_rotary1 (), read_rotary2 (), ...) will work because they are all using the same state variables: "prevNextCode" and "store".

in the code I posted I showed how to pass arguments to a single version of read_rotary() so that it could be used for different encoders. The store and prevNextCode arguments are defined with the "&" which means their values may be modified within the function and those values remain persistent when the function returns. Those values are arrays which can easily be expanded to 43. You can deal with the pins by putting their values in arrays as well

i suggest you understand how arguments can be passed to functions so that you don't duplicate functions unnecessarily.

looks like each encoder() and read_rotary() are 150 lines. do you plan on having a program with 6450 lines supporting 43 encoders?

looks like each encoder() and read_rotary() are 150 lines. do you plan on having a program with 6450 lines supporting 43 encoders?

Absolutely not, that is why I am asking :slight_smile:

i don't believe duplicating functions (read_rotary1 (), read_rotary2 (), ...) will work because they are all using the same state variables: "prevNextCode" and "store"

100% don't work, that's why I had to multiply the number of prevNextCode and store for the number of encoders...

I am studying your code...
why Sim.h library? I google it but I've only found things about GSM...

#define DATA    10
#define CLK     11

This is where I shall enter all the pins for my encoders?
Right now I renamed them like this

#define CLK_PIN_0 22
#define DATA_PIN_0 23
#define CLK_PIN_1 24
#define DATA_PIN_1 25
#define CLK_PIN_2 26
#define DATA_PIN_2 27

For three encoders...

#define N_ENC   5
int  encState [N_ENC] = {};
int  encInp   [N_ENC] = {};

Here you set the total number of encoders (i.e. 5) and create the two arrays, clear.

void
loop (void)
{
    byte data = digitalRead (DATA);
    byte clk  = digitalRead (CLK);

    int  res = read_rotary (encState [0], encInp [0], data, clk);

    char s [80];
    sprintf (s, " %20s %02X %2d", "", encState [0], res);
    Serial.println (s);
}

I absolutely have no idea of what this does...

Is this collapsed anywhere I shall I keep on making it for each encoder?

void encoder0() {
static int8_t c,val;

   if( val=read_rotary() ) {
      c +=val;
     
      if ( prevNextCode==0x0b) {
         ccw_counting(0);
         Keyboard.begin();
         Keyboard.press(ctrlkey);
         Keyboard.press(altkey);
         Keyboard.press(commandkey);
         Keyboard.press ('r');
         Keyboard.releaseAll();
         Keyboard.end();               
      }
      if ( prevNextCode==0x07) {
         cw_counting(0);
         Keyboard.begin();
         Keyboard.press(ctrlkey);
         Keyboard.press(altkey);
         Keyboard.press(commandkey);
         Keyboard.press ('e');
         Keyboard.releaseAll();       
         Keyboard.end();
      }   
      previous_event = event;
      display_level("RGB CHANNEL", "Highlights Input", 0);
   }
}

I am terribly sorry for the waste of time but I am completely confused...THANKS!

I am terribly sorry for the waste of time but I am completely confused.

but you're interested in understanding ... i'm happy to try to explain

why Sim.h library?

i don't have your hardware, but I want to test the code I post. So I simulate it on my laptop. My "sim.h" includes my main() and various stubs for Arduino functions and a mechanism to change pin states specified in a separate file.

i modified the code I posted earlier to be more precise for what you need to do. one specific change was to add arrays for the pins. my code uses 10 and 11

#define N_ENC   1
int  encState [N_ENC] = {};
int  encInp   [N_ENC] = {};
byte pinData  [N_ENC] = { 10 };
byte pinClk   [N_ENC] = { 11 };

the other is how to call the modified read_rotary() using the arrays in a for loop. I changed my code. I'm using the new pin arrays as arguments to digitalRead() .

I've also added a for-loop, but since I only simulate one encoder, i only step thru one array.

    for (int i = 0; i < N_ENC; i++)  {
        int res = read_rotary (encState [0], encInp [0],
                                digitalRead (pinData [i]),
                                digitalRead (pinClk  [i]) );

        if (res)  {
            sprintf (s, " %20s %02X %2d", "", encState [0], res);
            Serial.println (s);
        }
    }

i hope you're aware that read_rotary() only returns a non-zero value indicating direction after sequencing thru all 4 states.

complete code. hope it's clear that this could support many more encoders with just changes to N_ENC, pinData [] and pinClk []. I hope you understand how to append addition pins value to the pinData [] and pinClk[]. unspecified values inside "{}" are set to zero (hence encState [] and encInp [] are initialized to zeros).

there needs to be a check before printing that res is not zero

#include "sim.h"        // my main and Arduino subs for simulation

// -----------------------------------------------------------------------------
// A vald CW or  CCW move returns 1, invalid returns 0.
int8_t
read_rotary (
    int  & store,
    int  & prevNextCode,
    byte data,
    byte clk )
{
    static int8_t rot_enc_table[] = {
        0,1,1,0,   1,0,0,1,
        1,0,0,1,   0,1,1,0 };

    prevNextCode <<= 2;
    prevNextCode  |= data << 1;
    prevNextCode  |= clk;
    prevNextCode  &= 0x0F;

    // If valid then store as 16 bit data.
    if  (rot_enc_table [prevNextCode] ) {
        store <<= 4;
        store  |= prevNextCode;
        store  &= 0xFF;

        if (store == 0x2b) return -1;
        if (store == 0x17) return 1;
    }

    return 0;   // invalid input
}

// -----------------------------------------------------------------------------
#define N_ENC   1
int  encState [N_ENC] = {};
int  encInp   [N_ENC] = {};
byte pinData  [N_ENC] = { 10 };
byte pinClk   [N_ENC] = { 11 };

void
loop (void)
{
    char s [80];

    for (int i = 0; i < N_ENC; i++)  {
        int res = read_rotary (encState [0], encInp [0],
                                digitalRead (pinData [i]),
                                digitalRead (pinClk  [i]) );

        if (res)  {
            sprintf (s, " %20s %02X %2d", "", encState [0], res);
            Serial.println (s);
        }
    }
}

// -----------------------------------------------------------------------------
void
setup (void)
{
}

ask questions if not clear.

but you're interested in understanding ... i'm happy to try to explain

THANK YOU :smiley:
I am super willing to understand!

I think I understood the logic and the mechanism you are building but I am missing the technical knowledge to transform what I am think into a code that could actually work.
I thought as well of using arrays to define data and clock pins but I didn't know how to write it correctly and it never worked.
Also, the thing you do about read_rotary() passing values with "&" is something I understood now but actually I thought of as well...but I didn't even know what to search for on google to learn how to implement it...
It is still not clear 100% for me but I will think about it. Thank you!

So, just to recap and make an example...

// A vald CW or  CCW move returns 1, invalid returns 0.
int8_t
read_rotary (
    int  & store,
    int  & prevNextCode,
    byte data,
    byte clk )
{
    static int8_t rot_enc_table[] = {
        0,1,1,0,   1,0,0,1,
        1,0,0,1,   0,1,1,0 };

    prevNextCode <<= 2;
    prevNextCode  |= data << 1;
    prevNextCode  |= clk;
    prevNextCode  &= 0x0F;

    // If valid then store as 16 bit data.
    if  (rot_enc_table [prevNextCode] ) {
        store <<= 4;
        store  |= prevNextCode;
        store  &= 0xFF;

        if (store == 0x2b) return -1;
        if (store == 0x17) return 1;
    }

    return 0;   // invalid input
}

// -----------------------------------------------------------------------------
#define N_ENC   3
int  encState [N_ENC] = {};
int  encInp   [N_ENC] = {};
byte pinData  [N_ENC] = { 23, 25, 27 };
byte pinClk   [N_ENC] = { 22, 24, 26 };



void
setup (void)
{
  Serial.begin(9600);
  Serial.println("Rotary Encoder Test"); //Just to check if Serial works...
}

void
loop (void)
{
    char s [80];

    for (int i = 0; i < N_ENC; i++)  {
        int res = read_rotary (encState [0], encInp [0],
                                digitalRead (pinData [i]),
                                digitalRead (pinClk  [i]) );

        if (res)  {
            sprintf (s, " %20s %02X %2d", "", encState [0], res);
            Serial.println (s);
        }
    }
}

// -----------------------------------------------------------------------------

This is what I have done for my specific case at the moment: I have 3 rotary encoder attached to pins 22,23 / 24,25 / 26,27.
I hope I have done it correctly.

What I don't understand is how I read the signal of these encoders.
Before I was defining this method as follow

void encoder0() {
static int8_t c,val;

   if( val=read_rotary0() ) {
      c +=val;
      
      if ( prevNextCode0==0x0b) {
         ccw_counting(0);               // This is a counter for a screen I have attached
         Keyboard.begin();
         Keyboard.press(ctrlkey);
         Keyboard.press(altkey);
         Keyboard.press(commandkey);
         Keyboard.press ('r');
         Keyboard.releaseAll();
         Keyboard.end();               
      }
      if ( prevNextCode0==0x07) {
         cw_counting(0);                // This is a counter for a screen I have attached 
         Keyboard.begin();
         Keyboard.press(ctrlkey);
         Keyboard.press(altkey);
         Keyboard.press(commandkey);
         Keyboard.press ('e');
         Keyboard.releaseAll();       
         Keyboard.end();
      }    
      previous_event = event;        // This reset another counter to clear the screen after n seconds I haven't moved the encoder
      display_level("RGB CHANNEL", "Highlights Input", 0);     //This is a method that passes the information to the display
   }
}

Which basically simply press some keyboard buttons for every step the encoder does.
Now, my question is, BEFORE I was "making copies" of everything, personally changing the name of read_rotary0 (read_rotary_2, read_rotary_3, etc), prevNextCode0 (prevNextCode1, prevNextCode2, etc), store0 (store1, store2, etc), so that for every void encoder0() (void encoder1(), void encoder2(), etc) I simply had to change the number of read_rotary, prevNextCode, store to get the input from the different encoders.
As now read_rotary, prevNextCode, store always have the same name, how do I explain to my void encoder() method which encoder is he reading?

MANY MANY THANKS!

Ale_V:
how do I explain to my void encoder() method which encoder is he reading?

little confused about what your code is. Your read_rotary0 () has no arguments.

  if( val=read_rotary0() ) {

I added arguments so that the same function, read_rotary() can be used for different encoders with different state variables.

int8_t
read_rotary (
    int  & store,
    int  & prevNextCode,
    byte data,
    byte clk )

you need to call read_rotary with the proper arguments (I just see now that the indices to encState and encInp [] need to be "i" instead of "0"

int  encState [N_ENC] = {};
int  encInp   [N_ENC] = {};
byte pinData  [N_ENC] = { 10 };
byte pinClk   [N_ENC] = { 11 };

       int res = read_rotary (encState [i], encInp [i],    // *** indices need to be "i", not "0'
                                digitalRead (pinData [i]),
                                digitalRead (pinClk  [i]) );

Thank you very much gcjr!

I try to explain myself better, maybe I got this wrong since the beginning, I don't know...

My read_rotary() has no argument as I was duplicating the whole "read_rotary() thing" for each encoder, every time changing all the names to make the unique: so I had turned read_rotary() into read_rotary0(), read_rotary1(), etc...clearly I also had to number the different variables prevNextCode and store into prevNextCode0, prevNextCode1, etc, and store0, store1, etc...I attach a compilable example at the end.

I, first of all, followed the link you originally sent me, and, after some trials, I found out that the best code was this one (you can find it at the very end of the link, "Code for improved table decode).

Original code from the link: Code for improved table decode

// Robust Rotary encoder reading
//
// Copyright John Main - best-microcontroller-projects.com
//
#define CLK 2
#define DATA 7

void setup() {
  pinMode(CLK, INPUT);
  pinMode(CLK, INPUT_PULLUP);
  pinMode(DATA, INPUT);
  pinMode(DATA, INPUT_PULLUP);
  Serial.begin (115200);
  Serial.println("KY-040 Start:");
}

static uint8_t prevNextCode = 0;
static uint16_t store=0;

void loop() {
static int8_t c,val;

   if( val=read_rotary() ) {
      c +=val;
      Serial.print(c);Serial.print(" ");

      if ( prevNextCode==0x0b) {
         Serial.print("eleven ");
         Serial.println(store,HEX);
      }

      if ( prevNextCode==0x07) {
         Serial.print("seven ");
         Serial.println(store,HEX);
      }
   }
}

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

From my understanding this code was basically divided into two parts: definition of variables and pinMode declaration:

// Robust Rotary encoder reading
//
// Copyright John Main - best-microcontroller-projects.com
//
#define CLK 2
#define DATA 7

void setup() {
  pinMode(CLK, INPUT);
  pinMode(CLK, INPUT_PULLUP);
  pinMode(DATA, INPUT);
  pinMode(DATA, INPUT_PULLUP);
  Serial.begin (115200);
  Serial.println("KY-040 Start:");
}

static uint8_t prevNextCode = 0;
static uint16_t store=0;

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

And a loop which was reading a single encoder:

void loop() {
static int8_t c,val;

   if( val=read_rotary() ) {
      c +=val;
      Serial.print(c);Serial.print(" ");

      if ( prevNextCode==0x0b) {
         Serial.print("eleven ");
         Serial.println(store,HEX);
      }

      if ( prevNextCode==0x07) {
         Serial.print("seven ");
         Serial.println(store,HEX);
      }
   }
}

So my first thought (not being able to apply any other, more intelligent, idea for my lack of coding experience) as been to transform the whole loop of the example code into a method I have called encoder
n
() (where n stands for the number of the encoder I am reading) and then repeating the variable declaration and pinMode for each encoder (clearly changing the name both).

How did I know which encoder was I reading with each method "encodern()"?
Simply updating prevNextCoden with the following progressive number: encoder1() would refer to prevNextCode1, encoder2() would refer to prevNextCode2, etc etc...

Here is a little example of what I have done for two encoders:

// Example with two encoders

#define CLK_ENC_0 22
#define DATA_ENC_0 23
#define CLK_ENC_1 24
#define DATA_ENC_1 25

void setup() {
  pinMode(CLK_ENC_0, INPUT);
  pinMode(CLK_ENC_0, INPUT_PULLUP);
  pinMode(DATA_ENC_0, INPUT);
  pinMode(DATA_ENC_0, INPUT_PULLUP);
  pinMode(CLK_ENC_1, INPUT);
  pinMode(CLK_ENC_1, INPUT_PULLUP);
  pinMode(DATA_ENC_1, INPUT);
  pinMode(DATA_ENC_1, INPUT_PULLUP);
  Serial.begin (9600);
  Serial.println("2 Rotary Encoder Test");
}

////////////////////////////////////////////////////////////////////////////
//ENCODER 0 VARIABLES
static uint8_t prevNextCode0 = 0;
static uint16_t store0=0;

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

  prevNextCode0 <<= 2;
  if (digitalRead(DATA_ENC_0)) prevNextCode0 |= 0x02;
  if (digitalRead(CLK_ENC_0)) prevNextCode0 |= 0x01;
  prevNextCode0 &= 0x0f;

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

////////////////////////////////////////////////////////////////////////////
//ENCODER 1 VARIABLES
static uint8_t prevNextCode1 = 0;
static uint16_t store1=0;

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

  prevNextCode1 <<= 2;
  if (digitalRead(DATA_ENC_1)) prevNextCode1 |= 0x02;
  if (digitalRead(CLK_ENC_1)) prevNextCode1 |= 0x01;
  prevNextCode1 &= 0x0f;

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

////////////////////////////////////////////////////////////////////////////
void encoder0() {
static int8_t c,val;

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

      if ( prevNextCode0==0x0b) {
         Serial.println ("Rotary Encoder 0 Turning CCW");
      }

      if ( prevNextCode0==0x07) {
         Serial.println ("Rotary Encoder 0 Turning CW");
      }
   }
}

////////////////////////////////////////////////////////////////////////////
void encoder1() {
static int8_t c,val;

   if( val=read_rotary1() ) {
      c +=val;
      
      if ( prevNextCode1==0x0b) {
         Serial.println ("Rotary Encoder 1 Turning CCW");
      }

      if ( prevNextCode1==0x07) {
         Serial.println ("Rotary Encoder 1 Turning CW");
      }
   }
}

void loop(){
  encoder0();
  encoder1();
}

If you compile it it will work with two encoders connected to pin 22,23 / 24,25.

Now, what I am not able to understand of your code is: okay for the variables declaration, you do it with arrays, clear, but how do I target the different generated values of "read_rotary()", "prevNextCode" and "store" in my, so named, "encoder()" method?

I hope it's clear, I am so sorry for my unappropriate terminology..!

MANY MANY THANKS!