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

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.

I would rather let the programmer decide in which order he or she decides to add pins.

You have a point, and it should be made clear in the documentation that adding the most interrupted pin as first gives a better overall performance as it is found fastest.

On the subject of letting the programmer take responsibilty it would be nice to add an option to skip all of the rising/falling tests inside the inner loop of the ISR, it adds a few cycles in the most repeated part of the ISR code.

Duane B

rcarduino.blogspot.com

reviewed again,

#define PCINT_VERSION 2020 // This number MUST agree with the version number, above.

In the Add function, this line
p->PCintFunc=userFunc;
must be moved upwards so that when adding the new PCint object to the list, the function is available. Now there is still a race condition possible, that p is added to the list but the function is not in place.

I think

			if (thisChangedPin) {
				if ((thisChangedPin & portRisingPins & PCintPort::curr ) ||
		        	(thisChangedPin & portFallingPins & ~PCintPort::curr )) {

can be optimized as [ portRisingPins & PCintPort::curr ] and [ portFallingPins & ~PCintPort::curr] are invariant in the loop

Rob

Looked at the PCint() code and came up with this.

  • precalculated fastMask to simplify and speed up the mask testing in the inner while loop.
  • thisChangedPin optimized away

The var changedPins can be optimized away but probably the compiler will see that too.

Can you give it a try?

void PCintPort::PCint() 
{
  #ifdef FLASH
  if (*led_port & led_mask) *led_port&=not_led_mask;
  else *led_port|=led_mask;
  #endif
	
  #ifndef DISABLE_PCINT_MULTI_SERVICE
  uint8_t pcifr;
  do {
  #endif
    // get the pin states for the indicated port.
    uint8_t changedPins = PCintPort::curr ^ lastPinView;
    lastPinView = PCintPort::curr;
	
    uint8_t fastMask = changedPins & ((portRisingPins & PCintPort::curr ) | ( portFallingPins & ~PCintPort::curr ));  // bitwise OR is needed here
    // if fastmask eq zero there will be no irq in the linked list, optimization!
    if (fastMask != 0)
    {
      PCintPin* p = firstPin; 
      while (p) {
        if (p->mask & fastMask){
	  #ifndef NO_PIN_STATE
	  PCintPort::pinState=PCintPort::curr & p->mask ? HIGH : LOW;
	  #endif
	  #ifndef NO_PIN_NUMBER
	  PCintPort::arduinoPin=p->arduinoPin;
	  #endif
	  #ifdef PINMODE
	  PCintPort::pinmode=p->mode;
	  #endif
	  p->PCintFunc();
      }
      p = p->next;
      }  // while(p)
    }
    #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
}

Excellent, thats going to save a lot of cycles, thanks

Duane B

rcarduino.blogspot.com

If you have the new numbers can you post them?