KY-040 Rotary Encoder

At my previous workspace, I had a LED strip controlled by an Arduino Nano. With my new workspace, I want to extend the program for the Arduino so you can set the brightness and mode (of the LEDs) with a mechanical rotary encoder.

Testing the rotary encoder

The rotary encoder is a KY-040 (WH-040). It turns and has a button. You can press the knob to activate the button. You find this rotary encoders on a lot of devices, for example 3D-printers.

Rotary encoder with Arduino Nano

Code for the rotary encoder

// inputs
int pin1 = 2;
int pin2 = 3;
int pin3 = 4;

// pin states
int pinState1;
int pinState2;
int pinState3;
int pinState1Previous;
int pinState2Previous;
int pinState3Previous;
  
void setup() {
  Serial.begin(9600);

  // inputs setup
  // pin1 is the button pin, which does not have an external resistor for pullup/pulldown
  pinMode(pin1, INPUT_PULLUP);
  pinMode(pin2, INPUT);
  pinMode(pin3, INPUT);
}

void loop() {
  // read the input pins
  pinState1 = digitalRead(pin1);
  pinState2 = digitalRead(pin2);
  pinState3 = digitalRead(pin3);

  // only print the state when it differs from the previousstate
  // otherwise it will spam your serial monitor
  if((pinState1Previous != pinState1) || (pinState2Previous != pinState2) || (pinState3Previous != pinState3))
  {
    // print out the state of the pins pins
    Serial.print(pinState1);
    Serial.print(" ");
    Serial.print(pinState2);
    Serial.print(" ");
    Serial.println(pinState3);
  }

  // write the state in previous state to remember the state
  pinState1Previous = pinState1;
  pinState2Previous = pinState2;
  pinState3Previous = pinState3;
}

Output of the rotary encoder

The output of the Arduino serial monitor when pressing the button:

0 0 0
1 0 0

The output of the Arduino serial monitor when turning right:

1 0 0
1 0 1
1 1 1
1 1 0
1 0 0

The output of the Arduino serial monitor when turning left:

1 0 0
1 1 0
1 1 1
1 0 1
1 0 0

Because a pullup resistor is used for reading the button (by using INPUT_PULLUP), the button press is inverted.

Direction of the rotary encoder

Reading the direction is a bit more tricky. When turning right, pin3 is leading. When pin2 then becomes the same value as pin3, the encoder is turned right. At turning left, pin2 is leading. So in the program, the previous state (only the previous state from pin2 is used!) needs to be reminded.

// inputs
int pin1 = 2;
int pin2 = 3;
int pin3 = 4;

// pin states
int pinState1;
int pinState2;
int pinState3;
int pinState1Previous;
int pinState2Previous;
int pinState3Previous;

bool pin2first = false;
bool pin3first = false;
int counter = 0;
  
void setup() {
  Serial.begin(9600);

  // inputs setup
  // pin1 is the button pin, which does not have an external resistor for pullup/pulldown
  pinMode(pin1, INPUT_PULLUP);
  pinMode(pin2, INPUT);
  pinMode(pin3, INPUT);
}

void loop() {
  // read the input pins
  pinState1 = digitalRead(pin1);
  pinState2 = digitalRead(pin2);
  pinState3 = digitalRead(pin3);

  if(pinState2 != pinState2Previous)
  {
    if(pinState3 != pinState2Previous)    //checks the previous state of pin2!
    {
      counter ++; 
    }
    else
    {
      counter --;
    }
    Serial.println(counter);
  }

  // write the state in previous state to remember the state
  pinState1Previous = pinState1;
  pinState2Previous = pinState2;
  pinState3Previous = pinState3;
}

Interrupt is better

With a bigger code, using an interrupt is better for the overall performance. If you use polling (constantly checking if the button is pressed or not) and your code takes long to execute, it may happen that you miss the user input.

// inputs
int pin1 = 2;
int pin2 = 3;
int pin3 = 4;

// pin states
int pinState1;
int pinState2;
int pinState3;
int pinState1Previous;
int pinState2Previous;
int pinState3Previous;

bool pin2first = false;
bool pin3first = false;
int counter = 0;

bool rotaryEncoderFlag = false;
bool rotaryEncoderButtonFlag = false;
 
void setup() {
  Serial.begin(9600);

  // inputs setup
  // pin1 is the button pin, which does not have an external resistor for pullup/pulldown
  pinMode(pin1, INPUT_PULLUP);
  pinMode(pin2, INPUT);
  pinMode(pin3, INPUT);

  // interrupt on pin1 and pin2
  // interrupt pins on the Nano are pins 2 and 3 on the board
  // pin1 is pin 2 on the board
  // pin2 is pin 3 on the board
  attachInterrupt(digitalPinToInterrupt(pin1), pin1ISR, FALLING);
  attachInterrupt(digitalPinToInterrupt(pin2), pin2ISR, CHANGE);
}

void loop() 
{
  if(rotaryEncoderButtonFlag == true)
  {
    Serial.println("button pressed");
    rotaryEncoderButtonFlag = false;
  }

  if(rotaryEncoderFlag == true)
  {
    if(pinState2 != pinState2Previous)
    {
      if(pinState3 != pinState2Previous)    //checks the previous state of pin2!
      {
        counter ++; 
      }
      else
      {
        counter --;
      }
      Serial.println(counter);
    }
    rotaryEncoderFlag = false;

    pinState2Previous = pinState2;
    pinState3Previous = pinState3;
  }
}

void pin1ISR()
{
  rotaryEncoderButtonFlag = true;
}

void pin2ISR()
{
  rotaryEncoderFlag = true;
  pinState2 = digitalRead(pin2);
  pinState3 = digitalRead(pin3);
}

You may also like...

Leave a Reply

Your email address will not be published. Required fields are marked *