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
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.
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?
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}
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.
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.
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:
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.
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.
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?
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).
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!
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.
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.
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.
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.
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.
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.