micropython-micro-gui/gui/primitives/encoder.py

77 wiersze
2.9 KiB
Python

# encoder.py Asynchronous driver for incremental quadrature encoder.
# Copyright (c) 2021-2022 Peter Hinch
# Released under the MIT License (MIT) - see LICENSE file
import uasyncio as asyncio
from machine import Pin
class Encoder:
delay = 100 # Pause (ms) for motion to stop
def __init__(self, pin_x, pin_y, v=0, vmin=None, vmax=None, div=1,
callback=lambda a, b : None, args=(), mod=0):
self._pin_x = pin_x
self._pin_y = pin_y
self._x = pin_x()
self._y = pin_y()
self._v = 0 # Initialise hardware value
self._cv = v # Current (divided) value
if ((vmin is not None) and v < vmin) or ((vmax is not None) and v > vmax):
raise ValueError('Incompatible args: must have vmin <= v <= vmax')
self._tsf = asyncio.ThreadSafeFlag()
trig = Pin.IRQ_RISING | Pin.IRQ_FALLING
try:
xirq = pin_x.irq(trigger=trig, handler=self._x_cb, hard=True)
yirq = pin_y.irq(trigger=trig, handler=self._y_cb, hard=True)
except TypeError: # hard arg is unsupported on some hosts
xirq = pin_x.irq(trigger=trig, handler=self._x_cb)
yirq = pin_y.irq(trigger=trig, handler=self._y_cb)
asyncio.create_task(self._run(vmin, vmax, div, mod, callback, args))
# Hardware IRQ's. Duration 36μs on Pyboard 1 ~50μs on ESP32.
# IRQ latency: 2nd edge may have occured by the time ISR runs, in
# which case there is no movement.
def _x_cb(self, pin_x):
if (x := pin_x()) != self._x:
self._x = x
self._v += 1 if x ^ self._pin_y() else -1
self._tsf.set()
def _y_cb(self, pin_y):
if (y := pin_y()) != self._y:
self._y = y
self._v -= 1 if y ^ self._pin_x() else -1
self._tsf.set()
async def _run(self, vmin, vmax, div, modulo, cb, args):
pv = self._v # Prior hardware value
cv = self._cv # Current divided value as passed to callback
pcv = cv # Prior divided value passed to callback
delay = self.delay
while True:
await self._tsf.wait()
await asyncio.sleep_ms(delay) # Wait for motion to stop
new = self._v # Sample hardware (atomic read)
a = new - pv # Hardware change
# Ensure symmetrical bahaviour for + and - values
q, r = divmod(abs(a), div)
if a < 0:
r = -r
q = -q
pv = new - r # Hardware value when local value was updated
cv += q
if vmax is not None:
cv = min(cv, vmax)
if vmin is not None:
cv = max(cv, vmin)
if modulo:
cv %= modulo
self._cv = cv # For value()
if cv != pcv:
cb(cv, cv - pcv, *args) # User CB in uasyncio context
pcv = cv
def value(self):
return self._cv