rotary encoder/Reading it/06. The switch in the shaft
Reading it · 06 of 10

The switch in the shaft

Push the knob in and a switch inside the encoder joins BTN to VCC. A 10 kΩ pull-down holds BTN at 0 V the rest of the time, so released reads LOW and pushed reads HIGH. It bounces like any switch, so the sketch debounces it, and uses it to set the count back to zero.

A third switch

The encoder has a push switch built into its shaft. Push the knob in towards the board and it closes; let go and a spring opens it again. On this board one side of it is VCC and the other side is BTN.

The switch in the shaft
Your board
The knob
BTN
0.0 V
Reads
LOW
Current
none
Released, the switch is open and R2 holds BTN at 0 V: GPIO 6 reads LOW, steadily, rather than floating. That is what a pull-down is for. Nothing flows. The pin wants INPUT, not INPUT_PULLUP: the chip's pull-up would fight R2.

Released, the switch is open and R2, a 10 kΩ resistor from BTN to GND, holds BTN at 0 V. Your pin reads LOW, steadily, rather than floating. Pushed in, the switch joins BTN to VCC and your pin reads HIGH. That is a pull-down, the same arrangement as the TK04 push button, and the pull-down there explains it from the start.

It is the opposite way round from CLOCK and DATA, which rest HIGH and go LOW. The two rotation contacts close to GND; the push switch closes to VCC. So in one sketch, LOW on CLOCK is a contact closing, and HIGH on BTN is a push.

It bounces

BTN has no capacitor, unlike CLOCK and DATA. Its switch bounces as any metal contact does, and a loop that reads it thousands of times a second sees one push as several. The sketch uses the TK04's debounce: believe a change only once BTN has held still for 20 ms. Debouncing with millis() walks through it line by line.

The sketch

The knob half is the first count, unchanged. The button half keeps two readings apart: reading is what BTN says now, and pushed is what the sketch has decided. When pushed changes to HIGH, the count goes back to zero and the monitor prints zero.

That makes the encoder a control with a home position: turn to pick a value, push to start again. The same shape, with the count clamped between two limits, is a volume, a menu line or a brightness.

The code

The first count, plus the shaft switch. The knob moves the count exactly as before; a push, debounced for 20 ms, sets it back to zero. Change the three pin numbers to the pins you wired.

rotary_encoder_with_button.ino
/*
  Rotary Encoder - count and zero                      TK06 / /p/tk06

  Wiring. Count from the square pad on the TinkerBlock board, knob
  up, header at the bottom:

    GND    -> GND
    VCC    -> 5V on an Uno; 3V3 on an ESP32, ESP32-S3 or Pico
              (CLOCK, DATA and BTN all reach whatever VCC is)
    NC     -> nothing   (unconnected on the board)
    BTN    -> D4 on an Uno, GPIO 27 on an ESP32, GPIO 6 on an
              ESP32-S3, GP15 on a Raspberry Pi Pico
    CLOCK  -> D2, GPIO 25, GPIO 4, GP13   (the same four boards)
    DATA   -> D3, GPIO 26, GPIO 5, GP14

  Arduino IDE
    Tools > Board                 your board, e.g. ESP32S3 Dev Module
    Tools > Port                  the one that appears when you plug in
    Tools > USB CDC On Boot       Enabled   (ESP32-S3 only)
    No library needed.
*/

// CLOCK, DATA and BTN, as GPIO numbers.
// Uno: 2, 3, 4. ESP32: 25, 26, 27. ESP32-S3: 4, 5, 6. Pico: 13, 14, 15.
const int CLOCK_PIN = 4;
const int DATA_PIN = 5;
const int BUTTON_PIN = 6;
const unsigned long DEBOUNCE_MS = 20;   // longer than bounce lasts

long count = 0;
int lastClock = HIGH;
int lastReading = LOW;          // BTN as last read; released is LOW
int pushed = LOW;               // BTN as we have decided it is
unsigned long lastChange = 0;   // millis() when BTN last moved

void setup() {
  Serial.begin(115200);
  pinMode(CLOCK_PIN, INPUT);    // the block has its own pull-ups
  pinMode(DATA_PIN, INPUT);
  pinMode(BUTTON_PIN, INPUT);   // and its own pull-down on BTN
}

void loop() {
  // The knob, exactly as in the first count.
  int clockLevel = digitalRead(CLOCK_PIN);
  if (clockLevel != lastClock) {
    if (clockLevel == LOW) {
      if (digitalRead(DATA_PIN) == HIGH) {
        count++;                // CLOCK fell first
      } else {
        count--;                // DATA fell first
      }
      Serial.println(count);
    }
    lastClock = clockLevel;
  }

  // The shaft switch: HIGH is pushed. Debounced, as on the TK04.
  int reading = digitalRead(BUTTON_PIN);
  unsigned long now = millis();
  if (reading != lastReading) { // the contacts moved: restart the clock
    lastReading = reading;
    lastChange = now;
  }
  if (now - lastChange >= DEBOUNCE_MS && reading != pushed) {
    pushed = reading;
    if (pushed == HIGH) {       // one real push
      count = 0;
      Serial.println("zero");
    }
  }
}

BTN has a pull-down, so pushed is HIGH, and the pin is INPUT for the same reason as CLOCK and DATA: the block brings its own resistors. The debounce is the TK04 push button's, line for line: reading is what the pin says now, pushed is what the sketch has decided.

When it does not work

BTN reads pushed all the time.

Almost always a sketch written for a pull-up button board, which treats LOW as pushed. This block has a pull-down and HIGH is pushed. Also check BTN is not on a 3V3 or 5V pin by mistake, where it reads HIGH for ever.

BTN never reads pushed.

Check VCC: the switch joins BTN to VCC, so with VCC unconnected there is nothing to join it to. Then check BUTTON_PIN is the GPIO number BTN is wired to, and that the sketch uses INPUT, not INPUT_PULLUP.

Pushing the knob also moves the count by one.

Pushing can nudge the shaft a little, and a knob resting close to the edge of a click can tip over it. Push straight down the shaft. If it happens often, the knob is resting between clicks; turn it firmly into one first.

One push zeroes the count, but a second push straight after does nothing.

It did zero it; the count was already 0. The sketch acts once per push, on the change to HIGH, not all the time the knob is held in. Hold it in and nothing more happens until you let go and push again.

Where this goes next

The first count reads CLOCK once a loop, and a slow loop reads it too late.

Why polling misses steps

Edit this page — content/books/rotary-encoder/the-switch-in-the-shaft.mdx

Community

Questions about this product

See what other owners have asked, and read their solutions.

Ask a question ↗

Rotary Encoder

Loading discussions…

Discuss this article

Ask about this page. The answer stays here, on the page it belongs to, for whoever hits the same wall next.

Browse Modules and blocks on the forum