Concrete Computing
20. Interrupts




20.1 How does the outside world get my attention?

The Arduino programs you have created detect changes in the world by polling. That is, your program reads some kind of input from the external world and sees if it has changed. Thus, you detect that a button has been pressed by noticing that it has changed from being up the last time you polled to being down for this poll. Think of polling as in taking a survey - to detect a change in mood of the population you have to ask them again.

The problem with polling is that most change events happen infrequently, and so much of the time a poll is not detecting any change in the world. Thus a substantial amount of processor time is wasted. Even worse, if the change event is of very short duration, then unless you poll at a rate fast enough to see the event, you will miss it. So the worst kind of events to look for, as far as a processor is concerned, are infrequent and of short-duration.

One way to address this problem is through an interrupt. An interrupt is the hardware equivalent of the unhaappy crowd throwing a brick through your window. No need to poll them to see if they are unhappy.

Interrupts are conceptually simple. An interrupt is associated with an outside event by attaching an interrupt handling procedure to it. When the event occurs, if enabled, it will create an interrupt that causes the interrupt handling procedure to be called. You can think of an interrupt as creating an unexpected procedure call in the middle of whatever code happens to be running. When the interrupt handler returns, the program resumes right after the point it was interrupted. The process of detecting an event, interrupting the current execution of the process, calling the hander, doing work associated with the interrupt, and then returning back to the original execution is called servicing the interrupt.

Interrupts are such a powerful notion that they are even used in casual conversation, as in, "hold on a bit while I service this phone interrupt from my mother."

20.2 The process for servicing an interrupt

Here is a naive example:

code/Interrupts/IntBlink/IntBlink.pde

    #include <TimerThree.h>
    // variables that can be concurrently updated should be marked volatile
    // so the compiler will not optimize them.
     
    volatile byte state = 0;  // the LED state 0 LOW or 1 HIGH
     
    #define LED 12
     
    void tick() {
      // this procedure is called on each timer period expire
      digitalWrite(LED, state);
      state = !state;
      }
     
    long int tick_period = 500000;
     
    void setup()
    { Serial.begin( 9600 );
      Serial.println("Starting");
     
      Timer3.initialize();
     
      pinMode(LED, OUTPUT);
     
      // tick will occur every 500000 uS, i.e. 1/2 second.
      Timer3.attachInterrupt(&tick, tick_period);
    }
     
    void loop() {
     
        Serial.print("x");
        delay(3000);
     
    }




20.3 Pitfalls

There are many pitfalls to consider when handling interrupts, but the most important are:

  1. Modifying a shared data structure while the same data structure is in the process of being modified by another block of code. This concurrent update can result in an inconsistent state for the data structure.

  2. Taking too much time to handle the interrupt. Since the handler interrupts the flow of the currently executing code, it makes the original code run longer. This may be a factor if the original code was doing something time critical, like generating a video signal.

  3. Attempting to perform an operation that requires interrupts to be enabled. While executing an interrupt handler, interrupts are usually disabled. Thus you should not invoke an operation that requires interrupts in order to succeed. For example, reading from a serial port usually requires an interrupt to signal the arrival of a character. So it is generally not a good idea to read inside and interrupt handler.
The issue of concurrent updates can be quite subtle. Even updating a single variable can be a problem. An int on the Arduino consists of two bytes. So even something simple like x = y to assign one int to another takes a number of instructions. The interrupt can occur during this sequence, and corrupt the assignment if it happens to also modify x or y. (Note, this case has actually occured in radiotheraphy machines, and caused patient deaths.)

To address this problem requires the notion of a critical section. In essence, a critical section is a piece of code that once entered must run to completion without being interrupted. For example, the code that updates a shared resource must be allowed to complete the update in order to maintain consistency of the resource. Concurrent updates and critical sections appear in all non-trivial systems, and must be carefully addressed.

Things get more complicated if higher priority interrupts are allowed to interrupt a handler in the process of servicing the interrupt. This can lead to a priority inversion, in which a low priority task prevents a high priority one from running. One way to prevent priority inversions is to disable all interrupt processing while an interrupt is being serviced - all the more important to service an interrupt quickly. This is done with the interrupts() procedure to turn them on, and noInterrupts() to turn them off.

20.4 Examples



code/Interrupts/IntBlink2/IntBlink2.pde

    #include <TimerThree.h>
     
     
    // n beats against m 
     
    #define LED1 11
    #define LED2 12
     
     
    // 1/2 of the period of each of the lights, in milliseconds
    // the light is on for half and off for half
    int half_period1;  
    int half_period2;  
     
    // the number of ticks since the last light change
    int nticks1 = 0;
    int nticks2 = 0;  
     
    // the current state of the light
    int state1 = 0;
    int state2 = 0;
     
    // the clock ticks once every millisecond
    void tick() {
      // this procedure is called 1000 times per second
     
      // if exceeded the period, change state of output value and reset
      if ( nticks1 >= half_period1 ) {
        digitalWrite(LED1, state1);
        state1 = ! state1;
        nticks1 = 0;
      } else {
        nticks1++;
      }
     
      // if exceeded the period, change state of output value and reset
      if ( nticks2 >= half_period2 ) {
        digitalWrite(LED2, state2);
        state2 = ! state2;
        nticks2 = 0;
      } else {
        nticks2++;
      }
     
    }
     
    void setup()
    { Serial.begin( 9600 );
      Serial.println("Starting");
     
      Timer3.initialize();
     
      pinMode(LED1, OUTPUT);
      pinMode(LED2, OUTPUT);
     
      // tick will occur at 10000 Hz
      Timer3.attachInterrupt(&tick, 1000);
     
      half_period1 = 300;
      half_period2 = 200;
    }
     
    void loop() {
     
        // do something boring
        Serial.print("x");
        delay(50);
     
     
    }


code/Interrupts/CriticalSect/CriticalSect.pde

    #include <WProgram.h>
    #include <Wiring.h>
    #include <TimerThree.h>
     
    // Example of incorrect critical section handling
     
    #define LED1 11
    #define LED2 12
     
    /* interrupts can be manipulated with 
        noInterrupts(); 
        interrupts(); 
    we can protect critical sections with noInterrups - interupts pairs.
    We have to make sure any concurrent accesses are protected, not just
    modifying ones.
    */
     
     
    long int tick_period = 1000;
    const int list_len = 32;
    volatile int list[list_len];
     
    // add 1 to each element of list l
    void inc_list(volatile int * l, int len) {
        int i;
        for (i=0; i<len; i++) { l[i] = l[i] + 1; }
        }
     
    // check that l is in increasing order by increment 1,
    // return true of so, false if not
    int list_ok(volatile int * l, int len) {
        int i;
        for (i=0; i<len-1; i++) { 
            if ( l[i+1] != l[i] + 1 ) {
                return 0;
                }
            }
        return 1;
        }
     
    // the clock ticks once every timer expire
    void tick() {
      inc_list(list, list_len);
    }
     
    void setup()
    { Serial.begin( 9600 );
      Serial.println("Starting");
     
      Timer3.initialize();
     
      pinMode(LED1, OUTPUT);
      pinMode(LED2, OUTPUT);
     
      // initialize the list
      for (int i=0; i < list_len; i++) {
        list[i] = i;
        } 
     
      // tick is called 10^6/tick_period times per second
      Timer3.attachInterrupt(&tick, tick_period);
     
    }
     
    long int step_num = 0;
    void loop() {
        step_num++;
     
        noInterrupts();
        inc_list(list, list_len);
        interrupts();
     
        if ( ! list_ok(list, list_len) ) {
            Serial.print(step_num);
            Serial.println(" List is broken");
            }
        delay(50);
    }


code/Interrupts/Button/Button.pde

    // the external LED is attached to this pin.
    // onboard LED is attached to pin 13, 
    byte ledPin = 13;
    byte pressPin = 11;
     
    /* interrupt information
     
    Most Arduino boards have two external interrupts: 
        0 (on digital pin 2) 
        1 (on digital pin 3) 
    The Arduino Mega has an additional four: 
        2 (pin 21)
        3 (pin 20)
        4 (pin 19)
        5 (pin 18). 
    */
     
    // the pushbutton is attached to this digital input pin
    int buttonPin = 21;
    int buttonInterrupt = 2;
     
    // state of the button: 0 is up, 1 is pushed
    // volatile because altered in an interrupt service routine
     
    volatile byte buttonState = 0;
     
    void buttonPress() {
     
      if ( buttonState == 0 ) {
          buttonState = 1;
          digitalWrite(pressPin, HIGH);
          /* 
            Normally you should not do I/O like this in a handler.
          The only reason it works here is that interrupts are not
          required for output to the on-chip serial ports.  So this
          works.  Interrupts are needed for reading, so you should not
          do a read inside a handler.
          */
     
          Serial.println("*");
      }
    }
     
    void buttonHandled() {
      // indicate that the button interrupt has been handled and
      // we are ready for more
      
      digitalWrite(pressPin, LOW);
      /* since the only other code that could set the button state
        is in the interrupt handler, and buttonState is a single byte,
        then this operation is safe.  buttonState changes in one 
        instruction.
      */
      buttonState = 0;
    }
      
    void setup() {
      Serial.begin( 9600 );
      Serial.println("Starting");
     
      // configure ledPin to be a digital output
      pinMode(ledPin, OUTPUT);
      pinMode(pressPin, OUTPUT);
      
      digitalWrite(pressPin, LOW);
     
      // set buttonPin to INPUT and 
      // turn on internal pull up resistor 
      pinMode(buttonPin, INPUT);
      digitalWrite(buttonPin, HIGH);
      
      // establish interrupts on button transitions
     
      // FALLING means create an interrupt on a HIGH to LOW change
      attachInterrupt(buttonInterrupt, buttonPress, FALLING);
     
      // LOW means create an interrupt as long as LOW is present
      // attachInterrupt(buttonInterrupt, buttonPress, LOW);
     
      Serial.println("OK");
    }
     
    void loop() {
     
      Serial.println(buttonState, DEC);
      // see if button was pressed at some point
      if ( buttonState == 1 ) {
        Serial.println("TaDa");
        delay(75);
        buttonHandled();  
      }
     
     
      delay(200);
     
    }
     




20.5 Subtle Issues

Here is an example where we want to detect a button press and do something involving the lcd display. Because the lcd display interface requires reads from the serial port, it needs interrupts to be enabled. So no lcd operations are possible inside the interrupt handler. Instead we need to queue up the actions to be performed after the button has been pressed.

We also want to debounce the key presses via software, instead of using external hardware on the button. So the debounce code needs to know the last time that the last key was pressed, and not process any further "presses" until a suitable debounce time has passed.

If we debounce in hardware, then we will only get one key press interrupt. If we debouncd in software, then we will get a flurry of key press events, each of which needs to be handled.

The following example shows just how much bouncing goes on. NOte the use of critical sections around the linked list accessing.

code/Interrupts/Bounce/Bounce.pde

    #include "assert13.h"
    #include "ll.h"
     
    // the external LED is attached to this pin.
    // onboard LED is attached to pin 13, 
    byte ledPin = 13;
    byte pressPin = 11;
     
    /* interrupt information
     
    Most Arduino boards have two external interrupts: 
        0 (on digital pin 2) 
        1 (on digital pin 3) 
    The Arduino Mega has an additional four: 
        2 (pin 21)
        3 (pin 20)
        4 (pin 19)
        5 (pin 18). 
    */
     
    // The pushbutton is attached to this digital input pin
    // and ground.  With the pullup resistor turned on this
    // creates a falling signal when the button is pushed.
    // To create many interrupts, just rub the two wires 
    // together.
    int buttonPin = 21;
    int buttonInterrupt = 2;
     
    /* button press event */
    typedef struct {
        unsigned long time;
        int data;
        } intr_event;
     
    // a list of interrupt events 
    // volatile because altered in an interrupt service routine
    volatile linked_list intr_list;
     
    void buttonPress() {
      // Serial.println("Push");
      // return;
      
      /* add this button press event to the processing queue */
      intr_event * event;
      
      // these must be freed when removed from list!
      event = (intr_event *) malloc(sizeof(intr_event));
      assert13(event != 0, 1);
     
      event->time = micros();
      event->data = 1; 
     
      addElement(intr_list, event);
     
    }
     
      
    void setup() {
      Serial.begin( 9600 );
      Serial.println("Starting");
     
      // configure ledPin to be a digital output
      pinMode(ledPin, OUTPUT);
      pinMode(pressPin, OUTPUT);
      
      digitalWrite(pressPin, LOW);
     
      // set buttonPin to INPUT and 
      // turn on internal pull up resistor 
      pinMode(buttonPin, INPUT);
      digitalWrite(buttonPin, HIGH);
     
      intr_list = initList(); 
      // establish interrupts on button transitions
     
      // FALLING means create an interrupt on a HIGH to LOW change
      attachInterrupt(buttonInterrupt, buttonPress, FALLING);
     
      Serial.println("OK");
    }
     
    void loop() {
      intr_event * event;
      
      // get the next event from the queue and display it.
      
      noInterrupts(); // begin critical section wrt intr_list
      event = (intr_event *) removeElement(intr_list); 
      interrupts(); // end critical section
     
      if ( event != 0 ) {
          Serial.print(event->time, DEC);
          Serial.print(":");
          Serial.println(event->data);
          free(event);
          }
        
      delay(100);
    }


code/Interrupts/Bounce/assert13.h

    #ifndef _assert13_h
    #define _assert13_h
    void assert13(int invariant, int code);
    #endif


code/Interrupts/Bounce/assert13.cpp

    #include <WProgram.h>
    #include "assert13.h"
    // create a busy-loop delay, since timers are off
    int hard_loop(unsigned long int n) {
      // this introduction of y and the Serial.print
      // are to mess with gcc's optimization of the loop
      int y = 0;
      while (n > 0) {
        y = y + n;
        n--;
      }
      Serial.print(""); 
      return y;
    }
     
    #define DELAY_COUNT 1000000L
     
    /* assertion checker
       assert13(invariant, code);
    if invariant is false (i.e. 0) then fail and enter
    a hard loop with interrupts disabled and repeatedly
    sending code to the serial monitor, while blinking 
    the LED on pin 13.
     
    There is a small window in which an interrupt could
    occur, and in which a failure could call assert13.
    How would we guard against this?
    */
     
    void assert13(int invariant, int code) {
      unsigned long int count;
      if ( invariant ) { return; }
      Serial.println("Assertion failure");
      noInterrupts();
      pinMode(13, OUTPUT);
      while ( 1 ) {
        Serial.println(code);
        digitalWrite(13, LOW);
        // Serial.println("LOW");
        hard_loop( DELAY_COUNT );
        digitalWrite(13, HIGH);
        // Serial.println("HIGH");
        hard_loop( DELAY_COUNT );    
      }
    }


code/Interrupts/Bounce/ll.h

    #ifndef _ll_h
    #define _ll_h
    /*
       A linked list contains nodes which hold the information for that node
       and the pointer to the next node in the list 
    */
    typedef struct node {
        void * val;
        node * next;
    };
     
    /* A linked list is represented by a handle, which is a 
       pointer to the linked list control block.
       
       The control block maintains a pointer to the head and tail nodes
       We maintain length information so that we don't have to compute it.
    */
    typedef struct linked_list_cb {
        node * head;
        node * tail;
        int length;
    };
     
    // typedef linked_list_cb * linked_list;
    typedef linked_list_cb * linked_list;
     
    // create a linked list and return the handle
    linked_list initList();
    void addElement(linked_list list, void * val);
    void * removeElement(linked_list list);
    int getLength(linked_list list);
    #endif


code/Interrupts/Bounce/ll.cpp

    #include <stdlib.h>
    #include "assert13.h"
    #include "ll.h"
     
    // create a linked list and return the handle
    linked_list initList() {
      linked_list list;
      list = (linked_list) malloc(sizeof(linked_list_cb));
      assert13(list != 0, 16); 
      
      list->head = 0;
      list->tail = 0;
      list->length = 0;
      return list;
    }
     
    int getLength(linked_list list) {
      return list->length;
    }
     
    void addElement(linked_list list, void * val) {
        node * n = (node *) malloc(sizeof(node));
        assert13(n != 0, 17);
     
        n->val = val;
        n->next = 0;
        if ( list->length != 0 ) {
          list->tail->next = n;
        } else {
          list->head = n;
        }
        list->tail = n;
        list->length++;
    }
     
     
    // Should not call if list->length == 0
    void * removeElement(linked_list list) {
        if (list->length == 0) {
            // return the null pointer if the list is empty
            return 0;
            }
            
        void * rval = list->head->val;
     
        node * new_head = list->head->next;
        /* Now we can free the old head so that we don't leave unused memory 
           allocated
        */
     
        free(list->head);
        list->head = new_head;
        if ( list->head == 0 ) {
          list->tail = 0;
        }
     
        list->length--;
     
        return rval;
    }
     

20. Interrupts
Concrete Computing / Version 1.34 2012-02-03