miketeachman / micropython-rotary

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

Skipping one step when reversing direction #10

Closed ilium007 closed 2 years ago

ilium007 commented 3 years ago

I am trying out this library and it works great except when reversing direction. I forget the encoder model I am using but its the same across different model encoders. I use a hardware de-bouncer that feeds through a Schmidt Trigger.

If I turn clockwise I can count up, anti-clockwise it counts down but at the point of going from clock2wise to anti-clockwise I get one notch on the encoder that doesn't count.

I was using this library on the Atmega's that had the exact issue: https://github.com/mathertel/RotaryEncoder

I raised an issue: https://github.com/mathertel/RotaryEncoder/issues/19

Seems that this was the issue "This may be caused by the fact that the version < 1.5.0 only reported a new value on state 3 (not on state 0)"

In his code I use FOUR0

enum class LatchMode {
    FOUR3 = 1, // 4 steps, Latch at position 3 only (compatible to older versions)
    FOUR0 = 2, // 4 steps, Latch at position 0 (reverse wirings)
    TWO03 = 3  // 2 steps, Latch at position 0 and 3 
  };

Would be great if I could get this one working for Micropython!

image

ilium007 commented 3 years ago

I tried to add the debug line as per the other issue post:

def _process_rotary_pins(self, pin):
        print(str(self._hal_get_clk_value())+" "+str(self._hal_get_dt_value()))

but got this error when I turned the rotary encoder:

uncaught exception in ExtInt interrupt handler line 3
MemoryError:
uncaught exception in ExtInt interrupt handler line 9
MemoryError:
ilium007 commented 3 years ago

I should have stated that I'm using an Adafruit STM32F405 board to prototype with Micropython 1.14. I'm assuming it doesn't like printing whilst within the ISR?

ilium007 commented 3 years ago
MicroPython v1.14-83-gd20f4c588 on 2021-02-24; Adafruit Feather STM32F405 with STM32F405RG
Type "help()" for more information.
>>>
MPY: sync filesystems
MPY: soft reboot
result = 1   <-- one click clockwise
result = 2   <-- one click clockwise
result = 3   <-- one click clockwise
result = 4   <-- one click clockwise
result = 5   <-- one click clockwise
                   <-- one click anti-clockwise, **does nothing**
result = 4   <-- one click anti-clockwise
result = 3   <-- one click anti-clockwise
result = 2   <-- one click anti-clockwise
result = 1   <-- one click anti-clockwise
result = 0  <-- one click anti-clockwise
ilium007 commented 3 years ago

To get my encoder to work I changed the transition table. Hacky, I know.

_transition_table = [

    # |------------- NEXT STATE -------------|            |CURRENT STATE|
    # CLK/DT    CLK/DT     CLK/DT    CLK/DT
    #   00        01         10        11
    [_R_START, _R_CCW_1, _R_CW_1,  _R_START],             # _R_START
    [_R_CW_2,  _R_START, _R_CW_1,  _R_START | _DIR_CCW],  # _R_CW_1
    [_R_CW_2,  _R_CW_3,  _R_CW_1,  _R_START],             # _R_CW_2
    [_R_CW_2,  _R_CW_3,  _R_START, _R_START | _DIR_CW],   # _R_CW_3
    [_R_CCW_2, _R_CCW_1, _R_START, _R_START | _DIR_CW],   # _R_CCW_1
    [_R_CCW_2, _R_CCW_1, _R_CCW_3, _R_START],             # _R_CCW_2
    [_R_CCW_2, _R_START, _R_CCW_3, _R_START | _DIR_CCW],  # _R_CCW_3
    [_R_START, _R_START, _R_START, _R_START]]             # _R_ILLEGAL
miketeachman commented 3 years ago

Thanks for reporting this issue. This is new to me. I don't have any hardware encoders that show this behaviour.

Can I ask you to add some print debugging to rotary.py, in two places:

....

def _irq_print(rotary_instance):
    print('clk: {}, dt: {}'.format(rotary_instance._hal_get_clk_value(), rotary_instance._hal_get_dt_value()))

class Rotary(object):
....
....
 def _process_rotary_pins(self, pin):
        micropython.schedule(_irq_print, self)
        old_value = self._value
....

Run the simple example from the REPL to reproduce the problem where it doesn't count. Please capture the REPL output and post it. The debug information might help me to better understand what your hardware is doing.

For example, on my PyBoard (STM32F405) I capture this:

clk: 1, dt: 0
clk: 1, dt: 0
clk: 0, dt: 0
clk: 0, dt: 0
clk: 0, dt: 1
clk: 0, dt: 1
clk: 1, dt: 1
clk: 1, dt: 1
result = 1
clk: 0, dt: 1
clk: 0, dt: 0
clk: 0, dt: 0
clk: 0, dt: 0
clk: 0, dt: 0
clk: 0, dt: 0
clk: 0, dt: 0
clk: 0, dt: 0
clk: 0, dt: 0
clk: 1, dt: 0
clk: 1, dt: 1
clk: 1, dt: 1
result = 0

Note: This method of debug print works from an ISR because it does not allocate memory from the MicroPython heap (which is likely cause of the MemoryError exception that you see). For a better explanation: https://docs.micropython.org/en/latest/reference/isr_rules.html#creation-of-python-objects

ilium007 commented 3 years ago

This is using my hardware de-bounced rotary encoder circuit as shown at top of this issue.

>>>
MPY: sync filesystems
MPY: soft reboot
clk: 0, dt: 1     <== turned one notch clockwise
clk: 1, dt: 1
clk: 1, dt: 0
clk: 0, dt: 0

clk: 0, dt: 1     <== turned one notch clockwise
clk: 1, dt: 1
clk: 1, dt: 0
clk: 0, dt: 0
result = 1

clk: 0, dt: 1     <== turned one notch clockwise
clk: 1, dt: 1
clk: 1, dt: 0
clk: 0, dt: 0
result = 2

clk: 0, dt: 1     <== turned one notch clockwise
clk: 1, dt: 1
clk: 1, dt: 0
clk: 0, dt: 0
result = 3

clk: 1, dt: 0     <== turned one notch anti-clockwise
clk: 1, dt: 1
clk: 0, dt: 1
clk: 0, dt: 0

clk: 1, dt: 0     <== turned one notch anti-clockwise
clk: 1, dt: 1
clk: 0, dt: 1
result = 3       <== not sure why result=3 is before the last transition here
clk: 0, dt: 0

clk: 1, dt: 0     <== turned one notch anti-clockwise
clk: 1, dt: 1
clk: 0, dt: 1
clk: 0, dt: 0
result = 2

clk: 1, dt: 0     <== turned one notch anti-clockwise
clk: 1, dt: 1
clk: 0, dt: 1
clk: 0, dt: 0
result = 1

clk: 1, dt: 0     <== turned one notch anti-clockwise
clk: 1, dt: 1
clk: 0, dt: 1
clk: 0, dt: 0
result = 0

clk: 0, dt: 1     <== turned one notch clockwise
clk: 1, dt: 1
clk: 1, dt: 0
clk: 0, dt: 0

clk: 0, dt: 1     <== turned one notch clockwise
clk: 1, dt: 1
clk: 1, dt: 0
clk: 0, dt: 0
result = 1
ilium007 commented 3 years ago

And the same hardware de-bounced encoder with my transition table hack:


>>>
MPY: sync filesystems
MPY: soft reboot
clk: 0, dt: 1     <== turned one notch clockwise
clk: 1, dt: 1
clk: 1, dt: 0
result = 1
clk: 0, dt: 0

clk: 0, dt: 1     <== turned one notch clockwise
clk: 1, dt: 1
clk: 1, dt: 0
clk: 0, dt: 0
result = 2

clk: 0, dt: 1     <== turned one notch clockwise
clk: 1, dt: 1
clk: 1, dt: 0
clk: 0, dt: 0
result = 3

clk: 0, dt: 1     <== turned one notch clockwise
clk: 1, dt: 1
clk: 1, dt: 0
clk: 0, dt: 0
result = 4

clk: 0, dt: 1     <== turned one notch clockwise
clk: 1, dt: 1
clk: 1, dt: 0
clk: 0, dt: 0
result = 5

clk: 1, dt: 0     <== turned one notch anti-clockwise
clk: 1, dt: 1
clk: 0, dt: 1
clk: 0, dt: 0
result = 4

clk: 1, dt: 0     <== turned one notch anti-clockwise
clk: 1, dt: 1
clk: 0, dt: 1
clk: 0, dt: 0
result = 3

clk: 1, dt: 0     <== turned one notch anti-clockwise
clk: 1, dt: 1
clk: 0, dt: 1
clk: 0, dt: 0
result = 2

clk: 1, dt: 0     <== turned one notch anti-clockwise
clk: 1, dt: 1
result = 1
clk: 0, dt: 1
clk: 0, dt: 0

clk: 1, dt: 0     <== turned one notch anti-clockwise
clk: 1, dt: 1
clk: 0, dt: 1
clk: 0, dt: 0
result = 0

clk: 0, dt: 1     <== turned one notch clockwise
clk: 1, dt: 1
clk: 1, dt: 0
clk: 0, dt: 0
result = 1
ilium007 commented 3 years ago

For some reason when I tested a non-hardware de-bounced rotary encoder the pull-up option in the constructor did not enable pull-up resistors (or there is a hardware fault on mt STM32F405RG) - I could only get it working by adding two external 10k pull-up resistors.

ilium007 commented 3 years ago

Non-hardware de-bounced encoder using original transition table - its a lot noisier!

>>>
MPY: sync filesystems
MPY: soft reboot
clk: 0, dt: 1     <== turned one notch clockwise
clk: 0, dt: 1
clk: 0, dt: 1
clk: 0, dt: 1
clk: 0, dt: 0
clk: 1, dt: 0
clk: 1, dt: 0
clk: 1, dt: 1
result = 1

clk: 0, dt: 1     <== turned one notch clockwise
clk: 0, dt: 1
clk: 0, dt: 0
clk: 0, dt: 0
clk: 1, dt: 0
clk: 1, dt: 0
clk: 1, dt: 1
clk: 1, dt: 1
result = 2

clk: 0, dt: 1     <== turned one notch clockwise
clk: 0, dt: 1
clk: 0, dt: 0
clk: 0, dt: 0
clk: 1, dt: 0
clk: 1, dt: 0
clk: 1, dt: 1
clk: 1, dt: 1
result = 3

clk: 1, dt: 0     <== turned one notch anti-clockwise
clk: 1, dt: 0
clk: 1, dt: 0
clk: 0, dt: 0
clk: 0, dt: 0
clk: 0, dt: 1
clk: 0, dt: 1
clk: 1, dt: 1
clk: 1, dt: 1
result = 2

clk: 1, dt: 0     <== turned one notch anti-clockwise
clk: 1, dt: 0
clk: 0, dt: 0
clk: 0, dt: 0
clk: 0, dt: 1
clk: 0, dt: 1
clk: 1, dt: 1
clk: 1, dt: 1
result = 1

clk: 1, dt: 0     <== turned one notch anti-clockwise
clk: 1, dt: 0
clk: 1, dt: 0
clk: 1, dt: 0
clk: 1, dt: 0
clk: 0, dt: 0
clk: 0, dt: 0
clk: 0, dt: 0
clk: 0, dt: 0
clk: 0, dt: 0
clk: 0, dt: 0
clk: 0, dt: 1
clk: 0, dt: 1
clk: 1, dt: 1
clk: 1, dt: 1
result = 0
ilium007 commented 3 years ago

Non-hardware de-bounced encoder using my hacked transition table.

>>>
MPY: sync filesystems
MPY: soft reboot
clk: 0, dt: 1     <== turned one notch clockwise
clk: 0, dt: 1
clk: 0, dt: 1
clk: 0, dt: 1
clk: 0, dt: 1
clk: 0, dt: 0
clk: 1, dt: 0
clk: 1, dt: 0
clk: 1, dt: 1
clk: 1, dt: 1
result = 1

clk: 0, dt: 1     <== turned one notch clockwise
clk: 0, dt: 1
clk: 0, dt: 1
clk: 0, dt: 0
clk: 1, dt: 0
clk: 1, dt: 0
clk: 1, dt: 1
result = 2

clk: 0, dt: 1     <== turned one notch clockwise
clk: 0, dt: 1
clk: 0, dt: 1
clk: 0, dt: 1
clk: 0, dt: 0
clk: 0, dt: 0
clk: 0, dt: 0
clk: 1, dt: 0
clk: 1, dt: 0
clk: 1, dt: 1
clk: 1, dt: 1
result = 3

clk: 1, dt: 0     <== turned one notch anti-clockwise
clk: 1, dt: 0
clk: 1, dt: 0
clk: 0, dt: 0
clk: 0, dt: 0
clk: 0, dt: 0
clk: 0, dt: 1
clk: 0, dt: 1
clk: 1, dt: 1
clk: 1, dt: 1
result = 2

clk: 1, dt: 0     <== turned one notch anti-clockwise
clk: 1, dt: 0
clk: 0, dt: 0
clk: 0, dt: 0
clk: 0, dt: 1
clk: 0, dt: 1
clk: 0, dt: 1
clk: 1, dt: 1
clk: 1, dt: 1
result = 1

clk: 1, dt: 0     <== turned one notch anti-clockwise
clk: 1, dt: 0
clk: 1, dt: 0
clk: 1, dt: 0
clk: 1, dt: 0
clk: 1, dt: 0
clk: 0, dt: 0
clk: 0, dt: 0
clk: 0, dt: 0
clk: 0, dt: 0
clk: 0, dt: 1
clk: 1, dt: 1
clk: 1, dt: 1
result = 0
ilium007 commented 3 years ago

If I revert to a simple RC circuit on the encoder pins (no Schmidt triggers) everything works fine using this original transition table. I might just run with that save modifying this library.

>>>
MPY: sync filesystems
MPY: soft reboot
clk: 0, dt: 1     <== turned one notch clockwise
clk: 0, dt: 0
clk: 1, dt: 0
clk: 1, dt: 0
clk: 1, dt: 0
clk: 1, dt: 1
result = 1

clk: 0, dt: 1     <== turned one notch clockwise
clk: 0, dt: 0
clk: 0, dt: 0
clk: 0, dt: 0
clk: 1, dt: 0
clk: 1, dt: 1
result = 2

clk: 0, dt: 1     <== turned one notch clockwise
clk: 0, dt: 0
clk: 1, dt: 0
clk: 1, dt: 1
result = 3

clk: 1, dt: 0     <== turned one notch anti-clockwise
clk: 0, dt: 0
clk: 0, dt: 1
clk: 1, dt: 1
result = 2

clk: 1, dt: 0     <== turned one notch anti-clockwise
clk: 0, dt: 0
clk: 0, dt: 1
clk: 1, dt: 1
result = 1

clk: 1, dt: 0     <== turned one notch anti-clockwise
clk: 0, dt: 0
clk: 0, dt: 0
clk: 0, dt: 1
clk: 1, dt: 1
result = 0
miketeachman commented 3 years ago

This is using my hardware de-bounced rotary encoder circuit as shown at top of this issue.

>>>
MPY: sync filesystems
MPY: soft reboot
clk: 0, dt: 1     <== turned one notch clockwise
clk: 1, dt: 1
clk: 1, dt: 0
clk: 0, dt: 0

clk: 0, dt: 1     <== turned one notch clockwise
clk: 1, dt: 1
clk: 1, dt: 0
clk: 0, dt: 0
result = 1

Thanks for capturing some debug logs. When I look at the transitions it appears that the CLK and DT outputs are inverted. Is there any chance that the debounce circuit is inverting the signals from the encoder hardware?

miketeachman commented 3 years ago

For some reason when I tested a non-hardware de-bounced rotary encoder the pull-up option in the constructor did not enable pull-up resistors (or there is a hardware fault on mt STM32F405RG) - I could only get it working by adding two external 10k pull-up resistors.

For the STM32F405, the datasheet indicates that every GPIO pin supports pull-ups. But, perhaps they are too weak?

MPY: sync filesystems MPY: soft reboot clk: 0, dt: 1 <== turned one notch clockwise clk: 0, dt: 1 clk: 0, dt: 1 clk: 0, dt: 1 clk: 0, dt: 0 clk: 1, dt: 0 clk: 1, dt: 0 clk: 1, dt: 1 result = 1

This sequence is not what I expect for a clockwise turn. It almost seems that CLK and DT are wired backwards. Can you check.

ilium007 commented 3 years ago

The circuit posted above uses an inverting Schmidt trigger so I'd expect they'd be inverted, just substitute clockwise for anti-clockwise I suppose. Either way there is a state that is entered on change of direction that needs to be accounted for. See the explanation in the C++ library I had the same issue with that was later fixed: https://github.com/mathertel/RotaryEncoder/issues/19

ilium007 commented 3 years ago

As posted above also, if I make the change to your transition table it works as expected.

miketeachman commented 3 years ago

The circuit posted above uses an inverting Schmidt trigger

That explains the CLK/DT transitions which lead to missed steps. The circuit posted shows a SN74LVC1G17 device which is non-inverting Schmidt trigger. Perhaps you could implement the debounce circuit as shown (using a non-inverting Schmidt trigger)? Is there a design reason to use a non-inverting device?

miketeachman commented 3 years ago

As posted above also, if I make the change to your transition table it works as expected.

After seeing the inversion of the outputs it's making more sense to me. I'll take a look at the mathertel implementation to determine the best way to integrate a fix into this module.

I'll leave this issue open and investigate further when I get back to working on this module. I have to integrate the rPi Pico later in March, so I should be able to make any changes then. For the near future I think you've got a couple of workarounds: change the circuit to non-inverting or use a modified state table.

Thanks for patience to provide the debug information and explanations !

ilium007 commented 3 years ago

No probs - if I can fix this in the meantime I will raise a PR.

ilium007 commented 2 years ago

Still using the modified state table (I am tied to the inverting schmidt trigger IC's). Any progress on this issue?

miketeachman commented 2 years ago

Sorry. No progress. But, thanks for the nudge to get this back on my agenda.

miketeachman commented 2 years ago

I added an invert argument that is likely to fix this issue for your hardware. Right now I've implemented it on a branch. https://github.com/miketeachman/micropython-rotary/tree/invert In the constructor, add a new argument invert=True. When you get a chance can you please try this out... thanks!

ilium007 commented 2 years ago

Looks like its all working! Thanks for the fix!

miketeachman commented 2 years ago

Awesome! Thank you for testing this fix, and for prodding me to take some action on this issue.

I will integrate this branch into the master branch.

ilium007 commented 2 years ago

Thanks again