Additional external interrupt pins on Arduino Mega

Dear Community

I use an Arduino mega to run 6 DC motors and would like to attach 6 encoders, each if which sends its signal over 2 pins. I would like to attach these all to interrupt on change pins as I would like to automatically synchronize the six motors (to which legs will be attached).

As you may know, the Arduino mega only supports 6 external interrupt pins even though the ATmega 1280 supports 23 PCINT pins. Hence there must be a way to attach at least some of the additional as interrupt on change pins. In the playground I found a code snippet written for the ATmega 168, but wasn't able to find anything for the ATmega 1280.

Starting out from the code linked above I tried to come up with a snippet to attach Analog in pin 8-15 (PCINT 16-23) as interrupt on change pins. To test the software, I included quite some serial outputs and connected the pin to check with I/O 40 which changes it's value every 5 seconds (approximately).

#include "pins_arduino.h"
int counter = 0;
volatile uint8_t *port_to_pcmask[] = {
  &PCMSK0, //0
  0,
  &PCMSK0, // 2 ---> PCINT 0-7
  0,
  0,
  0,
  0,
  0,
  &PCMSK1, // 8 ---> PCINT 9-15
  &PCMSK2, // 9 ---> PCINT 16-23
  0,
  &PCMSK2  // 11 ---> Analog Pins PCINT 16-23
};

typedef void (*voidFuncPtr)(void);

volatile static voidFuncPtr PCintFunc[24] = { 
  NULL };

volatile static uint8_t PCintLast[3];

/*
 * attach an interrupt to a specific pin using pin change interrupts.
 * First version only supports CHANGE mode.
 */
 void PCattachInterrupt(uint8_t pin, void (*userFunc)(void), int mode) {
  uint8_t bit = digitalPinToBitMask(pin);
  uint8_t port = digitalPinToPort(pin);
  uint8_t slot;
  volatile uint8_t *pcmask;

  if (mode != CHANGE) {
    return;
  }
  // map pin to PCIR register
  if (port == NOT_A_PORT) {
    return;
  } 
  else {
   // port -= 2;
    pcmask = port_to_pcmask[port];
  }
  slot = port * 8 + (pin % 8);
  PCintFunc[slot] = userFunc;
  // set the mask
  *pcmask |= bit;
  // enable the interrupt
  PCICR |= 0x01 << port;
  Serial.print("START ANALOG Pin:");
  Serial.print(pin, DEC);
  Serial.print(" Port:");
  Serial.print(port, DEC);
  Serial.print(" Bit:");
  Serial.print(bit, DEC);
  Serial.print(" Slot:");
  Serial.println(slot, DEC);
}


// common code for isr handler. "port" is the PCINT number.
// there isn't really a good way to back-map ports and masks to pins.
static void PCint(uint8_t port) {
  uint8_t bit;
  uint8_t curr;
  uint8_t mask;
  uint8_t pin;
  Serial.print("Port:");
  Serial.println(port, DEC);
  // get the pin states for the indicated port.
  curr = *portInputRegister(11); //port+2
  if (port+2 == 4)
    mask = 10000; 
  else
    mask = curr ^ PCintLast[port];
  Serial.println(curr, BIN);
  Serial.println(PCintLast[port], BIN);
  Serial.println(mask, BIN);
  PCintLast[port] = curr;
  // mask is pins that have changed. screen out non pcint pins.
  if ((mask &= *port_to_pcmask[port]) == 0) {
    Serial.println("Mask caught");
    return;
  }
  // mask is pcint pins that have changed.
  for (uint8_t i=0; i < 8; i++) {
    bit = 0x01 << i;
    if (bit & mask) {
      pin = port * 8 + i - 2;
      if (PCintFunc[pin] != NULL) {
        PCintFunc[pin]();
        Serial.println("Personal Function called");
      }
      Serial.print("PIN:");
      Serial.print(pin, DEC);
      Serial.println("Interrupt fired");
    }
  }
}


SIGNAL(PCINT0_vect) {
  PCint(0);
}
SIGNAL(PCINT1_vect) {
  PCint(1);
}
SIGNAL(PCINT2_vect) {
  PCint(2);
}

volatile long ticktocks = 0;
long i = 0;

void tick(void) {
  ticktocks++;
}

void tock(void) {
  ticktocks--;
}

void setup()
{
  Serial.begin(115200);
  pinMode(40, OUTPUT);
  digitalWrite(40, HIGH);
  pinMode(10, INPUT);
  pinMode(62, INPUT);
  pinMode(63, INPUT);
  pinMode(64, INPUT);
  pinMode(65, INPUT);
  pinMode(2, INPUT);
  delay(3000);
  Serial.println("START PC ATTACH");
  //PCattachInterrupt(62, tick, CHANGE);
  PCattachInterrupt(10, tick, CHANGE);
  PCattachInterrupt(63, tick, CHANGE);
  PCattachInterrupt(64, tick, CHANGE);
  PCattachInterrupt(65, tick, CHANGE);
  attachInterrupt(0, tick, CHANGE);
}

void loop() {
  i++;
  if (counter == 5) {
      if (digitalRead(40) == HIGH) {
         digitalWrite(40, LOW);
         Serial.println("PIN LOW");
      } else {
         digitalWrite(40, HIGH);
         Serial.println("PIN HIGH");
      }
      counter = 0;      
  }
  delay(1000);
  Serial.print(i, DEC);
  Serial.print(" ");
  Serial.println(ticktocks);
  counter ++;
}

If I run this code for 15 duty cycles and connect Analog in pin 9 with the I/O 40 I get the following printout:

START PC ATTACH
START ANALOG Pin:10 Port:2 Bit:16 Slot:18
START ANALOG Pin:63 Port:11 Bit:2 Slot:95
START ANALOG Pin:64 Port:11 Bit:4 Slot:88
START ANALOG Pin:65 Port:11 Bit:8 Slot:89
1 0
2 0
3 0
4 0
5 0
Port:2
0
0
10000
Personal Function called
PIN:18Interrupt fired
PIN LOW
6 1
7 1
8 1
9 1
10 1
Port:2
111
0
10000
Personal Function called
PIN:18Interrupt fired
PIN HIGH
Port:2
10
111
10000
Personal Function called
PIN:18Interrupt fired
11 3
12 3
13 3
14 3
15 3
Port:2
0
10
10000
Personal Function called
PIN:18Interrupt fired
PIN LOW
16 4
17 4

So where are the issues? Well, there are several:

  1. If the pin goes from LOW to HIGH 3 instead of only one interrupt pins fire. I believe this is due to my manually set mask (10000) which always creates an "all changed" statement. Yet I have not a lot of experience in such programming and I have no clue how to actually get the mask, the ATmega 168 code doesn't work for the ATmega 1280.

  2. There is something weird regarding the ports/function: The code only works if I attach a PWM pin (eg. 10, which is PCINT 4) to the interrupt function before I attach any of the analog in. Why is that and why do the analog pins only work if I escape the "port -= 2;" line even though the PWM interrupt pins will only work if it is there?

It would be great if you could help me out, I have no clue what I'm missing and I have already invested about 12 hours trying to get this running :frowning: Especially issue number 1 is of great importance.

Thanks a lot, Garnele

Hi there

And here is the link to the ATmega 168 sourcecode: Arduino Playground - PcInt

Best, Garnele

There is some code in this thread that uses a single interrupt per encoder that may simplify what you want to do: http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1236368733/21#21

Hi mem

Your idea would work if my encode would work differently: It basically sends a two bit signal of its current state. Either 00, 01, 11 or 10. If I want to time it accurately I will have to connect both inputs to an interrupt on change pin as every quarter turn the signal it "sends" will change. You see?

But thanks for your input anyways, it is always helpful to see how others have done it :slight_smile:

Best, Garnele

Use this circuit to change your signals into step and direction:-

Hi there

I'm sorry I might be a little slow, does your post has to do with the issue I brought up in the first post or is it just a general advice? Do I get it right that the numbers 1-6 are I/O pins on the Arduino? If not, how do you read the step and the direction signal?

Thank you very much anyway, the sketch looks interesting.

All the best, Garnele

The numbers refer to pins on a 74LS74 flip-flop. Mike's circuit is a way to convert the output of your encoder into something that can be detected with a single interrupt per encoder.

BTW, do you have a link to the encoder you are using?

Hi

Sorry for the stupid question, just looked up the 74LS74 now it's clear to me. That circuit gets better the longer I look at it, might save me a lot of time. I'll get the link to the encoder for you, don't have it at hand right now :slight_smile:

Best, Garnele

Hi there

I finally found the datasheet of my encoder, sorry for the delay. You can find it here: http://www.bourns.com/data/global/pdfs/3315.pdf

What I don't quite understand though is, why are there only 3 pins? Of course two are output, is the third a 5V input? Why is there no ground?

Thanks a lot, Garnele

that sensor produces a quadrature output ( the datasheet shows a user supplied external circuit for binary output).

Pins A and B are the two quadrature output and pin C is common (Ground).

This should work with the circuits posted in the playground (connect A and B to the input pins, C to Gnd). Try it and see, but if you get noise on the signal (jumpy counts) then need to add some external debouncing logic.