Rotary encoder basics

I have looked around a bunch and read what I can find on rotary encoders but I can’t seem to lock down a basic implementation. I’m sure this is due to my lack of hardware understanding.

goal: use encoder to increment/decrement a counter variable while rest of program loops.

what is the right setup?:

pinMode(analog or digital?, PULLUP or PULLDOWN or ?);
attachInterrupt( InterruptablePin ,increment_function , CHANGE or ?);
Serial1.print(counter_var_incremented_in_function);

whats the correct wiring layout with whichever is the better (or simplest) implementation?

massive thanks for any help!

Here is one example …

Use the interrupt options otherwise the loop may not be fast enough to catch the pulses.

I tried adapting a couple of those but my understanding is clearly lacking. Most of them also say they are not optimal in one way or other. Here’s 2 examples that just stream numbers to the display as I have “adapted” them to the core.

#define encoder0PinA A2
#define encoder0PinB A3

volatile unsigned int encoder0Pos = 0;
static boolean rotating=false;

void setup() {
  pinMode(encoder0PinA, INPUT); 
  digitalWrite(encoder0PinA, HIGH);       
  pinMode(encoder0PinB, INPUT); 
  digitalWrite(encoder0PinB, HIGH); 

  attachInterrupt(A2, rotEncoder, CHANGE);  
  Serial1.begin (9600);
}

void rotEncoder(){
  rotating=true; 
  // If a signal change (noise or otherwise) is detected
  // in the rotary encoder, the flag is set to true
}

void loop() {
  while(rotating) {
    delay(2);
    // When signal changes we wait 2 milliseconds for it to 
    // stabilise before reading (increase this value if there
    // still bounce issues)
    if (digitalRead(encoder0PinA) == digitalRead(encoder0PinB)) {  
      encoder0Pos++;
    } 
    else {                                   
      encoder0Pos--;
    }
    rotating=false; // Reset the flag back to false
    Serial1.println(encoder0Pos);
  }
}

And a Classy one:

The code:

// This #include statement was automatically added by the Spark IDE.
#include "Encoder.h"




Encoder encoder(A2, A3);

void setup() { 
    attachInterrupt(A2, doEncoder, CHANGE); 
    Serial1.begin (9600);
    Serial1.println("start");
} 

void loop(){
    delay(1000);
    // do some stuff here - the joy of interrupts is that they take care of themselves
}

void doEncoder(){
    encoder.update();
    Serial1.println( encoder.getPosition() );
}

The Class:

class Encoder {
  /*  
    wraps encoder setup and update functions in a class

    !!! NOTE : user must call the encoders update method from an
    interrupt function himself! i.e. user must attach an interrupt to the
    encoder pin A and call the encoder update method from within the 
    interrupt

    uses Arduino pullups on A & B channel outputs
    turning on the pullups saves having to hook up resistors 
    to the A & B channel outputs 

    // ------------------------------------------------------------------------------------------------
    // Example usage :
    // ------------------------------------------------------------------------------------------------
        #include "Encoder.h"

        Encoder encoder(2, 4);

        void setup() { 
            attachInterrupt(0, doEncoder, CHANGE); 
            Serial.begin (115200);
            Serial.println("start");
        } 

        void loop(){
            // do some stuff here - the joy of interrupts is that they take care of themselves
        }

        void doEncoder(){
            encoder.update();
            Serial.println( encoder.getPosition() );
        }    
    // ------------------------------------------------------------------------------------------------
    // Example usage end
    // ------------------------------------------------------------------------------------------------
  */
public:

    // constructor : sets pins as inputs and turns on pullup resistors

    Encoder( int8_t PinA, int8_t PinB) : pin_a ( PinA), pin_b( PinB ) {
        // set pin a and b to be input 
        pinMode(pin_a, INPUT); 
        pinMode(pin_b, INPUT); 
        // and turn on pullup resistors
        digitalWrite(pin_a, HIGH);    
        digitalWrite(pin_b, HIGH);                 
    };

    // call this from your interrupt function

    void update () {
        if (digitalRead(pin_a)) digitalRead(pin_b) ? position++ : position--;
        else digitalRead(pin_b) ? position-- : position++;
    };

    // returns current position

    long int getPosition () { return position; };

    // set the position value

    void setPosition ( const long int p) { position = p; };

private:

    long int position;

    int8_t pin_a;

    int8_t pin_b;
};

A project that I have worked on, Brewpi, uses a rotary encoder - the implementation uses grey codes that avoids the debounce issue (one turn reported as multiple interrupts.)

RotaryEncoder.h
RotaryEncoder.cpp

I have a tutorial I am working on using a rotary encoder with interrupts. It has been a busy Summer with other things but I will try to get it pulled together soon.

Some advice–I have found all the software debouncing can be eliminated by adding some 0.1uF caps between the core inputs and ground.

ok, i managed to cobble together something that ~“works”.
articles that helped were http://hacks.ayars.org/2009/12/using-quadrature-encoder-rotary-switch.html & http://bildr.org/2012/08/rotary-encoder-arduino/
One of the many things I didn’t get was that the encoder was activating the interrupt 4 times per “click” of movement.

I have D1 connected to pin “A” of the encoder, D0 to pin “B” and the center common pin going to ground.
As suggested by @bko I have #104 capacitors in between the encoder A & B pins and the D0 & D1 pins respectively going to ground ( I think this is right, please correct if not).

int encoderPinA = D0;
int encoderPinB = D1;
volatile int counter;

void setup() {
  pinMode(encoderPinA, INPUT_PULLUP); 
  pinMode(encoderPinB, INPUT_PULLUP);
  attachInterrupt(encoderPinA, adjust_counter, FALLING); 
  
  Serial1.begin (9600);
}

void loop(){ 
    //Do stuff here
      
    clear_screen();
    Serial1.print(counter);
    delay(1000); //just here to slow down the output, and show it will work  even during a delay
}

void adjust_counter(){
    if (digitalRead(encoderPinB))
        counter-- ;
    else
        counter++ ;

    Serial1.print("called"); // sanity check how many times interrupt was called
}

void clear_screen (){
    Serial1.write(254); // move cursor to beginning of first line
    Serial1.write(128);
    
    Serial1.write("                "); // clear display
    Serial1.write("                ");
    
    Serial1.write(254); // move cursor to beginning of first line
    Serial1.write(128);
}
1 Like

bko, Did you ever post your tutorial on using the rotary encoder with particle.io? I have success with the Arduino using this library: http://www.pjrc.com/teensy/td_libs_Encoder.html but I have not been able to port it to Particle for the photon. Thanks
Kyle

Hi @kbowerma

I never posted a tutorial on this but I do have some code. I found that the software debouncing was not working for me with many examples that I tried, so putting the input pins to pull-up mode, I added two 0.1uF capacitors, one from A6 to GND and one from A7 to GND to debounce the inputs. That worked perfectly. Hope this helps!


void doEncoderA();
void doEncoderB();

int encoderA = A7;
int encoderB = A6;

volatile bool A_set = false;
volatile bool B_set = false;
volatile int encoderPos = 0;

int prevPos = 0;
int value = 0;

void setup() {
  Serial.begin(9600);
  delay(1000);
  Serial.println(encoderPos);
  pinMode(encoderA, INPUT_PULLUP);
  pinMode(encoderB, INPUT_PULLUP);
  attachInterrupt(encoderA, doEncoderA, CHANGE);
  attachInterrupt(encoderB, doEncoderB, CHANGE);
}

void loop() {
    if (prevPos != encoderPos) {
        prevPos = encoderPos;
        Serial.println(encoderPos);
    }
    delay(50);
}
//----------------------------------------------------------------

void doEncoderA(){
  if( digitalRead(encoderA) != A_set ) {  // debounce once more
    A_set = !A_set;
    // adjust counter + if A leads B
    if ( A_set && !B_set ) 
      encoderPos += 1;
  }
}

// Interrupt on B changing state, same as A above
void doEncoderB(){
   if( digitalRead(encoderB) != B_set ) {
    B_set = !B_set;
    //  adjust counter - 1 if B leads A
    if( B_set && !A_set ) 
      encoderPos -= 1;
  }
}
2 Likes

Ah man you are the best. I was just looking for more examples and found this. Thanks @bko

Quick question. I tried your code with D0 and D1 and it only worked clockwise. Why is that? It worked find on A0 and A1. They both doing a digitalRead()

Hi @kbowerma

I bet it is because D0 and D1 share an interrupt. Perhaps @BDub can help us out with a definite answer as to why.

If it is a shared interrupt problem, I bet we could re-write the code to work since right now it assumes that if you land in doEncoderA it is because pin encoderA changed, but that assumption could be tested explicitly.

@kbowerma I don’t believe EXTI interrupts are supported on D0 per this documentation PR that is waiting to be merged.
https://github.com/spark/docs/pull/75/files

Could you try switching to use D1 and D2 and see if things improve?

1 Like

I will but I am actually ok analog pins, it just so happened I had them on digital ones will I was trying some other encoder examples. Thanks to @bko for his example that I am using, it works perfectly.

My C++ skills are more like a C-- and I tried to move Brian’s example to a library but still can’t get it to compile. If there is any interest I would be happy to post it to github if anyone wants to look at it.

I would like to see that library, I might not have time to look at it a lot but I think the photon needs a good rotary encoder library

@bko @kbowerma I’m looking at BKO’s Quadurature Encoder code he posted awhile back and I’m wondering if it’s also perfectly fine for use with the Photon or if the interrupt input pins need changed due to not running this on the older Spark Core.

Also I’m using a magnetic off axis rotary encoder chip that provides digital A & B output pulses which I think will not need to be debounced since it is not a mechanical switch so I’m pretty sure I will not need to add the capicators avoid debouncing.

Any feedback is appreciated!

Hi @RWB

If you switch to D1 and D2, I think it will work on Photon.

Do you know the maximum rate at which the signals change (or the RPM of the shaft)? That could be limiting if it is too fast.

1 Like

@bko Good to know I’m going to give it a try.

The RPM is low, I’m only measuring the shaft that turns 180 Degrees at a very slow rate so speed is not a issue.

I’ll report back on if it works or not.

Yeah it works like a charm on the photon. BKO is the man! I have been meaning to turn it into a library but never got around to it. For some reason I switch to A0 and A1, I think is was because I used the oled shield in SPI mode.

Here is some sample code you are welcome to. https://github.com/kbowerma/holiday_road. It has a lot of moving parts so it might not be the prettiest thing in town.

1 Like

Thanks to everyone above. I created a Gist that strips @kbowerma’s code down to just the encoder parts. Thanks to him and @bko for the code and help. Caps and code work great.

1 Like