Rotary Encoder State Machine

One of the most interesting and useful design methods available to the engineer is State Machine design. State machines can many times simplify a design solution. The operational features of your system are reduced to a set of states. These states can track changes in your system, remember previous conditions of your system, and combine that information with the inputs and the current state of your system to generate new outputs and/or the next state of your system. A rotary encoder is an excellent device to use to learn about state machines. It is easy to understand the basic operation, it has only a few states to contend with, and its state progression is almost “automatic”.

State Machine Design

You are no doubt familiar with with the output on the A and B pins of a rotary encoder. (I found a Wiki page that has the A and B phases reversed, so watch out!) An obvious way to number our states will incorporate the A/B pin states themselves. In the diagram below, the two least significant bits are the A/B pin outputs. Using a “natural” signal such as this generally has the most utility. The device we are controlling/monitoring provides basic information about the current state and next state of our machine. But two bits only provides for 4 possible states. It is usually easy to provide additional bit values as required. In this case, we provide a third bit to indicate direction of rotation. It seems the most straightforward choice. We arbitrarily choose “0” to represent CW rotation and “1” to represent CCW rotation. Also, this bit is made to be the most significant bit of our new 3-bit state number. So, in the bubbles below, the states are NAB where N is our added bit and AB is the value of A/B from the encoder.

State machine diagram

This machine’s “home” state is numbered “011”, labeled R_START in the sketch. This is generally the detent output on the A/B pins of a rotary encoder. So it seems natural to start there. Turning the knob clockwise causes the state machine to follow the outside ring of the diagram. Turning the knob counter-clockwise causes the state machine to follow the inside ring of the diagram. The arrows show the normal transitions from state to state. The state is held in a variable. It is used (in this design) to access a 2-dimentional array. When the A/B pins are read the value is used to determine the next state by indexing into this array (code download available below). The value can make the machine go to its “next” state, the “previous” state, or the “same” state. It just depends upon the entries in the table.

This code accesses the 8 x 4 array. The first line in the code below forms a 2-bit value from the current state of the encoder A/B pins (pinstate). Next, the index into the row of the array is formed by masking the current state to 3 bits (because state may have the DIR_CW or DIR_CCW bits set and you don’t want those). Then state is combined with pinstate to access the value for the next state

    // Grab state of input pins
    unsigned char pinstate = (digitalRead( A ) << 1) | digitalRead( B );

    // Determine new state from the pins and state table.
    state = ttable[state & 0x07][pinstate];

    if( state & DIR_CW )    count++;        // count up for clockwise
    if( state & DIR_CCW )   count--;        // count down for counterclockwise

So looking at row index 0, the “R_CW_NEXT” state, for example:

    {R_CW_NEXT,  R_CW_BEGIN,  R_CW_FINAL,  R_START},                // R_CW_NEXT

  • If A/B is 00, the 0 element in this array, R_CW_NEXT, is accessed and used to set the next state. In this case, the state machine remains in the same state.
  • If A/B is 01, the 1 element in this array, R_CW_BEGIN, is accessed and used to set the next state. In this case, the state machine “backs-up” one position.
  • If A/B is 10, the 2 element in this array, R_CW_FINAL, is accessed and used to set the next state. In this case, the state machine “advances” one position.
  • If A/B is 11, the 3 element in this array, R_START, is accessed and used to set the next state. In this case, the value of 3 is not possible (see below), and this value is just a default value..

The other array entries are accessed in the same manner. The green arrows show the normal direction of state transitions. The green arrows also show where the state machine is allowed to “back-up” and transition in the opposite direction. The red arrows show where the state machine is NOT allowed to back-up. This is because the transition shown by red arrows is where we set the DIR_CW or DIR_CCW bits. That indicates that we have made a full trip around the state machine from detent to detent. If we allowed a transition from 011 to 010, we would risk that if the next A/B value were 11, we would regenerate the DIR_CW bit and falsely indicate another detent to detent transition.

Interrupt Polling

We looked at the driver using the timed interrupts. This allowed looking at the encoder state at regular intervals and setting a flag if the A/B state changed. The code in the program loop then detected the flag and processed the encoder state. We saw that it is possible to turn the encoder too fast for this type of polling. There are limitations on improving this situation:

  • You can poll more often.
    • This takes more time away from the main code.
    • No guarantee that your loop will run fast enough to catch the faster flags.
  • Move all state calculations into the ISR.
    • This will impact the “main” body of your code.
    • This will impact other interrupt processes you may be running.

There are no hard and fast rules in this situation. It all depends upon what your total system code is required to accomplish. In this situation, we are doing nothing else except printing out the count. And that is not impacted by the ISR at all. If you are missing counts, you can try putting the encoder processing in the ISR (it’s not much in this situation) and see what happens. I used the “flag poll” so that both interrupt methods demonstrated may be directly compared.

Pin-Change Interrupts

In this case, the pin-change interrupt capability of the ATmega328P turned out to be ideal. This method insures that the flag will be set at the earliest possible time during the rotation of the encoder. As soon as the encoder shaft moves to the next A/B position, an interrupt is generated. That simple response improvement makes this technique almost bulletproof. I did generate an error off-camera but I could not repeat it. If you can do your processing quickly enough in the ISR, then I don’t see how this could ever possibly fail. You would have to use a motor to turn the knob too quickly, and if you use a motor at high speed, you should be using an optical encoder anyway.

What To Do With This Info

Applying this information to your project has to be tempered by a through knowledge of all of the characteristics and requirements of your total system. When you hear someone saying “Just add capacitors to filter your noise” or “Software debouncing does not work”, an alarm should go off in your head. There are very few times where some arbitrary “rule” applies to all situations. You must consider the total environment in which you apply any technique.

  • Filtering:
    • What is the period and amplitude of the noise?
    • What is the typical noise over a wide sampling of parts?
    • How does noise change over the lifetime of this part?
    • What is the response to temperature?
    • How does the noise change from manufacturing batch to batch?
    • How will filtering affect the “real” signal?
  • Software debounce:
    • What are the noise characteristics as listed for Filtering?
    • How does debounce time impact your system?
    • Do you have to debounce both “press” and “release”?
  • Interrupts:
    • Will interrupt polling be sufficient?
    • Is your service code fast enough to be contained in the ISR?
    • Do you have an interrupt resource that fits your needs?
    • How will an interrupt impact the remainder of your system?

How about a “rule” of my own: “Never use capacitor filtering.” How do you like that? Why? Best results are obtained if you have a high quality switch/encoder with small bounce that does not vary much over age, temperature, and manufacturing. More parts (caps) means more money, and high quality parts means more money. Software is free after NRE cost. And the videos show where the best performance is with a poor quality, cheap encoder.

Velocity

So velocity.ino is my contribution to the velocity detection function. I have not looked any other solutions out there so I don’t know how mine rates compared to those. The parameters used are the first values off the top of my head. It “works” for demonstration purposes. If you’re interested, you can play with the VELOCITY and SPEED variables and maybe get it to work for you in your application. MsTimer2 library from arduino is required (see below).

Download

Contains roto_sm.ino and velocity.ino. You will need the >MsTimer2 library from arduino.cc

Download Rotary Encoder Project:  roto_encoder.zip




©copyright 2015 pretzelogic LLC. All rights reserved.
No part of this page may be reproduced without permission.
Software, schematics, and text are presented as reference works only.
No claim as to useability, suitability, or correctness for any application is made.