Hello
As I understand it, incremental encoder (knobs) are better handled by transition state machines, because these handle bouncing in one of the two X, Y signals only well. So for this use case I would recommend Mike Teachman s solution or the code below.
Interrupt routines like those in @pythoncoder s Encoder class
https://github.com/peterhinch/micropyth ... NCODERS.md are prone to error here, because they do not take the last readout signals into account: A change in YX from 00 to 01 triggering an interrupt will lead to an error if X bounced back before the interrupt routine reads the X value, for example. The error consists in then decrementing position. I found this to be the case in a simple setup with two switches pressed by hand.
A compromise which seems to work well, is to debounce each signal independent from the other. I changed pythoncoders class a bit:
Code: Select all
from machine import Pin
class Encoder:
def __init__(self, pin_x, pin_y, scale=1):
self.scale = scale
self.pin_x = pin_x
self.pin_y = pin_y
self._pos = 0
self.x_old = pin_x()
self.y_old = pin_y()
try:
self.x_int = pin_x.irq(trigger=Pin.IRQ_RISING | Pin.IRQ_FALLING, handler=self.x_callb, hard=True)
self.y_int = pin_y.irq(trigger=Pin.IRQ_RISING | Pin.IRQ_FALLING, handler=self.y_callb, hard=True)
except TypeError:
self.x_int = pin_x.irq(trigger=Pin.IRQ_RISING | Pin.IRQ_FALLING, handler=self.x_callb)
self.y_int = pin_y.irq(trigger=Pin.IRQ_RISING | Pin.IRQ_FALLING, handler=self.y_callb)
def x_callb(self, pin):
if pin() ^ self.x_old:
self._pos += -1 if self.x_old ^ self.pin_y() else 1
self.x_old ^= 1
def y_callb(self, pin):
if pin() ^ self.y_old:
self._pos += 1 if self.y_old ^ self.pin_x() else -1
self.y_old ^= 1
def position(self, value=None):
if value is not None:
self._pos = round(value / self.scale)
return self._pos * self.scale
class Report:
def __init__(self, timer, encoder):
self.position = encoder.position
timer.callback(self.cb)
def cb(self, tim):
print(self.position())
px = Pin('X11', Pin.IN, Pin.PULL_UP)
py = Pin('X12', Pin.IN, Pin.PULL_UP)
enc = Encoder(px, py)
rep = Report(pyb.Timer(4, freq=1), enc)
The
together with
does this.
Rotary encoders on motors on the other hand usually do not have contact bounce, as stated here before, so only the speed of processing the signals counts. If processing in hardware is not possible and processing in C not available I suggest evaluating only the change in one of the two signals, say X. Unfortunately we cannot register two hard interrupt routines for rising and falling edge of the same signal in micropython (this would make the code simpler, tried it just now with the pyb and machine module). Why this limitation, btw.?
So, for speed leave out the y interrupts, y callback routine and self.y_old and wrapping the callback routine in @micropython.viper
Also the callback could be adapted to work without unnecessary arguments, referring to fixed pins and global variables for the position to make it faster. Trying this I arrived at:
Code: Select all
import micropython
import stm
from machine import Pin
micropython.alloc_emergency_exception_buf(160)
# ---- these lines are for finding the constants 1073874960, 4 and 5 below ---
px = Pin('X11', Pin.IN, Pin.PULL_UP)
py = Pin('X12', Pin.IN, Pin.PULL_UP)
_ports = [stm.GPIOA, stm.GPIOB, stm.GPIOC]
print('px addr, bit: ', _ports[px.port()] + stm.GPIO_IDR, px.pin())
print('py addr, bit: ', _ports[py.port()] + stm.GPIO_IDR, py.pin())
# -----------------------------------------------------------------------------
_pos : int = 0
_xold : int = 0
@micropython.viper
def enc_callb(line):
global _pos, _xold
gpio_xy = ptr32(1073874960)
pos : int = int(_pos)
xold : int = int(_xold)
xval : int = (gpio_xy[0] >> 4) & 1
yval : int = (gpio_xy[0] >> 5) & 1
if xval ^ xold:
pos += 1 if xold ^ yval else -1
xold ^= 1
_pos = pos
_xold = xold
class Report:
def __init__(self, timer):
timer.callback(self.cb)
def cb(self, tim):
print(_pos)
px.irq(trigger=Pin.IRQ_RISING | Pin.IRQ_FALLING, handler=enc_callb, hard=True)
rep = Report(pyb.Timer(4, freq=1))