Saturday, 15 November 2014

Taming the Rotary Encoders

My Rotary Encoders have had it easy - it is time to introduce some tough love...

Ever since the earliest days of the Occam's Microcontroller project, the Arduino QRP transceiver that preceded it (or even the earliest games with the Raspberry Pi) I have used a simple software interface to my rotary encoders. Critics could (with some justification) call it a lazy software interface - but I will defend the word "simple"...

All my (Arduino) code observes the state of the rotary encoder switches once per "loop". This has the considerable benefit of simplicity (as advocated by the KISS principle, by our belovéd Franciscan, etc). It also has the considerable risk of inadequacy!

If the encoder is to be read accurately, it must be sampled sufficiently frequently. One danger of my "simple" approach is that the "one observation per pass through the loop( )" will not constitute a sufficiently high sample rate to satisfy Shannon's sampling theorem, such that neither direction nor speed of the encoder can be deduced. Errors will result.

Both the "correct" solutions (fast, interrupt-driven sampling of the encoder or explicit connection of the encoder to inputs with edge transition detection capability which can trigger interrupts) feel way-too complicated to me and offend against my sense of "appropriate engineering". But the simple approach was always known to be standing on thin ice - that ice has just melted, forcing me into action. Two things prompted my activity...

Firstly, I was getting errors from the Rotary Encoder in my own rig.

I could understand this - my rig was running way-bigger code than anything I'd released to the public (there's more in there than I've told you about - YET!), so my own "sample rate" was even lower than that of any of my "customers". Also, I had a rather long cable run from my board to my Rotary Encoder and I had a pretty hostile operating environment. Most importantly of all, I could readily ignore complaints from this source - even though the errors from my encoder were getting to be pretty bad!

The second thing that happened was much more significant - it really catalysed ACTION. A real, live reader of this blog, named Richard, came up to me at the Rishworth mini-convention and described the problem. He didn't complain - but he could (and perhaps should) have done.

In response to Richard's input, I've finally done something, which has fixed my own rig (above) and which seems to offer a useful "taming" of the Rotary Encoder - I've added two capacitors!

The capacitors are placed in parallel with the two "switches" in the rotation sensing part of the encoder, as shown in the schematic above.  In fact, I had tried this previously (in building the encoder system for the "VFO" for the SI5351-based BITX), where I found it to work very well.

Action of the additional capacitors is understood if you remember that there are resistors (shown dotted in the schematic above) - either fitted explicitly as extra components or embodied as internal pull-ups in the microcontroller. The combination of these resistors and the additional capacitors are going to define a time-constant (making appropriate choice of the capacitor value dependent upon the resistor) which modifies the switching action as sensed by the microcontroller...

The sketch below shows one switch closure with- (bottom) and without- (top) a capacitor fitted.

The downward transition isn't changed. But when the switch opens, the capacitor has to be charged to build up the voltage, introducing a time delay Δt before the voltage read on the microcontroller input pin reaches the logic high value. This "stretches" the pulse width. If the pulse is stretched wider, it can be observed successfully at a lower sample rate.

The capacitors also modify switch bounce (although I think this is irrelevant, given my lamentably slow sample rate) and suppress high-frequency noise which might be induced on the otherwise high impedance line.

I can't confirm the exact mechanism that is taming my Rotary Encoders - all I can report is that the addition of the 100nF capacitors, as shown, is doing an excellent job. I recommend that you add them to all the m0xpd rigs, VFOs, etc that use Rotary Encoders read by Arduino software.

Thanks Richard!

...-.- de m0xpd

Update (16 Nov)

Fred, wa1dlz, has been experiencing problems with my code, including the problem with the Rotary Encoder - see his comment below. In subsequent private correspondence, Fred told me that the capacitors described above made no difference at all on his system - in fact "the 100nf caps actually made things worse".

This information from Fred prompted me to try to visualise the "stretch" in the pulse from the rotary encoder to confirm that it actually exists.

I set up a system (running the "Double DDS" code) and instrumented the rotary encoder pins using my ripoff logic analyser. This claims to interpret VHIGH as being anything greater than 1.4V.

Given that the pulse widths from the encoder are controlled by the rotation speed - which itself is controlled by my hand - the entire experiment is rather difficult to construct. I decided to use one "side" of the encoder as a reference and to add the capacitor to the other "side", as suggested in this schematic...

The resistors (as shown dotted above) are the internal pull-ups in the ATmega328P microcontroller.

I made several measurements, in which I sought to rotate the encoder at roughly similar speed, with and without the capacitor Cx.

The following results are representative...

Whilst I don't suggest this is marked by rigor, I do think there is indication of an increase in the relative width of the "off" phase of the B signal as the capacitor Cx is increased.

Fred's findings show that the capacitors are - by no means - a panacea. They haven't helped him at all -but they have transformed my rig's tuning control from a frustration to a joy.

The "stretch" seems real.  But it may not stretch to solving your problem - please let me know, one way or the other!


  1. I have been able to improve the loop time a bit by putting an additional 'if'' statment around the T/R test at the start of the loop. In it I test to see if the T/R state has change; if not simply skip that block thus skipping over the redraw of the display. Tuning is noticably improved.

    I will have to add the caps though to se if it improves even more.

    Fred LaPlante, WA1DLZ

  2. I've had very good success with the code on this page: Using the bit-wise operations technique removes the need to de-bounce with capacitors or with code. In addition, and invalid input is automatically rejected. The only issue with the code as-is is that it is dependent on the speed of the sampling loop. This will cause some data to be missed, but may be acceptable for your use. The better way to handle it is by using a hardware interrupt on one (or both) of the pins and using a callback to track the encoder status. I find the combination of the bit-wise technique and hardware interrupts to be the simplest implementation from both a hardware and software perspective. In addition, the combination gives the most accurate results.