rotary encoder/Reading it/05. The first count
Reading it · 05 of 10

The first count

Five wires, no library, and a sketch that watches CLOCK. Each time it falls, the sketch looks at DATA: still HIGH, CLOCK went first and the count goes up; already LOW, DATA went first and it goes down. Turn the knob and the serial monitor counts.

Five wires

Five wires
Your board
Wires
5
VCC to
3V3
CLOCK, DATA, BTN
GPIO 4, GPIO 5, GPIO 6
GND to GND, VCC to 3V3, CLOCK to GPIO 4, DATA to GPIO 5, BTN to GPIO 6. VCC is the HIGH all three lines reach, so on a 3.3 V board it has to be 3V3, never 5V. None of the three pins is read by the ESP32-S3 at boot, and every one of them can raise an interrupt.

GND to GND. VCC to your board's logic supply: 5V on an Uno, 3V3 on the three 3.3 V boards. CLOCK, DATA and BTN to three digital pins. NC stays unconnected.

The pins are the same in every sketch in this book: D2, D3 and D4 on an Uno; GPIO 25, GPIO 26 and GPIO 27 on an ESP32; GPIO 4, GPIO 5 and GPIO 6 on an ESP32-S3; GP13, GP14 and GP15 on a Pico. None of them is read by the chip at boot or tied up with its flash memory, so the block cannot stop the board from starting. This sketch would work on any plain digital pins; the later ones need pins that can raise an interrupt, and on an Uno only D2 and D3 can.

The sketch

It keeps the last level of CLOCK it saw and compares every new read with it. Only on the pass where CLOCK has just gone from HIGH to LOW does it do anything: it reads DATA once and moves the count.

  • DATA still HIGH means CLOCK fell first. count++.
  • DATA already LOW means DATA fell first. count--.

Rises of CLOCK are ignored, so one cycle of the contacts is one step of the count. Two contacts out of step has the waveforms this is reading.

What you should see

The serial monitor at 115200 prints the count each time it changes. Turn the knob one way a few clicks, then back:

1
2
3
2
1
0

If clockwise counts down, swap count++ and count--: which contact leads is the part's choice, not a fault. If a click moves the count by two, or two clicks move it by one, your part's clicks do not sit one cycle apart; mark the knob, turn it once round and compare.

Turn it slowly and it counts every click. Flick it and it may not, and the reason is in why polling misses steps.

The code

No library. pinMode makes CLOCK and DATA inputs; the loop reads CLOCK, and on the pass where it has just gone LOW it reads DATA to find the direction. Change the three pin numbers to the pins you wired.

rotary_encoder_first_count.ino
/*
  Rotary Encoder - first count                         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;       // not used until the next sketch

long count = 0;
int lastClock = HIGH;           // at rest, the pull-up holds it HIGH

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

void loop() {
  int clockLevel = digitalRead(CLOCK_PIN);

  if (clockLevel != lastClock) {    // CLOCK has changed...
    if (clockLevel == LOW) {        // ...and it has just fallen
      if (digitalRead(DATA_PIN) == HIGH) {
        count++;                    // CLOCK fell first
      } else {
        count--;                    // DATA fell first
      }
      Serial.println(count);
    }
    lastClock = clockLevel;
  }
}

INPUT, not INPUT_PULLUP: the block has its own 10 kΩ pull-ups on CLOCK and DATA. The sketch counts one step per fall of CLOCK. There is no delay() in loop(), on purpose: the next articles are about what one does to the count.

When it does not work

It counts down when I turn clockwise.

Nothing is wrong. Which contact leads when you turn clockwise depends on the part. Swap count++ and count-- in the sketch, or swap the numbers in CLOCK_PIN and DATA_PIN, and it counts the other way.

It counts one for every two clicks, or two for every click.

The sketch counts one fall of CLOCK per cycle. On many EC11 parts one click is one cycle, but on parts whose clicks sit every half-cycle it counts every other click. Turn the knob one full turn and compare the count with the clicks you felt.

Nothing prints when I turn it.

Check VCC and GND first: without them CLOCK never rises or never falls. Then the pin numbers: CLOCK_PIN and DATA_PIN are GPIO numbers, printed beside the pins on your board, not positions along the header. On an ESP32-S3, also set Tools > USB CDC On Boot to Enabled.

Turned quickly, the count stalls or jumps backwards.

This sketch has to read CLOCK within a quarter of a cycle of each fall, and anything slow in loop() makes it late. The next chapter shows why, and counts with an interrupt instead.

Where this goes next

BTN reads HIGH when the knob is pushed in, and a sketch that uses it to zero the count.

The switch in the shaft

Edit this page — content/books/rotary-encoder/the-first-count.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