3

I'm trying to detect the direction of rotation for an incremental rotary encoder. I'm supposed to get +1 for clockwise and -1 for counter-clockwise. However, I receive random values. Any help appreciated.

Following is the code.

const int encoderPinA = 7;
const int encoderPinB = 12;
int val;
int encoderPos = 0;
boolean encoderALast = LOW; // remember the previous pin state

void setup() {
  pinMode(encoderPinA,INPUT);
  pinMode(encoderPinB,INPUT);
  Serial.begin(9600);
}

void loop() {
  analogWrite(3,250); //powering motor
  val = encoder_direction();
  // receive the direction for clockwise 1 and for counter clock wise  -1
  Serial.println(val); // print it in serial monitor
}

int encoder_direction() {
  boolean encoderA = digitalRead(encoderPinA);
  if ((encoderALast == HIGH) && (encoderA == LOW)) {
    if (digitalRead(encoderPinB)) {
      //encoderPos --;
      encoderPos = -1;
    } else {
      //encoderPos ++;
      encoderPos = 1;
    }
    return  encoderPos;
    // return the value either 1 or -1
  }
  encoderALast = encoderA;
}

// using digitalread() code  for four encoders
const int ENCODERS = 4; // the number of encoders
const int encoderPinA[ENCODERS] = { 7, 8, A1 , 2 };
// encoderA pins on 2 and 4
const int encoderPinB[ENCODERS] = { 12, 5, 4 , A2 };
// encoderB pins on 3 and 5
int encoderPos[ ENCODERS] = { 0, 0, 0, 0 };
// initialize the positions to 0
boolean encoderALast[ENCODERS] = { LOW, LOW, LOW, LOW };
// holds last state of encoderA pin

void setup() {
  Serial.begin (9600);
}

int updatePosition(int encoderIndex) {
  boolean encoderA = digitalRead(encoderPinA[encoderIndex]);
  if ((encoderALast[encoderIndex] == HIGH) && (encoderA == LOW)) {
    if (digitalRead(encoderPinB[encoderIndex]) == LOW) {
      //encoderPos[encoderIndex]--;
      encoderPos[encoderIndex] = -1;
    } else {
      //encoderPos[encoderIndex]++;
      encoderPos[encoderIndex] = 1;
    }
    Serial.print("Encoder ");
    Serial.print(encoderIndex+1,DEC);
    Serial.print("=");
    Serial.println (encoderPos[encoderIndex]);
  }
  encoderALast[encoderIndex] = encoderA;
}

void loop() {
  updatePosition(0);
  updatePosition(1);
  updatePosition(2);
  updatePosition(3);
}

// using direct access to digitalread code in order to process faster
int val[4];
int encoderPos = 0;
boolean encoderALast[4] ={LOW, LOW, LOW, LOW};
// remember the previous pin state

void setup() {
  Serial.begin(9600);
}

void loop() {
  for (int ii=0; ii<4;ii++) {
    encoder_direction(ii);
  }
}
int encoder_direction(int m) {
  boolean encoderA;
  boolean encoderB;
  if(m == 0) {
    encoderA = (PIND & _BV (7)) == 0;
    //   digitalRead(7) encoderPinA
    encoderB = (PINB & _BV (4)) == 0;
    // digitalRead(12) encoderPinB
  }
  if (m == 1) {
    encoderA = (PINB & _BV (0)) == 0;
    // digitalRead(8) encoderPinA
    encoderB = (PIND & _BV (5)) == 0;
    // digitalRead(5) encoderPinB
  }
  if (m == 2) {
    encoderA = (PINC & _BV (1)) == 0;
    // digitalRead(A1) encoderPinA
    encoderB = (PIND & _BV (4)) == 0;
    // digitalRead(4)  encoderPinB
  }
  if (m == 3) {
    encoderA = (PIND & _BV (2)) == 0;
    // digitalRead(2) encoderPinA
    encoderB = (PINC & _BV (2)) == 0;
    // digitalRead(A2)  encoderPinB
  }
  if ((encoderALast[m] == HIGH) && (encoderA == LOW)) {
    if (encoderB == LOW) {
      //encoderPos --;
      encoderPos = -1;
    } else {
      //encoderPos ++;
      encoderPos = 1;
    }
    Serial.print(" Encoder ");
    Serial.print(m+1);
    Serial.print(" = ");
    Serial.println(encoderPos);
  }
  encoderALast[m] = encoderA;
}
  • I don't think its possible using only 2 detector and detect only based on the order of activation. – dhimaspw Dec 23 '16 at 4:23
  • when this is in the loop works fine. But, now I want to use function. The problem is I can not retrieve correct value from function's output. – zalt Dec 23 '16 at 4:48
5

I have a page about rotary encoders - in that I show how you can detect encoder changes (and direction) using one interrupt pin and another pin, like this:

// Wiring: Connect common pin of encoder to ground.
// Connect pin A (one of the outer ones) to a pin that can generate interrupts (eg. D2)
// Connect pin B (the other outer one) to another free pin (eg. D5)

volatile bool fired;
volatile bool up;

const byte encoderPinA = 2;
const byte encoderPinB = 5;

// Interrupt Service Routine for a change to encoder pin A
void isr ()
{
  if (digitalRead (encoderPinA))
    up = digitalRead (encoderPinB);
  else
    up = !digitalRead (encoderPinB);
  fired = true;
}  // end of isr


void setup ()
{
  pinMode (encoderPinA, INPUT_PULLUP);     // enable pull-ups
  pinMode (encoderPinB, INPUT_PULLUP); 
  attachInterrupt (digitalPinToInterrupt (encoderPinA), isr, CHANGE);   // interrupt 0 is pin 2

  Serial.begin (115200);
}  // end of setup

void loop ()
{
static long rotaryCount = 0;

  if (fired)
    {
    if (up)
      rotaryCount++;
    else
      rotaryCount--;
    fired = false;

    Serial.print ("Count = ");  
    Serial.println (rotaryCount);
    }  // end if fired

}  // end of loop

In the main loop you just need to test the boolean fired to see if the encoder changed, and up to see if it went up (otherwise it went down).


I receive random of these values ...

I think with your code you need to detect a change - that is, if the encoder pin is now HIGH but was previously LOW.


Solution for 4 encoders

The problem is I am working on four encoders ...

I have reworked my code to use pin-change interrupts instead of external interrupts.

// Wiring: Connect common pin of encoder to ground.

volatile bool fired;

const byte ENCODERS = 4;

typedef struct 
  {
  int aPin;     // which Arduino pin for the "A" side
  int bPin;     // which Arduino pin for the "B" side

  // values below are calculated at run-time
  volatile byte * aPort;  // which processor port the A side is plugged into
  volatile byte * bPort;  // which processor port the B side is plugged into
  byte aBitMask;   // which bit in the A port
  byte bBitMask;   // which bit in the B port
  byte whichInterrupt;  // which pin-change interrupt port (0, 1, 2)
  byte oldValue;   // old value of A port (to see if it changed)
  int count;       // current encoder counter
  } encoder;

 volatile encoder encoders [ENCODERS] =
  {

 // A   B  pins  (eg. D4 and D8, D5 and D9 and so on)

  { 4,  8 },
  { 5,  9 },
  { 6, 10 },
  { 7, 11 },

  };  // end of encoders


void checkForPinChange (const byte which)
  {
  for (byte i = 0; i < ENCODERS; i++)
    {
    if (encoders [i].whichInterrupt == which)
      {
      byte newValue = *(encoders [i].aPort) & encoders [i].aBitMask;
      if (newValue != encoders [i].oldValue)
        {
        bool up;
        byte bPort = *(encoders [i].bPort) & encoders [i].bBitMask;
        if (newValue)
          up = bPort;
        else
          up = !bPort;
        fired = true;
        if (up)
          encoders [i].count++;
        else
          encoders [i].count--;
        encoders [i].oldValue = newValue;
        }  // end of if value has changed
      }   // end of if this is the right interrupt number
    }     // end of for each encoder
  } // end of checkForPinChange

// handle pin change interrupt for D8 to D13 here
ISR (PCINT0_vect)
 {
 checkForPinChange (PCIE0);
 }  // end of PCINT0_vect

// handle pin change interrupt for A0 to A5 here
ISR (PCINT1_vect)
 {
 checkForPinChange (PCIE1);
 }  // end of PCINT1_vect

// handle pin change interrupt for D0 to D7 here
ISR (PCINT2_vect)
 {
 checkForPinChange (PCIE2);
 }  // end of PCINT2_vect

void setup ()
{
 Serial.begin (115200);
 Serial.println ("Starting ...");

 PCIFR  |= bit (PCIF0) | bit (PCIF1) | bit (PCIF2);   // clear any outstanding interrupts

 for (byte i = 0; i < ENCODERS; i++)
    {
    pinMode (encoders [i].aPin, INPUT_PULLUP);
    pinMode (encoders [i].bPin, INPUT_PULLUP);
    // convert pin number to port, mask, etc. for efficiency

    // Input port
    encoders [i].aPort = portInputRegister (digitalPinToPort (encoders [i].aPin));
    encoders [i].bPort = portInputRegister (digitalPinToPort (encoders [i].bPin));

    // Which bit in the port to test
    encoders [i].aBitMask = digitalPinToBitMask (encoders [i].aPin);
    encoders [i].bBitMask = digitalPinToBitMask (encoders [i].bPin);

    // Which interrupt number (0, 1, 2)
    encoders [i].whichInterrupt = digitalPinToPCICRbit (encoders [i].aPin);

    // activate this pin-change interrupt bit (eg. PCMSK0, PCMSK1, PCMSK2)
    volatile byte * ICRmaskPort = digitalPinToPCMSK (encoders [i].aPin);
    *ICRmaskPort  |= bit (digitalPinToPCMSKbit (encoders [i].aPin));

    // enable this pin-change interrupt
    PCICR |= bit (digitalPinToPCICRbit (encoders [i].aPin));

    } // end of for each encoder

}  // end of setup

void loop ()
{
  if (fired)
    {
    for (byte i = 0; i < ENCODERS; i++)
      {
      char buf [10];
      sprintf (buf, "%5d", encoders [i].count);
      Serial.print (buf);
      }
    Serial.println ();
    fired = false;
    }  // end if fired

}  // end of loop

The code is general enough to handle 1 to x encoders (eg. 10 encoders) - within reason! You need to have 2 pins spare per encoder (the third pin goes to ground). One of the pins needs to be capable of pin-change interrupts (on the Uno that is all of them).

The code sets up to detect an interrupt on the "A" side of the encoder and tests the value on the "B" side to see if it was turned clockwise or counter-clockwise.

I am getting results like this (with 2 encoders connected):

   16  -19    1    1
   17  -19    1    1
   18  -19    1    1
   19  -19    1    1
   20  -19    1    1
   21  -19    1    1
   22  -19    1    1
   23  -19    1    1
   23  -20    1    1
   23  -21    1    1
   23  -22    1    1
   23  -23    1    1
   23  -24    1    1
   23  -25    1    1

The other two are showing as "1" (that is, not turned yet).

  • I see. The problem is I am working on four encoders, Therefore, I trying to realize by status change detecting. – zalt Dec 23 '16 at 7:06
  • It's always helpful to give as much detail as you can in the question. Then we don't have to guess, and we don't give an answer which doesn't suit your situation. – Nick Gammon Dec 23 '16 at 7:43
  • And if I may say, your posted code in the question doesn't have four encoders in it. – Nick Gammon Dec 23 '16 at 7:43
  • Nick, I think it would make sense to update your code to say pinMode(encoderPinA, INPUT_PULLUP); instead of digitalWrite (encoderPinA, HIGH); // enable pull-ups – James Waldby - jwpat7 Dec 23 '16 at 7:46
  • ok, I got to different code for four encoders. – zalt Dec 23 '16 at 7:48
2

Using polling to read multiple encoders may work if you program quite carefully and if you dedicate the processor just to reading encoders; but generally, interrupt-driven encoder processing has a higher likelihood of working ok. The example below uses pin-change interrupts to read from three rotary encoders that are attached to PD2...PD7, ie, six consecutive bits of port D. It can be extended to more encoders by adding another pin-change ISR to handle another interrupt vector, or by using PD0 and PD1 as encoder inputs. (However, PD0 and PD1 are RX and TX on the Uno I'm testing with, and I'm using Serial for test output.)

Example: rotors3.ino

/*  Rotors3 -- JW, December 2016 -- Uses 4-state state-machine
 *  implementation of rotary encoding for KY-040 rotary knobs.  The
 *  state-machine picture in a Feb 4, 2014 7:40 PM post at
 *  https://e2e.ti.com/support/microcontrollers/hercules/f/312/t/318762
 *  by Anthony Seely shows counts increasing on transitions 10 -> 11
 *  -> 01 -> 00 -> 10 and decreasing on transitions the other way.
 *  The ISR in this program decodes those transitions using logical
 *  operations on variables `changes` and `wayflag`.  Transitions
 *  between 00 and 11 or 10 and 01 are invalid and ignored.
 */

volatile byte abOld;        // Old state
volatile byte changedCount; // Change-indication
volatile int count[3];      // current rotary count

void setupRotors3(byte ioMode, byte enablePCI) {
  pinMode( 2, ioMode); //  2, PD2   PCINT 18
  pinMode( 3, ioMode); //  3, PD3   PCINT 19
  pinMode( 4, ioMode); //  4, PD4   PCINT 20
  pinMode( 5, ioMode); //  5, PD5   PCINT 21
  pinMode( 6, ioMode); //  6, PD6   PCINT 22
  pinMode( 7, ioMode); //  7, PD7   PCINT 23
  if (enablePCI) {
    PCMSK2 |= 0xfc; // Set mask for bits of interest
    PCIFR  |= 0x04; // Clear PC interrupts if any
    PCICR  |= 0x04; // Enable PC interrupts
  }
  abOld = changedCount = count[0] = count[1] = count[2] = 0;
}

void setup() {
  setupRotors3(INPUT_PULLUP, true);
  Serial.begin(115200);
  Serial.println("Starting Rotary Encoder Test");
}

// On interrupt, read input pins, compute new states, and adjust
// counts.  This code depends on encoder data appearing like ddccbbaa,
// ie data is in properly paired consecutive port bits.
ISR(PCINT2_vect) {
  byte abNew = PIND & 0xfc; // Read pins  2, 3, 4, 5, 6, 7,
  byte changes = abNew^abOld;   // Use XOR to flag changed bits
  char wayflag = 2*abNew^abNew; // Use XOR to get state-bits
  byte test2;
  for (byte i=0; i<3; ++i) {
    // For PD2-7, low 2 bits aren't relevant.  Move the shifts to 
    // end of for loop if low 2 bits carry encoder info.
    wayflag >>= 2;      // Discard low state-bits
    changes >>= 2;      // Discard low change-bits
    test2 = changes & 3;    // Get 2 bits of interest
    if (test2==1) {
      count[i] += 1-(wayflag&2);
      changedCount = true;
    } else
      if (test2==2) {
        count[i] -= 1-(wayflag&2);
        changedCount = true;
      }
  }
  abOld = abNew;        // Save new state
}

void loop() {
  if (changedCount) {
    changedCount = false;   // Race condition can be ignored
    for (byte i=0; i<3; ++i) {
      Serial.print(count[i]);   // Announce counts
      Serial.print("  ");
    }
    Serial.println(millis());   // Announce change-time
  }
}

Note, setupRotors3() and a few other statements in the program were generated using the ISR-framework-generating Sketch from an answer to an earlier question. If you need to use other input pins than those shown in the program above, you can use the framework generator to produce a setup routine and port-I/O statements for them.

Following is some output from the program as above, from when I turn two of the three 60-counts-per-turn encoders as fast as I can by hand, for half a turn each at the same time (about 4 RPS, or 240 RPM).

-2  1  0  1127
-2  2  0  1133
-2  3  0  1148
-2  4  0  1155
-3  4  0  1165
-3  5  0  1166
-4  5  0  1169
-4  6  0  1170
-5  6  0  1174
-5  7  0  1177
-6  7  0  1178
-6  8  0  1181
-7  8  0  1184
-7  9  0  1187
-8  9  0  1189
-8  10  0  1190
-9  10  0  1196
-9  11  0  1197
-10  12  0  1200
-10  12  0  1201
-11  12  0  1205
-11  13  0  1207
-12  13  0  1210
-12  14  0  1211
-12  15  0  1215
-13  15  0  1216
-13  16  0  1220
-14  16  0  1221
-15  16  0  1224
-15  17  0  1226
-16  17  0  1229
-16  18  0  1230
-17  18  0  1234
-17  19  0  1235
-18  19  0  1239
-18  20  0  1241
-19  20  0  1243
-19  21  0  1247
-20  21  0  1248
-21  21  0  1251
-21  22  0  1252
-22  22  0  1255
-22  23  0  1258
-23  23  0  1259
-24  23  0  1263
-24  24  0  1264
-25  24  0  1266
-25  25  0  1269
-26  25  0  1271
-27  25  0  1275
-27  26  0  1275
-28  26  0  1280
-28  27  0  1281
-29  27  0  1284
-29  28  0  1288
-30  28  0  1291
-31  28  0  1301
-31  29  0  1341
-32  29  0  1346
-32  30  0  1385
-33  30  0  1389
  • Looks like we had similar ideas. I didn't spot your answer until I had rewritten my code. :) We even have similar debugging output. – Nick Gammon Dec 24 '16 at 1:30

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.