Home

Awesome

MicroPython Rotary Encoder Driver

A MicroPython driver to read a rotary encoder. Works with Pyboard, Raspberry Pi Pico, ESP8266, and ESP32 development boards. This is a robust implementation providing effective debouncing of encoder contacts. It uses two GPIO pins configured to trigger interrupts, following Ben Buxton's implementation:

<img src="https://user-images.githubusercontent.com/12716600/46682576-bfee3500-cba2-11e8-9bbe-0bd6e341b446.jpg" width="300">

Key Implementation Features

Interrupt based

Whenever encoder pins DT and CLK change value a hardware interrupt is generated. This interrupt causes a python-based interrupt service routine (ISR) to run. The ISR interrupts normal code execution to process state changes in the encoder pins.

Transition state machine

A gray code based transition state table is used to process the DT and CLK changes. The use of the state table leads to accurate encoder counts and effective switch debouncing. Credit: Ben Buxton

File Installation

Two files are needed to use this module

Copying files to development boards

Copy files to the internal MicroPython filesystem using a utility such as ampy or rshell Ampy example below for Pyboards. Note: -d1 option is often needed for ESP8266 boards

ampy -pCOMx put rotary.py
ampy -pCOMx put rotary_irq_pyb.py

mip install

Starting with MicroPython 1.20.0, it can be installed from mip via:

>>> import mip
>>> mip.install("github:miketeachman/micropython-rotary")

Or from mpremote via:

mpremote mip install github:miketeachman/micropython-rotary

Class RotaryIRQ

Constructor

   RotaryIRQ(
       pin_num_clk, 
       pin_num_dt, 
       min_val=0, 
       max_val=10, 
       incr=1,
       reverse=False, 
       range_mode=RotaryIRQ.RANGE_UNBOUNDED,
       pull_up=False,
       half_step=False,
       invert=False)
argumentdescriptionvalue
pin_num_clkGPIO pin connected to encoder CLK pininteger
pin_num_dtGPIO pin connected to encoder DT pininteger
min_valminimum value in the encoder range. Also the starting valueinteger
max_valmaximum value in the encoder range (not used when range_mode = RANGE_UNBOUNDED)integer
incramount count changes with each encoder clickinteger (default=1)
reversereverse count directionTrue or False(default)
range_modecount behavior at min_val and max_valRotaryIRQ.RANGE_UNBOUNDED(default) RotaryIRQ.RANGE_WRAP RotaryIRQ.RANGE_BOUNDED
pull_upenable internal pull up resistors. Use when rotary encoder hardware lacks pull up resistorsTrue or False(default)
half_stephalf-step modeTrue or False(default)
invertinvert the CLK and DT signals. Use when encoder resting value is CLK, DT = 00True or False(default)
range_modedescription
RotaryIRQ.RANGE_UNBOUNDEDencoder has no bounds on the counting range
RotaryIRQ.RANGE_WRAPencoder will count up to max_val then wrap to minimum value (similar behaviour for count down)
RotaryIRQ.RANGE_BOUNDEDencoder will count up to max_val then stop. Count down stops at min_val

Methods

value() Return the encoder value


set(value=None, min_val=None, max_val=None, incr=None, reverse=None, range_mode=None) Set encoder value and internal configuration parameters. See constructor for argument descriptions. None indicates no change to the configuration parameter

Examples:


reset() set encoder value to min_val. Redundant with the addition of the set() method. Retained for backwards compatibility)


add_listener(function) add a callback function that will be called on each change of encoder count


remove_listener(function) remove a previously added callback function


close() deactivate microcontroller pins used to read encoder

Note: None of the arguments are checked for configuration errors.

Example

import time
from rotary_irq_esp import RotaryIRQ

r = RotaryIRQ(pin_num_clk=12, 
              pin_num_dt=13, 
              min_val=0, 
              max_val=5, 
              reverse=False, 
              range_mode=RotaryIRQ.RANGE_WRAP)
              
val_old = r.value()
while True:
    val_new = r.value()
    
    if val_old != val_new:
        val_old = val_new
        print('result =', val_new)
        
    time.sleep_ms(50)

Tested With:

Development Boards

Rotary Encoders

Wiring for KY-040 encoder

Encoder PinConnection
+3.3V
GNDGround
DTGPIO pin
CLKGPIO pin

Recommended ESP8266 input pins

This Rotary module requires pins that support interrupts. The following ESP8266 GPIO pins are recommended for this rotary encoder module

The following ESP8266 GPIO pins should be used with caution. There is a risk that the state of the CLK and DT signals can affect the boot sequence. When possible, use other GPIO pins.

One pin does not support interrupts.

Recommended ESP32 input pins

This Rotary module requires pins that support interrupts. All ESP32 GPIO pins support interrupts.

The following ESP32 GPIO strapping pins should be used with caution. There is a risk that the state of the CLK and DT signals can affect the boot sequence. When possible, use other GPIO pins.

Examples

MicroPython example code is contained in the Examples folder
simple example
uasyncio example
uasyncio with classes example

Oscilloscope Captures

CLK and DT transitions captured on an oscilloscope. CLK = Yellow. DT = Blue

One clockwise step cw

One counter-clockwise step ccw

Board Hall of Fame

Testing with Pyboard D, Pyboard v1.1, TinyPico, and Raspberry Pi Pico development boards pyboard d pyboard b tiny pico raspberry pi pico

Acknowlegements

This MicroPython implementation is an adaptation of Ben Buxton's C++ work:

Other implementation ideas and techniques taken from:

Future Ambitions