Rotary encoder with asyncio?

Discussion about programs, libraries and tools that work with MicroPython. Mostly these are provided by a third party.
Target audience: All users and developers of MicroPython.
User avatar
pythoncoder
Posts: 5956
Joined: Fri Jul 18, 2014 8:01 am
Location: UK
Contact:

Re: Rotary encoder with asyncio?

Post by pythoncoder » Tue Mar 29, 2022 12:42 pm

@illum07 You wanted an interface to (u)asyncio. This class needs to be run as part of a uasyncio script. See the test script for an example.

There is a widespread myth that you need a state table to decode quadrature signals. This is incorrect: it can be done using the bitwise exclusive-or operator. If you read this doc you will see a synchronous decoder which uses this method. It is faster and simpler than using a state table.

FWIW I used this approach to design a hardware decoder for an NC machine tool. With correct synchronisation it can provide edge-perfect decoding. Synchronisation issues tend to mean that software solutions are not edge-perfect.
Peter Hinch
Index to my micropython libraries.

User avatar
Mike Teachman
Posts: 155
Joined: Mon Jun 13, 2016 3:19 pm
Location: Victoria, BC, Canada

Re: Rotary encoder with asyncio?

Post by Mike Teachman » Tue Mar 29, 2022 7:29 pm

If you are turning an encoder at a slow speed (e.g. manual knob turning), here is a gray code state machine implementation that will likely meet your needs. It includes 2 uasyncio examples.
https://github.com/miketeachman/micropython-rotary
https://github.com/miketeachman/micropy ... asyncio.py
https://github.com/miketeachman/micropy ... o_class.py

tested on ESPs, pyboards, and rp2

rkompass
Posts: 66
Joined: Fri Sep 17, 2021 8:25 pm

Re: Rotary encoder with asyncio?

Post by rkompass » Tue Mar 29, 2022 9:23 pm

@pythoncoder: Hello Peter, I still think in this case your solution is flawed. See viewtopic.php?f=6&t=12002&p=65289#p65289. As I found via testing with ordinary switches.
You do not need a full state table, I agree. But you have to handle the case that the contact bouncing reversed the condition that initiated the interrupt. Which a state table would do. If you just read out the state of the signals in the interrupt routine and xor these you get false signals if one of these signals switched back while your interrupt routine started. So you have to take the last state before the interrupt into account. Just for one of both x, y signals is sufficient, because if both bounce you are lost anyway...

pidou46
Posts: 101
Joined: Sat May 28, 2016 7:01 pm

Re: Rotary encoder with asyncio?

Post by pidou46 » Wed Mar 30, 2022 9:35 am

ilium007 wrote:
Mon Mar 28, 2022 8:24 pm
I’m using the Adafruit STM32F405 board to prototype with.
It doesn't matter which board you use, the AS5600 sensor is really easy to use, you just have to request absolute position via 2ic bus each time you need it. But it's limited to one turn.
nodemcu V2 (amica)
micropython firmware Daily build 05/31/2016

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

Re: Rotary encoder with asyncio?

Post by pythoncoder » Wed Mar 30, 2022 12:27 pm

rkompass wrote:
Tue Mar 29, 2022 9:23 pm
@pythoncoder: Hello Peter, I still think in this case your solution is flawed...
This is not the case, providing signals are correctly synchronised. The exclusive or solution tracks every edge. So if contact bounce (or vibration with an optical encoder) occurs, the count jitters by one digit but does not systematically change. This is the best any algorithm can achieve; all correct algorithms produce the same result.

Work through the logic of the exclusive or solution and I think you will agree: every edge is accounted for, whether from motion or bounce/vibration.

However there is a big caveat, and this applies whether or not a state table is used and indeed whether the decoder is implemented in software or in hardware. If transitions occur at too high a rate, some will be missed. This causes drift. Further, bouncing contacts can lead to intermediate logic levels. The solution is to pre-synchronise the signals with hardware so that the maximum rate is limited. Even this is non-trivial because of metastability: for absolute accuracy you need multiple (usually two) stages of synchronisation.

If you study the hardware design of the STM32, with its hardware decoder, you will see that they pre-synchronise in the way I describe. See also the wikipedia article.

Getting this right 49 years ago took me a long time as the issues (especially metastability) were not widely known. So this stuff is seared in my memory.

The takeaway is that software solutions are adequate for typical use with switches. They are inadequate for bit perfect decoding without extra hardware.
Peter Hinch
Index to my micropython libraries.

rkompass
Posts: 66
Joined: Fri Sep 17, 2021 8:25 pm

Re: Rotary encoder with asyncio?

Post by rkompass » Thu Mar 31, 2022 5:29 pm

My argument has to be detailed:

Let's assume pin Y is 0.
Let's assume that pin X bounces. Say it goes from 0 to 1 starting an interrupt and then goes back to 0.
The x-callback does:
forward = pin_x() ^ pin_y() => forward = 0 ^ 0 = 0 (backward)
pos += 1 if forward else -1 => pos -=1

So the position gets decremented, although it shouldn't so.
This is what I observed when switching two switches x and y by hand. Sometimes the position changed by 3 or 4 just
from pressing one switch without going further - what is to be expected with real contact bounce.

My explanation is that although the logic of the xor theoretically works it requires the interrupt routine
to have access to the logic state of both pins immediately after the interrupting flank. As soon as there is time for a second change
before the interrupt routine processes the pin levels the logic is flawed.

rkompass
Posts: 66
Joined: Fri Sep 17, 2021 8:25 pm

Re: Rotary encoder with asyncio?

Post by rkompass » Thu Mar 31, 2022 5:55 pm

I see that you mentioned: "If transitions occur at too high a rate, some will be missed".
Yes, exactly. And contact bounce may be very fast.
My idea that lead to a modification of your class is that remembering the last state of the pin allows to delegate the evaluation of pin changes into the software. The interrupting flank just leads to a start of the interrupt routine and there the software checks if a change has occured. It fetches the momentary pin values and by comparing with the saved previous values decides if a change has to be processed.
Very fast 0 - 1 - 0 bouncing sequences thus will be ignored. If a change has to be processed the now known actual values go into the xor logic.
So the bouncing of an individual x (or y) signal may be much faster than the reaction of the interrupt routine. Of course the quadrature sequence 00 01 11 10 cannot be faster than the interrupt routine.

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

Re: Rotary encoder with asyncio?

Post by pythoncoder » Fri Apr 01, 2022 9:09 am

I have updated my doc on encoders to include a detailed section on algorithms and also a better description of the synchronisation issue, as this is something of a FAQ.

The doc is a work in progress so it may be a bit rough in places.
Peter Hinch
Index to my micropython libraries.

ilium007
Posts: 37
Joined: Tue Feb 16, 2021 10:29 am

Re: Rotary encoder with asyncio?

Post by ilium007 » Wed Apr 06, 2022 12:53 pm

The @pythoncoder micropython-async v3 encoder code is working perfectly in my project.

The Mike Teachman code (https://github.com/miketeachman/micropython-rotary) also works but had to modify for my specific encoder design (inverting schmidt triggers on output).

Post Reply