-
Notifications
You must be signed in to change notification settings - Fork 73
Usage
|
Refer to the examples/Simple/Simple.ino
sketch for an example of the library in use.
Compare the instructions below to the code in that file. The file is designed to be used with the ATmega328 chip; there are other examples for the other chips.
To use the library:
To attach an interrupt to your Arduino (or 1284 or ATtiny) Pin, calling your function "userFunc", and acting on the "mode", which is a change in the pin's state; either RISING, FALLING, CHANGE, or (on External Interrupt pins ONLY), LOW:
At the top of your sketch, include:
#include <EnableInterrupt.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:
enableInterrupt(pin,userFunc,mode)
Pins that you can use follow. See the PIN / PORT BESTIARY below for the complete lists of pins for each CPU type:
- ATmega328- All, except pins 0 and 1 are not recommended and not tested.
- ATmega2560- 10 - 15, 18 - 21, A8 - A15, SS, SCK, MOSI, MISO
- Leonardo- 0, 1, 2, 3, 7, 8, 9, 10, 11, MISO, SCK, MOSI, SS (on 3rd party boards)
- Mighty1284- All, except pins 0 and 1 are not recommended. This chip has not been tested.
- ATtiny44/84- All, except "pin 11" (PB3) is not defined in the support files (pins_arduino.h). The library should work to support that pin but you must modify the pins_arduino.h to support it.
- ATtiny45/85- All (note that Gemma uses 3 pins for RESET, XTAL1, and XTAL2).
- Due- All
- Zero- All except pin 4.
If you are a C++ programmer and enjoy working with objects, see the OOSimple.ino sketch in the examples folder.
enableInterrupt- Enables interrupt on a selected Arduino pin. disableInterrupt - Disables interrupt on the selected Arduino pin.
enableInterrupt(uint8_t pinNumber, void (*userFunction)(void), uint8_t mode); or enableInterrupt(uint8_t interruptDesignator, void (*userFunction)(void), uint8_t mode);
Enables interrupt on the selected pin. Example: enableInterrupt(10, userFunction, RISING);
The arguments are:
- pinNumber - The number of the Arduino pin, such as 3, or A0, or SCK. Note that these are not strings, so when you use A0 for example, do not use quotes.
- interruptDesignator- very much like a pin. See below.
- userFunction - The name of the function you want the interrupt to run. Do not use a pointer here, just give it the name of your function. See the example code in the Examples directory.
-
mode - What kind of signal you want the interrupt to interrupt on. For Pin Change Interrupt pins, the modes supported are RISING, FALLING, or CHANGE. For External Interrupt pins, the modes supported are the same, plus LOW. For the Due and Zero boards, the modes are the same as the External Interrupts, plus HIGH. Again, these are not strings- just type them in verbatim, they are derived into a proper value by the compiler.
- RISING - The signal went from "0", or zero volts, to "1", or 5 volts.
- FALLING - The signal went from "1" to "0".
- CHANGE - The signal either rose or fell.
- HIGH - The signal is at a high level. Due and Zero only
- LOW' - The signal is at a low level. Due, Zero, and External Interrupt pins only
It is possible to change the user function after enabling the interrupt (if you want), by disabling the interrupt and enabling it with a different function.
disableInterrupt(uint8_t pinNumber); or disableInterrupt(uint8_t interruptDesignator);
Disables interrupt on a selected Arduino pin. You can reenable the pin with the same or a different function and mode.
The arguments are:
- pinNumber - The number of the Arduino pin, such as 3, or A0, or SCK. Note that these are not strings, so when you use A0 for example, do not use quotes.
- interruptDesignator- very much like a pin. See below.
You can assign a function to each of the pins upon which you want to enable interrupts, like this:
enableInterrupt(9, myFunctionA, FALLING); enableInterrupt(10, myFunctionB, FALLING); enableInterrupt(11, myFunctionC, FALLING);
When any of the myFunctionX (where X is A, B, or C) gets called, you know exactly which pin triggered the interrupt. But what if you want the ISRs to share a single function? There is a facility in the library to identify the most recent pin that triggered an interrupt. Set the following definition before including the EnableInterrupt.h file in your sketch:
#define EI_ARDUINO_INTERRUPTED_PIN
Then, the ATmega chip will set a variable with every interrupt, and you can query it to find which pin interrupted your sketch. The variable is arduinoInterruptedPin
and it is of type uint8_t. See the test/example code in the examples/InterruptedPin[328|2560] directories. Here's a short example:
volatile uint16_t interruptCountA=0; // The count will go back to 0 after hitting 65535. volatile uint16_t interruptCountB=0; // The count will go back to 0 after hitting 65535. void interruptFunctionA() { switch (arduinoInterruptedPin) { case 10: interruptCountA++; break; case 11: interruptCountB++; break; } }
Note that arduinoInterruptedPin
is not marked volatile in the library, so the compiler is free to optimize it into a register. It may not be valid outside of an ISR.
Again, note (and I keep saying this): Because of the lag between the triggering of an interrupt and the actual running of an ISR, if you have multiple signals coming in on a port and a pin Y changes after pin X has triggered an interrupt, you may get false information. I imagine this would be rare, but it could happen- especially in high-speed circuits. If speed is of the essence (sub-millisecond, say), make sure you are using external interrupts.
You can set the interrupt on a port to take place on rising, or falling, or both directions of signals (also low or high, depending on the type of chip). If you want to trigger on both directions, how do you know which way the signal changed that triggered the interrupt? The EnableInterrupt library provides a variable arduinoPinState
which is updated early in the ISR code and should provide a fairly accurate indication.
This variable is provided when you use the #define EI_ARDUINO_INTERRUPTED_PIN
functionality. As above, set the definition before including the EnableInterrupt.h file in your sketch.
The value of the variable is either 0 or some power of two (i.e., 2, 4, 8). 0 means that the pin changed from high to low signal. Other than 0 means the pin went from a low signal level to high.
Essentially this is an Arduino pin, with a small bit of additional functionality, which is: You perform a bitwise "or" with the pin number and PINCHANGEINTERRUPT to specify that you want to use a Pin Change Interrupt type of interrupt on those pins that support both Pin Change and External Interrupts. Otherwise, the library will choose whatever interrupt type (External, or Pin Change) normally applies to that pin, with priority to External Interrupt.
Example:
enableInterrupt(2 | PINCHANGEINTERRUPT, myFunckyFunction, CHANGE);
...will set up a Pin Change interrupt on pin 2, and the ISR will call myFunckyFunction
on any CHANGE of signal. This:
enableInterrupt(2, myFunckyFunction, CHANGE);
will set up an External interrupt on pin 2. If you're troubled that there is a difference, my best piece of advice is this: Don't worry about it. If you use the Arduino long enough, you'll learn. But it's not significant enough at this stage in your development to worry about.
If you do simply use Arduino pin numbers, the Enable Interrupt library will choose whatever is appropriate for the pin, with a bias towards External interrupts. But make sure you use the proper mode- remember, LOW is not supported on Pin Change interrupts, and HIGH is only supported on the Due. These values are not error-checked for your platform/interrupt type, that would be too expensive software-wise.
This complexity is all because of 2 pins 2 on the ATmega328-based Arduinos, and 3 pins on the ATmega-644 family of processors. Those are the only pins in the supported processors that can share External or Pin Change Interrupt types. Otherwise, each pin only supports a single type of interrupt and the PINCHANGEINTERRUPT scheme changes nothing. This means you can ignore this whole discussion for ATmega2560- or ATmega32U4-based Arduinos.
You can define various preprocessor directives to save a few bytes (both static RAM and Flash). See SaveMemory.
It is possible to include this library in another library and thus make its functionality available
to it. However, there is one caveat: without taking special percautions you will see linker errors
about multiply-defined functions. You need to precede each one of the
#include
directives with #define LIBCALL_ENABLEINTERRUPT
like this:
#define LIBCALL_ENABLEINTERRUPT #include <EnableInterrupt.h>...except one. In one and only one file in your project (meaning, all the files collected together to make your program), do not include that
#define
. That will allow the functions and variables
to be compiled. Note that you must #include
the library in your main sketch so that
the Arduino system will add its path to the list of files to be compiled with your program, even
if your main sketch doesn't set up any interrupts. See the FAQ for more information.
See the SimpleWithLibrary.ino
sketch in the examples
folder of the distribution for an example usage.
You may find that you want to use another library that defines some of the same ISRs that the EnableInterrupt library defines. The compiler will throw an error because you are trying to define the same function twice. There are a couple of ways to handle this: You can either edit the other library or the EnableInterrupt library to remove the redundant ISRs, or you can eliminate some of the ISRs from the EnableInterrupt library by using some compiler directives to prevent them from being compiled. See SaveMemory for information about how to do that.
The EnableInterrupt library tries to be fast, but ultimately there is going to be some necessary overhead when calling a user-defined subroutine that is only known at compile time. The EnableInterrupt library has a mode whereby the ISRs do not call a user-defined subroutine but instead increment a chosen variable. This is called "HiSpeed mode". This limits the number of registers needed to be saved and restored and speeds up the ISR. Tests show that this greatly increases the speed of the ISRs. See Usage-HiSpeed for details on how to use this mode, and HiSpeed for forther technical discussion and speed test results.
After you've perhaps gotten the Simple.ino sketch working on your system, it's time to consider some of the nuances of the Arduino, especially of the Pin Change Interrupts.
Note that the ATmega processors supported by this library have two different kinds of interrupts: “external”, and “pin change”. For the list of available interrupt pins and their interrupt types, see the PORT / PIN BESTIARY, below.
The Due and Zero only have a single type of interrupt, but it is superior to the ATmega chip's interrupt system in every significant way. This interrupt is somewhat analagous to the external interrupt type, but it carries less overhead, offers an additional interrupt on HIGH level, and includes other functionality such as interrupt priority. However, the only additional feature covered by this library is interrupt on HIGH.
There are a varying number of external interrupt pins on the different ATmega processors. The Uno supports only 2 and they are mapped to Arduino pins 2 and 3. The 644/1284 support 3. The 2560 supports 6 usable, and the Leonardo supports 5. These interrupts can be set to trigger on RISING, or FALLING, or both ("CHANGE") signal levels, or on LOW level. The triggers are interpreted by hardware, so by the time your user function is running, you know exactly which pin interrupted at the time of the event, and how it changed. On the other hand, as mentioned there are a limited number of these pins.
Many of the interrupt pins on the ATmega processor used on all but the Arduino Due are "Pin Change Interrupt pins". This means that in the hardware, the pins only trigger on CHANGE, and a number of pins that share a "Port" share a single interrupt subroutine. For example, there are a total of 3 of these subroutines on the Arduino Uno. There is one subroutine for Port B, one for Port C, and one for Port D. There are 6 Port C pins (the A0 through A5 pins), and if Pin Change interrupts are enabled on those pins then if any of them change in any direction the single interrupt subroutine that serves those ports is called. It is not all bad, though, because you have to enable each pin that you wish to respond to Pin Change interrupts. So if you enable an interrupt on A0, the chip won't uselessly interrupt the other pins A1-A5. But the point is, if you enable an interrupt on A0 and any one or more of the other Analog pins, a single routine serves all of them.
But the EnableInterrupt library makes these interrupt types appear normal by creating the single interrupt routine to call a user-defined interrupt routine, so that each pin can individually support RISING or FALLING as well as CHANGE, and each pin can support its own user-defined interrupt subroutine- every usable pin on the processor can get its own interrupt subroutine (see AllPins328.ino in the examples directory). But there is a significant time between when the interrupt triggers and when the pins are read to determine what actually happened (rising or falling) and which pin changed. So the signal could have changed by the time the pin's status is read, returning a false reading back to the library and hence to your sketch.
Therefore, these pins are not suitable for fast changing signals, and under the right conditions such events as a bouncing switch may actually be missed. Caveat Programmer.
For an utterly engrossing overview of this issue see https://github.com/GreyGnome/EnableInterrupt/blob/master/Interrupt%20Timing.pdf
On the Arduino Uno (and again, all 328p-based boards) and 644/1284-based systems, the pin change interrupts can be enabled on any or all of the pins. The two pins 2 and 3 support either pin change or external interrupts. On the 644/1284-based systems, pin change interrupts are supported on all pins and external interrupts are supported on pins 2, 10, and 11. On 2560-based Arduinos, there are 18 pin change interrupt pins in addition to the 6 external interrupt pins. On the Leonardo there are 7 pin change interrupt pins in addition to the 5 external interrupt pins. See PIN BESTIARY below for the pin numbers and other details.
The Arduino Due and Zero with their ARM CPUs are a different beast than the ATmega series of chips. Not only are they 32 bit processors (vs. 8 on the ATmega), and their clocks run at a much higher speed, but their interrupt systems are superior to that on the ATmega chips, even compared to External interrupts.
This means that the EnableInterrupt library's work on the Due and Zero is trivial: if called on those platforms, we simply create a macro that converts the form of enableInterrupt() and disableInterrupt() to attachInterrupt()/detachInterrupt().
Since that's all it does, the ONLY advantage the EnableInterrupt library offers is as a consistent API across the chip families. If you are not interested in this then don't use the EnableInterrupt library.
The EnableInterrupt library was designed to present a single API for interrupts to all the Arduino processors. Issues with interrupts have traditionally been caused by the varying and complicated pin interrupt schemes of the ATmega line of processors. The SAM processors on the Due and Zero present a more straightforward interrupt design. They already do what the EnableInterrupt library is trying to present: To enable an interrupt on any capable pin, simply set up that pin with the type of interrupt you want, no conversion to an "Interrupt Number" (see the attachInterrupt() docs).
The Arduino Gemma is based on the ATtiny85. For portability, I did not use the Gemma as the basis for ATtiny45/85 testing, rather, I used DA Mellis' work as mentioned elsewhere. However, the Gemma's pins_arduino.h is the same as a generic ATtiny45/85 so it should work without issue. Please open an Issue on the Github site if you find any bugs.
These are the pins that are supported on the different types of chips used in the Arduino.
Theoretically pins 0 and 1 (RX and TX) are supported but as these pins have a special purpose on the Arduino, their use in this library has not been tested.
Interrupt Type | Pins -------------- | -------------- External | 2 3 Pin Change | 2-13 and A0-A5
Interrupt Type | Pins -------------- | -------------- External | 2 3 and 18-21 Pin Change | 10-15 and A8-A15 and SS, SCK, MOSI, MISO also, 'fake' pins 70-76 (see below under Details).
Interrupt Type | Pins -------------- | -------------- External | 0-3 and 7 Pin Change | 8-11 and SCK, MOSI, MISO, SS (SS on 3rd party boards)
Interrupt Type | Pins -------------- | -------------- External | 2 10 11 Pin Change | 2-23 and A0-A7
Interrupt Type | Pins -------------- | -------------- External | 8 Pin Change | 0 - 10 (and 11 if you modify pins_arduino.h from the ATtiny support files)
Interrupt Type | Pins -------------- | -------------- External | 2 Pin Change | 0-5
All.
All except 4.
ATmega168/328-based boards.
Interrupt Pins: Arduino External Arduino Pin Change Arduino Pin Change Pin Interrupt Pin Interrupt Pin Interrupt Port Port Port 2 INT0 PD2 2 PCINT18 PD2 A0 PCINT8 PC0 3 INT1 PD3 3 PCINT19 PD3 A1 PCINT9 PC1 4 PCINT20 PD4 A2 PCINT10 PC2 5 PCINT21 PD5 A3 PCINT11 PC3 6 PCINT22 PD6 A4 PCINT12 PC4 7 PCINT23 PD7 A5 PCINT13 PC5 8 PCINT0 PB0 9 PCINT1 PB1 10 PCINT2 PB2 11 PCINT3 PB3 12 PCINT4 PB4 13 PCINT5 PB5
External Interrupts ------------------------------------------------------------ The following External Interrupts are available on the Arduino: Arduino Pin PORT INT ATmega2560 pin 21 PD0 0 43 20 PD1 1 44 19 PD2 2 45 18 PD3 3 46 2 PE4 4 6 3 PE5 5 7 n/c PE6 6 8 (fake pin 75) ** n/c PE7 7 9 (fake pin 76) Pin Change Interrupts ---------------------------------------------------------- ATMEGA2560 Pin Change Interrupts Arduino Arduino Arduino Pin PORT PCINT Pin PORT PCINT Pin PORT PCINT A8 PK0 16 10 PB4 4 SS PB0 0 A9 PK1 17 11 PB5 5 SCK PB1 1 A10 PK2 18 12 PB6 6 MOSI PB2 2 A11 PK3 19 13 PB7 7 MISO PB3 3 A12 PK4 20 14 PJ1 10 A13 PK5 21 15 PJ0 9 A14 PK6 22 0 PE0 8 - this one is a little odd. * A15 PK7 23 * Note: Arduino Pin 0 is PE0 (PCINT8), which is RX0. Also, it is the only other pin on another port on PCI1. This would make it very costly to integrate with the library's code and thus is not supported by this library. It is the same pin the Arduino uses to upload sketches, and it is connected to the FT232RL USB-to-Serial chip (ATmega16U2 on the R3).
Fake Pins ---------------------------------------------------------- The library supports all interrupt pins, even though not all pins to the ATmega-2560 processor are exposed on the Arduino board. These pins are supported as "fake pins", and begin with pin 70 (there are 70 pins on the ATmega 2560 board). The fake pins are as follows:
pin: fake 70 PJ2 this is Pin Change Interrupt PCINT11 pin: fake 71 PJ3 this is Pin Change Interrupt PCINT12 pin: fake 72 PJ4 this is Pin Change Interrupt PCINT13 pin: fake 73 PJ5 this is Pin Change Interrupt PCINT14 pin: fake 74 PJ6 this is Pin Change Interrupt PCINT15 pin: fake 75 PE6 this is External Interrupt INT6 pin: fake 76 PE7 this is External Interrupt INT7
Why support these pins? There are some non-Arduino boards that expose more pins, and even on the Arduino these may be useful for software interrupts. Software interrupts are used in my tests when I'm measuring the speed of the Arduino's interrupt system. You may find another use for them.
Interrupt pins: Arduino Arduino Pin External Pin Pin Change Interrupt Interrupt Port Port 3 INT0 PD0 8 PCINT4 PB4 2 INT1 PD1 9 PCINT5 PB5 0 INT2 PD2 10 PCINT6 PB6 1 INT3 PD3 11 PCINT7 PB7 7 INT6 PE6 SCK/15 PCINT1 PB1 MOSI/16 PCINT2 PB2 MISO/14 PCINT3 PB3 SS/17 PCINT0 PB0 on ICSP: SCK/15: PCINT1 (PB1) MOSI/16: PCINT2 (PB2) MISO/14: PCINT3 (PB3) // Map SPI port to 'new' pins D14..D17 static const uint8_t SS = 17; static const uint8_t MOSI = 16; static const uint8_t MISO = 14; static const uint8_t SCK = 15; // A0 starts at 18
See http://www.ot-hobbies.com/resource/ard-1284.htm for issues related to the ATmega644 family.
The following External Interrupts are available on the Mighty 1284: Mighty Pin* PORT INT ATmega644/1284 pin 2 PB2 2 3 10 PD2 0 16 11 PD3 1 17 The following Pin Change Interrupts are available on the Mighty 1284: Mighty Mighty Pin* PORT PCINT ATmega644/1284 pin Pin* PORT PCINT ATmega644/1284 pin 0 PB0 8 1 15 PD7 31 21 1 PB1 9 2 16 PC0 16 22 2 PB2 2 3 17 PC1 17 23 3 PB3 11 4 18 PC2 18 24 4 PB4 12 5 19 PC3 19 25 5 PB5 13 6 20 PC4 20 26 6 PB6 14 7 21 PC5 21 27 7 PB7 15 8 22 PC6 22 28 8 PD0 24 14 23 PC7 23 29 9 PD1 25 15 31/A7 PA7 7 33 10 PD2 26 16 30/A6 PA6 6 34 11 PD3 27 17 29/A5 PA5 5 35 12 PD4 28 18 28/A4 PA4 4 36 13 PD5 29 19 27/A3 PA3 3 37 14 PD6 30 20 26/A2 PA2 2 38 25/A1 PA1 1 39 24/A0 PA0 0 40 // I use the mighty-1284p pinout from https://maniacbug.wordpress.com/2011/11/27/arduino-on-atmega1284p-4/ +---\/---+ (D 0) PB0 |1 40| PA0 (AI 0 / D24) (D 1) PB1 |2 39| PA1 (AI 1 / D25) INT2 (D 2) PB2 |3 38| PA2 (AI 2 / D26) PWM (D 3) PB3 |4 37| PA3 (AI 3 / D27) PWM SS (D 4) PB4 |5 36| PA4 (AI 4 / D28) MOSI (D 5) PB5 |6 35| PA5 (AI 5 / D29) PWM MISO (D 6) PB6 |7 34| PA6 (AI 6 / D30) PWM SCK (D 7) PB7 |8 33| PA7 (AI 7 / D31) RST |9 32| AREF VCC |10 31| GND GND |11 30| AVCC XTAL2 |12 29| PC7 (D 23) XTAL1 |13 28| PC6 (D 22) RX0 (D 8) PD0 |14 27| PC5 (D 21) TDI TX0 (D 9) PD1 |15 26| PC4 (D 20) TDO INT0 RX1 (D 10) PD2 |16 25| PC3 (D 19) TMS INT1 TX1 (D 11) PD3 |17 24| PC2 (D 18) TCK PWM (D 12) PD4 |18 23| PC1 (D 17) SDA PWM (D 13) PD5 |19 22| PC0 (D 16) SDL PWM (D 14) PD6 |20 21| PD7 (D 15) PWM +--------+
See https://github.com/damellis/attiny for the ATtiny support files. The README contains a link to a tutorial. See the examples directory for a sample sketch and a Makefile. NOTE: The sketch has been compile tested ONLY! Bug reports welcome, but I do not have any ATtiny chips at this time.
The following External Interrupt is available on the ATtiny44/84: ATtiny Pin* PORT INT ATtiny44/84 pin 8 PB2 0 5 The following Pin Change Interrupts are available on the ATtiny44/84: Arduino Arduino Pin* PORT PCINT ATtiny44/84 pin Pin* PORT PCINT ATtiny44/84 pin 0 PA0 0 13 5 PA5 5 8 1 PA1 1 12 6 PA6 6 7 2 PA2 2 11 7 PA7 7 6 3 PA3 3 10 8 PB2 10 5 4 PA4 4 9 9 PB1 9 3 10 PB0 8 2 11 PB3 11 4 The following pin numbering scheme is assumed: +-\/-+ VCC 1| |14 GND (D 10) PB0 2| |13 PA0 (D 0) == AREF (D 9) PB1 3| |12 PA1 (D 1) PB3 4| |11 PA2 (D 2) PWM INT0 (D 8) PB2 5| |10 PA3 (D 3) PWM (D 7) PA7 6| |9 PA4 (D 4) PWM (D 6) PA6 7| |8 PA5 (D 5) PWM +----+
See https://github.com/damellis/attiny for the ATtiny support files. The README contains a link to a tutorial. See the examples directory for a sample sketch and a Makefile. NOTE: The sketch has been compile tested ONLY! Bug reports welcome, but I do not have any ATtiny chips at this time.
The following External Interrupt is available on the ATtiny45/85: ATtiny Pin* PORT INT ATtiny45/85 pin 2 PB2 0 7 The following Pin Change Interrupts are available on the ATmega45/85: Arduino(tiny) Arduino(tiny) Pin* PORT PCINT ATmega45/85 pin Pin* PORT PCINT ATmega45/85 pin 0 PB0 0 5 3 PB3 3 2 1 PB1 1 6 4 PB4 4 3 2 PB2 2 7 5 PB5 5 1 The following pin numbering scheme is assumed: +-\/-+ (D 5) PB5 1| |8 Vcc (D 3) PB3 2| |7 PB2 (D 2) (INT0) (D 4) PB4 3| |6 PB1 (D 1) GND 4| |5 PB0 (D 0) +----+
Licensed under the Apache2.0 license. See the source files for the license boilerplate, the LICENSE file for the full text, and the NOTICE file which is required by the Apache2.0 license to be distributed with any code that you distribute that uses this library. The copyright holder for this code is Michael Schwager.