kopia lustrzana https://github.com/peterhinch/micropython-samples
Encoders: add to algorithm notes.
rodzic
b5dbfd6d03
commit
55955fa8d8
|
@ -1,4 +1,4 @@
|
||||||
# Incremental encoders
|
# 1. Incremental encoders
|
||||||
|
|
||||||
There are three technologies that I am aware of:
|
There are three technologies that I am aware of:
|
||||||
1. Optical.
|
1. Optical.
|
||||||
|
@ -28,7 +28,7 @@ because Pyboard timers can decode in hardware, as shown
|
||||||
from Dave Hylands. Hardware decoding eliminates all concerns over interrupt
|
from Dave Hylands. Hardware decoding eliminates all concerns over interrupt
|
||||||
latency or input pulse rates.
|
latency or input pulse rates.
|
||||||
|
|
||||||
# Basic encoder script
|
# 2. Basic encoder script
|
||||||
|
|
||||||
This comes from `encoder_portable.py` in this repo. It uses the simplest and
|
This comes from `encoder_portable.py` in this repo. It uses the simplest and
|
||||||
fastest algorithm I know. It should run on any MicrPython platform, but please
|
fastest algorithm I know. It should run on any MicrPython platform, but please
|
||||||
|
@ -67,7 +67,7 @@ class Encoder:
|
||||||
If the direction is incorrect, transpose the X and Y pins in the constructor
|
If the direction is incorrect, transpose the X and Y pins in the constructor
|
||||||
call.
|
call.
|
||||||
|
|
||||||
# Problem 1: Interrupt latency
|
# 3. Problem 1: Interrupt latency
|
||||||
|
|
||||||
By default, pin interrupts defined using the `machine` module are soft. This
|
By default, pin interrupts defined using the `machine` module are soft. This
|
||||||
introduces latency if a line changes state when a garbage collection is in
|
introduces latency if a line changes state when a garbage collection is in
|
||||||
|
@ -78,7 +78,7 @@ Hard IRQ's present their own issues documented
|
||||||
[here](https://docs.micropython.org/en/latest/reference/isr_rules.html) but
|
[here](https://docs.micropython.org/en/latest/reference/isr_rules.html) but
|
||||||
the above script conforms with these rules.
|
the above script conforms with these rules.
|
||||||
|
|
||||||
# Problem 2: Jitter
|
# 4. Problem 2: Jitter
|
||||||
|
|
||||||
The picture above is idealised. In practice it is possible to receive a
|
The picture above is idealised. In practice it is possible to receive a
|
||||||
succession of edges on one input line, with no transitions on the other. On
|
succession of edges on one input line, with no transitions on the other. On
|
||||||
|
@ -86,33 +86,34 @@ mechanical encoders this may be caused by
|
||||||
[contact bounce](http://www.ganssle.com/debouncing.htm). On any type it can
|
[contact bounce](http://www.ganssle.com/debouncing.htm). On any type it can
|
||||||
result from vibration, where the encoder happens to stop at an angle exactly
|
result from vibration, where the encoder happens to stop at an angle exactly
|
||||||
matching an edge. An arbitrarily long sequence of pulses on one line is the
|
matching an edge. An arbitrarily long sequence of pulses on one line is the
|
||||||
result; the frequency may be arbitrarily high. A correct algorithm must be
|
result. A correct algorithm must be able to cope with this: the outcome will be
|
||||||
able to cope with this: the outcome will be one digit of jitter in the output
|
one digit of jitter in the output count but no systematic drift.
|
||||||
count but no systematic drift.
|
|
||||||
|
|
||||||
Contrary to common opinion a state table is not necessary to produce a correct
|
Contrary to common opinion a state table is not necessary to produce a correct
|
||||||
algorithm: see the above sample for an example.
|
algorithm: see [section 7](./ENCODERS.md#7-algorithm).
|
||||||
|
|
||||||
# Problem 3: Synchronisation
|
In practice the frequency of such edges may be arbitrarily high. This imposes
|
||||||
|
a need for synchronisation.
|
||||||
|
|
||||||
Decoders of all types including hardware implementations can fail if edges
|
## 4.1 Synchronisation
|
||||||
on one line occur at too high a rate: transitions can be missed leading to a
|
|
||||||
gradual drift of measured count compared to actual position. The only solution
|
|
||||||
to this is to limit the rate by pre-synchronising the digital signals to a
|
|
||||||
clock.
|
|
||||||
|
|
||||||
For mechanical encoders there can also be an issue with invalid logic levels
|
Decoders of all types (including hardware implementations) can fail if edges on
|
||||||
caused by contact bounce: conditioning with a CR networks and a Schmitt trigger
|
one line occur at too high a rate: transitions can be missed leading to a
|
||||||
should be considered.
|
gradual drift of measured count compared to actual position. With an encoder
|
||||||
|
that produces good logic levels the solution is to limit the rate by
|
||||||
For bit-perfect results a single level of clock synchronisation is inadequate
|
pre-synchronising the digital signals to a clock. For bit-perfect results a
|
||||||
because of metastability. Typically two levels are used. See
|
single level of clock synchronisation is inadequate because of metastability.
|
||||||
|
Typically two levels are used. See
|
||||||
[this Wikipedia article](url=https://en.wikipedia.org/wiki/Incremental_encoder#Clock_synchronization).
|
[this Wikipedia article](url=https://en.wikipedia.org/wiki/Incremental_encoder#Clock_synchronization).
|
||||||
|
|
||||||
The clock rate of a synchroniser for a software decoder must be chosen with
|
The clock rate of a synchroniser for a software decoder must be chosen with
|
||||||
regard to the worst-case latency of the host. The clock rate will then
|
regard to the worst-case latency of the host. The clock rate will then
|
||||||
determine the maximum permissible rotation speed of the encoder.
|
determine the maximum permissible rotation speed of the encoder.
|
||||||
|
|
||||||
|
Contact bounce on mechanical encoders can also result in invalid logic levels.
|
||||||
|
This can cause a variety of unwanted results: conditioning with a CR network
|
||||||
|
and a Schmitt trigger should be considered.
|
||||||
|
|
||||||
In practice bit-perfect results are often not required and simple software
|
In practice bit-perfect results are often not required and simple software
|
||||||
solutions are fine. In particular encoders used for user controls normally have
|
solutions are fine. In particular encoders used for user controls normally have
|
||||||
some form of user feedback. The occasional missed pulse caused by fast contact
|
some form of user feedback. The occasional missed pulse caused by fast contact
|
||||||
|
@ -122,23 +123,20 @@ Where bit-perfect results are required the simplest approach is to use a target
|
||||||
which supports hardware decoding and which pre-synchronises the signals. STM32
|
which supports hardware decoding and which pre-synchronises the signals. STM32
|
||||||
meets these criteria.
|
meets these criteria.
|
||||||
|
|
||||||
In a careful test of a software decoder on a Pyboard 1.1 with an optical
|
# 5. Problem 3: Concurrency
|
||||||
encoder pulses were occasionally missed. This suggests that on rare occasions
|
|
||||||
pulses can arrive too fast for even hard IRQ's to keep track.
|
|
||||||
|
|
||||||
# Problem 4: Concurrency
|
|
||||||
|
|
||||||
The presented code samples use interrupts in order to handle the potentially
|
The presented code samples use interrupts in order to handle the potentially
|
||||||
high rate at which transitions can occur. The above script maintains a
|
high rate at which transitions can occur. The above script maintains a
|
||||||
position value `._pos` which can be queried at any time. This does not present
|
position value `._pos` which can be queried at any time. This does not present
|
||||||
concurrency issues. However some applications, notably in user interface
|
concurrency issues because changes to an integer are atomic.
|
||||||
designs, may require an encoder action to trigger complex behaviour. The
|
|
||||||
obvious solution would be to adapt the script to do this by having the two ISR
|
However some applications, notably in user interface designs, may require an
|
||||||
methods call a function. However the function would run in an interrupt context
|
encoder action to trigger complex behaviour. The obvious solution would be to
|
||||||
which (even with soft IRQ's) presents concurrency issues where an application's
|
adapt the script to do this by having the two ISR methods call a function.
|
||||||
data can change at any point in the application's execution. Further, a complex
|
However the function would run in an interrupt context which (even with soft
|
||||||
function would cause the ISR to block for a long period with the potential for
|
IRQ's) presents concurrency issues where an application's data can change at
|
||||||
data loss.
|
any point in the application's execution. Further, a complex function would
|
||||||
|
cause the ISR to block for a long period which is bad practice.
|
||||||
|
|
||||||
A solution to this is an interface between the ISR's and `uasyncio` whereby the
|
A solution to this is an interface between the ISR's and `uasyncio` whereby the
|
||||||
ISR's set a `ThreadSafeFlag`. This is awaited by a `uasyncio` `Task` which runs
|
ISR's set a `ThreadSafeFlag`. This is awaited by a `uasyncio` `Task` which runs
|
||||||
|
@ -150,7 +148,7 @@ This also handles the case where a mechanical encoder has a large number of
|
||||||
states per revolution. The driver has the option to divide these down, reducing
|
states per revolution. The driver has the option to divide these down, reducing
|
||||||
the rate at which callbacks occur.
|
the rate at which callbacks occur.
|
||||||
|
|
||||||
# Code samples
|
# 6. Code samples
|
||||||
|
|
||||||
1. `encoder_portable.py` Suitable for most purposes.
|
1. `encoder_portable.py` Suitable for most purposes.
|
||||||
2. `encoder_timed.py` Provides rate information by timing successive edges. In
|
2. `encoder_timed.py` Provides rate information by timing successive edges. In
|
||||||
|
@ -163,15 +161,16 @@ the rate at which callbacks occur.
|
||||||
|
|
||||||
These were written for encoders producing logic outputs. For switches, adapt
|
These were written for encoders producing logic outputs. For switches, adapt
|
||||||
the pull definition to provide a pull up or pull down as required, or provide
|
the pull definition to provide a pull up or pull down as required, or provide
|
||||||
physical resistors. This is my preferred solution as the internal resistors on
|
physical resistors. The latter is my preferred solution as the internal
|
||||||
most platforms have a rather high value.
|
resistors on most platforms have a rather high value posing a risk of slow
|
||||||
|
edges.
|
||||||
|
|
||||||
# Algorithm
|
# 7. Algorithm
|
||||||
|
|
||||||
Discussions on the MicroPython forum demonstrate a degree of confusion about
|
Discussions on the MicroPython forum demonstrate a degree of confusion about
|
||||||
the merits of different decoding algorithms and about contact debouncing. These
|
the merits of different decoding algorithms and about contact debouncing. These
|
||||||
notes aim to clarify the issues and to provide an explanation for the approach
|
notes aim to clarify the issues and to provide an explanation for the approach
|
||||||
used in my code samples.
|
used in my code samples; also to describe the mechanism where errors occur.
|
||||||
|
|
||||||
Incremental encoders produce two signals `x` and `y`. Possible state changes
|
Incremental encoders produce two signals `x` and `y`. Possible state changes
|
||||||
are shown in this state diagram.
|
are shown in this state diagram.
|
||||||
|
@ -197,7 +196,7 @@ cause a change in position and inspection shows that the direction is the
|
||||||
`exclusive or` of the current `x` and `y` values, with opposite polarity for
|
`exclusive or` of the current `x` and `y` values, with opposite polarity for
|
||||||
the `x` and `y` interrupts.
|
the `x` and `y` interrupts.
|
||||||
|
|
||||||
## Debouncing
|
## 7.1 Debouncing
|
||||||
|
|
||||||
Contact bounce or vibration effects cause an oscillating signal on one line.
|
Contact bounce or vibration effects cause an oscillating signal on one line.
|
||||||
The state diagram shows that this is logically indistinguishable from a
|
The state diagram shows that this is logically indistinguishable from a
|
||||||
|
@ -206,8 +205,45 @@ point. Consequently any valid decoding algorithm will register a change in
|
||||||
position of one LSB forwards and backwards. There is no systematic drift, just
|
position of one LSB forwards and backwards. There is no systematic drift, just
|
||||||
one LSB of positional uncertainty.
|
one LSB of positional uncertainty.
|
||||||
|
|
||||||
## Algorithm quality
|
## 7.2 Algorithm quality
|
||||||
|
|
||||||
All valid solutions to a combinatorial logic problem are equivalent. The only
|
All valid solutions to a combinatorial logic problem are equivalent. The only
|
||||||
ways in which one solution can be considered "better" than another are in
|
ways in which one solution can be considered "better" than another are in
|
||||||
qualities such as performance and code size.
|
qualities such as performance and code size.
|
||||||
|
|
||||||
|
## 7.3 Interrupt issues
|
||||||
|
|
||||||
|
As discussed above, any solution will have a limit to the rate at which edges
|
||||||
|
can be tracked. This section describes the limits of a MicroPython
|
||||||
|
interrupt-driven solution and the way in which incorrect counts can arise.
|
||||||
|
|
||||||
|
Interrupts suffer from latency: there is a time delay between an edge occurring
|
||||||
|
and the ISR executing. The magnitude depends on what is running at the moment
|
||||||
|
the edge occurs and consequently varies in real time. Another ISR may be
|
||||||
|
running. Higher priority interrupts may be pending service. In the case of soft
|
||||||
|
IRQ's garbage collection may be in progress.
|
||||||
|
|
||||||
|
Consider the fllowing ISR:
|
||||||
|
```python
|
||||||
|
def x_callback(self, pin_x):
|
||||||
|
forward = pin_x() ^ self.pin_y()
|
||||||
|
self._pos += 1 if forward else -1
|
||||||
|
```
|
||||||
|
This is necessarily triggered by either edge on `pin_x`. While `.pin_y` should
|
||||||
|
be stable when an edge occurs on `pin_x`, the state of `pin_x` may have changed
|
||||||
|
by the time the latency has elapsed and the ISR reads its value. In this case
|
||||||
|
the change will be registered with the wrong direction.
|
||||||
|
|
||||||
|
Further, this second pin change will trigger another interrupt. The consequence
|
||||||
|
of this depends on hardware and firmware implementations. The interrupt may be
|
||||||
|
missed, it may execute after the first has completed, or re-entrancy may take
|
||||||
|
place. However, by this time an error has already occurred as described above.
|
||||||
|
There is nothing to gain by trying to fix this (e.g. by disabling interrupts in
|
||||||
|
the ISR).
|
||||||
|
|
||||||
|
In a careful test of a software decoder on a Pyboard 1.1 with an optical
|
||||||
|
encoder pulses were occasionally missed. This confirms that, even with clean
|
||||||
|
logic levels and hard IRQ's, on rare occasions pulses arrive too fast for the
|
||||||
|
ISR to track.
|
||||||
|
|
||||||
|
If bit-perfect results are required, hardware rate limiting must be applied.
|
||||||
|
|
Ładowanie…
Reference in New Issue