-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add RotaryEncoder support #1
Comments
@PaulStroffregen implementationhttps://github.com/PaulStoffregen/Encoder Copyright (c) 2011,2013 PJRC.COM, LLC - Paul Stoffregen <paul@pjrc.com>
// _______ _______
// Pin1 ______| |_______| |______ Pin1
// negative <--- _______ _______ __ --> positive
// Pin2 __| |_______| |_______| Pin2
// new new old old
// pin2 pin1 pin2 pin1 Result
// ---- ---- ---- ---- ------
// 0 0 0 0 no movement
// 0 0 0 1 +1
// 0 0 1 0 -1
// 0 0 1 1 +2 (assume pin1 edges only)
// 0 1 0 0 -1
// 0 1 0 1 no movement
// 0 1 1 0 -2 (assume pin1 edges only)
// 0 1 1 1 +1
// 1 0 0 0 +1
// 1 0 0 1 -2 (assume pin1 edges only)
// 1 0 1 0 no movement
// 1 0 1 1 -1
// 1 1 0 0 +2 (assume pin1 edges only)
// 1 1 0 1 -1
// 1 1 1 0 +1
// 1 1 1 1 no movement
/*
// Simple, easy-to-read "documentation" version :-)
//
void update(void) {
uint8_t s = state & 3;
if (digitalRead(pin1)) s |= 4;
if (digitalRead(pin2)) s |= 8;
switch (s) {
case 0: case 5: case 10: case 15:
break;
case 1: case 7: case 8: case 14:
position++; break;
case 2: case 4: case 11: case 13:
position--; break;
case 3: case 12:
position += 2; break;
default:
position -= 2; break;
}
state = (s >> 2);
}
|
Please, view gpiozero/gpiozero#392 |
# -*- coding: utf-8 -*-
from gpiozero import DigitalInputDevice
class RotaryEncoder(object):
"""
Decode mechanical rotary encoder pulses.
The following example will print a Rotary Encoder change direction::
from gpiozero import RotaryEncoder
def change(value):
if value > 0:
print("clockwise")
else:
print("counterclockwise")
rotary = RotaryEncoder(13, 19)
rotary.when_rotated = change
Based in http://abyz.co.uk/rpi/pigpio/examples.html#Python_rotary_encoder_py
"""
gpio_a = None
gpio_b = None
high_a = False
high_b = True
when_rotated = lambda *args : None
def __init__(self, pin_a, pin_b, pull_up=False):
"""
Uses for dettect rotary encoder changes (set when_rotated attribute)
It takes one parameter which is +1 for clockwise and -1 for counterclockwise.
:param int pin_a:
Pin number of first (left) pin.
:param int pin_b:
Pin number of last (right) pin.
:param bool pull_up:
The common contact (middle) should be NOT connected to ground?
"""
self.gpio_a = DigitalInputDevice(pin=pin_a, pull_up=pull_up)
self.gpio_b = DigitalInputDevice(pin=pin_b, pull_up=pull_up)
self.high_a = self.gpio_a.value
self.high_b = self.gpio_b.value
high = True
self.gpio_a.when_activated = lambda *args : self.pulse(True, high)
self.gpio_a.when_deactivated = lambda *args : self.pulse(True, not high)
self.gpio_b.when_activated = lambda *args : self.pulse(False, high)
self.gpio_b.when_deactivated = lambda *args : self.pulse(False, not high)
def pulse(self, is_pin_a, level):
"""
Decode the rotary encoder pulse.
+---------+ +---------+ 1
| | | |
A | | | |
| | | |
+---------+ +---------+ +----- 0
+---------+ +---------+ 1
| | | |
B | | | |
| | | |
----+ +---------+ +---------+ 0
:param bool is_pin_a:
The pulse origin came from pin_a?
:param bool level:
The level is high?
"""
value = 0
plus_a = not self.high_a and self.gpio_a.is_active
plus_b = not self.high_b and self.gpio_b.is_active
minus_a = self.high_a and not self.gpio_a.is_active
minus_b = self.high_b and not self.gpio_b.is_active
self.update_values(is_pin_a, level)
high_a = self.high_a
high_b = self.high_b
low_a = not self.high_a
low_b = not self.high_b
# Commented: middle click interval
# Uncommented: end click interval
# Up transitions (0 → 1)
#if plus_a and low_b:
# value = -1
if plus_a and high_b:
value = 1
#elif low_a and plus_b:
# value = 1
elif high_a and plus_b:
value = -1
# Botton transitions (1 → 0)
elif minus_a and low_b:
value = 1
#elif minus_a and high_b:
# value = -1
elif low_a and minus_b:
value = -1
#elif high_a and minus_b:
# value = 1
# Other cases
elif plus_a and plus_b:
value = +2
elif plus_a and minus_b:
value = -2
elif minus_a and plus_b:
value = -2
elif minus_a and minus_b:
value = +2
# else is no movement
else:
return
self.when_rotated(value)
def update_values(self, is_pin_a, level):
"""
:param bool is_pin_a:
The pulse origin came from pin_a?
:param bool level:
The level is high?
"""
if is_pin_a:
self.high_a = level
else:
self.high_b = level |
|
I haven't tested this, but from a quick glance you really ought to be using DigitalinputDevice (and the EventsMixin callback names), rather than Button. |
@lurch, thanks for your review. I will study your requests for implement this. Actually I have an small problems when the encoder has very speed rotated. When I can make corrections and suggestions, will pull request! One question, some encoders have a button as well. I implement one RotaryEncoderWithButton extending RotaryEncoder or a single class? |
I do my best ;-D
I guess it's possible that at high speeds, python isn't "fast enough" to keep up with all the switching events?
I'd have a RotaryEncoder class, and if you really wanted to you could create a RotaryEncoderWithButton as a CompositeDevice consisting of a |
I need to find a development process that gives to test it.
Good to know the |
# -*- coding: utf-8 -*-
from gpiozero import DigitalInputDevice
class RotaryEncoder(object):
"""
Decode mechanical rotary encoder pulses.
The following example will print a Rotary Encoder change direction::
from gpiozero import RotaryEncoder
def change(value):
if value > 0:
print("clockwise")
else:
print("counterclockwise")
rotary = RotaryEncoder(13, 19)
rotary.when_rotated = change
Based in http://abyz.co.uk/rpi/pigpio/examples.html#Python_rotary_encoder_py
"""
gpio_a = None
gpio_b = None
high_a = False
high_b = False
when_rotated = lambda *args : None
def __init__(self, pin_a, pin_b, pull_up=False):
"""
Uses for dettect rotary encoder changes (set when_rotated attribute)
It takes one parameter which is +1 for clockwise and -1 for counterclockwise.
:param int pin_a:
Pin number of first (left) pin.
:param int pin_b:
Pin number of last (right) pin.
:param bool pull_up:
The common contact (middle) should be NOT connected to ground?
"""
self.gpio_a = DigitalInputDevice(pin=pin_a, pull_up=pull_up)
self.gpio_b = DigitalInputDevice(pin=pin_b, pull_up=pull_up)
self.gpio_a.when_activated = lambda *args : self.pulse()
self.gpio_a.when_deactivated = lambda *args : self.pulse()
self.gpio_b.when_activated = lambda *args : self.pulse()
self.gpio_b.when_deactivated = lambda *args : self.pulse()
self.high_a = self.gpio_a.is_active
self.high_b = self.gpio_b.is_active
self.table_values = TableValues()
def pulse(self):
"""
Calls when_rotated callback if dettected changes
"""
new_pin2 = self.gpio_b.is_active
new_pin1 = self.gpio_a.is_active
old_pin2 = self.high_b
old_pin1 = self.high_a
value = self.table_values.value(new_pin2, new_pin1, old_pin2, old_pin1)
self.high_b = new_pin2
self.high_a = new_pin1
if value != 0:
self.when_rotated(value)
class TableValues:
"""
Decode the rotary encoder pulse.
+---------+ +---------+ 1
| | | |
A | | | |
| | | |
+---------+ +---------+ +----- 0
+---------+ +---------+ 1
| | | |
B | | | |
| | | |
----+ +---------+ +---------+ 0
Based in table:
https://github.com/PedalPi/Physical/issues/1#issuecomment-248977908
"""
def __init__(self):
values = {
0: +0,
1: +1,
2: -1,
3: +2
4: -1,
5: +0,
6: -2,
7: +1,
8: +1,
9: -2
10: +0,
11: -1,
12: +2
13: -1
14: +1,
15: +0
}
def calcule_index(self, new_pin2, new_pin1, old_pin2, old_pin1):
value = 8 if new_pin2 else 0
value += 4 if new_pin1 else 0
value += 2 if old_pin2 else 0
value += 1 if old_pin1 else 0
return value
def value(self, new_pin2, new_pin1, old_pin2, old_pin1):
index = self.calcule_index(new_pin2, new_pin1, old_pin2, old_pin1)
return self.values[index] |
I wonder if you're also being affected by the same issue as gpiozero/gpiozero#385 , which suggests that the way GpioZero is implemented (using Python Events) is slower than 'just' using RPi.GPIO ?
GpioZero has quite a comprehensive test suite (with MockPins that you can use to simulate GPIO pins), but I dunno if it's able to generate pin-change-events quicker than it's able to read them? Couple of little code tips - feel free to ignore these if you wish:
def calcule_index(self, new_pin2, new_pin1, old_pin2, old_pin1):
value = 0
if new_pin2:
value += 8
if new_pin1:
value += 4
if old_pin2:
value += 2
if old_pin1:
value += 1
return value
But this is obviously all your own code, and I'm guessing it's still a work in progress, so feel free to do whatever you want with it :-) |
Slow?
I had not thought that might be it. I will test it and will inform you. I implemented a "driver" for PCD8544 for Pi4j (Pi4J/pi4j-v1#84, Pi4J/pi4j-v1#215). I try port it for gpiozero 3 months ago (https://github.com/PedalPi/Physical/tree/master/component/display), but it's verry slow. Anyway, I will test it. Tests
I do not understand what's going on, so I do not know what to simulate! Code reviewThanks for the suggestions! I'm Java developer, so pythonic patterns (like pep 8) sometimes escape from my fingers.
👍 In my old version, it's neccessary send parameters in the pulse calling. Now not.
Using the PyCharm, I understood that. But for me, the code is cleaner. But if the pattern is this, then I have to do
Readability. I have to update the commentary documentation to make more explicit the algorithm. What makes me realize is that high_a is that it is not clear. Will fix it.
I do not know for sure. It has to do with the responsibilities... I'll still change something, so then decide.
So for your consideration I'll update! 😄
I believe that improvements in this sense who have to make is the compiler. Robert "Uncle Bob" Martin, in Clean Code, convinced me to make small methods. (Ok, I try, right! 😆) Observations
|
Do not judge me from my version control be comments. haha from gpiozero import DigitalInputDevice
class RotaryEncoder(object):
"""
Decode mechanical rotary encoder pulses.
The following example will print a Rotary Encoder change direction::
from gpiozero import RotaryEncoder
def change(value):
if value > 0:
print("clockwise")
else:
print("counterclockwise")
rotary = RotaryEncoder(13, 19)
rotary.when_rotated = change
Based in http://abyz.co.uk/rpi/pigpio/examples.html#Python_rotary_encoder_py
"""
gpio_a = None
gpio_b = None
old_a_value = False
old_b_value = False
when_rotated = lambda *args : None
def __init__(self, pin_a, pin_b, pull_up=False):
"""
Uses for dettect rotary encoder changes (set when_rotated attribute)
It takes one parameter which is +1 for clockwise and -1 for counterclockwise.
:param int pin_a:
Pin number of first (left) pin.
:param int pin_b:
Pin number of last (right) pin.
:param bool pull_up:
The common contact (middle) should be NOT connected to ground?
"""
self.gpio_a = DigitalInputDevice(pin=pin_a, pull_up=pull_up)
self.gpio_b = DigitalInputDevice(pin=pin_b, pull_up=pull_up)
self.gpio_a.when_activated = self.pulse
self.gpio_a.when_deactivated = self.pulse
self.gpio_b.when_activated = self.pulse
self.gpio_b.when_deactivated = self.pulse
self.old_a_value = self.gpio_a.is_active
self.old_b_value = self.gpio_b.is_active
self.table_values = TableValues()
def pulse(self):
"""
Calls when_rotated callback if dettected changes
"""
new_b_value = self.gpio_b.is_active
new_a_value = self.gpio_a.is_active
value = self.table_values.value(new_b_value, new_a_value, old_b_value, old_a_value)
self.old_b_value = new_b_value
self.old_a_value = new_a_value
if value != 0:
self.when_rotated(value)
class TableValues:
"""
Decode the rotary encoder pulse.
+---------+ +---------+ 1
| | | |
A | | | |
| | | |
+---------+ +---------+ +----- 0
+---------+ +---------+ 1
| | | |
B | | | |
| | | |
----+ +---------+ +---------+ 0
Based in table:
https://github.com/PedalPi/Physical/issues/1#issuecomment-248977908
"""
def __init__(self):
self.values = {
0: +0,
1: +1,
2: -1,
# 3: +2,
# 4: -1,
5: +0,
6: -2,
# 7: +1,
# 8: +1,
9: -2,
10: +0,
# 11: -1,
# 12: +2,
13: -1,
14: +1,
15: +0
}
def calcule_index(self, new_b_value, new_a_value, old_b_value, old_a_value):
value = 0
if new_b_value:
value += 8
if new_a_value:
value += 4
if old_b_value:
value += 2
if old_a_value:
value += 1
return value
def value(self, new_b_value, new_a_value, old_b_value, old_a_value):
index = self.calcule_index(new_b_value, new_a_value, old_b_value, old_a_value)
try:
return self.values[index]
except:
return 0
current = 0
def change(value):
global current
current += value
print(current, value)
rotary = RotaryEncoder(19, 13, True)
rotary.when_rotated = change
while True:
continue |
Pins testCommentaryDummyIn my firsts tests I do it in the end:
This makes my processor to operate in> 25% unnecessarily. So soWhen I use the below, was a significant improvement
Correct way http://gpiozero.readthedocs.io/en/v1.3.1/notes.html#keep-your-script-runningUsign signal.pause, the best! AnalisysRPIOPin> sudo python3 rotaryEncoder.py
Traceback (most recent call last):
File "encoder2.py", line 3, in <module>
from gpiozero.pins.rpio import RPIOPin
File "/usr/lib/python3/dist-packages/gpiozero/pins/rpio.py", line 11, in <module>
import RPIO
ImportError: No module named 'RPIO'
> sudo python rotaryEncoder.py
Traceback (most recent call last):
File "encoder2.py", line 3, in <module>
from gpiozero.pins.rpio import RPIOPin
File "/usr/lib/python2.7/dist-packages/gpiozero/pins/rpio.py", line 11, in <module>
import RPIO
File "build/bdist.linux-armv7l/egg/RPIO/__init__.py", line 115, in <module>
File "build/bdist.linux-armv7l/egg/RPIO/_GPIO.py", line 7, in <module>
File "build/bdist.linux-armv7l/egg/RPIO/_GPIO.py", line 6, in __bootstrap__
SystemError: This module can only be run on a Raspberry Pi! Please note that at the time of writing, RPIO is only compatible with Pi 1’s. Ok, I'm usign RPi2... 😞 PiGPIOPin
NativePinIn general, the best results. RPiGPIOPinAfter I install the others (RPIOPin, PiGPIOPin) and slowed. Conclusion
|
Yes, GpioZero v1.3.1 still uses RPi.GPIO as the default Pin backend. But it's not the Pin backend that's making things slow, but the Event framework that @waveform80 has layered on top of it that is slowing down the detection of input-pin-changes. (which is what gpiozero/gpiozero#385 is looking at)
Looks like it should be possible to drive that display using SPI, and GpioZero has support for the Pi's hardware SPI interface (which is much faster than bit-banging) https://gpiozero.readthedocs.io/en/latest/api_spi.html
The Pin API is currently going through another re-working gpiozero/gpiozero#459
You mentioned that you weren't sure if it was your code that was going wrong, or your rotary encoder that was going wrong, so I was suggesting using an Arduino to "simulate" a rotary encoder (i.e. output the same pattern on the pins), so that you at least know the pin-changes are working reliably, to eliminate the possibly-dodgy rotary encoder ;-) (and by altering the timings, you can simulate a rotary encoder turning at different speeds)
Yup, Python is definitely very different to Java! ;-)
Python is an interpreted language, not compiled. So creating unnecessary classes, having more function calls than are strictly needed, etc. can sometimes slow down the code.
Yeah. The author started updating it, but then stopped again metachris/RPIO#77 (comment) :-(
Yeah, PiGPIO works by talking over a socket to the pigpiod daemon, which can sometimes slow things down.
Really? I'd expect it to be slower than RPi.GPIO shrug
Interesting... @waveform80 I wonder if this is related to the |
I will analyze this! Is a good idea!
You're right. I will have this care.
That was a good plan! But as I discovered that the problem was I will add this tip list checks on bugs in adverse situations :)
Macho, isso é paia, ó. I know it is interpreted and there are problems as well. Only he had tried to speak briefly. But it is at least strange language that trapped both readability "suggest" things like this (less functions). As is gained on the one hand, it loses the other.
A curiosity, you think of some way to support the wiring pi?
In fact, it works better with
I do not understand you're asking me something or asking @waveform80. Anyway, I will test the RPi 3 to bring more details. |
In RPi 3 works - the problem was not detected. Maybe it's something to do with |
Yeah, that's just using a busy-loop to suck up all the CPU, so it's not surprising other things slowed down ;-) As you've already discovered, using signal.pause is the 'proper' way to do this.
Well, it depends if you want fast code, or readable code. Sometimes both things aren't possible at the same time.
I did already suggest that ( gpiozero/gpiozero#180 ) but I guess @waveform80 decided it wasn't worth adding.
That's only possible if you choose to use PiGPIIOPin as the backend. But yeah, it's great that the flexibility in GpioZero means that it's possible to switch out the Pin backend, and all the higher-level classes (LED, Button, Motor, etc). all work exactly the same!
Sorry for the confusion, that was a question aimed at @waveform80 ;-)
RPi3 is actually bcm2837 :) BTW I've just looked at the table at the top of this page - and I'm not convinced by the +2 / -2 stuff. If both input pins have changed, then you know that the encoder has moved, but AFAICT it's impossible to tell in which direction it moved. |
Thanks for your tip!
Oops!
ok 😕
I have relied on https://github.com/PaulStoffregen/Encoder/blob/master/Encoder.h. I do not understand either. Maybe @PaulStoffregen can enlighten us. Do you think Rotary Encoder would be a nice increase for gpiozero? If so, I will do a pull request, and you (gpiozero team) - when you have time - could inform what dislike. OK? |
Sorry, I can't help with Raspberry Pi. |
Definitely :-) @PaulStoffregen We're just wondering how / why your code assumes a change in value of +2 or -2 when both input pins have changed. Surely if both pins have changed, the encoder could have moved in either direction? |
First off - sorry I haven't looked at the rotary encoder stuff yet. I've noticed it on my notifications list over on the gpio-zero repo, but I just haven't got time to look into right now (I am definitely interested in supporting it, but right now I'm concentrating on getting the foundations right!)
I'm afraid it fell a bit off the radar while I was investigating pigpio. I do still think I should add it as a backend at some point, simply for the sake of those who want it for whatever reason. However, in terms of capability it's not going to give us much more than RPi.GPIO does already: as I understand it, wiringPi's PWM is also software driven (or more precisely: it can do hardware PWM, but it only does "real" hardware PWM, i.e. on one or two pins only which I don't think is as useful as the DMA-based PWM that RPIO and pigpio provide). On the subject of using SPI for this (sorry if I've mis-understood, I'm just skimming the thread) "proper" support for remote SPI which alleviates the socket delays should be landing "real soon now". And finally ...
Huh?! That's utterly bizarre. NativePin is just some rubbish I threw together as a fallback for when nothing else was importable. It really is terrible, and I wouldn't seriously recommend people use it :)
Quite likely; I vaguely remember seeing similar effects when I was playing with NativePin. Causing that kernel mod distress seemed to affect several things including edge detection via epoll. |
@waveform80, a question.
In Internet, Joonas Pihlajamaa compare the gpio speed:
In your analysis, C (Native library) is the fastest and Shell /proc/mem access is the slowest penultimate. The native implementation (https://github.com/RPi-Distro/python-gpiozero/blob/master/gpiozero/pins/native.py) uses what? I'm needing to do SPI for 3 devices, via software is slow and via hardware could only use channel 1 with access to 2 devices. I plan on using WiringPi only to bitbang (https://github.com/PedalPi/Physical/blob/276f3f3519db5c3b8c03fa354db7b722b7ba5ca9/component/pcd8544/wiring_bitbang.c)... (I have no idea of the consequences of using together) |
Hi SrMouraSilva As the rotary encoder classes are not yet available in a current release of GPIOZERO could I use them as they are in a project and if so under which license? |
Thank you for the contact! It really was not chosen a license for the project. You can use the will, either commercially or for open-source purposes. The only thing I ask is not to use it for evil and, if possible, reference this project and GPIOZero - since the progress of inclusion in GPIOZero is a bit slow. If a problem occurs in the use of the library, please open an issue! |
EDIT: |
The native implementation bangs on the GPIO registers (in an mmap) like several of the C-based GPIO libraries. Because it avoids the file-system it's therefore "fast" but not as fast a C doing the same thing (i.e. the speed difference between it and C-based GPIO libraries is simply that native is pure python; the GPIO access technique is basically equivalent, but for edge detection). I'll certainly look to get rotary encoder into 1.5. Hopefully we'll be investigating the schedule for that shortly but there's no dates just yet. |
Description
Add RotaryEncoder support
Steps
Material
The text was updated successfully, but these errors were encountered: