# Interrupts¶

• Polling to check a signal works, but the processor still needs to check things
• This puts a load on the chip we really do not need
• What we need is for an alarm to go off that we react to
• Welcome to interrupts

## What is an interrupt?¶

Most computer systems support the idea of an interrupt?

• An interrupt is a signal generated by some device and sent to the processor
• These events happen at unpredictable times
• The source of the interrupt can be external or internal.
• The AVR can sense signals though the I/O pins on the chip
• Internal devices can generate them as well
• we will set up Timer0 so it generates an interrupt when it rolls over.

## Asynchronous events¶

• Interrupts are asynchronous events
• we do not know exactly what we will be doing when they happen
• The chip handles interrupts using something like a procedure call
• This call might happen in the middle of your code
• We need to preserve the state of the chip before dealing with the event
• when we return, the original code will not know this happened!

## Recognizing the interrupt¶

• We can turn all interrupts on or off with code
• The AVR has a Global Interrupt Enable flag
• every internal device that can generate an interrupt has an enable flag
• We need to set all these bits correctly for the interrupt system to work

## Controlling the global interrupt system¶

• If allowing interrupts might cause problems, we can do this:
• CLI - disable interrupts
• SEI - enable interrupts

The processor is initialized on power-up with interrupts disabled.

## Handling the interrupt¶

• Basically, the interrupt is handled by a special procedure call.
• It happens between two instructions right after the event
• We need to set up code for the procedure at specific addresses
• Each interrupt source will call a procedure at the assigned address
• We need as many handlers as we have interrupt sources

## AVR Interrupt table¶

• the AVR sets up a jump table, also called an interrupt vector table
• This table starts at address 0x00
• each entry is just a jump to the actual procedure code
• we only need entries at places where we want to handle specific interrupts

## New style AVR code¶

• To get things working with avr-gcc, we need to change code a bit
• The linker will set up the interrupt vector table
• Unfortunately, some simple code becomes not so simple
• We will use macros to make things work correctly!
• avr-gcc will set up the chip!

## Interrupt handler code¶

• The actual handler code looks like other procedures
• except this one ends with a new instruction:
InterruptHandler:
...
reti

• The last instruction is vital.
• It resets the interrupt system after each interrupt is recognized.

## The Reset Vector¶

• one special signal related to the interrupts, but is a bit different.
• This one happens when powering up of the processor
• Some systems have a reset button
• The reset handler is at location 0x00
• We normally place a jump to the actual program start point
• avr-gcc will set this up as part of the main entry system

## Saving Processor State¶

• We need to save the processor state in our handlers
• We know how to do that! (use the stack)
• Save any registers you intend to use
• Save the system flag register SREG as well
• The interrupted code will thank you!

## Using interrupts with Timer0¶

Let’s put this all together with a simple example.

• Our polling code checked the Timer0 Overflow (TOV0) interrupt flag.
• This flag was being set by the timer, but did not generate an interrupt
• We were running with interrupts disabled!
• To generate an interrupt, we need to reprogram the timer (and chip)
• We will use the blinking light for this example
• We want the LED to blink once per second

## Sample program¶

This program will consist of a main routine and timer code in separate files

#include "config.inc"

.extern Timer0Setup
.global main

• The entry point must be named main for this example
• Set project configurarion details in config.inc
• The name main is the entry point in this example

## The interrupt jump table¶

• This table will be set up by avr-gcc
• We need to declare labels defined in the include files for this chip
• This is what Timer.S includes:
        .global     TIMER0_OVF_vect
TIMER0_OVF_vect:


## Setting up the clock¶

For this example, we will run the chip at full speed

        ; set up the system clock
ldi     r24, 0x80               ; set up prescaler
sts     CLKPR, r24
sts     CLKPR, r1               ; set to full speed


## Setting up the LED¶

The LED on the Arduino is on pin 5 of PORTB

        ; set up LED port
sbi     _(DDRB), 5              ; set up the output port (bit 6)
cbi     _(PORTB), 5             ; start off with the LED off


## Finishing up¶

        call    Timer0Setup             ; initialze the timer
1:      rjmp    1b


Huh? Where is the work going to happen? In the handler!

In this simple example, we really have no work for the program to do, other than what will happen when interrupts occur. For that reason, we simply put the main code in an infinite lop. The interrupts will happen, and the processor will take care of those events with the code we provide.

## Timer code¶

timer.S starts up with this code

; Timer.S - Timer0 code for blink

#include "config.inc"

.global Timer0Setup

.section    .data

ISRcount:   .byte   0

.section    .text



We will discuss the ISRcount data item later.

## Timer setup¶

Set up the timer prescaler value here

;----------------------------------------------------------------------
; Initialize Timer 0 for interrupts
;
Timer0Setup:
in      r16, _(TCCR0B)
ori     r16, (1 << CS02) | (1 << CS00) ; divide by 1024
out     _(TCCR0B), r16      ; set timer clock
sbi     _(TIFR0), (1<< TOV0); clear interrupt flag
;


## Enabling interrupts¶

;
lds     r16, TIMSK0         ; get interrupt mask reg
ori     r16, (1 << TOIE0)   ; enable interrupts
sts     TIMSK0, r16
out     _(TCNT0), r1        ; zero the timer counter
sts     ISRcount, r1        ; and our counter variable
;
sei                         ; let the fun begin
ret


Once we get here, interrupts are at work!

## The handler code¶

Finally, we need our handler code:

;----------------------------------------------------------------------
; Timer0 overflow ISR
;

.global     TIMER0_OVF_vect
TIMER0_OVF_vect:
; save callers registers
push    r1
push    r0
in      r0, _(SREG)
push    r0
eor     r1, r1
push    r24
push    r25
;


This code protects the important registers in the chip, and any registers we plan on using in our code.

## Do the work¶

We let the handler toggle the LED on/off

        ; toggle LED port
in      r24, _(PORTB)       ; get current PORTD
ldi     r25, (1 << LED_PIN) ; LED bit position
eor     r24, r25            ; toggle bit
out     _(PORTB), r24       ; set back in place
sts     ISRcount, r1        ; sero the counter
1:


## Finishing up¶

Finally, we restore the system state

        ; recover user's registers
pop     r25
pop     r24
pop     r0
out     _(SREG), r0
pop     r0
pop     r1
reti


## Makefile¶

Here is the Makefile used to build this project

  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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 # source files TARGET := $(shell basename$(PWD)) CSRCS := $(wildcard *.c) COBJS :=$(CSRCS:.c=.o) SSRCS := $(wildcard *.S) SOBJS :=$(SSRCS:.S=.o) OBJS := $(COBJS)$(SOBJS) LST := $(TARGET).lst # define the processor here MCU := atmega328p FREQ := 16000000L # define the USB port on your system (this works on Linux) PORT := /dev/ttyACM0 PGMR := arduino # tools GCC := avr-gcc OBJDUMP := avr-objdump OBJCOPY := avr-objcopy DUDE := avrdude UFLAGS := -v -D -p$(MCU) -c$(PGMR) UFLAGS += -P$(PORT) UFLAGS += -b115200 CFLAGS := -c -Os -mmcu=$(MCU) CFLAGS += -DF_CPU=$(FREQ) LFLAGS += -mmcu=$(MCU) .PHONY all: all:$(TARGET).hex $(LST) # implicit build rules %.hex: %.elf$(OBJCOPY) -O ihex -R .eeprom $<$@ %.elf: $(OBJS)$(GCC) $(LFLAGS) -o$@ $^ %.o: %.c$(GCC) -c $(CFLAGS) -o$@ $^ %.o: %.S$(GCC) -c $(CFLAGS) -o$@ $< %.lst: %.elf$(OBJDUMP) -C -d $< >$@ # utility targets .PHONY: load load: $(DUDE)$(DUDECONF) $(UFLAGS) -Uflash:w:$(TARGET).hex:i .PHONY: clean clean: \$(RM) *.hex *.lst *.o *.elf 

## Wow - we are blinking fast¶

• Let’s try a simple trick.
• create a simple counter variable
• have the interrupt handler increment the counter each time it is called
• count up to 61, then trigger out LED toggle code.
• reset the counter as we toggle the LED and start over
• With any luck, we will end up with a blink every second.

## counter setup¶

We need a counter variable

        .section    .data

ISRcount:   .byte   0


THis was shown earlier.

We need to set the counter in the setup code

        sei                         ; let the fun begin
ret


In the handler, add this code to increment the counter

        ; bump the ISR counter
lds     r24, ISRcount       ; get current count
sts     ISRcount, r24       ; put it back
;


## Blinking only when the count is reached¶

        ; test the counter to see if we toggle the LED
lds     r24, ISRcount       ; needed?
cpi     r24, 61             ; one second is 61 interrupts
brcs    1f                  ; skip if not


The label is after the blink logic, just before we restore all the registers and end the handler.

## Resetting the count on toggle¶

• The last thing we do is reset the counter after toggling the LED
        eor     r24, r25            ; toggle bit
out     _(PORTB), r24       ; set back in place
sts     ISRcount, r1        ; sero the counter


That last line resets the counter for the next pass.

This now blinks (toggles) once per second!

This simple scheme to delay actions until some number of interrupts is seen is a simple mechanism to adjust when events are handled. We will use it later, when we explore a simple multi-tasking kernel for AVR projects.