A library for reading rotary encoders (ESP-8266 and Pyboard/stmhal)

Discuss development of drivers for external hardware and components, such as LCD screens, sensors, motor drivers, etc.
Target audience: Users and developers of drivers.
SpotlightKid
Posts: 463
Joined: Wed Apr 08, 2015 5:19 am

A library for reading rotary encoders (ESP-8266 and Pyboard/stmhal)

Post by SpotlightKid » Sun Sep 04, 2016 12:28 pm

A library to read rotary encoders:

https://github.com/SpotlightKid/micropy ... er/encoder

The rotary encoder needs to connect to two digital input pins of of the board, for which external interrupts can be enabled. The code employs gray code error checking using the technique described in this blog post, so no further hardware or software debouncing is needed and the library works well even with cheap encoders, which usually have switches with lots of bouncing.

Some MicroPython boards (notably the STM32F4-based boards like the pyboard), have hardware support for reading rotary encoders, which requires less work from the CPU and may be better suited when using encoders to read the radial speed of motors etc. But usually the number of encoders supported this way is very limited (one or two).

With this library instead, the number of supported encoders is only limited by the available input pins with external interrupt support (two such pins are needed per encoder). It was developed specifically for using rotary encoders as user input controls. It has support for setting the input value range, pulses per dedent ("click") and acceleration.

User avatar
pythoncoder
Posts: 5956
Joined: Fri Jul 18, 2014 8:01 am
Location: UK
Contact:

Re: A library for reading rotary encoders (ESP-8266 and Pyboard/stmhal)

Post by pythoncoder » Mon Sep 05, 2016 7:25 am

There is an algorithm using the exclusive or function, which also handles contact bounce.

Code: Select all

import pyb
class Encoder(object):
    def __init__(self, pin_x, pin_y, reverse, scale):
        self.reverse = reverse
        self.scale = scale
        self.forward = True
        self.pin_x = pin_x
        self.pin_y = pin_y
        self._pos = 0
        self.x_interrupt = pyb.ExtInt(pin_x, pyb.ExtInt.IRQ_RISING_FALLING, pyb.Pin.PULL_NONE, self.x_callback)
        self.y_interrupt = pyb.ExtInt(pin_y, pyb.ExtInt.IRQ_RISING_FALLING, pyb.Pin.PULL_NONE, self.y_callback)

    def x_callback(self, line):
        self.forward = self.pin_x.value() ^ self.pin_y.value() ^ self.reverse
        self._pos += 1 if self.forward else -1

    def y_callback(self, line):
        self.forward = self.pin_x.value() ^ self.pin_y.value() ^ self.reverse ^ 1
        self._pos += 1 if self.forward else -1

    @property
    def position(self):
        return self._pos*self.scale

    def reset(self):
        self._pos = 0
Constructor arguments:
pin_x, pin_y the Pin instances
reverse allows the direction to be set
scale enables optional distance scaling (I wrote this for an encoder on a robot wheel).
If using this with switches you might need to change the pyb.Pin.PULL_NONE to provide pullups or pull downs.
Peter Hinch
Index to my micropython libraries.

SpotlightKid
Posts: 463
Joined: Wed Apr 08, 2015 5:19 am

Re: A library for reading rotary encoders (ESP-8266 and Pyboard/stmhal)

Post by SpotlightKid » Mon Sep 05, 2016 9:51 am

pythoncoder wrote:There is an algorithm using the exclusive or function, which also handles contact bounce:
How so? Your callback functions only look at the current state of the pins after the interrupt. If one of the switches of the encoder bounces, its interrupt handler will be triggered several times in direct succession and you'll get illegal or misleading state transitions. The technique I'm using combines the current reading with the previous one to detect this and discard wrong transitions.

AFAICS, the exclusive or algorithm only implements the conversion of the encoder pulses into a direction, but doesn't handle debouncing.

User avatar
dhylands
Posts: 3821
Joined: Mon Jan 06, 2014 6:08 pm
Location: Peachland, BC, Canada
Contact:

Re: A library for reading rotary encoders (ESP-8266 and Pyboard/stmhal)

Post by dhylands » Mon Sep 05, 2016 5:33 pm

I've found that the best way to deal with debouncing for quadrature is simple polling. As long as you poll the pins at a rate faster than the change in values, then it self-corrects against noise and bounce.

With purely irq based you always run the risk of missing edges.

Combining IRQ with polling can also work.

You can also use external quadrature decoder chips, like the LS7366:
http://www.usdigital.com/products/inter ... FLS7366R-S
will poll at a very high rate (like 10 or 20 MHz) and provide a SPI interface.

User avatar
pythoncoder
Posts: 5956
Joined: Fri Jul 18, 2014 8:01 am
Location: UK
Contact:

Re: A library for reading rotary encoders (ESP-8266 and Pyboard/stmhal)

Post by pythoncoder » Tue Sep 06, 2016 7:26 am

SpotlightKid wrote:...If one of the switches of the encoder bounces, its interrupt handler will be triggered several times in direct succession and you'll get illegal or misleading state transitions...
This would only occur if state transitions occurred so fast that a second one occurred while the interrupt handler was running. Then, depending on processor architecture, an interrupt might be missed. Given the observed rates of contact bounce events and the simplicity of the ISR this strikes me as improbable and I didn't observe it in testing. I'm unaware of any other failure mechanism unless an interrupt is actually missed.

But you are right in principle. As a digression I implemented this in CMOS logic over 40 years ago in a CNC machine design. To achieve 100% accuracy in the presence of vibration I had to clock in the encoder signals using two stages of d-types to combat metastability issues (a problem little understood c1975) ;)
Peter Hinch
Index to my micropython libraries.

User avatar
dhylands
Posts: 3821
Joined: Mon Jan 06, 2014 6:08 pm
Location: Peachland, BC, Canada
Contact:

Re: A library for reading rotary encoders (ESP-8266 and Pyboard/stmhal)

Post by dhylands » Tue Sep 06, 2016 8:30 am

I think it depends on whether the signal is clean or not. On a clean signal, from a digital source, then there should be no bounce on the edges. But if you're using a mechanical encoder, it's entirely possibly that you'll get a bunch of edges on the transition. These are fast narrow edges, so whether and how exactly they get processed depends alot on the ISR routine and the MCU etc.

Even a simple RC circuit can help here, depending on the frequency of the signal.

Just because it happens to work today, doesn't mean that it will continue to work in the future (been there - done that). Changes in the environment (temperature or humidity) can all of a sudden make your project go from working to not working (or working all to the time to working most of the time) for no apparent reason.

User avatar
pythoncoder
Posts: 5956
Joined: Fri Jul 18, 2014 8:01 am
Location: UK
Contact:

Re: A library for reading rotary encoders (ESP-8266 and Pyboard/stmhal)

Post by pythoncoder » Thu Sep 08, 2016 6:33 am

SpotlightKid wrote:...Your callback functions only look at the current state of the pins after the interrupt. If one of the switches of the encoder bounces, its interrupt handler will be triggered several times in direct succession and you'll get illegal or misleading state transitions...
On reflection, and testing, I think both our solutions suffer from the same limitation. Receiving multiple successive edges on a single pin is not a problem for either algorithm, so long as the rate of arrival is limited.

But if a second edge occurs while the interrupt handler for the first edge is still running, the Pyboard locks up. I'm unsure how these various platforms are supposed to cope with re-entrancy but it's best avoided. A small capacitor on each line will ensure that edges can't occur at too high a frequency, as suggested by @dhylands. The internal pull-up/pull-down resistors are 30K minimum so 10nF would provide about 300uS delay to a rising edge (assuming a pull-up). Our ISR's should run in a considerably shorter time, but there is the potential issue of other, higher priority, ISR's running in the background. These have unknown durations which could effectively extend the run time of our ISR's.

I remain convinced that both solutions are equivalent. I also wonder whether with physical switches this is a practical or a merely theoretical problem, but I'm no expert on the physics of contact bounce.
Peter Hinch
Index to my micropython libraries.


User avatar
pythoncoder
Posts: 5956
Joined: Fri Jul 18, 2014 8:01 am
Location: UK
Contact:

Re: A library for reading rotary encoders (ESP-8266 and Pyboard/stmhal)

Post by pythoncoder » Thu Sep 08, 2016 7:06 am

Thanks for that, an interesting read. I never realised switches could be so dire. My usual approach is to re-read 20mS after the first edge, but this is evidently far from "worst case".

As an aside, to users of RC networks the Pyboard has schmitt triggers on the GPIO pins with ~200mV hysteresis. but that may not apply to other platforms. As the article states, slow edges on conventional logic inputs are very bad news.Caveat implementator!
Peter Hinch
Index to my micropython libraries.

vitalsparkplug
Posts: 1
Joined: Mon Aug 22, 2016 2:16 pm

Re: A library for reading rotary encoders (ESP-8266 and Pyboard/stmhal)

Post by vitalsparkplug » Tue Jan 17, 2017 11:56 pm

I am using SpotlightKid's [Rotary Encoder](https://github.com/SpotlightKid/micropython-stm-lib.git) and the OneHot lookup table plus interrupt callback latency seems to debounce the rotary switches nicely. The interrupt callback does a little calculation on the Encoder objects to update _value and acceleration.
Now my Encoder has a push button that I would like to multiplex between two Encoder objects,at least.Each object maintains state (min,_value,max) that are different (min=50,max=80,_value=current set temp) verses (min=60,max=400,_value=current duration in min).
I have read the doc guidelines on interrupts and understand that the heap is locked. Can I use any sort of information on the currently enabled/disabled (Boolean data member) object in the callback to direct which object should be updated?
When I create the second Encoder object I pass Pin objects. So the two Encoder objects have the same two Pin objects that read the encoder switches.
Pushing the button on the encoder multiple times can multiplex around any number of micro python data members that need changed...and perhaps shown on an LCD screen or WebPage.

Post Reply