IoAbstraction
IoAbstraction copied to clipboard
Rotary encoder missing steps or going wrong direction
Hi, thanks for the great library!
But, I have some trouble getting a rotary decoder working reliably. Using https://github.com/davetcc/IoAbstraction/blob/master/examples/directionOnlyEncoder/directionOnlyEncoder.ino as an example, I connected a rotary encoder to a ESP32.
When turning slowly and gripping the axle firmly the encoder works correctly. But when gripping lightly and turning slowly or fast there are missed steps or even events for the wrong direction.
down
down
down
down
down
down
down
down
down
up
down
down
down
down
down
down
down
down
down
down
down
up
down
down
down
up
When checking the A and B pins with a scope the encoder works correctly with pulses >10ms. But when under 10ms, like in the scope screenshot no event is recorded.
A rotary encoder does not need any (software or hardware) debouncing. A state machine is enough. See http://www.buxtronix.net/2011/10/rotary-encoders-done-properly.html for inspiration. The debouncing is implicitly handled because some pin changes are illegal and ignored, such as 2 pinA pulses without a pinB pulse.
I tried to search in the code for a reason why it does not work, but I have some difficulty understanding how the rotary events are processed. Could the missed events be caused by the debouncing code not recognizing a rotary pulse because it was too short? (https://github.com/davetcc/IoAbstraction/commit/4d012c4e65317fbfd7d2100d828a3f8cd0d776da)
It is actually based on a state machine where only known good states are accepted. But at the moment it depends on marshalling in task manager. IE the interrupt being converted into a a task executed by task manager. We only usually see such issues when task manager is running long running tasks, for example when working with large screens without yielding.
Your crystal ball is functioning very well.
I removed one line from my code:
u8g2.sendBuffer();
Now the events all arrive correctly.
I will try to solve combining input and output.
It’s still an issue though really because it should not be so sensitive to this. We are looking at ways to avoid this in future releases.
In terms of solving it, we normally work around it by putting faster displays, eg SPI OLED units running at 4 or 10 MHz.
Our longer term plan is to move away from marshalling the interrupt for this critical task and instead move to using an event instead. It would then be far less sensitive to delays.
In another esp8266 based project I handled the rotary encoder through the interrupt function of a MCP23017 I/O expander. But I also remember having problems doing I2C inside a interrupt handler. I solved that by setting a flag inside the interrupt handler and reading that flag inside loop() to then handle I2C. But then I solved a problem that WiFi was unstable by writing my own U8G2_PCD8544_84X48_F_4W_HW_SPI function that contained a yield() inside the loop that send the display data over SPI. Reflecting now on my original problem, they seem to be alike. Too much taking time doing something else.
You could probably use that same yielding u8g2 driver and change yield() to taskManager.yieldForMicros(0) and it would work pretty effectively. When an interrupt occurs task manager pretty much does the same as you did.
I’m looking at somehow safely capturing yield using a macro that calls through to the original, but it needs some thought to avoid possible problems.
You could probably use that same yielding u8g2 driver and change yield() to taskManager.yieldForMicros(0) and it would work pretty effectively. When an interrupt occurs task manager pretty much does the same as you did.
Tested. That works. Both screen and input are working.
Many thanks for bringing this up, we'd limped along with slow I2C screens for while in tcMenu. In the next version of it, we now give the option to automatically add a byte callback during menu code creation. It makes a HUGE difference to both the remote IoT support and to screen redrawing.
Whilst using low cost encoders in my project I found that I would see inaccurate direction changes and missing steps, this was so bad when the quarter cycle option was selected that I was forced to use the default value and hence get only every other step. I think the problem resides in the encoder decoding and I have altered this to use the simple state machine from here: https://github.com/buxtronix/arduino/tree/master/libraries/Rotary This has solved the problem for me. I have forked the IoAbstraction library to make the changes to SwitchInput.cpp which can be enabled withe a macro or used unmodified. See here for the new version: https://github.com/DavidJRichards/IoAbstraction . A key change is that the decoder now differentiates between forward, backward, and invalid state changes. hth David.
@DavidJRichards many thanks for this, I will apply all the fixes from that branch.
@DavidJRichards the code that you pulled in was GPL which is not really compatible with Apache license, so I reimplemented the state machine a different way, you'll see what I mean if you look in SwitchInput.cpp where you added the code last time, you'll see HwStateRotaryEncoder
that is now the default. I have tested it on an STM32 with a full cycle encoder, I can't locate a quarter cycle one, but if you could pull the master branch and try it that would be great.
@davetcc Dave, the new code appears to work well with my dodgy half cycle encoders and is equally as good as the version I posted. I shall continue to use it now and report back if I find any problems. p.s. Making library changes is so much easier now I've switched to using PlatformIO, simply point the ini file to the new repository instead of my local version. Kind regards, David.