Most of us, when working on an Arduino project, put their code in the "Loop" function and this code is executed again and again. We can of course use "If" or "Switch / Case" statements, often related to sensors data, to execute some code only if a specified conditions are met. But honestly, they are situation where this way of doing things is not efficient at all.
The "Doorbell" case
To illustrate such a case, imagine being at home, waiting for a relative or a friend. If you had been programmed with a main "Loop" function, you would do whatever you do and then go to open the door to check if your friend is waiting, and then do whatever you do and check the door and so on...
The above case illustrated as a skecth
void Setup ()
{
// no Setup needed
}
void Loop()
{
Do_stuff_1 ;
Do_stuff_2 ;
// ...
Do_stuff_3 ;
Go_Check_Entry_Door ;
If (someoneAtDoor)
{
Invite_Host ;
}
}
void Invite_Host()
{
Action_01 ;
Action_02 ;
Action_03 ;
}
Of course, no one would react in such a way. Instead, we do our stuff until we hear the door bell. Then we interrupts our activities and we go open the door. The door bell is the "interrupt".
What Is An Interrupt ?
Interrupts are signals that interrupt the normal flow of a program. Interrupts are usually used for hardware devices that require immediate attention when events occur. More precisely, An interrupt is a signal sent to the CPU that does exactly what it sounds like: it interrupts the current program flow and makes it jump off in a different section temporarily, before returning to whatever it was doing previously. As far as the main program code is concerned, it doesn't even need to know that an interrupt has taken place. It will simply lose some time in the middle of whatever it was doing; other than that, everything will continue as if nothing happened. They can be very useful if you want your code to react fast to external events, while keeping your code simple.
Using Interrupts with an Arduino
Most Arduino designs have two hardware interrupts (referred to as "interrupt0" and "interrupt1") hard-wired to digital I/O pins 2 and 3, respectively. The Arduino Mega has a total of six hardware interrupts, with the additional interrupts ("interrupt2" through "interrupt5") on pins 21, 20, 19, and 18, respectively.
By defining a special function called an "Interrupt Service Routine" (ISR) that you want executed whenever the interrupt is triggered, and then specifying the conditions under which that can happen. With an Arduino, you can trigger the interrupt under four conditions, which are predefined as valid values:
- LOW to trigger the interrupt whenever the pin is low,
- CHANGE to trigger the interrupt whenever the pin changes value
- RISING to trigger when the pin goes from low to high,
- FALLING for when the pin goes from high to low.
Note that while an interrupt routine is running, all other interrupts are blocked. As a result, timers will not work in interrupt routines and other functionality may not work as expected. Therefore, always keep your interrupt routines short and simple.
To Specify a function to call when an external interrupt occurs, use "AttachInterrupt" with the following parameters.
attachInterrupt(interrupt, function, mode)
- interrupt: the number of the interrupt (int)
- function: the ISR; this function must take no parameters and return nothing.
- mode: one of the four conditions listed above; Low, Change, Rising, Falling.
Note also that due to the fact that interrupts are blocked and timers are stopped during the ISR processing, inside the attached function, delay() won't work and the value returned by millis() will not increment. Serial data received while in the function may be lost. You should declare as volatile any variables that you modify within the attached function.
If we examine our doorbell case with an interrupt approach we will now have the following code:
interruptPin = 0;
void Setup ()
{
// declare interrupt with three paramaters
// If the doorbell rings (pin 0 changes its state)
// execute function Invite_Host
attachInterrupt(interruptPin , Invite_Host, CHANGE);
}
void Loop()
{
Do_stuff_1 ;
Do_stuff_2 ;
// ...
Do_stuff_3 ;
}
void Invite_Host()
{
Action_01 ;
Action_02 ;
Action_03 ;
}
Reassigning Interrupts
Interrupts can be changed at any point by using the attachInterrupt() method. As soon as this is done, any previously assigned interrupt on the associated pin is removed.
Starting / Stopping Interrupts
Arduino also has the ability to temporarily ignore all the interrupts. You may want to do this if you have some sensitive code that must be executed without interruption. In this case you would issue a noInterrupts() call. Once your sensitive code block has completed, interrupts can be restarted by calling interrupts().
Removing Interrupts
Interrupts can also be removed by using the detachInterrupt(interrupt_number) method.
Timers on Arduino
There are 3 hardware timers available on the ATmega168/328, and they can be configured in a variety of ways to achieve different functionality.
- Timer0 (System timing, PWM 5 and 6)Used to keep track of the time the program has been running. The millis() function to return the number of milliseconds since the program started using a global incremented in the timer 0 ISR. Timer 0 is also used for PWM outputs on digital pins 5 and 6.
- Timer1 (PWM 9 and 10)Used to drive PWM outputs for digital pins 9 and 10.
- Timer2 (PWM 3 and 11)Used to drive PWM outputs for digital pins 3 and 11.
While all the timers are used only Timer0 has an assigned timer ISR. This means we can hijack Timer1 and/or Timer2 for our uses. The PWM function on some of the I/O pins will be affected as a result however. If you plan to use PWM you need to know what is affected. I chose to use timer 2 so PWM pins 3 and 11 will be affected.
The easiest way to use the timers is to download the TimerOne Library. This library will allow you to execute an ISR (Interrupt Service Routine) with regular delay.
The most important routines of the TimerOne library do:
- initialize(period)You must call this method first to use any of the other methods. You can optionally specify the timer's period here (in microseconds, 1/1000000 second), by default it is set at 1 second. Note that this breaks analogWrite() for digital pins 9 and 10 on Arduino.
- setPeriod(period)Sets the period in microseconds. The minimum period or highest frequency this library supports is 1 microsecond or 1 MHz. The maximum period is 8388480 microseconds or about 8.3 seconds. Note that setting the period will change the attached interrupt and both pwm outputs' frequencies and duty cycles simultaneously.
- pwm(pin, duty, period)Generates a PWM waveform on the specified pin. Output pins for Timer1 are PORTB pins 1 and 2, so you have to choose between these two, anything else is ignored. On Arduino, these are digital pins 9 and 10, so those aliases also work. Output pins for Timer3 are from PORTE and correspond to 2,3 & 5 on the Arduino Mega. The duty cycle is specified as a 10 bit value, so anything between 0 and 1023. Note that you can optionally set the period with this function if you include a value in microseconds as the last parameter when you call it.
- attachInterrupt(function, period)Calls a function at the specified interval in microseconds. Be careful about trying to execute too complicated of an interrupt at too high of a frequency, or the CPU may never enter the main loop and your program will 'lock up'. Note that you can optionally set the period with this function if you include a value in microseconds as the last parameter when you call it.
- setPwmDuty(pin, duty)A fast shortcut for setting the pwm duty for a given pin if you have already set it up by calling pwm() earlier. This avoids the overhead of enabling pwm mode for the pin, setting the data direction register, checking for optional period adjustments etc. that are mandatory when you call pwm().
- detachInterrupt()Disables the attached interrupt.
- disablePwm(pin)Turns PWM off for the specified pin so you can use that pin for something else.
A simple implementation of the attachInterrupt of the TimerOne libray here below. This will execute myISR at a regular time lapse, a tenth of a second.
#include <TimerOne.h>
void setup()
{
Timer1.initialize(100000); // set a timer of length 100000 microseconds
Timer1.attachInterrupt(myIsr); // attach the service routine here
}
void loop()
{
// Main code loop
}
void myIsr()
{
// The code executed every tenth of a second here
// ....
}
More information on Arduino interrupts HERE
More information on TimerOne library HERE