Coding a KY-040 Rotary Encoder on a Raspberry Pi Pico - Detailed Explanation & Step by Step Code

Coding a KY-040 Rotary Encoder on a Raspberry Pi Pico - Detailed Explanation & Step by Step Code

Our 2nd most popular post is this step by step tutorial on how a KY-040 Rotary Encoder works using MicroPython code on a Raspberry Pi Pico or any Microcontroller of choice.

Some really great content here, you learn how to make Classes in Python, how to use Interrupt Requests, we even have some Binary Shifts. We show a simple circuit to investigate how these work and then add a microcontroller and build the code step by step.

Below is the full video but be sure to check below for more resources, code and information.

You should be able to apply what you have learned here to other rotary encoders. Ours had pull down resistors on the component, but it seems not all components listed as KY-040 have those but you can do that with code. Thanks to everyone who took the time to point that out to us. Including Dan McCreary.

Below is a simple example of how to use the rotary class.

from rotary import Rotary
import utime as time

rotary = Rotary(0,1,2)
val = 0

def rotary_changed(change):
    global val
    if change == Rotary.ROT_CW:
        val = val + 1
        print(val)
    elif change == Rotary.ROT_CCW:
        val = val - 1
        print(val)
    elif change == Rotary.SW_PRESS:
        print('PRESS')
    elif change == Rotary.SW_RELEASE:
        print('RELEASE')
        
rotary.add_handler(rotary_changed)

while True:
    time.sleep(0.1)

Here is the code for the class rotary.py

import machine
import utime as time
from machine import Pin
import micropython

class Rotary:
    
    ROT_CW = 1
    ROT_CCW = 2
    SW_PRESS = 4
    SW_RELEASE = 8
    
    def __init__(self,dt,clk,sw):
        self.dt_pin = Pin(dt, Pin.IN, Pin.PULL_DOWN)
        self.clk_pin = Pin(clk, Pin.IN, Pin.PULL_DOWN)
        self.sw_pin = Pin(sw, Pin.IN, Pin.PULL_DOWN)
        self.last_status = (self.dt_pin.value() << 1) | self.clk_pin.value()
        self.dt_pin.irq(handler=self.rotary_change, trigger=Pin.IRQ_FALLING | Pin.IRQ_RISING )
        self.clk_pin.irq(handler=self.rotary_change, trigger=Pin.IRQ_FALLING | Pin.IRQ_RISING )
        self.sw_pin.irq(handler=self.switch_detect, trigger=Pin.IRQ_FALLING | Pin.IRQ_RISING )
        self.handlers = []
        self.last_button_status = self.sw_pin.value()
        
    def rotary_change(self, pin):
        new_status = (self.dt_pin.value() << 1) | self.clk_pin.value()
        if new_status == self.last_status:
            return
        transition = (self.last_status << 2) | new_status
        if transition == 0b1110:
            micropython.schedule(self.call_handlers, Rotary.ROT_CW)
        elif transition == 0b1101:
            micropython.schedule(self.call_handlers, Rotary.ROT_CCW)
        self.last_status = new_status
        
    def switch_detect(self,pin):
        if self.last_button_status == self.sw_pin.value():
            return
        self.last_button_status = self.sw_pin.value()
        if self.sw_pin.value():
            micropython.schedule(self.call_handlers, Rotary.SW_RELEASE)
        else:
            micropython.schedule(self.call_handlers, Rotary.SW_PRESS)
            
    def add_handler(self, handler):
        self.handlers.append(handler)
    
    def call_handlers(self, type):
        for handler in self.handlers:
            handler(type)

You can download the code at https://github.com/gurgleapps/rotary-encoder

Classes are very useful if you find yourself using the same code in multiple projects. You can seperate out all the code and logic into a really useful class. It also makes your main project code easier to follow.

Interrupt Requests are also useful for time critical changes you want to detect.