Man with flag

In a computing context, interrupts are events or conditions that cause the microprocessor to stop what it's doing, work on the event that caused the interrupt and then resume its original task. They are analogous to someone attracting your attention, telling you something then letting you go back to what you were doing. Interrupts are usually dealt with by a dedicated function called an Interrupt Service Routine (ISR).

Interrupts can be external or internal but only external ones are explained here. Further, there are two types of external interrupts; (1) "external" interrupts can be programmed to detect rising edge, falling edge, change and low and (2) "pin change" interrupts that can be set on any pin but can only detect a change. External interrupts are useful because they allow sensors to be dealt with as and when events occur instead of continually polling the sensor to see if it has any data. Polling is wasteful of resources and can be difficult to program without causing unwanted side-effects such as delays. Just because an interrupt is so called, it doesn't mean that it can't be called many times. For example, a program that measures the speed of a fan might be calling the ISR many 10s of times each second or a frequency counter many 1000s of times.

When an external interrupt occurs it is processed by the ISR and this needs to be done quickly, without imposing any delay on the main program. This means that ISRs must be simple and not contain code that itself causes any interrupts; for example doing any I/O. ISRs generally communicate with the main program by means of changing one or more variables that indicate to the main program that the ISR has been triggered; this could be toggling a value, incrementing a counter, etc. Variables that are used in both an ISR and the main program must be declared outside both. An ISR cannot be called with any arguments and cannot return any values; all communication must be done through the shared variables.

Modern compilers are good at optimising out redundant code but may not understand that a variable's value could change unexpectedly and without an explicit assignment statement. In reality, there are only three types of variables that can change: memory-mapped registers for peripheral devices, global variables that are changed by more than one process in a multi-threaded application and global variables that are changed inside an ISR. To avoid problems, all variables that will be modified in an ISR must be declared with the volatile keyword, which is conventionally placed first in the statement. For example;

volatile int Counter;

There's an interesting page on interrupts at StackExchange.



This applies to all function calls, but particularly with the large numbers that are associated with timing events. If you are writing sketches that use millis() or micros(), you need to ensure that any variables that are given a value by those functions have the same type as the function, in this case, unsigned long.


If you're checking for a difference in time you need to remember that millis() and micros() will overflow and go past zero after about 50 days and 70 minutes respectively. If your sketch is expected to run longer than these times, you will need to accommodate the overflow.


Do not be tempted to do any kind of I/O in an ISR. printf() and the family of similar functions are generally large and slow may cause your ISR to miss interrupts and give erroneous results. See stackoverflow.com and opengroup.org for information on other reasons why I/O in an ISR is poor practice.


Interrupts are usually given an integer identification number, but these do not match the digital pins with which they are associated. For Arduinos and ESP devices, and to avoid confusion, it's good practice to use the digitalPinToInterrupt() function to convert from the digital pin number to the interrupt identifier. You'll need to do this if you wish to port the sketch to a different model of Arduino or ESP.