Tuesday, June 23, 2015

Adafruit Rotary Encoder for Raspberry Pi

One of my pet projects based on Raspberry Pi required a volume control, that should be rolled to both sides w/o bounds and should have ability to be pressed, as a push button. After some googling I found exactly I need here: Rotary Encoder + Extras
This control has the remarkable haptic granularity, quite enjoyable though. Push button is tight enough to don’t click it occasionally.

Documentation on their website has description and explanation how to adapt it to outer world with their USB controller (link to this description you may find it in web page I mentioned above). But I didn't want the additional hardware, because I have the whole computer with really great GPIO bus!
So I decided to adapt it myself. Fortunately it was not very complex.

Rotary Encoder has very simple construction: 3 mechanical switches inside: 2 for rolling - for CW and CCW, and 1 for push button (see the Technical Specification for more details). So, all that I need is just 3 unused inputs on GPIO bus of my Raspberry.
Since in my case all GPIO pins that correspond to old versions of Raspberry were occupied by display, I selected GPIO 5, GPIO 6 and GPIO 13 from extended area. Though, if you have no display, you may use any pins you want.

So, the Rotary Encoder is mounted according to the scheme:



The real bredboard wiring:



The next step is to convert the signals from these input pins into something that may be really used.
So, I wrote the Python code based on USB driver they provide:

#!/usr/bin/env python
import time
import datetime
import RPi.GPIO as GPIO
from Queue import Queue
channelA = 5
channelB = 6
channelC = 13
q = Queue()
def main():
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
GPIO.setup(channelA, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(channelB, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(channelC, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.add_event_detect(channelA, GPIO.BOTH, callback=roll_callback)
GPIO.add_event_detect(channelB, GPIO.BOTH, callback=roll_callback)
GPIO.add_event_detect(channelC, GPIO.FALLING, callback=push_callback, bouncetime=300)
prev_pos = 0;
if GPIO.input(channelA) == False:
prev_pos |= 1 << 0
if GPIO.input(channelB) == False:
prev_pos |= 1 << 1
q.put((prev_pos, 0))
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
pass
GPIO.cleanup()
def roll_callback(channel):
action = 0 # 1 or -1 if moved, sign is direction
cur_pos = 0
if GPIO.input(channelA) == False:
cur_pos |= 1 << 0
if GPIO.input(channelB) == False:
cur_pos |= 1 << 1
item = q.get()
prev_pos = item[0]
flags = item[1]
if cur_pos != prev_pos:
if prev_pos == 0x00:
# this is the first edge
if cur_pos == 0x01:
flags |= 1 << 0
elif cur_pos == 0x02:
flags |= 1 << 1
if cur_pos == 0x03:
# this is when the encoder is in the middle of a "step"
flags |= 1 << 4
elif cur_pos == 0x00:
# this is the final edge
if prev_pos == 0x02:
flags |= 1 << 2
elif prev_pos == 0x01:
flags |= 1 << 3
# check the first and last edge
# or maybe one edge is missing, if missing then require the middle state
# this will reject bounces and false movements
if flags & 1 << 0 > 0 and (flags & 1 << 2 > 0 or flags & 1 << 4 > 0):
action = 1
elif flags & 1 << 2 > 0 and (flags & 1 << 0 > 0 or flags & 1 << 4 > 0):
action = 1
elif flags & 1 << 1 > 0 and (flags & 1 << 3 > 0 or flags & 1 << 4 > 0):
action = -1
elif flags & 1 << 3 > 0 and (flags & 1 << 1 > 0 or flags & 1 << 4 > 0):
action = -1
flags = 0
if action > 0:
print 'CW'
elif action < 0:
print 'CCW'
q.put((cur_pos, flags))
def push_callback(channel):
if GPIO.input(channel) == False:
print 'Push'
if __name__ == '__main__':
main()

Just save this code as e.g. "rotary.py" file in your Raspberry and make it executable:
$ chmod +x rotary.py
And then run it as root:
$ sudo ./rotary.py

Then rotate the encoder and push it - each your action will be followed by new printed line in console, like that:

$ sudo ./rotary.py
CW CW CW CW CW CW CCW CCW CCW CCW CW CW CW Push Push CW CW CW


That's it!

All that you need now is to decide where and how you want to react on these events.
Just place desired code into lines "print 'CW'", "print 'CCW'" and "print 'Push'" appropriately.

Hope I saved time for somebody :)

No comments: