CPU Interrupted

Up until now, we have used a technique called polling to perform inputs from the Arduino‘s digital I/O pins. Polling consists in actively sampling the status of an external device or memory as a synchronous activity. On the Arduino, this usually occurs within the built-in loop() function. Polling is the method of choice in the majority of cases as it allows a program to easily and synchronously read external device states and values and handle external events. However, there are times when we want a program to handle an external event the moment it happens. This is what we explore in this post on interrupts.

Interrupts

In digital computers, a Central Processing Unit (CPU) sequentially executes instructions that manipulate data. This sequential execution of instructions can be interrupted by events to execute a different sequence of instructions, called an interrupt service routine, allowing the CPU to handle an event the moment it happens. Typically computers can handle interrupts from external sources such as timers, electrical signals, power failure, or events internal to the CPU such as accesses to invalid memory addresses, divisions by zero, and special interrupt instructions.

An interrupt is similar to a function call in that the current program counter is pushed onto the stack, thus remembering where to return upon completion of the interrupt service routine. Upon receiving an interrupt signal, the CPU completes the current instruction, disables interrupts, pushes the program counter onto the stack and calls the interrupt service routine. Upon executing a “return from interrupt” instruction at the end of the interrupt service routine, the program counter is popped from the stack, interrupts are enabled, and execution resumes where it had been interrupted in the program’s sequence of instructions. The difference between interrupt handling and a function call is that interrupt handling is disabled upon entering the interrupt service routine and that while function calls occur within the sequential flow of instructions, interrupts may occur anytime and anywhere within program execution. The fact that interrupts may occur anywhere in a program’s sequence of instructions may cause side effects which we will discuss later in this post.

Setting up Interrupt Service Routines

On the Arduino UNO, interrupt service routines can be written to handle events associated with external interrupt requests, pins, timers, Serial Peripheral Interface (SPI) data transfers, Inter-Integrated Circuit (I2C) data transfers, Universal Synchronous/Asynchronous Receiver/Transmitter (USART), Analog to Digital Converter (ADC), Electrically Erasable Programmable Read Only Memory (EEPROM) and flash memory accesses. In this post, we will only deal with external interrupts using the Arduino built-in function attachInterrupt(). On the Arduino Uno, external interrupts are tied to levels and state changes for digital input pins 2 and 3. Interrupts may be set to occur when the state of the input pin changes, on the rising or falling edge of the signal, or when the input pin is LOW. The attachInterrupt() function is used as follows:

attachInterrupt (interrupt, ISR, mode)

Where interrupt is the number of the interrupt. On the Arduino Uno, interrupt number 0 corresponds to external interrupts from digital pin 2 and interrupt number 1, corresponds to external interrupts from digital pin 3. Interrupt numbers may differ between Arduino board types. For this reason, it is recommended not to use the interrupt number directly, but to use the digitalPinToInterrupt() built-in function that returns the interrupt number associated to the pin. ISR (Interrupt Service Routine) is the name of the function to be called when an interrupt occurs. This function takes no argument and does not return a value. Finally, mode, specifies the mode of operation for the external interrupt. The following values are supported for the mode argument:

  • LOW – Interrupt when the pin is LOW.
  • CHANGE – Interrupt when the pin changes from LOW to HIGH or HIGH to LOW.
  • RISING – Interrupt when the pin changes from LOW to HIGH.
  • FALLING – Interrupt when the pin changes from HIGH to LOW.

Typically, the attachInterrupt() function is called in the built-in setup() function, where we set the pin mode of digital input 2 or 3 to INPUT or INPUT_PULLUP, and attach an interrupt service routine to one of the two pins. For instance, in the following code snippet:

// Switch value will be read from digital input pin 2
define INPORT 2

// Interrupt Service Routine
void buttonPress() {
  // Handle button press
}

// Setup the board.
void setup() {
  // Set Arduino's input ports
  pinMode(INPORT, INPUT);

  //Initialize interrupt service routine
  attachInterrupt(digitalPinToInterrupt(INPORT), buttonPress, FALLING); 
}

We declare a function, buttonPress(), that takes no argument and does not return a value. This function is called when an interrupt occurs. Within the built-in setup() function, we set the mode of digital pin 2 to INPUT using the built-in pinMode() function. We then attach interrupt service routine buttonPress() to digital pin 2. The interrupt service routine is called when the value at the input pin goes from HIGH to LOW, from 5 volts to 0 volts. The built-in function attachInterrupt() is used to attach the interrupt service routine buttonPress() to the digital pin. Note the use of the built-in function digitalPinToInterrupt() to make the use of digital pin 2 independent of board type.

Interrupt Side-Effects

Since interrupt service routines take no arguments and do not return information, they can only exchange information with the rest of the program through global variables. Global variables are variables declared in the program’s header, outside of functions, that are accessible at all times by all parts of the program. We have used global variables already to hold information shared between the setup() and loop() built-in functions and to hold information persistent between iterations of the loop() built-in function.

Side-effects arise because interrupt service routines use and modify information that is also used and modified by the main program. Because interrupts may occur anytime while a program is running, the interrupt service routine may modify information while the main program instructions were in the midst of looking at or even modifying the same information. In the next sections, we will look at two types of side effects that occur within a micro-controller when using interrupts. The first type of side-effect is volatility caused by the use of a high-level programming language when dealing with interrupts. The second type of side-effect is concurrent data access that cause data manipulations to be erroneous in certain conditions.

Volatility

When we write code for the Arduino, we do so in a programming language called C++ which is translated into machine code, real instructions for the Atmega processor on the Arduino Uno board, by a compiler. When it translates C++ code into machine code, the compiler makes assumptions and optimizes the final code as much as it can. It may decide, for instance, never to store a variable in memory and use the processor’s storage registers instead, or it may decide to delay storing the content of a variable in memory until the end of a routine. This may be problematic for variables that are to be used by an interrupt service routine as well as in the main program. The interrupt service routine may start using a variable by loading it from memory while the main program was manipulating the same variable temporarily held in one of the processor’s a storage register.

The C++ language supports a directive called volatile which directs the compiler to always use the variable from main memory and not from a storage register. A variable should be declared volatile if its value can be changed outside the scope and control of the code section in which it appears. This is the case of all variables that are manipulated by the interrupt service routine as well as in the rest of the program’s code. the volatile directive is used within the declaration of a variable and generally precedes the variable type declaration as in the following variable declaration:

volatile int counter = 0;

Variable counter is declared as a volatile signed integer (int) initialized with the value 0. Its value can be changed by the main program as well as within an interrupt service routine directly in main memory.

Concurrent Data Access

Errors due to concurrent data access occur when, upon an interrupt, the interrupt service routine uses or modifies global variables while code in the main program uses or modifies the same global variables. Several things can go wrong, among which: the main program reads the global variable, the interrupt service routine stores a new value for the global variable, then the main program overwrites the global variable, causing the interrupt routine to have no effect; the main program updates the global variable with an intermediate computation value, then the interrupt service routine erroneously uses the intermediate value; the main program uses the global variable to make a sequence of operations and in the midst of the sequence, the interrupt service routine modifies the global variable causing errors in the main program sequence.

All of these errors are caused by the fact that a value was used or modified in mid-stride by two parts of the same program. The way to fix these problems is to use and modify values atomically, which is to say in an indivisible way. For instance, if a value used in both the main program and in an interrupt service routine is to be read, modified, and saved, then all of these operations must occur as if done by a single uninterruptible instruction. The way to prevent interruption while a series of instructions is being executed is to disable interrupts while the value is used and then to enable interrupts again. To do this on the Arduino, we use the built-in functions noInterrupts() and interrupts(). Hence, in the following code sequence slightly modified from the previous post:

noInterrupts();
digitalWrite(LED0, litLED == 0);
digitalWrite(LED1, litLED == 1);
digitalWrite(LED2, litLED == 2);
digitalWrite(LED3, litLED == 3);
interrupts();

If we were not to disable interrupts while variable litLED is used, we would run in the possibility that an interrupt service routine modifies the value of litLED between calls to built-in function digitalWrite(). To illustrate the problem better, let’s imagine that interrupts are not disabled and that the value of litLED is 1. The first two digitalWrite() function calls are executed resulting in LED0 being extinguished and LED1 being lit. An interrupt occurs and the interrupt service routine increments the value of litLED by one, then returns. Execution resumes and the third digitalWrite() function call lights LED2 since the value of litLED is now 2. The end result is that LEDs 1 and 2 are simultaneously lit while the intent was to always light only one LED in sequence. Disabling interrupts while the sequence of digitalWrite() function calls execute resolves the problem. It is important to note from this example that the whole sequence must be executed with interrupts disabled because all four LEDs as a whole represent the value of variable litLED. If an interrupt does occur while interrupts are disabled, it is processed as soon as interrupts are re-enabled, right after the call to the interrupts() built-in function.

Putting it Together

In order to demonstrate the use of external interrupts on the Arduino, we will use the same circuit as was used in the previous post, Switch Debouncing. I built a circuit and Arduino program that sequentially turns LEDs on and off once a second as well as every time a high to low edge is detected on digital pin 2 of an Arduino Uno. A push-button is attached to digital pin 2 and we will use an interrupt service routine to react to the push-button being depressed.

The Electronics Setup

In this circuit, the Arduino’s digital input/output pin 2 is connected to a push-button switch and a 10 K pull-up resistor. The other end of the pull-up resistor is connected to the 5 V supply and the other end of the switch is connected to ground. The rest of the circuit consists of 4 LEDs whose cathodes are connected to ground through 330 Ω resistors and whose anodes are connected to the Arduino’s digital input/output 8, 9, 10, and 11 respectively. The following diagram depicts how to connect the different parts using a solderless breadboard, jumper wires, four LEDs, a push-button switch, a 10 K resistor and four 330 Ω resistor.

CPU Interruption Demonstration Sketch

The Arduino sketch used to demonstrate CPU interruption can be found on Github at https://github.com/lagacemichel/CpuInterrupted. Download the sketch CpuInterrupted.ino and load it in the Arduino IDE. You can also copy the following code directly into a new Arduino sketch within the IDE.

/*
Switch Interrupt sketch
Uses four LEDs connected to digital I/O pins 8, 9, 10, and 11 to demonstrate
how interrupts work. The LEDs are lit one after the other, once a second, as
well as when the switch is depressed. When the switch is depressed, it causes
an interrupt that calls an interrupt routine that increments the count and
then displays the LED corresponding to the count.
MIT License
Copyright (c) 2020, Michel Lagace
*/

// Delay time to wait at end of each loop
#define WAIT_TIME 1000

// Switch value will be read from pin 2
#define INPORT 2

// LED values will be output on pins 8, 9, 10, and 11
#define LED0 11
#define LED1 10
#define LED2 9
#define LED3 8
volatile int counter = 0; // Currently lit LED

// Increment count
void countUp() {
  counter++;
  if (counter > 3) {
    counter = 0;
  }
}

// Display count
void displayCount() {
  // Light up the appropriate LED
  digitalWrite(LED0, counter == 0);
  digitalWrite(LED1, counter == 1);
  digitalWrite(LED2, counter == 2);
  digitalWrite(LED3, counter == 3);
}

// Interrupt Service Routine
void buttonPress() {
  countUp();
  displayCount();
}

// Setup the board.
void setup() {
  // Set Arduino's input and output ports
  pinMode(INPORT, INPUT);
  pinMode(LED0, OUTPUT);
  pinMode(LED1, OUTPUT);
  pinMode(LED2, OUTPUT);
  pinMode(LED3, OUTPUT);

  // Initialize currently lit LEDs
  counter = 0;

  //Initialize interrupt routine
  attachInterrupt(digitalPinToInterrupt(INPORT), buttonPress, FALLING);
}

// Repeat forever
void loop() {
  // Count up and display count
  noInterrupts();
  countUp();
  displayCount();
  interrupts();

  // Wait for a while
  delay(WAIT_TIME);
}

In the sketch above, we first define WAIT_TIME, the time to wait before lighting the next LED in the sequence, then INPORT, digital input port 2 from which the program will get interrupts, the four digital output ports attached to the LEDs to light in sequence, and finally, counter, the volatile integer variable maintaining which LED to light. Global variable counter is set to volatile because, as discussed previously, it is used and modified in the interrupt service routine as well as in the main program.

Next, we define two functions that are used throughout the program. Both functions do not take arguments and do not return values. Function countUp() increments global variable counter and keeps its value between 0 and 3. The other function, displayCount(), lights the LED corresponding to the value of counter and extinguishes the other LEDs.

The interrupt service routine buttonPress() is then defined. It is called by the Arduino internal software whenever the push-button is pressed. We will see later how the interrupt service routine is declared to the Arduino system. The buttonPress() routine increments the counter global variable using the countUp() function and then lights the appropriate LED using the displayCount() function. Note that interrupts are already disabled when entering the interrupt service routine which guarantees that all operations within the interrupt service routine are executed atomically, without interruption.

The setup() built-in function, called once before the loop() built-in function is repeatedly called, sets digital input/output port INPORT for INPUT and digital input/output ports LED0, LED1, LED2, and LED3 for OUTPUT. It then initializes the counter global variable to zero. No need to disable interrupts at this point since the interrupt service routine has not been set yet. Next, we declare the buttonPress() interrupt service routine through the attachInterrupt() built-in function. The digitalPinToInterrupt() built-in function is used to map INPORT to the internal interrupt number used by the system. Digital input port INPORT is set to interrupt and call the buttonPress() interrupt service routine when the signal at the INPORT digital input pin goes from HIGH to LOW by using the FALLING parameter.

Finally, the loop() built-in function is repeatedly called. It increments the counter global variable using the countUp() function and then lights the LED associated to the value of counter using the displayCount() function. Both countUp() and displayCount() functions are sandwiched between calls to built-in functions noInterrupts() and interrupts() ensuring that manipulations of the counter global variable are done atomically and that no interrupts occur while the global variable is acted upon. The delay() built-in function is then called to make the program wait the specified amount of time.

Demonstration

Set up the circuit as shown previously and, using the Arduino IDE, compile and download the code onto the Arduino board. As the code starts executing, the LEDs will light in sequence, one after the other, every second. Every time the switch is depressed, an interrupt is generated and the buttonPress() interrupt service routine gets called, which increments the counter and lights the next LED in the sequence, thus demonstrating the use of interrupts on an Arduino board.

Published by

Michel Lagacé

More than 40 years working in the high technology sector, I now share tips and tricks on software and electronics. I also love to cook and to write in my spare time.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s