Using rotary encoders

Decode a mechanical knob’s direction and count complete quadrature cycles without mistaking every bounce for a turn.

Rotary encoder module

Ingredients

Follow a part’s name for an introduction and compatibility notes.

A potentiometer reports a position within a limited arc. An incremental rotary encoder can turn indefinitely and reports changes instead. That makes it useful for a timer, a volume setting, or a menu value that should not jump when you first touch the knob.

Wire the two channels

Encoder to Uno R3
ConnectionDestination or purpose
Module GND / +GND / 5 V if the module uses 5 V pull-ups.
CLK / AD2.
DT / BD3.
SWLeave disconnected for this exercise.

A bare encoder may have no supply pin: connect its common terminal to ground and use the input pull-ups. Do not assume every module’s contact or resistor arrangement is the same. At 3.3 V, any external pull-ups must also go to 3.3 V.

Upload and turn slowly

Download the complete Arduino sketch

const byte aPin = 2, bPin = 3;
const int8_t transitions[16] = {0, -1, 1, 0, 1, 0, 0, -1, -1, 0, 0, 1, 0, 1, -1, 0};
byte previous = 0;
int quarterSteps = 0;
long count = 0;
long reportedCount = 0;
unsigned long reportedAt = 0;

byte readState() {
  return (digitalRead(aPin) << 1) | digitalRead(bPin);
}

void setup() {
  pinMode(aPin, INPUT_PULLUP); pinMode(bPin, INPUT_PULLUP);
  previous = readState();
  Serial.begin(9600);
  Serial.println("Turn slowly; count is full quadrature cycles, not necessarily detents");
}

void loop() {
  const byte current = readState();
  if (current != previous) {
    if ((current ^ previous) == 3) quarterSteps = 0; // Invalid two-bit jump.
    else quarterSteps += transitions[(previous << 2) | current];
    previous = current;
    if (quarterSteps >= 4) { ++count; quarterSteps -= 4; }
    if (quarterSteps <= -4) { --count; quarterSteps += 4; }
  }
  const unsigned long now = millis();
  if (now - reportedAt >= 100 && count != reportedCount) {
    reportedAt = now; reportedCount = count;
    Serial.println(count);
  }
}

Watch Serial Monitor at 9600 baud. Turning one way increases the count; turning the other decreases it. The sign depends on the channel order. One physical click does not always equal one full electrical cycle, so count several detents and determine the ratio on your own encoder.

Why two channels help

The two bits normally move through a sequence such as 00 → 01 → 11 → 10 → 00. Only one bit changes at a time. The reverse order means the opposite direction. The transition table adds or subtracts quarter steps and ignores impossible transitions.

Many contact bounces trace a step forward and then backward, cancelling out. A jump where both bits change is treated as invalid, and the partial cycle is discarded. This is more informative than counting every rising edge on one contact, but it is not magic: missed samples still lose movement.

Keep the sampling loop quick

The example polls continuously and limits printing to changed values at most ten times per second. Long delays, slow screen redraws, or blocking motor commands can cause missed transitions. Faster or more demanding applications need interrupt-based decoding or a suitable encoder library and input conditioning.

Troubleshooting

  • Count direction feels backward: swap A and B or reverse the sign in the application.
  • Two clicks per count: your detents and complete quadrature cycles have a different ratio. Calibrate the user-facing step size.
  • Counts disappear when turning fast: shorten the loop and check wiring before adding arbitrary delays.
  • Button press is needed too: wire SW separately and use the debounced button pattern.

Build a kitchen countdown knob with a small display and a cancel button. Compare its incremental control with an analog joystick; the appropriate input depends on whether the project needs a position or a change.