Efficiently Reading Quadrature With Interrupts

 Once you start wanting to control the position of a motor through feedback control, you will likely end up using a rotary encoder as the feedback device. Encoders can be broadly categorized as:

  • Absolute position
  • Incremental position
Absolute position encoders will tell you what the angular position is all the time, even between power cycles. Most use "grey code" (a modified form of binary) with several "tracks" (bits) to read position.

Incremental position encoders will tell you what the angular position is, but only relative to where it was when you started paying attention (usually on power-up). Two common types of incremental outputs are:
  • Incremental (clever name)
  • Quadrature
Incremental is rather useless for position control because it doesn't give you any information about what direction you are turning, just that you are turning. Quadrature encoders give you direction as well as incremental position. This article deals with efficiently reading quadrature output on an Arduino by utilizing external interrupts and some AVR bare-bones code.


What is Quadrature?



There are two channels of output in quadrature lovingly referred to as channels A and B. They are each a square wave, but are offset from each other by 90 degrees. Whether channel A is leading or lagging channel B depends on the direction the shaft is turning, which is what allows you to determine direction. For example, both channels are low and then channel A goes high, you know that you are spinning CCW. If channel B had instead gone high before channel A, you would then know you are spinning CW. Of course, this can be deduced starting from any state as can be seen in the diagram. The output channels can be produced by a variety of means, usually either magnets in a disk attached to the shaft and a pair of hall effect sensors, or a disk with slots cut out and a pair of optical sensors looking through the slots. A google image search of "quadrature encoder" will come up with plenty of pictures to explain it. 

How many magnets or slots are in a revolution of the encoder is known as pulses per revolution or p/r. Only the pulses on one channel are counted, the other channel will of necessity have the same number of pulses. Encoders can range anywhere from <10 p/r to 1000's of p/r, the higher numbers giving a higher resolution. If you are looking for maximum resolution, multiply the p/r by 4 since for each pulse there are two detectable events (rising edge and falling edge) on each channel. For example, a 1024 p/r encoder will give you 1024 pulses on channel A and 1024 pulses on channel B. If you detect both rising and falling edges on each channel, you have 4096 events in one revolution, giving an angular resolution of $$\frac{360^{\circ}}{1024 \tfrac{pulses}{rev} \times 4 \tfrac{events}{pulse}} = 0.0879^{\circ}/event$$

Selecting an Encoder


Selecting the best encoder for your design is more than just getting the highest resolution you can afford since the higher the p/r the faster you have to read the events. If you need to keep track of your position while spinning at 1,000 rpm with a 1024 p/r encoder, that means that between each event there is only

$$\frac{1}{1024 \tfrac{pulses}{rev} \times 4 \tfrac{events}{pulse} \times \frac{1000 \tfrac{rev}{min}}{60 \tfrac{sec}{min}} } = 0.000014648 sec \approx 15\mu s$$
You can see how a higher resolution than needed could result in not being able to keep up with the incoming information. Of course, if you needed to you could always just look for one edge on one channel and increase the 15 microseconds to 60 microseconds, but you loose resolution and direction when doing so.

From this example we can see some important factors that must be considered when selecting an appropriate encoder:
  • What is your required angular resolution
  • What is your maximum speed you need to be able to track
  • How fast can you read and process the encoder output
The remainder of this article will deal with the third bullet point, allowing you to read the encoder output as fast as possible. This will give you more freedom when considering the other two points.

Connect Quadrature Output to External Interrupts


Too often I see people trying to connect the quadrature output to some digital inputs and read them in the program loop. The problem with this is that if your loop is slower than the incoming quadrature information, then you loose track of where you are. Wouldn't it be nice if there was a way to have the encoder tell us when it's ready to send information, rather than asking for updates every time around the loop?

There is! It's called external interrupts, and an Arduino has two of them. Baically, you just connect the two quadrature output channels to the two interrups pins (digital pins 2 and 3 on a standard form factor arduino) and connect the interrupts in software:
 
  1. volatile long enc_count = 0;
  2.  
  3. void setup() {
  4. // all your normal setup code
  5. attachInterrupt(0,encoder_isr,CHANGE);
  6. attachInterrupt(1,encoder_isr,CHANGE);
  7. }
  8.  Some important things here. First, I've defined enc_count as a global variable for future use. Next, an ISR (Interrupt Service Routine) is a bit of code that gets called whenever the interrupt is triggered. ISR's take no inputs and give no outputs (hence the need for a global enc_count). You'll define this bit of code yourself, and I'll cover that a bit later. Both interrupts (interrupts 0 and 1 on pins 2 and 3, respectively) are calling the same ISR. In general the can call different ISR's, but for our purposes we want them both to behave the same. Next, we see the word "CHANGE". This is telling the program to interrupt every time there is a change on the pin, whether it goes from low to high, or high to low. Other options in place of "CHANGE" are "FALLING", "RISING", and "LOW". Check out http://arduino.cc/en/Reference/AttachInterrupt for additional info on using interrupts.

    So what happens is your loop is running happily along with no thought of checking the quadrature. Once one of the pins changes, the loop is interrupted, the ISR is executed, and once that's done the loop picks up where it left off. You must be careful to make the ISR as fast as possible, because your regular loop stops dead in it's tracks, and if you have any time sensitive stuff in the loop it may not work right. Also, you have to make sure the ISR ends before another one is called.

    The Look-up Table


    I wish I could claim credit for what I'm about to share with you. But the truth is, I can't. I found the concept in some dusty corner of the internet. At the time I didn't understand what was going on, but through copy and pasting code and some trial and error I was able to get it working for the project I was working on at the time. Since then I have figured out what was going on, and have expanded and generalized the concept a fair amount. I went to find the website I originally got this from, but was unable to locate it. If anybody has some idea of where it came from, please post in the comments.

    UPDATE: The source has been found! http://www.circuitsathome.com/mcu/reading-rotary-encoder-on-arduino
     
  9.  
  10.  
 

Post a Comment

أحدث أقدم