PinChangeInt library- To attach interrupts to multiple Arduino (Uno/Mega) pins

DuaneB:
I assume its a memory leak as my understanding is that delete does not free memory in Arduino and the current implementation of detach seems to assume that delete works.

Yes, I ran a test and noticed that my memory got gobbled up pretty quickly. Drat!!! ...The problem is not merely in delete(), because delete just calls free(). The problem is that free() doesn't seem to work.

Anyway, regardless of that I like your idea of having detachInterrupt() simply disable the pin from interrupting, rather than using the delete() operator. I am working on it doing just that.

attachInterrupt() will return -1 on error, 0 if the pin has already been added to the list (the pin will be enabled if it is called and had previously been created already), and 1 if a new pin is created.

Perfect, I will post a link to this topic from the two related threads so that the ops are aware of this and can follow the progress.

Thanks

Duane B

DuaneB:
In both topics, the ops would like to detach pin change interrupts and later reattach them to enter and exit autonomous and RC Modes.

Is that done in the context of the interrupt handler?


PinChangeInt
---- RELEASE NOTES ---


Version 2.13 (beta) Mon Nov 12 09:33:06 CST 2012
SIGNIFICANT BUGFIX release! Significant changes:

  1. PCintPort::curr bug. Interrupts that occur rapidly will likely not get serviced properly by PCint().
  2. PCint() interrupt handler optimization.
  3. PCIFR port bit set bug fix.
  4. Many static variables added for debugging; used only when #define PINMODE is on.
  5. detachInterrupt() no longer does a delete(), since that wasn't working anyway. When you detachInterrupt(), the PORT just disables interrupts for that pin; the PCintPin object remains in memory and in the linked list of pins (possibly slowing down your interrupts a couple of micros). You can reenable a detached interrupt- but you must do it within the PinChangeInt library (would anyone ever enable an interrupt on a pin, then disable it, then have need to reenable it but not using the library?).
  6. attachInterrupt() now returns a uint8_t value: 1 on successful attach, 0 on successful attach but using an already-enabled pin, and -1 if the new() operator failed to create a PCintPin object.
    Also, modified these release notes.

Details:

Uncovered a nasty bug, thanks to robtillaart on the Arduino Forums and Andre' Franken who posted to the PinChangeInt groups. This bug was introduced by me when I assigned PCintPort::curr early in the interrupt handler:

ISR(PCINT0_vect) {
    PCintPort::curr = portB.portInputReg;
    portB.PCint();
}

Later, in the interrupt handler PCint(), we loop as long as PCIFR indicates a new interrupt wants to be triggered, provided DISABLE_PCINT_MULTI_SERVICE is not defined (it is not by default):

#ifndef DISABLE_PCINT_MULTI_SERVICE
        pcifr = PCIFR & PCICRbit;
        PCIFR = pcifr;  // clear the interrupt if we will process it (no effect if bit is zero)
} while(pcifr);
#endif

...Well. Problem is, if a pin pops up and causes the PCIFR to change, we have to reread the port and look at how it is now! I wasn't doing that before, so if a new interrupt appeared while I was still servicing the old one, odd behavior would take place. For example, an interrupt would register but then the userFunc would not be called upon to service it. The code needs to be:

        pcifr = PCIFR & PCICRbit;
        PCIFR = pcifr;  // clear the interrupt if we will process it (no effect if bit is zero)
        PCintPort::curr=portInputReg; // ...Fixed in 2.11beta.
} while(pcifr);

Also, made the interrupt handler even faster with an optimization from robtillaart to take out the checks for changed pins from the while() loop that steps through the pins:

uint8_t changedPins = (PCintPort::curr ^ lastPinView) &
                      ((portRisingPins & PCintPort::curr ) | ( portFallingPins & ~PCintPort::curr ));

...This speedup is offset by more changes in the PCint() handler, which now looks like the following; there are two bug fixes:

FIX 1: sbi(PCIFR, PCICRbit);
FIX 2: ...the aforementioned PCintPort::curr=portInputReg;
Here's the new code:

    #ifndef DISABLE_PCINT_MULTI_SERVICE
        pcifr = PCIFR & PCICRbit;
        if (pcifr) {
        //if (PCIFR & PCICRbit) { // believe it or not, this adds .6 micros
            sbi(PCIFR, PCICRbit); // This was a BUG: PCIFR = pcifr  ...And here is the fix.
            #ifdef PINMODE
            PCintPort::pcint_multi++;
            if (PCIFR & PCICRbit) PCintPort::PCIFRbug=1; // PCIFR & PCICRbit should ALWAYS be 0 here!
            #endif
            PCintPort::curr=portInputReg; // ...Fixed in 2.11beta.
            goto loop;  // A goto!!! Don't want to look at the portInputReg gratuitously, so the while() will not do.
        }
    #endif

Also I added a lot of variables for debugging when PINMODE is defined, for routing out nasty bugs. I may need them in the future... :frowning:

Finally, I am not putting newlines in this commentary so I can make it easier to paste online.

BTW, I am still testing this release but I wanted it to get out there because I uncovered some nasty bugs. They made my interrupts unreliable! So it's important if you use this code, to realize that the new version should work better.

I realized that about 5-10% of my interrupts were missed due to my switch bounce; some of the bounces happen only 50 microseconds apart or so. And my code was not processing them properly. With this code, I notice that my switches are more reliable.

I also notice that I am not getting the speed bump that I hoped to get, and some of my bugfixes actually introduce a few extra microseconds of processing. This is disappointing and I hope to find them if I can.

I already thought of a bug in v. 2.13beta. If you detach an interrupt and attach it again, and give it a new user function, it will not attach the new user function- it will use the first user function that you attached.

However you are able to change the mode at this point (RISING, FALLING, or CHANGE).

Version 2.15beta is out now, and I allow for attaching a new user function. I don't know if anyone would really do that, but there should be no nasty surprises in the code, and attachInterrupt() will thus do exactly what you tell it to do in the argument list.

URL: Google Code Archive - Long-term storage for Google Code Project Hosting.

  1. goto loop: ? why not use break?

addpcint

	if (firstPin != NULL) {
		tmp=firstPin;
		if (tmp->arduinoPin == arduinoPin) { enable(tmp, userFunc, mode); return(0); }
		while (tmp->next != NULL) {
			tmp=tmp->next;
			if (tmp->arduinoPin == arduinoPin)  { enable(tmp, userFunc, mode); return(0); }
		};
	}

shorter == smaller footprint? faster?

	if (firstPin != NULL) {
		tmp=firstPin;
                do {
        		if (tmp->arduinoPin == arduinoPin) { enable(tmp, userFunc, mode); return(0);
                        tmp=tmp->next;
                } while (tmp != NULL);
	}

Thanks for the input. This saved 12 bytes; I haven't measured the speed:

    if (firstPin != NULL) {
        tmp=firstPin;
        do {
            if (tmp->arduinoPin == arduinoPin) { enable(tmp, userFunc, mode); return(0); }
            if (tmp->next == NULL) break;
            tmp=tmp->next;
        } while (true);
    }

I have also converted the goto to a do/while/break situation. No change to interrupt speed, but I guess it looks better :-).

                } while (tmp != NULL);

This won't work because tmp will become null, which will exit us from the loop.

I need tmp->next later in the code, therefore I need the last non-null value of tmp.

oops,
I figured that out too and you replied while I removed my previous post,

Version 2.19beta has been released. The library now includes Sanguino support. There have been a number of recent bugfixes, so be sure to look at the Release Notes. If you have any library prior to version 2.17beta, it is highly recommended that you upgrade to 2.17beta or later.

hey,

I like to use the PINCHANGE Library with the Arduino DUE.
But I still get this failure:

In file included from FirstTask.ino:24:
.....\Arduino\libraries\PinChangeInt/PinChangeInt.h:103: fatal error: new.h: No such file or directory
compilation terminated.

Maybe some know this failure?

I need to use the pinchange lib because I need to know of many pins, which pin has changed without using for every pin a own function like this:

     attachInterrupt(1, action1, HIGH);
attachInterrupt(2, action2, HIGH);

Maybe someone knows also an alternative solution :slight_smile:

(very little Due experience)
you could try to uncomment it , and see if it is needed for the DUE
The DUe is an ARM processor while the lib was originally written for the AVR.

just a thought!

MorgKrig:
hey,

I like to use the PINCHANGE Library with the Arduino DUE.
But I still get this failure:

In file included from FirstTask.ino:24:

.....\Arduino\libraries\PinChangeInt/PinChangeInt.h:103: fatal error: new.h: No such file or directory
compilation terminated.



Maybe some know this failure?

I need to use the pinchange lib because I need to know of many pins, which pin has changed without using for every pin a own function like this:


attachInterrupt(1, action1, HIGH);
attachInterrupt(2, action2, HIGH);




Maybe someone knows also an alternative solution :-)

Not sure why you would need that.
The attach interrupt doc at http://arduino.cc/en/Reference/AttachInterrupt clearly states:

The Arduino Due board has powerful interrupt capabilities that allows you to attach an interrupt function on all available pins. You can directly specify the pin number in attachInterrupt().

The overhead of creating a function for each pin is probably nothing compared to linking and porting the pinchangeint lib.
Not that I would care about the size of the program or data when writing code for the due.
Best regards
Jantje

After some time, I have updated the library. There is a significant fix submitted by jrhelbert which makes the two Port J pins work properly with the Mega. See Google Code Archive - Long-term storage for Google Code Project Hosting..

Downloads available at Service End for Bintray, JCenter, GoCenter, and ChartCenter | JFrog

Bleeding edge source available at GitHub - GreyGnome/PinChangeInt: Pin Change Interrupt library for the Arduino

Note: I don't have a Mega, so I didn't test ports J and K. If you use it, please let me know if it works. Thanks.

Maybe somebody can help me?
I have this code, it is used to read a rotary encoder with an Arduino Uno. I want to make this work on an Arduino Mega.

  PORTC |= _BV(PORTC0) | _BV(PORTC1) | _BV(PORTC2) | _BV(PORTC3);   // enable pullup for pins
  PCMSK1 = _BV(PCINT8) | _BV(PCINT9) | _BV(PCINT10) | _BV(PCINT11); // enable button pin change interrupt A0 A1 A2 A3
  PCICR = _BV(PCIE1);

I can not find any help about converting this to a working Mega sketch. Can you help me?

First you have to study the Mega pinout and figure out which pins and ports are available to you. Have you done this?

It looks like you have two rotary encoders (4 pins total). Is that correct?

I tried to use this lib, but sadly doesn't work as expected, so moved to http://www.geertlangereis.nl/Electronics/Pin_Change_Interrupts/PinChange_en.html . It works fine and I got all Analog pins on my Mega 2560 working as interrupt. but there I got other problem.... I lost Serial1, Serial2, Serial3.
None other than Serial0 Serial3 works with PCINT1 to PCINT23, with the ATMEL datasheet for 2560 it has stated that Serial3 (ATMEL's RXD3/TXD3) are on PCINT9 and PCINT10 , so there are chances that Serial3 wont work with manipulated PCICR setting. Yet we have 2 other Serial Ports (Serial1 and Serial2) which haven't specified on any PCINT pins.
Please guide me about this.
This is where I Initialise Interrupts

[code]void InitialiseInterrupt(){
  cli();		// switch interrupts off while messing with their settings  

  PCICR =0x06;          // Enable PCIE2 and PCIE1
  PCMSK1 = 0b11111111; // Enabling A0 to A7 pins on Mega 2560 (PCINT23:16)
  PCMSK2 = 0b11111111; // Enabling A8 to A15 pins on Mega 2560(PCINT15:8)
  sei();		// turn interrupts back on
}

also as per the ATMEL's pdf you dont have any PCINT on ADC0...ADC7 pins. yet those are working

the full code is

#include <TimerOne.h>
#include <Encoder.h>
#include <SPI.h>

Encoder xenc(18,19);
int iPin[] = {54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69}; //{"A0","A1","A2","A3","A4","A5","A6","A7","A8","A9","A10","A11","A12","A13","A14","A15"};
int iLast[16];
void setup()
{
  SPI.begin();
  Serial.begin(9600);
  Serial.println("Boe");
  Serial2.begin(9600);
  InitialiseIO();
  InitialiseInterrupt();
Timer1.initialize(100000);
Timer1.attachInterrupt(Timer1_tick);
}
void Timer1_tick(){
  float z = xenc.read();
digitalWrite(13,!digitalRead(13));
SPI.transfer(z);
}
void serialEvent(){
Serial2.print("A");
}
void loop() {
  /* Nothing to do: the program jumps automatically to Interrupt Service Routine "blink"
   in case of a hardware interrupt  */
}  

void InitialiseIO(){
  pinMode(13,OUTPUT);
  digitalWrite(13,LOW);
  pinMode(A0, INPUT);	   // Pin A0 is input to which a switch is connected
  digitalWrite(A0, HIGH);   // Configure internal pull-up resistor
  pinMode(A1, INPUT);	   // Pin A1 is input to which a switch is connected
  digitalWrite(A1, HIGH);   // Configure internal pull-up resistor
  pinMode(A2, INPUT);	   // Pin A2 is input to which a switch is connected
  digitalWrite(A2, HIGH);   // Configure internal pull-up resistor
   pinMode(A3, INPUT);	   // Pin A2 is input to which a switch is connected
  digitalWrite(A3, HIGH);   // Configure internal pull-up resistor
   pinMode(A4, INPUT);	   // Pin A2 is input to which a switch is connected
  digitalWrite(A4, HIGH);   // Configure internal pull-up resistor
   pinMode(A5, INPUT);	   // Pin A2 is input to which a switch is connected
  digitalWrite(A5, HIGH);   // Configure internal pull-up resistor
   pinMode(A6, INPUT);	   // Pin A2 is input to which a switch is connected
  digitalWrite(A6, HIGH);   // Configure internal pull-up resistor
   pinMode(A7, INPUT);	   // Pin A2 is input to which a switch is connected
  digitalWrite(A7, HIGH);   // Configure internal pull-up resistor
  pinMode(A8, INPUT);	   // Pin A0 is input to which a switch is connected
  digitalWrite(A8, HIGH);   // Configure internal pull-up resistor
  pinMode(A9, INPUT);	   // Pin A1 is input to which a switch is connected
  digitalWrite(A9, HIGH);   // Configure internal pull-up resistor
  pinMode(A10, INPUT);	   // Pin A2 is input to which a switch is connected
  digitalWrite(A10, HIGH);   // Configure internal pull-up resistor
   pinMode(A11, INPUT);	   // Pin A2 is input to which a switch is connected
  digitalWrite(A11, HIGH);   // Configure internal pull-up resistor
   pinMode(A12, INPUT);	   // Pin A2 is input to which a switch is connected
  digitalWrite(A12, HIGH);   // Configure internal pull-up resistor
   pinMode(A13, INPUT);	   // Pin A2 is input to which a switch is connected
  digitalWrite(A13, HIGH);   // Configure internal pull-up resistor
   pinMode(A14, INPUT);	   // Pin A2 is input to which a switch is connected
  digitalWrite(A14, HIGH);   // Configure internal pull-up resistor
   pinMode(A15, INPUT);	   // Pin A2 is input to which a switch is connected
  digitalWrite(A15, HIGH);   // Configure internal pull-up resistor
  
}

void InitialiseInterrupt(){
  cli();		// switch interrupts off while messing with their settings  

  PCICR =0x06;          // Enable PCINT1 interrupt
  PCMSK1 = 0b11111111;
  PCMSK2 = 0b11111111;
  sei();		// turn interrupts back on
}
/*
ISR(PCINT1_vect) {    // Interrupt service routine. Every single PCINT8..14 (=ADC0..5) change
            // will generate an interrupt: but this will always be the same interrupt routine
  if (digitalRead(A0)==0)  Serial.println("A0");
  if (digitalRead(A1)==0)  Serial.println("A1");
  if (digitalRead(A2)==0)  Serial.println("A2");
   if (digitalRead(A3)==0)  Serial.println("A3");
  if (digitalRead(A4)==0)  Serial.println("A4");
  if (digitalRead(A5)==0)  Serial.println("A5");
   if (digitalRead(A6)==0)  Serial.println("A6");
  if (digitalRead(A7)==0)  Serial.println("A7");

}
ISR(PCINT2_vect) {    // Interrupt service routine. Every single PCINT8..14 (=ADC0..5) change
            // will generate an interrupt: but this will always be the same interrupt routine
  if (digitalRead(A8)==0)  Serial.println("A8");
  if (digitalRead(A9)==0)  Serial.println("A9");
  if (digitalRead(A10)==0)  Serial.println("A10");
   if (digitalRead(A11)==0)  Serial.println("A11");
  if (digitalRead(A12)==0)  Serial.println("A12");
  if (digitalRead(A13)==0)  Serial.println("A13");
   if (digitalRead(A14)==0)  Serial.println("A14");
  if (digitalRead(A15)==0)  Serial.println("A15");

}*/
ISR(PCINT1_vect) {
  for (int q = 0; q <= 7; q++) {
    int u = digitalRead(iPin[q]);
    String OP = "IP";

    if (iLast[q] != u) {
      OP += q;
      OP += u;
      OP += "!";
      Serial.print(OP);
    }
    iLast[q] = u;
  }

}
ISR(PCINT2_vect) {
  for (int q = 8; q <= 15; q++) {
    int u = digitalRead(iPin[q]);
    String OP = "IP";

    if (iLast[q] != u) {
      OP += q;
      OP += u;
      OP += "!";
      Serial.print(OP);
    }
    iLast[q] = u;
  }

}

[/code]