Microcontrollers are designed to run concise firmware that’s dedicated to a specific application. The firmware is embedded software, which is written into the program memory. The firmware codes are typically short and designed to manage and execute several micro tasks down to the hardware level.
Since microcontrollers are dedicated to a single application, these devices have no parallel computing and, by default, code is run sequentially. This means that any external (hardware) changes or software conditions are polled in a linear order through the programming loop.
In Arduino’s ecosystem, this programming loop is written and executed within the loop() function.
Polling is the default by which a microcontroller functions. Polling is the process where the controller waits for its state or next task from the external device. This can be problematic when the controller needs to respond to a situation within a short time — or immediately.
This can occur when there is a sudden change in programming conditions or the hardware state. Fortunately, microcontrollers have a function to deal with such challenges, called interrupts.
What is an interrupt?
An interrupt in relation to microcontrollers is a mechanism that temporarily suspends the main program, passing control to an interim code.
In terms of Arduino, this means the typical code as defined within the programming loop (i.e. loop() function) is suspended and a response code is executed, which relates to the specific software condition or hardware change. This response is defined within a well-structured block of code, called Interrupt Service Routine (ISR).
Let’s suppose that a microcontroller in a robotic rover is programmed to navigate through rough terrain, overcoming different obstacles. The rover tracks its state by monitoring data, using ultrasonic and accelerometer sensors. At one point, a dark, pit is detected along the way.
However, the pit is only perceived when the rover is a foot or so away from it, which is a problem. The data from the sensors are monitored in a regular programming loop, meaning this message about the pit would typically be relayed too late for the controller to respond in time.
Fortunately, an interrupt saves the day for this rover. The accelerometer sensor, in this case, generates an interrupt whenever there is a sudden change in the orientation of the robotic rover, signaling it to stop. The interrupt code saved the rover from falling into the pit at the last minute.
Understanding ISR
An interrupt service routine (ISR) is a block of code that’s executed in response to an interrupt. Of course, there are different types of interrupts.
The key is that the interrupts for a microcontroller must always be well-defined and correspond to a specific software condition or hardware state.
In Arduino, interrupts are identified by interrupt vectors.
Interrupts in Arduino
There are several different boards under the Arduino portfolio, each with different microcontrollers. This table summarizes several of those platforms…
Note: This table does not include retired Arduino boards, such as Arduino Esplora, Arduino Industrial 101, Arduino 101, Arduino Ethernet Rev3, LilyPad Arduino, LilyPad Arduino Simple, LilyPad Arduino USB, LilyPad Arduino SimpleSnap, Arduino Gemma, Arduino Yún Mini, Arduino Yún, Arduino Leonardo ETH, Arduino Tian, Arduino M0, Arduino M0 Pro, Arduino Mega ADK Rev3, Arduino Robot, Arduino Pro, Arduino Mini, and Arduino Pro Mini.
Since every microcontroller has a set of built-in peripherals and input/output interfaces, it makes sense that the interrupts are different for each microcontroller platform.
The controller at the center of Arduino Uno and Arduino Nano — ATmega328 — supports these interrupts…
All of the interrupts are generated by ATmega328’s built-in peripherals or I/O interfaces, which are different in other microcontrollers.
The interrupt priority noted in the above table is important when more than one interrupt is generated at the same time. When this happens, the interrupt of higher priority is executed while the one of lower priority is suppressed.
“Reset” is the interrupt of highest priority and it has no interrupt vector. This means that on the reset/reboot, no user-defined routine can be run.
Broadly speaking, interrupts are classified into two categories.
1. Hardware interrupts: generated by the microcontroller’s input/output pins. An external hardware/component triggers a change in voltage signal for Arduino’s input/output pin.
In Arduino, there are two types of hardware interrupts: external and pin change interrupts.
2. Software interrupts: generated by user-defined program instructions. They’re always associated with the microcontroller’s built-in peripherals and communication port. These interrupts are not triggered by an external hardware component but by changes to the built-in peripherals or software configuration.
The built-in peripherals must be configured to generate interrupts by writing program instructions. Otherwise, the interrupt is generated automatically on completion of a software instruction that’s associated with a communication port/peripheral.
For example, timer interrupts are software interrupts. Timer interrupts must be configured by user-defined program instructions. The interrupts generated by communication ports are also software interrupts as they’re triggered when the data communication process is completed by the user-defined program instructions.
Implementing interrupts in Arduino
The ISRs are defined as separate blocks of code other than the main loop() function. The interrupts are already enabled by default. All software interrupts require set programming.
For instance, timers can be configured to generate interrupts only when they are programmed by the user. The hardware interrupts (meaning the external and pin-change interrupts) must be configured in the setup() function.
It’s also possible to enable or disable interrupts in the loop() function to avoid interruptions in program execution or re-enable interrupts.
To disable interrupts, call the noInterrupts() function within the loop() function as follows…
loop(){
…
noInterrupts();
}
When the interrupts are disabled, a critical, time-sensitive code must follow and needs to run uninterrupted. The interrupts can be re-enabled in the loop() function by calling the interrupts() function.
loop(){
…
noInterrupts();//disabling interrupts
// critical, time-sensitive code ..
interrupts() //re-enabling interrupts
}
The interrupts can be served by writing a routine, in which a particular interrupt is identified by passing its interrupt vector as a parameter. Typically, this routine is named ISR().
For example, to serve the timer interrupt, configure it in the loop() function and the ISR(). However, the timer interrupt must be defined outside the loop().
loop(){
…
//instructions to configure and activate Timer/Counter0 Overflow interrupt
…
}
ISR(TIMER0_OVF_vect){
//Code serving Timer/Counter0 Overflow interrupt
}
Both software and hardware interrupts can be served using their interrupt vectors. To configure and activate the software interrupts, Arduino’s associated built-in registers must be modified or programmed by the user.
The external interrupts are configured by calling the attachInterrupt() function in the setup() function. The external interrupts are disabled in the loop() function, using the detachInterrupt() function. The pin-change interrupts are configured or activated by modifying or programming Arduino’s associated built-in registers.
External interrupts
There are two types of hardware interrupts: external and pin-change interrupts. The external interrupts are available on Arduino’s selective pins.
Here’s a list of the usable pins for external interrupts on different Arduino boards.
The external interrupts must be configured using the attachInterrupt() function in the setup.
setup() {
…
attachInterrupt(digitalPinToInterrupt(pin), ISR, mode);
}
The attachInterrupt() accepts three parameters.
1. The first parameter specifies the pin number at which the external interrupt is activated. That pin number must be passed as a parameter to the digitalPinToInterrupt() function.
2. The second parameter is the name of the interrupt service routine serving the external interrupt at a given pin.
3. The third parameter is the mode of interrupt or signal transition at which the interrupt should be generated.
Here are the possible modes of an external interrupt…
- LOW: the interrupt is generated when the digital signal at the pin is low.
- CHANGE: the interrupt is generated when the pin values change.
- RISING: the interrupt is generated when the digital signal at the pin moves from low to high.
- FALLING: the interrupt is generated when the digital signal at the pin moves from high to low.
The Due, Zero, and MKR1000 boards also support a fifth mode.
- HIGH: the interrupt is generated when the digital signal at the pin is high.
An external interrupt defined for Arduino Uno’s pin 2 is as follows…
volatile int interruptPin = 2;
setup() {
…
pinMode(interruptPin, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(interruptPin), ISR, mode);
}
loop() {
…
}
ISR(){
//Code to deal with external interrupt at pin 2
…
}
If required, the external interrupts can also be disabled in the loop() function by calling the detachInterrupt() function.
loop() {
…
detachInterrupt(digitalPinToInterrupt(interruptPin))
}
Writing ISRs
There are several important considerations when writing interrupt service routines.
- Make them short and sweet. ISRs are intended for sudden, emergency-like responses. They’re not meant for executing long blocks of codes. Therefore, ISRs should be short, precise, and relevant.
- Steer clear of timing functions. The timing functions — such as, millis(), micros(), delayMicroseconds() or delay() — should never be used in an ISR. This is because they use timer interrupts. It’s not possible to call or use an interrupt in another interrupt. So, there’s no point in using timing functions with ISRs.
- Avoid serial communication protocols. Communication interfaces raise their own interrupts, which are of lower priority and cannot override the hardware or timer interrupts. Also, any data exchanged when running an ISR is lost. If it’s important to store date while running an ISR, it can only be done by changing the global variables. In the loop() function, the state of these variables can be used to print or communicate messages.
- Remember: ISRs have no parameters. Interrupt service routines cannot accept any parameters except the interrupt vector. Additional arguments can be only passed to an ISR via the global variables.
- ISRs return nothing. ISRs have a data type of void. They cannot return any value. If a value must be passed to the main loop from the ISR, it can only be passed by modifying the global variables. (Although it’s possible to read and write digital input/output pins within an ISR.)
- Use volatile variables. The variables that are used inside and outside of the ISRs must be global variables and defined as volatile. Volatile variables are not optimized by the compiler and have no risk of removal during the compilation of source code. However, it’s important to avoid defining every variable in an ISR as volatile because they slow down the code. Therefore, only define those variables inside and outside the ISR as volatile.
Conclusion
Interrupts provide an important function in the microcontroller world. They are the only resort when dealing with critical or challenging situations.
There are hardware and software interrupts. Hardware interrupts are generated by external components at the controller’s input/output pins. Arduino has two types, external and pin-change interrupts.
Software interrupts relate to the microcontroller’s built-in peripherals and communication ports. These interrupts must be activated and configured by user-defined program instructions.
You may also like:
Filed Under: Electronic Projects, Featured