Rotary encoder
A rotary encoder is an endless knob — no end stops — that reports how far and which way it was turned, plus a push switch in the shaft. It costs $3 and is the natural input for menus, volume and setpoints, where a potentiometer would run out of travel. The shop's part is a KY-040 module: two contacts (CLK and DT) that open and close in quadrature as you turn, pull-up resistors already fitted, and a switch that shorts SW to ground when you press the shaft.
Pins
| Pin | What it is | Type |
|---|---|---|
| CLK | Output A (pulled up) | io |
| DT | Output B (pulled up) | io |
| SW | Push switch to GND (no pull-up on most boards) | io |
| + | Supply 3.3–5 V (for the pull-ups) | power |
| GND | Ground | ground |
Power the module from 3V3 through + — that same supply feeds its 10 kΩ pull-ups, so CLK and DT swing to 3.3 V, never 5 V. SW has no pull-up on most boards, so gpiozero's internal one is what holds it high. The module's own pins are marked the same way as the canvas: CLK, DT, SW, + and GND.
Wiring it in circuit.py
from wiring import *
pi = Pi()
encoder = RotaryEncoder()
pi.V3_3 >> encoder.plus # powers the module's pull-ups: keep this 3.3 V
encoder.gnd >> pi.GND
encoder.clk >> pi.GPIO17 # output A
encoder.dt >> pi.GPIO27 # output B
encoder.sw >> pi.GPIO22 # push switch (internal pull-up in software)gpiozero.RotaryEncoder needs both A and B, so two GPIOs minimum; the switch is a separate Button on a third. Any spare GPIOs will do — there is no special function required.
Driving it in main.py
from gpiozero import RotaryEncoder, Button
from time import sleep
knob = RotaryEncoder(17, 27, max_steps=0) # max_steps=0: count without wrapping
press = Button(22, pull_up=True, bounce_time=0.02)
while True:
print(f"steps {knob.steps} pressed {press.is_pressed}")
sleep(0.2)Each detent is one full quadrature cycle, so steps counts detents: 20 per revolution on this part. threshold_steps and wrap in gpiozero tame wild counts, and encoder.when_rotated fires callbacks.
Electrical notes
- The datasheet models 20 detents per revolution; each detent plays a complete quadrature cycle — four edges, one every 400 µs — with CLK leading DT when turned clockwise. Software that watches both pins can tell the direction from which edge comes first.
- The module's pull-ups are 10 kΩ; contacts are about 1 Ω closed and bounce for 0.5–3 ms at the edges.
- Power
+from 3V3. From 5 V the module's pull-ups put 5 V on CLK, DT and SW, and the simulator raises anovervoltagewarning that fails the judge's damage check. (Most KY-040 boards work at 3.3 V.) gpiozero.RotaryEncoderdebounces withbounce_timeif you pass it; without it, the 0.5–3 ms chatter can add or lose a step. The push switch is a plain button and needs the same treatment.
Simulator vs real hardware
- Turn the knob from the inspector (or drive the angle field with a wave) while the simulation runs; click the shaft to press the switch. The canvas knob follows the angle and the contacts play the quadrature cycle.
- The model schedules one edge every 400 µs between detents, so a fast turn in Python sees a realistic burst of edges instead of an instant jump.
- A real KY-040 usually reports four edges per detent and some firmware counts two detents per click; gpiozero's
max_stepsandthreshold_stepsabsorb that. The simulator is the clean version. - Mechanical wear and missed edges at high speed are not modelled; the angle feed is authoritative.
Going deeper
- Primer: pull-up and pull-down resistors — the module's pull-ups and the switch's missing one.
- The switch half is a push button; the knob half is the digital cousin of the potentiometer.
- Read two phases as one menu: pair it with the 4×4 keypad for a control panel.
- gpiozero library notes cover the
RotaryEncoderclass in the simulator.
Further reading
- Bourns PEC11R incremental encoder datasheet — the mechanical encoder inside KY-040 modules.
- gpiozero: RotaryEncoder —
steps,max_stepsand callbacks. - Adafruit: Rotary encoder — quadrature on a bench, with pull-ups and debouncing.