Using avr external interrupts to detect changes on two channels

Hi all,

Greetings from a new forum member.

I am trying to write some demonstration code which shows how to use interrupts on the Arduino Mega to detect (and count) changes in digital value on pins connected to the two channels of an incremental encoder. One purpose of this code is to show how this can be done either “the Arduino way” or “the low-level way”. I can get this to work fine using the Arduino function attachInterrupt, but when I try to use the low-level coding of the interrupts etc. using registers, I encounter two problems:

  • The interrupts do not get triggered as expected
  • Serial printing sometimes seems not to work correctly, specifically some numerical fields are printed as zeros (though I cannot reproduce this issue here with the present “toy” program).

I’m not attaching the whole sketch as it is rather lengthy and covers various things that don’t concern us now, but here is a “toy” sketch that (in theory):

  • Triggers an ISR whenever a pin state change occurs on either pin 2 or 3 of the Arduino Mega
  • Within the ISR, increments a simple counter every time this happens
  • Within the ISR, toggles pin 13 to show that the ISR has run.

Here is the code:

// #define ARDUINO_INTERRUPTS // Use Arduino interrupt approach if set, otherwise AVR registers
#define LEDpin 13

bool state = false;
long count = 0;

void setup() {
  // Input pins connected to incremental encoder
  pinMode(2,INPUT);
  pinMode(3,INPUT);
  Serial.begin(9600);
  #ifdef ARDUINO_INTERRUPTS
    // Use Arduino approach to attaching interrupts to routine here called myISR
    attachInterrupt(digitalPinToInterrupt(2), myISR, CHANGE);
    attachInterrupt(digitalPinToInterrupt(3), myISR, CHANGE);
  #else
    // Try to do roughly the same thing for ISR using atmega 2560 registers
    EIMSK &= ~(1 << INT0);               /* disable INT0 while we configure */
    EIMSK &= ~(1 << INT1);               /* disable INT1 while we configure */  
    EICRA = 0;
    EICRA |= (1 << ISC00);                /* trigger INT0 when pin 2 changes */
    EICRA |= (1 << ISC10);                /* trigger INT1 when pin 3 changes */
    EIMSK |= (1 << INT0);                 /* enable INT0 */
    EIMSK |= (1 << INT1);                 /* enable INT1 */
    sei();   
  #endif
  pinMode(LEDpin,OUTPUT);
}

void loop() {
  Serial.println(count);
  delay (1000);
}

#ifdef ARDUINO_INTERRUPTS
  void myISR()
#else
  ISR(INT0_vect)
#endif
{
   // Increment a simple counter.  In practice this involves a state 
   // machine to decode the quadrature signal on pins 2 and 3 but let's keep
   // things simple here.
   count++;

   // Toggle the LED to show we have been here
   state = !state;
   digitalWrite(LEDpin, state);
}

#ifndef ARDUINO_INTERRUPTS
  ISR(INT1_vect, ISR_ALIASOF(INT0_vect));
#endif

I have set a preprocessor constant in the first line to switch between use of attachInterrupt or direct use of ISR. As I indicated, the former works fine, the latter doesn’t.

I am using an Arduino Mega 2560 (bought a few days ago, and not a clone), and am using an encoder with appropriate pull-up resistors connected to pins 2 and 3. It works fine in other applications and when the #define on the first line is set.

Please can anyone tell me what I am doing wrong? Apologies if I am making a “schoolkid error” or have missed a post on this elsewhere, but I have looked on numerous forums and tutorials and have got nowhere.

Many thanks indeed!

For reading encoders, I would use PCINTs, not the INTn pins - using PCINTs is convenient since you're reading two pins at a time, and even more so when extended to multiple encoders.

Lines 803~861 show how I use PCINTs on a m328p to read two encoders simultaneously to control two menus.

attachInterrupt is an abomination IMO; I wouldn't touch it with a 39.5 foot pole.

Thanks DrAzzy, I'll give this a go. I'd got the impression that INTn pins was quicker and also I'd tried the PCINTn approach without success, but I've had a look at your code and I'll have a go tomorrow following your approach (I suspect I had multiple misunderstandings). Many thanks anyway and let's see how it goes.

Thanks again DrAzzy, I got it working as you suggested - very much appreciated. But in order to do so I had to change the input pins from 2 and 3 to (for example) 14 and 15 as PCINTx is not available on 2 and 3 on the Arduino Mega 2560 AFAIK.

And now I can see why my original code didn't work - the code was basically fine but INT0 and INT1 are not connected to digital pins 2 and 3 on the Arduino Mega (they are on the Uno). Once I connected the encoder signals to 21 and 20 it worked fine. I believe the correct interrupts for pins 2 and 3 are INT4 and INT5 and that also works fine when I put the encoder back to those pins. Sorry for missing this point.

Thanks again to DrAzzy and anyone else who was looking at this.