I'm developing a prototype that uses a quadrature encoder to measure it's linear displacement. The encoder is attached to the prototype body and has a wheel in it's shaft. As the prototype moves straight forward the encoder measure the linear distance. All the trouble around the conversion between linear/angular units have been worked out.

The reason to measure the distance with an encoder is that i NEED to know everytime that the prototype has traveled exacly 1 mm (1 millimeter = 0.0393 in). Everytime it moves 1 mm, PIC activates another system.

The way it has been done before I started working on it is by reading the encoder with a PIC (16F688) in it's I/O ports. It was kindda ok but when I added some features to the PIC code, if the prototype linear speed is greater than ~1.77 in/s (~45 mm/s) PIC starts missing the encoder count.

I've tried using the IOC (interrupt on change) from portA to read the quadrature signal (channels A and B) but it wasn't effective.

I know that there are dsPIC's but I need to solve this in my board and I have only 14 pins to do so lol. So i found this PIC16F1825 which has a higher (4x) freq. Could that (my PIC low freq) be my main problem??

I'm was thinking about using the LFLS7183 UP/DOWN signals with the timer/counter from PIC or another counter IC so that would reduce the amount of signaling to PIC. Would that work? Which counter should I use? I don't have 8 pins in my PIC to use as in those http://goo.gl/2Wc3d.

My encoder isn't my problem, I've checked and it can track as far as 76 feet/s for my wheel radius.

  • \$\begingroup\$ What frequencies are you talking about? How fast changes the encoder and how fast run yor MCU clock? \$\endgroup\$
    – Turbo J
    May 9, 2012 at 22:42

6 Answers 6


You left out the all-important information about how fast the pulses will be coming in, so I'll just assume the worst cases pulses are still slow enough for properly written firmware to catch.

I have found a few tricks to doing quadrature decoding in firmware:

  1. Don't try to catch individual changes, poll at regular intervals instead. Assume that the encoder can get stuck right on the transition for any one edge, and that edge can therefore rapidly dither. This would make a mess of any interrupt on change technique.

    With regular polling, you can tune the system carefully for how much time you want to spend in the interrupt routine. The upper limit of encoder speed is also nicely known, and isn't really any slower than the worst case with the interrupt on change method anyway. The polling method is guaranteed to work correctly up to some maximum motion speed you can easily calculate. The interrupt on change method degrades unpredictably as there is bouncing on transitions.

  2. Always decode the full 4x position. Don't try to get cutesy and do stupid stuff like count +1 when A transitions while B is high, etc. These "shortcuts" are actually harder to implement and have corner cases that get you into trouble. Even if upper levels only want to know position to a single fully cycle, you keep yourself out of trouble by decoding the new position from every change of state.

  3. Decode using a lookup table. Each interrupt, you take a snapshot of the current state of the two encoder lines. This together with the snapshot from the previous interrupt is 4 bits of data. From the previous state and the new state you can tell whether the 4x count stayed the same, or went up or down 1. Since there are only 16 possible cases, you can use these to dispatch from a table. There are 4 cases for each of staying the same, +1, and -1.

    There are also the 4 cases of the count having changed by 2 that require special handling. A change of 2 means that one transition was missed and the encoder is moving rapidly. From looking at the old and new state alone, you don't know whether it went forward 1/2 cycle or backward. Note that 1/2 cycle is a count of +2 or -2 in this system. The way I deal with this is to keep a last known direction flag. The code that does +1 and -1 sets this flag according to the known direction. When you get a step of 2, you assume it's going in the same direction as the last step of 1. That's a pretty good assumption considering inertia of real mechanical systems.

    In reality therefore you really have 5 bits of information to process each interrupt, the 2 previous encoder line levels, the new encoder line levels, and the last known direction. So for the ultimate in speed you make this a 32 entry table and handle each possible case with explicit code. The code for each of the cases adds from -2 to +2 to the current position and possibly updates the known direction flag.

As you can see, only a few instuctions are needed each polling interrupt. Your PIC (16F1825) can run at up to 32 MHz clock rate, which is 8 MHz instruction rate. Let's say you set up a interrupt every 50 instructions, which is well more than needed to do what I described above. That means you can poll at 160 kHz. Since the encoder can change by up to 1/2 cycle per interrupt, this supports up to 80 kHz encoder full cycle rate.


I did a similar project recently using a Freescale microcontroller. I made use of the timer capture capability to capture the time of the events, without having to rely on an interrupt occurring (e.g. interrupt on change) precisely when the event occurred. Your interrupt routine just reads and stores a counter value, and the rest of the calculation is dome in the base-level code.

I checked, and the PIC16F1825 has this capability also. Section 24.1 (Capture Mode) of the datasheet I downloaded:

Capture mode makes use of the 16-bit Timer1 resource. When an event occurs on the CCPx pin, the 16-bit CCPRxH:CCPRxL register pair captures and stores the 16-bit value of the TMR1H:TMR1L register pair, respectively. An event is defined as one of the following and is configured by the CCPxM<3:0> bits of the CCPxCON register:

• Every falling edge

• Every rising edge

• Every 4th rising edge

• Every 16th rising edge

When a capture is made, the Interrupt Request Flag bit CCPxIF of the PIRx register is set. The interrupt flag must be cleared in software. If another capture occurs before the value in the CCPRxH, CCPRxL register pair is read, the old captured value is overwritten by the new captured value.

You still have to deal with the captured values fast enough to avoid missing an event.


Use port B for detecting edge change for encoder pulses. You need to manage software for reading pulses whether encoder is rotating CW or CCW.


Your problem is that the pin change interrupts generated by the encoder are occurring too fast for your software to handle. This is probably because your interrupt handler runs too long. The sequence of events is probably something like:

  1. Shaft moves
  2. Encoder produces rising edge
  3. Pin change interrupt occurs
  4. Pin change interrupt handler begins
  5. Shaft moves again before interrupt handler is finished
  6. Another interrupt is generated, but not handled because you're still handling the last one
  7. Pin change interrupt handler ends, clearing the interrupt flag
  8. Second interrupt is never handled

That's how you miss interrupts at high speed - the next interrupt occurs before you're done handling the first and is never handled.

First, you should not do anything that takes a lot of time in an interrupt handler. Interrupt handlers should be as short as possible to avoid the problem you're seeing right now. You probably are trying to do everything under the sun in your handler. Don't.

Second, you should switch to a less software-heavy method of reading the encoder. If you have a free timer you can use it to measure the displacement of your shaft. If you don't care about direction or shaft speed then you can measure the displacement of your shaft in 1mm intervals by doing the following:

  1. Calculate how many encoder counts are in 1mm. Hopefully it's a fairly large number - in the 100's or so
  2. Connect the encoder to an external clock signal of one of your timers - on the 16F88 it's probably preferable to use Timer 1 since it can operate as a counter.
  3. Configure the timer to operate as a counter, be driven by the external clock and to interrupt on overflow.
  4. Load the timer count register with a value of MAX-(# of encoder counts in 1mm). Timer 1 is a 16 bit timer so MAX is 0xFFFF.
  5. Start the timer and enable interrupts.
  6. Every time your timer overflows, in the handler, reload the count register with the original value of MAX-# counts in 1mm

Now, every time your timer interrupts, your shaft will have moved 1mm. This isn't perfect - it only cares that your shaft moves 1mm but it has no idea what direction the shaft moved in or how fast it's going. This is, however, a quick and easy method of getting an interrupt whenever the shaft moves 1mm in any direction.

  • 1
    \$\begingroup\$ Most quadrature encoders can oscillate while in rest, this will make your timer count fake position changes. However, when decoded properly this oscillation usually has no bad effects. \$\endgroup\$
    – Turbo J
    May 9, 2012 at 22:59
  • 1
    \$\begingroup\$ He shouldn't be connecting the encoder pins to inputs that generate interrupts. Use the CCP and poll for direction, use an up/down counter and read the count, or use a timer interrupt and look for the quadrature state changes. \$\endgroup\$
    – akohlsmith
    May 10, 2012 at 2:15
  • 1
    \$\begingroup\$ It can be done in real time data acquisition mode or asynchronous IRQ event mode, either way the uC needs enough speed to count the event in real time. The point is the problem has not been specified, so any advice is uncertain for anyone to give a perfect solution. Step #1 define the inputs , rate and states \$\endgroup\$ May 10, 2012 at 5:03
  • \$\begingroup\$ as I understand it 45 mm/s @ 1mm per event is 45 pps input rate is pretty darn slow . There is something wrong with his code that misses these events.. I believe it is due to lack of event memory and it needs to be latched. We do not know why the event is lost. in that 22 ms period ( 1/45pps) But we do not know the exact nature of this signal, for example if there hysteresis etc as there should be for 1/4 of a quadrature cycle. If the code can take more than this time. then it certainly needs to be driven synchronously ( eg 15 ms rate) if routines cannot be interrupted. ( bad design) \$\endgroup\$ May 10, 2012 at 5:10

I know you explicitly said that you only have 14 pins to solve your problem but I don't understand if this is related to the amount of real state you have on the PCB or if this is a legacy board. If this is due to real state, you could use a dsPIC33FJ12MC201, which is a 20 pin part, not very far from your 14 pin limit, and it includes a QEI module which would make your life much easier; maybe you can squeeze this part in your board.

Hope this helps.


Was your PIC ISR simple?

  • compute jump based on Quadrature value

  • return +1 or -1 or 0

  • then add to present value.

It should be able to handle it, I think...

....any metastates? then latch the input values.

If still nogo. Then use a CPLD with Up/down counters and shift out to PIC on a serial I/O pin. Choose binary or decimal as you wish.

  • 1
    \$\begingroup\$ In 1978, I recall a tiny aspect of a major design of an Eddy Current robotic inspection system for Candu secondary heat exchanger system, I helped design. the Eddy current probe was tracked every 0.1mm along the tube at speeds I think were up to 5000 mm per second and 12 bit quadrature probe impedance readings were combined with probe position taken from simple rotary quadrature encoder like yours. We used custom hardware and a 6800 mpu. If your design is too slow , get the right tools for the job. But dont assume. Create an output pulse every ISR and check it. \$\endgroup\$ May 10, 2012 at 3:28

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.