Skip to content

Commit db21199

Browse files
committed
Encoder primitive now has div arg.
1 parent 3661ee9 commit db21199

File tree

2 files changed

+75
-33
lines changed

2 files changed

+75
-33
lines changed

v3/docs/DRIVERS.md

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -353,15 +353,22 @@ must be tracked.
353353

354354
This driver runs the user supplied callback in an `asyncio` context, so it runs
355355
only when other tasks have yielded to the scheduler. This ensures that the
356-
callback can run safely. The driver allows limits to be assigned to the control
357-
so that a dial running from (say) 0 to 100 may be implemented. If limits are
358-
used, encoder values no longer represent absolute angles.
356+
callback can run safely, even if it triggers complex application behaviour.
359357

360-
The callback only runs if a change in position has occurred.
358+
The `Encoder` can be instantiated in such a way that its effective resolution
359+
can be reduced. A virtual encoder with lower resolution can be useful in some
360+
applications.
361361

362-
A consequence of the callback running in an `asyncio` context is that, by the
363-
time it runs, the encoder's position may have changed by more than one
364-
increment.
362+
The driver allows limits to be assigned to the virtual encoder's value so that
363+
a dial running from (say) 0 to 100 may be implemented. If limits arenused,
364+
encoder values no longer represent absolute angles, as the user might continue
365+
to rotate the dial when it is "stuck" at an endstop.
366+
367+
The callback only runs if a change in position of the virtual encoder has
368+
occurred. In consequence of the callback running in an `asyncio` context, by
369+
the time it is scheduled, the encoder's position may have changed by more than
370+
one increment. The callback receives two args, the absolute value of the
371+
virtual encoder and the signed change since the previous callback run.
365372

366373
## 6.1 Encoder class
367374

@@ -372,18 +379,39 @@ Constructor arguments:
372379
3. `v=0` Initial value.
373380
4. `vmin=None` By default the `value` of the encoder can vary without limit.
374381
Optionally maximum and/or minimum limits can be set.
375-
5. `vmax=None`
376-
6. `callback=lambda *_ : None` Optional callback function. The callback
382+
5. `vmax=None` As above. If `vmin` and/or `vmax` are specified, a `ValueError`
383+
will be thrown if the initial value `v` does not conform with the limits.
384+
6. `div=1` A value > 1 causes the motion rate of the encoder to be divided
385+
down, to produce a virtual encoder with lower resolution. This was found usefl
386+
in some applications with the Adafruit encoder.
387+
7. `callback=lambda a, b : None` Optional callback function. The callback
377388
receives two args, `v` being the encoder's current value and `delta` being
378389
the signed difference between the current value and the previous one. Further
379390
args may be appended by the following.
380-
7. `args=()` An optional tuple of args for the callback.
391+
8. `args=()` An optional tuple of args for the callback.
381392

382393
Synchronous method:
383394
* `value` No args. Returns an integer being the `Encoder` current value.
384395

385396
Class variable:
386-
* `LATENCY=50` This sets a minumum period (in ms) between callback runs.
397+
* `delay=100` After motion is detected the driver waits for `delay` ms before
398+
reading the current position. This was found useful with the Adafruit encoder
399+
which has mechanical detents, which span multiple increments or decrements. A
400+
delay gives time for motion to stop, enabling just one call to the callback.
401+
402+
#### Note
403+
404+
The driver works by maintaining an internal value `._v` which uses hardware
405+
interrupts to track the absolute position of the physical encoder. In theory
406+
this should be precise, but on ESP32 with the Adafruit encoder it is not.
407+
408+
Currently under investigation: it may be a consequence of ESP32's use of soft
409+
IRQ's.
410+
411+
This is probably of little practical consequence as encoder knobs are usually
412+
used in systems where there is user feedback. In a practical application
413+
([ugui](https://github.com/peterhinch/micropython-micro-gui)) I can see no
414+
evidence of the missed pulses.
387415

388416
###### [Contents](./DRIVERS.md#1-contents)
389417

v3/primitives/encoder.py

Lines changed: 36 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,29 +3,32 @@
33
# Copyright (c) 2021 Peter Hinch
44
# Released under the MIT License (MIT) - see LICENSE file
55

6-
# This driver is intended for encoder-based control knobs. It is not
7-
# suitable for NC machine applications. Please see the docs.
6+
# This driver is intended for encoder-based control knobs. It is
7+
# unsuitable for NC machine applications. Please see the docs.
88

99
import uasyncio as asyncio
1010
from machine import Pin
1111

1212
class Encoder:
13-
LATENCY = 50
13+
delay = 100 # Pause (ms) for motion to stop
1414

15-
def __init__(self, pin_x, pin_y, v=0, vmin=None, vmax=None,
15+
def __init__(self, pin_x, pin_y, v=0, vmin=None, vmax=None, div=1,
1616
callback=lambda a, b : None, args=()):
1717
self._pin_x = pin_x
1818
self._pin_y = pin_y
19-
self._v = v
19+
self._v = 0 # Hardware value always starts at 0
20+
self._cv = v # Current (divided) value
21+
if ((vmin is not None) and v < min) or ((vmax is not None) and v > vmax):
22+
raise ValueError('Incompatible args: must have vmin <= v <= vmax')
2023
self._tsf = asyncio.ThreadSafeFlag()
24+
trig = Pin.IRQ_RISING | Pin.IRQ_FALLING
2125
try:
22-
xirq = pin_x.irq(trigger=Pin.IRQ_RISING | Pin.IRQ_FALLING, handler=self._x_cb, hard=True)
23-
yirq = pin_y.irq(trigger=Pin.IRQ_RISING | Pin.IRQ_FALLING, handler=self._y_cb, hard=True)
24-
except TypeError:
25-
xirq = pin_x.irq(trigger=Pin.IRQ_RISING | Pin.IRQ_FALLING, handler=self._x_cb)
26-
yirq = pin_y.irq(trigger=Pin.IRQ_RISING | Pin.IRQ_FALLING, handler=self._y_cb)
27-
asyncio.create_task(self._run(vmin, vmax, callback, args))
28-
26+
xirq = pin_x.irq(trigger=trig, handler=self._x_cb, hard=True)
27+
yirq = pin_y.irq(trigger=trig, handler=self._y_cb, hard=True)
28+
except TypeError: # hard arg is unsupported on some hosts
29+
xirq = pin_x.irq(trigger=trig, handler=self._x_cb)
30+
yirq = pin_y.irq(trigger=trig, handler=self._y_cb)
31+
asyncio.create_task(self._run(vmin, vmax, div, callback, args))
2932

3033
# Hardware IRQ's
3134
def _x_cb(self, pin):
@@ -38,21 +41,32 @@ def _y_cb(self, pin):
3841
self._v += 1 if fwd else -1
3942
self._tsf.set()
4043

41-
async def _run(self, vmin, vmax, cb, args):
42-
pv = self._v # Prior value
44+
async def _run(self, vmin, vmax, div, cb, args):
45+
pv = self._v # Prior hardware value
46+
cv = self._cv # Current divided value as passed to callback
47+
pcv = cv # Prior divided value passed to callback
48+
mod = 0
49+
delay = self.delay
4350
while True:
4451
await self._tsf.wait()
45-
cv = self._v # Current value
52+
await asyncio.sleep_ms(delay) # Wait for motion to stop
53+
new = self._v # Sample hardware (atomic read)
54+
a = new - pv # Hardware change
55+
# Ensure symmetrical bahaviour for + and - values
56+
q, r = divmod(abs(a), div)
57+
if a < 0:
58+
r = -r
59+
q = -q
60+
pv = new - r # Hardware value when local value was updated
61+
cv += q
4662
if vmax is not None:
4763
cv = min(cv, vmax)
4864
if vmin is not None:
4965
cv = max(cv, vmin)
50-
self._v = cv
51-
#print(cv, pv)
52-
if cv != pv:
53-
cb(cv, cv - pv, *args) # User CB in uasyncio context
54-
pv = cv
55-
await asyncio.sleep_ms(self.LATENCY)
66+
self._cv = cv # For value()
67+
if cv != pcv:
68+
cb(cv, cv - pcv, *args) # User CB in uasyncio context
69+
pcv = cv
5670

5771
def value(self):
58-
return self._v
72+
return self._cv

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy