adafruit / circuitpython

CircuitPython - a Python implementation for teaching coding with microcontrollers
https://circuitpython.org
Other
4.09k stars 1.21k forks source link

RotaryIO Incremental Encoder 'missing' steps #8695

Closed imdbere closed 10 months ago

imdbere commented 10 months ago

CircuitPython version

Adafruit CircuitPython 8.2.6 on 2023-09-12; Raspberry Pi Pico with rp2040

Code/REPL

import rotaryio
import board

last_encoder_position = None
encoder = rotaryio.IncrementalEncoder(board.GP7, board.GP8, 1)
while True:
    position = encoder.position
    if last_encoder_position is None or position != last_encoder_position:
        print("Encoder pos: " + str(position))
    last_encoder_position = position

Behavior

Hi, I am using CircuitPython with a generic incremental encoder and i have the following problem: Whenever i turn the encoder it seems to not react to one of the increments and then add two to the next one. So the outputted position increases like 0, 2, 4 etc. while only reacting to every second increment. This means the total amount of increments is correct, but it makes it not really usable for things like an input for a UI.

I did some further measurements and it seems like the library is only counting a step when either of the pins goes from HIGH to LOW, and not doing anything when they go from LOW to HIGH. Is this intended behaviour? Thanks :)

Description

No response

Additional information

No response

todbot commented 10 months ago

I believe you want to change the last argument you have to rotaryio.IncrementalEncoder() from a 1 to a 4 (the default). e.g. do this:

encoder = rotaryio.IncrementalEncoder(board.GP7, board.GP8, 4)

or

encoder = rotaryio.IncrementalEncoder(board.GP7, board.GP8)
imdbere commented 10 months ago

Thanks, i tried that but setting it to 4 makes the value only change on every 4th increment

todbot commented 10 months ago

Have you tried setting it to 2? It really depends on the encoder what that value should be.

dhalbert commented 10 months ago

Could you link to the encoder you are using? How are you wiring it? There should be two signal terminals, and a common terminal connected to ground.

jepler commented 10 months ago

I had a rotary encoder project handy, QT Py RP2040 and CNC rotary encoder

This project is running Adafruit CircuitPython 8.2.8 on 2023-11-16; Adafruit QT Py RP2040 with rp2040

# SPDX-FileCopyrightText: 2018 Kattni Rembor for Adafruit Industries
#
# SPDX-License-Identifier: MIT

import board
#import rotaryio
from rotaryio import IncrementalEncoder
import usb_hid
from adafruit_hid.mouse import Mouse

r = IncrementalEncoder(board.MOSI, board.MISO, count=1)
mouse = Mouse(usb_hid.devices)
old_position = r.position

while True:
    position = r.position
    delta = position - old_position
    if delta:
        print(f"{position%4} {delta:+2d}")
    old_position = position
    if delta != 0:
        mouse.move(wheel=-delta)

I don't reproduce the mentioned problem. Let me describe what I do see:

There are all kinds of encoder wheels in the world; those that have detents can have them every 1, 2, or 4 quadrature counts apart. Most or all of the encoder wheels that Adafruit sells that have detents are 4 counts per detent, which is why the default is 4.

imdbere commented 10 months ago

I don't know the exact model of the encoder anymore unfortunately and i tried all the settings, like i said a divisor of 1 leads to the correct total counted clicks, just the way its counted is wrong (2 position changes every two clicks).

I think the problem is that the library counts every falling edge twice while not counting rising edges at all, which probably makes no difference if the encoder has more than 1 count per detent. Maybe this graphic will help:

encoder

Thanks for you help!

todbot commented 10 months ago

It really depends on the encoder, which is why DanH asked for details. The number of felt detents does not always correlate to number of pulses. See this datasheet for the common EC11 series of rotary encoders: https://www.mouser.com/datasheet/2/15/EC11-1370808.pdf Notice the "number detents" vs "number of pulses" columns.

dhalbert commented 10 months ago

Do you have another non-RP2040 board to try, such as a SAMD21 or SAMD51 (or Espressif or nRF)? The internal implementation is different for those processor families. If you get different results, then that says something is different about the RP2040 implementation. We have done testing of this and it didn't seem off (for instance the rotary encoder on the MacroPad, which uses an RP2040).

imdbere commented 10 months ago

Like mentioned i don't know the exact model but measuring the output seems to match this diagram in the datasheet, so basically half a cycle per detent. I do have some ESP32s laying around which i could give a try if that helps

image
jepler commented 10 months ago

These details apply to RP2040; other microcontroller families may use other methods for quadrature counting

The IncrementalEncoder on the RP2040 uses all quadrature edges (different than either of the images you showed).

Each transition 00 -> 01 -> 11 -> 10 -> 00 increases an internal counter by 1, and any transition in the reverse direction decreases it by 1. (invalid glitches like 00 -> 11 or 01 -> 10 do not change the count; non-changes like 00 -> 00 do not change the count)

When the internal counter reaches the division value in positive direction, the position property increases by 1. When it reaches it in the negative direction (e.g., -1, -2 or -4) the position property decreases by 1.

It might be helpful to eliminate your encoder entirely. I grabbed an Adafruit Macropad because it has an RP2040 microcontroller, display, and keys; and created a program that watches the first two keys and treats them like a quadrature encoder.

import board
from rotaryio import IncrementalEncoder

e = IncrementalEncoder(board.KEY1, board.KEY2, 1)
v = e.position
print(f"{0:+1d} {v}")

while True:
    new_v = e.position
    if v != new_v:
        delta = new_v - v
        print(f"{delta:+1d} {new_v}")
        v = new_v

you can probably create a similar setup on your Pico with some spare switches.

If I go through these steps starting with no keys pressed:

+0 0
+1 1
+1 2
+1 3
+1 4
+1 5
+1 6
+1 7
+1 8
-1 7
-1 6
-1 5
-1 4
-1 3
-1 2
-1 1
-1 0
-1 -1
-1 -2
-1 -3
-1 -4

If I change the divisor from 1 to 2 or 4, then position changes by just 1/2 or 1/4 as many:

+0 0
+1 1
+1 2
-1 1
-1 0
-1 -1
imdbere commented 10 months ago

Thanks for the detailed response, will do some debugging and then come back to you, if it is counted like you described there must be an error in my setup šŸ¤”

jepler commented 10 months ago

Please show us how you have wired the project. I think that would be more helpful in figuring out what is going on, vs substituting a different microcontroller.

Repeating Dan's earlier message:

There should be two signal terminals, and a common terminal connected to ground.

jepler commented 10 months ago

example of wiring an encoder with circuitpython (this one also wires the "press in to click" function available on some encoders) image

dhalbert commented 10 months ago

if one of the signal terminals is interchanged with ground, that might cause what you are seeing

imdbere commented 10 months ago

I checked my circuit again and I indeed had the ground wire switched with one of the signal wires (didn't have a pinout or a datasheet unfortunately šŸ˜‘). Feeling quite stupid now, thanks for your all your help and sorry for the caused effort!

jepler commented 10 months ago

You're welcome. it could happen to anybody :)