Speed of External Interrupt on SAMD21 w/ Rotary Encoder

With apologies for the format of this post if it is incorrect, I am trying...

I'm trying to use an external interrupt on my SAMD21 (Adafruit ItsyBitsy M0 and Arduino Zero) to gather input from a rotary encoder. Actually, I've used two approaches to getting the input (not at the same time of course); polling inside of loop() and attachInterrupt(). I started with polling, and performance was fine, if not perfect. But polling inside of loop() seems rather amateurish when I feel I should be using an interrupt. So I implemented with an interrupt, too. My problem is that the polling approach is actually better than the interrupt; i.e. polling does a better job capturing the input from the rotary encoder. Why?
How can I fix this? I really want to use the interrupt but it seems to be skipping some of the input. I've looked at some of the code that's out on the internet for programming interrupts at the 'bare metal' level, but frankly that code is way over my head. Is there something simple I can do to increase the frequency of the external interrupt trigger? Here's some code (the program itself is over 10,000 lines long, so I can only post some snips):

setup()

#if USE_EXT_INTERRUPTS
  attachInterrupt( digitalPinToInterrupt( rotaryPin1 ), pollEncoder, LOW );     // setup the interrupts for the rotary encoder
  attachInterrupt( digitalPinToInterrupt( rotaryPin2 ), pollEncoder, LOW );     // polling is more responsive
#endif

loop()

void loop() {                                                                   // executes forever until reset button is pressed or power is interrupted
    #if USE_EXT_INTERRUPTS == 0
      pollEncoder();                                                            // tried to use interrupts for the encoder but the results were inconsistent
    #endif
  ProcessEvent();                                                               // process any events that might have occurred.
}

pollEncoder()

void pollEncoder() {
  change = 0;
  curModeA = digitalRead(rotaryPin1);                                           // compare the four possible states to figure out what has happened
  curModeB = digitalRead(rotaryPin2);                                           // then increment/decrement the current encoder's position
  if (curModeA != lastModeA) {
    if (curModeA == LOW) {
      if (curModeB == LOW) {
        encPos--;
      } else {
        encPos++;
      }
    } else {
      if (curModeB == LOW) {
        encPos++;
      } else {
        encPos--;
      }
    }
  }
  if (curModeB != lastModeB) {
    if (curModeB == LOW) {
      if (curModeA == LOW) {
        encPos++;
      } else {
        encPos--;
      }
    } else {
      if (curModeA == LOW) {
        encPos--;
      } else {
        encPos++;
      }
    }
  }
  lastModeA = curModeA;                                                         // set the current pin modes (HIGH/LOW) to be the last know pin modes
  lastModeB = curModeB;                                                         // for the next loop to compare to
  if (encPos < encPosLast) {                                                    // if this encoder's position changed, flag the change variable so we
    if (encPos%4 == 0) {                                                        // know about it later
      change = 1;
      event = EVENT_ROTATE_CW;
    }
  } else if (encPos > encPosLast) {
    if (encPos%4 == 0) {
      change = 1;
      event = EVENT_ROTATE_CC;
    }
  }
  
  if (change == 1) {
    encPosLast = encPos;                                                        // if an encoder has changed set encPosLast
  }
}

It occurs to me as I post this that I should have two different pollEncoder methods (and maybe rename them); e.g. rotaryPin1Triggered() and rotaryPin2Triggered(), and that there's no need to read the input again from within the interrupt routine...perhaps this is part of my problem...

Try the interrupts with the mode CHANGE.

https://www.arduino.cc/reference/en/language/functions/external-interrupts/attachinterrupt/

Here's a doc page for a simulated rotary encoder. There is example code that uses but one interrupt and is generally simpler than your polling turned ISR code.

See if you can extract and handle it that way. There are links on the page to the running simulation.

a7

1 Like

Take a look at this: https://github.com/gfvalvo/NewEncoder

1 Like

There are VERY few good uses for an interrupt mode of LOW, or HIGH, as they will cause continuous interrupts to occur whenever the input is in the specified state, wasting lots and lots of CPU cycles for no good reason. What you pribably WANT is interrupts on any CHANGE of the input, or, use either RISING or FALLING to interrupt on only one edge of the input signal.

Yes, I believe this suggestion solved the problem. Thank you for the advice and for pointing out the problem.

Best,
Dan

Thanks RayLivingston, that did the trick.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.