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

Rotary Encoder Module

BUY

Jumper Wires

BUY

Fusion HAT+

-

Raspberry Pi

-


Circuit Diagram

Below are the schematic diagrams for the project:

../_images/2.1.6_rotary_sch.png

Wiring Diagram

Connect the components as shown in the wiring diagram below:

../_images/2.1.6_rotary_bb.png

Pin Mapping Used in the Example

  • CLKGPIO 17

  • DTGPIO 4

  • SW (button)GPIO 27 (with internal pull-up)

  • +3.3V

  • GNDGND

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

  1. Imports

    from fusion_hat.pin import Pin, Mode, Pull
    import time
    
    • Pin is used to configure and read GPIO pins for the rotary encoder.

    • Mode and Pull define the pin direction and internal pull-up resistors.

    • time is used to control polling intervals and button debounce timing.

  2. 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)
    
    • CLK and DT are the two quadrature signal pins of the rotary encoder.

    • SW is 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 0 when pressed.

  3. State Variables

    raw = 0
    last_clk = clk.value()
    last_detent = None
    
    • raw counts low-level quadrature transitions from the encoder.

    • last_clk stores the previous CLK state to detect signal changes.

    • last_detent keeps track of the last displayed counter value to avoid repeated output.

  4. 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.

  5. 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.

  6. Clean Console Output

    if detent != last_detent:
        print(f"\\rCounter: {detent}   ", end="", flush=True)
    
    • Output is updated only when the counter value changes.

    • \\r moves the cursor back to the beginning of the line for in-place updates.

    • flush=True ensures immediate display on the terminal.

  7. 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.

  8. Polling Interval and Program Exit

    time.sleep(0.001)
    
    • A 1 ms polling delay balances responsiveness and CPU usage.

    • Pressing Ctrl + C exits the program gracefully.


Troubleshooting

  1. 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).

  2. 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.

  3. 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_UP is set.

  4. 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

  1. 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}')
    
  2. 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]}')
    
  3. 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()
    
  4. Long-Press to Perform a Special Action

    • Track press duration using timestamps in sw.when_activated and (if available) sw.when_deactivated to 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.