Forum: Mikrocontroller und Digitale Elektronik STM32F103 Cortex M3 Assembler Interrupt Routine


von Max M. (maxmicr)


Lesenswert?

Guten Tag,

weiß jemand, wie man in Assembler für den STM32F103 eine Interrupt 
Routine definiert? Ich hab alle nötigen Bits in den Registern gesetzt 
und weiß auch die Adresse des Vectors, aber ich hab keine Idee wie ich 
in meinem Programm Code an diese Adresse komme, so dass ich von da in 
meine Funktion springen kann (sofern das überhaupt so funktioniert?).

Also bisher hab ich das an Code
1
    .thumb
2
    .syntax unified
3
4
    //Clock
5
    .equ RCC_APB1ENR, 0x4002101C
6
    .equ RCC_APB2ENR, 0x40021018
7
8
    //GPIO
9
    .equ GPIOA_CRL, 0x40010800
10
    .equ GPIOA_ODR, 0x4001080C
11
12
    //Timer2
13
    .equ TIM2_CR1,  0x40000000
14
    .equ TIM2_DIER, 0x4000000C
15
    .equ TIM2_SR,   0x40000010
16
    .equ TIM2_PSC,  0x40000028
17
    .equ TIM2_ARR,  0x4000002C
18
19
    //Interrupt for Timer2 (Interrupt Position 28)
20
    .equ NVIC_ISER0,    0xE000E100
21
    .equ NVIC_TIM2_POS, 0x8000000
22
23
main:
24
    //Enable Clock for GPIO Port A
25
    ldr r0,=RCC_APB2ENR
26
    mov r1,0b100
27
    str r1,[r0]
28
29
    //Setup GPIO Port A Pin 0 as Output
30
    ldr r0,=GPIOA_CRL
31
    mov r1,0b0011
32
    str r1,[r0]
33
34
    //Enable Clok for Timer2
35
    ldr r0,=RCC_APB1ENR
36
    mov r1,0b01
37
    str r1,[r0]
38
39
    //Set Prescaler
40
    ldr r0,=TIM2_PSC
41
    mov r1,0b00
42
    str r1,[r0]
43
44
    //Set Reload Value
45
    ldr r0,=TIM2_ARR
46
    mov r1,225
47
    str r1,[r0]
48
49
    //Enable Update Interrupt
50
    ldr r0,=TIM2_DIER
51
    mov r1,0b01
52
    str r1,[r0]
53
54
    //Enable Global Timer2 Interrupt
55
    ldr r0,=NVIC_ISER0
56
    ldr r1,=NVIC_TIM2_POS
57
    str r1,[r0]
58
59
    //Enable
60
    ldr r0,=TIM2_CR1
61
    mov r1,0b01
62
    str r1,[r0]
63
64
65
loop:
66
    //Set GPIO Port A Pin 0 high
67
    ldr r0,=GPIOA_ODR
68
    mov r1,0b01
69
    str r1,[r0]
70
71
    //Set GPIO Port A Pin 0 low
72
    ldr r0,=GPIOA_ODR
73
    mov r1,0b00
74
    str r1,[r0]
75
76
    b   loop
77
78
.global main

: Bearbeitet durch User
von Steff (Gast)


Lesenswert?

schau mal hier:

https://github.com/x893/STM32F103-DualCDC/tree/master/Libraries/CMSIS/Core/CM3/startup

das sind verschieden startups files für dem cm3.
Vielleicht bringt dich das weiter.

von Christopher J. (christopher_j23)


Lesenswert?

Max M. schrieb:
> .thumb

Das würde ich mal durch .thumb_func ersetzen und am besten direkt vor 
deine main: schreiben. Siehe auch 
Beitrag "Re: STM32F401 C-ISR durch Assembler-ISR ersetzen"
Genauso würde ich das .global main davor schreiben.

Max M. schrieb:
> Ich hab alle nötigen Bits in den Registern gesetzt
> und weiß auch die Adresse des Vectors, aber ich hab keine Idee wie ich
> in meinem Programm Code an diese Adresse komme, so dass ich von da in
> meine Funktion springen kann (sofern das überhaupt so funktioniert?).

Ich bin nicht ganz sicher ob ich dich richtig verstehe aber ich versuche 
mal mein bestes. Die Interrupt-Vektortabelle, die am Beginn des Flash 
liegt besteht im Prinzip aus Funktionszeigern, d.h. wenn du in C 
irgendwo eine Funktion TIM2_IRQHandler() definierst, dann wird der 
Linker dafür sorgen, dass im Startup-Code wo ein ".word TIM2_IRQHandler" 
steht am Ende die Adresse dieser Funktion landet, so das die Funktion 
vom Interrupt-Controller angesprungen werden kann. Willst du deine ISR 
nicht in C, sondern in ASM schreiben, dann musst du auch irgendwie dafür 
sorgen, dass der Linker nachher die Adresse der Funktion in die 
Vektortabelle packen kann. Dafür Sorgen kannst du mittels 
Assembler-Direktiven, d.h. wie deiner main() deklarierst du ein globales 
Symbol ".global TIM2_IRQHandler" und implementierst dann da in ASM deine 
Funktion.

von pegel (Gast)


Lesenswert?


von Max M. (maxmicr)


Lesenswert?

Christopher J. schrieb:
> Dafür Sorgen kannst du mittels
> Assembler-Direktiven, d.h. wie deiner main() deklarierst du ein globales
> Symbol ".global TIM2_IRQHandler" und implementierst dann da in ASM deine
> Funktion.

Ah, vielen Dank, das macht Sinn. Wie returne ich von dieser Funktion 
zurück zum letzten Befehl im laufenden Programm?

von pegel (Gast)


Lesenswert?

Max M. schrieb:
> Wie returne ich

Siehe in meinem ersten link


_EXTI_Line0:
......

von Jim M. (turboj)


Lesenswert?

Max M. schrieb:
> Ah, vielen Dank, das macht Sinn. Wie returne ich von dieser Funktion
> zurück zum letzten Befehl im laufenden Programm?

So wie immer bei function calls:
BX LR
oder

push {LR}
//... weitere befehle/function calls
POP {PC}

von Max M. (maxmicr)


Lesenswert?

Müsste das dann so passen?
1
.global TIM2_IRQHandler
2
TIM2_IRQHandler:
3
    //Set GPIO Port A Pin 0 high
4
    ldr r0,=GPIOA_ODR
5
    mov r1,0b01
6
    str r1,[r0]
7
8
    //Clear pending interrupt bit
9
    ldr r0,=TIM2_SR
10
    mov r1,0b00
11
    str r1,[r0]
12
13
    bx  lr

von Nop (Gast)


Lesenswert?

Max M. schrieb:
> Müsste das dann so passen?

Vielleicht noch R0 und R1 erst sichern und dann wieder herstellen. Außer 
wenn Du die in Deinem sonstigen Programm nicht benutzt natürlich.

von Jim M. (turboj)


Lesenswert?

Nop schrieb:
> Vielleicht noch R0 und R1 erst sichern und dann wieder herstellen.

Nö. Macht der Prozessor automagisch beim Interrupt Entry/Exit.

Lies Dir mal durch wie das genau funktioniert - es gelten für Interrupt 
Handler dieselben Regeln wie für normale C Funktionen.

von Max M. (maxmicr)


Lesenswert?

Hm, irgendwie klappt das nicht ganz:
1
    .syntax unified
2
3
    //Clock
4
    .equ RCC_APB1ENR, 0x4002101C
5
    .equ RCC_APB2ENR, 0x40021018
6
7
    //GPIO
8
    .equ GPIOA_CRL, 0x40010800
9
    .equ GPIOA_ODR, 0x4001080C
10
11
    //Timer2
12
    .equ TIM2_CR1,  0x40000000
13
    .equ TIM2_DIER, 0x4000000C
14
    .equ TIM2_SR,   0x40000010
15
    .equ TIM2_PSC,  0x40000028
16
    .equ TIM2_ARR,  0x4000002C
17
18
    //Interrupt for Timer2 (Interrupt Position 28)
19
    .equ NVIC_ISER1,    0xE000E100
20
    .equ NVIC_TIM2_POS, 0x8000000
21
22
    .thumb_func
23
.global main
24
main:
25
    //Enable Clock for GPIO Port A
26
    ldr r0,=RCC_APB2ENR
27
    mov r1,0b100
28
    str r1,[r0]
29
30
    //Setup GPIO Port A Pin 0 as Output
31
    ldr r0,=GPIOA_CRL
32
    mov r1,0b0011
33
    str r1,[r0]
34
35
    //Enable Clok for Timer2
36
    ldr r0,=RCC_APB1ENR
37
    mov r1,0b01
38
    str r1,[r0]
39
40
    //Set Prescaler
41
    ldr r0,=TIM2_PSC
42
    mov r1,0b00
43
    str r1,[r0]
44
45
    //Set Reload Value
46
    ldr r0,=TIM2_ARR
47
    mov r1,225
48
    str r1,[r0]
49
50
    //Enable Update Interrupt
51
    ldr r0,=TIM2_DIER
52
    mov r1,0b01
53
    str r1,[r0]
54
55
    //Enable Global Timer2 Interrupt
56
    ldr r0,=NVIC_ISER1
57
    ldr r1,=NVIC_TIM2_POS
58
    str r1,[r0]
59
60
    //Enable
61
    ldr r0,=TIM2_CR1
62
    mov r1,0b01
63
    str r1,[r0]
64
loop:
65
    nop
66
    b   loop
67
68
.global TIM2_IRQHandler
69
TIM2_IRQHandler:
70
    //Set GPIO Port A Pin 0 high
71
    ldr r0,=GPIOA_ODR
72
    mov r1,0b01
73
    str r1,[r0]
74
75
    //Clear pending interrupt bit
76
    ldr r0,=TIM2_SR
77
    mov r1,0b00
78
    str r1,[r0]
79
80
    bx  lr

Also der Timer-Wert wird hochgezählt (und entsprechend dem Reload-Wert 
resettet), aber ein Breakpoint im TIM2_IRQHandler wird nie erreicht. Ich 
denke, dass das am NVIC liegt.

Der TIM2 global interrupt hat die Position "28" (liegt also in 
NVIC_ISER0), also muss in diesem Register (ISER0) das 28. Bit gesetzt 
werden, die Funktion NVIC_EnableIRQ in C macht folgendes:
1
__STATIC_INLINE void NVIC_EnableIRQ(IRQn_Type IRQn)
2
{
3
  NVIC->ISER[((uint32_t)(IRQn) >> 5)] = (1 << ((uint32_t)(IRQn) & 0x1F)); /* enable interrupt */
4
}

Ich verstehe das *& 0x1F* nicht, kann mir das jemand erklären?

von Nop (Gast)


Lesenswert?

Jim M. schrieb:

> Lies Dir mal durch wie das genau funktioniert - es gelten für Interrupt
> Handler dieselben Regeln wie für normale C Funktionen.

Die gegebene Funktion ist aber nicht in C, sondern in Assembler.

von Nop (Gast)


Lesenswert?

Max M. schrieb:

> Ich verstehe das *& 0x1F* nicht, kann mir das jemand erklären?

Es stellt sicher, daß die 1 um maximal 31 geshiftet wird, selbst wenn 
man da ungültige IRQ-Nummern (d.h. größer als 31) übergibt.

von Christopher J. (christopher_j23)


Lesenswert?

Da fehlt meiner Meinung nach noch ein .thumb_func vor dem 
TIM2_IRQHandler. Wo landest du denn stattdessen? Zufällig im Hardfault?

von Max M. (maxmicr)


Lesenswert?

Christopher J. schrieb:
> Da fehlt meiner Meinung nach noch ein .thumb_func vor dem
> TIM2_IRQHandler.

Hm, hat leider auch nicht geholfen.
1
.global TIM2_IRQHandler
2
    .thumb_func
3
TIM2_IRQHandler:
4
    //Set GPIO Port A Pin 0 high
5
    ldr r0,=GPIOA_ODR
6
    mov r1,0b01
7
    str r1,[r0]
8
9
    //Clear pending interrupt bit
10
    ldr r0,=TIM2_SR
11
    mov r1,0b00
12
    str r1,[r0]
13
14
    bx  lr

Christopher J. schrieb:
> Wo landest du denn stattdessen?

Ich bleibe in der loop hängen.

von Nop (Gast)


Lesenswert?

Max M. schrieb:

>     //Enable Global Timer2 Interrupt
>     ldr r0,=NVIC_ISER1

> Der TIM2 global interrupt hat die Position "28" (liegt also in
> NVIC_ISER0), also muss in diesem Register (ISER0) das 28. Bit gesetzt
> werden,

Im Assemblerteil nimmst Du aber ISER1?

von Christopher J. (christopher_j23)


Lesenswert?

Nop schrieb:
> Im Assemblerteil nimmst Du aber ISER1?

Die Adresse die er angegeben hat entspricht aber ISER0, daran sollte es 
also nicht liegen.

Allerdings entspricht das 28. Bit 1<<28 bzw. 2^28 und das ist 0x10000000 
und nicht 0x8000000 (was 2^27 ist).

von Max M. (maxmicr)


Lesenswert?

Christopher J. schrieb:
> Allerdings entspricht das 28. Bit 1<<28 bzw. 2^28 und das ist 0x10000000
> und nicht 0x8000000 (was 2^27 ist).

Danke, das war das Problem. Jetzt klappt alles!

von Christopher J. (christopher_j23)


Lesenswert?

Nop schrieb:
> Jim M. schrieb:
>
>> Lies Dir mal durch wie das genau funktioniert - es gelten für Interrupt
>> Handler dieselben Regeln wie für normale C Funktionen.
>
> Die gegebene Funktion ist aber nicht in C, sondern in Assembler.

Das war auch mein erster Gedanke aber bei Interrupts sichert der 
Prozessor die Register tatsächlich automatisch, also immer dann wenn der 
Prozessor aus dem Thread- in den Handlermodus wechselt. Genauso werden 
sie dann beim Verlassen des Handlers wieder automatisch vom Stack 
geholt.

von Nop (Gast)


Lesenswert?

Christopher J. schrieb:

> Das war auch mein erster Gedanke aber bei Interrupts sichert der
> Prozessor die Register tatsächlich automatisch

Bei Exceptions ja, dafür hat man einen Exception-Stack. Bei Interrupts 
wäre mir das jetzt neu.

Zumal man den Threadmodus für Baremetal nicht wirklich gebrauchen kann. 
Der ergibt nur Sinn beim Einsatz eines RTOS.

von Christopher J. (christopher_j23)


Lesenswert?

Mit Threadmodus meine ich den "normalen" Modus in dem sich der Prozessor 
quasi ab Reset befindet, egal ob mit oder ohne RTOS und ja, die Register 
(wenn auch nicht alle) werden wirklich automatisch auf dem Stack 
gespeichert und nach ausführen der ISR wieder zurückgeholt, ohne das 
dafür eine einzige Instruktion nötig wäre. Konnte es selber nicht 
glauben und habe es ausprobiert. Ist aber tatsächlich so.

von Nop (Gast)


Lesenswert?

Das Programming Manual sagt das zwar nicht, nur für Exceptions (oder ich 
finde die Stelle für Interrupts nicht?!), aber bei näherem Nachdenken 
ist das logisch.

Die calling convention für C-Funktionen sagt ja, daß ein Teil der 
Register von den aufgerufenen Funktionen versaut werden darf (u.a. R0 
und R1), und wenn der Aufrufer die noch braucht, muß er die selber 
retten.

Da man zumindest beim GCC die Serviceroutinen für ISRs und Exceptions 
nicht speziell markieren muß, sehen die für den Compiler wie ganz 
normale C-Funktionen aus, d.h. er kann R0 und R1 nach Belieben benutzen.

Der Aufrufer, der sich dann um deren Sicherung kümmern muß, ist die CPU 
selber.

von Markus F. (mfro)


Lesenswert?

Nop schrieb:
> Bei Exceptions ja, dafür hat man einen Exception-Stack. Bei Interrupts
> wäre mir das jetzt neu.

Nop schrieb:
> Das Programming Manual sagt das zwar nicht, nur für Exceptions (oder ich
> finde die Stelle für Interrupts nicht?!), aber bei näherem Nachdenken
> ist das logisch.


In der ARM-Denke (und sonstwo eigentlich auch) ist "Interrupt" eine 
Untermenge von "Exception". Für Interrupts muß das also nicht extra 
irgendwo dokumentiert werden, wenn's für Exceptions schon geschehen ist.

von Nop (Gast)


Lesenswert?

Markus F. schrieb:

> In der ARM-Denke (und sonstwo eigentlich auch) ist "Interrupt" eine
> Untermenge von "Exception".

Dann ergibt's Sinn. Onwohl es mitunter auch umgedreht ist. So hatte ich 
mich einmal gewundert, wieso der Systick nicht ging, nachdem ich CPSIE d 
gemacht hatte - das sollte laut Manual ja die Interrupts sperren, 
während der Systick eine Exception ist.

Scheint also eigentlich dasselbe zu sein, mit dem Unterschied, daß 
Interrupts über den NVIC eingestellt werden müssen und Exceptions nicht. 
Allerdings geht's im Programming Manual beim NVIC dann auf einmal wieder 
mit Exceptions durcheinander.

Ich liebe es ja, wenn Doku verschiedene Begriffe für fast dasselbe 
gebraucht, keine klare Abgrenzung trifft und sie dann auch noch 
durcheinanderbringt.

Beitrag #7243821 wurde von einem Moderator gelöscht.
Beitrag #7243877 wurde von einem Moderator gelöscht.
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.