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

Version 1.4 is now available at Google Code Archive - Long-term storage for Google Code Project Hosting.

The PinChangeInt library implements Pin Change interrupts for the Arduino environment. It is a drop in replacement for the PCint library.

What are Pin Change interrupts? The ATmega328p processor at the heart of the Arduino has two different kinds of interrupts: “external”, and “pin change”. There are only two external interrupt pins, INT0 and INT1, and they are mapped to Arduino pins 2 and 3. These interrupts can be set to trigger on RISING or FALLING signal edges, or on low level. The triggers are interpreted by hardware, and the interrupt is very fast. On the other hand there are only 2 such pins on the ATmega328p in the Arduino Uno and Duemilanove.

On the other hand the pin change interrupts can be enabled on any or all of the Arduino's signal pins. They are triggered equally on RISING or FALLING signal edges, so it is up to the interrupt code to set the proper pins to receive interrupts, to determine what happened (which pin? ...did the signal rise, or fall?), and to handle it properly. Furthermore, the pin change interrupts are grouped into 3 “port”s on the MCU, so there are only 3 interrupt vectors (subroutines) for the entire body of 20 pins. This makes the job of resolving the action on a single interrupt even more complicated. The interrupt routine should be fast, but complication is the enemy of speed. The PinChangeInt library is designed to handle the Arduino's pin change interrupts as quickly and reasonably as possible (without using Assembly, that is).

Version 1.4 puts all the code in the .h file, to allow the programmer to configure it through their sketch for minimum ram usage. In earlier versions, it must be configured through the PinChangeIntConfig header file instead.

See the Wiki pages for more information.

I just posted version 1.5. From the notes:

Version 1.5 Thu Feb  2 18:09:49 CST 2012
Added the PCintPort::pinState static variable to allow the programmer to query the state
of the pin at the time of interrupt.

Added two new #defines, NO_PIN_STATE and NO_PIN_NUMBER so as to reduce the code
size by 20-50 bytes,and to speed up the interrupt routine slightly by declaring that you
don't care if the static variables PCintPort::pinState and/or PCintPort::arduinoPin are set
and made available to your interrupt routine.
// #define NO_PIN_STATE        // to indicate that you don't need the pinState
// #define NO_PIN_NUMBER       // to indicate that you don't need the arduinoPin

...aaaand, version 1.51 is out. Fixes a bug in 1.5, that caused the pinState to be incorrectly reported :cold_sweat: . It appears to work correctly now. Apologies for the messup.

I created another bug :-(. Version 1.6 is out; includes a couple of enhancements. The changes are these:

Set the value of the current register settings, first thing in each ISR; e.g.,
ISR(PCINT0_vect) {
       PCintPort::curr = portB.portInputReg; // version 1.6
        ...
 ...instead of at the beginning of the PCintPort::PCint() static method.
This means that the port is read closer to the beginning of the interrupt,
and may be slightly more accurate- only by a couple of microseconds, really,
but it's a cheap win.

Fixed a bug- a BUG!- in the attachInterrupt() and detachInterrupt() methods.
I didn't have breaks in my switch statements!  Augh!  What am I, a (UNIX) shell
programmer?  ...Uh, generally, yes...

Added the PINMODE define and the PCintPort::pinmode variable.  See the Usage
Wiki page on the website for more info.

Version 1.70beta is out as of yesterday. Includes the following enhancements; from the Release Notes:


Happy Birthday to me! Happy Birthday tooooo meee! Happy Birthday, Dear Meeeeee-eeeee! Happy Birthday to me!

Yes, it is on this auspicious occasion of mine (and Elizabeth Taylor's [R.I.P.]) birthday that I humbly submit to you, gracious Arduino PinChangeInt user, version 1.70beta of the PinChangeInt library. I hope you enjoy it.

New in this release:

The PinChangeIntTest sketch was created, which can be found in the Examples directory. It exercises:

  • Two interrupting pins, one on each of the Arduino's PORTs.
  • detachInterrupt().
    Hopefully this will help avoid the embarrassing bugs that I have heretofore missed.

As well, it has come to this author's (GreyGnome) attention that the Serial class in Arduino 1.0 uses an interrupt that, if you attempt to print from an interrupt (which is what I was doing in my tests) can easily lock up the Arduino. So I have taken SigurðurOrn's excellent ByteBuffer library and modified it for my own nefarious purposes. (see http://siggiorn.com/?p=460). The zipfile comes complete with the ByteBuffer library; see the ByteBuffer/ByteBuffer.h file for a list of changes, and see the PinChangeIntTest sketch for a usage scenario. Now the (interrupt-less and) relatively fast operation of filling a circular buffer is used in the interrupt routines. The buffer is then printed from loop().

The library has been modified so it can be used in other libraries, such as my AdaEncoder library (http://code.google.com/p/adaencoder/). When #include'd by another library you should #define the LIBCALL_PINCHANGEINT macro. For example:

        #ifndef PinChangeInt_h
        #define LIBCALL_PINCHANGEINT
        #include "../PinChangeInt/PinChangeInt.h"
        #endif

This is necessary because the IDE compiles both your sketch and the .cpp file of your library, and the .h file is included in both places. But since the .h file actually contains the code, any variable or function definitions would occur twice and cause compilation errors- unless #ifdef'ed out.

Version 1.71beta is now available for upload. From the Release Notes:

Code reordering: Starting in version 1.3 of this library, I put the code that enables
interrupts for the given pin, and the code that enables Pin Change Interrupts, ahead of actually
setting the user's function for the pin. Thus in the small interval between turning on the
interrupts and actually creating a valid link to an interrupt handler, it is possible to get an
interrupt. At that point the value of the pointer is 0, so this means that the Arduino
will start over again from memory location 0- just as if you'd pressed the reset button. Oops!

I corrected it so the code now operates in the proper order.
(EDITORIAL NOTE: If you want to really learn something, teach it!)

Minor code clean-up: All references to PCintPort::curr are now explicit. This changes the compiled
hex code not one whit. I just sleep better at night.

Numbering: Changed the numbering scheme. Beta versions will end with an odd number in the hundredths
place- because they may be odd- and continue to be marked "beta". I'll just sleep better at night. :slight_smile:

I have stuck a fork in the beta version 1.71 and called it "DONE!". Thus, the release version 1.72 is now available. There are no code changes since the 1.71beta release.

Also I uploaded a new version of the Speed Tests document.

PinChangeInt Library version 1.81beta has now been released. It adds MEGA support, for the ATmega1280 and its brethren. See Google Code Archive - Long-term storage for Google Code Project Hosting.

From the release notes:

To sum it up for the Mega: No Port C, no Port D. Instead, you get Port J and Port K. Port B remains. Port J, however, is practically useless because there is only 1 pin available for interrupts. Most of the Port J pins are not even connected to a header connector. Caveat Programmer.

From cserveny's (the author of the MEGA stuff) notes:

Mega and friends are using port B, J and K for interrupts. B is working without any modifications.

J is mostly useless, because of the hardware UART. I was not able to get pin change notifications from the TX pin (14), so only 15 left. All other (PORT J) pins are not connected on the Arduino boards. If you can verify this for me, I would love to know for user. Please send me an email or post to the bug reports section. Thanks. -Ed

K controls Arduino pin A8-A15, working fine.

328/168 boards use C and D. So in case the lib is compiled with Mega target, the C and D will be disabled. Also you cannot see port J/K with other targets. For J and K new flags introduced:

NO_PORTJ_PINCHANGES and NO_PORTK_PINCHANGES.

My MEGA changes have not been tested. The library compiles and runs on my Duemilanove, but I don't have an Arduino Mega. I would appreciate feedback. Thanks.

  • GreyGnome

June 28, 2012: Version 2.01beta has been released! I had a brainstorm and modified how the ISR loops over the list of possible pins, thereby saving 2 microseconds in the ISR. Look at the notes for the download (here: Google Code Archive - Long-term storage for Google Code Project Hosting.) or read the VERSIONS section of PinChangeInt.h for more information.

Hi,

Two microseconds ? That's a huge gain in performance, well done. I am off to read what you have done now.

Thanks for your continued work on the library, as you might have seen before I use it a lot.

Duane B

rcarduino.blogspot.com

Is it somehow possible to change the trigger event of a pint without calling the detachInterrupt followd by an attachInterrupt?

PCintPort::detachInterrupt(PinIndex);
PCintPort::attachInterrupt(PinIndex, PinChangeInt_RC, FALLING);

Is there somethong like a PCintPort:ChangeTrigger(Pin, UserFunc, TriggerEvent); ???

I'm asking because I think there is a lot of overhead wehen I call deleteIntterupt + attachInterrupt just to change the trigger event.

2eck:
Is it somehow possible to change the trigger event of a pint without calling the detachInterrupt followd by an attachInterrupt?

PCintPort::detachInterrupt(PinIndex);
PCintPort::attachInterrupt(PinIndex, PinChangeInt_RC, FALLING);

Is there somethong like a PCintPort:ChangeTrigger(Pin, UserFunc, TriggerEvent); ???

I'm asking because I think there is a lot of overhead wehen I call deleteIntterupt + attachInterrupt just to change the trigger event.

me too, is a way to make it work smoothly? :!

Why do you need to change the trigger event, there might be an easier approach.

Duane B

already smooth :slight_smile:

sorry about that :stuck_out_tongue:

from the 2.01 beta?

void PCintPort::addPin(uint8_t arduinoPin, uint8_t mode,PCIntvoidFuncPtr userFunc)
{
	// Create pin p:  fill in the data.  This is no longer in another method (saves some bytes).
	PCintPin* p=new PCintPin;
	p->arduinoPin=arduinoPin;
	p->mode = mode;
	p->next=NULL;
	p->mask = digitalPinToBitMask(arduinoPin); // the mask

#ifdef DEBUG
	Serial.print("createPin. pin given: "); Serial.print(arduinoPin, DEC);
	int addr = (int) p;
	Serial.print(" instance addr: "); Serial.println(addr, HEX);
	Serial.print("userFunc addr: "); Serial.println((int)p->PCintFunc, HEX);
#endif

	// pin created

	if (p == NULL) return;

The test for p == NULL should be directly after PCintPin* p=new PCintPin;
as one cannot access the members of p when p == NULL

void PCintPort::detachInterrupt(uint8_t arduinoPin)
{
	PCintPort *port;
#ifdef DEBUG
	Serial.print("detachInterrupt: "); Serial.println(arduinoPin, DEC);
#endif
	uint8_t portNum = digitalPinToPort(arduinoPin);
	if (portNum == NOT_A_PORT) return;
	port=lookupPortNumToPort(portNum);
        if (port == NULL)                                             // <<<<<<<<<<<<<<<<<<<<<<< handle error? from code point it is possible.
	port->delPin(digitalPinToBitMask(arduinoPin));
}

You use a linked list for the PCintPin objects.
you could keep this list in sorted order to speed up the average search time of void PCintPort::PCint()
On average the nr of search steps would be half of the current implementation,

it might be more robust to add a return value for addPin and delPin that indicates the #pins added /delete. addPin might return -1 if pin exist.
These values can be propagated to attachInterrupt/detachIRQ.

sofar my 2 cents
Rob

Hi
I tested the library with a mega. I can confirm following ports to work
15,A8,A9,A10,A11,A12,A13,A14,A15
I did not succeed in getting port 14 to work.
Thanks for the great work.
Best regards
Jantje

Jantje:
Hi
I tested the library with a mega. I can confirm following ports to work
15,A8,A9,A10,A11,A12,A13,A14,A15
I did not succeed in getting port 14 to work.
Thanks for the great work.
Best regards
Jantje

playing a bit more I came to the conclusion you need to use 14 and 15 to get 15 to work.

PCintPort::attachInterrupt(14, interruptroutine,CHANGE);
PCintPort::attachInterrupt(15, interruptroutine,CHANGE);

Makes pin 15 work
but

PCintPort::attachInterrupt(15, interruptroutine,CHANGE);

Does not.
Best regards
Jantje

The test for p == NULL should be directly after PCintPin* p=new PCintPin;
as one cannot access the members of p when p == NULL

Fixed. Nice catch, thanks!
-GreyGnome

robtillaart:
You use a linked list for the PCintPin objects.
you could keep this list in sorted order to speed up the average search time of void PCintPort::PCint()
On average the nr of search steps would be half of the current implementation,

it might be more robust to add a return value for addPin and delPin that indicates the #pins added /delete. addPin might return -1 if pin exist.
These values can be propagated to attachInterrupt/detachIRQ.

Thanks for the feedback, Rob.

I had considered the sorted linked list, but then I realized that sorting on pin number may not be what the user wants. It only speeds up the average search time if you can guarantee an even distribution of interrupts. I would rather let the programmer decide in which order he or she decides to add pins.

Having addPin() and attachInterrupt() return values seems pretty easy and somewhat smart, so I will look into doing that.