Friday 18 January 2013

Rotary Encoders on RPi

I've been playing with an incremental rotary encoder on the Raspberry Pi - for no better reason than the mountaineers' lame excuse "because it's there"...


The incremental rotary encoder produces two quadrature square waves (as you can learn by reading any of a number of descriptions including this one). By detecting the relative phase of the square waves, you can figure the direction of rotation and (if you are really keen to encode angular velocity accurately) the speed of rotation too.

I settled for a clumsy attempt to decode direction alone, using this circuit...


Of course, you need some code too - I wrote the following simple demonstration in Python. It detects positive-going transitions on "PinA" and looks at the state of PinB during these transition events to figure the direction in which the encoder has been moved.



After the fun-and-games with the MIDI controller, I'm really excited about object-oriented software for the RPi, so here's an elaboration which includes a class for Rotary Encoders, connected to any two GPIO pins on the RPi. There's also facility to read a push-button (which some encoders - including mine - feature).


Here's a screen shot showing me running the second program ...


The code works - but only just...

It is important to sample a rotary encoder at a sufficiently high sample rate to avoid aliasing which - in this case - results in missed "moves" or even moves of incorrect direction.

I cannot get the RPi to sample fast enough to guarantee that every movement is perfectly observed, whether in the "while" loop seen in the programs above, or in a tkinter ".after()" command with a delay argument of 1 millisecond. What's worse, the little graph of processor utilisation at the bottom right of the RPi desktop (like the PC's "Task Manager" or the Mac's "Activity Monitor") reveals that the processor is working hard to sample the encoder at this rate (as you can just see in the screenshot above). I don't know enough about RPi to know if an interrupt-driven approach, such as would be easy and obvious on a PIC, is possible or desirable.

Having said all that, the code above works well enough to let me add a "knob" to my simple RPi MIDI controller, making it much more user-friendly than the original mouse drag-able sliders.

Next, I hope to use the rotary encoder to control the frequency of the DDS generator module from eBay that's just arrived on my doormat - watch this space!

...-.- de m0xpd

5 comments:

  1. I'm looking for some help as a raw beginner. I'm not sure if I'm in the right place.
    I have a DC motor running with a small piece of Python code on the Pi. I'm using GPio pins to control the motor. The code is taken from one of the many sites that I have visited so I acknowledge the rights of the originator. The code is added below. I did make some modifications and it works.
    On the shaft of the motor is as wheel with 8 slots. These interrupt a Photo diode and a photo transistor. I need some help with the code in getting this input into my Pi. I would like to count the slots after a period of time running the motor. Most of the code that I have seen is for quadrature encoders.These are a bit more complex than what I have here.
    I appologise if I'm in the wrong area. Please point me in the right direction if I am.

    Many thanks
    George

    #!/usr/bin/python
    #http://www.raspberrypi.org/phpBB3/viewtopic.php?f=37&t=55288
    import RPi.GPIO as gpio
    import time

    loop_counter = 0

    try:
    gpio.setmode(gpio.BOARD)
    gpio.setup(7, gpio.OUT)
    gpio.setup(11, gpio.OUT)
    gpio.setup(13, gpio.OUT)
    gpio.setup(15, gpio.OUT)
    gpio.output(7, True)
    gpio.output(11, True)

    while loop_counter < 10:
    print "Start of motor loop"
    print "Motor forward for .25 second"
    gpio.output(13, True)
    gpio.output(15, False)
    time.sleep(.5)
    print "Motor reverse for 0.5 Second"
    gpio.output(13, False)
    gpio.output(15, True)
    time.sleep(.5)
    print "Motor forward for 0.15 Second"
    gpio.output(13, True)
    gpio.output(15, False)
    time.sleep(.25)
    print "Motor reverse for 0.5 Second"
    gpio.output(13, False)
    gpio.output(15, True)
    time.sleep (.5)
    #print "Motor Forward for 0.1 Second"
    loop_counter = loop_counter + 1;
    print "\n", loop_counter
    except KeyboardInterrupt:
    print "loop ending"

    finally:
    gpio.cleanup() # this ensures a clean exit
    print "Now the Ports are all sqeeky clean"


    ReplyDelete
  2. George. I really don't know enough to help you directly on this one in the context of the R Pi - I would do this on Arduino or a PIC and have no experience of this sort on the Pi.

    You'll want to count pulses from the optical sensor. This is (as you say) a rather different task to responding to a rotary encoder (not least because you're only talking about one input, whereas the rot. enc. uses two). HOWEVER, the "bones" of a rotary encoder interface routine (specifically an interrupt-driven, pulse-counting routine rather than the naive way in which I observed single steps) would teach you what you need to know. That's where I'd look if I were you.

    Good luck with your project.

    ReplyDelete
  3. Hello Sorry, you know the same application programming C?

    ReplyDelete
    Replies
    1. Fernando
      Not sure if I understand your question / comment...
      If you are asking about implementation of a simple rotary encoder read in "C", then the Arduino code for the "Occam's Microcontroller" rig includes essentially the same procedure as was expressed in this post in Python. You'll find the code on this page:
      https://sites.google.com/site/occamsmicrocontroller/home/software
      The Arduino code isn't pure C - but it is close enough to see what is happening.

      Delete
  4. Hi there,

    Thanks for your post, I was missing something quite important : POWER ! (Yeah I forgot to connect the 3V3 and I was asking myself : "But why does is this s**t not working?").

    You just saved my night and my hair, congratulations !

    Best

    ReplyDelete