miketeachman / micropython-rotary

MicroPython module to read a rotary encoder.
MIT License
269 stars 56 forks source link

RFC: Design concept #29

Open peterhinch opened 11 months ago

peterhinch commented 11 months ago

For context please see this doc on the design of encoder drivers and this driver.

The following specifics are potential issues:

  1. The runtime of the ISR's is relatively long, and may be extended if there are user callbacks. This runs the risk of reentrancy because the frequency of contact bounce may be arbitrarily high.
  2. The latency incurred by soft ISR's increases the risk of reentrancy or missed bounce transitions.
  3. User callbacks run in an ISR context. This can cause unexpected hazards particularly if the callbacks run complex code.
  4. It is possible to design the ISR's so that they run very fast by replacing the lookup tables with bitwise logic and by delegating to a non-ISR task operations such as rate division, end-stops and running callbacks.
  5. Doing this enables callbacks to run outside of an ISR context removing concurrency hazards.

The driver cited above has the following characteristics. ISR's run in 36μs on a Pyboard 1.1 and 50μs on ESP32 (soft ISR's run fast, but are subject to latency of multiple ms). The ISR design aims to allow for potential latency, detecting missed bounce transitions. Hard ISR's are used if available because latency limits allowable rotational speed.

miketeachman commented 10 months ago

Hi Peter, Thanks for these options to improve this implementation! Sorry for the long delay in responding. It's been a busy summer and MicroPython fun has taken a back seat. I'll try to carve out some time to dig into your encoder implementation.

The first question I have is about reentrancy. On the ESP32 and ESP8266 ports the pin interrupt code queues the callbacks into the scheduler. This should protect the rotary callback code from reentrancy issues as this code will run to completion before it is called again by the scheduler. I assumed that the other ports (stm32, mimxrt, rp2, ...) would be consistent in how the pin callbacks are handled (even though they don't appear to use the scheduler). But, maybe that is a wrong assumption?

peterhinch commented 10 months ago

IRQ's may be hard or soft. Soft IRQ's are run by the scheduler and are protected, however they suffer latency measured in ms. Hard IRQ's are only supported on some platforms (e.g. STM). They run with latency on the order of 15μs. Coding them requires care - see para on reentrancy.

There is the option always to use soft IRQ's.

One potential issue with latency/slow ISR's is that pulses might be missed. In many cases with a rotary control the user would never notice. The case where the encoder driver tracks mechanical detents is where missing pulses could matter, with alignment gradually drifting.

As a bit of background nearly 50 years ago (!) I designed a hardware interface for an optical encoder. It was on a machine tool and had to be pulse-perfect. That was a baptism of fire. For a digital circuit involving a handful of simple chips it was remarkably hard to get right and was my first encounter with metastability - something little-known in 1974.

I know we discussed encoders in the past and I'm not sure at what point we left the discussion - I have talked with a number of people and thought it worth sharing the outcome with you. It was only when I was planning adding encoder support to micro-gui that I realised that there was a risk of great swathes of GUI code running in an ISR context.