Arduino Interrupts

Interrupts are events that occur while the program is running, in which case the Arduino interrupts the main program to respond immediately to external events.

Arduino uses a number of interrupts. An example is the first priority interrupt, Reset. USART, SPI, TWI/I2C communication ports. Timers/counters, Watchdog, signal level changes on individual pins of the Arduino device, etc. The following list summarizes the interrupts of ATmega328 based devices in order of priority.

1. Reset
2. External Interrupt Request 0 (pin D2) (INT0_vect)
3. External Interrupt Request 1 (pin D3) (INT1_vect)
4. Pin Change Interrupt Request 0 (pins D8 to D13) (PCINT0_vect)
5. Pin Change Interrupt Request 1 (pins A0 to A5) (PCINT1_vect)
6. Pin Change Interrupt Request 2 (pins D0 to D7) (PCINT2_vect)
7. Watchdog Time-out Interrupt (WDT_vect)
8. Timer/Counter2 Compare Match A (TIMER2_COMPA_vect)
9. Timer/Counter2 Compare Match B (TIMER2_COMPB_vect)
10. Timer/Counter2 Overflow (TIMER2_OVF_vect)
11. Timer/Counter1 Capture Event (TIMER1_CAPT_vect)
12. Timer/Counter1 Compare Match A (TIMER1_COMPA_vect)
13. Timer/Counter1 Compare Match B (TIMER1_COMPB_vect)
14. Timer/Counter1 Overflow (TIMER1_OVF_vect)
15. Timer/Counter0 Compare Match A (TIMER0_COMPA_vect)
16. Timer/Counter0 Compare Match B (TIMER0_COMPB_vect)
17. Timer/Counter0 Overflow (TIMER0_OVF_vect)
18. SPI Serial Transfer Complete (SPI_STC_vect)
19. USART Rx Complete (USART_RX_vect)
20. USART, Data Register Empty (USART_UDRE_vect)
21. USART, Tx Complete (USART_TX_vect)
22. ADC Conversion Complete (ADC_vect)
23. EEPROM Ready (EE_READY_vect)
24. Analog Comparator (ANALOG_COMP_vect)
25. 2-wire Serial Interface (I2C) (TWI_vect)
26. Store Program Memory Ready (SPM_READY_vect)

Our Arduino program may have multiple interrupt events. In this case, only one interrupt can run at a time, the other interrupts are executed after the currently running interrupt depending on their priority.

Interrupts are enabled by default, but can be temporarily disabled by calling the noInterrupts() function. This feature can be useful when we are in a critical section of the Arduino code and we don’t want any external events to affect the running of the code. An exception to this is the reset interrupt, which cannot be disabled.

We only use it in justified cases, because if we turn off the interruptions, it can cause certain functions to malfunction. When the critical piece of code has finished running, interrupts are enabled by calling the interrupts() function.

void loop()
{
  noInterrupts();
  // critical code snippet here
  interrupts();

  // additional code...
}

External interrupts can be useful e.g. for managing button presses, monitoring encoder pulses, while other tasks can be performed in the main program.

The attachInterrupt() function allows a change in the Arduino’s dedicated pins to trigger an interrupt event. In the following table, you can see the pins that can be used for interrupts on some Arduino boards.

Arduino boardLegs for breaking
Uno, Nano, Mini and other 328 based boards2, 3
Uno WiFi Rev.2, Nano Everyall digital pins
Mega, Mega2560, MegaADK2, 3, 18, 19, 20, 21 (  pins 20 and 21  cannot be used for interrupts while they are used for I2C communication)
Micro, Leonardo, other 32u4 based boards0, 1, 2, 3, 7
Zeroall digital pins except 4
MKR Family boards0, 1, 4, 5, 6, 7, 8, 9, A1, A2
Nano 33 IoT2, 3, 9, 10, 11, 13, A1, A5, A7
Nano 33 BLE, Nano 33 BLE Senseall pins
Dueall digital pins
101all digital contacts (Only contacts 2, 5, 7, 8, 10, 11, 12, 13 work with the  CHANGE  function)

With the help of interrupt routines in Arduino, called (ISR) Interrupt Service Routines, we can handle these events easily and immediately. However, there are some rules to follow when using interrupt routines.

An ISR can have no parameters and no return value. If data needs to be transferred from the main program to the ISR, or if we expect a return value from the ISR, use global variables. We declare these global variables as volatile.

The interrupt routine must finish running as quickly as possible, only the most important things are done in the body of the ISR, the time-consuming tasks are handed over to the main program.

Avoid timings. delay() requires interrupts to work, it won’t work inside an ISR. The millis() function also uses interrupts to count, so it will never be incremented in the body of the interrupt routine. micros() works at first, but after a few ms it also starts to behave erratically. Since delayMicroseconds() doesn’t use a counter, it can work fine.

The attachInterrupt() function is used to initialize the external interrupt. The function has three parameters.

attachInterrupt(interrupt, ISR, mode);

The first parameter is the interrupt number. This can be specified in several ways. We can enter the interrupt number directly, but this is not recommended, because on some cards the external interrupts may be assigned to a different pin, so this solution reduces the portability of the code. Another option is to specify the pin number, but this does not work on all Arduino boards, so this is also not recommended.

The correct solution is to use the digitalPinToInterrupt(pin) function. As a parameter of the digitalPinToInterrupt function, we enter the number of the pin used for the interrupt and it returns with the interrupt number of the current arduino board.

int pin = 2;
attachInterrupt(digitalPinToInterrupt(pin), ISR, mode)

The second parameter of the attachInterrupt() function is the ISR, the name of the function that handles the interrupt.

The third parameter determines which phase of the signal level change of the pin used for the interrupt will trigger an interrupt. Four constant values ​​are predefined in Arduino.

LOW, trigger the interrupt when the pin is at a low signal level.

CHANGE, when the signal level of the pin changes in any way, the interrupt is triggered.

RISING when the pin changes from low to high, the interrupt is activated.

FALLING when the pin changes from high to low.

For Arduino Due, Zero and MKR1000 boards, HIGH works, the interrupt is triggered when the pin is at a high signal level.

The attachInterrupt() function must be called in the setup() section, and we also need an ISR to handle the interrupt.

const byte buttonPin = 2;
volatile byte buttonState = LOW;

void setup()
{
  pinMode(buttonPin, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(buttonPin), buttonPressed, FALLING);
}

void loop()
{
  // Main program....
}

void buttonPressed()
{
  buttonState = !buttonState;
}

It may happen that in our arduino program we no longer need to monitor the event that triggered the interrupt, or we want to use the pin used for the interrupt in a different way. In such a case, we can turn off the given interrupt to free up the resources of the Arduino.

We use Arduino’s detachInterrupt() function for this purpose. The value returned by the well-known digitalPinToInterrupt(pin) function as a parameter determines which interrupt should be turned off.

detachInterrupt(digitalPinToInterrupt(pin))

A common method of use is the reading of interrupts, for example signals from rotary encoders. By using interrupts, we can avoid pulse loss of the encoder signals in the Arduino program.

Degree Rotary Encoder Module KY-040

Degree Rotary Encoder Module KY-040

CLK: Encoder Pin A
DT: Encoder Pin B
SW: No Pushbutton Switch
+: 5V Supply
GND: Encoder Pin C

Ad

An encoder, or incremental encoder, is a sensor that emits pulses on its outputs when its axis turns. The amount of pulses is proportional to the degree of angular rotation and the resolution of the encoder.

An encoder disk is placed on the shaft of the mechanical incremental encoder, this is the middle, common output of the rotary encoder, we connect this leg to gnd. The encoder disk is a perforated disk, the “A” and “B” pins of the encoder come into contact with this encoder disk.

encoder schematic diagram

When the encoder contacts “A” and “B” touch the encoder disc during rotation, two square signals are generated. These signals are offset relative to each other as one contact contacts the encoder wheel before the other.

When the encoder is turned clockwise, pin “A” connects to the dial before pin “B”. When the “A” pin changes to a low signal level, the “B” pin is still at a high signal level. So when pin “A” changes level, “B” is always at the opposite signal level.

encoder output quadrature signal for clockwise rotation

When the rotary encoder is rotated counter-clockwise, pin “B” will make contact with the encoder wheel first. In this case, contact “A” is equal to the signal level of contact “B” when the level is changed.

encoder output square signal for counterclockwise rotation

If we observe the sequence of changes in the signal level of contacts A and B, we can determine the direction of rotation of the encoder.

Let’s connect a KY-040 rotary encoder to the Arduino UNO based on the picture below. The outputs “A” and “B” (“DT” and “CLK”) of the KY-040 encoder are pulled up to the supply voltage with a 10k resistor on the circuit board.

Connecting KY-040 encoder to Arduino Uno

Then upload the following code to the Arduino UNO. I will not describe the operation of the code separately, I commented a lot in the sketch.

// Arduino Uno pins used for connecting the encoder
const char clkPin = 2;
const char dtPin = 3;
const char switchPin = 4;

// Variables used in the ISR must be declared as volatile.
// variable used to count encoder rotations.
volatile int counter = 0;

// Variable storing the previous state of the encoder CLK pin
volatile int clkLastState = LOW;

// A variable that stores the current state of the encoder CLK pin
volatile int clkCurrentState;


// The ISR used to handle encoder-triggered interrupts
void encoderHandler()
{  
  // The current state of the CLK pin is stored in the clkCurrentState variable
  clkCurrentState = digitalRead(clkPin);
  
  // If the low signal level of the CLK pin goes high
  if((clkLastState == LOW) && (clkCurrentState == HIGH)) 
  {
    // and the signal level of the DT leg is also high
    if(digitalRead(dtPin) == HIGH)
    {
      // the encoder turned counter-clockwise
      // we decrease the value of the counter
      counter--;
    }
    // if, on the other hand, the DT leg is low 
    else
    {
      // the encoder turned clockwise
      // so we increase the value of the counter
      counter++;
    }
    // the current value of the counter is written to the serial monitor. 
    Serial.println(counter);
  }
  // The current state of the CLK pin is transferred to the clkLastState variable
  clkLastState = clkCurrentState;
}

void setup()
{
  Serial.begin(9600);
  
  // The encoder CLK and DT pins are set as inputs. 
  // they have pull-up resistors on the encoder circuit board
  pinMode(clkPin, INPUT);
  pinMode(dtPin, INPUT);
  
  // The SW leg of the encoder pushbutton is also set as an input.
  // here we use the Arduino's internal pull-up resistor
  pinMode(switchPin, INPUT_PULLUP);
  
  // we set the interrupts to monitor the encoder rotation
  attachInterrupt(digitalPinToInterrupt(clkPin), encoderHandler, CHANGE);
  attachInterrupt(digitalPinToInterrupt(dtPin), encoderHandler, CHANGE);
}

void loop()
{
 // we examine the state of the push button 
 if(digitalRead(switchPin) == LOW)
  {
    // if it was pressed, we reset the counter
    counter = 0;
  }
}

advertising – Amazon.com

In the next section, we walk around the arrays.