How does the compiler know the proper function to call when a certain interrupt occurs? using function attribute to handle interrupts.

Go To Last Post
10 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I am using the function attributes to handle interrupt in the Atmega32.

I write this

void __vector_1(void) __attribute__((signal));
void __vector_2(void) __attribute__((signal));
void __vector_3(void) __attribute__((signal));

I know that when external interrupt 0 happens, the function '__vector_1' gets called, when external interrupt 1 happens, the function '__vector_2' gets called, and when external interrupt 2 happens, the function '__vector_3' gets called.

The question is 

How does the compiler know that it should call the vector_1 function when the external interrupt 0 occurs, and call the vector_2 function when the external interrupt 1 occurs and so on ?

 

Thank you.

 

This topic has a solution.
Last Edited: Thu. Aug 22, 2019 - 04:51 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

This is related to another question: how does main() get called?

 

So, this is how it's done: Every CPU architecture has a default entry point where code starts execution after reset. For C programs to work, some kind of startup program needs to be in this area, which will take care of initialization stuff, including setting up the ISR vectors and calling main.

 

On avr-gcc, these files will usually be at (gcc-path)/avr/lib as precompiled libraries (crtXXXX.o or crtXXXX.a).

 

You can find the source code here (it's assembly code): http://svn.savannah.gnu.org/view...

 

edit: note that you can write your own startup code. If you are using avr-gcc, the default startup can be disabled with the -nostarfiles option, then you need to supply your custom startup code.

For an example of startup code written in C (not for classic AVR, but the new AVR-0/1 series) and how to use it, see pp. 15-17 of this appnote: https://www.microchip.com/wwwApp...

Last Edited: Tue. Aug 20, 2019 - 12:48 PM
This reply has been marked as the solution. 
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

As he says, it is this:

 

http://svn.savannah.gnu.org/viewvc/avr-libc/trunk/avr-libc/crt1/gcrt1.S?revision=2519&view=markup

 

specifically this:

 

42 .macro vector name
43 .if (. - __vectors < _VECTORS_SIZE)
44 .weak \name
45 .set \name, __bad_interrupt
46 XJMP \name
47 .endif
48 .endm

 

That is a macro to make a vector table entry with a JMP (RJMP) to __bad_interrupt but in which that jump target is "weak" so if something else makes a definition of the jump destination it will over-rule the jump. Then the file just does:

 

50 .section .vectors,"ax",@progbits
51 .global __vectors
52 .func __vectors
53 __vectors:
54 XJMP __init
55 vector __vector_1
56 vector __vector_2
57 vector __vector_3
58 vector __vector_4
59 vector __vector_5
60 vector __vector_6

...

179 vector __vector_125
180 vector __vector_126
181 vector __vector_127
182 .endfunc

 

and that continues on all  the way to a potential __vector_127 (on the basis that even the biggest ones don't have that many vectors).

 

Remember the test form the macro that does:

        .if (. - __vectors < _VECTORS_SIZE)

that ensures that the macro stops generating jumps once the right number for the particular AVR have been reached. 

 

So for ATmega32 you will find:

C:\Program Files (x86)\Atmel\Studio\7.0\toolchain\avr8\avr8-gnu-toolchain\avr\lib\avr5>avr-objdump -S crtatmega32.o

crtatmega32.o:     file format elf32-avr


Disassembly of section .text:

00000000 <__bad_interrupt>:
   0:   0c 94 00 00     jmp     0       ; 0x0 <__bad_interrupt>

Disassembly of section .vectors:

00000000 <__vectors>:
   0:   0c 94 00 00     jmp     0       ; 0x0 <__vectors>
   4:   0c 94 00 00     jmp     0       ; 0x0 <__vectors>
   8:   0c 94 00 00     jmp     0       ; 0x0 <__vectors>
   c:   0c 94 00 00     jmp     0       ; 0x0 <__vectors>
  10:   0c 94 00 00     jmp     0       ; 0x0 <__vectors>
  14:   0c 94 00 00     jmp     0       ; 0x0 <__vectors>
  18:   0c 94 00 00     jmp     0       ; 0x0 <__vectors>
  1c:   0c 94 00 00     jmp     0       ; 0x0 <__vectors>
  20:   0c 94 00 00     jmp     0       ; 0x0 <__vectors>
  24:   0c 94 00 00     jmp     0       ; 0x0 <__vectors>
  28:   0c 94 00 00     jmp     0       ; 0x0 <__vectors>
  2c:   0c 94 00 00     jmp     0       ; 0x0 <__vectors>
  30:   0c 94 00 00     jmp     0       ; 0x0 <__vectors>
  34:   0c 94 00 00     jmp     0       ; 0x0 <__vectors>
  38:   0c 94 00 00     jmp     0       ; 0x0 <__vectors>
  3c:   0c 94 00 00     jmp     0       ; 0x0 <__vectors>
  40:   0c 94 00 00     jmp     0       ; 0x0 <__vectors>
  44:   0c 94 00 00     jmp     0       ; 0x0 <__vectors>
  48:   0c 94 00 00     jmp     0       ; 0x0 <__vectors>
  4c:   0c 94 00 00     jmp     0       ; 0x0 <__vectors>
  50:   0c 94 00 00     jmp     0       ; 0x0 <__vectors>

Disassembly of section .init2:

00000000 <.init2>:
   0:   11 24           eor     r1, r1
   2:   1f be           out     0x3f, r1        ; 63
   4:   c0 e0           ldi     r28, 0x00       ; 0
   6:   d0 e0           ldi     r29, 0x00       ; 0
   8:   de bf           out     0x3e, r29       ; 62
   a:   cd bf           out     0x3d, r28       ; 61

Disassembly of section .init9:

00000000 <.init9>:
   0:   0e 94 00 00     call    0       ; 0x0 <.init9>
   4:   0c 94 00 00     jmp     0       ; 0x0 <.init9>

You can see the vector table in that. At present all CALL and JMP destinations are 0 as this code is "unlinked". But if you build the shortest of C programs:

#include <avr/io.h>

int main() {
	while(1) {
	}
}

then you get:

Disassembly of section .text:

00000000 <__vectors>:
   0:	0c 94 2a 00 	jmp	0x54	; 0x54 <__ctors_end>
   4:	0c 94 34 00 	jmp	0x68	; 0x68 <__bad_interrupt>
   8:	0c 94 34 00 	jmp	0x68	; 0x68 <__bad_interrupt>
   c:	0c 94 34 00 	jmp	0x68	; 0x68 <__bad_interrupt>
  10:	0c 94 34 00 	jmp	0x68	; 0x68 <__bad_interrupt>
  14:	0c 94 34 00 	jmp	0x68	; 0x68 <__bad_interrupt>
  18:	0c 94 34 00 	jmp	0x68	; 0x68 <__bad_interrupt>
  1c:	0c 94 34 00 	jmp	0x68	; 0x68 <__bad_interrupt>
  20:	0c 94 34 00 	jmp	0x68	; 0x68 <__bad_interrupt>
  24:	0c 94 34 00 	jmp	0x68	; 0x68 <__bad_interrupt>
  28:	0c 94 34 00 	jmp	0x68	; 0x68 <__bad_interrupt>
  2c:	0c 94 34 00 	jmp	0x68	; 0x68 <__bad_interrupt>
  30:	0c 94 34 00 	jmp	0x68	; 0x68 <__bad_interrupt>
  34:	0c 94 34 00 	jmp	0x68	; 0x68 <__bad_interrupt>
  38:	0c 94 34 00 	jmp	0x68	; 0x68 <__bad_interrupt>
  3c:	0c 94 34 00 	jmp	0x68	; 0x68 <__bad_interrupt>
  40:	0c 94 34 00 	jmp	0x68	; 0x68 <__bad_interrupt>
  44:	0c 94 34 00 	jmp	0x68	; 0x68 <__bad_interrupt>
  48:	0c 94 34 00 	jmp	0x68	; 0x68 <__bad_interrupt>
  4c:	0c 94 34 00 	jmp	0x68	; 0x68 <__bad_interrupt>
  50:	0c 94 34 00 	jmp	0x68	; 0x68 <__bad_interrupt>

00000054 <__ctors_end>:
  54:	11 24       	eor	r1, r1
  56:	1f be       	out	0x3f, r1	; 63
  58:	cf e5       	ldi	r28, 0x5F	; 95
  5a:	d8 e0       	ldi	r29, 0x08	; 8
  5c:	de bf       	out	0x3e, r29	; 62
  5e:	cd bf       	out	0x3d, r28	; 61
  60:	0e 94 36 00 	call	0x6c	; 0x6c <main>
  64:	0c 94 37 00 	jmp	0x6e	; 0x6e <_exit>

00000068 <__bad_interrupt>:
  68:	0c 94 00 00 	jmp	0	; 0x0 <__vectors>

0000006c <main>:
#include <avr/io.h>

int main() {
  6c:	ff cf       	rjmp	.-2      	; 0x6c <main>

0000006e <_exit>:
  6e:	f8 94       	cli

00000070 <__stop_program>:
  70:	ff cf       	rjmp	.-2      	; 0x70 <__stop_program>

that's because the contents of main.o (which was built from main.c) and the crtatmega32.o have now been linked together (so all the CALL and JMP have final destination addresses).

 

There are 20 entries in the vector table because:

#define _VECTORS_SIZE 84

So the macro that could potentially generate 127 vectors has stopped when it reached address 84 (0x0054)

 

If an interrupt vector is now added:

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

int main() {
	while(1) {
	}
}

ISR(ADC_vect) {
	PORTB = 0x55;	
}

then the iom32.h contains:

/* ADC Conversion Complete */
#define ADC_vect_num		16
#define ADC_vect			_VECTOR(16)
#define SIG_ADC				_VECTOR(16)

so this will set vector 16 which can be seen:

00000000 <__vectors>:
   0:	0c 94 2a 00 	jmp	0x54	; 0x54 <__ctors_end>
   4:	0c 94 34 00 	jmp	0x68	; 0x68 <__bad_interrupt>
   8:	0c 94 34 00 	jmp	0x68	; 0x68 <__bad_interrupt>
   c:	0c 94 34 00 	jmp	0x68	; 0x68 <__bad_interrupt>
  10:	0c 94 34 00 	jmp	0x68	; 0x68 <__bad_interrupt>
  14:	0c 94 34 00 	jmp	0x68	; 0x68 <__bad_interrupt>
  18:	0c 94 34 00 	jmp	0x68	; 0x68 <__bad_interrupt>
  1c:	0c 94 34 00 	jmp	0x68	; 0x68 <__bad_interrupt>
  20:	0c 94 34 00 	jmp	0x68	; 0x68 <__bad_interrupt>
  24:	0c 94 34 00 	jmp	0x68	; 0x68 <__bad_interrupt>
  28:	0c 94 34 00 	jmp	0x68	; 0x68 <__bad_interrupt>
  2c:	0c 94 34 00 	jmp	0x68	; 0x68 <__bad_interrupt>
  30:	0c 94 34 00 	jmp	0x68	; 0x68 <__bad_interrupt>
  34:	0c 94 34 00 	jmp	0x68	; 0x68 <__bad_interrupt>
  38:	0c 94 34 00 	jmp	0x68	; 0x68 <__bad_interrupt>
  3c:	0c 94 34 00 	jmp	0x68	; 0x68 <__bad_interrupt>
  40:	0c 94 37 00 	jmp	0x6e	; 0x6e <__vector_16>
  44:	0c 94 34 00 	jmp	0x68	; 0x68 <__bad_interrupt>
  48:	0c 94 34 00 	jmp	0x68	; 0x68 <__bad_interrupt>
  4c:	0c 94 34 00 	jmp	0x68	; 0x68 <__bad_interrupt>
  50:	0c 94 34 00 	jmp	0x68	; 0x68 <__bad_interrupt>

so the original weak link as the target for the JMP in the ADC vector location has been changed into a hard link to the now provided __vector_16: code which can be seen at:

0000006e <__vector_16>:

ISR(ADC_vect) {
  6e:	1f 92       	push	r1
  70:	0f 92       	push	r0
  72:	0f b6       	in	r0, 0x3f	; 63
  74:	0f 92       	push	r0
  76:	11 24       	eor	r1, r1
  78:	8f 93       	push	r24
	PORTB = 0x55;	
  7a:	85 e5       	ldi	r24, 0x55	; 85
  7c:	88 bb       	out	0x18, r24	; 24
  7e:	8f 91       	pop	r24
  80:	0f 90       	pop	r0
  82:	0f be       	out	0x3f, r0	; 63
  84:	0f 90       	pop	r0
  86:	1f 90       	pop	r1
  88:	18 95       	reti

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

El Tangas wrote:

This is related to another question: how does main() get called?

 

So, this is how it's done: Every CPU architecture has a default entry point where code starts execution after reset. For C programs to work, some kind of startup program needs to be in this area, which will take care of initialization stuff, including setting up the ISR vectors and calling main.

 

On avr-gcc, these files will usually be at (gcc-path)/avr/lib as precompiled libraries (crtXXXX.o or crtXXXX.a).

 

You can find the source code here (it's assembly code): http://svn.savannah.gnu.org/view...

 

edit: note that you can write your own startup code. If you are using avr-gcc, the default startup can be disabled with the -nostarfiles option, then you need to supply your custom startup code.

For an example of startup code written in C (not for classic AVR, but the new AVR-0/1 series) and how to use it, see pp. 15-17 of this appnote: https://www.microchip.com/wwwApp...

 

 

Does the startup code have some portion that makes this syntax

 

void __vector_x(void) __attribute__((signal));

 

refer to the vector number x in the vector table ?

Does this portion of code in the startup code make the compiler call __vector_1 function when external interrupt 0 occurs ? 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I just showed you exactly how something like ADCvect gets linked.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

clawson wrote:
I just showed you exactly how something like ADCvect gets linked.

 

It is a little advanced for me to understand it, I've learnt this __vector_x method from someone's code, but I didn't study it in detail.

I need only a basic understanding without details, the details come next.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

AhmedH wrote:

Does the startup code have some portion that makes this syntax

 

void __vector_x(void) __attribute__((signal));

 

refer to the vector number x in the vector table ?

Does this portion of code in the startup code make the compiler call __vector_1 function when external interrupt 0 occurs ? 

 

Read #3 very carefully, the info is there.

 

However, I will add this: the execution of ISRs is done by the CPU automatically, so for example, if it detects interrupt 1, the CPU will automatically jump to the corresponding address (if interrupts are enabled). The compiler is not involved in this step.

 

So the compiler has to make sure there is something to be executed at that address. This is where the start file comes into play, it places a jump at each ISR address to the default ISR handler called  __bad_interrupt... well, I'll not repeat post #3. Just go read it.

 

edit: 

AhmedH wrote:

It is a little advanced for me to understand it, I've learnt this __vector_x method from someone's code, but I didn't study it in detail.

I need only a basic understanding without details, the details come next.

 

Yea, this is advanced, but your question requires this kind of answer, I don't see how it can be simplified?

Last Edited: Tue. Aug 20, 2019 - 02:59 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

AhmedH wrote:
I need only a basic understanding without details, the details come next.
Then what is the point of this thread? The user manual is pretty clear about how to do ISRs with avr-gcc\AVR-LibC:

 

https://www.nongnu.org/avr-libc/user-manual/group__avr__interrupts.html

 

The only thing you need to know are the _vect names for the chip you are using. For mega32 they are:

C:\Program Files (x86)\Atmel\Studio\7.0\toolchain\avr8\avr8-gnu-toolchain\avr\include\avr>grep _VECTOR( iom32.h | grep _vect
#define INT0_vect                       _VECTOR(1)
#define INT1_vect                       _VECTOR(2)
#define INT2_vect                       _VECTOR(3)
#define TIMER2_COMP_vect                _VECTOR(4)
#define TIMER2_OVF_vect                 _VECTOR(5)
#define TIMER1_CAPT_vect                _VECTOR(6)
#define TIMER1_COMPA_vect               _VECTOR(7)
#define TIMER1_COMPB_vect               _VECTOR(8)
#define TIMER1_OVF_vect                 _VECTOR(9)
#define TIMER0_COMP_vect                _VECTOR(10)
#define TIMER0_OVF_vect                 _VECTOR(11)
#define SPI_STC_vect                    _VECTOR(12)
#define USART_RXC_vect                  _VECTOR(13)
#define USART_UDRE_vect                 _VECTOR(14)
#define USART_TXC_vect                  _VECTOR(15)
#define ADC_vect                        _VECTOR(16)
#define EE_RDY_vect                     _VECTOR(17)
#define ANA_COMP_vect                   _VECTOR(18)
#define TWI_vect                        _VECTOR(19)
#define SPM_RDY_vect                    _VECTOR(20)

So if you wanted to use the timer 2 overflow vector you'd simply use:

ISR(TIMER2_OVF_vect) {
    // handle the event
}

That really is ALL you need to know about interrupts. Like I say most of these things are macros so "TIMER2_OVF_vect" really means:

_VECTOR(5)

and ISR() itself is:

#  define ISR(vector, ...)            \
    void vector (void) __attribute__ ((signal,__INTR_ATTRS)) __VA_ARGS__; \
    void vector (void)
#endif

while _VECTOR() is:

#ifndef _VECTOR
#define _VECTOR(N) __vector_ ## N
#endif

so that just turns _VECTOR(5) into the text string "__vector_5". Putting that all together means that:

ISR(TIMER2_OVF_vect) {
    // handle the event
}

becomes:

void __vector_5 (void) __attribute__ ((signal,used, externally_visible)) ; void __vector_5 (void) 
{
 
}

so that defines a void (*)(void) called __vector_5 and then this all links back to my earlier post as the predefined vectors have a JMP to __vector_N as a strong link with a weak link to __bad_interrupt. If you don't define a function called __vector_N for each vector then the JUMP retains its weak target of __bad_interrupt but if you define one or more __vector_N() functions then those are used to over-ride the weak links.

 

But the bottom line is that the avr-gcc authors were clever - they have put in place a "hidden" mechanism that no normal user needs to know about or understand. Users can just use:

ISR(SOME_vect) {

}

and as long as it's one of the entries in the .h list then it will make a hard link to the jump vector.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thank you, it seems that I should just use the ISR(INT0_vect) method in this stage. The details are confusing for me now, but they will be clear later.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

AhmedH wrote:
but they will be clear later.
I wouldn't worry about it EVER. The fact is it "just works". It's like when you call on your phone. There's some pretty advanced technology going on that relays your voice to the recipients ear and yet no one really cares about the detail - the point is it just works. Same goes for something like printf(). We all use it - no one generally cares how it works internally.

 

(except that I am a bit anal and love to pull technology to pieces!)