We already used the Yocto-Knob to interface potentiometers, buttons, photo-diodes, but never so far a rotary encoder. This week, we are going to see how to interface a rotary encoder with a Yocto-Knob to create a more advanced human-computer interface.
A rotary encoder enables you to detect the rotation of a dial but, in the opposite to the potentiometer, a rotary encoder is able to measure an unlimited number of turns. There are several types of rotary encoders: absolute encoders and incremental encoders. Absolute rotary encoders enable you to determine the button angle without reference to the context, by measuring the encoder distinct signals. The wind vane that we built and described in this post is actually a absolute rotary encoder.
A rotary encoder and its button
An incremental rotary encoder does not enable you to determine the button angle, but is detects the rotation of the dial angle and the direction of the rotation. For example, it enables you to detect an event of the type "rotation of a 1/30th of a full turn to the right". But you cannot compute the absolute position of the dial from the signals of the encoder.
The scroll wheel of a mouse uses an incremental rotary encoder
The advantage of an incremental rotary encoder is that it is simpler to build. You need only two signals, A and B, to detect a rotation of a fraction of a turn (for example 1/16 of a turn). A rotary encoder is very useful to create human-computer interfaces, in particular to navigate in menus and lists. You are probably using one at this moment: the scroll wheel of your mouse.
The A and B signals are reversed every 1/X turns but, in order to detect the rotation direction, both signals are shifted. In this way, you can determine the rotation direction by looking whether the last transition is that of the A signal or of the B signal.
The logic behind an incremental rotary encoder
To reliably determine the rotation direction and the number of rotations, it is very important to not miss a transition and to memorize the latest transition of each signal. Detecting a series of transitions with a mini ARM computer (Raspberry Pi or other) can seem simple, but it is not so. You need to check the states of the A and B inputs under interruption to be sure not to miss a transition and to suppress the rebound effect of each transition. Fortunately, the Yocto-Knob can perform both of these tasks.
To interpret the state of the encoder, we use the Yocto-Knob transition counters. The Yocto-Knob has a transition counter for each input. On top of these transition counters, the Yocto-Knob stores the "hour" of the latest "pressed" transition and of the latest "released" transition of each input. Thanks to this information, we can determine the number of rotations and their direction.
Our encoder is a serrated model and each notch corresponds to 1/30th of a turn. When this encoder is in a notch, both inputs are in the same position (see the diagram above). We are going to show you two implementations: one by polling and one by callback.
Use by polling
The first step is to set to zero the transition counters of input A and B, as well as our global variable old_counter.
encoderB = YAnButton.FindAnButton("encoderB")
encoderA.set_pulseCounter(0)
encoderB.set_pulseCounter(0)
old_counter = 0
For a reliable result, we compute the rotation only when the A and B signals have the same value (because they are in a notch). We then compute the number of transitions since the last call by subtracting old_counter to the value of input A counter. When we have determined the number of transitions, we must determine the rotation direction. If the latest transition occurred with signal B, the rotation is clockwise. In the opposite, if the latest transition occurred with signal A, the rotation is counterclockwise.
The function is relatively simple and makes use of the following methods of the YAnButton class:
- get_isPressed: returns true if the input is "pressed"
- get_pulseCounter:returns the value of the pulse counter
- get_lastTimePressed: returns the number of milliseconds between when the module was powered on and the latest "pressed" transition
if encoderA.get_isPressed() != encoderB.get_isPressed():
return
counter = encoderA.get_pulseCounter()
global old_counter
nb_transitions = counter - old_counter
if nb_transitions != 0:
b_get_last_time_pressed = encoderB.get_lastTimePressed()
a_get_last_time_pressed = encoderA.get_lastTimePressed()
if (b_get_last_time_pressed > a_get_last_time_pressed):
print("rotate clockwise of %d Detents " % nb_transitions)
else:
print("rotate anti-clockwise of %d Detents" % nb_transitions)
old_counter = counter
We only need to call handleRotatePolling() regularly to detect the rotations.
handleRotatePolling()
...
do_some_stuff()
...
Be careful to not wait for too long in between each call. For example, if you call our function once per second, you risk to have interpretation errors. In a second, the user can rotate of a quarter of a turn to the left and then of a quarter of a turn to the right (coming back to the starting position). In this case, instead of detecting two inverse rotations, the program detects a 180° rotation to the right.
Use by callbacks
If you need a maximum of reactivity, you can work with callbacks. In this case, we don't use the Yocto-Knob transition counter but we store a callback on each input. The callback is called each time the values of the Yocto-Knob inputs change. The number of rotations is therefore always 1. To determine the direction, we check whether it's the A or B signal that went up or down.
encoderB = YAnButton.FindAnButton("encoderB")
last_ev_pressed = encoderA.get_isPressed()
encoderA.registerValueCallback(handleRotate)
encoderB.registerValueCallback(handleRotate)
...
def handleRotate(button, value):
"""
handle rotation of encoder
@type button: YAnButton
@type value: str
"""
global last_ev_pressed
pressed = int(value) < 500
if pressed != button.get_userData():
if last_ev_pressed != pressed:
if (button.get_logicalName() == "encoderA"):
print("rotate clockwise of 1 Detents")
else:
print("rotate anti clockwise of 1 Detents")
last_ev_pressed = pressed
button.set_userData(pressed)
Sources files are available on GitHub.
Conclusion
Human-computer interaction using a rotary encoder with a Yocto-Knob |
Rotary encoders are a clever and more advanced mean to create human-computer interfaces. Even more so with some encoders containing a button which enables it to detect a push on the dial. In this case, with a single dial, you can use three functions: rotation, reverse rotation, and click. It's particularly useful to navigate in a list or a menu. Beware nonetheless to not abuse it :-)