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:
-
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.
-
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 Especially issue number 1 is of great importance.
Thanks a lot, Garnele