Encoders Spin Us Right Round

Rotary encoders are great devices. Monitoring just a few pins you can easily and quickly read in rotation and direction of a user input (as well as many other applications). But as with anything, there are caveats. I recently had the chance to dive into some of the benefits and drawbacks of rotary encoders and how to work with them.

I often work with students on different levels of electronic projects. One student project needed a rotary encoder. These come in mechanical and optical variants. In a way, they are very simple devices. In another way, they have some complex nuances. The target board was an ST Nucleo. This particular board has a small ARM processor and can use mbed environment for development and programming. The board itself can take Arduino daughter boards and have additional pins for ST morpho boards (whatever those are).

The mbed system is the ARM’s answer to Arduino. A web-based IDE lets you write C++ code with tons of support libraries. The board looks like a USB drive, so you download the program to this ersatz drive, and the board is programmed. I posted an intro to mbed awhile back with a similar board, so if you want a refresher on that, you might like to read that first.

Reading the Encoder

The encoder we had was on a little PCB that you get when you buy one of those Chinese Arduino 37 sensor kits. (By the way, if you are looking for documentation on those kinds of boards, look here.; in particular, this was a KY-040 module.) The board has power and ground pins, along with three pins. One of the pins is a switch closure to ground when you depress the shaft of the encoder. The other two encode the direction and speed of the shaft rotation. There are three pull-up resistors, one for each output.

I expected to explain how the device worked, and then assist in writing some code with a good example of having to debounce, use pin change interrupts, and obviously throw in some other arcane lore. Turns out that was wholly unnecessary. Well… sort of.

Abstract Reasoning

I forget that the Arduino, mbed, and other similar platforms have a wealth of (often user-contributed) libraries. I did a quick search from the import dialog (see below) and found several likely-looking libraries. The highlighted item (from [Karl Zweimüller]) was updated this year, so I figured it was a safe bet. Pressing the ridiculously large import button near the top right of the screen took care of it.

import

With the library in the project, the question becomes: how to use it? Luckily, there’s documentation built in. When you navigate your project, some nodes are source code and some are documentation (they look like little blue documents in the navigator). Clicking on the mRotaryEncoder node shows a useful help document (shown below). What else do you need to know?

doc

The Code

Armed with this, there are just a few lines of code to write. This one sets up the encoder:

mRotaryEncoder enc(D7,D8, D4,PullNone);

The D7, D8, and D4 constants define pin numbers (D7 and D8 are the encoder pins and D4 is the switch which I am actually not using). The PullNone prevents the CPU from enabling internal pullup resistors because the board already has them.

Next, we can assign functions that the library will call on certain events. In particular:

void cw()
{
  dutycycle+=0.1;
  if (dutycycle>1.0) dutycycle=1.0;
}

void ccw()
{
  dutycycle-=0.1;
  if (dutycycle<0.0) dutycycle=0.0;
}

You may have deduced my clever naming scheme. The cw function handles clockwise motion, and the ccw function handles the reverse. How does the library know what to call? It takes some setup in the program’s main function:

enc.attachROTCW(cw);
enc.attachROTCCW(ccw);

But Wait a Minute!

Sure, that’s simple. It is a great abstraction that handles a lot of complex issues. But what do you learn doing that? Not much. For all you can tell, the encoder might have a microprocessor on it and is sending data via some serial protocol (which, actually, isn’t a bad idea with as cheap as processors are now).

I decided it wasn’t sufficient to just roll out a canned library. I drug out the scope, and we put the encoder on channel 1 and channel 4 (it is a shame that channel 2 and channel 4 have nearly the same color on the probe rings). Here’s the result of twisting the shaft clockwise and counterclockwise:

The key is to look at the falling edge of the top trace. Note that in the clockwise case (left image), the bottom trace is high at that time. In the counterclockwise case (right image), it is low. You can actually detect on both edges if you like, but for most purposes, this is sufficient and it really is that simple. Watch for the edge, note the other signal’s state, and that tells you the direction. Clearly, the faster the edges come, the faster the shaft is spinning.

This is known as quadrature encoding because the two signals are 90 degrees out of phase.

Bouncy Bouncy

In an ideal world, this would be pretty easy. However, the world is far from ideal. You can see some noise on the signals if you look closely. Let’s zoom in:

newfile5

You don’t want to take all of those as falling edges! You need some debouncing. Ideally, you’d wait for the signal to go low, wait a bit, make sure it was still low and then react. Then you would not look again for some other time out delay.

This can be tricky because you typically don’t want to poll for the edge. You want to use something like a pin change interrupt that calls your code when a pin changes state.

Since the mbed libraries include the source, it was easy to look at what was really going on. The library doesn’t use an interrupt. It uses a class known as PinDetect (by [Andy Kirkham]) that polls on a timer (by default, every 20mS).

The code doesn’t have any comments, so I added a few to the partial code below:

void isr(void) {
  int currentState = _in->read();  // get current pin state
 
  if ( currentState != _prevState ) {  // if the state has changed
      if ( _samplesTillAssert == 0 ) {   // see if that was long enough
        _prevState = currentState;       // remember state for next time
        _samplesTillHeld = _samplesTillHeldReload;  // reset hold time
        if ( currentState == _assertValue ) // call correct callback
          _callbackAsserted.call();
        else 
          _callbackDeasserted.call();
      }
      else {
        _samplesTillAssert--;  // state held, so keep counting down
      }
    }
  else {
    _samplesTillAssert = _samplesTillAssertReload;  // reset
  }
...

Remember, this code runs every 20mS. It looks to see if the input changed from last time and makes the appropriate decision.

The actual encoder object is simple given that you have debounced data. Here’s the function PinDetect calls when the main input goes low:

void mRotaryEncoder::fall(void) {
  // debouncing does PinDetect for us
  //pinA still low?
  if (*m_pinA == 0) {
    if (*m_pinB == 1) {  // state of 2nd pin tells us CW or CCW
      m_position++;
      rotCWIsr.call();  // notify user code
    } else {
      m_position--;
      rotCCWIsr.call();  // notify user code
    }
    rotIsr.call(); // call the isr for rotation; notify user code
  }
}

Naturally, you might not provide all the callbacks, and that’s not a problem. The library has a similar piece of code for the rising edge, as well. You can find the entire library online if you want to browse it without creating a project.

Just to put this all in perspective, I joined the KY-040 encoder module with a KY-016 RGB LED. I selected pins on the Nucleo board, so the LED module will just plug in, but you’ll still need wires for the encoder. You can find the code on the mbed site, and the comments in the code describe the wiring.

The idea is simple. You can use the encoder to set the level of red the LED emits. Push on the shaft of the encoder, and you can control the green level. Another push sets the blue level. Subsequent pushes repeat the cycle. A simple program, but strangely entertaining to dial in strange colors (like pink or aqua). There’s a quick and dirty demo in the video, below.

Meanwhile

I often say you don’t have to know how an engine works to drive a car. But the best drivers do know. By the same token, it was easy enough to use this library to read the encoder, and if all you want is results, that’s the way to go. After all, all engineering boils down to abstractions. Maybe you understand assembly language. Maybe you can design a CPU at the logic gate level. Maybe even at the device level. But you probably aren’t growing your own silicon crystals, lapping a wafer, and building the device from there. Even then, you are relying on abstractions about how electrons flow and probably a few others.

However, it probably wouldn’t hurt if you had at least a feeling for how each one of those abstractions work. I don’t think letting students avoid understanding how a rotary encoder works is a good thing. Even if you choose to use a library to hide the details, it is good to understand what is really happening under the hood.

By the way, if you are running short on pins, you might be interested in this alternate method which is truly a hack. Meanwhile, I should probably take my own medicine and build my own encoders.

https://www.youtube.com/watch?v=m704Vw2pmtM

44 thoughts on “Encoders Spin Us Right Round

  1. I had an awful experience trying to use a mechanical encoder. Even with software debouncing and RC filtering on the encoder, I would still get horrible bouncing that made it appear to be rotating backwards occasionally. I got the encoder from BGmicro and I suspect it was quite old and the contacts must have been terrible inside.

    I would recommend to stay away from cheap mechanical encoders and go optical wherever possible

    1. When one switch bounces and the other is stable, little problem.

      When both switches bounce, no amount of delaying will fix it.

      Best, low-pass filter each switch with R-C, then try again with the delay debouncing (filtering won’t work 100% on it’s own).

    2. I first wrote code for cheap sh!tty encoders from China.

      What I learnt was to use one extra variable to store the current direction. Left -1, Right 1, Unknown 0

      Then when a pulse is missed or is synchronous with the other (as happens with cheap sh!tty encoders from China) then you assume 2 steps in the current direction. The next step after that should confirm the current direction. If however it indicates a reversal then you go three steps back. This might sound unworkable but if you put it into one function then you can return the three steps back faster than it takes for a LCD to update because they are without debounce delay.

      The above will give you full quad encoding ie four step events per quadrature cycle.

  2. Grey-code rotary encoders take me back.

    There was one on the knob of the game Tempest. As the text notes, you can use rising edges of one output as a step count and the level of the other line as a direction indicator. Tempest used a bidirectional hex counter chip. One output was connected to the clock input, the other to direction.

    One issue is that when you change direction, the first step captured may wind up being the wrong direction. In Tempest’s case, the knob’s sensitivity is dramatically reduced in software, so the “wrong” tick is lost as noise.

    1. That Tempest encoder sounds like an incremental encoder. Gray code is different. It’s used for multi bit, parallel, absolute encoders. Only 1 bit changes at any given step, unlike binary counting.
      Older industrial equipment used that in case one bit was faulty or noisy, so the resulting value would dither only by the amount of the least significant digit.

  3. Can someone explain to me, why there are lots of encoders where there are two (electrical) step per one mechanical indent? I.e. one mechanical step is equivalent to two electrical steps?

    Is there any point in doing this?

    1. Are you referring to each of the 2 outputs on a quadrature encoder changing state when the shaft is rotated one indent? If so, as the article describes, this allows you to tell the direction in which the shaft was turning when the indent was reached.

      1. Yes, but this would also be possible if your quadrature encoding progresses just one step per indent. i.e. |A|AB|B|_|A|AB|B|_| where each item in the sequence is matched with a mechanical indent: if “B” follows “AB” it is turned right, if “_” follows B it is turned right, if “A” follows “AB” it is turned left etc.
        There is no need to demand two steps in the grey sequence for the movement from one indent to the next.

        1. I believe it’s done to help discriminate a real movement from noise or bouncing. Noise/bouncing might look like a valid sequence, but if two electrical steps occur per encoder indent, it’s more obvious when the encoder was actually moved

        2. A Gray Code (Invented by Frank Gray) sequence changes 1 value per step in a multi-value encoding, like 4 bit Gray Code takes 4 signal wires and only 1 of them changes at a time.

        3. Because you can get get positioning information that is 2x the precision of the encoder markings. For example, HP inkjet printers have a quadrature encoder that consists of 150 lines per inch ruled on a mylar strip. So that is a line about 1/300 inch wide followed by a space about 1/300 inch. By placing the ootical sensors centered some odd multiple of 1/300 inches apart, you get about 1/600 inch precision from the strip. The arrangement is actually even a bit tolerant of imperfect spacing and optics.

    2. Because even with switch bounce the encoder has to physically get to the correct position before it can make contact.

      So contact is never a false positive but a contact break can be a false positive.

      You have three software styles –

      4 steps events per quad – hard to write code for that will compensate for bounce and noise – state machine

      2 steps event per quad – easier to write code for but half the resolution – just de-bounce and damn the torpedos for niose

      1 step per quad – bullet proof code is easy but it’s low resolution makes it far less cost effective

    3. I know the opposite – and it’s annoying: On my microwave only each second mechanical step does an electrical step.

      There are several different encoders: With 1, 2 or 4 steps per full cycle of the output signals. And once I had an quite expensive optical encoder, which had 1 mechanical step per cycle, but it was not defined, what the output state in the rest position was! So when it hit the mechanical indent, it could be 00, 01, 10, 11. That was very difficult to decode and you had to read the datasheet carefully to see this specification. I had to reproduce some small test equipment with this encoder and wondered, why it did not set the PLL but flickered with the display. Then I noticed, that it worked as intended, when I put the encoder slightly off it’s mech. rest position. At first I thought, it is defective, then I reread the datasheet and noticed it’s broken by design. So instead of just screwing and soldering together some parts I had to rewrite the software, which was written by some intern a year before. Basically I had to program a full quadrature evaluation using any available edge and divide by four. That was not easy as my knowledge of C was very limited at this time as I normally do only hardware design and nobody had any reference book available.

    1. Detecting quad encided signals via polling is utter bullshit to begin with. The article has some merits, but they are a bit diminished by things like using polling inder the hood to decode an encoder .. interrupts are the only way to go there if you want any speed at all.

      Th relevamce here being that the PinDetect code should never have surfaced in connection with rhis effort to begin with.

      1. Polling is fine for encoder knobs that are controlled by a human at low speed. It can even help with the denounce when timed properly. It’s like a mouse, the data path is sh!t but the human compensates for it.

  4. Minor detail: i have two of the really cheap ky-040 modules, and while the attached PCB has solder pads for pull-ups for all three switches, the pushbutton’s pull-up’s pads are unoccupied.

  5. It’s a good time to note that some microcontrollers offer an encoder counter as a hardware module.

    For instance, the Microchip 30F and 33F families of mid-range 16 bit processors have a very versatile quadrature interface peripheral complete with cyclic count and index functions.

    If you’re doing something moderately complex this is nice because it frees you from the cycles you’d normally loose to polling and servicing the quadrature lines – you just set up the hardware block which then keeps track of the encoder in
    the background.

    You only have to go back and retrieve the current count every once in a while.

    1. Only if the signal is clean or you are willing to block (debounce) in the ISR which is dicey if it is for any length of time. Otherwise you wind up with lots of spurious interrupts and you still have to filter them in software.

  6. I set myself the task of reading an encoder like this through a Raspberry Pi. Yeah … I know.

    Using Logisim I figured out a combination of gates which would turn one of two flip flops on, depending on which of the encoder contacts is engaged first. The flipflop would only be reset once both contacts return to off. I have built the circuit on a breadboard now, and it sometimes works, but seeing as this is my first time with logic design and logic ICs, getting this to work reliably will be a challenge. I already dread the task of wiring this on a perfboard, maybe an FPGA/CPLD board would have been a better idea!

    The next stage could be an MCP23017 Portexpander, with an interrupt set up for changes in the flip flop outputs. Now the Raspberry Pi only has to read the INTCAPA/B register about as often as the encoder will turn, to capture every event. Reading the INTCAP register resets the interrupts.

    I already have a working Arduino solution (with polling, without logic ICs), so this is what I call an educational exercise in over-engineering.

    1. I have seen circuits that use gates to go from two quadrature signals to a direction signal and a step signal and they work fine with the addition of two Resistor/Capacitor networks for debouncing.

      http://sub.allaboutcircuits.com/images/quiz/01384×02.png

      One other trick that I haven’t tried yet is a circuit technique that was sometimes used in video sync circuits and that may work as debounce as well.

      http://members.optusnet.com.au/eviltim/vga2arc/syncfix.png

      By adding an extra input resistor you could also add hysteresis as well.

  7. https://www.ccsinfo.com/forum/viewtopic.php?t=41115

    I like this approach. It uses polling and de-bouncing is built into the logic. In short, a timer subroutine tracks prior and current state of A & B inputs. These states are combined and used as index to constant array (enc_states[] = {0,-1,1,0,1,0,0,-1,-1,0,0,1,0,1,-1,0}; ) The value of the array position is added to the tracked value of the encoder position. Invalid transition states from noisy encoders are the zeros in this array and don’t change the count.

  8. I made the same mistake of overly complicating the problem initially, but simplified it down to the following lines of code. I use this code in my 1kB entry for a DC bench supply and it works great.

    // positive count is clockwise
    volatile int8_t Encoder_Count;
    static uint8_t Encoder_Prev;

    ISR(CLK_IRQ)
    {
    uint8_t Enc_Status;

    Enc_Status = REG(ENC_PORT,PIN) & (ENC_CLK|ENC_DT);

    if((Encoder_Prev == ENC_DT) && (Enc_Status == ENC_CLK))
    Encoder_Count–;
    else if((!Encoder_Prev) && (Enc_Status == ENC_CLK|ENC_DT))
    Encoder_Count++;

    Encoder_Prev = Enc_Status;
    }

    Encoder_Count is a shared variable that is read by main code and reset to 0.

    if(Encoder_Count)
    {
    cli();
    Setpoint_Voltage += Encoder_Count;
    Encoder_Count = 0;
    sei();
    :
    :
    }

    https://hackaday.io/project/7700-collection-of-misc-small-project/log/50683-using-rotary-encoder

  9. By concatenating the previous state and the new state and treating the result as a binary number, you can construct a lookup table with a well-known position change value for each possible combination (+1 for one direction, -1 for the other, 0 for nonsensical combinations and no change). This way most contact bounce will either make the position bounce back and forth (alternating between adding +1 and -1) until it settles on the correct value, or nonsensical combinations that don’t affect the position at all (adding 0 to the value). The process is described here: http://makeatronics.blogspot.se/2013/02/efficiently-reading-quadrature-with.html

  10. You had me worried when you started talking about libraries, but the “Wait a Minute” section made me smile. Open source libraries are for the millennials – us old DDJ readers write our own code! :-)

  11. Use edge detecting interrupts. As long as your interrupt handler is fast enough, it gives you a bounce-immune result .. every false edge will just bump you temporarily one step away from the true result, and then you get bumped back to the correct count when you return to the actual level for the current state.

    With that, and some code that detects and compensates for infeasible state transitions, you should never miss a step.

Leave a Reply to Al WilliamsCancel reply

Please be kind and respectful to help make the comments section excellent. (Comment Policy)

This site uses Akismet to reduce spam. Learn how your comment data is processed.