Hardware Interrupts - Arduino
Hardware Interrupts - Arduino
Arduino
Here we will explore hardware interrupts on the Arduino microcontroller. This is an
electrical signal change on a microcontroller pin that causes the CPU to do the following
largely in this order:
1. Finish execution of current instruction.
3. The CPU will "push" the program counter onto a "stack" or memory storage
location for later retrieval.
5. The CPU loads that memory address and executes the function or subroutine
that should end with a "return" instruction.
6. The CPU will "pop" the original memory address from the "stack", load it into
program counter, and continue execution of original program from where the
interrupt occurred.
void setup() {
pinMode(LED, OUTPUT);
attachInterrupt(0, toggle, RISING);
}
void loop() {
digitalWrite(LED, state);
}
void toggle() {
state = !state;
}
Above is our first sample program. The label "LED" to the compiler is defined as the
number 9. So digitalWrite(LED, HIGH) is the same as digitalWrite(9, HIGH) which is the
same as digitalWrite(LED, 1), etc.
The second line defines the variable "state" as both a byte (8-bit variable) and "volatile".
This differs from the usual byte, integer, or float in how the system and compiler use it. If
being used as an interrupt routine variable be it byte, float, etc. is must have a "volatile"
declaration. The variable is set to 0 - in reality we are using only one of the eight bits, bit
0.
In setup() we come to the function attachInterrupt(interrupt, function, mode)where
"interrupt" is the interrupt number 0 to 5; "function" is known as theinterrupt service
routine or ISR a function address pointed to by the interrupt vector location; "mode"
configures the hardware electrical characteristics for an interrupt. This is done internally
by the compiler and hidden from the user.
So in this case of attachInterrupt(0, toggle, FALLING) zero corresponds to interrupt 0
on DP2, toggle() is the ISR routine at the bottom of the program, and RISING means
when an electrical signal goes from 0V to 5V the toggle() ISR performs its function -
what started as state = LOW is now state = HIGH and vise-versa. The right "}" is
considered the "return" command - in fact "return;" plased after "state = !state;" is
ignored and won't produce an error.
In other words loop() will simply keep writing the variable "state" to the LED on DP9,
and on an interrupt caused by pressing SW0 will halt, save address counter, jump to
ISR toggle(), will come back where it stopped and continue.
Assuming the LED1 is off press SW0 and the LED will come on, release nothing
happens. Press again and the LED is off. What toggle() really does is a bitwise XOR of
1; state = !state is the same as state = state ^ 1.
With attachInterrupt(interrupt, function, mode) there are four "mode" declarations
that defines when the interrupt should be triggered and are predefined as:
RISING to trigger when the pin goes from low to high - as wired above press switch and
LED state will toggle.
FALLING for when the pin goes from high to low - press switch nothing, release switch
LED state will toggle.
CHANGE to trigger the interrupt whenever the pin changes value - press switch LED
state will toggle, release switch LED will toggle again.
LOW to trigger the interrupt whenever the pin is low - sort of like FALLING but erratic -
don't use.
Program Example 2
#define LED1 9
#define LED2 10
#define SW1 2
#define SW2 3
void setup() {
pinMode(LED1, OUTPUT);
pinMode(LED2, OUTPUT);
digitalWrite(LED1, 0); // LED off
digitalWrite(LED2, 0); // LED off
pinMode(SW1, INPUT);
pinMode(SW2, INPUT);
attachInterrupt(0, ISR0, FALLING);
// interrupt 0 digital pin 2 connected SW0
attachInterrupt(1, ISR1, RISING);
// interrupt 1 digital pin 3 connected SW1
}
void loop() {
// do nothing
}
void ISR1() {
toggle(LED2);
}
The above program introduces several new concepts. There are no "volatile" variables
defined or needed. Both INTR0 (DP2) and INTR1 (DP3) both have their own separate
ISR routines ISR0() and ISR1() respectively. There is a separate non-interrupt
subroutine toggle().
In this case either ISR routines will simply call toggle() and will let toggle() do the work.
Certain functions such as delay() won't work inside a ISR function so it's a good idea to
use separate subroutines as long as they are not overly complex or time consuming -
another interrupt rolling onto an ISR called subroutine that hasn't finished could be
interesting.
In addition functions that use interrupts such as delay() will not work if called from an
ISR routine. For example the modified version of toggle() that follows won't work if
called by an ISR but works fine if called from loop() or another non-interrupt subroutine.
The selected LED will come on for two seconds then go off.
The ISR routine maintains control of the interrupts until it's finished and executes a
"return" from interrupt command.
void myDelay(int x) {
for(int i=0; i<=x; i++)
{
delayMicroseconds(1000);
}
}
Use myDelay(5000) for delay(5000) and the program will work.
Sample Program 3
// LED must be connected between
// digital pin and ground
#define LED1 9
#define LED2 10
#define SW1 2
#define SW2 3
void setup() {
pinMode(LED1, OUTPUT);
pinMode(LED2, OUTPUT);
digitalWrite(LED1, 0); // LED off
digitalWrite(LED2, 0); // LED off
pinMode(SW1, INPUT);
pinMode(SW2, INPUT);
attachInterrupt(0, ISR0, FALLING);
// interrupt 0 digital pin 2 connected SW0
attachInterrupt(1, ISR1, RISING);
// interrupt 1 digital pin 3 connected SW1
}
void loop() {
} // end loop
void myDelay(int x) {
for(int i=0; i<=x; i++)
{
delayMicroseconds(1000);
}
}
void ISR0()
{
toggle(LED1);
}
void ISR1(){
detachInterrupt(0); // not needed!
toggle(LED2);
myDelay(5000); // 5 sec.
toggle(LED2);
attachInterrupt(0, ISR0, RISING); // not needed
flag1 = 0; // clear flag
}
In the above sketch press SW0 and LED1 will toggle. Press SW1 and LED2 will turn on
for 5 seconds at the same time ISR1 disconnects INTR0. Press SW1 during this time
and when the 5 seconds are up LED1 will then toggle. If SW0 is pressed more than
once during that 5 seconds only one will be remembered because flag0 will be
interpreted as HIGH only once even if the count is greater than 1.
Final Example
#define LED1 9
#define LED2 10
#define SW1 2
#define SW2 3
volatile byte flag0 = LOW; // declare IRQ flag
volatile byte flag1 = LOW; // declare IRQ flag
void setup() {
pinMode(LED1, OUTPUT);
pinMode(LED2, OUTPUT);
digitalWrite(LED1, 0); // LED off
digitalWrite(LED2, 0); // LED off
pinMode(SW1, INPUT);
pinMode(SW2, INPUT);
attachInterrupt(0, ISR0, FALLING);
attachInterrupt(1, ISR1, RISING);
}
void loop() {
noInterrupts();
toggle(LED2);
myDelay(2000);
toggle(LED2);
myDelay(2000);
interrupts();
// no other interrupt work except from here to begin loop.
} // end loop
void myDelay(int x) {
for(unsigned int i=0; i<=x; i++)
{
delayMicroseconds(1000);
}
}
void ISR0()
{
toggle(LED1);
}
void ISR1(){
toggle(LED2);
myDelay(5000); // 5 sec.
toggle(LED2);
flag1 = 0; // clear flag
}
Your turn to test this sketch.
That completes this introduction to Arduino interrupts.