Note
Hello, welcome to the SunFounder Raspberry Pi & Arduino & ESP32 Enthusiasts Community on Facebook! Dive deeper into Raspberry Pi, Arduino, and ESP32 with fellow enthusiasts.
Why Join?
Expert Support: Solve post-sale issues and technical challenges with help from our community and team.
Learn & Share: Exchange tips and tutorials to enhance your skills.
Exclusive Previews: Get early access to new product announcements and sneak peeks.
Special Discounts: Enjoy exclusive discounts on our newest products.
Festive Promotions and Giveaways: Take part in giveaways and holiday promotions.
👉 Ready to explore and create with us? Click [here] and join today!
2.12 Rotary Encoder
Introduction
A rotary encoder is an input device that converts rotational motion into digital signals. It’s commonly used for navigating menus, adjusting values, or scrolling through items. In this project, you’ll connect a rotary encoder (with an integrated push-button) to a Raspberry Pi via the Fusion HAT+, read rotation steps, and reset the counter with the encoder’s switch.
This experiment illustrates how to interface a rotary encoder using event callbacks for smooth, low-latency input handling.
What You’ll Need
Here are the components required for this project:
COMPONENT INTRODUCTION |
PURCHASE LINK |
|---|---|
- |
|
Raspberry Pi |
- |
Circuit Diagram
Below are the schematic diagrams for the project:
Wiring Diagram
Connect the components as shown in the wiring diagram below:
Pin Mapping Used in the Example
CLK → GPIO 17
DT → GPIO 4
SW (button) → GPIO 27 (with internal pull-up)
+ → 3.3V
GND → GND
Ensure that all connections are secure. If your encoder has separate C (COM) and NO/NC for the switch, connect C to GND and NO to the SW pin (internal pull-up enabled in software).
Running the Example
All example code used in this tutorial is available in the ai-lab-kit directory.
Follow these steps to run the example:
cd ~/ai-lab-kit/python/
sudo python3 2.12_RotaryEncoder.py
After running the script, rotating the rotary encoder will increase or decrease a counter, and the current counter value is printed to the console in real time. Pressing the encoder’s button resets the counter to zero and displays a reset message. The program keeps running and responding to user input until you press Ctrl + C to exit.
Code
Below is the Python code used for this project:
#!/usr/bin/env python3
from fusion_hat.pin import Pin, Mode, Pull
import time
# GPIO pins (BCM numbering)
CLK_PIN = 17
DT_PIN = 4
SW_PIN = 27
# Initialize pins with internal pull-ups
clk = Pin(CLK_PIN, mode=Mode.IN, pull=Pull.UP)
dt = Pin(DT_PIN, mode=Mode.IN, pull=Pull.UP)
sw = Pin(SW_PIN, mode=Mode.IN, pull=Pull.UP) # Button is active LOW
raw = 0 # Raw quadrature transitions
last_clk = clk.value() # Previous CLK state
last_detent = None # Last displayed detent value
print("Rotate the knob. Press the button to reset. CTRL + C to exit.")
try:
while True:
c = clk.value()
if c != last_clk:
# Direction: DT != CLK means one direction, else the other
raw += 1 if dt.value() != c else -1
# Most encoders generate 2 transitions per detent (click)
detent = raw // 2
# Only update output when the detent value changes
if detent != last_detent:
print(f"\rCounter: {detent} ", end="", flush=True)
last_detent = detent
last_clk = c
# Reset when button is pressed
if sw.value() == 0:
raw = 0
detent = 0
print("\rCounter: 0 ", end="", flush=True)
last_detent = 0
time.sleep(0.25) # Button debounce
time.sleep(0.001) # Polling interval (1 ms)
except KeyboardInterrupt:
print("\nExit")
Understanding the Code
Imports
from fusion_hat.pin import Pin, Mode, Pull import time
Pinis used to configure and read GPIO pins for the rotary encoder.ModeandPulldefine the pin direction and internal pull-up resistors.timeis used to control polling intervals and button debounce timing.
GPIO Pin Setup
CLK_PIN = 17 DT_PIN = 4 SW_PIN = 27 clk = Pin(CLK_PIN, mode=Mode.IN, pull=Pull.UP) dt = Pin(DT_PIN, mode=Mode.IN, pull=Pull.UP) sw = Pin(SW_PIN, mode=Mode.IN, pull=Pull.UP)
CLKandDTare the two quadrature signal pins of the rotary encoder.SWis the built-in push button of the encoder.Internal pull-up resistors keep the signal stable when the encoder is idle.
The button is active LOW and reads
0when pressed.
State Variables
raw = 0 last_clk = clk.value() last_detent = None
rawcounts low-level quadrature transitions from the encoder.last_clkstores the previous CLK state to detect signal changes.last_detentkeeps track of the last displayed counter value to avoid repeated output.
Rotation Detection
c = clk.value() if c != last_clk: raw += 1 if dt.value() != c else -1
The script continuously polls the CLK pin.
A change in CLK indicates encoder movement.
The rotation direction is determined by comparing DT with CLK.
Detent (Click) Calculation
detent = raw // 2
Most rotary encoders generate two signal transitions per physical click.
Dividing by 2 converts raw transitions into clean detent counts.
Clean Console Output
if detent != last_detent: print(f"\\rCounter: {detent} ", end="", flush=True)
Output is updated only when the counter value changes.
\\rmoves the cursor back to the beginning of the line for in-place updates.flush=Trueensures immediate display on the terminal.
Button Reset Handling
if sw.value() == 0: raw = 0 detent = 0 print("\\rCounter: 0 ", end="", flush=True) time.sleep(0.25)
Pressing the button resets the counter to zero.
A short delay is used for button debounce to prevent multiple triggers.
Polling Interval and Program Exit
time.sleep(0.001)
A 1 ms polling delay balances responsiveness and CPU usage.
Pressing
Ctrl + Cexits the program gracefully.
Troubleshooting
No Output on Rotation
Cause: Miswired CLK/DT or poor ground connection.
Solution: Verify CLK→GPIO17, DT→GPIO4, GND common. Ensure 3.3V supply is correct (do not use 5V encoders directly on GPIO).
Counter Jumps Erratically (Bounces/Noise)
Cause: Mechanical bounce or long wires picking up noise.
Solution: Keep wires short and twisted as a pair if possible; add small capacitors (e.g., 0.01–0.1 µF) from CLK/DT to GND if needed; enable any available debounce options in your library version.
Button Not Detected
Cause: Switch contacts wired to 3.3V instead of GND or pull-up misconfiguration.
Solution: Ensure SW connects to GND when pressed; confirm
pull=Pin.PULL_UPis set.
Direction Reversed
Cause: CLK and DT swapped relative to your desired direction.
Solution: Swap the CLK and DT wires or invert at the application layer (e.g., subtract steps).
Extendable Ideas
Adjust a Variable (e.g., Volume/Brightness) Smoothly
value = 50 # 0..100 def rotary_change(): global value value = max(0, min(100, 50 + encoder.steps())) print(f'Value = {value}')
Use the Button to Toggle a Mode
mode = ['Fine', 'Coarse'] idx = 0 def reset_counter(): global idx idx = 1 - idx encoder.reset() print(f'Mode switched to: {mode[idx]}')
LED/Buzzer Feedback
from fusion_hat import Pin led = Pin(26, Pin.OUT) def rotary_change(): print('Counter =', encoder.steps()) led.on() # brief flash without blocking callbacks is recommended led.off()
Long-Press to Perform a Special Action
Track press duration using timestamps in
sw.when_activatedand (if available)sw.when_deactivatedto trigger different actions for short/long presses.
Conclusion
This experiment shows how to read a rotary encoder and its integrated button using event-driven callbacks on the Fusion HAT+. With this pattern, you can build responsive interfaces such as menu navigators, dial-controlled settings, and jog wheels for real-time control in your Raspberry Pi projects.