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

Ultimate rotary encoder switch decoder

Tue Mar 15, 2016 10:48 am

Hi,


Recently I started a project where I needed to read 2-bit rotary encoder switch. I got rotary encoders from SunFounder with pull-ups & push button and started searching for simple code that would give me direction and number of steps turned. Simplest example would be volume control.

And here is where complications started. Each and every code and example I found relied on interpreting bit pairs (0:0, 1:0, 0:1, 1:1) encoder produced while being turned. While all of that works in theory in praxis all of them suffered various problems due to real world: bouncing contacts, skipped or wrong readings, .... So most of the code tried to somehow come around those problems by guessing missed steps, filtering obviously wrong inputs and so on. In other words all of them (at least the ones I found) where imperfect or complicated.

Solution to all of above is really simple, one had to look at the problem from another angle:

Working principle of 2-bit rotary encoder switch is that states of lines (A and B) MUST change at different point of time as seen in picture. Otherwise it would be impossible to read the direction of turning!

Image

Each single step of encoder produces 4 state pairs but ultimately it ends with (1:1), and in this lies the solution:

Instead of trying to read and match states and calculate direction, solution is dead simple: IGNORE all changes before final state (1:1) and, using interrupts, determine which edge came first before reaching (1:1) - A or B. And this will give you direction of turning. Added bonus is that you don't need any hardware debouncing as it is included in code itself.
No steps are missed. No steps are misinterpreted!

In attached example I simulated volume knob. Depending on speed with which the knob is turned volume is increased/decreased as square function of speed. Rotary encoder is connected to GPIO pins 4 & 14 and interrupts are used.

Main loop checks every 100 msec if volume has been turned, and if so it adjusts Volume variable. Added complication in this example is that if you leave the code running for VERY LONG time and turn the knob always in one direction variable in which number of changes is held will wrap around to zero when ti reaches MAX or MIN integer value! To avoid it you have to reset it to 0 and in doing so watch out for simultaneous access from interrupt thread. I used simple locking for that.

Here is the code in Python, rewriting it in any other language should be simple:

Code: Select all


import RPi.GPIO as GPIO
import threading
from time import sleep

						# GPIO Ports
Enc_A = 4  				# Encoder input A: input GPIO 4 
Enc_B = 14  			        # Encoder input B: input GPIO 14 

Rotary_counter = 0  			# Start counting from 0
Current_A = 1					# Assume that rotary switch is not 
Current_B = 1					# moving while we init software

LockRotary = threading.Lock()		# create lock for rotary switch
	

# initialize interrupt handlers
def init():
	GPIO.setwarnings(True)
	GPIO.setmode(GPIO.BCM)					# Use BCM mode
											# define the Encoder switch inputs
	GPIO.setup(Enc_A, GPIO.IN) 				
	GPIO.setup(Enc_B, GPIO.IN)
											# setup callback thread for the A and B encoder 
											# use interrupts for all inputs
	GPIO.add_event_detect(Enc_A, GPIO.RISING, callback=rotary_interrupt) 				# NO bouncetime 
	GPIO.add_event_detect(Enc_B, GPIO.RISING, callback=rotary_interrupt) 				# NO bouncetime 
	return



# Rotarty encoder interrupt:
# this one is called for both inputs from rotary switch (A and B)
def rotary_interrupt(A_or_B):
	global Rotary_counter, Current_A, Current_B, LockRotary
													# read both of the switches
	Switch_A = GPIO.input(Enc_A)
	Switch_B = GPIO.input(Enc_B)
													# now check if state of A or B has changed
													# if not that means that bouncing caused it
	if Current_A == Switch_A and Current_B == Switch_B:		# Same interrupt as before (Bouncing)?
		return										# ignore interrupt!

	Current_A = Switch_A								# remember new state
	Current_B = Switch_B								# for next bouncing check


	if (Switch_A and Switch_B):						# Both one active? Yes -> end of sequence
		LockRotary.acquire()						# get lock 
		if A_or_B == Enc_B:							# Turning direction depends on 
			Rotary_counter += 1						# which input gave last interrupt
		else:										# so depending on direction either
			Rotary_counter -= 1						# increase or decrease counter
		LockRotary.release()						# and release lock
	return											# THAT'S IT

# Main loop. Demonstrate reading, direction and speed of turning left/rignt
def main():
	global Rotary_counter, LockRotary
	

	Volume = 0									# Current Volume	
	NewCounter = 0								# for faster reading with locks
						

	init()										# Init interrupts, GPIO, ...
				
	while True :								# start test 
		sleep(0.1)								# sleep 100 msec
		
												# because of threading make sure no thread
												# changes value until we get them
												# and reset them
												
		LockRotary.acquire()					# get lock for rotary switch
		NewCounter = Rotary_counter			# get counter value
		Rotary_counter = 0						# RESET IT TO 0
		LockRotary.release()					# and release lock
					
		if (NewCounter !=0):					# Counter has CHANGED
			Volume = Volume + NewCounter*abs(NewCounter)	# Decrease or increase volume 
			if Volume < 0:						# limit volume to 0...100
				Volume = 0
			if Volume > 100:					# limit volume to 0...100
				Volume = 100
			print NewCounter, Volume			# some test print
											


# start main demo function
main()


asterixorobelix
Posts: 1
Joined: Fri Jul 08, 2016 9:02 am
Location: South Africa

Re: Ultimate rotary encoder switch decoder

Fri Jul 08, 2016 9:57 am

Hi

I am building a testing rig with a Raspberry Pi 3 and a NOOBS setup.

I am a mechanical engineer with a small amount of coding and electronics experience.

I want to to create the following setup: RPi switches on motor in forward direction, through a two channel relay board which I have bought. By monitoring the rotary encoder, the RPi will work out how many degrees forward the motor has turned. When the preset value has been reached, the RPi must wait a few seconds and drive the motor in reverse through the other relay.

I need to read values from an incremental rotary encoder taken from an industrial setup. The rotary encoder receives DC voltage of 24 volts. The two output (A and B) channels are also 24 volts DC. I am dropping these signals to 3.3 volts, through a voltage divider circuit. I have verified that 3.3 volts is coming out of these voltage regulators and going into the RPi.

I am struggling to fully understand what the code shared above does.
Last edited by asterixorobelix on Wed Jul 13, 2016 12:25 pm, edited 1 time in total.

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

Re: Ultimate rotary encoder switch decoder

Sun Jul 10, 2016 7:39 pm

I did one design a while a go too. I think mine is even more simple.
viewtopic.php?f=37&t=126753&p

Enjoy!

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

Re: Ultimate rotary encoder switch decoder

Sun Sep 18, 2016 10:11 pm

Hi hrvoje,

Will this code work on a motor encoder???

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

Re: Ultimate rotary encoder switch decoder

Mon Sep 19, 2016 7:55 am

Hi neteng,

I don't see why not. In my example variable Rotary_counter is either decreased or increased for each step, depending on direction. If it is all what you need just use it unmodified and discard functionality in main() function (except init() of course).

johnestan
Posts: 12
Joined: Thu Sep 15, 2016 7:37 pm

Re: Ultimate rotary encoder switch decoder

Mon Sep 19, 2016 2:57 pm

Hi. I can't get this code to work. I have the A and B pins hooked directly to the raspberry pi. With some printing, I found that my callback doesn't get triggered when turning the rotary encoded. Got any idea? Thanks

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

Re: Ultimate rotary encoder switch decoder

Tue Sep 20, 2016 7:45 am

Hi johnestan,

Well, not knowing how your setup looks like here are some ideas: most obvious one: did you call init()?
Is rotary encoder connected to correct pins. In my example I have used GPIO 4 and and GPIO 14 which correspond to pin numbers 7 and 8 on 40 pin connector. Next is that GPIO 14 an be used also as UART0-TX - do you have a conflict there?

I have used Soundfounder Rotary encoder, check this link how to connect it:
https://www.sunfounder.com/learn/Super_ ... rrypi.html

Hope this helps

boyoh
Posts: 1255
Joined: Fri Nov 23, 2012 3:30 pm
Location: Selby. North Yorkshire .UK

Re: Ultimate rotary encoder switch decoder

Tue Sep 20, 2016 4:11 pm

This subject takes me back to my National Service days in the early 1950s
I worked on Heavy Anti Aircraft gun control firing systems ( Shells not Rockets )
Information was transmitted between Radar Predictors & guns, using
Magslips ( Synchro Transmitters & Receivers ) all electro/mechanical.
The Predictor was the hart of the system , this did all the ballistic calculations
For the guns to set before firing , No transistors or I/Cs in them days
All electro / mechanical. And the Predictor was designed in the 1930s

Working with and generating pulses , is a very precise operation
A very stable and noise free power supply is a must, There is the
The Ideal pulse and the Real pulse, The Ideal pulse you can't generate
Due to rise time, The real pulse can be subject to inductance and ringing
At the top of the rising edge and the bottom of the falling edge,
So you might take this in to account, Be careful when using
Potential deviders as voltage levelers
BoyOh ( Selby, North Yorkshire.UK)
Some Times Right Some Times Wrong

boyoh
Posts: 1255
Joined: Fri Nov 23, 2012 3:30 pm
Location: Selby. North Yorkshire .UK

Re: Ultimate rotary encoder switch decoder

Tue Sep 20, 2016 7:44 pm

asterixorobelix wrote:Hi

I am building a testing rig with a Raspberry Pi 3 and a NOOBS setup.

I am a mechanical engineer with a small amount of coding and electronics experience.

I want to to create the following setup: RPi switches on motor in forward direction, through a two channel relay board which I have bought. By monitoring the rotary encoder, the RPi will work out how many degrees forward the motor has turned. When the preset value has been reached, the RPi must wait a few seconds and drive the motor in reverse through the other relay.

I need to read values from an incremental rotary encoder taken from an industrial setup. The rotary encoder receives DC voltage of 24 volts. The two output (A and B) channels are also 24 volts DC. I am dropping these signals to 3.3 volts, through a voltage divider circuit. I have verified that 3.3 volts is coming out of these voltage regulators and going into the RPi.

I am struggling to fully understand what the code shared above does.
Using solid state switching , and not relays might be a better Idea for your test rig
Relays have a bad habit of bouncing, causing the motor to be eratic on starting.
You could use a H Bridge for motor control reversing, What type of motor are you
Using , are you using feed back from the rotation to tell you how many degrease
The motor as turned , Are you testing the motor on load or off load, there will be
Slight difference in the time it starts rotating and stopping.
BoyOh ( Selby, North Yorkshire.UK)
Some Times Right Some Times Wrong

johnestan
Posts: 12
Joined: Thu Sep 15, 2016 7:37 pm

Re: Ultimate rotary encoder switch decoder

Tue Sep 20, 2016 9:51 pm

hrvoje wrote:Hi johnestan,

Well, not knowing how your setup looks like here are some ideas: most obvious one: did you call init()?
Is rotary encoder connected to correct pins. In my example I have used GPIO 4 and and GPIO 14 which correspond to pin numbers 7 and 8 on 40 pin connector. Next is that GPIO 14 an be used also as UART0-TX - do you have a conflict there?

I have used Soundfounder Rotary encoder, check this link how to connect it:
https://www.sunfounder.com/learn/Super_ ... rrypi.html

Hope this helps
Thanks for your reply. It was a silly mistake on my part. I had the middle pin of the encoder attached to ground instead of 3.3V. The program is working now, but I'm getting some readings in the incorrect direction. I'm using the adafruit rotary encoder, which is a 24-pulse encoder. Starting with the encoder pointing up, slowly turning clockwise , and making a full rotation (feeling all 24 steps), this is the output I received:

Code: Select all

Rotary Encoder Test Program
direction ->  11
direction ->  12
direction <-  11
direction <-  10
direction <-  9
direction <-  8
direction <-  7
direction <-  6
direction ->  7
direction ->  8
direction <-  7
direction ->  8
direction ->  9
direction ->  10
direction <-  9
direction <-  8
direction <-  7
direction <-  6
direction <-  5
direction <-  4
direction <-  3
direction <-  2
direction <-  1
direction <-  0
direction <-  -1
direction <-  -2
direction ->  -1
Do you have any idea what's causing this?

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

Re: Ultimate rotary encoder switch decoder

Wed Sep 21, 2016 9:16 am

Hi johnestan,

Yes, I actually do: the problem lies in fact that falling and raising edges of contacts are smooth happens only in perfect world. In real world jittering occurs so if you connect oscilloscope to pins you will see that instead of only one edge you have few of them in moment when switch connects or disconnects. Best solution is to add debouncing circuit to rotary switch (simple search for "switch debouncing circuit" will give you lot of solutions). You can also try to add software debouncing in init function: replace:

GPIO.add_event_detect(Enc_A, GPIO.RISING, callback=rotary_interrupt)
with
GPIO.add_event_detect(Enc_A, GPIO.RISING, callback=rotary_interrupt, bouncetime=50)

which will delay / ignore all changes within 50 msec. This has naturally drawback that if you need to measure steps faster than 50msec you will miss some of them. But then again same will happen with hardware debouncing. Using high quality encoders can improve situation. It all depends what exactly you want to do and if you can ignore some of false readouts.

User avatar
joan
Posts: 13805
Joined: Thu Jul 05, 2012 5:09 pm
Location: UK

Re: Ultimate rotary encoder switch decoder

Wed Sep 21, 2016 9:20 am

It might be worth giving http://abyz.co.uk/rpi/pigpio/examples.h ... encoder_py a try. It uses a different method to capture the pulses.

johnestan
Posts: 12
Joined: Thu Sep 15, 2016 7:37 pm

Re: Ultimate rotary encoder switch decoder

Wed Sep 21, 2016 4:09 pm

joan wrote:It might be worth giving http://abyz.co.uk/rpi/pigpio/examples.h ... encoder_py a try. It uses a different method to capture the pulses.
Thanks you so much! Your method gives me the accurate encoder readings I need.

Simba82
Posts: 5
Joined: Fri May 16, 2014 3:12 pm

Re: Ultimate rotary encoder switch decoder

Tue May 01, 2018 4:31 pm

I've tried both hrvoj's and paulv's method. For my cheapo Aliexpress encoder, both still gave some false readings but hrvoj's was considerably more stable. My test was far from scientific, so take my finding as anecdotal ;-)

Not tried abyz's version, because I'm lazy and didn't want to port the pigpio to the RPi.GPIO lib :roll:

User avatar
OutoftheBOTS
Posts: 705
Joined: Tue Aug 01, 2017 10:06 am

Re: Ultimate rotary encoder switch decoder

Tue May 01, 2018 7:56 pm

Here is the easiest way to count quadrature encoders. https://www.youtube.com/watch?v=p4BCFhIuC88&t=447s

mattv
Posts: 4
Joined: Mon Sep 17, 2018 9:43 pm

Re: Ultimate rotary encoder switch decoder

Mon Oct 01, 2018 4:07 pm

Hi,
I am working on an old radip project. I have a raspberry pi running Kodi and have a Pro Trinket and Rotary Encoder I just bought. My goal is to be able to turn the rotary encoder knob and be able to scroll and select through the Kodi menu (imagine selecting music, turning the knob to select the artist, then song, and finally playing the song through a speaker connected to the pi). I am looking for wiring and coding help.

Any thoughts on where I can get help?

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

Re: Ultimate rotary encoder switch decoder

Fri Oct 05, 2018 10:33 am

Hi,

After long time coming back to my original post I would like to share my experience and findings about this topic. It took quit some time as I don’t have much time to pursue my small project but here it is.

So first, thanks to all replies and tips about different and better ways to decode rotary switch. Based on those I compiled and bit modified all ideas and solutions and came up with very simple code that seems to work and has debouncing “built-in”. This version is using RPi.GPIO library:

Code: Select all

#!/usr/bin/env python
# -*- coding: utf-8 -*- 

import RPi.GPIO as GPIO, time

Enc_A = 4  			# Encoder input A: input GPIO 4 
Enc_B = 14  			# Encoder input B: input GPIO 14 

lastitem = (0,1,1)

def init():
	GPIO.setwarnings(True)
	GPIO.setmode(GPIO.BCM)			# Use BCM mode
	GPIO.setup(Enc_A, GPIO.IN) 			# setup callback thread for the A and B encoder 	
	GPIO.setup(Enc_B, GPIO.IN)			
	GPIO.add_event_detect(Enc_A, GPIO.RISING, callback=rotary_interrupt) 	# NO bouncetime 
	GPIO.add_event_detect(Enc_B, GPIO.RISING, callback=rotary_interrupt) 	# NO bouncetime 

# 'interrupt' handler
def rotary_interrupt(w):			
	global lastitem
	
	if w == Enc_A:
		item = (w, 1, GPIO.input(Enc_B))
	else:
		item = (w, GPIO.input(Enc_A),1)
		
	if item == (Enc_A,1,1) and lastitem[1] == 0:	# Is it in END position?
		print ("DOWN")
	elif item == (Enc_B,1,1) and lastitem[2] == 0:	# Same but for ENC_B
		print ("UP")
	lastitem = item	

# init and loop forever (stop with CTRL C)
init()
while 1:
	time.sleep(1)
Now, you probably noted that I wrote that it SEEMS to be working. The sad fact with this version is that it works as long it is only process running on your Pi. I came to this conclusion hard way: as my project grew in complexity, adding threads to program, handling display, networking, http requests my rotary readings became more and more unreliable. Missing steps, readings in wrong direction. Totally random results, sometimes it worked perfectly sometimes totally erratically.

So finally (at least I think so) I traced down the problem to PRI.GPIO library. (Just as a side note, I am aware that raspbian OS is not real time, but due to various facts this was only possibility for me). What I think it happens is that as soon CPU utilization starts going up python and RPi.GPIO are starting to miss interrupts. (I don’t know how this library works: is it really hooked up as interrupt handler or is it pooling pins and simulating interrupts.) As a result defined callback function is NOT called for every state change of rotary pins and so it gives wrong results. I tried lot of things while trying to fix it but to no avail.

Following the links from other users who posted in this thread (many thanks to everybody) I tried the luck with pigpiod. Which in fact starts separate daemon and python library communicates with it.

And wow, this one worked for me. If it is really using interrupts or just pools the pins much faster and more reliable I don’t know, but pigpiod is much more robust in not loosing any state changes. It has one small drawback: on my PI 3 system it constantly uses cca 8% of CPU time, which is small tradeoff when considering that it solved my problem and all of my processing needs never go to 100% anyway. Here is sample code, which by the way, is just shorter version of original code found here: http://abyz.me.uk/rpi/pigpio/code/rotary_encoder_py.zip :

Code: Select all

#!/usr/bin/env python
# -*- coding: utf-8 -*- 

# pigpiod version:

import pigpio, time

Enc_A = 4  		# Encoder input A: input GPIO 4 
Enc_B = 14  		# Encoder input B: input GPIO 14 

pi = 0

last_A = 1
last_B = 1
last_gpio = 0


def init():
	global pi
	
	pi = pigpio.pi()		# init pigpio deamon
	pi.set_mode(Enc_A, pigpio.INPUT)
	pi.set_mode(Enc_B, pigpio.INPUT)
	pi.callback(Enc_A, pigpio.EITHER_EDGE, rotary_interrupt)
	pi.callback(Enc_B, pigpio.EITHER_EDGE, rotary_interrupt)

# Callback fn:
def rotary_interrupt(gpio,level,tim):
	global last_A, last_B,  last_gpio
	
	if gpio == Enc_A:
		last_A = level
	else:
		last_B = level;
		
	if gpio != last_gpio: 					# debounce
		last_gpio = gpio
		if gpio == Enc_A and level == 1:
			if last_B == 1:
				print "DOWN"
		elif gpio == Enc_B and level == 1:
			if last_A == 1:
				print "UP"

# init and loop forever (stop with CTRL C)
init()
while 1:
	time.sleep(1)	
Final thoughts on this topic: this or similar problematic has probably been described and discussed may times on various forums, but there is so much “noise” that I did not manage to find any concise and simple answer regarding interrupt handling on raspberry using python. And if you are using RPI.GIPO: it is good piece of code, helps me in other applications but you must be aware of it’s limitations.

Once again, thanks to everybody for giving their thoughts and suggestions

Cyanoazimin
Posts: 1
Joined: Sun Mar 31, 2019 7:26 am

Re: Ultimate rotary encoder switch decoder

Mon Apr 15, 2019 9:48 am

Hi hrvoje, Thank you for your code (the one in your initial post). I have used it in the following Python script for Raspberry 3+ buttons to control Volumio / Moode / RompR / mpc / mpd music, audio, radio servers.

pigpiod does not work in this case (much too noisy). The wires from the rotary encoder to the RPi should not be too close to each other: initially I used an old 40-pin IDE cable, but the A and the B channel interfered so that the volume was decreased regardless of the turning direction. (Almost) completely improved when separating the wires of the IDE cable by pulling them apart.

Code: Select all

#!/usr/bin/python
#coding: utf8
# Script for mpc/mpd audio GPIO buttons partly based on remy1961's code on http://moodeaudio.org/forum/showthread.php?tid=198&pid=8382#pid8382
# Rotary encoder mostly based on hrvoje's code https://www.raspberrypi.org/forums/viewtopic.php?t=140250 . Comments on the reliability of RPi.GPIO library there
# mpd on default settings: localhost 6600
# Worked with one rotary encoder only (with two not reliably enough) in my case
# All buttons and rotary encoder connect to ground without hardware debouncing or hardware pull-up/-down. Rotary encoder bouncing is not improved with capacitors across A/B and ground.

import RPi.GPIO as GPIO             # pigpio library does NOT work, due to loud noise (louder than the music)
import threading                    # rotary interrupt
import time
import os
import subprocess
#import datetime                    # for testing only

#GPIO.setwarnings(True)
GPIO.setmode(GPIO.BCM)              # dset GPIO mode instead of GPIO.setmode(GPIO.BOARD) - Zählweise der Pins festlegen

#On playlist names no spaces
PLFAV = "Favoriten"                 # This is the playlist to which files are saved when pushing the button. /var/lib/mpd/playlists/Favoriten.m3u will be created if it does not exist yet.
PLDEF = "Aktuell"                   # This is the default playlist which is played when pushing the respective button (/var/lib/mpd/playlists/Aktuell.m3u needs to be generated e.g. by renaming an existing .m3u)
PLDLT = "Delete"                    # This is the delete list. /var/lib/mpd/playlists/Delete.m3u will be created if it does not exist.

#Define your GPIO (some GPIO taken by HiFiBerry Digi+ in my case, i.e. not working or crashing), 7 buttons + 1 rotary encoder with button + 1 LED (with 470 Ohm resistor)
# VDD | VDD | Pin 1
# SDA (I2C) | GPIO 2 (SDA1) | Pin 3
# SCK (I2C) | GPIO 3 (SCL1) | Pin 5
# Button 'Next' | GPIO 4 | Pin 7
# GND | GND | Pin 9
# Button 'Play/pause' | GPIO 17 | Pin 11
# HiFiBerry Digi+ | GPIO 18 | Pin 12
# Button 'Previous' | GPIO 22 | Pin 15
# Button 'Volume up' | GPIO 23 | Pin 16
# Button 'Volume down' | GPIO 24 | Pin 18
# Button 'Add to Delete' | GPIO 25 | Pin 22
# EEPROM | (GPIO 0) ID_SD | Pin 27
# EEPROM | (GPIO 1) ID_SC | Pin 28
# Button 'Forward' | GPIO 5 | Pin 29
# Button 'Shut down' | GPIO 12 | Pin 32
# Button 'Play default' | GPIO 13 | Pin 33
# HiFiBerry Digi+ | GPIO 19 | Pin 35
# HiFiBerry Digi+ | GPIO 16 | Pin 36
# Button 'Add to Favoriten' | GPIO 26 | Pin 37
# HiFiBerry Digi+ | GPIO 20 | Pin 38
# GND | GND | Pin 39
# HiFiBerry Digi+ | GPIO 21 | Pin 40
# Button 'LED' | GPIO 27 | Pin 13
# HiFiBerry Digi+ | GPIO 6 | Pin 31
SW_PREV = 22                        # Previous
SW_NEXT = 4                         # Next
SW_FAV = 26                         # Add to favorites
SW_POWEROFF = 12                    # Shut down
SW_PLAY = 17                        # Toggle play/pause, e.g. button on the rotary encoder
SW_FW = 5                           # Go forward within current song
SW_DEF = 13                         # Play default playlist
SW_DLT = 25                         # adds the currently played song to the delete list (to be deleted semi-manually on the master repository - the Raspberry Pi accesses a copy on my NAS only)
RO_A = 23                           # Volume up
RO_B = 24                           # Volume down
LED = 27                            # Confirmation

PRELL = 500                         # debouncing for regular buttons
PRELLL = 1500                       # debouncing for "add to list" buttons
PRELLROT = 30                       # debouncing for rotary, favorable to diminish skipping. Higher value allows only slow volume increase
SCHRITT = 5                         # % change in Volume
SPRING = 10                         # % forward within current song
LockRotary = threading.Lock()		# create lock for rotary switch
Current_A = 0			    		# Assume that rotary switch is not moving while we init software
Current_B = 0

#Code to manage BUTTONS
GPIO.setup(SW_PREV, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(SW_NEXT, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(SW_FAV, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(SW_POWEROFF, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(SW_PLAY, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(SW_FW, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(SW_DEF, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(SW_DLT, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(RO_A, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(RO_B, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(LED, GPIO.OUT)
GPIO.output(LED, GPIO.LOW)

#Interrupt events
def eSW_PREV(channel):
    subprocess.call(['mpc', 'prev' ])
#   print str(datetime.datetime.now())[:19] + " previous"               # for testing only

def eSW_NEXT(channel):
    subprocess.call(['mpc', 'next' ])
#   print str(datetime.datetime.now())[:19] + " next"                   # for testing only

def eSW_FAV(channel):
    NAMEN = subprocess.check_output("mpc -f %file% current", shell=True)[:-1]
    NAME = NAMEN.replace("\'","\\'\\'\'")                               # for the sigle quotes in echo
    PFAD = "echo 'playlistadd " + PLFAV + " \"" + NAME + "\"' | netcat -q 0 localhost 6600"  # or "-w 1" instead of "-q 0" if not saving reliably?
    os.system(PFAD)
    GPIO.output(LED, GPIO.HIGH)
    time.sleep(0.6)
    GPIO.output(LED, GPIO.LOW)
    print "Zu " + PLFAV + ": " + NAMEN

def eSW_POWEROFF(channel):
    subprocess.call(['mpc', 'stop' ])
    subprocess.call(['sudo', 'systemctl', 'stop', 'pydpiper' ])
    subprocess.call(['sudo', 'poweroff' ])
#   print str(datetime.datetime.now())[:19] + " shutting down"          # for testing only

def eSW_PLAY(channel):
    subprocess.call(['mpc', 'toggle' ])
#   print str(datetime.datetime.now())[:19] + " play"                   # for testing only

def eSW_FW(channel):
    subprocess.call(['mpc', 'seek', '+'+str(SPRING)+'%' ])
#   print str(datetime.datetime.now())[:19] + " forward"                # for testing only
        

def eSW_DEF(channel):
    subprocess.call(['mpc','clear' ])
    subprocess.call(['mpc','load',PLDEF ])
    subprocess.call(['mpc','play' ])
#   print str(datetime.datetime.now())[:19] + " default playlist"       # for testing only

def eSW_DLT(channel):
    NAMEN = subprocess.check_output("mpc -f %file% current", shell=True)[:-1]
    NAME = NAMEN.replace("\'","\\'\\'\'")                               # for the sigle quotes in echo
    PFAD = "echo 'playlistadd " + PLDLT + " \"" + NAME + "\"' | netcat -q 0 localhost 6600"  # or "-w 1" instead of "-q 0" if not saving reliably?
    os.system(PFAD)
    GPIO.output(LED, GPIO.HIGH)
    time.sleep(0.2)
    GPIO.output(LED, GPIO.LOW)
    time.sleep(0.2)
    GPIO.output(LED, GPIO.HIGH)
    time.sleep(0.2)
    GPIO.output(LED, GPIO.LOW)
    print "Zur Löschliste " + PLDLT + ": " + NAMEN

# Rotary encoder interrupt is called for both inputs from rotary switch (A and B)
def eRO(A_or_B):
	global Current_A, Current_B, LockRotary
    
	Switch_A = GPIO.input(RO_A)                                         # Read both of the switches
	Switch_B = GPIO.input(RO_B)
    
	if Current_A == Switch_A and Current_B == Switch_B:		            # Now check if state of A or B has changed. If not that means that bouncing caused it
		return									               	        # Same interrupt as before (Bouncing)? Ignore interrupt!

	Current_A = Switch_A						    	    	        # remember new state
	Current_B = Switch_B						    	    	        # for next bouncing check

	if (Switch_A == 0 and Switch_B == 0):				                # Both one active? Yes -> end of sequence
		LockRotary.acquire()			        		    	        # get lock 
		if A_or_B == RO_B:						            	        # Turning direction depends on which input gave last interrupt
                    subprocess.call(['mpc', 'volume', '+'+str(SCHRITT) ])
		else:						        				            # so depending on direction either increase or decrease counter
                    subprocess.call(['mpc', 'volume', '-'+str(SCHRITT) ])
		LockRotary.release()		        				            # and release lock
	return						        					            # done

# Declare interrupt events
GPIO.add_event_detect(SW_PREV, GPIO.FALLING, callback = eSW_PREV, bouncetime = PRELL)
GPIO.add_event_detect(SW_NEXT, GPIO.FALLING, callback = eSW_NEXT, bouncetime = PRELL)
GPIO.add_event_detect(SW_FAV, GPIO.FALLING, callback = eSW_FAV, bouncetime = PRELLL)
GPIO.add_event_detect(SW_POWEROFF, GPIO.FALLING, callback = eSW_POWEROFF, bouncetime = PRELL)
GPIO.add_event_detect(SW_PLAY, GPIO.FALLING, callback = eSW_PLAY, bouncetime = PRELL)
GPIO.add_event_detect(SW_FW, GPIO.FALLING, callback = eSW_FW, bouncetime = PRELL)
GPIO.add_event_detect(SW_DEF, GPIO.FALLING, callback = eSW_DEF, bouncetime = PRELL)
GPIO.add_event_detect(SW_DLT, GPIO.FALLING, callback = eSW_DLT, bouncetime = PRELLL)
GPIO.add_event_detect(RO_A, GPIO.FALLING, callback=eRO, bouncetime = PRELLROT)
GPIO.add_event_detect(RO_B, GPIO.FALLING, callback=eRO, bouncetime = PRELLROT)

# Main
while True:
    time.sleep(1)
# the following is for testing pins. In my case e.g. pin 20 was low (0) all the time
'''
    print str(GPIO.input(SW_PREV)) + " SW_PREV " + str(SW_PREV) + "   |    " +  \
    str(GPIO.input(SW_NEXT)) + " SW_NEXT " + str(SW_NEXT) + "   |    " +  \
    str(GPIO.input(SW_POWEROFF)) + " SW_POWEROFF " + str(SW_POWEROFF) + "   |    " +  \
    str(GPIO.input(SW_PLAY)) + " SW_PLAY " + str(SW_PLAY) + "   |    " +  \
    str(GPIO.input(RO_A)) + " RO_A " + str(RO_A) + "   |    " +  \
    str(GPIO.input(RO_B)) + " RO_B " + str(RO_B) + "   |    " +  \       # for testing only
'''

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

Re: Ultimate rotary encoder switch decoder

Tue Apr 16, 2019 5:47 pm

Hi Cyanoazimin!

I’m glad I have been of some help. I did have some problems with pigpio daemon but I used PCM instead of PWM for clock (i.e. sudo pigpiod -t 1) which solved my problem. Then recently, I did a small project for my friend building him a network player with HiFiBerry Amp2 and Volumio. First thing - I used optical encoder instead of mechanical one.

Then, instead of using either pigpio or RPi.GPIO I tried solution with dtoverlay. And it worked perfectly, both with optical encoder and old one, from Sunfounder (which is bottom quality and contacts bounce like crazy). Both worked like a charm. Never missing a step or giving wrong direction. Also no programming at all. Here is a simple bash script which handles volume up / down and toggles stop/play on pushbutton (this one is for Volumio with HiFiBerry Amp2 but can be easily modified for other purppuses). Also note steps-per-period parameter. Grayhill encoder I used needs 4 detentions to make complete quadradture code. More info can be found on: https://raw.githubusercontent.com/raspb ... ays/README

It worked so well for me that I rewrote my other projects where I use rotary encoder. It' s quite simple using evdev python library.

Hope this helps you further.

Code: Select all

#!/bin/bash

# Must have evtest installed for script to work: sudo apt install evtest

# you can also use mpc volume +2 > /dev/null ; \ instead of amixer ………
# depending on your audio configuration

# Start overlay for controlling rotary switch
# GPIO pins are 23 and 22. Pushbutton is GPIO 24

sudo dtoverlay rotary-encoder pin_a=23 pin_b=22 relative_axis=1 steps-per-period=4

sudo dtoverlay gpio-key gpio=24 label=MYBTN keycode=0x101

sleep 2

# Rotary - volume control

evtest /dev/input/event0 |  \
    while read line ; do \
       if [[ $line =~ .*value\ 1.* ]]; then  \
           amixer -c 1 set Digital 2%+ > /dev/null ;
       else  \
           if [[ $line =~ .*value\ -1.* ]]; then  \
               amixer -c 1 set Digital 2%- > /dev/null ;
           fi; \
       fi; \
    done &

# Pushbutton - toggle play / pause

evtest /dev/input/event1 |  \
    while read line ; do \
       if [[ $line =~ .*value\ 1.* ]]; then  \
           curl localhost:3000/api/v1/commands/?cmd=toggle > /dev/null ; \
       fi; \
    done

Return to “Automation, sensing and robotics”