Path: Home => AVR overview => Interrupt programming => vector table
Interrupt AVR assembler tutorial logo

The interrupt vector table


Here is all you need to know about the interrupt vector table: what it does, how you should use it and what can be done wrong.

What is a vector table?

First of all: forget the terms "vector" and "table" for a while, those are confusing words that generate wrong expectations.

Imagine all internal hardware compounds of an AVR have a mechanism that enables those to signal if a certain condition occurs, such as From this selection you already see that there are many different conditions that might be used, and there are even more the more internal hardware is on board of the AVR. The many different instances need a place where they can automatically jump to if that condition occurs.

The place where to jump to is at address 1 or 2 of the AVR, plus a specific adder of 1/2, 3/4, etc., for each interrupt type. At that location, a jump to the correct interrupt handler routine has to be placed. In smaller AVRs an RJMP will be placed there, requiring one instruction word. In larger AVRs a JMP instruction with 2 instruction words has to be placed there. That is why some use addresses 1, 2 or 3 for those relative jumps, others use 2, 4 or 6 for these absolute jumps.

The jump table starts at 1 or 2 because the address 0 is a special location: here, the AVR starts execution if its operating voltage is first applied or whenever the RESET input pin goes up. This address needs to jump to the location where the AVR starts its work.

How to use it

So the first address locations of an ATtiny13's program always look like this:

.cseg ; Code segment to assemble to
.org 0 ; Make sure that this starts at address zero
  rjmp Main ; Reset, jump to init code
  rjmp Int0Isr ; INT0 interrupt, jump to the INT0 service routine
  reti ; PCINT0 interrupt, not used, return from interrupt
  rjmp TC0OvflwIsr ; TC0 overflow interrupt, jump to the TC0 overflow routine
  reti ; EEREADY interrupt, not used, return from interrupt
  reti ; Analaog comparer interrupt, not used, return from interrupt
  rjmp TC0CmpAIsr ; TC0 compare A interrupt, jump to TC0 compare A routine
  jrmp TC0CmpBIsr ; TC0 compare B interrupt, jump to TC0 compare B routine
  reti ; Watchdog timer interrupt, not used, return from interrupt
  reti ; AD converter interrupt, not used, return from interrupt
; End of Reset- and Interrupt locations
; Interrupt service routines:
Int0Isr:
  ; INT0 interrupt jumps here
  ; do something here
  reti ; Finished INT0 interrupt
TC0OvflwIsr:
  ; TC0 overflow jumps here
  ; do something else here
  reti ; Finished TC0 overflow interrupt
TC0CmpAIsr:
  ; TC0 compare A jumps here
  ; do something else here
  reti ; Finished TC0 compare A interrupt
TC0CmpBIsr:
  ; TC0 compare B jumps here
  ; do something else here
  reti ; Finished TC0 compare B interrupt
;
; The main program init
Main:
  ; CPU starts here after a reset
  ; do something else here
  ; ...

Of course, if you do not need the compare A interrupt for TC0, just replace its jmump table entry with a RETI and you do not need the respective interrupt service routine. The same if you need the ADC interrupt: replace the reti by an rjmp to the added service routine.

If you write that program for an ATmega16, you'll have to know that this has a two-instruction jump. So instead of rjmp Main you'll have to write jmp Main, and the same for all rjmps in the jump list. But what to write instead of reti, which is a single-word-instruction? Replace those by reti followed by an nop. That ensures that the AVR finds the interrupt jump address at its right location in flash memory.

But that is not all: the interrupt jump list of an ATmega16 is completely different from that of an ATtiny13. There are many more interrupt jumps, and their rowing is completely different. So better start with a new interrupt jump list, and derive it from the databook's interrupt jump list, e.g. like that:

  ; Reset- and interrupt jump list ATmega16
  jmp Main ; Reset address 0, jump to Main init
  reti ; INT0 interrupt
  nop
  reti ; INT1 interrupt
  nop
  reti ; TC2 compare match interrupt
  nop
  reti ; TC2 overflow interrupt
  nop
  reti ; TC1 capture interrupt
  nop
  reti ; TC1 compare A interrupt
  nop
  reti ; TC1 compare B interrupt
  nop
  reti ; TC0 overflow interrupt
  nop
  reti ; SPI/STC interrupt
  nop
  reti ; USART RXC interrupt
  nop
  reti ; USART UDRE interrupt
  nop
  reti ; ADC conversion complete interrupt
  nop
  reti ; EE ready interrupt
  nop
  reti ; Analog comparer interrupt
  nop
  reti ; TWI serial interrupt
  nop
  reti ; INT2 interrupt
  nop
  reti ; TC0 compare interrupt
  nop
  reti ; SPM ready interrupt
  nop



As you can see, the number of interrupt jumps is much larger than in an ATtiny13. And that the rows are completely different. See the table in this document if you want to see more AVR's jump lists.

And: the jump table list holds also another information. What if the compare values A and B are equal and both interrupts are enabled? Now, the interrupt request that is higher in the jump list comes first. As this is always A, the interrupt on B seems to be ignored while A is executed. But immediately after interrupt service routine terminates with reti, the B routine executes. But only if no other interrupt request with a higher priority - up in the list - is waiting. For that, any interrupt has a flag that is set whenever a request happens. That bit is automatically cleared if the interrupt is actually handled.

Now back to the terms "vector" and "table". The term "vector" means nothing more than that each interrupt has ist specific address where it jumps to. In other controllers a displacement is added to a variable base address, and this displacement was named "vector". It could as well be named as "int-adder", but for whatever reason it was named "vector". But AVRs base address is always zero (and cannot be altered) and AVRs are not adding something, they simply jump to a fixed address. In that view the term "vector" makes no sense in AVRs, but as anyone uses it (including ATMEL) we'll have to use it, too.

The same with the term "table". This is an equally misleading term as it is not a table where something is read or picked from but it is a list of jump instructions. So whenever someone uses the term "table": he is only kidding and wants to confuse you. This is a list of sorted rjmp or jmp instructions, sorted by interrupt priority.

How not to use it

Very often one can see the following (misuse) of a vector table:
   rjmp Main ; Reset, jump to init
.org $0001
   rjmp Int0Sr ; External interrupt INT0, jump to service routine
.org $0003
   rjmp IntTc0OvflwSr ; TC0 overflow interrupt, jump to routine
That works and is fine, if you exactly know what you do. But where are all the other jumps? What if an error in interrupt enabling is made and the controller jumps to a different location? Now, completely unpredictable things will happen then, depending from the errorneous vector address.

A construction like this places the rjmps to their correct locations but leaves the space in between empty. "Empty" means in that case that there are instruction words 0xFFFF in these locations. These are the same as 0x0000 (nop) and the controller just executes the next word.

If the following code are interrupt service routines those are executed erroneously. If the main init init follows, the controller restarts all over again.

Do not do that, it is too risky.

Another argument, that the software runs on different AVRs if you use interrupt addresses instead of fixed addresses, is also very risky. In several cases ATMEL decided to change the rows of interrupts. In that case an error message of the assembler occurs (".org directive to smaller PC value!"), so that the row can be changed.

Therefore the following rules:

To the top of that page

©2009-2019 by http://www.avr-asm-tutorial.net