I have a standard 5 ping rotary encoder (3 pins for encoder, 2 pins for button). I'm trying to get a working code that manages to read the rotary encoder.

I began with the code I found here: http://bildr.org/2012/08/rotary-encoder-arduino/

My problem is that the interrup method never being called. I read that all the GPIO pins on NodeMCU support interrupts (except D16). Here's my code:

volatile int lastEncoded = 0;
volatile long encoderValue = 0;

long lastencoderValue = 0;

int lastMSB = 0;
int lastLSB = 0;

void setup() {

  pinMode(D0, INPUT); 
  pinMode(D3, INPUT);

  digitalWrite(D0, HIGH); //turn pullup resistor on
  digitalWrite(D3, HIGH); //turn pullup resistor on  

  attachInterrupt(D0, handleInterrupt, CHANGE);
  attachInterrupt(D3, handleInterrupt, CHANGE);

void handleInterrupt() {
  int MSB = digitalRead(D0); //MSB = most significant bit
  int LSB = digitalRead(D3); //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

void loop() {
    Serial.print("Encoder: ");

migrated from electronics.stackexchange.com Oct 9 '16 at 4:02

This question came from our site for electronics and electrical engineering professionals, students, and enthusiasts.

  • My initial thought is that if this code is where the problem is then you would be better off in the Arudino SE. You did not provide a schematic though- do you have pullup resistors on the A/B pins and is the common grounded? – Spehro Pefhany Oct 8 '16 at 22:08
  • Check that interrupts are working with some simpler code, eg. use void handleInterrupt() { encoderValue ++; } instead of the more complex handler code. Then you will either know interrupts are happening, or their is a bug in the handler. – gbulmer Oct 8 '16 at 23:14
  • @SpehroPefhany I have the same wiring as in the blog article I linked. So I found that I need a debounce circuit to make this work flawlessly. – papaiatis Oct 9 '16 at 17:37

Different RotaryEncoders work in different ways. Mine connects the two outputs sequentially to the Gnd or + inputs, depending on the rotation direction. The resultant phase changes need to be detected and decoded by the processor.

I took your code and ran it on a LoLin NodeMCU using my 5-pin RotaryEncoder.

I connected (NodeMCU to RotaryEncoder):

  • D0 to Clk;
  • D3 to DT;
  • GND to GND; and
  • 3V to +.

I ran the code, and it worked: the printed value went up when I rotated it in one direction, and down when it rotated in the other. The "bounce" was terrible though - getting just a single increment or decrement for one "detent" was difficult; I typically got 2-4.

Because of my encoder, I was able to remove the two digitalWrite(..., HIGH); lines and it continued to work. Perhaps that's what your problem is? Did you connect the + input? You mention the two switch pins: perhaps there's only one switch pin, that connects to the Gnd pin?

EDIT: The code!

So I went away and wrote a RotaryEncoder polling library, rather than the interrupt code that the original question was based on. The idea is that you can poll a rotary encoder (relatively) infrequently, and stil "catch" the transitions as it rotates. Of course this won't work if you poll it too slowly - your mileage may vary!

But here are three files:

  • The header and library source for the RotaryEncoder module; and
  • An example usage of the module.


// RotaryEncoder.h
// This library manages a Keyes KY-040 Rotary Encoder by sampling the
// appropriate pins when requested. It is NOT interrupt driven, so you'll need
// to sample often.
// The encoder has five pins in a 0.1" row, so you can directly plug it into
// an Arduino's data-pin sockets. The sequence of pins matches the order of
// parameters in the constructor - use ascending (or descending) numbers for
// ease.
// If you pass in NoPin for gnd, vcc, or sw (the switch), then that parameter
// is ignored. You'll need to organise those pins yourself! Of course, NoPin is
// not valid for dt or clk.

#ifndef RotaryEncoder_h
#define RotaryEncoder_h

class RotaryEncoder {

public: // Typedefs and enums

   typedef byte Pin;

   typedef signed char OptPin;

   enum Pins : OptPin {
      NoPin = -1
   }; // Pins

public: // Methods

   // Define which pins to use for the various functions.
   // If Optional Pins are "NoPin", then they won't be initialised
   RotaryEncoder(OptPin gnd, OptPin vcc, OptPin sw, Pin dt, Pin clk);

   // Start RotaryEncoder with optional maxBias (call Rebias() periodically)
   void Begin(byte maxBias=1);

   // Returns true if switch is currently pressed
   bool Switch() const;

   // Returns 0 for no change, +ve for clockwise, -ve for counter-clockwise
   int Get();

   // Call this periodically to reduce the bias
   void Rebias(int delta=1);

private: // Variables

   const OptPin gndPin;

   const OptPin vccPin;

   const OptPin swPin;

   const Pin dtPin;

   const Pin clkPin;

   byte maxBias;

   byte bias;

   bool clk;

   bool dt;

}; // RotaryEncoder

#endif // RotaryEncoder_h


// RotaryEncoder.cpp

#include <Arduino.h>
#include "RotaryEncoder.h"

RotaryEncoder::RotaryEncoder(OptPin gnd, OptPin vcc, OptPin sw, Pin dt, Pin clk) :
               dt(false) {
} // RotaryEncoder::RotaryEncoder(gnd, vcc, sw, dt, clk)

void RotaryEncoder::Begin(byte maxBias) {
   this->maxBias = maxBias;
   if (gndPin!=NoPin) {
      pinMode(gndPin, OUTPUT); // Let's power the device,
      digitalWrite(gndPin, LOW);  // Power -ve with 0
   } // if
   if (vccPin!=NoPin) {
      pinMode(vccPin, OUTPUT); // ,with both - and +
      digitalWrite(vccPin, HIGH); // Power +ve with 1
   } // if
   if (swPin!=NoPin) {
      pinMode(swPin, INPUT_PULLUP); // They pushed it!
   } // if
   pinMode(dtPin,  INPUT); // One half
   pinMode(clkPin, INPUT); // The other half
} // RotaryEncoder::Begin(maxBias)

bool RotaryEncoder::Switch() const {
   return swPin!=NoPin ?
          digitalRead(swPin)==LOW :
} // RotaryEncoder::Switch()

int RotaryEncoder::Get() {
   bool clkNew = digitalRead(clkPin);
   if (clk!=clkNew) {
      clk = clkNew; // Clock's changed; sample data
      bool dtNew = digitalRead(dtPin);
      if (dt!=dtNew) {
         dt = dtNew; // Data's changed; remember it!
         if (clk) {  // Only pay attention to Clk Low-High
            int temp = bias;
            bias = maxBias;
            if (dt) {
               return -temp;
            } // if
            else {
               return +temp;
            } // else
         } // if
      } // if
   } // if
   return 0;
} // RotaryEncoder::Get()

void RotaryEncoder::Rebias(int delta) {
   if (bias<=1) {
   } // if
   bias -= bias>delta ? delta : bias-1;
} // RotaryEncoder::Rebias()


// RotaryBlinker
// This example changes how fast the built-in LED blinks by using the
// RotaryEncoder library.
// It starts with an intermediate value (80h).
// * If you rotate it left, it slows down (then it jumps to the fastest).
// * If you rotate it right, it speeds up (then it jumps to the slowest).
// The jumps are because there is no logic to handle wrap-around - an exercise
// for the reader!
// Also note that a bias feature has been included. The faster you turn the knob, the
// exponentially quicker the rate changes. This is done by setting a fast rate on
// every detent, then when the millisecond has changed it re-biases the knob lower.
// To handle the knob correctly, it needs to INCREASE the time interval when
// dialling LOWER - hence you'll see a negative number in the code.
// It also checks for the switch. If it's pressed, it simply turns the LED full on.

#include <RotaryEncoder.h>

// Choose pins that are convenient for the RotaryEncoder's pins
#if defined(ESP8266)

const byte LED = D5;                   // No built-in LED! Choose... this one!
const unsigned baud = 115200;
const RotaryEncoder::OptPin GND = -1; // Use GND pin
const RotaryEncoder::OptPin VCC = -1; // Use V+ pin
const RotaryEncoder::OptPin SW  =  D4; // Switch
const RotaryEncoder::Pin    DT  =  D3; // Data
const RotaryEncoder::Pin    CLK =  D2; // Clock

#elif defined(ADifferentBoard) // Put the board's definition here

// And then change these assignments
const byte LED = LED_BUILTIN;
const unsigned baud = 57600;
const RotaryEncoder::OptPin GND =  8; // Need 0V
const RotaryEncoder::OptPin VCC =  9; // Need +V
const RotaryEncoder::OptPin SW  = 10; // Switch
const RotaryEncoder::Pin    DT  = 11; // Data
const RotaryEncoder::Pin    CLK = 12; // Clock

#else // Arduino Uno default

const byte LED = LED_BUILTIN;
const unsigned baud = 57600;
const RotaryEncoder::OptPin GND =  8; // Need 0V
const RotaryEncoder::OptPin VCC =  9; // Need +V
const RotaryEncoder::OptPin SW  = 10; // Switch
const RotaryEncoder::Pin    DT  = 11; // Data
const RotaryEncoder::Pin    CLK = 12; // Clock

#endif // Boards

// Tell library which pin does what. Note Optional Pins can be -1
RotaryEncoder encoder(GND, VCC, SW, DT, CLK);

// Setup the board for these definitions
void setup() {
    pinMode(LED, OUTPUT); // Want to see it working

    encoder.Begin(10); // Start, with a re-bias of 10
} // setup()

bool ledON = false;  // Current LED state
byte repeat = 0x80;  // How quickly to restart, in ms
byte count = repeat; // Count down 'til next time!

unsigned last = 0;   // Last time millis() was recorded

void loop() {
    // Have switch override pulsing - turn it hard ON
    if (encoder.Switch()) { // Hard override!
        ledON = false; // Pretend LED is now OFF
        digitalWrite(LED, HIGH);
    } // if
    else {
        int value = encoder.Get(); // Returns positive for clockwise turn
        repeat += -value;   // We want to INCREASE time to slow down blinking
        if (value!=0) {     // Did it change?
            Serial.print(value);    // Print latest value
            Serial.println(repeat); // Print new result
        } // if
    } // else

    // Is it time to blink the LED again?
    unsigned latest = millis();
    if (last!=latest) {
        last = latest;
        encoder.Rebias();    // Re-bias encoder, to decelerate rotation
        if (--count==0) {    // Reached zero yet?
            ledON = !ledON;  // Yes! Toggle LED
            digitalWrite(LED, ledON ? HIGH : LOW);
            count = repeat;  // Count for next time
       } // if
    } // if
} // loop()
  • So I wired everything together again and now I have the same effect as you described. The bounce is really bad. So it seems the code is okay. The hardware needs more attention. Can you suggest a good debounce circuit? – papaiatis Oct 9 '16 at 17:38
  • Actually, the easiest way to debounce the device is to not interrogate it so often. Using interrupts on every single change is just asking for trouble... I don't know the rest of your code, but does it loop reasonably quickly? Rather than read the results of the interrupt, could you simply poll the device? A rotation, even a fast one, gives pulses in the order of 10s of milliseconds, so if you could poll the encoder as to its current status every 5-10 ms, you'd get debounce for free. – John Burger Oct 10 '16 at 8:33
  • One more question though : the device I'm building is an IoT device. What if I'm rotating the encoder while I'm downloading/uploading something to the Internet? That takes hundreds of milliseconds. Not easy to create a code that loops quick enough. How would you accomplish that? – papaiatis Oct 11 '16 at 15:36
  • Agreed: that's the problem with switch bounce. You've got three options: remove the bounce in hardware (add capacitors - difficult for a Rotary Encoder); don't interrogate as often; or get each and every bounce, but ignore those that happen "too quickly". One technique is, on every switch, to get the current value of micros(). Compare that to the last saved value, and if too recent, simply ignore it. Otherwise, remember the new time as the last time, and process the change. The problem is that micros() may not update quickly enough... – John Burger Oct 12 '16 at 3:33
  • Fix to make the code inc/dec on every step instead of every second step: 1. Comment out this line in cpp if (clk) { // Only pay attention to Clk Low-High 2.Change the next conditional to: if (dt^clk) – MattiasF Aug 23 '17 at 21:17

did you try a different pin? (D2, D1) should work.. Seems like D0 does not have interrupt https://opensprinkler.com/wp-content/uploads/2016/03/NODEmcuPinOut.png

also there is a rotary encoder library so try using that ..

The library i use is able to work without interrupts Encoder by Paul Stoffregen available from Arduino Library Manager or https://www.pjrc.com/teensy/td_libs_Encoder.html

  • Which library is that? – papaiatis Nov 5 '17 at 19:31
  • See the link above – Marek Halmo Feb 14 '18 at 11:48

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.