Interrupt - Daniel-Git-Hub/ELEC3042 GitHub Wiki

Interrupts and you, from timers to buttons, with a brief interlude for sleeping

The ATMEGA328p has many different interrupts, with the complete list being able to be found on page 74 of the datasheet.

An interrupt is a routine (similar to a function) that when the criteria is met (such as the button being pushed, or a timer matching its compare) it will immediately run the code inside that routine then going back and running the code that it was initially running.

Below is code that will toggle pin B1 (Arduino pin 9) every 1 second and will toggle B0 (Arduino pin 8) everytime the button is pressed.

Here is the complete code: https://github.com/Daniel-Git-Hub/ELEC3042/blob/main/Interrupt/InterruptExample.c

Part 1) Headers (not libraries)

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>

In C headers are used to tell the compiler how it should translate our C code into assembly. Note that Rex banned libraries and not headers in this unit, which broadly speaking allows you to include any of the “avr/*.h” files. This headers are needed as to globally enable interrupts (and sleep) a special assembly code needs to be called instead of the standard register manipulation

Part 2) Init (setup)

void init(){
  //set timer1 (which we use for delays and shifting the LEDs) to CTC with a prescalar of 256
  //require the use of 16 bit timer as it is the only timer with capacity to delay 0.5+ seconds
  TCCR1A = 0;
  TCCR1B = 0b00001100;
  TIMSK1 = 0b00000010;
  OCR1A = delay;
  
  //pin modes
  DDRB   = 0b00000011;  //make pin 8 and 9 as outputs
  DDRD   = 0b00000000;  //make sure that pin 2 is set to input
  PORTD  = 0b00000100; //turn on pullup resistor on pin 2
  
  //interupt for pin 2 (INT0))
  EICRA = 0b00000010; //trigger interupt on low level (could also use falling edge)
  EIMSK = 0b00000001; //enable interupt mask
  
  sei(); //globally enable all interupts

  //set up sleep modes
  set_sleep_mode(SLEEP_MODE_IDLE); //It is possible to turn off even more subroutines to save even more power
  sleep_enable();                  //sets the global sleep flag

}

A) Timer1

Shortly I will release a discussion thread on timers, in particular how they practically function and where the magic formulas in the data sheet come from. In that post I can discuss what the TCC1A, TCC1B, OCR1A do. But it is sufficient knowledge to know that with our delay the timer will count up to OCR1A every 1 or so seconds and then reset back to a count of 0 (due to us selecting CTC).

TIMSK1 (Timer 1 Interupt Mask) = 0b00000010;

Sets the bit representing OCIE1A, Timer 1, Output Compare A Match Interrupt Enable to 1 (page 144, 145). This tells the board to call our interrupt function whenever Timer 1 matches OCR1A.

To be more precise it tells the board to call our routine whenever the OCF1A (Output compare A match flag) (located in bit 1 of the TIFR1 [Timer Interrupt Flag Register]) is set to 1, which the board does when OCR1A matches the current count.

B) Pin modes

By “turning on” a pin which is declared as an input we enable the inbuilt resistor which pulls the pin high. By connecting the button to this pin and ground we allow that pin to read as 1 when it is not pressed as the current is going from the internal 5v port through the internal resistor and then into are input pin and reading as 0 when it is pressed (as this will short the pin to ground)

C) External Interrupt INT0

Here we define that we want to enable the interrupt on INT0 (pin 2) and that we want it to trigger on a falling edge (remember that when the button is pressed it goes from high to low).

EIMSK (External Interupt Mask register) = 0b00000001; enables that interrupt to occur when the corresoponding bit in EIFR (bit 0, INTF0) is set

EICRA = 0b00000010; tells the board to set the bit in EIFR on a falling edge (the different modes are listed on page 80), it should be noted that mode 00 (The low level of INT0 generates an interrupt request) also works for this purpose

D) Global flags

Here we globally enable both sleep mode and interrupts.

For an ISR to be ran BOTH the global interrupt flag (done by running “sei()”) and the individuals interrupt flag (for example how we set EIMSK) must be set.

I will go further into sleep mode in part 4.

Part 3) ISR

//ISR stands for Interupt Service Routine
//TIMER1_COMPA_vect tells it that this routine is for for TIMER1_COMPA interupt
ISR(TIMER1_COMPA_vect){
  PORTB = (PORTB & 0b00000001) | ((~PORTB) & 0b00000010); //toggles LED on B1 preserving the state of B0
}

ISR(INT0_vect){
  //NOTE that code to deal with button bouncing needs to be implemented as it is likely that one press of the button will generate more then one signal
  PORTB = (PORTB & 0b00000010) | ((~PORTB) & 0b00000001); //toggles LED on B0 preserving the state of B1
}

Here we use a special function “ISR( )” passing our interrupt name as an argument to tell the board to run this code when the interrupt flag is set.

Aside that in this context vector is just another name for an interrupt. The reason it is referred to as an vector(Rex can correct me on this) is that an Interrupt Routine moves the current instruction pointer to where our ISR is (i.e it is a location)

The difference between ISR and a typical function is that we can’t pass any additional arguments into it and we must declare it with an interrupt name. To find the interrupt name look on page 74 and append it with “_vect”.

For example “INT0” becomes “INT0_vect”

Part 4) Main

int main(void) {
 init();
 
 while(1){ //when a ISR finishes it will go back here (or the line below) and put the chip back to sleep
   sleep_cpu(); //every time a interupt finishes turn off most functions by idling (prevents busy looping). Importantly this keeps both the hardware interupts and the timers on
 }
}

First we run init once the we run our while loop.

First sleep mode is when the chip turns off various features. In particular idle mode will mean that code will no longer execute BUT timers will still count, and external interrupts can still be called. And on these interrupts the chip will wake back up

It also leaves on a few other features that we won’t be using such as BOD and the analog to digital. For a complete list look at page 48

This means that once sleep_cpu() will only run once between each interrupt. And once an interruptis finished the code will pick back on the next line, which is the closing tag of the while loop jumping back to the start of that loop and running the sleep command again.

The proof and example of this can be done by running this code with LEDs attached to pins 8 - 13 (note that all 6 LEDS aren't needed)

And noticing that “PORTB++;” runs only once every second (i.e when the timer interrupt occurs)

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>

void init(){
  //set timer1 (which we use for delays and shifting the LEDs) to CTC with a prescalar of 256
  //require the use of 16 bit timer as it is the only timer with capacity to delay 0.5+ seconds
  TCCR1A = 0;
  TCCR1B = 0b00001100;
  TIMSK1 = 0b00000010;
  OCR1A = 62499; // represents 1s
  
  //pin modes
  DDRB  = 0b11111111; //make pin 8 and 9 as outputs
  
  sei(); //globally enable all interupts

  //set up sleep modes
  set_sleep_mode(SLEEP_MODE_IDLE); //It is possible to turn off even more subroutines to save even more power
  sleep_enable();         //sets the global sleep flag
 
}

int main(void) {
  init();
  
  while(1){ //when a ISR finishes it will go back here (or the line below) and put the chip back to sleep
    sleep_cpu(); //every time a interupt finishes turn off most functions by idling (prevents busy looping). Importantly this keeps both the hardware interupts and the timers on
    PORTB++;
  }
}

Why use sleep compared to (which does work)?

while(1){
  ;
}

Because if we use the above code then the chip is running the compare and an empty instructions 16 million times per second. This consumes a lot of power, while doing nothing productive. In real life, for the majority of applications a chip will be in idle mode for this reason

⚠️ **GitHub.com Fallback** ⚠️