-
Notifications
You must be signed in to change notification settings - Fork 70
Usage
- Introduction
- Overview
- The Quick and Dirty Method
- Reference
- Public Methods and Variables
- Optimizations
- Optimization:MAX_PIN_CHANGE_PINS
- Optimizations:Memory Utilization and Speed
- Optimization:NO_PIN_ARDUINO
- Optimization:NO_PIN_STATE
- Optimization:DISABLE_PCINT_MULTI_SERVICE
- More Information
- Miscellaneous
- Serial.print() Inside an ISR
- Use from Another Library
- Software Interrupts
- Multiple ISRs on a Pin
- Examples
- Overview
Here's how to use the PinChangeInt library in your own programs. I assume you have properly installed the library. See the Installation wiki page for more information.
I am writing this for version 1.4 and above of the PinChangeInt library, but most things will apply to earlier versions as well.
This library has been developed and is supported on the Arduino Uno/Duemilanove/Mega2560 only. The author has no other Arduinos. As mentioned in the Introduction on the Project Home page, the Arduino Nano has been confirmed to work with this library. In general I believe that Arduino's based on the ATmega168 or ATmega328 will work.
Sometimes people have trouble getting this library to work, and give up. I see this especially on other "-duino"s, that is on Arduino clones. If it doesn't work on your platform I recommend you not give up- try the Simple examples which come in the examples directory. Then simply document what you've done, open a trouble ticket on the main website and let's see if we can't get your platform to work. If you want.
To attach an interrupt to your Arduino Pin, calling your function "userFunc", and acting on the "mode", which is a change in the pin's state; either RISING or FALLING or CHANGE:
At the top of your sketch, include:
#include <PinChangeInt.h>
Create a function that you want to run when your sketch is interrupted. Do not put any Serial.print() statements in your function. It also must not return any values and it cannot take any arguments (that is, its definition has to look like this):
void userFunc() { ...your code here... }
In setup()
, include:
attachPinChangeInterrupt(pin,userFunc,mode)
Pins that you can use are:
- ATmega328- All, except pins 0 and 1 are not recommended and not tested.
- ATmega2560- 10, 11, 12, 13, 14, 15, A8, A9, A10, A11, A12, A13, A14, A15, SS, SCK, MOSI, MISO
- Leonardo- not supported
If you need to exchange information to/from the interrupt, you can use global volatile variables. See the sketches in the example directory for more information.
Later in your sketch you can detach the interrupt:
detachPinChangeInterrupt(pin)
You can reattach the same function or attach a different one.
If you want to see what the *last* pin that triggered an interrupt was, you can get it this way:
getInterruptedPin()
Note: If you have multiple pins that are triggering interrupts and they are sufficiently fast, you will not be able to find all the pins that interrupted.
- In your sketch, include:
#include <PinChangeInt.h>
- Create a quick function that the library will call whenever your pin(s) are interrupted:
volatile uint8_t interrupt_count[20]={0}; // 20 possible arduino pins on ATmega328 processors void quicfunc() { latest_interrupted_pin=getInterruptedPin(); interrupt_count[latest_interrupted_pin]++; }
- Decide if you want to interrupt on RISING, FALLING, or CHANGE of signal. In setup(), attach an interrupt to your pin. Here, we attach to pin 2:
PCintPort::attachInterrupt(2, &quicfunc, CHANGE);
In this case, whenever pin 2 changes, quicfunc()
will get called, and
variable latest_interrupted_pin
will be set to 2. Your loop()
can poll latest_interrupted_pin
whenever it wants. quicfunc()
can be more elaborate, but it should be quick because it is called by an interrupt.
uint8_t i; loop() { for (i=0; i < 20; i++) { if (interrupt_count[i] != 0) { count=interrupt_count[i]; interrupt_count[i]=0; Serial.print("Count for pin "); if (i < 14) { Serial.print("D"); Serial.print(i, DEC); } else { Serial.print("AI"); Serial.print(i-14, DEC); } Serial.print(" is "); Serial.println(count, DEC); } } }
New, as of version 1.4, is the ability to apply some optimization switches right in your
sketch. Read over these #defines and decide if any apply to you. You can add them to your
code ahead of the #include <PinChangeInt.h>
.
// You can reduce the memory footprint of this handler by declaring that there // will be no pin change interrupts on any one or two of the three ports. If // only a single port remains, the handler will be declared inline // reducing the size and latency of the handler. // #define NO_PORTB_PINCHANGES // to indicate that port b will not be used for pin change interrupts // #define NO_PORTC_PINCHANGES // to indicate that port c will not be used for pin change interrupts // #define NO_PORTD_PINCHANGES // to indicate that port d will not be used for pin change interrupts // *** New in version 1.5: ******************************** // You can reduce the code size by 20-50 bytes, and you can 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 // ********************************************************* // if there is only one PCInt vector in use the code can be inlined // reducing latency and code size // define DISABLE_PCINT_MULTI_SERVICE below to limit the handler to servicing a single interrupt per invocation. // #define DISABLE_PCINT_MULTI_SERVICE
How to decide if that applies to you? Read over the Logic page, the ATmega328p Pins and
Ports section. Now review your desired pin connections, and correlate your pins with the
ATmega328p ports. If you are not using a particular port,
#define NO_PORTx_PINCHANGES
for that port "x". For example,
#define NO_PORTC_PINCHANGES
Remember: When you define that a port has no pin changes, you can not attach an interrupt to that port. Attempting to do so will make things a little... weird...
ENJOY! your Arduino and its Pin Change interrupts!
Usage: void attachInterrupt(uint8_t pin, void *userFunc, int mode)
Attaches an interrupt to the given pin. The interrupt will call the function
userFunc
, based on the mode. The arguments are:
- pin - The pin on the Arduino that you want to attach an interrupt to.
- userFunc - A user-defined function that the interrupt routine will call.
- mode - The interrupt will trigger based on mode, which can be one of RISING, FALLING, or CHANGE
Usage: void detachInterrupt(uint8_t pin)
Stops interrupting on the given pin.
Declared as:
static uint8_t arduinoPin
This variable is assigned the value of the latest pin that caused an interrupt. This
pin will be correct for the current run of the userFunc()
. Once
userFunc
has exited, another interrupt is free to update this variable,
so expect it to change.
Declared as:
static uint8_t pinState
This variable is assigned the state of the latest pin that caused an interrupt.
Its value will be either HIGH or LOW (defined as 1 and 0, respectively, in the
Arduino.h
include file). Its value is checked at some time (estimated
at around 4 microseconds) after the actual interrupt (see next paragraph). The
state will be correct for the current run of the userFunc
. Once
userFunc
has exited, another interrupt is free to update this variable,
so expect it to change.
If you are using this library to interrupt on fast-changing events, including switches (which may bounce), you must realize that this checking of the pin's state is not immediate. Pin Change Interrupts only trigger on change to either level (HIGH or LOW). Therefore, we must determine by querying the pin what its level is. But this can only take place at some time after the interrupt.
How much time? There are at least 11 push instructions that the compiler generates, which store the status of CPU registers on the stack. Each one takes 2 clock cycles. There are 4 clock cycles that the CPU consumes to enter the interrupt. There are various and sundry other instructions that run which are generated by the compiler, prior to us being able to query the pin's state. If we estimate 32 instructions of 2 clock cycles (average) each, that's 64 clock cycles. At 16 MHz, each cycle is 62.5 nanoseconds, so 64 * 62.5 == 4 microseconds, more or less.
Note: The External Interrupts, by contrast, can be set to trigger on low level, on
change to either level (HIGH or LOW), by change to HIGH, or by change to LOW- and
these triggers are set by the hardware. So if you set an External Interrupt to
trigger on a transition to LOW, then you know immediately afterward that the current
logic level of the pin is LOW. External Interrupts are not used by this library-
you can use the Arduino's attachInterrupt()
function for those.
Declared as:
static uint8_t pinmode
Only available if you #define PINMODE
in your sketch. This variable will
be set to the mode of the interrupting pin; ie, one of CHANGE, RISING, or FALLING.
This was created for testing purposes (after all, it is you who set the mode of the
pin), but since it's available I will document it.
There are a small number of parameters that the programmer can modify to limit the amount of memory the library uses and perhaps speed it up a bit.
For modern versions of PinChangeInt- 1.4 and above- insert these parameters ahead
of the #include PinChangeInt.h
, right at the top of your sketch.
For PinChangeInt versions prior to 1.4, these parameters must be set in the
PinChangeIntConfig.h
file which comes with the package. They will have
no effect if set anywhere else, including your sketch. Trust me on this one:
You must go to the libraries directory, and edit this config file itself for it to be of use.
Obsolete
Limit on the Number of Pins
In versions prior to and including 1.2 of the library, the variable MAX_PIN_CHANGE_PINS was used to limit memory usage. As of version 1.3 this variable is no longer used, as pins are added dynamically. You must modify this macro in the PinChangeIntConfig.h file.
There are 3 #define's that control how many interrupt vectors are enabled. In version 1.4 and later of the library you add this macro to your sketch. In version 1.3 and earlier of the library you must modify these macros in the PinChangeIntConfig.h file.
//#define NO_PORTB_PINCHANGES //#define NO_PORTC_PINCHANGES //#define NO_PORTD_PINCHANGES
The corresponding code looks like this:
#ifndef NO_PORTB_PINCHANGES ISR(PCINT0_vect) { PCintPort::pcIntPorts[0].PCint(); } #endif
So if you know, for example, that you have only pins on PORTB, you can uncomment the lines as follows:
//#define NO_PORTB_PINCHANGES #define NO_PORTC_PINCHANGES #define NO_PORTD_PINCHANGES
This will not set up the corresponding interrupt vectors and (because only 1 PORT remains in use) it will inline the single remaining interrupt routine. I have measured the effect on interrupt routine speed; see the Speed wiki page here.
Remember: Once you define that a port has no pin changes on it, make sure you do not attach any interrupts to pins on that port! There is precious little error checking in this code.
New in version 1.5
Upon interrupt, the PCintPort::PCint()
function is called for that Port. Within that function, we call the user function that has been assigned by you the programmer. Here is the section of code (abbreviated for clarity):
PCintPort::pinState=curr & changedPins ? HIGH : LOW; // version 1.5 PCintPort::arduinoPin=p->arduinoPin; p->PCintFunc(); // this is your user function
Since PCintPort::arduinoPin
is a static variable, it is available for your function to query. If you find you don't need it, having it in the code adds bloat and a bit of slowness. #defineing this macro eliminates the PCintPort::arduinoPin=p->arduinoPin;
from your code entirely.
New in version 1.5
Likewise, if you find you don't care to know what the state of the pin was at the time of the interrupt, #defineing this macro eliminates the setting of
PCintPort::pinState
from your code entirely.
For servicing multiple interrupts.
Normally, the PinChangeInt interrupt routine will attempt to handle all changed pins on a port. This can alleviate the overhead to enter and exit the interrupt routine. However, if you don't want this- for example, with rapid interrupts your CPU may never exit the interrupt routine- then you can define this macro. In version 1.4 and later of the library you add this macro to your sketch. In version 1.3 and earlier of the library you must modify this macro in the PinChangeIntConfig.h
file.
#define DISABLE_PCINT_MULTI_SERVICE
See the Logic wiki page in this project for more detailed information about the operation of this library.
In short: Don't do it.
In my tests I had been using the PinChangeInt with rotary encoders and pushbutton switches, and printing status information via the serial line all from inside the ISR. As of Arduino 1.0 this breaks. In previous version of Arduino you could get away with it. But as of version 1.70beta of the library, I have discontinued developing or maintaining code with this technique. The Serial library uses an interrupt in order to work and this interferes with the pin change interrupts. Essentially it will lock up your Arduino, probably within a short period of time.
Instead, I have utilized a circular buffer from Sigurður Örn (http://siggiorn.com/?p=460) that I ship with each release
(I received permission from Siggi). Now, as you can see in PinChangeIntTest.ino
(delivered starting with version 1.70beta),
I populate the buffer and then print it from the main loop. This can make for some pretty obnoxious-looking output but
at least it won't lock up the CPU.
New as of version 1.70beta
If you want to use this library from another library, you must define the LIBCALL_PINCHANGEINT macro in the .h header file for your library, like this:
#ifndef PinChangeInt_h #define LIBCALL_PINCHANGEINT #include "../PinChangeInt/PinChangeInt.h" #endif
Thus you will be able to include PinChangeInt.h
from your sketch and from your library.
It is possible to interrupt an Arduino pin via software. This would cause an interrupt just like connecting an external switch to your Arduino, but you can do it right from your own sketch. I use this to test the speed of the interrupt code, by having the interrupt triggered by a change on the port from the sketch. You may come up with another use for such a thing. Remember that you must provision the pin as an output pin, so you should not have any switches connected to it. You can connect an LED or transistor or gate or what have you to the pin.
Note that the Arduino's digitalWrite()
library function carries some overhead.
You may want to modify a port by writing directly to a register. Here's a quick example of
creating a software interrupt by writing to a register. Also, see the PinChangeIntSpeedTest.ino
sketch that comes in the examples folder of version 1.3 or higher. This example was distilled
from that sketch. This example was not tested and is presented here as a guide only. It should be
close. I have a daughter so I'll test it eventually but for now it's family first :-).
#define PTEST 2 volatile uint8_t *pinTest_OutPort; uint8_t pinTest_Mask, not_pinTest_Mask; volatile uint8_t pintest, pinIntLow, pinIntHigh; void setup() { Serial.begin(115200); Serial.println("---------------------------------------"); pinMode(PTEST, OUTPUT); digitalWrite(2, HIGH); // ***************************************************************************** // set up ports for output ************ // ***************************************************************************** pinTest_OutPort=portOutputRegister(digitalPinToPort(PTEST)); // output port pinTest_Mask=digitalPinToBitMask(PTEST); // mask not_pinTest_Mask=pinTest_Mask^0xFF; // not-mask PCintPort::attachInterrupt(PTEST, &quicfunc, CHANGE); // C technique; v1.3 or earlier } // end setup() uint8_t k=0; unsigned long milliStart, milliEnd, elapsed; void loop() { k=0; *pinTest_OutPort|=pinTest_Mask; // PTEST to 1 Serial.print("TEST: "); Serial.print(TEST, DEC); Serial.print(" "); Serial.print("test pin mask: "); Serial.print(pinTest_Mask, HEX); Serial.print(" interrupted pin: "); Serial.print(PTEST, DEC); delay(2000); Serial.println(" Start..."); milliStart=millis(); while (k < 10) { i=0; while (i < 10000) { *pinTest_OutPort&=not_pinTest_Mask; // pintest to 0 ****************************** *pinTest_OutPort|=pinTest_Mask; // pintest to 1 ****************************** i++; } k++; } milliEnd=millis(); elapsed=milliEnd-milliStart; Serial.print("Elapsed: "); Serial.println(elapsed, DEC); delay(500); }
In short: Don't do it!
It is possible to appear to attach more than one interrupt subroutine to a particular pin, however this is not a supported feature. The second function attached should be the one that is applied. I mention this so that you can beware of any odd behavior in your code.
The sets of pins attached to a port are simply a linked list. Here is some code from addPin(), which is called by attachInterrupt():
PCintPin* p=createPin(arduinoPin, mode);
if (p == NULL) return; // Add to linked list, starting with firstPin if (firstPin == NULL) firstPin=p; else { PCintPin* tmp=firstPin; while (tmp->next != NULL) { tmp=tmp->next; }; tmp->next=p; } p->PCintFunc=userFunc;
The effect of adding multiple ISRs to a pin, however, is undefined. If you do so, I don't know which routine will be called first- although it should be whichever is attached first- nor am I inclined to maintain such a feature. This is a bug, not a feature, but as the Arduino has such a small memory footprint, the code does not contain much error checking. In other words, caveat programmer!
Here is an example with a pushbutton connected to pin2 and another to pin3. One side of the pushbutton is connected to ground. The other side connects to the Arduino pin.
// These two pins are connected for interrupts. // Add more Pins at your leisure. // For the Analog Input pins, you can use 14, 15, 16, etc. // or you can use A0, A1, A2, etc. The Arduino code comes with #define's // for the Analog Input pins. #define PIN1 2 #define PIN2 3 uint8_t latest_interrupted_pin; uint8_t interrupt_count[20]={0}; // 20 possible arduino pins void quicfunc() { latest_interrupted_pin=PCintPort::arduinoPin; interrupt_count[latest_interrupted_pin]++; }; void setup() { pinMode(PIN1, INPUT); digitalWrite(PIN1, HIGH); PCintPort::attachInterrupt(PIN1, &quicfunc, FALLING); // add more attachInterrupt code as required pinMode(PIN2, INPUT); digitalWrite(PIN2, HIGH); PCintPort::attachInterrupt(PIN2, &quicfunc, FALLING); Serial.begin(115200); Serial.println("---------------------------------------"); } uint8_t i; void loop() { uint8_t count; Serial.print("."); delay(1000); for (i=0; i < 20; i++) { if (interrupt_count[i] != 0) { count=interrupt_count[i]; interrupt_count[i]=0; Serial.print("Count for pin "); if (i < 14) { Serial.print("D"); Serial.print(i, DEC); } else { Serial.print("A"); Serial.print(i-14, DEC); } Serial.print(" is "); Serial.println(count, DEC); } } }