Go Down

Topic: Need to improve 2x Encoder.h performance on Arduino Uno (Read 375 times) previous topic - next topic

wado1971

Hi all.
I'm using the Encoder.h library from PJRC (link) to read 2 encoders on an Auduino Uno. As this board only has 2 external interrupt pins I'm limited to "Good" performance by only using 1 external interrupt pin per encoder. I was wondering if I can implement "pin change" interrupts together with Encoder.h to improve my performance by then using 2 interrupt pins (I'm missing some steps at higher speeds). 
Thanks
Rob

robtillaart

Please post your example code that shows the performance problem.

How much interrupts per second do you want to process?

2 interrupts pin can read 2 different encoders.

Pin change interrupts are not to difficult (helped GrayNomad to optimize his lib). Problem is that you get an interrupt and then you need to check all pins to see which (multiple) pins did change. This takes time.



Another solution uses an additional XOR chip on the A and B lines of the encoders


  A    B    XOR
-----------------
  0    0     0
  0    1     1
  1    1     0
  1    0     1
  0    0     0


By putting the XOR line to the interrupt pin you get all the changes of the encoder.
In the interrupt you need to read the pinA and pinB which are connected to e.g. pin 3 and 4
Reading those 2 pins can be done fast on registerlevel, comparing them with previous value
indicates the direction

Give it a try



Rob Tillaart

Nederlandse sectie - http://arduino.cc/forum/index.php/board,77.0.html -
(Please do not PM for private consultancy)

horace

I would move to a Mega which has more digital pins which support interrupts
or move to a microcontroller which has hardware QEI support, see
http://www.microchip.com/maps/microcontroller.aspx

dougp

Another solution uses an additional XOR chip on the A and B lines of the encoders
Nice to know!  That very idea is on my experiments to-do list.

If OP wants to try this, know that you don't need an XOR IC (74XX86), you can  create an XOR function with a quad-NAND.
So two neutrinos went into a bar.  Nothing happened.  They were just passing through.

robtillaart

Nice to know!  That very idea is on my experiments to-do list.

If OP wants to try this, know that you don't need an XOR IC (74XX86), you can  create an XOR function with a quad-NAND.
When you do I am curious what the max nr of irq's is you can handle in practice.
Note:
The XOR solution generates 4 irq's per rotation, while connecting pinA directly to irq can be configured to give 1 irq per rotation. Question is allways does the application need it?
Rob Tillaart

Nederlandse sectie - http://arduino.cc/forum/index.php/board,77.0.html -
(Please do not PM for private consultancy)

dougp

When you do I am curious what the max nr of irq's is you can handle in practice.
I'm only planning on doing this on a manual rotary encoder, like you use for navigating menus.  I don't know how to quantify the responsiveness at speed.

So two neutrinos went into a bar.  Nothing happened.  They were just passing through.

robtillaart

I'm only planning on doing this on a manual rotary encoder, like you use for navigating menus.  I don't know how to quantify the responsiveness at speed.
Put a drill on it and see at what speed "stuttering" in the output begins
Rob Tillaart

Nederlandse sectie - http://arduino.cc/forum/index.php/board,77.0.html -
(Please do not PM for private consultancy)

dougp

Put a drill on it and see at what speed "stuttering" in the output begins
Hmmm.  Guess that means incorporating a tach function on the Arduino.
So two neutrinos went into a bar.  Nothing happened.  They were just passing through.

robtillaart

#8
Jan 14, 2018, 04:12 pm Last Edit: Jan 14, 2018, 04:14 pm by robtillaart
Hmmm.  Guess that means incorporating a tach function on the Arduino.
a counter that counts interrupts and is printed/reset once a second should be sufficient.
wrote a small sketch that could test it (unfortunatelly no rotary encoder and drill nearby)

Code: (experimental) [Select]
//
//    FILE: encoderFast.ino
//  AUTHOR: Rob Tillaart
// VERSION: 0.0.1
//    DATE: 2018-01-14
// PURPOSE: fast decoder algorithm with one interrupt and an XOR
//     URL: http://forum.arduino.cc/index.php?topic=522410
//
// Encoder          Arduino
//  A --------------- 3
//      |
//      ---|
//         | XOR ---- 2  --> interrupt on change
//      ---|
//      |
//  B --------------- 4
//
//  A    B    XOR
// ---------------
//  0    0     0
//  0    1     1
//  1    1     0
//  1    0     1
//  0    0     0
//
//

uint32_t lastUpdate = 0;

volatile uint32_t irqCount;
volatile int32_t stepsSinceStart = 0;

const int encoderPinA = 3;
const int encoderPinB = 4;

void setup()
{
  Serial.begin(230400);
  Serial.println(__FILE__);

  pinMode(encoderPinA, INPUT_PULLUP);
  pinMode(encoderPinB, INPUT_PULLUP);
  attachInterrupt(0, readEncoder, CHANGE);
}

void loop()
{
  uint32_t now = millis();
  if (now - lastUpdate >= 1000)
  {
    lastUpdate = now;
    cli();
    uint32_t cnt = irqCount;
    irqCount = 0;
    uint32_t pos = stepsSinceStart;
    sei();
    Serial.print("CNT: ");
    Serial.print(cnt);
    Serial.print("\t\tPOS: ");
    Serial.println(pos);
  }
}

void readEncoder()
{
  static uint8_t val = 0;
  irqCount++;
  val <<= 2;
  val |= digitalRead(encoderPinA) << 1;   // direct register read is even faster
  val |= digitalRead(encoderPinB);

  // val = bitpattern [x x x x prevA prevB curA curB ]
  //       x = ignore
  // 8 transitions possible, 4 CW and 4 CCW
  switch (val & 0x0F)
  {
    case B0001:
    case B0111:
    case B1110:
    case B1000:
      stepsSinceStart++;
      break;
    case B0010:
    case B1011:
    case B1101:
    case B0100:
      stepsSinceStart--;
      break;
  }
}

// --- END OF FILE ---
Rob Tillaart

Nederlandse sectie - http://arduino.cc/forum/index.php/board,77.0.html -
(Please do not PM for private consultancy)

dougp

a counter that counts interrupts and is printed/reset once a second should be sufficient.
wrote a small sketch that could test it (unfortunatelly no rotary encoder and drill nearby)
Thanks for the sketch!  I wasn't looking forward to slogging through past posts to find one to hack at or cobbling one together from scratch.

I've modified your sketch slightly to have encA and encB drive interrupts 0 & 1 - which is the way my encoder is currently hooked up.  Highest reading so far, turning by hand is 621.  I'll chuck up a drill tomorrow and see what happens with a more consistent speed.

I was disappointed to see your wiring diagram.  My vision was that using the XOR would not only save an interrupt pin but would still only require two pins overall.  Looking at the wiring and the truth table I see that can't happen.   Oh, well, it still saves an interrupt.   :smiley-lol:   

I've placed an order for a few ICs, etc.,  but don't yet have a delivery date so the XOR portion of this will have to wait a bit.

Do you think this experiment should continue here or, would it be better to open a new thread?
So two neutrinos went into a bar.  Nothing happened.  They were just passing through.

robtillaart

#10
Jan 15, 2018, 07:38 am Last Edit: Jan 15, 2018, 07:46 am by robtillaart
The XOR solution only works with attachInterrupt(f(), CHANGE); not with RISING or FALLING.
If you use RISING (FALLING) you could connect pinA to an IRQ pin. When the intterupt comes you already know it is HIGH (LOW) so you do not need to read it. So you only need to read pinB to know the direction of rotation in the interrupt routine.

Quote
Do you think this experiment should continue here or, would it be better to open a new thread?
As the experiment tries to answer the question of the title I propose to keep it here.
Rob Tillaart

Nederlandse sectie - http://arduino.cc/forum/index.php/board,77.0.html -
(Please do not PM for private consultancy)

Jimmus

Well, there are 3 different banks of pin change interrupts.  If you use INT0 and INT1 for one encoder, you could use one pin in PortB and one in PortC and you wouldn't have to try to figure out which pin changed.

I have had good luck with pin change interrupts and encoders.

wado1971

Well I've decided to take the advice from horace and move to the Mega board which allows me additional interrupt pins.  The Encoder.h library has worked great for me and would rather keep the electronics and program intact.  This will also solve my memory size issue - my current program is using 90% the Uno available flash and I still have some functionality to add.   
Unfortunately this poses a new problem as I have a specific motor library (ClearPath s/d motors) which will need to be modified.  Every solution poses a new problem (challenge) - and I'll be posting separate thread on how best to deal with this library (think it's the use of direct port access in the library)
Thanks for the replies - and good luck with the XOR direction.

dougp

Update:  With the encoder wired as described in post #9 the count reading maxes out at a little over sixteen hundred.  I believe it could go higher but the drill speed is topped out.  It generally varies plus or minus twenty to forty counts at all speeds, no doubt partly due tolerances in the drill speed controller - and me keeping my finger steady!

I've been looking over the interrupt section in the 328P datasheet.  I was hoping for some way to get the state of the pin which caused the interrupt.  Nothing jumps out.  Could it just be as easy a doing a digital read of the port/pin?
So two neutrinos went into a bar.  Nothing happened.  They were just passing through.

Jimmus

@dougp that is the standard way of doing it, and works most of the time.  The problem is that there is a very small but real possibility that the state of that pin will have changed between when it fired the interrupt and when you check it in the ISR.  Due to bounce.  Just keep that in mind and handle the possibility.

Go Up