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!

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