Interrupts

What is an interrupt? 

Interrupts are an extremely useful mechanism built into Arduinos. They can be used to have your microcontroller react to execute a function when a specified event occurs and then return to where it left off in the code.

In the context of this project we use interrupts, to calibrate the motor offset. We need to take a rolling average of the wheel's rotation and do some comparisons on that. The most important thing to getting a correct offset is good data. Our use of interrupts ensures that we always have the most up to date data as soon as possible.

So why can't we just use a simple function to count the ticks? An interrupt frees up the processor to continue doing other important things like run the motors. If we didn't use an interrupt then the processor would have to stop running the motors in order to count the ticks which might result in a tick being missed, thus throwing off the counts.

How to use interrupts 

Only certain pins can be used as interrupts. On your microcontroller these pins are Digital pins 2 and 3. These specific pins can be set to trigger on RISING or FALLING signal edges. This means that when you open your Serial Plotter and see a graph of the signal inputs you can have a interrupt triggered and your function executed when the signal goes from LOW to HIGH (RISING) or from HIGH to LOW (FALLING). This also means that the event that triggers your interrupt has to be connected to either pin 2 or 3.

Here we have a simple button sketch included in the Arduino IDE (Files -> Examples -> Digital -> Button):

Download file Copy to clipboard
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
const int buttonPin = 6;     // the number of the pushbutton pin
const int ledPin =  13;      // the number of the LED pin

// variables will change:
int buttonState = 0;         // variable for reading the pushbutton status

void setup() {
  // initialize the LED pin as an output:
  pinMode(ledPin, OUTPUT);
  // initialize the pushbutton pin as an input:
  pinMode(buttonPin, INPUT);
}

void loop() {
  // read the state of the pushbutton value:
  buttonState = digitalRead(buttonPin);

  // check if the pushbutton is pressed.
  // if it is, the buttonState is HIGH:
  if (buttonState == HIGH) {
    // turn LED on:
    digitalWrite(ledPin, HIGH);
  }
  else {
    // turn LED off:
    digitalWrite(ledPin, LOW);
  }
}

Say instead you want your program to do something else and only blink the LED when a certain event occurs. You would need to attach your LED to an interrupt pin first (pins 2 or 3) and then initialize or "attach" and interrupt in your setup. When your code executes it runs the main code and when an event occurs (the button being pressed) the processor stops running the main code and jumps to the interrupt function and runs that. When it finishes it jumps back into the main code right where it left off!

Download file Copy to clipboard
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const int buttonPin = 2;     // the number of the pushbutton pin
const int ledPin =  13;      // the number of the LED pin

// variables will change:
volatile int buttonState = 0;         // variable for reading the pushbutton status

void setup() {
  // initialize the LED pin as an output:
  pinMode(ledPin, OUTPUT);
  // initialize the pushbutton pin as an input:
  pinMode(buttonPin, INPUT);
  // Attach an interrupt to the ISR vector
  attachInterrupt(0, pin_ISR, CHANGE);
}

void loop() {
  // your main code goes here!
}

void pin_ISR() {
  buttonState = digitalRead(buttonPin);
  digitalWrite(ledPin, buttonState);
}

The syntax for the attachInterrupt function is as follows: attachInterrupt(digitalPinToInterrupt, ISR, mode).

digitalPinToInterrupt This is the interrupt vector which is a reference to where in the processor should look to see if an interrupt should be triggered. On your Seeeduino pins 2 and 3 correspond to interrupt vectors 0 and 1 respectively.

ISR An ISR is an Interrupt Service Routine. These functions are unique because they cannot have an parameters and should not return anything. They should be short so that the main code function is not delayed too long and if multiple ISR's are called, they execute in an order that depends on the priority they have. The delay() and millis() functions will not work inside of an ISR because both functions require interrupts to work! In short, what goes here is the name of the function you want executed when the interrupt is triggered.

mode This defines what type of event should trigger the interrupt. There are four predefined constants listed below:

  • 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.

Quick note on volatile variables

When you compile code the compiler not only turns your code into something a processor can read but it also gets rid of unused variables. When using an interrupt it might seen as if the variable does not change because its state is determined by an external event such as user input. By declaring the buttonState to be a volatile int, like in the above example, you are telling the processor that even though it seems as though the buttonState variable doesn't change and isn't used, it actually is.

Wrapping up Interrupts 

Interrupts are an extremely useful tool that allow you to free up your microcontroller's processor and react to real time events. After reading through this tutorial we encourage you to take a closer look at the main code of this kit and see how interrupts are used with your robot. For more information on interrupts, how to use them, and more examples follow this link.