Closed gampam2000 closed 2 years ago
Thanks for trying out the encoder library! I appreciate the feedback. I have some time in the next couple of days to look into this problem.
Can you tell me what encoder you are using. e.g. purchase link? or name?
Also, do you know the approximate turning speed where you start to see missed steps. e.g. 2 turns per second? That will help me to test with the similar conditions.
Hi, its an alps encoder. And it generate only 15pulses and has 30 steps, so I think your implementation is correct. https://www.alps.com/prod/info/E/HTML/Encoder/Incremental/EC11/EC11E15244G1.html
About the missing steps, when I turn it slighty faster it skippes steps. I will test it with a different encoder, because the only thing this could happen if the interrupt callback takes longer than the next interrupt, but I doubt thats the case. Especially on the ESP32 at full clock.
Hi,
I experience same behavior.
My encoder is 100 step CNC rotary encoder, https://tinyurl.com/y38vyyu7 . I am using ESP32 and even it says 5V encoder, it seems to work fine in ESP32 3.3 V.
I have used same encoder with Arduino and this library https://www.pjrc.com/teensy/td_libs_Encoder.html , it does not lose steps ever.
With ESP32 and this library, it works but has three problems.
This is very constant problem. When I change direction it needs always two steps before value is changed. When I change direction and do only one step, value is same as previous value. If I move one step back and forw continuously, value does not change at all.
Similar to problem 1. After ESP32 reset, it needs two steps before the value changes. This happens always.
This is less constant, but sometimes it loses steps. It is rare and not depending on speed I rotate. I can rotate very fast and in most cases it is fine.
Thanks for this library, it is very useful.
Hi,
I added this debug line and did some testing to clarify my previous post
def _process_rotary_pins(self, pin):
print(str(self._hal_get_clk_value())+" "+str(self._hal_get_dt_value()))
Encoder was initialized with this command, rest is example.py
r = RotaryIRQ(pin_num_clk=19, pin_num_dt=23)
See below log of the test. I added manually comments to log starting with //
// ------ reset
0 1
1 1
1 0
0 0
// ------ rotated right 1: note value did not change
0 1
1 1
1 0
result = 1
0 0
// ------ rotated right 1
0 1
1 1
result = 2
1 0
0 0
// ------ rotated right 1
0 1
1 1
1 0
0 0
result = 3
// ------ rotated right 1
0 1
1 1
result = 4
1 0
0 0
// ------ rotated right 1
0 1
1 1
1 0
0 0
result = 5
// ------ rotated right 1
0 1
1 1
1 0
result = 6
0 0
// ------ rotated right 1
0 1
1 1
1 0
0 0
result = 7
// ------ rotated right 1
1 1
result = 8
1 0
0 0
// ------ rotated right 1
1 0
1 1
0 1
0 0
// ------ rotated left 1, note: value did not change
1 0
1 1
0 1
result = 7
0 0
// ------ rotated left 1, note: value changed when rotated two steps left
1 0
1 1
0 1
0 0
1 0
result = 6
0 0
// ------ rotated left 1, it went slightly over the slot and then back to slot
1 0
1 1
0 1
0 0
result = 5
// ------ rotated left 1
0 1
1 1
1 0
0 0
// ------ rotated right 1, note: value did not change
1 0
1 1
0 1
0 0
// ------ rotated left 1, note: value did not change
0 1
1 1
1 0
0 0
// ------ rotated right 1, note: value did not change
0 1
1 1
1 0
0 0
result = 6
// ------ rotated right 1, note: value changed only after rotated 2 steps right
And for losing steps problem.
I tested more and it is not losing steps at all, no matter if i rotate fast (>200 steps/s) or slow.
Only problems are related to direction change and first step after reset.
@gampam2000
I will test it with a different encoder
That would be great. I don't own the type of encoder that you mention.
because the only thing this could happen if the interrupt callback takes longer than the next interrupt
I wanted to share some oscilloscope captures that might give some insight into the limitations of the ESP32 and this MicroPython based implementation of an encoder.
Signals: Yellow: CLK, Aqua: DT, Purple: ISR duration (_process_rotary_pins() in rotary.py)
Scope plot#1 shows CLK/DT signals for one encoder click and the resulting calls to the ISR
Scope plot#2 zooms in on the fall of DT and shows 7 calls to the ISR. The multiple calls to the ISR are caused by bouncing of the DT contact. Note that interrupts are queued by the ESP32 and result in multiple calls to the ISR. Running the ISR 7 times back-to-back takes 1.920ms
Scope plot#3 zooms in on the first call to the ISR. This capture shows the bouncing of DT, which triggers the ISR multiple times. The ISR takes 240us to complete, and the ISR latency is 83us (DT falling to ISR start)
These oscilloscope captures show that:
These performance reducing factors result in missed steps when the encoder is rotated quickly.
To illustrate this point, here is a final scope capture when the encoder is rapidly turned 6 steps in 50.6ms - it shows an almost continuous operation of the ISR. In this case 4 steps were missed.
Hope this helps to understand what is happening "under the hood" of this rotary encoder implementation
@hpirila
See below log of the test. I added manually comments to log starting with //
Your debug print and comments helped a lot. I'm fairly certain that I see the issue - the encoder you are using has different resting values for CLK and DT than the encoder I used when developing the Rotary module.
At rest, your CNC encoder has CLK=0 and DT=0. But, the state table is designed to work with an encoder that has CLK=1 and DT=1 in the resting state. e.g. if you put an oscilloscope (or multimeter) on DT and CLK you will see that CLK=0V and DT=0V when it is at rest.
The different resting values for CLK/DT changes how the transition table operates.
Here is a workaround that I think will make your encoder function properly - it involves changing one line in the ISR as shown below to invert the CLK/DT values.
def _process_rotary_pins(self, pin):
clk_dt_pins = ((not self._hal_get_clk_value()) << 1) | (not self._hal_get_dt_value())
Is it possible to try this workaround?
One other observation: the CLK and DT signals coming from your encoder appear to have no contact bounce. This is a good thing as it will allow you to have a higher rotation rate before counts are lost.
I tried the workaround and it works very well. Both problems, first step after reset and direction change are now working as expected.
I also did some stress test to rapidly move back and forth as fast as I can. It never loses a single step. If I start from slot 0, result is always 0 when I move back to slot 0. Also tested with two encoders moving simultaneously and rapidly, still works fine.
This encoder has 6 connectors, 0V, +5V, A, B, A- and B-. I am currently connected to A and B, likely the library would have work straight away had I used A- and B- connectors instead. Not tested that yet.
So I quess my encoder bounces a lot.
So it bounces more at the beginning, maybe the problem can be reduced by disable the interrupts when entering the ISR and enable it at the end. So during the time of the ISR no more Interrupts can be queued. When looking at your Plot #2 the 7 ISR calls are all the result of the spikes in small time left of the first white vertical line.
Than the max. rotation speed without losing steps is limited by the time the ISR takes to complete.
Or did I miss something?
You can try to add capacitors between A, B and ground as seen here https://hifiduino.wordpress.com/2010/10/20/rotaryencoder-hw-sw-no-debounce/
I've now tested it with a different encoder. A Honeywell 600EN-128-CN1 (https://datasheet.octopart.com/600128CN1-Honeywell-datasheet-11736964.pdf)
This Encoder has 128 steps per revolution. If i turn it really slowy (4s/revolution) it counts only 80 steps out of the 128. So here I also loose Steps.
If I turn it with 1rev/s one step equals to 7,8ms. I will test it with the debouncing circuit.
@gampam2000
about the max rotation speed. As you mentioned, max speed without losing steps is limited by the ISR duration .. and, also the number of interrupts caused by contact bounce.
I'm interested in the results of the debounce capacitor test. I haven't tried that yet. It might be a good way to improve the performance of bouncy encoders.
@hpirila glad that the workaround helped. I'll look at adding an option to the constructor, to support encoders that have DT/CLK=0 on start. Thanks for the link on the capacitor technique to reduce contact bounce. I'll try it out the next time I'm using encoders.
The new invert argument in the commit https://github.com/miketeachman/micropython-rotary/commit/e7673121e7c6071033ff13377e71ee063fa63b25 replaces the need for this workaround
Hi,
first of all i like the implementation, great work. I tested it on a ESP32 and I experienced that I have to turn the encoder 2 steps so that it is counted +1 or -1. Have you experienced this behaviour? But maybe it is just my encoder (30steps per Revolution).
A second issue is, that if i turn the encoder faster it skips steps. Is there a solution to this problem?