paulv
Posts: 558
Joined: Tue Jan 15, 2013 12:10 pm
Location: Netherlands

Yet Another Rotary Encoder Solution (Python)

Mon Nov 23, 2015 11:17 am

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

#!/usr/bin/python
#-------------------------------------------------------------------------------
# FileName:     Rotary_Encoder-1a.py
# 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
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    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 <http://www.gnu.org/licenses/>
#-------------------------------------------------------------------------------

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"

    GPIO.setwarnings(True)

    # Use the Raspberry Pi BCM pins
    GPIO.setmode(GPIO.BCM)

    # 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
    #
    return


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)
        return

    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)
        return

    else: # discard all other combinations
        return



def main():
    '''
    The main routine.

    '''

    try:

        init()
        while True :
            #
            # wait for an encoder click
            sleep(1)

    except KeyboardInterrupt: # Ctrl-C to terminate the program
        GPIO.cleanup()


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

Enjoy!

hrvoje
Posts: 10
Joined: Thu Mar 10, 2016 12:43 pm
Location: Croatia

Re: Yet Another Rotary Encoder Solution (Python)

Tue Mar 15, 2016 11:02 am

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.

Regards,
Hrvoje


viewtopic.php?f=37&t=140250

paulv
Posts: 558
Joined: Tue Jan 15, 2013 12:10 pm
Location: Netherlands

Re: Yet Another Rotary Encoder Solution (Python)

Tue Mar 15, 2016 6:29 pm

I did, nice solution!
Thanks for posting.

dannyh914
Posts: 1
Joined: Tue Aug 23, 2016 2:13 am

Re: Yet Another Rotary Encoder Solution (Python)

Tue Aug 23, 2016 2:23 am

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?

paulv
Posts: 558
Joined: Tue Jan 15, 2013 12:10 pm
Location: Netherlands

Re: Yet Another Rotary Encoder Solution (Python)

Tue Aug 23, 2016 7:28 pm

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

Success!

neteng
Posts: 24
Joined: Wed May 11, 2016 3:56 am

Re: Yet Another Rotary Encoder Solution (Python)

Mon Sep 19, 2016 6:38 am

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

Thanks,
neteng

paulv
Posts: 558
Joined: Tue Jan 15, 2013 12:10 pm
Location: Netherlands

Re: Yet Another Rotary Encoder Solution (Python)

Mon Sep 19, 2016 9:14 am

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.

Success!

neteng
Posts: 24
Joined: Wed May 11, 2016 3:56 am

Re: Yet Another Rotary Encoder Solution (Python)

Sun Sep 25, 2016 11:32 pm

So what you are saying paulv, I should externally pull up my encoder output with 1k resistors???......

paulv
Posts: 558
Joined: Tue Jan 15, 2013 12:10 pm
Location: Netherlands

Re: Yet Another Rotary Encoder Solution (Python)

Mon Sep 26, 2016 4:26 am

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

Return to “Automation, sensing and robotics”