Page 1 of 1

Yet Another Rotary Encoder Solution (Python)

Posted: Mon Nov 23, 2015 11:17 am
by paulv
For a new project, I needed some Rotary Encoders, and to also reduce some conventional hardware, I used PICAXE chips. (Yeah, blasphemy on this Forum, I know) It took me a surprisingly long time to find a solution that worked for me in that environment. Eventually I selected an "early detection" solution and wrote a post on the PICAXE forum to explain my endeavors.

With a lot of knowledge and even more confidence, I then wanted to "port" that rather simple solution to the Raspberry Pi, but boy, was I wrong! It didn't work at all. So, for the same reasons that many solutions do not work on the PICAXE, many also do not work well on the Pi.

I have to admit that I'm adamant in using Python, instead of C, and also for using the RPi.GPIO library. That didn't help me, but my assumption is that there are more like me (in a way anyway).

So, working from the same methodology, the "early detection" method, I finally was able to finish a Python program that works pretty reliably.

The code is explained the best I could, so you should have no problems following it, or modifying it for your own application.

Code: Select all

# FileName:
# Purpose:      This program decodes a rotary encoder switch.

# Note:         All dates are in European format DD-MM-YY[YY]
# Author:       Paul Versteeg
# Created:      23-Nov-2015
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation, either version 3 of the License, or
#    (at your option) any later version.
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    GNU General Public License for more details.
#    You should have received a copy of the GNU General Public License
#    along with this program.  If not, see <>

import RPi.GPIO as GPIO
from time import sleep

# Global constants & variables
# Constants
__author__ = 'Paul Versteeg'

counter = 10  # starting point for the running directional counter

# GPIO Ports
Enc_A = 23  # Encoder input A: input GPIO 23 (active high)
Enc_B = 24  # Encoder input B: input GPIO 24 (active high)

def init():
    Initializes a number of settings and prepares the environment
    before we start the main program.
    print "Rotary Encoder Test Program"


    # Use the Raspberry Pi BCM pins

    # define the Encoder switch inputs
    GPIO.setup(Enc_A, GPIO.IN) # pull-ups are too weak, they introduce noise
    GPIO.setup(Enc_B, GPIO.IN)

    # setup an event detection thread for the A encoder switch
    GPIO.add_event_detect(Enc_A, GPIO.RISING, callback=rotation_decode, bouncetime=2) # bouncetime in mSec

def rotation_decode(Enc_A):
    This function decodes the direction of a rotary encoder and in- or
    decrements a counter.

    The code works from the "early detection" principle that when turning the
    encoder clockwise, the A-switch gets activated before the B-switch.
    When the encoder is rotated anti-clockwise, the B-switch gets activated
    before the A-switch. The timing is depending on the mechanical design of
    the switch, and the rotational speed of the knob.

    This function gets activated when the A-switch goes high. The code then
    looks at the level of the B-switch. If the B switch is (still) low, then
    the direction must be clockwise. If the B input is (still) high, the
    direction must be anti-clockwise.

    All other conditions (both high, both low or A=0 and B=1) are filtered out.

    To complete the click-cycle, after the direction has been determined, the
    code waits for the full cycle (from indent to indent) to finish.


    global counter

    sleep(0.002) # extra 2 mSec de-bounce time

    # read both of the switches
    Switch_A = GPIO.input(Enc_A)
    Switch_B = GPIO.input(Enc_B)

    if (Switch_A == 1) and (Switch_B == 0) : # A then B ->
        counter += 1
        print "direction -> ", counter
        # at this point, B may still need to go high, wait for it
        while Switch_B == 0:
            Switch_B = GPIO.input(Enc_B)
        # now wait for B to drop to end the click cycle
        while Switch_B == 1:
            Switch_B = GPIO.input(Enc_B)

    elif (Switch_A == 1) and (Switch_B == 1): # B then A <-
        counter -= 1
        print "direction <- ", counter
         # A is already high, wait for A to drop to end the click cycle
        while Switch_A == 1:
            Switch_A = GPIO.input(Enc_A)

    else: # discard all other combinations

def main():
    The main routine.



        while True :
            # wait for an encoder click

    except KeyboardInterrupt: # Ctrl-C to terminate the program

if __name__ == '__main__':
Here is the schematic of the encoder with some de-bouncing hardware.
Rot_Enc_Pi.png (31.2 KiB) Viewed 13872 times
[edit] the caps are 100nF. Better de-bounce timing. I used a "classic" B, standard stock, latest f/w


Re: Yet Another Rotary Encoder Solution (Python)

Posted: Tue Mar 15, 2016 11:02 am
by hrvoje
Hi Paulv,

I have seen that you fought with same problem as I did, if you want you can check my solution - maybe it can help you.



Re: Yet Another Rotary Encoder Solution (Python)

Posted: Tue Mar 15, 2016 6:29 pm
by paulv
I did, nice solution!
Thanks for posting.

Re: Yet Another Rotary Encoder Solution (Python)

Posted: Tue Aug 23, 2016 2:23 am
by dannyh914
Hey Paulv,

Your post was exactly what I was looking for! Thanks for the help! One question, how would you add a button to the code?

Re: Yet Another Rotary Encoder Solution (Python)

Posted: Tue Aug 23, 2016 7:28 pm
by paulv
Hi Danny,

Glad you like it.
If you want to add a button, I suggest you have a look at the following post, because depending on your application and environment, adding a simple button seems easy enough, but can be frustratingly difficult. viewtopic.php?f=29&t=133740


Re: Yet Another Rotary Encoder Solution (Python)

Posted: Mon Sep 19, 2016 6:38 am
by neteng
Hi paulv,

You mention in the codes that the pullups are weak??...What does this mean???...Does it mean that I will get unstable count values if I use the pull up feature???


Re: Yet Another Rotary Encoder Solution (Python)

Posted: Mon Sep 19, 2016 9:14 am
by paulv
Well, you need to remember that the RPi GPIO pull-ups are only about 50K Ohms.
They were most likely designed for CMOS applications and the likes, not for hobbyists.
For an exercise, calculate the current that flows from a 3.3V supply.

You need to be aware of this limitation (a very meager pull-up/down) if you use it to create an R/C filter for button or switch bounce noise. You also need to be aware of tha fact that a GPIO pull may not be in the state you want when the RPi powers up. Therefore it is generally safer/better/more reliable/intuitive to use a "real" resistor of something like 1K. This value still protects the input port if you accidentally set it to output with a low.

If you know what you're doing you're OK.
That is what I wanted to point out.


Re: Yet Another Rotary Encoder Solution (Python)

Posted: Sun Sep 25, 2016 11:32 pm
by neteng
So what you are saying paulv, I should externally pull up my encoder output with 1k resistors???......

Re: Yet Another Rotary Encoder Solution (Python)

Posted: Mon Sep 26, 2016 4:26 am
by paulv
If you follow my hardware example and software design for the rotary switch, you need 10K pull-downs. Look at the diagram in the first post of this topic.

I used 10K pull-down resistors in this application, because that is the value needed for the optimum de-bounce in combination with the capacitor value and the series resistor.

The 1K value for a pull (up or down) I referred to earlier was used in general terms.