[TriEmbed] Fwd: Quadrature encoder help

Scott Hall scottghall1 at gmail.com
Tue Sep 13 17:38:02 CDT 2016


I found the following snippet from this article:
http://www.robotoid.com/appnotes/circuits-quad-encoding.html
Reading the Encoder with an Arduino

Let's assume you've built a quad encoder using the LS7184 IC, and connected
it to an Arduino microcontroller development board, as shown in the
schematic above. The clock output of the LS7184 provides a pulse for each
complete step of the encoder wheel. If the wheel has 32 stripes, for
example, you'll get 32 pulses per revolution.

*RoboTip!*
Check out the Mode pin on the LS7184. This sets whether the clock is
multiplied by 1, 2, or 4. I'm showing it set at 1X, which provides one
clock pulse per encoder step (if 1 revolution = 32 stripes, then 1
revolution = 32 pulses). When pin 6 of the LS7184 is brought HIGH, the
output is multiplied by 2, so you get 64 pulses per revolution. And when
the pin is left floating the multiplication factor is 4X.

You can use the Mode setting to increase the resolution of the encoder,
letting you detect smaller changes in rotation. It's useful when using
encoder discs with relatively few stripe transitions.

This sketch for the Arduino reads the clock changes on pin D2, which
provides for an external interrupt. Each time a clock pulse is detected on
this pin, the Arduino immediately branches off to an interrupt routine --
called *encoderIntA* in the sketch -- to process the pulse.

Code within the interrupt routine examines the instantaneous state of the
direction output of the LS7184, which is connected to Arduino pin D4. If
the direction is HIGH, the encoder is assumed to be going forward, so the
count goes up by one. If the direction is LOW, the encoder is assumed to be
going in reverse, so the count goes down by one.

For demonstration purposes the current count value is displayed in the
Serial Monitor window. I use an *int*-size variable to hold the count. This
variable can store positive and negative values in the range -32,768 to
+32,767. If the count goes above +32,767, it wraps around as a negative
value.

If you suspect this might ever happen in your application use a *long*-size
variable instead.
These store values from -2,147,483,648 to +2,147,483,647. That's a lot of
wheel rotations!

/*
  Quadrature encoder example
  Interface to LSI LS7184 quadrature clock converter
  (USDigital part #LFLS7184)

  Demonstrates reading quadrature encoder using Arduino interrupts.
  Clock output is connected to pin 2 (Int0); direction output is
    connected to pin 4.
  Current position is displayed in Serial Monitor window.
*/

const int clkPinA = 2;
const int dirPinA = 4;
volatile int encoderACount = 0;
volatile boolean changeFlag = false;

void setup() {
  Serial.begin(9600);
  pinMode(clkPinA, INPUT);
  pinMode(dirPinA, INPUT);
  attachInterrupt(0, encoderIntA, RISING);
}

void loop() {
 if (changeFlag) {
    changeFlag = false;
    Serial.println(encoderACount);
  }
}

void encoderIntA() {
  if (digitalRead(dirPinA) == HIGH)
    encoderACount++;
  else
    encoderACount--;
  changeFlag = true;
}


This article,
http://makeatronics.blogspot.com/2013/02/efficiently-reading-quadrature-with.html,
has the following section in it:

Using Only 1 Interrupt

If you're using an arduino Mega, or the new Due, you have more external
interrupts to work with, so if you want to hook up multiple encoders or you
have interrupts dedicated to other hardware you're probably covered. But if
you're working on an Uno or one of it's predecessors, you are limited to
only 2 interrupts.

It is possible to use this same look-up technique with only 1 interrupt per
encoder. The trade off is that you will loose half of your resolution. In
this case, you would hook up the other channel to a regular digital pin,
and then rework your look-up table keeping in mind that you can only detect
when one of the channels is changing.

<http://3.bp.blogspot.com/-9WVs1ImbheQ/U9w53Hm10FI/AAAAAAAAOv4/H313k8qFmnw/s1600/quadrature_half_res.png>

You might think I'm missing some information on the B channel, but remember
that the microcontroller only sees when A changes, and reads B at that
time. In the CW direction, when state 2 started A was high and B was low.
When it gets to state 3, A is low and B is high. We have no information
about when B changed from low to high, only that it is now high. That's why
we loose half the resolution when using only one interrupt.

Working out the entire look-up table:

<http://4.bp.blogspot.com/-9vT8QVtvlug/U9w537vwMsI/AAAAAAAAOwE/fxmM6ao7DyI/s1600/quadrature_table_half_res.png>

If you instead connected channel B to the interrupt, this table would be
different. One way to tell if you've done the table correctly is that the
direction column should always be symmetric about the middle. That is,
entry 1 should equal entry 16, entry 2 = entry 15, entry 3 = entry 14, etc.
Meeting this condition doesn't guarantee that you've done it right, but if
you don't meet this condition I guarantee you've done it wrong.

You will need to make some changes to the ISR as well. Line 6 in the ISR
above assumed that the quadrature channels were hooked up the pins 2 and 3.
For convenience in the code, I suggest you keep the two channels adjacent
to each other whenever possible, i.e. pins 3 and 4 or pins 1 and 2. But
remember the pins 0 and 1 are used for TX and RX.

If you connect channel A to the interrupt on pin 3, and channel B to pin 4,
the ISR becomes:


   1. void encoder_isr() {
   2.     static int8_t lookup_table[] = {0,0,0,-1,0,0,1,0,0,1,0,0,-1,0,0,0};
   3.     static uint8_t enc_val = 0;
   4.
   5.     enc_val = enc_val << 2;
   6.     enc_val = enc_val | ((PIND & 0b11000) >> 3)
   7.
   8.     enc_count = enc_count + lookup_table[enc_val & 0b1111];
   9. }



You can also see what was done here in developing this library:

http://www.mathertel.de/Arduino/RotaryEncoderLibrary.aspx


for reference:
https://www.google.com/search?num=100&newwindow=1&safe=active&q=arduino+quadrature+encoder+code+single+interupt&oq=arduino+quadrature+encoder+code+single+interupt&gs_l=serp.3..0i71k1l8.0.0.0.959.0.0.0.0.0.0.0.0..0.0....0...1c..64.serp..0.0.0.-xq6S5kokU4

- sgh

On Tue, Sep 13, 2016 at 12:33 AM, Rodney Radford via TriEmbed <
triembed at triembed.org> wrote:

> The 'best' way to solve any programming problem is to first look at it,
> hit your head on the wall, post it for others to help, go to bed... and
> then immediately realize the error and have to get up to correct it.
>
> Although I looked at this for over an hour before posting it, it was not
> until I was trying to sleep that I realized why I was always seeing 0 or 1
> - each transition would just bump it up, and then back down. In order to
> count up or down, I was going to need to look at more than just two bits of
> state.
>
> I started to code this, and did another google search - you know, the same
> thing I did before I asked and came up empty?  Yeah, that thing... and this
> time I found a great resource. While I did not need everything in the
> library, I was able to pull out just the interrupt routine and that solved
> my problem.
>
> The nice library I found was at: https://www.pjrc.com/teensy/
> td_libs_Encoder.html
>
> The updated (and working) code is below:
>
> /*
>  * Simple quadrature encoder implementation.  I only have one interrupt
> available (as I
>  *    am using the other for another purpose on this project), so I can
> only watch one
>  *    of the two encoder pins.  While this reduces the number of
> transitions per revolution
>  *    that is okay for this purpose.
>  *
>  * When running this, I only see a stream of 0 and 1 values for the
> encoderValue - what am
>  *    I doing wrong?
>  */
> #include "Arduino.h"
>
> int encoderA = 3;   // This one is an interrupt pin
> int encoderB = 4;   // This is a standard (not interrupt) pin
>
> volatile long encoderValue = 0;
>
> void updateEncoder(void);
>
> /*
>  * Setup the input pins and attach our interrupt vector
>  */
> void setup() {
>    Serial.begin(9600);
>
>    // Set both quadrature channels to input and turn on pullup resistors
>    pinMode(encoderA, INPUT);
>    pinMode(encoderB, INPUT);
>    digitalWrite(encoderA, HIGH);
>    digitalWrite(encoderB, HIGH);
>
>    // invoke interrupt routine on each falling edge of encoderA
>    attachInterrupt(1, updateEncoder, CHANGE);
> }
>
>
> /*
>  * Simply print out the encoder value (at a reasonable rate)
>  */
> void loop() {
>
>   Serial.println(encoderValue);
>   delay(100);
> }
>
>
> int state = 0;
>
> /**
>  *               _______         _______
>  *   Pin1 ______|       |_______|       |______ Pin1
>  *           _______         _______         __
>  *   Pin2 __|       |_______|       |_______|   Pin2
>  *
>  *    <-- negative count                    positive count -->
>  *
>  *    new     new     old     old
>  *    pin2    pin1    pin2    pin1    Result
>  *    ----    ----    ----    ----    ------
>  *    0       0       0       0       no movement
>  *    0       0       0       1       +1
>  *    0       0       1       0       -1
>  *    0       0       1       1       +2  (assume pin1 edges only)
>  *    0       1       0       0       -1
>  *    0       1       0       1       no movement
>  *    0       1       1       0       -2  (assume pin1 edges only)
>  *    0       1       1       1       +1
>  *    1       0       0       0       +1
>  *    1       0       0       1       -2  (assume pin1 edges only)
>  *    1       0       1       0       no movement
>  *    1       0       1       1       -1
>  *    1       1       0       0       +2  (assume pin1 edges only)
>  *    1       1       0       1       -1
>  *    1       1       1       0       +1
>  *    1       1       1       1       no movement
>  *
>  * Implementation based on Encoder library at:
>  *     https://www.pjrc.com/teensy/td_libs_Encoder.html
>  */
> void updateEncoder(void) {
>    uint8_t s = state & 3;
>    if (digitalRead(encoderA)) s |= 4;
>    if (digitalRead(encoderB)) s |= 8;
>    switch (s) {
>        case 0: case 5: case 10: case 15:
>           break;
>        case 1: case 7: case 8: case 14:
>           encoderValue++;
>           break;
>        case 2: case 4: case 11: case 13:
>           encoderValue--;
>           break;
>        case 3: case 12:
>           encoderValue += 2;
>           break;
>        default:
>           encoderValue -= 2;
>           break;
>        }
>    state = (s >> 2);
>
> }
>
>
> ---------- Forwarded message ----------
> From: Rodney Radford <ncgadgetry at gmail.com>
> Date: Mon, Sep 12, 2016 at 11:45 PM
> Subject: Quadrature encoder help
> To: TriEmbed Discussion <triembed at triembed.org>
>
>
> I am adding a quadrature encoder to a project I am working on, but having
> a problem understanding why this is not working.
>
> I have used quadrature encoders before, implementing them with 0, 1, and 2
> interrupt lines.  In this case, I only have one interrupt line available so
> I am interrupting on one channel and checking the other.
>
> I know this is possible, but I have a bug in the code below and I can't
> seem to find it.
>
> What am I missing?
>
>
> /*
>  * Simple quadrature encoder implementation.  I only have one interrupt
> available (as I
>  *    am using the other for another purpose on this project), so I can
> only watch one
>  *    of the two encoder pins.  While this reduces the number of
> transitions per revolution
>  *    that is okay for this purpose.
>  *
>  * When running this, I only see a stream of 0 and 1 values for the
> encoderValue - what am
>  *    I doing wrong?
>  */
> #include "Arduino.h"
>
> int encoderA = 3;   // This one is an interrupt pin
> int encoderB = 4;   // This is a standard (not interrupt) pin
>
> volatile int  lastB = 0;
> volatile long encoderValue = 0;
>
> void updateEncoder(void);
>
> /*
>  * Setup the input pins and attach our interrupt vector
>  */
> void setup() {
>    Serial.begin(9600);
>
>    // Set both quadrature channels to input and turn on pullup resistors
>    pinMode(encoderA, INPUT);
>    pinMode(encoderB, INPUT);
>    digitalWrite(encoderA, HIGH);
>    digitalWrite(encoderB, HIGH);
>
>    // invoke interrupt routine on each falling edge of encoderA
>    attachInterrupt(1, updateEncoder, FALLING);
> }
>
>
> /*
>  * Simply print out the encoder value (at a reasonable rate)
>  */
> void loop() {
>
>   Serial.println(encoderValue);
>   delay(100);
> }
>
>
> /*
>  * This interrupt routine is called on each falling edge of encoder
> channel A. We
>  *   look at the B edge to to determine if we are counting up, down, or
> staying in
>  *   the same location.
>  * Note if we only looked at the current B (and not the previous B), we
> would get
>  *   duplicate up/down counts on every transition due to non-debounced
> quadrature
>  *   signals.
>  */
> void updateEncoder(void) {
>
>   // read the current encoderB value
>   int newB = (digitalRead(encoderB) & 0x1);
>
>   // create the 'transition' variable - show both old and new states - if
> 00 or 11
>   //    this is a duplicate falling edge (not moved), but if 01 or 10, we
> have moved
>   int transition = (lastB << 1) | newB;
>
>   // are we counting up, down, or staying the same location?
>   switch (transition) {
>       // transitioned from a 0 -> 1 edge, so counting up
>       case B01:
>            encoderValue++;
>            break;
>       // transitioned from a 1 -> 0 edge, so counting down
>       case B10:
>            encoderValue--;
>            break;
>   }
>
>    // Save current encoderB value for next transition variable
>    lastB = newB;
> }
>
>
> _______________________________________________
> Triangle, NC Embedded Computing mailing list
> TriEmbed at triembed.org
> http://mail.triembed.org/mailman/listinfo/triembed_triembed.org
> TriEmbed web site: http://TriEmbed.org
>
>


-- 
Scott G. Hall
Raleigh, NC, USA
scottghall1 at gmail.com
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.triembed.org/pipermail/triembed_triembed.org/attachments/20160913/6d34bd0a/attachment.htm>


More information about the TriEmbed mailing list